1273 lines
37 KiB
Rust
1273 lines
37 KiB
Rust
#[cfg(linux_android)]
|
|
use crate::*;
|
|
use nix::sys::socket::{
|
|
getsockopt, setsockopt, socket, sockopt, AddressFamily, SockFlag,
|
|
SockProtocol, SockType,
|
|
};
|
|
use rand::{rng, Rng};
|
|
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
|
|
|
|
// NB: FreeBSD supports LOCAL_PEERCRED for SOCK_SEQPACKET, but OSX does not.
|
|
#[cfg(freebsdlike)]
|
|
#[test]
|
|
pub fn test_local_peercred_seqpacket() {
|
|
use nix::{
|
|
sys::socket::socketpair,
|
|
unistd::{Gid, Uid},
|
|
};
|
|
|
|
let (fd1, _fd2) = socketpair(
|
|
AddressFamily::Unix,
|
|
SockType::SeqPacket,
|
|
None,
|
|
SockFlag::empty(),
|
|
)
|
|
.unwrap();
|
|
let xucred = getsockopt(&fd1, sockopt::LocalPeerCred).unwrap();
|
|
assert_eq!(xucred.version(), 0);
|
|
assert_eq!(Uid::from_raw(xucred.uid()), Uid::current());
|
|
assert_eq!(Gid::from_raw(xucred.groups()[0]), Gid::current());
|
|
}
|
|
|
|
#[cfg(any(freebsdlike, apple_targets))]
|
|
#[test]
|
|
pub fn test_local_peercred_stream() {
|
|
use nix::{
|
|
sys::socket::socketpair,
|
|
unistd::{Gid, Uid},
|
|
};
|
|
|
|
let (fd1, _fd2) = socketpair(
|
|
AddressFamily::Unix,
|
|
SockType::Stream,
|
|
None,
|
|
SockFlag::empty(),
|
|
)
|
|
.unwrap();
|
|
let xucred = getsockopt(&fd1, sockopt::LocalPeerCred).unwrap();
|
|
assert_eq!(xucred.version(), 0);
|
|
assert_eq!(Uid::from_raw(xucred.uid()), Uid::current());
|
|
assert_eq!(Gid::from_raw(xucred.groups()[0]), Gid::current());
|
|
}
|
|
|
|
#[cfg(apple_targets)]
|
|
#[test]
|
|
pub fn test_local_peer_pid() {
|
|
use nix::sys::socket::socketpair;
|
|
|
|
let (fd1, _fd2) = socketpair(
|
|
AddressFamily::Unix,
|
|
SockType::Stream,
|
|
None,
|
|
SockFlag::empty(),
|
|
)
|
|
.unwrap();
|
|
let pid = getsockopt(&fd1, sockopt::LocalPeerPid).unwrap();
|
|
assert_eq!(pid, std::process::id() as _);
|
|
}
|
|
|
|
#[cfg(apple_targets)]
|
|
#[test]
|
|
pub fn test_local_peer_token() {
|
|
use nix::sys::socket::{audit_token_t, socketpair};
|
|
|
|
#[link(name = "bsm", kind = "dylib")]
|
|
extern "C" {
|
|
/// Extract the process ID from an `audit_token_t`, used to identify
|
|
/// Mach tasks and senders of Mach messages as subjects of the audit
|
|
/// system.
|
|
///
|
|
/// - `atoken`: The Mach audit token.
|
|
/// - Returns: The process ID extracted from the Mach audit token.
|
|
fn audit_token_to_pid(atoken: audit_token_t) -> libc::pid_t;
|
|
}
|
|
|
|
let (fd1, _fd2) = socketpair(
|
|
AddressFamily::Unix,
|
|
SockType::Stream,
|
|
None,
|
|
SockFlag::empty(),
|
|
)
|
|
.unwrap();
|
|
let audit_token = getsockopt(&fd1, sockopt::LocalPeerToken).unwrap();
|
|
assert_eq!(
|
|
unsafe { audit_token_to_pid(audit_token) },
|
|
std::process::id() as _
|
|
);
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[test]
|
|
fn is_so_mark_functional() {
|
|
use nix::sys::socket::sockopt;
|
|
|
|
require_capability!("is_so_mark_functional", CAP_NET_ADMIN);
|
|
|
|
let s = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&s, sockopt::Mark, &1337).unwrap();
|
|
let mark = getsockopt(&s, sockopt::Mark).unwrap();
|
|
assert_eq!(mark, 1337);
|
|
}
|
|
|
|
#[test]
|
|
fn test_so_buf() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
SockProtocol::Udp,
|
|
)
|
|
.unwrap();
|
|
let bufsize: usize = rng().random_range(4096..131_072);
|
|
setsockopt(&fd, sockopt::SndBuf, &bufsize).unwrap();
|
|
let actual = getsockopt(&fd, sockopt::SndBuf).unwrap();
|
|
assert!(actual >= bufsize);
|
|
setsockopt(&fd, sockopt::RcvBuf, &bufsize).unwrap();
|
|
let actual = getsockopt(&fd, sockopt::RcvBuf).unwrap();
|
|
assert!(actual >= bufsize);
|
|
}
|
|
|
|
#[cfg(target_os = "freebsd")]
|
|
#[test]
|
|
fn test_so_listen_q_limit() {
|
|
use nix::sys::socket::{bind, listen, Backlog, SockaddrIn};
|
|
use std::net::SocketAddrV4;
|
|
use std::str::FromStr;
|
|
|
|
let std_sa = SocketAddrV4::from_str("127.0.0.1:4004").unwrap();
|
|
let sock_addr = SockaddrIn::from(std_sa);
|
|
|
|
let rsock = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
bind(rsock.as_raw_fd(), &sock_addr).unwrap();
|
|
let pre_limit = getsockopt(&rsock, sockopt::ListenQLimit).unwrap();
|
|
assert_eq!(pre_limit, 0);
|
|
listen(&rsock, Backlog::new(42).unwrap()).unwrap();
|
|
let post_limit = getsockopt(&rsock, sockopt::ListenQLimit).unwrap();
|
|
assert_eq!(post_limit, 42);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(target_os = "cygwin", ignore)]
|
|
fn test_so_tcp_maxseg() {
|
|
use nix::sys::socket::{
|
|
accept, bind, connect, getsockname, listen, Backlog, SockaddrIn,
|
|
};
|
|
use std::net::SocketAddrV4;
|
|
use std::str::FromStr;
|
|
|
|
let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap();
|
|
let mut sock_addr = SockaddrIn::from(std_sa);
|
|
|
|
let rsock = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
bind(rsock.as_raw_fd(), &sock_addr).unwrap();
|
|
sock_addr = getsockname(rsock.as_raw_fd()).unwrap();
|
|
listen(&rsock, Backlog::new(10).unwrap()).unwrap();
|
|
let initial = getsockopt(&rsock, sockopt::TcpMaxSeg).unwrap();
|
|
// Initial MSS is expected to be 536 (https://tools.ietf.org/html/rfc879#section-1) but some
|
|
// platforms keep it even lower. This might fail if you've tuned your initial MSS to be larger
|
|
// than `segsize`
|
|
let segsize: u32 = 873;
|
|
assert!(initial < segsize);
|
|
cfg_if! {
|
|
if #[cfg(linux_android)] {
|
|
setsockopt(&rsock, sockopt::TcpMaxSeg, &segsize).unwrap();
|
|
}
|
|
}
|
|
|
|
// Connect and check the MSS that was advertised
|
|
let ssock = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
|
|
connect(ssock.as_raw_fd(), &sock_addr).unwrap();
|
|
|
|
let rsess = accept(rsock.as_raw_fd()).unwrap();
|
|
let rsess = unsafe { OwnedFd::from_raw_fd(rsess) };
|
|
|
|
cfg_if! {
|
|
if #[cfg(apple_targets)] {
|
|
// on apple targets (and unlike linux), we can only set the MSS on a *connected*
|
|
// socket. Also, the same MSS can't be read using getsockopt from the other end.
|
|
|
|
assert_ne!(segsize, getsockopt(&rsess, sockopt::TcpMaxSeg).unwrap());
|
|
setsockopt(&rsess, sockopt::TcpMaxSeg, &segsize).unwrap();
|
|
assert_eq!(segsize, getsockopt(&rsess, sockopt::TcpMaxSeg).unwrap());
|
|
|
|
assert_ne!(segsize, getsockopt(&ssock, sockopt::TcpMaxSeg).unwrap());
|
|
setsockopt(&ssock, sockopt::TcpMaxSeg, &segsize).unwrap();
|
|
assert_eq!(segsize, getsockopt(&ssock, sockopt::TcpMaxSeg).unwrap());
|
|
} else {
|
|
use nix::unistd::write;
|
|
|
|
write(&rsess, b"hello").unwrap();
|
|
let actual = getsockopt(&ssock, sockopt::TcpMaxSeg).unwrap();
|
|
// Actual max segment size takes header lengths into account, max IPv4 options (60 bytes) + max
|
|
// TCP options (40 bytes) are subtracted from the requested maximum as a lower boundary.
|
|
if cfg!(linux_android) {
|
|
assert!((segsize - 100) <= actual);
|
|
assert!(actual <= segsize);
|
|
} else {
|
|
assert!(initial < actual);
|
|
assert!(536 < actual);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_so_type() {
|
|
let sockfd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(Ok(SockType::Stream), getsockopt(&sockfd, sockopt::SockType));
|
|
}
|
|
|
|
/// getsockopt(_, sockopt::SockType) should gracefully handle unknown socket
|
|
/// types. Regression test for https://github.com/nix-rust/nix/issues/1819
|
|
#[cfg(linux_android)]
|
|
#[test]
|
|
fn test_so_type_unknown() {
|
|
use nix::errno::Errno;
|
|
|
|
require_capability!("test_so_type", CAP_NET_RAW);
|
|
// SOCK_PACKET is deprecated, but since it is used for testing here, we allow it
|
|
#[allow(deprecated)]
|
|
let raw_fd = unsafe { libc::socket(libc::AF_PACKET, libc::SOCK_PACKET, 0) };
|
|
assert!(raw_fd >= 0, "Error opening socket: {}", nix::Error::last());
|
|
let sockfd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
|
|
|
|
assert_eq!(Err(Errno::EINVAL), getsockopt(&sockfd, sockopt::SockType));
|
|
}
|
|
|
|
// The CI doesn't supported getsockopt and setsockopt on emulated processors.
|
|
// It's believed to be a QEMU issue; the tests run ok on a fully emulated
|
|
// system. Current CI just runs the binary with QEMU but the kernel remains the
|
|
// same as the host.
|
|
// So the syscall doesn't work properly unless the kernel is also emulated.
|
|
#[test]
|
|
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
|
|
#[cfg_attr(qemu, ignore)]
|
|
fn test_tcp_congestion() {
|
|
use std::ffi::OsString;
|
|
use std::os::unix::ffi::OsStrExt;
|
|
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
let val = getsockopt(&fd, sockopt::TcpCongestion).unwrap();
|
|
let bytes = val.as_os_str().as_bytes();
|
|
for b in bytes.iter() {
|
|
assert_ne!(*b, 0, "OsString should contain no embedded NULs: {val:?}");
|
|
}
|
|
setsockopt(&fd, sockopt::TcpCongestion, &val).unwrap();
|
|
|
|
setsockopt(
|
|
&fd,
|
|
sockopt::TcpCongestion,
|
|
&OsString::from("tcp_congestion_does_not_exist"),
|
|
)
|
|
.unwrap_err();
|
|
|
|
assert_eq!(getsockopt(&fd, sockopt::TcpCongestion).unwrap(), val);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "freebsd")]
|
|
fn test_tcp_function_blk_alias() {
|
|
use std::ffi::CStr;
|
|
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
let tfs = getsockopt(&fd, sockopt::TcpFunctionBlk).unwrap();
|
|
let name = unsafe { CStr::from_ptr(tfs.function_set_name.as_ptr()) };
|
|
assert!(!name.to_bytes().is_empty());
|
|
|
|
let aliastfs = getsockopt(&fd, sockopt::TcpFunctionAlias).unwrap();
|
|
let aliasname =
|
|
unsafe { CStr::from_ptr(aliastfs.function_set_name.as_ptr()) };
|
|
// freebsd default tcp stack has no alias.
|
|
assert!(aliasname.to_bytes().is_empty());
|
|
|
|
// We can't know at compile time what options are available. So just test the setter by a
|
|
// no-op set.
|
|
// TODO: test if we can load for example BBR tcp stack kernel module.
|
|
setsockopt(&fd, sockopt::TcpFunctionBlk, &tfs).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(linux_android)]
|
|
fn test_bindtodevice() {
|
|
skip_if_not_root!("test_bindtodevice");
|
|
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
let val = getsockopt(&fd, sockopt::BindToDevice).unwrap();
|
|
setsockopt(&fd, sockopt::BindToDevice, &val).unwrap();
|
|
|
|
assert_eq!(getsockopt(&fd, sockopt::BindToDevice).unwrap(), val);
|
|
}
|
|
|
|
#[test]
|
|
fn test_so_tcp_keepalive() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, sockopt::KeepAlive, &true).unwrap();
|
|
assert!(getsockopt(&fd, sockopt::KeepAlive).unwrap());
|
|
|
|
#[cfg(any(linux_android, freebsdlike))]
|
|
{
|
|
let x = getsockopt(&fd, sockopt::TcpKeepIdle).unwrap();
|
|
setsockopt(&fd, sockopt::TcpKeepIdle, &(x + 1)).unwrap();
|
|
assert_eq!(getsockopt(&fd, sockopt::TcpKeepIdle).unwrap(), x + 1);
|
|
|
|
let x = getsockopt(&fd, sockopt::TcpKeepCount).unwrap();
|
|
setsockopt(&fd, sockopt::TcpKeepCount, &(x + 1)).unwrap();
|
|
assert_eq!(getsockopt(&fd, sockopt::TcpKeepCount).unwrap(), x + 1);
|
|
|
|
let x = getsockopt(&fd, sockopt::TcpKeepInterval).unwrap();
|
|
setsockopt(&fd, sockopt::TcpKeepInterval, &(x + 1)).unwrap();
|
|
assert_eq!(getsockopt(&fd, sockopt::TcpKeepInterval).unwrap(), x + 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(linux_android)]
|
|
#[cfg_attr(qemu, ignore)]
|
|
fn test_get_mtu() {
|
|
use nix::sys::socket::{bind, connect, SockaddrIn};
|
|
use std::net::SocketAddrV4;
|
|
use std::str::FromStr;
|
|
|
|
let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap();
|
|
let std_sb = SocketAddrV4::from_str("127.0.0.1:4002").unwrap();
|
|
|
|
let usock = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
SockProtocol::Udp,
|
|
)
|
|
.unwrap();
|
|
|
|
// Bind and initiate connection
|
|
bind(usock.as_raw_fd(), &SockaddrIn::from(std_sa)).unwrap();
|
|
connect(usock.as_raw_fd(), &SockaddrIn::from(std_sb)).unwrap();
|
|
|
|
// Loopback connections have 2^16 - the maximum - MTU
|
|
assert_eq!(getsockopt(&usock, sockopt::IpMtu), Ok(u16::MAX as i32))
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(linux_android, target_os = "freebsd"))]
|
|
fn test_ttl_opts() {
|
|
let fd4 = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd4, sockopt::Ipv4Ttl, &1)
|
|
.expect("setting ipv4ttl on an inet socket should succeed");
|
|
let fd6 = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd6, sockopt::Ipv6Ttl, &1)
|
|
.expect("setting ipv6ttl on an inet6 socket should succeed");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(linux_android, target_os = "freebsd"))]
|
|
fn test_multicast_ttl_opts_ipv4() {
|
|
let fd4 = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd4, sockopt::IpMulticastTtl, &2)
|
|
.expect("setting ipmulticastttl on an inet socket should succeed");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(linux_android)]
|
|
fn test_multicast_ttl_opts_ipv6() {
|
|
let fd6 = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd6, sockopt::IpMulticastTtl, &2)
|
|
.expect("setting ipmulticastttl on an inet6 socket should succeed");
|
|
}
|
|
|
|
#[test]
|
|
fn test_ipv6_multicast_hops() {
|
|
let fd6 = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd6, sockopt::Ipv6MulticastHops, &7)
|
|
.expect("setting ipv6multicasthops on an inet6 socket should succeed");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(apple_targets)]
|
|
fn test_dontfrag_opts() {
|
|
let fd4 = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd4, sockopt::IpDontFrag, &true)
|
|
.expect("setting IP_DONTFRAG on an inet stream socket should succeed");
|
|
setsockopt(&fd4, sockopt::IpDontFrag, &false).expect(
|
|
"unsetting IP_DONTFRAG on an inet stream socket should succeed",
|
|
);
|
|
let fd4d = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd4d, sockopt::IpDontFrag, &true).expect(
|
|
"setting IP_DONTFRAG on an inet datagram socket should succeed",
|
|
);
|
|
setsockopt(&fd4d, sockopt::IpDontFrag, &false).expect(
|
|
"unsetting IP_DONTFRAG on an inet datagram socket should succeed",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(linux_android, apple_targets))]
|
|
// Disable the test under emulation because it fails in Cirrus-CI. Lack
|
|
// of QEMU support is suspected.
|
|
#[cfg_attr(qemu, ignore)]
|
|
fn test_v6dontfrag_opts() {
|
|
let fd6 = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd6, sockopt::Ipv6DontFrag, &true).expect(
|
|
"setting IPV6_DONTFRAG on an inet6 stream socket should succeed",
|
|
);
|
|
setsockopt(&fd6, sockopt::Ipv6DontFrag, &false).expect(
|
|
"unsetting IPV6_DONTFRAG on an inet6 stream socket should succeed",
|
|
);
|
|
let fd6d = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd6d, sockopt::Ipv6DontFrag, &true).expect(
|
|
"setting IPV6_DONTFRAG on an inet6 datagram socket should succeed",
|
|
);
|
|
setsockopt(&fd6d, sockopt::Ipv6DontFrag, &false).expect(
|
|
"unsetting IPV6_DONTFRAG on an inet6 datagram socket should succeed",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "linux")]
|
|
fn test_so_priority() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
let priority = 3;
|
|
setsockopt(&fd, sockopt::Priority, &priority).unwrap();
|
|
assert_eq!(getsockopt(&fd, sockopt::Priority).unwrap(), priority);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(linux_android, target_os = "freebsd"))]
|
|
fn test_ip_tos() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
let tos = 0x80; // CS4
|
|
setsockopt(&fd, sockopt::Ipv4Tos, &tos).unwrap();
|
|
assert_eq!(getsockopt(&fd, sockopt::Ipv4Tos).unwrap(), tos);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(linux_android, target_os = "freebsd"))]
|
|
// Disable the test under emulation because it fails in Cirrus-CI. Lack
|
|
// of QEMU support is suspected.
|
|
#[cfg_attr(qemu, ignore)]
|
|
fn test_ipv6_tclass() {
|
|
let fd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
let class = 0x80; // CS4
|
|
setsockopt(&fd, sockopt::Ipv6TClass, &class).unwrap();
|
|
assert_eq!(getsockopt(&fd, sockopt::Ipv6TClass).unwrap(), class);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "freebsd")]
|
|
fn test_receive_timestamp() {
|
|
let fd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap();
|
|
assert!(getsockopt(&fd, sockopt::ReceiveTimestamp).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "freebsd")]
|
|
fn test_ts_clock_realtime_micro() {
|
|
use nix::sys::socket::SocketTimestamp;
|
|
|
|
let fd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
// FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP.
|
|
setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap();
|
|
|
|
setsockopt(
|
|
&fd,
|
|
sockopt::TsClock,
|
|
&SocketTimestamp::SO_TS_REALTIME_MICRO,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
getsockopt(&fd, sockopt::TsClock).unwrap(),
|
|
SocketTimestamp::SO_TS_REALTIME_MICRO
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "freebsd")]
|
|
fn test_ts_clock_bintime() {
|
|
use nix::sys::socket::SocketTimestamp;
|
|
|
|
let fd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
// FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP.
|
|
setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap();
|
|
|
|
setsockopt(&fd, sockopt::TsClock, &SocketTimestamp::SO_TS_BINTIME).unwrap();
|
|
assert_eq!(
|
|
getsockopt(&fd, sockopt::TsClock).unwrap(),
|
|
SocketTimestamp::SO_TS_BINTIME
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "freebsd")]
|
|
fn test_ts_clock_realtime() {
|
|
use nix::sys::socket::SocketTimestamp;
|
|
|
|
let fd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
// FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP.
|
|
setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap();
|
|
|
|
setsockopt(&fd, sockopt::TsClock, &SocketTimestamp::SO_TS_REALTIME)
|
|
.unwrap();
|
|
assert_eq!(
|
|
getsockopt(&fd, sockopt::TsClock).unwrap(),
|
|
SocketTimestamp::SO_TS_REALTIME
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "freebsd")]
|
|
fn test_ts_clock_monotonic() {
|
|
use nix::sys::socket::SocketTimestamp;
|
|
|
|
let fd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
// FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP.
|
|
setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap();
|
|
|
|
setsockopt(&fd, sockopt::TsClock, &SocketTimestamp::SO_TS_MONOTONIC)
|
|
.unwrap();
|
|
assert_eq!(
|
|
getsockopt(&fd, sockopt::TsClock).unwrap(),
|
|
SocketTimestamp::SO_TS_MONOTONIC
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(linux_android)]
|
|
// Disable the test under emulation because it fails with ENOPROTOOPT in CI
|
|
// on cross target. Lack of QEMU support is suspected.
|
|
#[cfg_attr(qemu, ignore)]
|
|
fn test_ip_bind_address_no_port() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, sockopt::IpBindAddressNoPort, &true).expect(
|
|
"setting IP_BIND_ADDRESS_NO_PORT on an inet stream socket should succeed",
|
|
);
|
|
assert!(getsockopt(&fd, sockopt::IpBindAddressNoPort).expect(
|
|
"getting IP_BIND_ADDRESS_NO_PORT on an inet stream socket should succeed",
|
|
));
|
|
setsockopt(&fd, sockopt::IpBindAddressNoPort, &false).expect(
|
|
"unsetting IP_BIND_ADDRESS_NO_PORT on an inet stream socket should succeed",
|
|
);
|
|
assert!(!getsockopt(&fd, sockopt::IpBindAddressNoPort).expect(
|
|
"getting IP_BIND_ADDRESS_NO_PORT on an inet stream socket should succeed",
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(linux_android)]
|
|
fn test_tcp_fast_open_connect() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, sockopt::TcpFastOpenConnect, &true).expect(
|
|
"setting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed",
|
|
);
|
|
assert!(getsockopt(&fd, sockopt::TcpFastOpenConnect).expect(
|
|
"getting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed",
|
|
));
|
|
setsockopt(&fd, sockopt::TcpFastOpenConnect, &false).expect(
|
|
"unsetting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed",
|
|
);
|
|
assert!(!getsockopt(&fd, sockopt::TcpFastOpenConnect).expect(
|
|
"getting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed",
|
|
));
|
|
}
|
|
|
|
#[cfg(linux_android)]
|
|
#[test]
|
|
fn can_get_peercred_on_unix_socket() {
|
|
use nix::sys::socket::{socketpair, sockopt, SockFlag, SockType};
|
|
|
|
let (a, b) = socketpair(
|
|
AddressFamily::Unix,
|
|
SockType::Stream,
|
|
None,
|
|
SockFlag::empty(),
|
|
)
|
|
.unwrap();
|
|
let a_cred = getsockopt(&a, sockopt::PeerCredentials).unwrap();
|
|
let b_cred = getsockopt(&b, sockopt::PeerCredentials).unwrap();
|
|
assert_eq!(a_cred, b_cred);
|
|
assert_ne!(a_cred.pid(), 0);
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn pid_from_pidfd(pidfd: OwnedFd) -> u32 {
|
|
use std::fs::read_to_string;
|
|
|
|
let fd = pidfd.as_raw_fd();
|
|
let fdinfo = read_to_string(format!("/proc/self/fdinfo/{fd}")).unwrap();
|
|
let pidline = fdinfo.split('\n').find(|s| s.starts_with("Pid:")).unwrap();
|
|
pidline.split('\t').next_back().unwrap().parse().unwrap()
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[test]
|
|
fn can_get_peerpidfd_on_unix_socket() {
|
|
use nix::sys::socket::{socketpair, sockopt, SockFlag, SockType};
|
|
|
|
let (a, b) = socketpair(
|
|
AddressFamily::Unix,
|
|
SockType::Stream,
|
|
None,
|
|
SockFlag::empty(),
|
|
)
|
|
.unwrap();
|
|
|
|
match (
|
|
getsockopt(&a, sockopt::PeerPidfd),
|
|
getsockopt(&b, sockopt::PeerPidfd),
|
|
) {
|
|
(Ok(a_pidfd), Ok(b_pidfd)) => {
|
|
let a_pid = pid_from_pidfd(a_pidfd);
|
|
let b_pid = pid_from_pidfd(b_pidfd);
|
|
assert_eq!(a_pid, b_pid);
|
|
assert_ne!(a_pid, 0);
|
|
}
|
|
(Err(nix::Error::ENOPROTOOPT), Err(nix::Error::ENOPROTOOPT)) => {
|
|
// Pidfd can still be unsupported on some CI runners
|
|
}
|
|
(Err(err), _) | (_, Err(err)) => panic!("{err:?}"),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn is_socket_type_unix() {
|
|
use nix::sys::socket::{socketpair, sockopt, SockFlag, SockType};
|
|
|
|
let (a, _b) = socketpair(
|
|
AddressFamily::Unix,
|
|
SockType::Stream,
|
|
None,
|
|
SockFlag::empty(),
|
|
)
|
|
.unwrap();
|
|
let a_type = getsockopt(&a, sockopt::SockType).unwrap();
|
|
assert_eq!(a_type, SockType::Stream);
|
|
}
|
|
|
|
#[test]
|
|
fn is_socket_type_dgram() {
|
|
use nix::sys::socket::{
|
|
getsockopt, sockopt, AddressFamily, SockFlag, SockType,
|
|
};
|
|
|
|
let s = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let s_type = getsockopt(&s, sockopt::SockType).unwrap();
|
|
assert_eq!(s_type, SockType::Datagram);
|
|
}
|
|
|
|
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
|
|
#[test]
|
|
fn can_get_listen_on_tcp_socket() {
|
|
use nix::sys::socket::{
|
|
getsockopt, listen, socket, sockopt, AddressFamily, Backlog, SockFlag,
|
|
SockType,
|
|
};
|
|
|
|
let s = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let s_listening = getsockopt(&s, sockopt::AcceptConn).unwrap();
|
|
assert!(!s_listening);
|
|
listen(&s, Backlog::new(10).unwrap()).unwrap();
|
|
let s_listening2 = getsockopt(&s, sockopt::AcceptConn).unwrap();
|
|
assert!(s_listening2);
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
// Some architectures running under cross don't support `setsockopt(SOL_TCP, TCP_ULP)`
|
|
// because the cross image is based on Ubuntu 16.04 which predates TCP ULP support
|
|
// (it was added in kernel v4.13 released in 2017). For these architectures,
|
|
// the `setsockopt(SOL_TCP, TCP_ULP, "tls", sizeof("tls"))` call succeeds
|
|
// but the subsequent `setsockopt(SOL_TLS, TLS_TX, ...)` call fails with `ENOPROTOOPT`.
|
|
// It's as if the first `setsockopt` call enabled some other option, not `TCP_ULP`.
|
|
// For example, `strace` says:
|
|
//
|
|
// [pid 813] setsockopt(4, SOL_TCP, 0x1f /* TCP_??? */, [7564404], 4) = 0
|
|
//
|
|
// It's not clear why `setsockopt(SOL_TCP, TCP_ULP)` succeeds if the container image libc doesn't support it,
|
|
// but in any case we can't run the test on such an architecture, so skip it.
|
|
#[cfg_attr(qemu, ignore)]
|
|
#[test]
|
|
fn test_ktls() {
|
|
use nix::sys::socket::{
|
|
accept, bind, connect, getsockname, listen, Backlog, SockaddrIn,
|
|
};
|
|
use std::net::SocketAddrV4;
|
|
use std::str::FromStr;
|
|
|
|
let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap();
|
|
let mut sock_addr = SockaddrIn::from(std_sa);
|
|
|
|
let rsock = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
bind(rsock.as_raw_fd(), &sock_addr).unwrap();
|
|
sock_addr = getsockname(rsock.as_raw_fd()).unwrap();
|
|
listen(&rsock, Backlog::new(10).unwrap()).unwrap();
|
|
|
|
let ssock = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
connect(ssock.as_raw_fd(), &sock_addr).unwrap();
|
|
|
|
let _rsess = accept(rsock.as_raw_fd()).unwrap();
|
|
|
|
match setsockopt(&ssock, sockopt::TcpUlp::default(), b"tls") {
|
|
Ok(()) => (),
|
|
|
|
// TLS ULP is not enabled, so we can't test kTLS.
|
|
Err(nix::Error::ENOENT) => skip!("TLS ULP is not enabled"),
|
|
|
|
Err(err) => panic!("{err:?}"),
|
|
}
|
|
|
|
// In real life we would do a TLS handshake and extract the protocol version and secrets.
|
|
// For this test we just make some up.
|
|
|
|
let tx = sockopt::TlsCryptoInfo::Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128 {
|
|
info: libc::tls_crypto_info {
|
|
version: libc::TLS_1_2_VERSION,
|
|
cipher_type: libc::TLS_CIPHER_AES_GCM_128,
|
|
},
|
|
iv: *b"\x04\x05\x06\x07\x08\x09\x0a\x0b",
|
|
key: *b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
|
|
salt: *b"\x00\x01\x02\x03",
|
|
rec_seq: *b"\x00\x00\x00\x00\x00\x00\x00\x00",
|
|
});
|
|
setsockopt(&ssock, sockopt::TcpTlsTx, &tx)
|
|
.expect("setting TLS_TX after enabling TLS ULP should succeed");
|
|
|
|
let rx = sockopt::TlsCryptoInfo::Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128 {
|
|
info: libc::tls_crypto_info {
|
|
version: libc::TLS_1_2_VERSION,
|
|
cipher_type: libc::TLS_CIPHER_AES_GCM_128,
|
|
},
|
|
iv: *b"\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb",
|
|
key: *b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef",
|
|
salt: *b"\xf0\xf1\xf2\xf3",
|
|
rec_seq: *b"\x00\x00\x00\x00\x00\x00\x00\x00",
|
|
});
|
|
match setsockopt(&ssock, sockopt::TcpTlsRx, &rx) {
|
|
Ok(()) => (),
|
|
Err(nix::Error::ENOPROTOOPT) => {
|
|
// TLS_TX was added in v4.13 and TLS_RX in v4.17, so we appear to be between that range.
|
|
// It's good enough that TLS_TX worked, so let the test succeed.
|
|
}
|
|
Err(err) => panic!("{err:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(apple_targets)]
|
|
fn test_utun_ifname() {
|
|
skip_if_not_root!("test_utun_ifname");
|
|
|
|
use nix::sys::socket::connect;
|
|
use nix::sys::socket::SysControlAddr;
|
|
|
|
let fd = socket(
|
|
AddressFamily::System,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
SockProtocol::KextControl,
|
|
)
|
|
.unwrap();
|
|
|
|
let unit = 123;
|
|
let addr = SysControlAddr::from_name(
|
|
fd.as_raw_fd(),
|
|
"com.apple.net.utun_control",
|
|
unit,
|
|
)
|
|
.unwrap();
|
|
|
|
connect(fd.as_raw_fd(), &addr).unwrap();
|
|
|
|
let name = getsockopt(&fd, sockopt::UtunIfname)
|
|
.expect("getting UTUN_OPT_IFNAME on a utun interface should succeed");
|
|
|
|
let expected_name = format!("utun{}", unit - 1);
|
|
assert_eq!(name.into_string(), Ok(expected_name));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "freebsd")]
|
|
fn test_reuseport_lb() {
|
|
let fd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, sockopt::ReusePortLb, &false).unwrap();
|
|
assert!(!getsockopt(&fd, sockopt::ReusePortLb).unwrap());
|
|
setsockopt(&fd, sockopt::ReusePortLb, &true).unwrap();
|
|
assert!(getsockopt(&fd, sockopt::ReusePortLb).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(linux_android, target_os = "freebsd"))]
|
|
fn test_ipv4_recv_ttl_opts() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, sockopt::Ipv4RecvTtl, &true)
|
|
.expect("setting IP_RECVTTL on an inet stream socket should succeed");
|
|
setsockopt(&fd, sockopt::Ipv4RecvTtl, &false)
|
|
.expect("unsetting IP_RECVTTL on an inet stream socket should succeed");
|
|
let fdd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fdd, sockopt::Ipv4RecvTtl, &true)
|
|
.expect("setting IP_RECVTTL on an inet datagram socket should succeed");
|
|
setsockopt(&fdd, sockopt::Ipv4RecvTtl, &false).expect(
|
|
"unsetting IP_RECVTTL on an inet datagram socket should succeed",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(linux_android, target_os = "freebsd"))]
|
|
fn test_ipv6_recv_hop_limit_opts() {
|
|
let fd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, sockopt::Ipv6RecvHopLimit, &true).expect(
|
|
"setting IPV6_RECVHOPLIMIT on an inet6 stream socket should succeed",
|
|
);
|
|
setsockopt(&fd, sockopt::Ipv6RecvHopLimit, &false).expect(
|
|
"unsetting IPV6_RECVHOPLIMIT on an inet6 stream socket should succeed",
|
|
);
|
|
let fdd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fdd, sockopt::Ipv6RecvHopLimit, &true).expect(
|
|
"setting IPV6_RECVHOPLIMIT on an inet6 datagram socket should succeed",
|
|
);
|
|
setsockopt(&fdd, sockopt::Ipv6RecvHopLimit, &false).expect(
|
|
"unsetting IPV6_RECVHOPLIMIT on an inet6 datagram socket should succeed",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(linux_android, target_os = "freebsd"))]
|
|
fn test_ipv4_recv_tos_opts() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, sockopt::IpRecvTos, &true)
|
|
.expect("setting IP_RECVTOS on an inet stream socket should succeed");
|
|
setsockopt(&fd, sockopt::IpRecvTos, &false)
|
|
.expect("unsetting IP_RECVTOS on an inet stream socket should succeed");
|
|
let fdd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fdd, sockopt::IpRecvTos, &true)
|
|
.expect("setting IP_RECVTOS on an inet datagram socket should succeed");
|
|
setsockopt(&fdd, sockopt::IpRecvTos, &false).expect(
|
|
"unsetting IP_RECVTOS on an inet datagram socket should succeed",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(linux_android, target_os = "freebsd"))]
|
|
fn test_ipv6_recv_traffic_class_opts() {
|
|
let fd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, sockopt::Ipv6RecvTClass, &true).expect(
|
|
"setting IPV6_RECVTCLASS on an inet6 stream socket should succeed",
|
|
);
|
|
setsockopt(&fd, sockopt::Ipv6RecvTClass, &false).expect(
|
|
"unsetting IPV6_RECVTCLASS on an inet6 stream socket should succeed",
|
|
);
|
|
let fdd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fdd, sockopt::Ipv6RecvTClass, &true).expect(
|
|
"setting IPV6_RECVTCLASS on an inet6 datagram socket should succeed",
|
|
);
|
|
setsockopt(&fdd, sockopt::Ipv6RecvTClass, &false).expect(
|
|
"unsetting IPV6_RECVTCLASS on an inet6 datagram socket should succeed",
|
|
);
|
|
}
|
|
|
|
#[cfg(apple_targets)]
|
|
#[test]
|
|
fn test_linger_sec() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
let set_linger = libc::linger {
|
|
l_onoff: 1,
|
|
l_linger: 1,
|
|
};
|
|
setsockopt(&fd, sockopt::LingerSec, &set_linger).unwrap();
|
|
|
|
let get_linger = getsockopt(&fd, sockopt::Linger).unwrap();
|
|
assert_eq!(get_linger.l_linger, set_linger.l_linger * 100);
|
|
}
|
|
|
|
/// Users should be able to define their own sockopts.
|
|
mod sockopt_impl {
|
|
use nix::sys::socket::{
|
|
getsockopt, setsockopt, socket, AddressFamily, SockFlag, SockProtocol,
|
|
SockType,
|
|
};
|
|
|
|
sockopt_impl!(KeepAlive, Both, libc::SOL_SOCKET, libc::SO_KEEPALIVE, bool);
|
|
|
|
#[test]
|
|
fn test_so_tcp_keepalive() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, KeepAlive, &true).unwrap();
|
|
assert!(getsockopt(&fd, KeepAlive).unwrap());
|
|
}
|
|
|
|
sockopt_impl!(
|
|
Linger,
|
|
Both,
|
|
libc::SOL_SOCKET,
|
|
libc::SO_LINGER,
|
|
libc::linger
|
|
);
|
|
#[test]
|
|
fn test_linger() {
|
|
let fd = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
|
|
let set_linger = libc::linger {
|
|
l_onoff: 1,
|
|
l_linger: 42,
|
|
};
|
|
setsockopt(&fd, Linger, &set_linger).unwrap();
|
|
|
|
let get_linger = getsockopt(&fd, Linger).unwrap();
|
|
assert_eq!(get_linger.l_linger, set_linger.l_linger);
|
|
}
|
|
}
|
|
|
|
#[cfg(solarish)]
|
|
#[test]
|
|
fn test_exclbind() {
|
|
use nix::errno::Errno;
|
|
use nix::sys::socket::{
|
|
bind, socket, AddressFamily, SockFlag, SockType, SockaddrIn,
|
|
};
|
|
use std::net::SocketAddrV4;
|
|
use std::str::FromStr;
|
|
let fd1 = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let addr = SocketAddrV4::from_str("127.0.0.1:8081").unwrap();
|
|
let excl = true;
|
|
|
|
setsockopt(&fd1, sockopt::ExclBind, &excl).unwrap();
|
|
bind(fd1.as_raw_fd(), &SockaddrIn::from(addr)).unwrap();
|
|
assert_eq!(getsockopt(&fd1, sockopt::ExclBind), Ok(true));
|
|
let fd2 = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
bind(fd2.as_raw_fd(), &SockaddrIn::from(addr)),
|
|
Err(Errno::EADDRINUSE)
|
|
);
|
|
}
|
|
|
|
#[cfg(target_os = "illumos")]
|
|
#[test]
|
|
fn test_solfilter() {
|
|
use nix::errno::Errno;
|
|
let s = socket(
|
|
AddressFamily::Inet,
|
|
SockType::Stream,
|
|
SockFlag::empty(),
|
|
SockProtocol::Tcp,
|
|
)
|
|
.unwrap();
|
|
let data = std::ffi::OsStr::new("httpf");
|
|
let attach = sockopt::FilterAttach;
|
|
let detach = sockopt::FilterDetach;
|
|
|
|
// These 2 options won't work unless the needed kernel module is installed:
|
|
// https://github.com/nix-rust/nix/pull/2611#issuecomment-2750237782
|
|
//
|
|
// So we only test the binding here
|
|
assert_eq!(Err(Errno::ENOENT), setsockopt(&s, attach, data));
|
|
assert_eq!(Err(Errno::ENOENT), setsockopt(&s, detach, data));
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[test]
|
|
pub fn test_so_attach_reuseport_cbpf() {
|
|
let fd = socket(
|
|
AddressFamily::Inet6,
|
|
SockType::Datagram,
|
|
SockFlag::empty(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
setsockopt(&fd, sockopt::ReusePort, &true).unwrap();
|
|
setsockopt(&fd, sockopt::ReuseAddr, &true).unwrap();
|
|
let mut flt: [libc::sock_filter; 2] = unsafe { std::mem::zeroed() };
|
|
flt[0].code = (libc::BPF_LD | libc::BPF_W | libc::BPF_ABS) as u16;
|
|
flt[0].k = (libc::SKF_AD_OFF + libc::SKF_AD_CPU) as u32;
|
|
flt[1].code = (libc::BPF_RET | 0x10) as u16;
|
|
let fp = libc::sock_fprog {
|
|
len: flt.len() as u16,
|
|
filter: flt.as_mut_ptr(),
|
|
};
|
|
setsockopt(&fd, sockopt::AttachReusePortCbpf, &fp).unwrap_or_else(|e| {
|
|
assert_eq!(e, nix::errno::Errno::ENOPROTOOPT);
|
|
});
|
|
}
|