Repository: rust-lang/socket2 (rust)

Issue:
Support IPV6_TCLASS
`IPV6_TCLASS` is `IP_TOS` in IPv6. It is already defined in `libc` ([ref](https://docs.rs/libc/0.2.138/libc/constant.IPV6_TCLASS.html)).

A simple solution would be `pub(crate) use libc::IPV6_TCLASS;`. But I'm not sure how to correctly configure the cfg attribute.

Pull request description:
Add support for IPV6_TCLASS and IPV6_RECVTCLASS
A simple implementation of the IPV6_TCLASS and IPV6_RECVTCLASS modeled after the existing IP_TOS and IP_RECVTOS implementations.

close #363

Patch:
diff --git a/src/socket.rs b/src/socket.rs
index 24a1af5..0da99eb 100644
--- a/src/socket.rs
+++ b/src/socket.rs
@@ -1444,8 +1444,6 @@ impl Socket {
         target_os = "solaris",
     )))]
     pub fn set_recv_tos(&self, recv_tos: bool) -> io::Result<()> {
-        let recv_tos = i32::from(recv_tos);
-
         unsafe {
             setsockopt(
                 self.as_raw(),
@@ -1667,6 +1665,52 @@ impl Socket {
             )
         }
     }
+
+    /// Get the value of the `IPV6_RECVTCLASS` option for this socket.
+    ///
+    /// For more information about this option, see [`set_recv_tclass_v6`].
+    ///
+    /// [`set_recv_tclass_v6`]: Socket::set_recv_tclass_v6
+    #[cfg(not(any(
+        target_os = "dragonfly",
+        target_os = "fuchsia",
+        target_os = "illumos",
+        target_os = "netbsd",
+        target_os = "openbsd",
+        target_os = "redox",
+        target_os = "solaris",
+    )))]
+    pub fn recv_tclass_v6(&self) -> io::Result<bool> {
+        unsafe {
+            getsockopt::<c_int>(self.as_raw(), sys::IPPROTO_IPV6, sys::IPV6_RECVTCLASS)
+                .map(|recv_tclass| recv_tclass > 0)
+        }
+    }
+
+    /// Set the value of the `IPV6_RECVTCLASS` option for this socket.
+    ///
+    /// If enabled, the `IPV6_TCLASS` ancillary message is passed with incoming
+    /// packets. It contains a byte which specifies the traffic class field of
+    /// the packet header.
+    #[cfg(not(any(
+        target_os = "dragonfly",
+        target_os = "fuchsia",
+        target_os = "illumos",
+        target_os = "netbsd",
+        target_os = "openbsd",
+        target_os = "redox",
+        target_os = "solaris",
+    )))]
+    pub fn set_recv_tclass_v6(&self, recv_tclass: bool) -> io::Result<()> {
+        unsafe {
+            setsockopt(
+                self.as_raw(),
+                sys::IPPROTO_IPV6,
+                sys::IPV6_RECVTCLASS,
+                recv_tclass as c_int,
+            )
+        }
+    }
 }
 
 /// Socket options for TCP sockets, get/set using `IPPROTO_TCP`.
diff --git a/src/sys/unix.rs b/src/sys/unix.rs
index 972dfe1..cae633a 100644
--- a/src/sys/unix.rs
+++ b/src/sys/unix.rs
@@ -76,6 +76,16 @@ pub(crate) use libc::{
 #[cfg(not(target_os = "redox"))]
 pub(crate) use libc::{MSG_TRUNC, SO_OOBINLINE};
 // Used in `Socket`.
+#[cfg(not(any(
+    target_os = "dragonfly",
+    target_os = "fuchsia",
+    target_os = "illumos",
+    target_os = "netbsd",
+    target_os = "openbsd",
+    target_os = "redox",
+    target_os = "solaris",
+)))]
+pub(crate) use libc::IPV6_RECVTCLASS;
 #[cfg(all(feature = "all", not(target_os = "redox")))]
 pub(crate) use libc::IP_HDRINCL;
 #[cfg(not(any(
@@ -2057,6 +2067,91 @@ impl crate::Socket {
     pub fn detach_filter(&self) -> io::Result<()> {
         unsafe { setsockopt(self.as_raw(), libc::SOL_SOCKET, libc::SO_DETACH_FILTER, 0) }
     }
+
+    /// Get the value of the `IPV6_TCLASS` option for this socket.
+    ///
+    /// For more information about this option, see [`set_tclass_v6`].
+    ///
+    /// [`set_tclass_v6`]: Socket::set_tclass_v6
+    #[cfg(all(
+        feature = "all",
+        any(
+            target_os = "android",
+            target_os = "dragonfly",
+            target_os = "freebsd",
+            target_os = "fuchsia",
+            target_os = "linux",
+            target_os = "macos",
+            target_os = "netbsd",
+            target_os = "openbsd"
+        )
+    ))]
+    #[cfg_attr(
+        docsrs,
+        doc(cfg(all(
+            feature = "all",
+            any(
+                target_os = "android",
+                target_os = "dragonfly",
+                target_os = "freebsd",
+                target_os = "fuchsia",
+                target_os = "linux",
+                target_os = "macos",
+                target_os = "netbsd",
+                target_os = "openbsd"
+            )
+        )))
+    )]
+    pub fn tclass_v6(&self) -> io::Result<u32> {
+        unsafe {
+            getsockopt::<c_int>(self.as_raw(), IPPROTO_IPV6, libc::IPV6_TCLASS)
+                .map(|tclass| tclass as u32)
+        }
+    }
+
+    /// Set the value of the `IPV6_TCLASS` option for this socket.
+    ///
+    /// Specifies the traffic class field that is used in every packets
+    /// sent from this socket.
+    #[cfg(all(
+        feature = "all",
+        any(
+            target_os = "android",
+            target_os = "dragonfly",
+            target_os = "freebsd",
+            target_os = "fuchsia",
+            target_os = "linux",
+            target_os = "macos",
+            target_os = "netbsd",
+            target_os = "openbsd"
+        )
+    ))]
+    #[cfg_attr(
+        docsrs,
+        doc(cfg(all(
+            feature = "all",
+            any(
+                target_os = "android",
+                target_os = "dragonfly",
+                target_os = "freebsd",
+                target_os = "fuchsia",
+                target_os = "linux",
+                target_os = "macos",
+                target_os = "netbsd",
+                target_os = "openbsd"
+            )
+        )))
+    )]
+    pub fn set_tclass_v6(&self, tclass: u32) -> io::Result<()> {
+        unsafe {
+            setsockopt(
+                self.as_raw(),
+                IPPROTO_IPV6,
+                libc::IPV6_TCLASS,
+                tclass as c_int,
+            )
+        }
+    }
 }
 
 #[cfg_attr(docsrs, doc(cfg(unix)))]
diff --git a/src/sys/windows.rs b/src/sys/windows.rs
index 03e1a60..c9e57dd 100644
--- a/src/sys/windows.rs
+++ b/src/sys/windows.rs
@@ -69,12 +69,13 @@ pub(crate) type socklen_t = i32;
 pub(crate) use windows_sys::Win32::Networking::WinSock::IP_HDRINCL;
 pub(crate) use windows_sys::Win32::Networking::WinSock::{
     linger, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP, IPV6_MREQ as Ipv6Mreq,
-    IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, IPV6_UNICAST_HOPS, IPV6_V6ONLY,
-    IP_ADD_MEMBERSHIP, IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP,
-    IP_MREQ as IpMreq, IP_MREQ_SOURCE as IpMreqSource, IP_MULTICAST_IF, IP_MULTICAST_LOOP,
-    IP_MULTICAST_TTL, IP_RECVTOS, IP_TOS, IP_TTL, MSG_OOB, MSG_PEEK, SO_BROADCAST, SO_ERROR,
-    SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_RCVBUF, SO_RCVTIMEO, SO_REUSEADDR, SO_SNDBUF,
-    SO_SNDTIMEO, SO_TYPE, TCP_NODELAY,
+    IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF, IPV6_MULTICAST_LOOP, IPV6_RECVTCLASS,
+    IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP, IP_ADD_SOURCE_MEMBERSHIP,
+    IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP, IP_MREQ as IpMreq,
+    IP_MREQ_SOURCE as IpMreqSource, IP_MULTICAST_IF, IP_MULTICAST_LOOP, IP_MULTICAST_TTL,
+    IP_RECVTOS, IP_TOS, IP_TTL, MSG_OOB, MSG_PEEK, SO_BROADCAST, SO_ERROR, SO_KEEPALIVE, SO_LINGER,
+    SO_OOBINLINE, SO_RCVBUF, SO_RCVTIMEO, SO_REUSEADDR, SO_SNDBUF, SO_SNDTIMEO, SO_TYPE,
+    TCP_NODELAY,
 };
 pub(crate) const IPPROTO_IP: c_int = windows_sys::Win32::Networking::WinSock::IPPROTO_IP as c_int;
 pub(crate) const SOL_SOCKET: c_int = windows_sys::Win32::Networking::WinSock::SOL_SOCKET as c_int;


Test patch:
diff --git a/tests/socket.rs b/tests/socket.rs
index 54c8cd9..39838e4 100644
--- a/tests/socket.rs
+++ b/tests/socket.rs
@@ -902,7 +902,7 @@ fn sendfile() {
         assert_eq!(n, HELLO_WORLD.data.len());
 
         let mut buf = Vec::with_capacity(HELLO_WORLD.data.len() + 1);
-        let n = receiver.recv(spare_capacity_mut(&mut buf)).unwrap();
+        let n = receiver.recv(buf.spare_capacity_mut()).unwrap();
         assert_eq!(n, HELLO_WORLD.data.len());
         unsafe { buf.set_len(n) };
         assert_eq!(buf, HELLO_WORLD.data);
@@ -923,7 +923,7 @@ fn sendfile() {
         let mut buf = Vec::with_capacity(LOREM.data.len() + 1);
         let mut total = 0;
         while total < LOREM.data.len() {
-            let n = receiver.recv(spare_capacity_mut(&mut buf)).unwrap();
+            let n = receiver.recv(buf.spare_capacity_mut()).unwrap();
             unsafe { buf.set_len(buf.len() + n) };
             total += n;
         }
@@ -932,25 +932,6 @@ fn sendfile() {
     }
 }
 
-// TODO: use `Vec::spare_capacity_mut` once stable.
-#[cfg(all(
-    feature = "all",
-    any(
-        target_os = "android",
-        target_os = "freebsd",
-        target_os = "linux",
-        target_vendor = "apple",
-    )
-))]
-fn spare_capacity_mut(buf: &mut Vec<u8>) -> &mut [MaybeUninit<u8>] {
-    unsafe {
-        std::slice::from_raw_parts_mut(
-            buf.as_mut_ptr().add(buf.len()) as *mut MaybeUninit<u8>,
-            buf.capacity() - buf.len(),
-        )
-    }
-}
-
 #[cfg(all(
     feature = "all",
     any(
@@ -1218,6 +1199,33 @@ test!(IPv6 only_v6, set_only_v6(true));
 #[cfg(any(windows, target_os = "freebsd"))]
 test!(IPv6 only_v6, set_only_v6(false));
 
+#[cfg(all(
+    feature = "all",
+    any(
+        target_os = "android",
+        target_os = "dragonfly",
+        target_os = "freebsd",
+        target_os = "fuchsia",
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "netbsd",
+        target_os = "openbsd"
+    )
+))]
+test!(IPv6 tclass_v6, set_tclass_v6(96));
+
+#[cfg(not(any(
+    target_os = "dragonfly",
+    target_os = "fuchsia",
+    target_os = "illumos",
+    target_os = "netbsd",
+    target_os = "openbsd",
+    target_os = "redox",
+    target_os = "solaris",
+    target_os = "windows",
+)))]
+test!(IPv6 recv_tclass_v6, set_recv_tclass_v6(true));
+
 #[cfg(all(
     feature = "all",
     any(target_os = "android", target_os = "fuchsia", target_os = "linux")