Skip to main content

tokio_quiche/socket/
capabilities.rs

1// Copyright (C) 2025, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27#[cfg(target_os = "linux")]
28mod linux_imports {
29    pub use libc::c_int;
30    pub use libc::c_void;
31    pub use libc::sock_txtime;
32    pub use libc::socklen_t;
33    pub use libc::IPPROTO_IP;
34    pub use libc::IPPROTO_IPV6;
35    pub use libc::IPV6_MTU_DISCOVER;
36    pub use libc::IPV6_PMTUDISC_PROBE;
37    pub use libc::IP_MTU_DISCOVER;
38    pub use libc::IP_PMTUDISC_PROBE;
39    pub use libc::SOL_SOCKET;
40    pub use libc::SO_RCVMARK;
41    pub use nix::errno::Errno;
42    pub use nix::sys::socket::getsockopt;
43    pub use nix::sys::socket::setsockopt;
44    pub use nix::sys::socket::sockopt::IpFreebind;
45    pub use nix::sys::socket::sockopt::IpTransparent;
46    pub use nix::sys::socket::sockopt::Ipv4OrigDstAddr;
47    pub use nix::sys::socket::sockopt::Ipv4PacketInfo;
48    pub use nix::sys::socket::sockopt::Ipv6OrigDstAddr;
49    pub use nix::sys::socket::sockopt::Ipv6RecvPacketInfo;
50    #[cfg(feature = "perf-quic-listener-metrics")]
51    pub use nix::sys::socket::sockopt::ReceiveTimestampns;
52    pub use nix::sys::socket::sockopt::RxqOvfl;
53    pub use nix::sys::socket::sockopt::TxTime;
54    pub use nix::sys::socket::sockopt::UdpGroSegment;
55    pub use nix::sys::socket::sockopt::UdpGsoSegment;
56    pub use nix::sys::socket::SetSockOpt;
57    pub use std::io;
58    pub use std::os::fd::AsFd;
59    pub use std::os::fd::AsRawFd;
60    pub use std::os::fd::BorrowedFd;
61}
62
63#[cfg(target_os = "linux")]
64use linux_imports::*;
65
66#[cfg(target_os = "linux")]
67#[derive(Clone)]
68struct IpMtuDiscoverProbe;
69
70#[cfg(target_os = "linux")]
71impl SetSockOpt for IpMtuDiscoverProbe {
72    type Val = ();
73
74    fn set<F: AsFd>(&self, fd: &F, _val: &Self::Val) -> nix::Result<()> {
75        let pmtud_mode: c_int = IP_PMTUDISC_PROBE;
76        let ret = unsafe {
77            libc::setsockopt(
78                fd.as_fd().as_raw_fd(),
79                IPPROTO_IP,
80                IP_MTU_DISCOVER,
81                &pmtud_mode as *const c_int as *const c_void,
82                std::mem::size_of::<c_int>() as socklen_t,
83            )
84        };
85
86        match ret {
87            0 => Ok(()),
88            _ => Err(Errno::last()),
89        }
90    }
91}
92
93#[cfg(target_os = "linux")]
94#[derive(Clone)]
95struct Ipv6MtuDiscoverProbe;
96
97#[cfg(target_os = "linux")]
98impl SetSockOpt for Ipv6MtuDiscoverProbe {
99    type Val = ();
100
101    fn set<F: AsFd>(&self, fd: &F, _val: &Self::Val) -> nix::Result<()> {
102        let pmtud_mode: c_int = IPV6_PMTUDISC_PROBE;
103        let ret = unsafe {
104            libc::setsockopt(
105                fd.as_fd().as_raw_fd(),
106                IPPROTO_IPV6,
107                IPV6_MTU_DISCOVER,
108                &pmtud_mode as *const c_int as *const c_void,
109                std::mem::size_of::<c_int>() as socklen_t,
110            )
111        };
112
113        match ret {
114            0 => Ok(()),
115            _ => Err(Errno::last()),
116        }
117    }
118}
119
120#[cfg(target_os = "linux")]
121#[derive(Clone)]
122struct RcvMark;
123
124#[cfg(target_os = "linux")]
125impl SetSockOpt for RcvMark {
126    type Val = ();
127
128    fn set<F: AsFd>(&self, fd: &F, _val: &Self::Val) -> nix::Result<()> {
129        // https://elixir.bootlin.com/linux/v6.17/source/net/core/sock.c#L1523
130        const ENABLE_SOCKOPT: i32 = 1;
131
132        let ret = unsafe {
133            libc::setsockopt(
134                fd.as_fd().as_raw_fd(),
135                SOL_SOCKET,
136                SO_RCVMARK,
137                &ENABLE_SOCKOPT as *const c_int as *const c_void,
138                std::mem::size_of::<c_int>() as socklen_t,
139            )
140        };
141
142        match ret {
143            0 => Ok(()),
144            _ => Err(Errno::last()),
145        }
146    }
147}
148
149/// Builder to enable Linux sockopts which improve QUIC performance.
150#[cfg(target_os = "linux")]
151pub struct SocketCapabilitiesBuilder<'s> {
152    socket: BorrowedFd<'s>,
153    cap: SocketCapabilities,
154}
155
156#[cfg(target_os = "linux")]
157impl<'s> SocketCapabilitiesBuilder<'s> {
158    /// Creates a new sockopt builder for `socket`.
159    pub fn new<S: AsFd>(socket: &'s S) -> Self {
160        Self {
161            socket: socket.as_fd(),
162            cap: Default::default(),
163        }
164    }
165
166    /// Enables [`UDP_SEGMENT`](https://man7.org/linux/man-pages/man7/udp.7.html),
167    /// a generic segmentation offload (GSO).
168    ///
169    /// GSO improves transmit performance by treating multiple sequential UDP
170    /// packets as a single entity in the kernel. Segmentation into
171    /// individual packets happens in the NIC, if it supports GSO. The
172    /// parameter specifies the packet size.
173    pub fn gso(&mut self) -> io::Result<()> {
174        // Initialize GSO with the maximum segment size so later increases to
175        // `max_send_udp_payload_size` do not exceed the initial setting.
176        //
177        // https://elixir.bootlin.com/linux/v6.14.6/source/net/ipv4/udp.c#L2998
178        // https://elixir.bootlin.com/linux/v6.14.6/source/include/vdso/limits.h#L5
179        setsockopt(&self.socket.as_fd(), UdpGsoSegment, &(u16::MAX as i32))?;
180        self.cap.has_gso = true;
181        Ok(())
182    }
183
184    /// Enables [`SO_RXQ_OVFL`](https://man7.org/linux/man-pages/man7/socket.7.html),
185    /// which reports dropped packets due to insufficient buffer space.
186    pub fn check_udp_drop(&mut self) -> io::Result<()> {
187        setsockopt(&self.socket.as_fd(), RxqOvfl, &1)?;
188
189        self.cap.check_udp_drop = true;
190        Ok(())
191    }
192
193    /// Enables [`SO_TXTIME`](https://man7.org/linux/man-pages/man8/tc-etf.8.html)
194    /// to control packet transmit timestamps for QUIC pacing.
195    pub fn txtime(&mut self) -> io::Result<()> {
196        let cfg = sock_txtime {
197            clockid: libc::CLOCK_MONOTONIC,
198            flags: 0,
199        };
200        setsockopt(&self.socket.as_fd(), TxTime, &cfg)?;
201
202        self.cap.has_txtime = true;
203        Ok(())
204    }
205
206    /// Enables [`SO_TIMESTAMPNS`](https://man7.org/linux/man-pages/man7/socket.7.html),
207    /// which records a wall-clock timestamp for each received packet.
208    #[cfg(feature = "perf-quic-listener-metrics")]
209    pub fn rxtime(&mut self) -> io::Result<()> {
210        setsockopt(&self.socket.as_fd(), ReceiveTimestampns, &true)?;
211
212        self.cap.has_rxtime = true;
213        Ok(())
214    }
215
216    /// Enables [`UDP_GRO`](https://man7.org/linux/man-pages/man7/udp.7.html),
217    /// a generic receive offload (GRO).
218    ///
219    /// GRO improves receive performance by allowing the kernel to yield
220    /// multiple UDP packets in one [`recvmsg(2)`](https://man7.org/linux/man-pages/man2/recv.2.html)
221    /// call. It is the equivalent of GSO for the receive path.
222    pub fn gro(&mut self) -> io::Result<()> {
223        UdpGroSegment.set(&self.socket.as_fd(), &true)?;
224
225        self.cap.has_gro = true;
226        Ok(())
227    }
228
229    /// Enables [`IP_PKTINFO`](https://man7.org/linux/man-pages/man7/ip.7.html)
230    /// to control the source IP in outbound IPv4 packets.
231    pub fn ipv4_pktinfo(&mut self) -> io::Result<()> {
232        setsockopt(&self.socket.as_fd(), Ipv4PacketInfo, &true)?;
233
234        self.cap.has_ippktinfo = true;
235        Ok(())
236    }
237
238    /// Enables [`IP_RECVORIGDSTADDR`](https://man7.org/linux/man-pages/man7/ip.7.html),
239    /// which reports each packet's real IPv4 destination address.
240    ///
241    /// This can be different from the socket's local address due to netfilter
242    /// TPROXY rules or eBPF redirects.
243    pub fn ipv4_recvorigdstaddr(&mut self) -> io::Result<()> {
244        setsockopt(&self.socket.as_fd(), Ipv4OrigDstAddr, &true)?;
245
246        self.cap.has_iprecvorigdstaddr = true;
247        Ok(())
248    }
249
250    /// Enables [`IPV6_RECVPKTINFO`](https://man7.org/linux/man-pages/man7/ipv6.7.html)
251    /// to control the source IP in outbound IPv6 packets.
252    pub fn ipv6_pktinfo(&mut self) -> io::Result<()> {
253        setsockopt(&self.socket.as_fd(), Ipv6RecvPacketInfo, &true)?;
254
255        self.cap.has_ipv6pktinfo = true;
256        Ok(())
257    }
258
259    /// Enables [`IPV6_RECVORIGDSTADDR`](https://elixir.bootlin.com/linux/v6.12/source/net/ipv6/datagram.c#L722-L743),
260    /// which reports each packet's real IPv6 destination address.
261    ///
262    /// This can be different from the socket's local address due to netfilter
263    /// TPROXY rules or eBPF redirects.
264    pub fn ipv6_recvorigdstaddr(&mut self) -> io::Result<()> {
265        setsockopt(&self.socket.as_fd(), Ipv6OrigDstAddr, &true)?;
266
267        self.cap.has_ipv6recvorigdstaddr = true;
268        Ok(())
269    }
270
271    /// Sets [`IP_MTU_DISCOVER`](https://man7.org/linux/man-pages/man7/ip.7.html), to
272    /// `IP_PMTUDISC_PROBE`, which disables kernel PMTUD and sets the `DF`
273    /// (Don't Fragment) flag.
274    pub fn ip_mtu_discover_probe(&mut self) -> io::Result<()> {
275        setsockopt(&self.socket.as_fd(), IpMtuDiscoverProbe, &())?;
276
277        self.cap.has_ip_mtu_discover_probe = true;
278        Ok(())
279    }
280
281    /// Sets [`IPV6_MTU_DISCOVER`](https://man7.org/linux/man-pages/man7/ipv6.7.html), to
282    /// `IPV6_PMTUDISC_PROBE`, which disables kernel PMTUD and sets the `DF`
283    /// (Don't Fragment) flag.
284    pub fn ipv6_mtu_discover_probe(&mut self) -> io::Result<()> {
285        setsockopt(&self.socket.as_fd(), Ipv6MtuDiscoverProbe, &())?;
286
287        self.cap.has_ipv6_mtu_discover_probe = true;
288        Ok(())
289    }
290
291    /// Tests whether [`IP_FREEBIND`](https://man7.org/linux/man-pages/man7/ip.7.html)
292    /// or [`IP_TRANSPARENT`](https://man7.org/linux/man-pages/man7/ip.7.html) are
293    /// enabled for this socket.
294    ///
295    /// # Warning
296    /// These sockopts require elevated permissions to enable, so the builder
297    /// will only check their status. **If neither of them is enabled, the
298    /// `PKTINFO` sockopts will cause errors when sending packets.**
299    pub fn allows_nonlocal_source(&self) -> io::Result<bool> {
300        Ok(getsockopt(&self.socket.as_fd(), IpFreebind)? ||
301            getsockopt(&self.socket.as_fd(), IpTransparent)?)
302    }
303
304    pub fn rcvmark(&mut self) -> io::Result<()> {
305        setsockopt(&self.socket.as_fd(), RcvMark, &())?;
306
307        self.cap.has_mark = true;
308        Ok(())
309    }
310
311    /// Consumes the builder and returns the configured [`SocketCapabilities`].
312    pub fn finish(self) -> SocketCapabilities {
313        self.cap
314    }
315}
316
317// TODO(erittenhouse): use `dgram`'s SocketCapabilities when we migrate over
318#[cfg_attr(not(target_os = "linux"), expect(rustdoc::broken_intra_doc_links))]
319/// Indicators of sockopts configured for a socket.
320///
321/// On Linux, a socket can be configured using a [`SocketCapabilitiesBuilder`],
322/// which returns the sockopts that were applied successfully. By default, all
323/// options are assumed to be disabled (including on OSes besides Linux).
324///
325/// As a shortcut, you may call `apply_all_and_get_compatibility` to apply the
326/// maxmimum set of capabilities supported by this crate. The result will
327/// indicate which options were actually enabled.
328#[derive(Debug, Default)]
329pub struct SocketCapabilities {
330    /// Indicates if the socket has `UDP_SEGMENT` enabled.
331    pub(crate) has_gso: bool,
332
333    /// Indicates if the socket has `SO_RXQ_OVFL` set.
334    // NOTE: RX-side sockopts are `expect(dead_code)` because we check for
335    // received cmsgs directly
336    #[cfg_attr(not(target_os = "linux"), expect(dead_code))]
337    pub(crate) check_udp_drop: bool,
338
339    /// Indicates if the socket was configured with `SO_TXTIME`.
340    pub(crate) has_txtime: bool,
341
342    /// Indicates if the socket has `SO_TIMESTAMPNS` enabled.
343    #[cfg_attr(
344        not(all(target_os = "linux", feature = "perf-quic-listener-metrics")),
345        expect(dead_code)
346    )]
347    pub(crate) has_rxtime: bool,
348
349    /// Indicates if the socket has `UDP_GRO` enabled.
350    #[cfg_attr(not(target_os = "linux"), expect(dead_code))]
351    pub(crate) has_gro: bool,
352
353    /// Indicates if the socket has `IP_PKTINFO` set.
354    pub(crate) has_ippktinfo: bool,
355
356    /// Indicates if the socket has `IP_RECVORIGDSTADDR` set.
357    #[cfg_attr(not(target_os = "linux"), expect(dead_code))]
358    pub(crate) has_iprecvorigdstaddr: bool,
359
360    /// Indicates if the socket has `IPV6_RECVPKTINFO` set.
361    pub(crate) has_ipv6pktinfo: bool,
362
363    /// Indicates if the socket has `IPV6_RECVORIGDSTADDR` set.
364    #[cfg_attr(not(target_os = "linux"), expect(dead_code))]
365    pub(crate) has_ipv6recvorigdstaddr: bool,
366
367    // Indicates if the socket has `IP_MTU_DISCOVER` set to `IP_PMTUDISC_PROBE`.
368    #[cfg_attr(not(target_os = "linux"), expect(dead_code))]
369    pub(crate) has_ip_mtu_discover_probe: bool,
370
371    // Indicates if the socket has `IPV6_MTU_DISCOVER` set to
372    // `IPV6_PMTUDISC_PROBE`.
373    #[cfg_attr(not(target_os = "linux"), expect(dead_code))]
374    pub(crate) has_ipv6_mtu_discover_probe: bool,
375
376    /// Indicates if the socket is set to receive `SO_MARK` messages via
377    /// `SO_RCVMARK`.
378    #[cfg_attr(not(target_os = "linux"), expect(dead_code))]
379    pub(crate) has_mark: bool,
380}
381
382impl SocketCapabilities {
383    /// Tries to enable all supported sockopts and returns indicators
384    /// of which settings were successfully applied.
385    #[cfg(target_os = "linux")]
386    pub fn apply_all_and_get_compatibility<S>(socket: &S) -> Self
387    where
388        S: AsFd,
389    {
390        let mut b = SocketCapabilitiesBuilder::new(socket);
391        let _ = b.gso();
392        let _ = b.check_udp_drop();
393        let _ = b.txtime();
394        #[cfg(feature = "perf-quic-listener-metrics")]
395        let _ = b.rxtime();
396        let _ = b.gro();
397        let _ = b.rcvmark();
398
399        // We can't determine if this is an IPv4 or IPv6 socket, so try setting
400        // the relevant options for both
401        let _ = b.ip_mtu_discover_probe();
402        let _ = b.ipv6_mtu_discover_probe();
403        if let Ok(true) = b.allows_nonlocal_source() {
404            let _ = b.ipv4_pktinfo();
405            let _ = b.ipv4_recvorigdstaddr();
406            let _ = b.ipv6_pktinfo();
407            let _ = b.ipv6_recvorigdstaddr();
408        }
409        b.finish()
410    }
411}