From 25b437c183d302c09333358a1fe9b6bc09a26b9a Mon Sep 17 00:00:00 2001 From: Matteo Bigoi <1781140+crisidev@users.noreply.github.com> Date: Tue, 23 Jul 2024 01:36:24 +0200 Subject: [PATCH] feat: add support for DSCP and TTL / Hop Limit (#2425) * feat: add support for DSCP and TTL / Hop Limit * Support IP_RECVTTL and IPV6_RECVHOPLIMIT socket options and related control messages for recvmsg. * Support setting DSCP in control messages for both sendmsg and recvmsg. Signed-off-by: Bigo <1781140+crisidev@users.noreply.github.com> * Add PR changelog * This is not supported on Freebsd * IPV6_RECVTCLASS not supported on freebsd * Properly limit IPV6_RECVTCLASS * Properly limit IP_TOS * Restrict everything to target_os linux * ... * Protect bind * Apply suggestions from code review Co-authored-by: SteveLauC * Address PR comments * Run cargo fmt * Address further comments from PR * Run tests under qemu * Use libc from git * Disable qemu IPTOS / IPV6TCLASS tests on mips * Apply suggestions from code review Co-authored-by: SteveLauC * Fix more code review suggestions * Fix missing renames in tests * Testing * Fixes * Fix freebsd * Trigger CI again * Trigger CI again * Use the same control message in linux and freebsd for ipv4ttl * test: remove a println --------- Signed-off-by: Bigo <1781140+crisidev@users.noreply.github.com> Co-authored-by: SteveLauC --- Cargo.toml | 2 +- changelog/2425.added.md | 2 + src/sys/socket/mod.rs | 155 +++++++++++++++++++++++++++++++- src/sys/socket/sockopt.rs | 48 +++++++++- test/sys/test_socket.rs | 185 ++++++++++++++++++++++++++++++++++++++ test/sys/test_sockopt.rs | 122 ++++++++++++++++++++++++- 6 files changed, 507 insertions(+), 7 deletions(-) create mode 100644 changelog/2425.added.md diff --git a/Cargo.toml b/Cargo.toml index 8382b01c3a..0629d095c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ targets = [ ] [dependencies] -libc = { version = "0.2.155", features = ["extra_traits"] } +libc = { git = "https://github.com/rust-lang/libc", branch = "libc-0.2", features = ["extra_traits"] } bitflags = "2.3.3" cfg-if = "1.0" pin-utils = { version = "0.1.0", optional = true } diff --git a/changelog/2425.added.md b/changelog/2425.added.md new file mode 100644 index 0000000000..96afad4378 --- /dev/null +++ b/changelog/2425.added.md @@ -0,0 +1,2 @@ +Improve support for extracting the TTL / Hop Limit from incoming packets +and support for DSCP (ToS / Traffic Class). diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index cb7618a932..bc49f7897a 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -769,6 +769,43 @@ pub enum ControlMessageOwned { #[cfg_attr(docsrs, doc(cfg(feature = "net")))] Ipv6OrigDstAddr(libc::sockaddr_in6), + /// Time-to-Live (TTL) header field of the incoming IPv4 packet. + /// + /// [Further reading](https://www.man7.org/linux/man-pages/man7/ip.7.html) + #[cfg(linux_android)] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Ttl(i32), + + /// Time-to-Live (TTL) header field of the incoming IPv4 packet. + /// + /// [Further reading](https://datatracker.ietf.org/doc/html/rfc3542.html) + #[cfg(target_os = "freebsd")] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Ttl(u8), + + /// Hop Limit header field of the incoming IPv6 packet. + /// + /// [Further reading for Linux](https://www.man7.org/linux/man-pages/man7/ip.7.html) + /// [Further reading for FreeBSD](https://datatracker.ietf.org/doc/html/rfc3542.html) + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6HopLimit(i32), + + /// Retrieve the DSCP (ToS) header field of the incoming IPv4 packet. + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Tos(u8), + + /// Retrieve the DSCP (Traffic Class) header field of the incoming IPv6 packet. + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6TClass(i32), + /// UDP Generic Receive Offload (GRO) allows receiving multiple UDP /// packets from a single sender. /// Fixed-size payloads are following one by one in a receive buffer. @@ -987,6 +1024,42 @@ impl ControlMessageOwned { let content_type = unsafe { ptr::read_unaligned(p as *const u8) }; ControlMessageOwned::TlsGetRecordType(content_type.into()) }, + #[cfg(linux_android)] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_TTL) => { + let ttl = unsafe { ptr::read_unaligned(p as *const i32) }; + ControlMessageOwned::Ipv4Ttl(ttl) + }, + #[cfg(target_os = "freebsd")] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVTTL) => { + let ttl: u8 = unsafe { ptr::read_unaligned(p as *const u8) }; + ControlMessageOwned::Ipv4Ttl(ttl) + }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_HOPLIMIT) => { + let ttl = unsafe { ptr::read_unaligned(p as *const i32) }; + ControlMessageOwned::Ipv6HopLimit(ttl) + }, + #[cfg(linux_android)] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_TOS) => { + let tos = unsafe { ptr::read_unaligned(p as *const u8) }; + ControlMessageOwned::Ipv4Tos(tos) + }, + #[cfg(target_os = "freebsd")] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVTOS) => { + let tos = unsafe { ptr::read_unaligned(p as *const u8) }; + ControlMessageOwned::Ipv4Tos(tos) + }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_TCLASS) => { + let tc = unsafe { ptr::read_unaligned(p as *const i32) }; + ControlMessageOwned::Ipv6TClass(tc) + }, (_, _) => { let sl = unsafe { std::slice::from_raw_parts(p, len) }; let ucmsg = UnknownCmsg(*header, Vec::::from(sl)); @@ -1124,6 +1197,18 @@ pub enum ControlMessage<'a> { #[cfg_attr(docsrs, doc(cfg(feature = "net")))] Ipv4SendSrcAddr(&'a libc::in_addr), + /// Configure the Time-to-Live for v4 traffic. + #[cfg(linux_android)] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Ttl(&'a libc::c_int), + + /// Configure the Time-to-Live for v4 traffic. + #[cfg(target_os = "freebsd")] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Ttl(&'a libc::c_uchar), + /// Configure the hop limit for v6 multicast traffic. /// /// Set the IPv6 hop limit for this message. The argument is an integer @@ -1138,9 +1223,9 @@ pub enum ControlMessage<'a> { Ipv6HopLimit(&'a libc::c_int), /// SO_RXQ_OVFL indicates that an unsigned 32 bit value - /// ancilliary msg (cmsg) should be attached to recieved + /// ancillary msg (cmsg) should be attached to received /// skbs indicating the number of packets dropped by the - /// socket between the last recieved packet and this + /// socket between the last received packet and this /// received packet. #[cfg(any(linux_android, target_os = "fuchsia"))] RxqOvfl(&'a u32), @@ -1152,6 +1237,22 @@ pub enum ControlMessage<'a> { /// page. #[cfg(target_os = "linux")] TxTime(&'a u64), + + /// Configure DSCP / IP TOS for outgoing v4 packets. + /// + /// Further information can be found [here](https://en.wikipedia.org/wiki/Differentiated_services). + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Tos(&'a u8), + + /// Configure DSCP / IPv6 TCLASS for outgoing v6 packets. + /// + /// Further information can be found [here](https://en.wikipedia.org/wiki/Differentiated_services). + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6TClass(&'a i32), } // An opaque structure used to prevent cmsghdr from being a public type @@ -1245,6 +1346,9 @@ impl<'a> ControlMessage<'a> { #[cfg(any(freebsdlike, netbsdlike))] #[cfg(feature = "net")] ControlMessage::Ipv4SendSrcAddr(addr) => addr as *const _ as *const u8, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Ttl(ttl) => ttl as *const _ as *const u8, #[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "haiku"))] #[cfg(feature = "net")] ControlMessage::Ipv6HopLimit(limit) => limit as *const _ as *const u8, @@ -1256,6 +1360,16 @@ impl<'a> ControlMessage<'a> { ControlMessage::TxTime(tx_time) => { tx_time as *const _ as *const u8 }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Tos(tos) => { + tos as *const _ + }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6TClass(tclass) => { + tclass as *const _ as *const u8 + }, }; unsafe { ptr::copy_nonoverlapping( @@ -1307,6 +1421,11 @@ impl<'a> ControlMessage<'a> { #[cfg(any(freebsdlike, netbsdlike))] #[cfg(feature = "net")] ControlMessage::Ipv4SendSrcAddr(addr) => mem::size_of_val(addr), + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Ttl(ttl) => { + mem::size_of_val(ttl) + }, #[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "haiku"))] #[cfg(feature = "net")] ControlMessage::Ipv6HopLimit(limit) => { @@ -1320,6 +1439,16 @@ impl<'a> ControlMessage<'a> { ControlMessage::TxTime(tx_time) => { mem::size_of_val(tx_time) }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Tos(tos) => { + mem::size_of_val(tos) + }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6TClass(tclass) => { + mem::size_of_val(tclass) + }, } } @@ -1347,6 +1476,9 @@ impl<'a> ControlMessage<'a> { #[cfg(any(freebsdlike, netbsdlike))] #[cfg(feature = "net")] ControlMessage::Ipv4SendSrcAddr(_) => libc::IPPROTO_IP, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Ttl(_) => libc::IPPROTO_IP, #[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "haiku"))] #[cfg(feature = "net")] ControlMessage::Ipv6HopLimit(_) => libc::IPPROTO_IPV6, @@ -1354,6 +1486,12 @@ impl<'a> ControlMessage<'a> { ControlMessage::RxqOvfl(_) => libc::SOL_SOCKET, #[cfg(target_os = "linux")] ControlMessage::TxTime(_) => libc::SOL_SOCKET, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Tos(_) => libc::IPPROTO_IP, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6TClass(_) => libc::IPPROTO_IPV6, } } @@ -1392,6 +1530,9 @@ impl<'a> ControlMessage<'a> { #[cfg(any(freebsdlike, netbsdlike))] #[cfg(feature = "net")] ControlMessage::Ipv4SendSrcAddr(_) => libc::IP_SENDSRCADDR, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Ttl(_) => libc::IP_TTL, #[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "haiku"))] #[cfg(feature = "net")] ControlMessage::Ipv6HopLimit(_) => libc::IPV6_HOPLIMIT, @@ -1403,6 +1544,16 @@ impl<'a> ControlMessage<'a> { ControlMessage::TxTime(_) => { libc::SCM_TXTIME }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Tos(_) => { + libc::IP_TOS + }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6TClass(_) => { + libc::IPV6_TCLASS + }, } } diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index 8df71a2778..7a88cff2d7 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -408,7 +408,7 @@ sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] /// Set or receive the Type-Of-Service (TOS) field that is /// sent with every IP packet originating from this socket - IpTos, + Ipv4Tos, Both, libc::IPPROTO_IP, libc::IP_TOS, @@ -418,13 +418,35 @@ sockopt_impl!( #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] - /// Traffic class associated with outgoing packets + /// If enabled, the IP_TOS ancillary message is passed with incoming packets. + IpRecvTos, + Both, + libc::IPPROTO_IP, + libc::IP_RECVTOS, + bool +); +#[cfg(any(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set the traffic class associated with outgoing packets. Ipv6TClass, Both, libc::IPPROTO_IPV6, libc::IPV6_TCLASS, libc::c_int ); +#[cfg(any(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// If enabled, the IPV6_TCLASS ancillary message is passed with incoming packets. + Ipv6RecvTClass, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_RECVTCLASS, + bool +); #[cfg(any(linux_android, target_os = "fuchsia"))] #[cfg(feature = "net")] sockopt_impl!( @@ -1058,6 +1080,17 @@ sockopt_impl!( libc::IP_TTL, libc::c_int ); +#[cfg(any(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] +sockopt_impl!( + /// Enables a receiving socket to retrieve the Time-to-Live (TTL) field + /// from incoming IPv4 packets. + Ipv4RecvTtl, + Both, + libc::IPPROTO_IP, + libc::IP_RECVTTL, + bool +); #[cfg(any(apple_targets, linux_android, target_os = "freebsd"))] sockopt_impl!( /// Set the unicast hop limit for the socket. @@ -1069,6 +1102,17 @@ sockopt_impl!( ); #[cfg(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "net")] +sockopt_impl!( + /// Enables a receiving socket to retrieve the Hop Limit field + /// (similar to TTL in IPv4) from incoming IPv6 packets. + Ipv6RecvHopLimit, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_RECVHOPLIMIT, + bool +); +#[cfg(any(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] /// The `recvmsg(2)` call will return the destination IP address for a UDP diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index 44a32aaf5e..afe5629142 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -2577,6 +2577,191 @@ fn test_recvmsg_rxq_ovfl() { assert_eq!(drop_counter, 1); } +#[cfg(any(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] +// qemu doesn't seem to be emulating this correctly in these architectures +#[cfg_attr( + all( + qemu, + any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6", + ) + ), + ignore +)] +#[test] +pub fn test_ip_tos_udp() { + use nix::sys::socket::ControlMessageOwned; + use nix::sys::socket::{ + bind, recvmsg, sendmsg, setsockopt, socket, sockopt, ControlMessage, + MsgFlags, SockFlag, SockType, SockaddrIn, + }; + + let sock_addr = SockaddrIn::from_str("127.0.0.1:6909").unwrap(); + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&rsock, sockopt::IpRecvTos, &true).unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); + + let sbuf = [0u8; 2048]; + let iov1 = [std::io::IoSlice::new(&sbuf)]; + + let mut rbuf = [0u8; 2048]; + let mut iov2 = [std::io::IoSliceMut::new(&mut rbuf)]; + let mut rcmsg = cmsg_space!(libc::c_int); + + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + setsockopt(&ssock, sockopt::Ipv4Tos, &20).unwrap(); + + // Test the sendmsg control message and check the received packet has the same TOS. + let scmsg = ControlMessage::Ipv4Tos(&20); + sendmsg( + ssock.as_raw_fd(), + &iov1, + &[scmsg], + MsgFlags::empty(), + Some(&sock_addr), + ) + .unwrap(); + + // TODO: this test is weak, but testing for the actual ToS value results in sporadic + // failures in CI where the ToS in the message header is not the one set by the + // sender, so for now the test only checks for the presence of the ToS in the message + // header. + let mut tc = None; + let recv = recvmsg::<()>( + rsock.as_raw_fd(), + &mut iov2, + Some(&mut rcmsg), + MsgFlags::empty(), + ) + .unwrap(); + for c in recv.cmsgs().unwrap() { + if let ControlMessageOwned::Ipv4Tos(t) = c { + tc = Some(t); + } + } + assert!(tc.is_some()); +} + +#[cfg(target_os = "linux")] +// qemu doesn't seem to be emulating this correctly in these architectures +#[cfg_attr( + all( + qemu, + any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6", + ) + ), + ignore +)] +#[cfg(feature = "net")] +#[test] +pub fn test_ipv6_tclass_udp() { + use nix::sys::socket::ControlMessageOwned; + use nix::sys::socket::{ + bind, recvmsg, sendmsg, setsockopt, socket, sockopt, ControlMessage, + MsgFlags, SockFlag, SockType, SockaddrIn6, + }; + + let std_sa = SocketAddrV6::from_str("[::1]:6902").unwrap(); + let sock_addr: SockaddrIn6 = SockaddrIn6::from(std_sa); + let rsock = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&rsock, sockopt::Ipv6RecvTClass, &true).unwrap(); + // This bind call with IPV6_RECVTCLASS fails on the Linux aarch64 target with EADDRNOTAVAIL, + // so the test will only run if `bind` does not return an error.. + if bind(rsock.as_raw_fd(), &sock_addr).is_ok() { + let sbuf = [0u8; 2048]; + let iov1 = [std::io::IoSlice::new(&sbuf)]; + + let mut rbuf = [0u8; 2048]; + let mut iov2 = [std::io::IoSliceMut::new(&mut rbuf)]; + let mut rcmsg = cmsg_space!(libc::c_int); + + let ssock = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + setsockopt(&ssock, sockopt::Ipv6TClass, &10).unwrap(); + + sendmsg( + ssock.as_raw_fd(), + &iov1, + &[], + MsgFlags::empty(), + Some(&sock_addr), + ) + .unwrap(); + + let mut tc = None; + let recv = recvmsg::<()>( + rsock.as_raw_fd(), + &mut iov2, + Some(&mut rcmsg), + MsgFlags::empty(), + ) + .unwrap(); + for c in recv.cmsgs().unwrap() { + if let ControlMessageOwned::Ipv6TClass(t) = c { + tc = Some(t); + } + } + assert_eq!(tc, Some(10)); + + let scmsg = ControlMessage::Ipv6TClass(&20); + sendmsg( + ssock.as_raw_fd(), + &iov1, + &[scmsg], + MsgFlags::empty(), + Some(&sock_addr), + ) + .unwrap(); + + let mut tc = None; + let recv = recvmsg::<()>( + rsock.as_raw_fd(), + &mut iov2, + Some(&mut rcmsg), + MsgFlags::empty(), + ) + .unwrap(); + for c in recv.cmsgs().unwrap() { + if let ControlMessageOwned::Ipv6TClass(t) = c { + tc = Some(t); + } + } + + assert_eq!(tc, Some(20)); + } +} + #[cfg(linux_android)] mod linux_errqueue { use super::FromStr; diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index 427e5854ec..fd055ef3dd 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -479,8 +479,8 @@ fn test_ip_tos() { ) .unwrap(); let tos = 0x80; // CS4 - setsockopt(&fd, sockopt::IpTos, &tos).unwrap(); - assert_eq!(getsockopt(&fd, sockopt::IpTos).unwrap(), tos); + setsockopt(&fd, sockopt::Ipv4Tos, &tos).unwrap(); + assert_eq!(getsockopt(&fd, sockopt::Ipv4Tos).unwrap(), tos); } #[test] @@ -877,3 +877,121 @@ fn test_reuseport_lb() { setsockopt(&fd, sockopt::ReusePortLb, &true).unwrap(); assert!(getsockopt(&fd, sockopt::ReusePortLb).unwrap()); } + +#[test] +#[cfg(any(linux_android, target_os = "freebsd"))] +fn test_ipv4_recv_ttl_opts() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::Ipv4RecvTtl, &true) + .expect("setting IP_RECVTTL on an inet stream socket should succeed"); + setsockopt(&fd, sockopt::Ipv4RecvTtl, &false) + .expect("unsetting IP_RECVTTL on an inet stream socket should succeed"); + let fdd = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fdd, sockopt::Ipv4RecvTtl, &true) + .expect("setting IP_RECVTTL on an inet datagram socket should succeed"); + setsockopt(&fdd, sockopt::Ipv4RecvTtl, &false).expect( + "unsetting IP_RECVTTL on an inet datagram socket should succeed", + ); +} + +#[test] +#[cfg(any(linux_android, target_os = "freebsd"))] +fn test_ipv6_recv_hop_limit_opts() { + let fd = socket( + AddressFamily::Inet6, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::Ipv6RecvHopLimit, &true).expect( + "setting IPV6_RECVHOPLIMIT on an inet6 stream socket should succeed", + ); + setsockopt(&fd, sockopt::Ipv6RecvHopLimit, &false).expect( + "unsetting IPV6_RECVHOPLIMIT on an inet6 stream socket should succeed", + ); + let fdd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fdd, sockopt::Ipv6RecvHopLimit, &true).expect( + "setting IPV6_RECVHOPLIMIT on an inet6 datagram socket should succeed", + ); + setsockopt(&fdd, sockopt::Ipv6RecvHopLimit, &false).expect( + "unsetting IPV6_RECVHOPLIMIT on an inet6 datagram socket should succeed", + ); +} + +#[test] +#[cfg(any(linux_android, target_os = "freebsd"))] +fn test_ipv4_recv_tos_opts() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::IpRecvTos, &true) + .expect("setting IP_RECVTOS on an inet stream socket should succeed"); + setsockopt(&fd, sockopt::IpRecvTos, &false) + .expect("unsetting IP_RECVTOS on an inet stream socket should succeed"); + let fdd = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fdd, sockopt::IpRecvTos, &true) + .expect("setting IP_RECVTOS on an inet datagram socket should succeed"); + setsockopt(&fdd, sockopt::IpRecvTos, &false).expect( + "unsetting IP_RECVTOS on an inet datagram socket should succeed", + ); +} + +#[test] +#[cfg(any(linux_android, target_os = "freebsd"))] +fn test_ipv6_recv_traffic_class_opts() { + let fd = socket( + AddressFamily::Inet6, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::Ipv6RecvTClass, &true).expect( + "setting IPV6_RECVTCLASS on an inet6 stream socket should succeed", + ); + setsockopt(&fd, sockopt::Ipv6RecvTClass, &false).expect( + "unsetting IPV6_RECVTCLASS on an inet6 stream socket should succeed", + ); + let fdd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fdd, sockopt::Ipv6RecvTClass, &true).expect( + "setting IPV6_RECVTCLASS on an inet6 datagram socket should succeed", + ); + setsockopt(&fdd, sockopt::Ipv6RecvTClass, &false).expect( + "unsetting IPV6_RECVTCLASS on an inet6 datagram socket should succeed", + ); +}