Skip to content

Commit f9105fd

Browse files
committed
Add support for SO_TXTIME / SCM_TXTIME
This is an undocumented linux feature that lets you time outgoing packets.
1 parent 4d5e6bf commit f9105fd

File tree

10 files changed

+288
-3
lines changed

10 files changed

+288
-3
lines changed

src/backend/libc/net/sockopt.rs

+33
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,13 @@ use crate::net::Protocol;
4949
use crate::net::RawProtocol;
5050
#[cfg(any(linux_kernel, target_os = "fuchsia"))]
5151
use crate::net::SocketAddrV4;
52+
#[cfg(all(target_os = "linux", feature = "time"))]
53+
use crate::net::TxTimeFlags;
5254
use crate::net::{Ipv4Addr, Ipv6Addr, SocketType};
5355
#[cfg(linux_kernel)]
5456
use crate::net::{SocketAddrV6, UCred};
57+
#[cfg(all(target_os = "linux", feature = "time"))]
58+
use crate::time::ClockId;
5559
use crate::utils::as_mut_ptr;
5660
#[cfg(feature = "alloc")]
5761
#[cfg(any(
@@ -1046,6 +1050,35 @@ pub(crate) fn socket_peercred(fd: BorrowedFd<'_>) -> io::Result<UCred> {
10461050
getsockopt(fd, c::SOL_SOCKET, c::SO_PEERCRED)
10471051
}
10481052

1053+
#[cfg(all(target_os = "linux", feature = "time"))]
1054+
#[inline]
1055+
pub(crate) fn set_txtime(
1056+
fd: BorrowedFd<'_>,
1057+
clockid: ClockId,
1058+
flags: TxTimeFlags,
1059+
) -> io::Result<()> {
1060+
setsockopt(
1061+
fd,
1062+
c::SOL_SOCKET,
1063+
c::SO_TXTIME,
1064+
c::sock_txtime {
1065+
clockid: clockid as _,
1066+
flags: flags.bits(),
1067+
},
1068+
)
1069+
}
1070+
1071+
#[cfg(all(target_os = "linux", feature = "time"))]
1072+
#[inline]
1073+
pub(crate) fn get_txtime(fd: BorrowedFd<'_>) -> io::Result<(ClockId, TxTimeFlags)> {
1074+
let txtime: c::sock_txtime = getsockopt(fd, c::SOL_SOCKET, c::SO_TXTIME)?;
1075+
1076+
Ok((
1077+
txtime.clockid.try_into().map_err(|_| io::Errno::RANGE)?,
1078+
TxTimeFlags::from_bits(txtime.flags).ok_or(io::Errno::RANGE)?,
1079+
))
1080+
}
1081+
10491082
#[cfg(target_os = "linux")]
10501083
#[inline]
10511084
pub(crate) fn set_xdp_umem_reg(fd: BorrowedFd<'_>, value: XdpUmemReg) -> io::Result<()> {

src/backend/linux_raw/c.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ pub(crate) use linux_raw_sys::{
9797
SO_DOMAIN, SO_ERROR, SO_INCOMING_CPU, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE,
9898
SO_ORIGINAL_DST, SO_PASSCRED, SO_PROTOCOL, SO_RCVBUF, SO_RCVBUFFORCE, SO_RCVTIMEO_NEW,
9999
SO_RCVTIMEO_NEW as SO_RCVTIMEO, SO_RCVTIMEO_OLD, SO_REUSEADDR, SO_REUSEPORT, SO_SNDBUF,
100-
SO_SNDBUFFORCE, SO_SNDTIMEO_NEW, SO_SNDTIMEO_NEW as SO_SNDTIMEO, SO_SNDTIMEO_OLD, SO_TYPE,
101-
TCP_CONGESTION, TCP_CORK, TCP_KEEPCNT, TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_NODELAY,
102-
TCP_QUICKACK, TCP_THIN_LINEAR_TIMEOUTS, TCP_USER_TIMEOUT,
100+
SO_SNDBUFFORCE, SO_SNDTIMEO_NEW, SO_SNDTIMEO_NEW as SO_SNDTIMEO, SO_SNDTIMEO_OLD,
101+
SO_TXTIME, SO_TYPE, TCP_CONGESTION, TCP_CORK, TCP_KEEPCNT, TCP_KEEPIDLE, TCP_KEEPINTVL,
102+
TCP_NODELAY, TCP_QUICKACK, TCP_THIN_LINEAR_TIMEOUTS, TCP_USER_TIMEOUT,
103103
},
104104
netlink::*,
105105
xdp::{
@@ -114,6 +114,22 @@ pub(crate) use linux_raw_sys::{
114114
},
115115
};
116116

117+
#[cfg(feature = "time")]
118+
pub use linux_raw_sys::general::__kernel_clockid_t;
119+
120+
// TODO: https://github.com/sunfishcode/linux-raw-sys/pull/153
121+
#[cfg(all(feature = "net", feature = "time"))]
122+
pub(crate) const SOF_TXTIME_DEADLINE_MODE: u32 = 0x1;
123+
#[cfg(all(feature = "net", feature = "time"))]
124+
pub(crate) const SOF_TXTIME_REPORT_ERRORS: u32 = 0x2;
125+
#[cfg(all(feature = "net", feature = "time"))]
126+
#[repr(C)]
127+
#[derive(Debug, Copy, Clone)]
128+
pub(crate) struct sock_txtime {
129+
pub(crate) clockid: __kernel_clockid_t,
130+
pub(crate) flags: u32,
131+
}
132+
117133
// Cast away bindgen's `enum` type to make these consistent with the other
118134
// `setsockopt`/`getsockopt` level values.
119135
#[cfg(feature = "net")]

src/backend/linux_raw/net/sockopt.rs

+33
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77

88
use crate::backend::c;
99
use crate::backend::conv::{by_mut, c_uint, ret, socklen_t};
10+
#[cfg(all(target_os = "linux", feature = "time"))]
11+
use crate::clockid::ClockId;
1012
use crate::fd::BorrowedFd;
1113
#[cfg(feature = "alloc")]
1214
use crate::ffi::CStr;
1315
use crate::io;
1416
use crate::net::sockopt::Timeout;
1517
#[cfg(target_os = "linux")]
1618
use crate::net::xdp::{XdpMmapOffsets, XdpOptionsFlags, XdpRingOffset, XdpStatistics, XdpUmemReg};
19+
#[cfg(all(target_os = "linux", feature = "time"))]
20+
use crate::net::TxTimeFlags;
1721
use crate::net::{
1822
AddressFamily, Ipv4Addr, Ipv6Addr, Protocol, RawProtocol, SocketAddrBuf, SocketAddrV4,
1923
SocketAddrV6, SocketType, UCred,
@@ -848,6 +852,35 @@ pub(crate) fn socket_peercred(fd: BorrowedFd<'_>) -> io::Result<UCred> {
848852
getsockopt(fd, c::SOL_SOCKET, linux_raw_sys::net::SO_PEERCRED)
849853
}
850854

855+
#[cfg(all(target_os = "linux", feature = "time"))]
856+
#[inline]
857+
pub(crate) fn set_txtime(
858+
fd: BorrowedFd<'_>,
859+
clockid: ClockId,
860+
flags: TxTimeFlags,
861+
) -> io::Result<()> {
862+
setsockopt(
863+
fd,
864+
c::SOL_SOCKET,
865+
c::SO_TXTIME,
866+
c::sock_txtime {
867+
clockid: clockid as _,
868+
flags: flags.bits(),
869+
},
870+
)
871+
}
872+
873+
#[cfg(all(target_os = "linux", feature = "time"))]
874+
#[inline]
875+
pub(crate) fn get_txtime(fd: BorrowedFd<'_>) -> io::Result<(ClockId, TxTimeFlags)> {
876+
let txtime: c::sock_txtime = getsockopt(fd, c::SOL_SOCKET, c::SO_TXTIME)?;
877+
878+
Ok((
879+
txtime.clockid.try_into().map_err(|_| io::Errno::RANGE)?,
880+
TxTimeFlags::from_bits(txtime.flags).ok_or(io::Errno::RANGE)?,
881+
))
882+
}
883+
851884
#[cfg(target_os = "linux")]
852885
#[inline]
853886
pub(crate) fn set_xdp_umem_reg(fd: BorrowedFd<'_>, value: XdpUmemReg) -> io::Result<()> {

src/clockid.rs

+61
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::backend::c;
22
use crate::fd::BorrowedFd;
3+
use crate::io;
34

45
/// `CLOCK_*` constants for use with [`clock_gettime`].
56
///
@@ -94,6 +95,51 @@ pub enum ClockId {
9495
BoottimeAlarm = bitcast!(c::CLOCK_BOOTTIME_ALARM),
9596
}
9697

98+
#[cfg(not(any(apple, target_os = "wasi")))]
99+
impl TryFrom<c::clockid_t> for ClockId {
100+
type Error = io::Errno;
101+
102+
fn try_from(value: c::clockid_t) -> Result<Self, Self::Error> {
103+
match value {
104+
c::CLOCK_REALTIME => Ok(ClockId::Realtime),
105+
c::CLOCK_MONOTONIC => Ok(ClockId::Monotonic),
106+
#[cfg(any(freebsdlike, target_os = "openbsd"))]
107+
c::CLOCK_UPTIME => Ok(ClockId::Uptime),
108+
#[cfg(not(any(
109+
solarish,
110+
target_os = "horizon",
111+
target_os = "netbsd",
112+
target_os = "redox",
113+
target_os = "vita"
114+
)))]
115+
c::CLOCK_PROCESS_CPUTIME_ID => Ok(ClockId::ProcessCPUTime),
116+
#[cfg(not(any(
117+
solarish,
118+
target_os = "horizon",
119+
target_os = "netbsd",
120+
target_os = "redox",
121+
target_os = "vita"
122+
)))]
123+
c::CLOCK_THREAD_CPUTIME_ID => Ok(ClockId::ThreadCPUTime),
124+
#[cfg(any(linux_kernel, target_os = "freebsd"))]
125+
c::CLOCK_REALTIME_COARSE => Ok(ClockId::RealtimeCoarse),
126+
#[cfg(any(linux_kernel, target_os = "freebsd"))]
127+
c::CLOCK_MONOTONIC_COARSE => Ok(ClockId::MonotonicCoarse),
128+
#[cfg(linux_kernel)]
129+
c::CLOCK_MONOTONIC_RAW => Ok(ClockId::MonotonicRaw),
130+
#[cfg(linux_kernel)]
131+
c::CLOCK_REALTIME_ALARM => Ok(ClockId::RealtimeAlarm),
132+
#[cfg(all(linux_kernel, feature = "linux_4_11"))]
133+
c::CLOCK_TAI => Ok(ClockId::Tai),
134+
#[cfg(any(linux_kernel, target_os = "fuchsia", target_os = "openbsd"))]
135+
c::CLOCK_BOOTTIME => Ok(ClockId::Boottime),
136+
#[cfg(any(linux_kernel, target_os = "fuchsia"))]
137+
c::CLOCK_BOOTTIME_ALARM => Ok(ClockId::BoottimeAlarm),
138+
_ => Err(io::Errno::RANGE),
139+
}
140+
}
141+
}
142+
97143
/// `CLOCK_*` constants for use with [`clock_gettime`].
98144
///
99145
/// These constants are always supported at runtime, so `clock_gettime` never
@@ -124,6 +170,21 @@ pub enum ClockId {
124170
ThreadCPUTime = c::CLOCK_THREAD_CPUTIME_ID,
125171
}
126172

173+
#[cfg(apple)]
174+
impl TryFrom<c::clockid_t> for ClockId {
175+
type Error = io::Errno;
176+
177+
fn try_from(value: c::clockid_t) -> Result<Self, Self::Error> {
178+
match value {
179+
c::CLOCK_REALTIME => Ok(ClockId::Realtime),
180+
c::CLOCK_MONOTONIC => Ok(ClockId::Monotonic),
181+
c::CLOCK_PROCESS_CPUTIME_ID => Ok(ClockId::ProcessCPUTime),
182+
c::CLOCK_THREAD_CPUTIME_ID => Ok(ClockId::ThreadCPUTime),
183+
_ => Err(io::Errno::RANGE),
184+
}
185+
}
186+
}
187+
127188
/// `CLOCK_*` constants for use with [`clock_gettime_dynamic`].
128189
///
129190
/// These constants may be unsupported at runtime, depending on the OS version,

src/net/send_recv/msg.rs

+26
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ macro_rules! cmsg_space {
6666
$len * ::core::mem::size_of::<$crate::net::UCred>(),
6767
)
6868
};
69+
(TxTime($len:expr)) => {
70+
$crate::net::__cmsg_space(
71+
$len * ::core::mem::size_of::<u64>(),
72+
)
73+
};
6974

7075
// Combo Rules
7176
($firstid:ident($firstex:expr), $($restid:ident($restex:expr)),*) => {{
@@ -94,6 +99,11 @@ macro_rules! cmsg_aligned_space {
9499
$len * ::core::mem::size_of::<$crate::net::UCred>(),
95100
)
96101
};
102+
(TxTime($len:expr)) => {
103+
$crate::net::__cmsg_aligned_space(
104+
$len * ::core::mem::size_of::<u64>(),
105+
)
106+
};
97107

98108
// Combo Rules
99109
($firstid:ident($firstex:expr), $($restid:ident($restex:expr)),*) => {{
@@ -138,6 +148,13 @@ pub enum SendAncillaryMessage<'slice, 'fd> {
138148
#[cfg(linux_kernel)]
139149
#[doc(alias = "SCM_CREDENTIAL")]
140150
ScmCredentials(UCred),
151+
/// Transmission time, in nanoseconds. The value will be interpreted by
152+
/// whichever clock was configured on the socket with [set_txtime].
153+
///
154+
/// [set_txtime]: (super::sockopt::set_txtime).
155+
#[cfg(target_os = "linux")]
156+
#[doc(alias = "SCM_TXTIME")]
157+
TxTime(u64),
141158
}
142159

143160
impl SendAncillaryMessage<'_, '_> {
@@ -149,6 +166,8 @@ impl SendAncillaryMessage<'_, '_> {
149166
Self::ScmRights(slice) => cmsg_space!(ScmRights(slice.len())),
150167
#[cfg(linux_kernel)]
151168
Self::ScmCredentials(_) => cmsg_space!(ScmCredentials(1)),
169+
#[cfg(target_os = "linux")]
170+
Self::TxTime(_) => cmsg_space!(TxTime(1)),
152171
}
153172
}
154173
}
@@ -290,6 +309,13 @@ impl<'buf, 'slice, 'fd> SendAncillaryBuffer<'buf, 'slice, 'fd> {
290309
};
291310
self.push_ancillary(ucred_bytes, c::SOL_SOCKET as _, c::SCM_CREDENTIALS as _)
292311
}
312+
#[cfg(target_os = "linux")]
313+
SendAncillaryMessage::TxTime(tx_time) => {
314+
let tx_time_bytes = unsafe {
315+
slice::from_raw_parts(addr_of!(tx_time).cast::<u8>(), size_of_val(&tx_time))
316+
};
317+
self.push_ancillary(tx_time_bytes, c::SOL_SOCKET as _, c::SO_TXTIME as _)
318+
}
293319
}
294320
}
295321

src/net/sockopt.rs

+18
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@
143143
#![doc(alias = "getsockopt")]
144144
#![doc(alias = "setsockopt")]
145145

146+
#[cfg(all(target_os = "linux", feature = "time"))]
147+
use crate::clockid::ClockId;
146148
#[cfg(target_os = "linux")]
147149
use crate::net::xdp::{XdpMmapOffsets, XdpOptionsFlags, XdpStatistics, XdpUmemReg};
148150
#[cfg(not(any(
@@ -171,6 +173,8 @@ use crate::net::Protocol;
171173
use crate::net::SocketAddrV4;
172174
#[cfg(linux_kernel)]
173175
use crate::net::SocketAddrV6;
176+
#[cfg(all(target_os = "linux", feature = "time"))]
177+
use crate::net::TxTimeFlags;
174178
use crate::net::{Ipv4Addr, Ipv6Addr, SocketType};
175179
use crate::{backend, io};
176180
#[cfg(feature = "alloc")]
@@ -1522,6 +1526,20 @@ pub fn socket_peercred<Fd: AsFd>(fd: Fd) -> io::Result<super::UCred> {
15221526
backend::net::sockopt::socket_peercred(fd.as_fd())
15231527
}
15241528

1529+
/// `getsockopt(fd, SOL_SOCKET, SO_TXTIME)` — Get transmission timing configuration.
1530+
#[cfg(all(target_os = "linux", feature = "time"))]
1531+
#[doc(alias = "SO_TXTIME")]
1532+
pub fn get_txtime<Fd: AsFd>(fd: Fd) -> io::Result<(ClockId, TxTimeFlags)> {
1533+
backend::net::sockopt::get_txtime(fd.as_fd())
1534+
}
1535+
1536+
/// `setsockopt(fd, SOL_SOCKET, SO_TXTIME)` — Configure transmission timing.
1537+
#[cfg(all(target_os = "linux", feature = "time"))]
1538+
#[doc(alias = "SO_TXTIME")]
1539+
pub fn set_txtime<Fd: AsFd>(fd: Fd, clockid: ClockId, flags: TxTimeFlags) -> io::Result<()> {
1540+
backend::net::sockopt::set_txtime(fd.as_fd(), clockid, flags)
1541+
}
1542+
15251543
/// `setsockopt(fd, SOL_XDP, XDP_UMEM_REG, value)`
15261544
///
15271545
/// On kernel versions only supporting v1, the flags are ignored.

src/net/types.rs

+16
Original file line numberDiff line numberDiff line change
@@ -1680,6 +1680,22 @@ bitflags! {
16801680
}
16811681
}
16821682

1683+
#[cfg(all(target_os = "linux", feature = "time"))]
1684+
bitflags! {
1685+
/// Flags for use with [`set_txtime`].
1686+
///
1687+
/// [`set_txtime`]: crate::net::sockopt::set_txtime
1688+
/// [`get_txtime`]: crate::net::sockopt::get_txtime
1689+
#[repr(transparent)]
1690+
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
1691+
pub struct TxTimeFlags: u32 {
1692+
/// `SOF_TXTIME_DEADLINE_MODE`
1693+
const DEADLINE_MODE = bitcast!(c::SOF_TXTIME_DEADLINE_MODE);
1694+
/// `SOF_TXTIME_REPORT_ERRORS`
1695+
const REPORT_ERRORS = bitcast!(c::SOF_TXTIME_REPORT_ERRORS);
1696+
}
1697+
}
1698+
16831699
/// `AF_XDP` related types and constants.
16841700
#[cfg(target_os = "linux")]
16851701
pub mod xdp {

tests/net/sockopt.rs

+18
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ use rustix::io;
99
target_env = "newlib"
1010
))]
1111
use rustix::net::ipproto;
12+
#[cfg(target_os = "linux")]
13+
use rustix::net::TxTimeFlags;
1214
use rustix::net::{sockopt, AddressFamily, SocketType};
15+
#[cfg(target_os = "linux")]
16+
use rustix::time::ClockId;
1317
use std::net::Ipv4Addr;
1418
use std::time::Duration;
1519

@@ -188,6 +192,20 @@ fn test_sockopts_socket(s: &OwnedFd) {
188192
sockopt::set_socket_nosigpipe(s, true).unwrap();
189193
assert_eq!(sockopt::socket_nosigpipe(s).unwrap(), true);
190194
}
195+
196+
// Check the initial value of `SO_TXTIME`, set it, and check it.
197+
#[cfg(target_os = "linux")]
198+
{
199+
assert_eq!(
200+
sockopt::get_txtime(s).unwrap(),
201+
(ClockId::Realtime, TxTimeFlags::empty())
202+
);
203+
sockopt::set_txtime(s, ClockId::Monotonic, TxTimeFlags::DEADLINE_MODE).unwrap();
204+
assert_eq!(
205+
sockopt::get_txtime(s).unwrap(),
206+
(ClockId::Monotonic, TxTimeFlags::DEADLINE_MODE)
207+
);
208+
}
191209
}
192210

193211
// Test `tcp` socket options.

0 commit comments

Comments
 (0)