Repository: rust-lang/socket2
Pull request #374 (2023-01-02 20:00:39)

372: Implement `Eq` and `Hash` for `socket2::SockAddr`
Closes: #372.

This PR started as: #373, which was based off of `v0.4.x` by mistake, changing the base to `master`.

Cannot derive `Eq` and `Hash` for `socket2::SockAddr` because `sockaddr_storage` does not have both traits derived/implemented. `sockaddr_storage` is defined within `s_no_extra_traits`, my assumption it is expected to stay this way and I should not derive/implement `Eq` and `Hash` for `sockaddr_storage`.

## TODO:
- [x] add test cases. Quickcheck tests removed from this PR, can be found here: https://github.com/lshlyapnikov/socket2/blob/sockaddr-quickcheck/src/sockaddr.rs#L501-L554
- [x] add benchmarks for `socket2::SockAddr::as_socket` + `std::net::SocketAddr::hash` vs `socket2::SockAddr::hash` (this implementation). Benchmarks are not part of this PR, can be found here: https://github.com/lshlyapnikov/socket2/blob/sockaddr-quickcheck/benches/sockaddr_bench.rs




Patch:
diff --git a/src/sockaddr.rs b/src/sockaddr.rs
index 309ed38..1a19b5a 100644
--- a/src/sockaddr.rs
+++ b/src/sockaddr.rs
@@ -1,3 +1,4 @@
+use std::hash::Hash;
 use std::mem::{self, size_of, MaybeUninit};
 use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
 use std::path::Path;
@@ -312,44 +313,197 @@ impl fmt::Debug for SockAddr {
     }
 }
 
-#[test]
-fn ipv4() {
-    use std::net::Ipv4Addr;
-    let std = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876);
-    let addr = SockAddr::from(std);
-    assert!(addr.is_ipv4());
-    assert_eq!(addr.family(), AF_INET as sa_family_t);
-    assert_eq!(addr.domain(), Domain::IPV4);
-    assert_eq!(addr.len(), size_of::<sockaddr_in>() as socklen_t);
-    assert_eq!(addr.as_socket(), Some(SocketAddr::V4(std)));
-    assert_eq!(addr.as_socket_ipv4(), Some(std));
-    assert!(addr.as_socket_ipv6().is_none());
-
-    let addr = SockAddr::from(SocketAddr::from(std));
-    assert_eq!(addr.family(), AF_INET as sa_family_t);
-    assert_eq!(addr.len(), size_of::<sockaddr_in>() as socklen_t);
-    assert_eq!(addr.as_socket(), Some(SocketAddr::V4(std)));
-    assert_eq!(addr.as_socket_ipv4(), Some(std));
-    assert!(addr.as_socket_ipv6().is_none());
+impl PartialEq for SockAddr {
+    fn eq(&self, other: &Self) -> bool {
+        unsafe {
+            let these_bytes: &[u8] = any_as_u8_slice(&self.storage, self.len as usize);
+            let those_bytes: &[u8] = any_as_u8_slice(&other.storage, other.len as usize);
+            these_bytes == those_bytes
+        }
+    }
+}
+
+impl Eq for SockAddr {}
+
+impl Hash for SockAddr {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        unsafe {
+            let these_bytes: &[u8] = any_as_u8_slice(&self.storage, self.len as usize);
+            these_bytes.hash(state);
+        }
+    }
+}
+
+unsafe fn any_as_u8_slice<T: Sized>(p: &T, size: usize) -> &[u8] {
+    ::std::slice::from_raw_parts((p as *const T) as *const u8, size)
 }
 
-#[test]
-fn ipv6() {
-    use std::net::Ipv6Addr;
-    let std = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12);
-    let addr = SockAddr::from(std);
-    assert!(addr.is_ipv6());
-    assert_eq!(addr.family(), AF_INET6 as sa_family_t);
-    assert_eq!(addr.domain(), Domain::IPV6);
-    assert_eq!(addr.len(), size_of::<sockaddr_in6>() as socklen_t);
-    assert_eq!(addr.as_socket(), Some(SocketAddr::V6(std)));
-    assert!(addr.as_socket_ipv4().is_none());
-    assert_eq!(addr.as_socket_ipv6(), Some(std));
-
-    let addr = SockAddr::from(SocketAddr::from(std));
-    assert_eq!(addr.family(), AF_INET6 as sa_family_t);
-    assert_eq!(addr.len(), size_of::<sockaddr_in6>() as socklen_t);
-    assert_eq!(addr.as_socket(), Some(SocketAddr::V6(std)));
-    assert!(addr.as_socket_ipv4().is_none());
-    assert_eq!(addr.as_socket_ipv6(), Some(std));
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn ipv4() {
+        use std::net::Ipv4Addr;
+        let std = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876);
+        let addr = SockAddr::from(std);
+        assert!(addr.is_ipv4());
+        assert_eq!(addr.family(), AF_INET as sa_family_t);
+        assert_eq!(addr.domain(), Domain::IPV4);
+        assert_eq!(addr.len(), size_of::<sockaddr_in>() as socklen_t);
+        assert_eq!(addr.as_socket(), Some(SocketAddr::V4(std)));
+        assert_eq!(addr.as_socket_ipv4(), Some(std));
+        assert!(addr.as_socket_ipv6().is_none());
+
+        let addr = SockAddr::from(SocketAddr::from(std));
+        assert_eq!(addr.family(), AF_INET as sa_family_t);
+        assert_eq!(addr.len(), size_of::<sockaddr_in>() as socklen_t);
+        assert_eq!(addr.as_socket(), Some(SocketAddr::V4(std)));
+        assert_eq!(addr.as_socket_ipv4(), Some(std));
+        assert!(addr.as_socket_ipv6().is_none());
+    }
+
+    #[test]
+    fn ipv6() {
+        use std::net::Ipv6Addr;
+        let std = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12);
+        let addr = SockAddr::from(std);
+        assert!(addr.is_ipv6());
+        assert_eq!(addr.family(), AF_INET6 as sa_family_t);
+        assert_eq!(addr.domain(), Domain::IPV6);
+        assert_eq!(addr.len(), size_of::<sockaddr_in6>() as socklen_t);
+        assert_eq!(addr.as_socket(), Some(SocketAddr::V6(std)));
+        assert!(addr.as_socket_ipv4().is_none());
+        assert_eq!(addr.as_socket_ipv6(), Some(std));
+
+        let addr = SockAddr::from(SocketAddr::from(std));
+        assert_eq!(addr.family(), AF_INET6 as sa_family_t);
+        assert_eq!(addr.len(), size_of::<sockaddr_in6>() as socklen_t);
+        assert_eq!(addr.as_socket(), Some(SocketAddr::V6(std)));
+        assert!(addr.as_socket_ipv4().is_none());
+        assert_eq!(addr.as_socket_ipv6(), Some(std));
+    }
+
+    #[test]
+    fn ipv4_eq() {
+        use std::net::Ipv4Addr;
+
+        let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876);
+        let std2 = SocketAddrV4::new(Ipv4Addr::new(5, 6, 7, 8), 8765);
+
+        test_eq(
+            SockAddr::from(std1),
+            SockAddr::from(std1),
+            SockAddr::from(std2),
+        );
+    }
+
+    #[test]
+    fn ipv4_hash() {
+        use std::net::Ipv4Addr;
+
+        let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876);
+        let std2 = SocketAddrV4::new(Ipv4Addr::new(5, 6, 7, 8), 8765);
+
+        test_hash(
+            SockAddr::from(std1),
+            SockAddr::from(std1),
+            SockAddr::from(std2),
+        );
+    }
+
+    #[test]
+    fn ipv6_eq() {
+        use std::net::Ipv6Addr;
+
+        let std1 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12);
+        let std2 = SocketAddrV6::new(Ipv6Addr::new(3, 4, 5, 6, 7, 8, 9, 0), 7654, 13, 14);
+
+        test_eq(
+            SockAddr::from(std1),
+            SockAddr::from(std1),
+            SockAddr::from(std2),
+        );
+    }
+
+    #[test]
+    fn ipv6_hash() {
+        use std::net::Ipv6Addr;
+
+        let std1 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12);
+        let std2 = SocketAddrV6::new(Ipv6Addr::new(3, 4, 5, 6, 7, 8, 9, 0), 7654, 13, 14);
+
+        test_hash(
+            SockAddr::from(std1),
+            SockAddr::from(std1),
+            SockAddr::from(std2),
+        );
+    }
+
+    #[test]
+    fn ipv4_ipv6_eq() {
+        use std::net::Ipv4Addr;
+        use std::net::Ipv6Addr;
+
+        let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876);
+        let std2 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12);
+
+        test_eq(
+            SockAddr::from(std1),
+            SockAddr::from(std1),
+            SockAddr::from(std2),
+        );
+
+        test_eq(
+            SockAddr::from(std2),
+            SockAddr::from(std2),
+            SockAddr::from(std1),
+        );
+    }
+
+    #[test]
+    fn ipv4_ipv6_hash() {
+        use std::net::Ipv4Addr;
+        use std::net::Ipv6Addr;
+
+        let std1 = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876);
+        let std2 = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12);
+
+        test_hash(
+            SockAddr::from(std1),
+            SockAddr::from(std1),
+            SockAddr::from(std2),
+        );
+
+        test_hash(
+            SockAddr::from(std2),
+            SockAddr::from(std2),
+            SockAddr::from(std1),
+        );
+    }
+
+    #[allow(clippy::eq_op)] // allow a0 == a0 check
+    fn test_eq(a0: SockAddr, a1: SockAddr, b: SockAddr) {
+        assert!(a0 == a0);
+        assert!(a0 == a1);
+        assert!(a1 == a0);
+        assert!(a0 != b);
+        assert!(b != a0);
+    }
+
+    fn test_hash(a0: SockAddr, a1: SockAddr, b: SockAddr) {
+        assert!(calculate_hash(&a0) == calculate_hash(&a0));
+        assert!(calculate_hash(&a0) == calculate_hash(&a1));
+        // technically unequal values can have the same hash, in this case x != z and both have different hashes
+        assert!(calculate_hash(&a0) != calculate_hash(&b));
+    }
+
+    fn calculate_hash(x: &SockAddr) -> u64 {
+        use std::collections::hash_map::DefaultHasher;
+        use std::hash::Hasher;
+
+        let mut hasher = DefaultHasher::new();
+        x.hash(&mut hasher);
+        hasher.finish()
+    }
 }
diff --git a/src/sys/unix.rs b/src/sys/unix.rs
index 418c690..fe9ba3b 100644
--- a/src/sys/unix.rs
+++ b/src/sys/unix.rs
@@ -204,6 +204,10 @@ const MAX_BUF_LEN: usize = ssize_t::MAX as usize;
 #[cfg(target_vendor = "apple")]
 const MAX_BUF_LEN: usize = c_int::MAX as usize - 1;
 
+// TCP_CA_NAME_MAX isn't defined in user space include files(not in libc)
+#[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))]
+const TCP_CA_NAME_MAX: usize = 16;
+
 #[cfg(any(
     all(
         target_os = "linux",
@@ -2154,6 +2158,55 @@ impl crate::Socket {
             )
         }
     }
+
+    /// Get the value of the `TCP_CONGESTION` option for this socket.
+    ///
+    /// For more information about this option, see [`set_tcp_congestion`].
+    ///
+    /// [`set_tcp_congestion`]: Socket::set_tcp_congestion
+    #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))]
+    #[cfg_attr(
+        docsrs,
+        doc(cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux"))))
+    )]
+    pub fn tcp_congestion(&self) -> io::Result<Vec<u8>> {
+        let mut payload: [u8; TCP_CA_NAME_MAX] = [0; TCP_CA_NAME_MAX];
+        let mut len = payload.len() as libc::socklen_t;
+        syscall!(getsockopt(
+            self.as_raw(),
+            IPPROTO_TCP,
+            libc::TCP_CONGESTION,
+            payload.as_mut_ptr().cast(),
+            &mut len,
+        ))
+        .map(|_| {
+            let buf = &payload[..len as usize];
+            // TODO: use `MaybeUninit::slice_assume_init_ref` once stable.
+            unsafe { &*(buf as *const [_] as *const [u8]) }.into()
+        })
+    }
+
+    /// Set the value of the `TCP_CONGESTION` option for this socket.
+    ///
+    /// Specifies the TCP congestion control algorithm to use for this socket.
+    ///
+    /// The value must be a valid TCP congestion control algorithm name of the
+    /// platform. For example, Linux may supports "reno", "cubic".
+    #[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))]
+    #[cfg_attr(
+        docsrs,
+        doc(cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux"))))
+    )]
+    pub fn set_tcp_congestion(&self, tcp_ca_name: &[u8]) -> io::Result<()> {
+        syscall!(setsockopt(
+            self.as_raw(),
+            IPPROTO_TCP,
+            libc::TCP_CONGESTION,
+            tcp_ca_name.as_ptr() as *const _,
+            tcp_ca_name.len() as libc::socklen_t,
+        ))
+        .map(|_| ())
+    }
 }
 
 #[cfg_attr(docsrs, doc(cfg(unix)))]


Test patch:
diff --git a/tests/socket.rs b/tests/socket.rs
index 39838e4..c3872bd 100644
--- a/tests/socket.rs
+++ b/tests/socket.rs
@@ -1341,3 +1341,49 @@ fn original_dst_ipv6() {
         Err(err) => assert_eq!(err.raw_os_error(), Some(libc::EOPNOTSUPP)),
     }
 }
+
+#[test]
+#[cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))]
+fn tcp_congestion() {
+    let socket: Socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
+    // Get and set current tcp_ca
+    let origin_tcp_ca = socket
+        .tcp_congestion()
+        .expect("failed to get tcp congestion algorithm");
+    socket
+        .set_tcp_congestion(&origin_tcp_ca)
+        .expect("failed to set tcp congestion algorithm");
+    // Return a Err when set a non-exist tcp_ca
+    socket
+        .set_tcp_congestion(b"tcp_congestion_does_not_exist")
+        .unwrap_err();
+    let cur_tcp_ca = socket.tcp_congestion().unwrap();
+    assert_eq!(
+        cur_tcp_ca, origin_tcp_ca,
+        "expected {origin_tcp_ca:?} but get {cur_tcp_ca:?}"
+    );
+    let cur_tcp_ca = cur_tcp_ca.splitn(2, |num| *num == 0).next().unwrap();
+    const OPTIONS: [&[u8]; 2] = [
+        b"cubic",
+        #[cfg(target_os = "linux")] // or Android.
+        b"reno",
+        #[cfg(target_os = "freebsd")]
+        b"newreno",
+    ];
+    // Set a new tcp ca
+    #[cfg(target_os = "linux")]
+    let new_tcp_ca = if cur_tcp_ca == OPTIONS[0] {
+        OPTIONS[1]
+    } else {
+        OPTIONS[0]
+    };
+    #[cfg(target_os = "freebsd")]
+    let new_tcp_ca = OPTIONS[1];
+    socket.set_tcp_congestion(new_tcp_ca).unwrap();
+    // Check if new tcp ca is successfully set
+    let cur_tcp_ca = socket.tcp_congestion().unwrap();
+    assert_eq!(
+        cur_tcp_ca.splitn(2, |num| *num == 0).next().unwrap(),
+        new_tcp_ca,
+    );
+}