From 1018c40a391a14ae4151057abeab353924a12e45 Mon Sep 17 00:00:00 2001 From: BobAnkh Date: Sat, 24 Dec 2022 04:50:08 +0000 Subject: [PATCH 1/8] Add support for TCP_CONGESTION Co-authored-by: Campbell He --- src/sys/unix.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ tests/socket.rs | 22 ++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 418c6907..26645c15 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -204,6 +204,9 @@ 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; +#[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 +2157,50 @@ 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 { + unsafe { + getsockopt::<[u8; TCP_CA_NAME_MAX]>(self.as_raw(), IPPROTO_TCP, libc::TCP_CONGESTION) + .map(|buf| { + String::from_utf8_lossy(&buf) + .trim_matches(char::from(0)) + .to_string() + }) + } + } + + /// 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: &str) -> io::Result<()> { + let name = std::ffi::OsString::from(tcp_ca_name); + syscall!(setsockopt( + self.as_raw(), + IPPROTO_TCP, + libc::TCP_CONGESTION, + name.as_bytes().as_ptr() as *const std::os::raw::c_void, + name.len() as libc::socklen_t, + )) + .map(|_| ()) + } } #[cfg_attr(docsrs, doc(cfg(unix)))] diff --git a/tests/socket.rs b/tests/socket.rs index 39838e4c..da60a9f0 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1341,3 +1341,25 @@ 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: String = 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("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}" + ); +} From 6a59ab6873d25470b33e0f7d8ef57cb6f1741681 Mon Sep 17 00:00:00 2001 From: BobAnkh Date: Sun, 25 Dec 2022 03:23:41 +0000 Subject: [PATCH 2/8] Use raw getsockopt syscall may modify len to true string length --- src/sys/unix.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 26645c15..97b5d42f 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -2169,13 +2169,22 @@ impl crate::Socket { doc(cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))) )] pub fn tcp_congestion(&self) -> io::Result { + let mut payload: MaybeUninit<[u8; TCP_CA_NAME_MAX]> = MaybeUninit::uninit(); + let mut len = size_of::<[u8; TCP_CA_NAME_MAX]>() as libc::socklen_t; unsafe { - getsockopt::<[u8; TCP_CA_NAME_MAX]>(self.as_raw(), IPPROTO_TCP, libc::TCP_CONGESTION) - .map(|buf| { - String::from_utf8_lossy(&buf) - .trim_matches(char::from(0)) - .to_string() - }) + syscall!(getsockopt( + self.as_raw(), + IPPROTO_TCP, + libc::TCP_CONGESTION, + payload.as_mut_ptr().cast(), + &mut len, + )) + .map(|_| payload.assume_init()) + .map(|buf| { + String::from_utf8_lossy(&buf) + .trim_matches(char::from(0)) + .to_string() + }) } } From fba11854bc487dff1b8443e427affa7d15459f1a Mon Sep 17 00:00:00 2001 From: BobAnkh Date: Sun, 25 Dec 2022 03:52:09 +0000 Subject: [PATCH 3/8] Remove str after null terminator --- src/sys/unix.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 97b5d42f..6760a992 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -2171,21 +2171,21 @@ impl crate::Socket { pub fn tcp_congestion(&self) -> io::Result { let mut payload: MaybeUninit<[u8; TCP_CA_NAME_MAX]> = MaybeUninit::uninit(); let mut len = size_of::<[u8; TCP_CA_NAME_MAX]>() as libc::socklen_t; - unsafe { - syscall!(getsockopt( - self.as_raw(), - IPPROTO_TCP, - libc::TCP_CONGESTION, - payload.as_mut_ptr().cast(), - &mut len, - )) - .map(|_| payload.assume_init()) - .map(|buf| { - String::from_utf8_lossy(&buf) - .trim_matches(char::from(0)) - .to_string() - }) - } + syscall!(getsockopt( + self.as_raw(), + IPPROTO_TCP, + libc::TCP_CONGESTION, + payload.as_mut_ptr().cast(), + &mut len, + )) + .map(|_| { + let buf = unsafe { payload.assume_init() }; + let raw_name = String::from_utf8_lossy(&buf); + match raw_name.split_once(char::from(0)) { + Some((left, _)) => left.to_string(), + None => raw_name.to_string(), + } + }) } /// Set the value of the `TCP_CONGESTION` option for this socket. From 9ac17bcd6dcb07ef849fb93634b6cf8ea84cada1 Mon Sep 17 00:00:00 2001 From: BobAnkh Date: Sun, 25 Dec 2022 15:22:08 +0000 Subject: [PATCH 4/8] Improve code --- src/sys/unix.rs | 16 ++++++---------- tests/socket.rs | 6 +++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 6760a992..c287f8bd 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -2168,7 +2168,7 @@ impl crate::Socket { docsrs, doc(cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))) )] - pub fn tcp_congestion(&self) -> io::Result { + pub fn tcp_congestion(&self) -> io::Result> { let mut payload: MaybeUninit<[u8; TCP_CA_NAME_MAX]> = MaybeUninit::uninit(); let mut len = size_of::<[u8; TCP_CA_NAME_MAX]>() as libc::socklen_t; syscall!(getsockopt( @@ -2180,11 +2180,8 @@ impl crate::Socket { )) .map(|_| { let buf = unsafe { payload.assume_init() }; - let raw_name = String::from_utf8_lossy(&buf); - match raw_name.split_once(char::from(0)) { - Some((left, _)) => left.to_string(), - None => raw_name.to_string(), - } + let name = buf.splitn(2, |num| *num == 0).next().unwrap().to_vec(); + name }) } @@ -2199,14 +2196,13 @@ impl crate::Socket { docsrs, doc(cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))) )] - pub fn set_tcp_congestion(&self, tcp_ca_name: &str) -> io::Result<()> { - let name = std::ffi::OsString::from(tcp_ca_name); + pub fn set_tcp_congestion(&self, tcp_ca_name: &[u8]) -> io::Result<()> { syscall!(setsockopt( self.as_raw(), IPPROTO_TCP, libc::TCP_CONGESTION, - name.as_bytes().as_ptr() as *const std::os::raw::c_void, - name.len() as libc::socklen_t, + tcp_ca_name.as_ptr() as *const std::os::raw::c_void, + tcp_ca_name.len() as libc::socklen_t, )) .map(|_| ()) } diff --git a/tests/socket.rs b/tests/socket.rs index da60a9f0..deb60e68 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1347,7 +1347,7 @@ fn original_dst_ipv6() { fn tcp_congestion() { let socket: Socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap(); // Get and set current tcp_ca - let origin_tcp_ca: String = socket + let origin_tcp_ca = socket .tcp_congestion() .expect("failed to get tcp congestion algorithm"); socket @@ -1355,11 +1355,11 @@ fn tcp_congestion() { .expect("failed to set tcp congestion algorithm"); // Return a Err when set a non-exist tcp_ca socket - .set_tcp_congestion("tcp_congestion_does_not_exist") + .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}" + "expected {origin_tcp_ca:?} but get {cur_tcp_ca:?}" ); } From 64dd03ba1d1294a9045d187fd21a9c2592e54f54 Mon Sep 17 00:00:00 2001 From: BobAnkh Date: Mon, 26 Dec 2022 15:14:20 +0000 Subject: [PATCH 5/8] Add new tcp ca test --- src/sys/unix.rs | 5 +++-- tests/socket.rs | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index c287f8bd..765f92da 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -2180,8 +2180,9 @@ impl crate::Socket { )) .map(|_| { let buf = unsafe { payload.assume_init() }; - let name = buf.splitn(2, |num| *num == 0).next().unwrap().to_vec(); - name + let buf = &buf[..len as usize]; + // TODO: use `MaybeUninit::slice_assume_init_ref` once stable. + unsafe { &*(buf as *const [_] as *const [u8]) }.into() }) } diff --git a/tests/socket.rs b/tests/socket.rs index deb60e68..4f87347a 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1362,4 +1362,26 @@ fn tcp_congestion() { 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 + let new_tcp_ca = if cur_tcp_ca == OPTIONS[0] { + OPTIONS[1] + } else { + OPTIONS[0] + }; + 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, + "expected {new_tcp_ca:?}, but get {cur_tcp_ca:?}" + ); } From d2bbbee9816abf16748e6e02c2aa6cb474c828b5 Mon Sep 17 00:00:00 2001 From: BobAnkh Date: Mon, 26 Dec 2022 15:40:23 +0000 Subject: [PATCH 6/8] Fix FreeBSD error - FreeBSD in CI only support newreno --- tests/socket.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/socket.rs b/tests/socket.rs index 4f87347a..e562cdab 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1371,11 +1371,14 @@ fn tcp_congestion() { 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(); From 0d2c2ed515a065ebd3992d10cafbf0ff18a7e311 Mon Sep 17 00:00:00 2001 From: BobAnkh Date: Sat, 7 Jan 2023 03:27:20 +0000 Subject: [PATCH 7/8] Polish code --- src/sys/unix.rs | 9 ++++----- tests/socket.rs | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 765f92da..ec37691e 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -2169,8 +2169,8 @@ impl crate::Socket { doc(cfg(all(feature = "all", any(target_os = "freebsd", target_os = "linux")))) )] pub fn tcp_congestion(&self) -> io::Result> { - let mut payload: MaybeUninit<[u8; TCP_CA_NAME_MAX]> = MaybeUninit::uninit(); - let mut len = size_of::<[u8; TCP_CA_NAME_MAX]>() as libc::socklen_t; + 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, @@ -2179,8 +2179,7 @@ impl crate::Socket { &mut len, )) .map(|_| { - let buf = unsafe { payload.assume_init() }; - let buf = &buf[..len as usize]; + let buf = &payload[..len as usize]; // TODO: use `MaybeUninit::slice_assume_init_ref` once stable. unsafe { &*(buf as *const [_] as *const [u8]) }.into() }) @@ -2202,7 +2201,7 @@ impl crate::Socket { self.as_raw(), IPPROTO_TCP, libc::TCP_CONGESTION, - tcp_ca_name.as_ptr() as *const std::os::raw::c_void, + tcp_ca_name.as_ptr() as *const _, tcp_ca_name.len() as libc::socklen_t, )) .map(|_| ()) diff --git a/tests/socket.rs b/tests/socket.rs index e562cdab..c3872bd1 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -1385,6 +1385,5 @@ fn tcp_congestion() { assert_eq!( cur_tcp_ca.splitn(2, |num| *num == 0).next().unwrap(), new_tcp_ca, - "expected {new_tcp_ca:?}, but get {cur_tcp_ca:?}" ); } From 21c2452b7319259ce7b88a3f5960e5a65c78b220 Mon Sep 17 00:00:00 2001 From: BobAnkh Date: Sat, 7 Jan 2023 09:36:50 +0000 Subject: [PATCH 8/8] Add comment of TCP_CA_NAME_MAX --- src/sys/unix.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index ec37691e..fe9ba3b0 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -204,6 +204,7 @@ 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;