Skip to content

Commit

Permalink
Implement more socket options.
Browse files Browse the repository at this point in the history
Implement IP_FREEBINDC, IPV6_FREEBIND, IPV6_TCLASS, SO_COOKIE,
SO_INCOMING_CPU, SO_PROTOCOL, SO_REUSEPORT, SO_REUSEPORT_LB,
TCP_CORK, TCP_QUICKACK, TCP_THIN_LINEAR_TIMEOUTS, TCP_CONGESTION,
SO_ORIGINAL_DST, and IP6T_SO_ORIGINAL_DST.
  • Loading branch information
sunfishcode committed Sep 29, 2023
1 parent 77c4aae commit 495a1c7
Show file tree
Hide file tree
Showing 5 changed files with 923 additions and 67 deletions.
280 changes: 264 additions & 16 deletions src/backend/libc/net/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use super::ext::{in6_addr_new, in_addr_new};
use crate::backend::c;
use crate::backend::conv::{borrowed_fd, ret};
use crate::fd::BorrowedFd;
#[cfg(any(linux_like, solarish, target_os = "freebsd", target_os = "fuchsia"))]
use crate::ffi::CStr;
use crate::io;
use crate::net::sockopt::Timeout;
#[cfg(not(any(
Expand All @@ -18,13 +20,35 @@ use crate::net::sockopt::Timeout;
target_os = "nto",
)))]
use crate::net::AddressFamily;
#[cfg(any(
linux_like,
target_os = "freebsd",
target_os = "fuchsia",
target_os = "openbsd",
target_os = "redox",
target_env = "newlib"
))]
use crate::net::Protocol;
#[cfg(any(
linux_like,
target_os = "freebsd",
target_os = "fuchsia",
target_os = "openbsd",
target_os = "redox"
))]
use crate::net::RawProtocol;
#[cfg(linux_kernel)]
use crate::net::SocketAddrV6;
use crate::net::{Ipv4Addr, Ipv6Addr, SocketType};
use crate::utils::{as_mut_ptr, as_ptr};
#[cfg(any(linux_kernel, target_os = "fuchsia"))]
use crate::net::{SocketAddrAny, SocketAddrStorage, SocketAddrV4};
use crate::utils::as_mut_ptr;
#[cfg(apple)]
use c::TCP_KEEPALIVE as TCP_KEEPIDLE;
#[cfg(not(any(apple, target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
use c::TCP_KEEPIDLE;
use core::mem::size_of;
use core::mem::MaybeUninit;
use core::time::Duration;
#[cfg(windows)]
use windows_sys::Win32::Foundation::BOOL;
Expand All @@ -37,25 +61,38 @@ fn getsockopt<T: Copy>(fd: BorrowedFd<'_>, level: i32, optname: i32) -> io::Resu
"Socket APIs don't ever use `bool` directly"
);

let mut value = MaybeUninit::<T>::uninit();
getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;

// On Windows at least, `getsockopt` has been observed writing 1
// byte on at least (`IPPROTO_TCP`, `TCP_NODELAY`), even though
// Windows' documentation says that should write a 4-byte `BOOL`.
// So, we initialize the memory to zeros above, and just assert
// that `getsockopt` doesn't write too many bytes here.
assert!(
optlen as usize <= size_of::<T>(),
"unexpected getsockopt size"
);

unsafe { Ok(value.assume_init()) }
}

#[inline]
fn getsockopt_raw<T>(
fd: BorrowedFd<'_>,
level: i32,
optname: i32,
value: &mut MaybeUninit<T>,
optlen: &mut c::socklen_t,
) -> io::Result<()> {
unsafe {
let mut value = core::mem::zeroed::<T>();
ret(c::getsockopt(
borrowed_fd(fd),
level,
optname,
as_mut_ptr(&mut value).cast(),
&mut optlen,
))?;
// On Windows at least, `getsockopt` has been observed writing 1
// byte on at least (`IPPROTO_TCP`, `TCP_NODELAY`), even though
// Windows' documentation says that should write a 4-byte `BOOL`.
// So, we initialize the memory to zeros above, and just assert
// that `getsockopt` doesn't write too many bytes here.
assert!(
optlen as usize <= size_of::<T>(),
"unexpected getsockopt size"
);
Ok(value)
as_mut_ptr(value).cast(),
optlen,
))
}
}

Expand All @@ -66,13 +103,23 @@ fn setsockopt<T: Copy>(fd: BorrowedFd<'_>, level: i32, optname: i32, value: T) -
optlen as usize >= core::mem::size_of::<c::c_int>(),
"Socket APIs don't ever use `bool` directly"
);
setsockopt_raw(fd, level, optname, &value, optlen)
}

#[inline]
fn setsockopt_raw<T>(
fd: BorrowedFd<'_>,
level: i32,
optname: i32,
ptr: *const T,
optlen: c::socklen_t,
) -> io::Result<()> {
unsafe {
ret(c::setsockopt(
borrowed_fd(fd),
level,
optname,
as_ptr(&value).cast(),
ptr.cast(),
optlen,
))
}
Expand Down Expand Up @@ -321,6 +368,67 @@ pub(crate) fn get_socket_oobinline(fd: BorrowedFd<'_>) -> io::Result<bool> {
getsockopt(fd, c::SOL_SOCKET, c::SO_OOBINLINE).map(to_bool)
}

#[cfg(not(any(solarish, windows)))]
#[inline]
pub(crate) fn set_socket_reuseport(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT, from_bool(value))
}

#[cfg(not(any(solarish, windows)))]
#[inline]
pub(crate) fn get_socket_reuseport(fd: BorrowedFd<'_>) -> io::Result<bool> {
getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT).map(to_bool)
}

#[cfg(target_os = "freebsd")]
#[inline]
pub(crate) fn set_socket_reuseport_lb(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT_LB, from_bool(value))
}

#[cfg(target_os = "freebsd")]
#[inline]
pub(crate) fn get_socket_reuseport_lb(fd: BorrowedFd<'_>) -> io::Result<bool> {
getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT_LB).map(to_bool)
}

#[cfg(any(
linux_like,
target_os = "freebsd",
target_os = "fuchsia",
target_os = "openbsd",
target_os = "redox",
target_env = "newlib"
))]
#[inline]
pub(crate) fn get_socket_protocol(fd: BorrowedFd<'_>) -> io::Result<Option<Protocol>> {
getsockopt(fd, c::SOL_SOCKET, c::SO_PROTOCOL).map(|raw| {
if let Some(raw) = RawProtocol::new(raw) {
Some(Protocol::from_raw(raw))
} else {
None
}
})
}

#[cfg(linux_like)]
#[inline]
pub(crate) fn get_socket_cookie(fd: BorrowedFd<'_>) -> io::Result<u64> {
getsockopt(fd, c::SOL_SOCKET, c::SO_COOKIE)
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn get_socket_incoming_cpu(fd: BorrowedFd<'_>) -> io::Result<u32> {
getsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU)
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn set_socket_incoming_cpu(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
setsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU, value)
}

#[inline]
pub(crate) fn set_ip_ttl(fd: BorrowedFd<'_>, ttl: u32) -> io::Result<()> {
setsockopt(fd, c::IPPROTO_IP, c::IP_TTL, ttl)
Expand Down Expand Up @@ -604,6 +712,76 @@ pub(crate) fn get_ipv6_recvtclass(fd: BorrowedFd<'_>) -> io::Result<bool> {
getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_RECVTCLASS).map(to_bool)
}

#[cfg(any(linux_kernel, target_os = "fuchsia"))]
#[inline]
pub(crate) fn set_ip_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
setsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND, from_bool(value))
}

#[cfg(any(linux_kernel, target_os = "fuchsia"))]
#[inline]
pub(crate) fn get_ip_freebind(fd: BorrowedFd<'_>) -> io::Result<bool> {
getsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND).map(to_bool)
}

#[cfg(linux_kernel)]
#[inline]
pub(crate) fn set_ipv6_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND, from_bool(value))
}

#[cfg(linux_kernel)]
#[inline]
pub(crate) fn get_ipv6_freebind(fd: BorrowedFd<'_>) -> io::Result<bool> {
getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND).map(to_bool)
}

#[cfg(any(linux_kernel, target_os = "fuchsia"))]
#[inline]
pub(crate) fn get_ip_original_dst(fd: BorrowedFd<'_>) -> io::Result<SocketAddrV4> {
let level = c::IPPROTO_IP;
let optname = c::SO_ORIGINAL_DST;
let mut value = MaybeUninit::<SocketAddrStorage>::uninit();
let mut optlen = core::mem::size_of_val(&value).try_into().unwrap();

getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;

let any = unsafe { SocketAddrAny::read(value.as_ptr(), optlen as usize)? };
match any {
SocketAddrAny::V4(v4) => Ok(v4),
_ => unreachable!(),
}
}

#[cfg(linux_kernel)]
#[inline]
pub(crate) fn get_ipv6_original_dst(fd: BorrowedFd<'_>) -> io::Result<SocketAddrV6> {
let level = c::IPPROTO_IPV6;
let optname = c::IP6T_SO_ORIGINAL_DST;
let mut value = MaybeUninit::<SocketAddrStorage>::uninit();
let mut optlen = core::mem::size_of_val(&value).try_into().unwrap();

getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;

let any = unsafe { SocketAddrAny::read(value.as_ptr(), optlen as usize)? };
match any {
SocketAddrAny::V6(v6) => Ok(v6),
_ => unreachable!(),
}
}

#[cfg(not(solarish))]
#[inline]
pub(crate) fn set_ipv6_tclass(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS, value)
}

#[cfg(not(solarish))]
#[inline]
pub(crate) fn get_ipv6_tclass(fd: BorrowedFd<'_>) -> io::Result<u32> {
getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS)
}

#[inline]
pub(crate) fn set_tcp_nodelay(fd: BorrowedFd<'_>, nodelay: bool) -> io::Result<()> {
setsockopt(fd, c::IPPROTO_TCP, c::TCP_NODELAY, from_bool(nodelay))
Expand Down Expand Up @@ -666,6 +844,76 @@ pub(crate) fn get_tcp_user_timeout(fd: BorrowedFd<'_>) -> io::Result<u32> {
getsockopt(fd, c::IPPROTO_TCP, c::TCP_USER_TIMEOUT)
}

#[cfg(any(linux_like, target_os = "fuchsia"))]
#[inline]
pub(crate) fn set_tcp_quickack(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
setsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK, from_bool(value))
}

#[cfg(any(linux_like, target_os = "fuchsia"))]
#[inline]
pub(crate) fn get_tcp_quickack(fd: BorrowedFd<'_>) -> io::Result<bool> {
getsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK).map(to_bool)
}

#[cfg(any(linux_like, solarish, target_os = "freebsd", target_os = "fuchsia"))]
#[inline]
pub(crate) fn set_tcp_congestion(fd: BorrowedFd<'_>, value: &str) -> io::Result<()> {
let level = c::IPPROTO_TCP;
let optname = c::TCP_CONGESTION;
let optlen = value.len().try_into().unwrap();
setsockopt_raw(fd, level, optname, value.as_ptr(), optlen)
}

#[cfg(any(linux_like, solarish, target_os = "freebsd", target_os = "fuchsia"))]
#[inline]
pub(crate) fn get_tcp_congestion(fd: BorrowedFd<'_>) -> io::Result<String> {
let level = c::IPPROTO_TCP;
let optname = c::TCP_CONGESTION;
const OPTLEN: c::socklen_t = 16;
let mut value = MaybeUninit::<[MaybeUninit<u8>; OPTLEN as usize]>::uninit();
let mut optlen = OPTLEN;
getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;
unsafe {
let value = value.assume_init();
let slice: &[u8] = core::mem::transmute(&value[..optlen as usize]);
Ok(
core::str::from_utf8(CStr::from_bytes_until_nul(slice).unwrap().to_bytes())
.unwrap()
.to_owned(),
)
}
}

#[cfg(any(linux_like, target_os = "fuchsia"))]
#[inline]
pub(crate) fn set_tcp_thin_linear_timeouts(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
setsockopt(
fd,
c::IPPROTO_TCP,
c::TCP_THIN_LINEAR_TIMEOUTS,
from_bool(value),
)
}

#[cfg(any(linux_like, target_os = "fuchsia"))]
#[inline]
pub(crate) fn get_tcp_thin_linear_timeouts(fd: BorrowedFd<'_>) -> io::Result<bool> {
getsockopt(fd, c::IPPROTO_TCP, c::TCP_THIN_LINEAR_TIMEOUTS).map(to_bool)
}

#[cfg(any(linux_like, solarish, target_os = "fuchsia"))]
#[inline]
pub(crate) fn set_tcp_cork(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
setsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK, from_bool(value))
}

#[cfg(any(linux_like, solarish, target_os = "fuchsia"))]
#[inline]
pub(crate) fn get_tcp_cork(fd: BorrowedFd<'_>) -> io::Result<bool> {
getsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK).map(to_bool)
}

#[inline]
fn to_ip_mreq(multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> c::ip_mreq {
c::ip_mreq {
Expand Down
28 changes: 17 additions & 11 deletions src/backend/linux_raw/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,27 @@ pub(crate) use linux_raw_sys::{
AF_NETBEUI, AF_NETLINK, AF_NETROM, AF_PACKET, AF_PHONET, AF_PPPOX, AF_RDS, AF_ROSE,
AF_RXRPC, AF_SECURITY, AF_SNA, AF_TIPC, AF_UNIX, AF_UNSPEC, AF_WANPIPE, AF_X25,
IPPROTO_FRAGMENT, IPPROTO_ICMPV6, IPPROTO_MH, IPPROTO_ROUTING, IPV6_ADD_MEMBERSHIP,
IPV6_DROP_MEMBERSHIP, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_LOOP, IPV6_RECVTCLASS,
IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP, IP_ADD_SOURCE_MEMBERSHIP,
IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP, IP_MULTICAST_LOOP, IP_MULTICAST_TTL,
IP_RECVTOS, IP_TOS, IP_TTL, MSG_CMSG_CLOEXEC, MSG_CONFIRM, MSG_DONTROUTE, MSG_DONTWAIT,
MSG_EOR, MSG_ERRQUEUE, MSG_MORE, MSG_NOSIGNAL, MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL,
SCM_CREDENTIALS, SCM_RIGHTS, SHUT_RD, SHUT_RDWR, SHUT_WR, SOCK_DGRAM, SOCK_RAW, SOCK_RDM,
SOCK_SEQPACKET, SOCK_STREAM, SOL_SOCKET, SO_ACCEPTCONN, SO_BROADCAST, SO_DOMAIN, SO_ERROR,
SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_PASSCRED, SO_RCVBUF, SO_RCVTIMEO_NEW,
SO_RCVTIMEO_NEW as SO_RCVTIMEO, SO_RCVTIMEO_OLD, SO_REUSEADDR, SO_SNDBUF, SO_SNDTIMEO_NEW,
SO_SNDTIMEO_NEW as SO_SNDTIMEO, SO_SNDTIMEO_OLD, SO_TYPE, TCP_KEEPCNT, TCP_KEEPIDLE,
TCP_KEEPINTVL, TCP_NODELAY, TCP_USER_TIMEOUT,
IPV6_DROP_MEMBERSHIP, IPV6_FREEBIND, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_LOOP,
IPV6_RECVTCLASS, IPV6_TCLASS, IPV6_UNICAST_HOPS, IPV6_V6ONLY, IP_ADD_MEMBERSHIP,
IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP, IP_FREEBIND,
IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_RECVTOS, IP_TOS, IP_TTL, MSG_CMSG_CLOEXEC,
MSG_CONFIRM, MSG_DONTROUTE, MSG_DONTWAIT, MSG_EOR, MSG_ERRQUEUE, MSG_MORE, MSG_NOSIGNAL,
MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, SCM_CREDENTIALS, SCM_RIGHTS, SHUT_RD, SHUT_RDWR,
SHUT_WR, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET, SOCK_STREAM, SOL_SOCKET,
SO_ACCEPTCONN, SO_BROADCAST, SO_COOKIE, SO_DOMAIN, SO_ERROR, SO_INCOMING_CPU, SO_KEEPALIVE,
SO_LINGER, SO_OOBINLINE, SO_PASSCRED, SO_PROTOCOL, SO_RCVBUF, SO_RCVTIMEO_NEW,
SO_RCVTIMEO_NEW as SO_RCVTIMEO, SO_RCVTIMEO_OLD, SO_REUSEADDR, SO_REUSEPORT, SO_SNDBUF,
SO_SNDTIMEO_NEW, SO_SNDTIMEO_NEW as SO_SNDTIMEO, SO_SNDTIMEO_OLD, SO_TYPE, TCP_CONGESTION,
TCP_CORK, TCP_KEEPCNT, TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_NODELAY, TCP_QUICKACK,
TCP_THIN_LINEAR_TIMEOUTS, TCP_USER_TIMEOUT,
},
netlink::*,
};

// TODO: Modify linux-raw-sys to include these.
pub(crate) const IP6T_SO_ORIGINAL_DST: u32 = 80;
pub(crate) const SO_ORIGINAL_DST: u32 = 80;

// Cast away bindgen's `enum` type to make these consistent with the other
// `setsockopt`/`getsockopt` level values.
#[cfg(feature = "net")]
Expand Down
Loading

0 comments on commit 495a1c7

Please sign in to comment.