diff --git a/changelog/2760.added.md b/changelog/2760.added.md new file mode 100644 index 0000000000..cbf537fa1a --- /dev/null +++ b/changelog/2760.added.md @@ -0,0 +1,3 @@ +Added `TcpSaveSyn` and `TcpSavedSyn` socket options for Linux. `TcpSaveSyn` +enables saving a copy of the client SYN packet on a listening socket; +`TcpSavedSyn` retrieves the raw IP+TCP header bytes from the accepted socket. diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index 03a55d407c..8c1c30047f 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -41,7 +41,7 @@ const TCP_CA_NAME_MAX: usize = 16; /// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`) /// to the `setsockopt` call. /// * Type of the value that you are going to set. -/// * Type that implements the `Set` trait for the type from the previous item +/// * Type that implements the `Set` trait for the type from the previous item /// (like `SetBool` for `bool`, `SetUsize` for `usize`, etc.). #[macro_export] macro_rules! setsockopt_impl { @@ -248,6 +248,13 @@ macro_rules! sockopt_impl { $crate::sys::socket::sockopt::SetOsString); }; + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, + OsString<$array:ty>) => + { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, std::ffi::OsString, $crate::sys::socket::sockopt::GetOsString<$array>); + }; + /* * Matchers with generic getter types must be placed at the end, so * they'll only match _after_ specialized matchers fail @@ -364,9 +371,9 @@ sockopt_impl!( sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] /// Used to disable Nagle's algorithm. - /// + /// /// Nagle's algorithm: - /// + /// /// Under most circumstances, TCP sends data when it is presented; when /// outstanding data has not yet been acknowledged, it gathers small amounts /// of output to be sent in a single packet once an acknowledgement is @@ -762,6 +769,40 @@ sockopt_impl!( libc::TCP_REPAIR, u32 ); +#[cfg(linux_android)] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(all(feature = "net", target_os = "linux"))))] + /// If enabled, the kernel saves a copy of the SYN packet for each + /// accepted connection. The saved packet can be retrieved on the + /// accepted socket via [`TcpSavedSyn`]. Must be set on the listening + /// socket before `accept()` is called. + /// + /// See `tcp(7)` and `TCP_SAVE_SYN` in the Linux kernel documentation. + TcpSaveSyn, + Both, + libc::IPPROTO_TCP, + libc::TCP_SAVE_SYN, + bool +); +/// Maximum size of a saved SYN packet retrieved via [`TcpSavedSyn`]: +/// IPv4/IPv6 header (up to 60 bytes) + TCP header (up to 60 bytes). +#[cfg(linux_android)] +pub const TCP_SAVED_SYN_MAX: usize = 120; + +#[cfg(linux_android)] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(all(feature = "net", target_os = "linux"))))] + /// Retrieves the SYN packet saved by [`TcpSaveSyn`] on the listening + /// socket. Returns the raw IP + TCP headers from the client's initial SYN + /// as bytes. Returns an empty value if no SYN was saved. + TcpSavedSyn, + GetOnly, + libc::IPPROTO_TCP, + libc::TCP_SAVED_SYN, + OsString<[u8; TCP_SAVED_SYN_MAX]> +); #[cfg(not(any( target_os = "openbsd", target_os = "haiku", @@ -1216,7 +1257,7 @@ sockopt_impl!( #[cfg(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "net")] sockopt_impl!( - /// Enables a receiving socket to retrieve the Time-to-Live (TTL) field + /// Enables a receiving socket to retrieve the Time-to-Live (TTL) field /// from incoming IPv4 packets. Ipv4RecvTtl, Both, @@ -1236,7 +1277,7 @@ sockopt_impl!( #[cfg(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "net")] sockopt_impl!( - /// Enables a receiving socket to retrieve the Hop Limit field + /// Enables a receiving socket to retrieve the Hop Limit field /// (similar to TTL in IPv4) from incoming IPv6 packets. Ipv6RecvHopLimit, Both, @@ -1268,10 +1309,7 @@ sockopt_impl!( libc::IP_DONTFRAG, bool ); -#[cfg(any( - all(linux_android, not(target_env = "uclibc")), - apple_targets -))] +#[cfg(any(all(linux_android, not(target_env = "uclibc")), apple_targets))] sockopt_impl!( /// Set "don't fragment packet" flag on the IPv6 packet. Ipv6DontFrag, @@ -1850,7 +1888,6 @@ impl<'a> Set<'a, usize> for SetUsize { } } - /// Getter for a `OwnedFd` value. // Hide the docs, because it's an implementation detail of `sockopt_impl!` #[doc(hidden)] @@ -1900,7 +1937,9 @@ impl<'a> Set<'a, OwnedFd> for SetOwnedFd { fn new(val: &'a OwnedFd) -> SetOwnedFd { use std::os::fd::AsRawFd; - SetOwnedFd { val: val.as_raw_fd() as c_int } + SetOwnedFd { + val: val.as_raw_fd() as c_int, + } } fn ffi_ptr(&self) -> *const c_void { diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index 5b06a5c13d..f865f1cd96 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -1270,3 +1270,64 @@ pub fn test_so_attach_reuseport_cbpf() { assert_eq!(e, nix::errno::Errno::ENOPROTOOPT); }); } + +/// Test that TCP_SAVE_SYN can be set and read back on a listening socket, and +/// that TCP_SAVED_SYN returns the raw SYN bytes on an accepted connection. +#[test] +#[cfg(linux_android)] +#[cfg_attr(qemu, ignore)] +fn test_tcp_save_syn() { + use nix::sys::socket::{ + accept, bind, connect, getsockopt, listen, setsockopt, socket, sockopt, + AddressFamily, Backlog, SockFlag, SockProtocol, SockType, SockaddrIn, + }; + use std::net::SocketAddrV4; + use std::os::fd::{FromRawFd, OwnedFd}; + use std::str::FromStr; + + // Create a listening socket and enable TCP_SAVE_SYN. + let listener = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + + setsockopt(&listener, sockopt::ReuseAddr, &true).unwrap(); + setsockopt(&listener, sockopt::TcpSaveSyn, &true).unwrap(); + assert!(getsockopt(&listener, sockopt::TcpSaveSyn).unwrap()); + + let addr = SockaddrIn::from(SocketAddrV4::from_str("127.0.0.1:0").unwrap()); + bind(listener.as_raw_fd(), &addr).unwrap(); + listen(&listener, Backlog::new(1).unwrap()).unwrap(); + + // Determine the bound port. + let bound: SockaddrIn = + nix::sys::socket::getsockname(listener.as_raw_fd()).unwrap(); + + // Connect a client. + let client = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + connect(client.as_raw_fd(), &bound).unwrap(); + + // Accept the connection and verify TCP_SAVED_SYN returns SYN bytes. + let conn_fd = accept(listener.as_raw_fd()).unwrap(); + let conn = unsafe { OwnedFd::from_raw_fd(conn_fd) }; + + let syn_bytes = getsockopt(&conn, sockopt::TcpSavedSyn).unwrap(); + // The saved SYN must contain at least the IP and TCP fixed headers. + assert!( + !syn_bytes.is_empty(), + "TCP_SAVED_SYN should return SYN packet bytes" + ); + + // Disable TCP_SAVE_SYN and verify it reads back as false. + setsockopt(&listener, sockopt::TcpSaveSyn, &false).unwrap(); + assert!(!getsockopt(&listener, sockopt::TcpSaveSyn).unwrap()); +}