Repository: rust-lang/socket2 (Rust)

Issue:
Support IP_HDRINCL socket option
I'd love to see the option to set `IP_HDRINCL` on RAW sockets!


Discussion:

Patch:
diff --git a/src/socket.rs b/src/socket.rs
index 30dcbc40..9c83475d 100644
--- a/src/socket.rs
+++ b/src/socket.rs
@@ -991,6 +991,44 @@ fn into_linger(duration: Option<Duration>) -> sys::linger {
 /// * Linux: <https://man7.org/linux/man-pages/man7/ip.7.html>
 /// * Windows: <https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options>
 impl Socket {
+    /// Get the value of the `IP_HDRINCL` option on this socket.
+    ///
+    /// For more information about this option, see [`set_header_included`].
+    ///
+    /// [`set_header_included`]: Socket::set_header_included
+    #[cfg(all(feature = "all", not(target_os = "redox")))]
+    #[cfg_attr(docsrs, doc(all(feature = "all", not(target_os = "redox"))))]
+    pub fn header_included(&self) -> io::Result<bool> {
+        unsafe {
+            getsockopt::<c_int>(self.as_raw(), sys::IPPROTO_IP, sys::IP_HDRINCL)
+                .map(|included| included != 0)
+        }
+    }
+
+    /// Set the value of the `IP_HDRINCL` option on this socket.
+    ///
+    /// If enabled, the user supplies an IP header in front of the user data.
+    /// Valid only for [`SOCK_RAW`] sockets; see [raw(7)] for more information.
+    /// When this flag is enabled, the values set by `IP_OPTIONS`, [`IP_TTL`],
+    /// and [`IP_TOS`] are ignored.
+    ///
+    /// [`SOCK_RAW`]: Type::RAW
+    /// [raw(7)]: https://man7.org/linux/man-pages/man7/raw.7.html
+    /// [`IP_TTL`]: Socket::set_ttl
+    /// [`IP_TOS`]: Socket::set_tos
+    #[cfg(all(feature = "all", not(target_os = "redox")))]
+    #[cfg_attr(docsrs, doc(all(feature = "all", not(target_os = "redox"))))]
+    pub fn set_header_included(&self, included: bool) -> io::Result<()> {
+        unsafe {
+            setsockopt(
+                self.as_raw(),
+                sys::IPPROTO_IP,
+                sys::IP_HDRINCL,
+                included as c_int,
+            )
+        }
+    }
+
     /// Get the value of the `IP_TRANSPARENT` option on this socket.
     ///
     /// For more information about this option, see [`set_ip_transparent`].
@@ -1016,7 +1054,7 @@ impl Socket {
     /// are routed through the TProxy box (i.e., the system
     /// hosting the application that employs the IP_TRANSPARENT
     /// socket option).  Enabling this socket option requires
-    /// superuser privileges (the CAP_NET_ADMIN capability).
+    /// superuser privileges (the `CAP_NET_ADMIN` capability).
     ///
     /// TProxy redirection with the iptables TPROXY target also
     /// requires that this option be set on the redirected socket.
diff --git a/src/sys/unix.rs b/src/sys/unix.rs
index aa4274e3..b2452a68 100644
--- a/src/sys/unix.rs
+++ b/src/sys/unix.rs
@@ -75,6 +75,8 @@ pub(crate) use libc::{
 #[cfg(not(target_os = "redox"))]
 pub(crate) use libc::{MSG_TRUNC, SO_OOBINLINE};
 // Used in `Socket`.
+#[cfg(all(feature = "all", not(target_os = "redox")))]
+pub(crate) use libc::IP_HDRINCL;
 #[cfg(not(any(
     target_os = "fuschia",
     target_os = "redox",
diff --git a/src/sys/windows.rs b/src/sys/windows.rs
index cd28f97d..b92fa221 100644
--- a/src/sys/windows.rs
+++ b/src/sys/windows.rs
@@ -66,6 +66,8 @@ pub(crate) use winapi::shared::ws2def::{
     IPPROTO_IP, SOL_SOCKET, SO_BROADCAST, SO_ERROR, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE,
     SO_RCVBUF, SO_RCVTIMEO, SO_REUSEADDR, SO_SNDBUF, SO_SNDTIMEO, SO_TYPE, TCP_NODELAY,
 };
+#[cfg(feature = "all")]
+pub(crate) use winapi::shared::ws2ipdef::IP_HDRINCL;
 pub(crate) use winapi::shared::ws2ipdef::{
     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,


Test patch:
diff --git a/tests/socket.rs b/tests/socket.rs
index c2d8c195..4ab875e0 100644
--- a/tests/socket.rs
+++ b/tests/socket.rs
@@ -1082,11 +1082,11 @@ macro_rules! test {
 
         let initial = socket.$get_fn().expect("failed to get initial value");
         let arg = $arg;
-        let expected = $expected;
         assert_ne!(initial, arg, "initial value and argument are the same");
 
         socket.$set_fn(arg).expect("failed to set option");
         let got = socket.$get_fn().expect("failed to get value");
+        let expected = $expected;
         assert_eq!(got, expected, "set and get values differ");
     };
 }
@@ -1174,3 +1174,27 @@ test!(
     tcp_user_timeout,
     set_tcp_user_timeout(Some(Duration::from_secs(10)))
 );
+
+#[test]
+#[cfg(all(feature = "all", not(target_os = "redox")))]
+fn header_included() {
+    let socket = match Socket::new(Domain::IPV4, Type::RAW, None) {
+        Ok(socket) => socket,
+        // Need certain permissions to create a raw sockets.
+        Err(ref err) if err.kind() == io::ErrorKind::PermissionDenied => return,
+        #[cfg(unix)]
+        Err(ref err) if err.raw_os_error() == Some(libc::EPROTONOSUPPORT) => return,
+        Err(err) => panic!("unexpected error creating socket: {}", err),
+    };
+
+    let initial = socket
+        .header_included()
+        .expect("failed to get initial value");
+    assert_eq!(initial, false, "initial value and argument are the same");
+
+    socket
+        .set_header_included(true)
+        .expect("failed to set option");
+    let got = socket.header_included().expect("failed to get value");
+    assert_eq!(got, true, "set and get values differ");
+}