Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/2760.added.md
Original file line number Diff line number Diff line change
@@ -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.
61 changes: 50 additions & 11 deletions src/sys/socket/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down
61 changes: 61 additions & 0 deletions test/sys/test_sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Loading