rustix/backend/linux_raw/net/
sockopt.rs

1//! linux_raw syscalls supporting `rustix::net::sockopt`.
2//!
3//! # Safety
4//!
5//! See the `rustix::backend` module documentation for details.
6#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
7
8use crate::backend::c;
9use crate::backend::conv::{by_mut, c_uint, ret, socklen_t};
10use crate::fd::BorrowedFd;
11#[cfg(feature = "alloc")]
12use crate::ffi::CStr;
13use crate::io;
14use crate::net::sockopt::Timeout;
15#[cfg(target_os = "linux")]
16use crate::net::xdp::{XdpMmapOffsets, XdpOptionsFlags, XdpRingOffset, XdpStatistics, XdpUmemReg};
17use crate::net::{
18    AddressFamily, Ipv4Addr, Ipv6Addr, Protocol, RawProtocol, SocketAddrBuf, SocketAddrV4,
19    SocketAddrV6, SocketType, UCred,
20};
21#[cfg(feature = "alloc")]
22use alloc::borrow::ToOwned as _;
23#[cfg(feature = "alloc")]
24use alloc::string::String;
25use core::mem::{size_of, MaybeUninit};
26use core::time::Duration;
27use linux_raw_sys::general::{__kernel_old_timeval, __kernel_sock_timeval};
28use linux_raw_sys::net::{IPV6_MTU, IPV6_MULTICAST_IF, IP_MTU, IP_MULTICAST_IF};
29#[cfg(target_os = "linux")]
30use linux_raw_sys::xdp::{xdp_mmap_offsets, xdp_statistics, xdp_statistics_v1};
31#[cfg(target_arch = "x86")]
32use {
33    crate::backend::conv::{slice_just_addr, x86_sys},
34    crate::backend::reg::{ArgReg, SocketArg},
35    linux_raw_sys::net::{SYS_GETSOCKOPT, SYS_SETSOCKOPT},
36};
37
38#[inline]
39fn getsockopt<T: Copy>(fd: BorrowedFd<'_>, level: u32, optname: u32) -> io::Result<T> {
40    let mut optlen: c::socklen_t = size_of::<T>().try_into().unwrap();
41    debug_assert!(
42        optlen as usize >= size_of::<c::c_int>(),
43        "Socket APIs don't ever use `bool` directly"
44    );
45
46    let mut value = MaybeUninit::<T>::uninit();
47    getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;
48
49    assert_eq!(
50        optlen as usize,
51        size_of::<T>(),
52        "unexpected getsockopt size"
53    );
54
55    unsafe { Ok(value.assume_init()) }
56}
57
58#[inline]
59fn getsockopt_raw<T>(
60    fd: BorrowedFd<'_>,
61    level: u32,
62    optname: u32,
63    value: &mut MaybeUninit<T>,
64    optlen: &mut c::socklen_t,
65) -> io::Result<()> {
66    #[cfg(not(target_arch = "x86"))]
67    unsafe {
68        ret(syscall!(
69            __NR_getsockopt,
70            fd,
71            c_uint(level),
72            c_uint(optname),
73            value,
74            by_mut(optlen)
75        ))
76    }
77    #[cfg(target_arch = "x86")]
78    unsafe {
79        ret(syscall!(
80            __NR_socketcall,
81            x86_sys(SYS_GETSOCKOPT),
82            slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
83                fd.into(),
84                c_uint(level),
85                c_uint(optname),
86                value.into(),
87                by_mut(optlen),
88            ])
89        ))
90    }
91}
92
93#[inline]
94fn setsockopt<T: Copy>(fd: BorrowedFd<'_>, level: u32, optname: u32, value: T) -> io::Result<()> {
95    let optlen = size_of::<T>().try_into().unwrap();
96    debug_assert!(
97        optlen as usize >= size_of::<c::c_int>(),
98        "Socket APIs don't ever use `bool` directly"
99    );
100    setsockopt_raw(fd, level, optname, &value, optlen)
101}
102
103#[inline]
104fn setsockopt_raw<T>(
105    fd: BorrowedFd<'_>,
106    level: u32,
107    optname: u32,
108    ptr: *const T,
109    optlen: c::socklen_t,
110) -> io::Result<()> {
111    #[cfg(not(target_arch = "x86"))]
112    unsafe {
113        ret(syscall_readonly!(
114            __NR_setsockopt,
115            fd,
116            c_uint(level),
117            c_uint(optname),
118            ptr,
119            socklen_t(optlen)
120        ))
121    }
122    #[cfg(target_arch = "x86")]
123    unsafe {
124        ret(syscall_readonly!(
125            __NR_socketcall,
126            x86_sys(SYS_SETSOCKOPT),
127            slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
128                fd.into(),
129                c_uint(level),
130                c_uint(optname),
131                ptr.into(),
132                socklen_t(optlen),
133            ])
134        ))
135    }
136}
137
138#[inline]
139pub(crate) fn socket_type(fd: BorrowedFd<'_>) -> io::Result<SocketType> {
140    getsockopt(fd, c::SOL_SOCKET, c::SO_TYPE)
141}
142
143#[inline]
144pub(crate) fn set_socket_reuseaddr(fd: BorrowedFd<'_>, reuseaddr: bool) -> io::Result<()> {
145    setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEADDR, from_bool(reuseaddr))
146}
147
148#[inline]
149pub(crate) fn socket_reuseaddr(fd: BorrowedFd<'_>) -> io::Result<bool> {
150    getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEADDR).map(to_bool)
151}
152
153#[inline]
154pub(crate) fn set_socket_broadcast(fd: BorrowedFd<'_>, broadcast: bool) -> io::Result<()> {
155    setsockopt(fd, c::SOL_SOCKET, c::SO_BROADCAST, from_bool(broadcast))
156}
157
158#[inline]
159pub(crate) fn socket_broadcast(fd: BorrowedFd<'_>) -> io::Result<bool> {
160    getsockopt(fd, c::SOL_SOCKET, c::SO_BROADCAST).map(to_bool)
161}
162
163#[inline]
164pub(crate) fn set_socket_linger(fd: BorrowedFd<'_>, linger: Option<Duration>) -> io::Result<()> {
165    // Convert `linger` to seconds, rounding up.
166    let l_linger = if let Some(linger) = linger {
167        duration_to_secs(linger)?
168    } else {
169        0
170    };
171    let linger = c::linger {
172        l_onoff: c::c_int::from(linger.is_some()),
173        l_linger,
174    };
175    setsockopt(fd, c::SOL_SOCKET, c::SO_LINGER, linger)
176}
177
178#[inline]
179pub(crate) fn socket_linger(fd: BorrowedFd<'_>) -> io::Result<Option<Duration>> {
180    let linger: c::linger = getsockopt(fd, c::SOL_SOCKET, c::SO_LINGER)?;
181    Ok((linger.l_onoff != 0).then(|| Duration::from_secs(linger.l_linger as u64)))
182}
183
184#[inline]
185pub(crate) fn set_socket_passcred(fd: BorrowedFd<'_>, passcred: bool) -> io::Result<()> {
186    setsockopt(fd, c::SOL_SOCKET, c::SO_PASSCRED, from_bool(passcred))
187}
188
189#[inline]
190pub(crate) fn socket_passcred(fd: BorrowedFd<'_>) -> io::Result<bool> {
191    getsockopt(fd, c::SOL_SOCKET, c::SO_PASSCRED).map(to_bool)
192}
193
194#[inline]
195pub(crate) fn set_socket_timeout(
196    fd: BorrowedFd<'_>,
197    id: Timeout,
198    timeout: Option<Duration>,
199) -> io::Result<()> {
200    let time = duration_to_linux_sock_timeval(timeout)?;
201    let optname = match id {
202        Timeout::Recv => c::SO_RCVTIMEO_NEW,
203        Timeout::Send => c::SO_SNDTIMEO_NEW,
204    };
205    match setsockopt(fd, c::SOL_SOCKET, optname, time) {
206        Err(io::Errno::NOPROTOOPT) if c::SO_RCVTIMEO_NEW != c::SO_RCVTIMEO_OLD => {
207            set_socket_timeout_old(fd, id, timeout)
208        }
209        otherwise => otherwise,
210    }
211}
212
213/// Same as `set_socket_timeout` but uses `__kernel_old_timeval` instead of
214/// `__kernel_sock_timeval` and `_OLD` constants instead of `_NEW`.
215fn set_socket_timeout_old(
216    fd: BorrowedFd<'_>,
217    id: Timeout,
218    timeout: Option<Duration>,
219) -> io::Result<()> {
220    let time = duration_to_linux_old_timeval(timeout)?;
221    let optname = match id {
222        Timeout::Recv => c::SO_RCVTIMEO_OLD,
223        Timeout::Send => c::SO_SNDTIMEO_OLD,
224    };
225    setsockopt(fd, c::SOL_SOCKET, optname, time)
226}
227
228#[inline]
229pub(crate) fn socket_timeout(fd: BorrowedFd<'_>, id: Timeout) -> io::Result<Option<Duration>> {
230    let optname = match id {
231        Timeout::Recv => c::SO_RCVTIMEO_NEW,
232        Timeout::Send => c::SO_SNDTIMEO_NEW,
233    };
234    let time: __kernel_sock_timeval = match getsockopt(fd, c::SOL_SOCKET, optname) {
235        Err(io::Errno::NOPROTOOPT) if c::SO_RCVTIMEO_NEW != c::SO_RCVTIMEO_OLD => {
236            return socket_timeout_old(fd, id)
237        }
238        otherwise => otherwise?,
239    };
240    Ok(duration_from_linux_sock_timeval(time))
241}
242
243/// Same as `get_socket_timeout` but uses `__kernel_old_timeval` instead of
244/// `__kernel_sock_timeval` and `_OLD` constants instead of `_NEW`.
245fn socket_timeout_old(fd: BorrowedFd<'_>, id: Timeout) -> io::Result<Option<Duration>> {
246    let optname = match id {
247        Timeout::Recv => c::SO_RCVTIMEO_OLD,
248        Timeout::Send => c::SO_SNDTIMEO_OLD,
249    };
250    let time: __kernel_old_timeval = getsockopt(fd, c::SOL_SOCKET, optname)?;
251    Ok(duration_from_linux_old_timeval(time))
252}
253
254/// Convert a `__linux_sock_timeval` to a Rust `Option<Duration>`.
255#[inline]
256fn duration_from_linux_sock_timeval(time: __kernel_sock_timeval) -> Option<Duration> {
257    if time.tv_sec == 0 && time.tv_usec == 0 {
258        None
259    } else {
260        Some(Duration::from_secs(time.tv_sec as u64) + Duration::from_micros(time.tv_usec as u64))
261    }
262}
263
264/// Like `duration_from_linux_sock_timeval` but uses Linux's old 32-bit
265/// `__kernel_old_timeval`.
266fn duration_from_linux_old_timeval(time: __kernel_old_timeval) -> Option<Duration> {
267    if time.tv_sec == 0 && time.tv_usec == 0 {
268        None
269    } else {
270        Some(Duration::from_secs(time.tv_sec as u64) + Duration::from_micros(time.tv_usec as u64))
271    }
272}
273
274/// Convert a Rust `Option<Duration>` to a `__kernel_sock_timeval`.
275#[inline]
276fn duration_to_linux_sock_timeval(timeout: Option<Duration>) -> io::Result<__kernel_sock_timeval> {
277    Ok(match timeout {
278        Some(timeout) => {
279            if timeout == Duration::ZERO {
280                return Err(io::Errno::INVAL);
281            }
282            // `subsec_micros` rounds down, so we use `subsec_nanos` and
283            // manually round up.
284            let mut timeout = __kernel_sock_timeval {
285                tv_sec: timeout.as_secs().try_into().unwrap_or(i64::MAX),
286                tv_usec: ((timeout.subsec_nanos() + 999) / 1000) as _,
287            };
288            if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
289                timeout.tv_usec = 1;
290            }
291            timeout
292        }
293        None => __kernel_sock_timeval {
294            tv_sec: 0,
295            tv_usec: 0,
296        },
297    })
298}
299
300/// Like `duration_to_linux_sock_timeval` but uses Linux's old 32-bit
301/// `__kernel_old_timeval`.
302fn duration_to_linux_old_timeval(timeout: Option<Duration>) -> io::Result<__kernel_old_timeval> {
303    Ok(match timeout {
304        Some(timeout) => {
305            if timeout == Duration::ZERO {
306                return Err(io::Errno::INVAL);
307            }
308
309            // `subsec_micros` rounds down, so we use `subsec_nanos` and
310            // manually round up.
311            let mut timeout = __kernel_old_timeval {
312                tv_sec: timeout.as_secs().try_into().unwrap_or(c::c_long::MAX),
313                tv_usec: ((timeout.subsec_nanos() + 999) / 1000) as _,
314            };
315            if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
316                timeout.tv_usec = 1;
317            }
318            timeout
319        }
320        None => __kernel_old_timeval {
321            tv_sec: 0,
322            tv_usec: 0,
323        },
324    })
325}
326
327#[inline]
328pub(crate) fn socket_error(fd: BorrowedFd<'_>) -> io::Result<Result<(), io::Errno>> {
329    let err: c::c_int = getsockopt(fd, c::SOL_SOCKET, c::SO_ERROR)?;
330    Ok(if err == 0 {
331        Ok(())
332    } else {
333        Err(io::Errno::from_raw_os_error(err))
334    })
335}
336
337#[inline]
338pub(crate) fn set_socket_keepalive(fd: BorrowedFd<'_>, keepalive: bool) -> io::Result<()> {
339    setsockopt(fd, c::SOL_SOCKET, c::SO_KEEPALIVE, from_bool(keepalive))
340}
341
342#[inline]
343pub(crate) fn socket_keepalive(fd: BorrowedFd<'_>) -> io::Result<bool> {
344    getsockopt(fd, c::SOL_SOCKET, c::SO_KEEPALIVE).map(to_bool)
345}
346
347#[inline]
348pub(crate) fn set_socket_recv_buffer_size(fd: BorrowedFd<'_>, size: usize) -> io::Result<()> {
349    let size: c::c_int = size.try_into().map_err(|_| io::Errno::INVAL)?;
350    setsockopt(fd, c::SOL_SOCKET, c::SO_RCVBUF, size)
351}
352
353#[inline]
354pub(crate) fn set_socket_recv_buffer_size_force(fd: BorrowedFd<'_>, size: usize) -> io::Result<()> {
355    let size: c::c_int = size.try_into().map_err(|_| io::Errno::INVAL)?;
356    setsockopt(fd, c::SOL_SOCKET, c::SO_RCVBUFFORCE, size)
357}
358
359#[inline]
360pub(crate) fn socket_recv_buffer_size(fd: BorrowedFd<'_>) -> io::Result<usize> {
361    getsockopt(fd, c::SOL_SOCKET, c::SO_RCVBUF).map(|size: u32| size as usize)
362}
363
364#[inline]
365pub(crate) fn set_socket_send_buffer_size(fd: BorrowedFd<'_>, size: usize) -> io::Result<()> {
366    let size: c::c_int = size.try_into().map_err(|_| io::Errno::INVAL)?;
367    setsockopt(fd, c::SOL_SOCKET, c::SO_SNDBUF, size)
368}
369
370#[inline]
371pub(crate) fn set_socket_send_buffer_size_force(fd: BorrowedFd<'_>, size: usize) -> io::Result<()> {
372    let size: c::c_int = size.try_into().map_err(|_| io::Errno::INVAL)?;
373    setsockopt(fd, c::SOL_SOCKET, c::SO_SNDBUFFORCE, size)
374}
375
376#[inline]
377pub(crate) fn socket_send_buffer_size(fd: BorrowedFd<'_>) -> io::Result<usize> {
378    getsockopt(fd, c::SOL_SOCKET, c::SO_SNDBUF).map(|size: u32| size as usize)
379}
380
381#[inline]
382pub(crate) fn socket_domain(fd: BorrowedFd<'_>) -> io::Result<AddressFamily> {
383    let domain: c::c_int = getsockopt(fd, c::SOL_SOCKET, c::SO_DOMAIN)?;
384    Ok(AddressFamily(
385        domain.try_into().map_err(|_| io::Errno::OPNOTSUPP)?,
386    ))
387}
388
389#[inline]
390pub(crate) fn socket_acceptconn(fd: BorrowedFd<'_>) -> io::Result<bool> {
391    getsockopt(fd, c::SOL_SOCKET, c::SO_ACCEPTCONN).map(to_bool)
392}
393
394#[inline]
395pub(crate) fn set_socket_oobinline(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
396    setsockopt(fd, c::SOL_SOCKET, c::SO_OOBINLINE, from_bool(value))
397}
398
399#[inline]
400pub(crate) fn socket_oobinline(fd: BorrowedFd<'_>) -> io::Result<bool> {
401    getsockopt(fd, c::SOL_SOCKET, c::SO_OOBINLINE).map(to_bool)
402}
403
404#[inline]
405pub(crate) fn set_socket_reuseport(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
406    setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT, from_bool(value))
407}
408
409#[inline]
410pub(crate) fn socket_reuseport(fd: BorrowedFd<'_>) -> io::Result<bool> {
411    getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT).map(to_bool)
412}
413
414#[inline]
415pub(crate) fn socket_protocol(fd: BorrowedFd<'_>) -> io::Result<Option<Protocol>> {
416    getsockopt(fd, c::SOL_SOCKET, c::SO_PROTOCOL)
417        .map(|raw: u32| RawProtocol::new(raw).map(Protocol::from_raw))
418}
419
420#[inline]
421pub(crate) fn socket_cookie(fd: BorrowedFd<'_>) -> io::Result<u64> {
422    getsockopt(fd, c::SOL_SOCKET, c::SO_COOKIE)
423}
424
425#[inline]
426pub(crate) fn socket_incoming_cpu(fd: BorrowedFd<'_>) -> io::Result<u32> {
427    getsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU)
428}
429
430#[inline]
431pub(crate) fn set_socket_incoming_cpu(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
432    setsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU, value)
433}
434
435#[inline]
436pub(crate) fn set_ip_ttl(fd: BorrowedFd<'_>, ttl: u32) -> io::Result<()> {
437    setsockopt(fd, c::IPPROTO_IP, c::IP_TTL, ttl)
438}
439
440#[inline]
441pub(crate) fn ip_ttl(fd: BorrowedFd<'_>) -> io::Result<u32> {
442    getsockopt(fd, c::IPPROTO_IP, c::IP_TTL)
443}
444
445#[inline]
446pub(crate) fn set_ipv6_v6only(fd: BorrowedFd<'_>, only_v6: bool) -> io::Result<()> {
447    setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_V6ONLY, from_bool(only_v6))
448}
449
450#[inline]
451pub(crate) fn ipv6_v6only(fd: BorrowedFd<'_>) -> io::Result<bool> {
452    getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_V6ONLY).map(to_bool)
453}
454
455#[inline]
456pub(crate) fn ip_mtu(fd: BorrowedFd<'_>) -> io::Result<u32> {
457    getsockopt(fd, c::IPPROTO_IP, IP_MTU)
458}
459
460#[inline]
461pub(crate) fn ipv6_mtu(fd: BorrowedFd<'_>) -> io::Result<u32> {
462    getsockopt(fd, c::IPPROTO_IPV6, IPV6_MTU)
463}
464
465#[inline]
466pub(crate) fn set_ip_multicast_if_with_ifindex(
467    fd: BorrowedFd<'_>,
468    multiaddr: &Ipv4Addr,
469    address: &Ipv4Addr,
470    ifindex: u32,
471) -> io::Result<()> {
472    let mreqn = to_ip_mreqn(multiaddr, address, ifindex as i32);
473    setsockopt(fd, c::IPPROTO_IP, IP_MULTICAST_IF, mreqn)
474}
475
476#[inline]
477pub(crate) fn set_ip_multicast_if(fd: BorrowedFd<'_>, value: &Ipv4Addr) -> io::Result<()> {
478    setsockopt(fd, c::IPPROTO_IP, IP_MULTICAST_IF, to_imr_addr(value))
479}
480
481#[inline]
482pub(crate) fn ip_multicast_if(fd: BorrowedFd<'_>) -> io::Result<Ipv4Addr> {
483    getsockopt(fd, c::IPPROTO_IP, IP_MULTICAST_IF).map(from_in_addr)
484}
485
486#[inline]
487pub(crate) fn set_ipv6_multicast_if(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
488    setsockopt(fd, c::IPPROTO_IPV6, IPV6_MULTICAST_IF, value as c::c_int)
489}
490
491#[inline]
492pub(crate) fn ipv6_multicast_if(fd: BorrowedFd<'_>) -> io::Result<u32> {
493    getsockopt(fd, c::IPPROTO_IPV6, IPV6_MULTICAST_IF)
494}
495
496#[inline]
497pub(crate) fn set_ip_multicast_loop(fd: BorrowedFd<'_>, multicast_loop: bool) -> io::Result<()> {
498    setsockopt(
499        fd,
500        c::IPPROTO_IP,
501        c::IP_MULTICAST_LOOP,
502        from_bool(multicast_loop),
503    )
504}
505
506#[inline]
507pub(crate) fn ip_multicast_loop(fd: BorrowedFd<'_>) -> io::Result<bool> {
508    getsockopt(fd, c::IPPROTO_IP, c::IP_MULTICAST_LOOP).map(to_bool)
509}
510
511#[inline]
512pub(crate) fn set_ip_multicast_ttl(fd: BorrowedFd<'_>, multicast_ttl: u32) -> io::Result<()> {
513    setsockopt(fd, c::IPPROTO_IP, c::IP_MULTICAST_TTL, multicast_ttl)
514}
515
516#[inline]
517pub(crate) fn ip_multicast_ttl(fd: BorrowedFd<'_>) -> io::Result<u32> {
518    getsockopt(fd, c::IPPROTO_IP, c::IP_MULTICAST_TTL)
519}
520
521#[inline]
522pub(crate) fn set_ipv6_multicast_loop(fd: BorrowedFd<'_>, multicast_loop: bool) -> io::Result<()> {
523    setsockopt(
524        fd,
525        c::IPPROTO_IPV6,
526        c::IPV6_MULTICAST_LOOP,
527        from_bool(multicast_loop),
528    )
529}
530
531#[inline]
532pub(crate) fn ipv6_multicast_loop(fd: BorrowedFd<'_>) -> io::Result<bool> {
533    getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_MULTICAST_LOOP).map(to_bool)
534}
535
536#[inline]
537pub(crate) fn set_ipv6_multicast_hops(fd: BorrowedFd<'_>, multicast_hops: u32) -> io::Result<()> {
538    setsockopt(fd, c::IPPROTO_IP, c::IPV6_MULTICAST_HOPS, multicast_hops)
539}
540
541#[inline]
542pub(crate) fn ipv6_multicast_hops(fd: BorrowedFd<'_>) -> io::Result<u32> {
543    getsockopt(fd, c::IPPROTO_IP, c::IPV6_MULTICAST_HOPS)
544}
545
546#[inline]
547pub(crate) fn set_ip_add_membership(
548    fd: BorrowedFd<'_>,
549    multiaddr: &Ipv4Addr,
550    interface: &Ipv4Addr,
551) -> io::Result<()> {
552    let mreq = to_ip_mreq(multiaddr, interface);
553    setsockopt(fd, c::IPPROTO_IP, c::IP_ADD_MEMBERSHIP, mreq)
554}
555
556#[inline]
557pub(crate) fn set_ip_add_membership_with_ifindex(
558    fd: BorrowedFd<'_>,
559    multiaddr: &Ipv4Addr,
560    address: &Ipv4Addr,
561    ifindex: u32,
562) -> io::Result<()> {
563    let mreqn = to_ip_mreqn(multiaddr, address, ifindex as i32);
564    setsockopt(fd, c::IPPROTO_IP, c::IP_ADD_MEMBERSHIP, mreqn)
565}
566
567#[inline]
568pub(crate) fn set_ip_add_source_membership(
569    fd: BorrowedFd<'_>,
570    multiaddr: &Ipv4Addr,
571    interface: &Ipv4Addr,
572    sourceaddr: &Ipv4Addr,
573) -> io::Result<()> {
574    let mreq_source = to_imr_source(multiaddr, interface, sourceaddr);
575    setsockopt(fd, c::IPPROTO_IP, c::IP_ADD_SOURCE_MEMBERSHIP, mreq_source)
576}
577
578#[inline]
579pub(crate) fn set_ip_drop_source_membership(
580    fd: BorrowedFd<'_>,
581    multiaddr: &Ipv4Addr,
582    interface: &Ipv4Addr,
583    sourceaddr: &Ipv4Addr,
584) -> io::Result<()> {
585    let mreq_source = to_imr_source(multiaddr, interface, sourceaddr);
586    setsockopt(fd, c::IPPROTO_IP, c::IP_DROP_SOURCE_MEMBERSHIP, mreq_source)
587}
588
589#[inline]
590pub(crate) fn set_ipv6_add_membership(
591    fd: BorrowedFd<'_>,
592    multiaddr: &Ipv6Addr,
593    interface: u32,
594) -> io::Result<()> {
595    let mreq = to_ipv6mr(multiaddr, interface);
596    setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_ADD_MEMBERSHIP, mreq)
597}
598
599#[inline]
600pub(crate) fn set_ip_drop_membership(
601    fd: BorrowedFd<'_>,
602    multiaddr: &Ipv4Addr,
603    interface: &Ipv4Addr,
604) -> io::Result<()> {
605    let mreq = to_ip_mreq(multiaddr, interface);
606    setsockopt(fd, c::IPPROTO_IP, c::IP_DROP_MEMBERSHIP, mreq)
607}
608
609#[inline]
610pub(crate) fn set_ip_drop_membership_with_ifindex(
611    fd: BorrowedFd<'_>,
612    multiaddr: &Ipv4Addr,
613    address: &Ipv4Addr,
614    ifindex: u32,
615) -> io::Result<()> {
616    let mreqn = to_ip_mreqn(multiaddr, address, ifindex as i32);
617    setsockopt(fd, c::IPPROTO_IP, c::IP_DROP_MEMBERSHIP, mreqn)
618}
619
620#[inline]
621pub(crate) fn set_ipv6_drop_membership(
622    fd: BorrowedFd<'_>,
623    multiaddr: &Ipv6Addr,
624    interface: u32,
625) -> io::Result<()> {
626    let mreq = to_ipv6mr(multiaddr, interface);
627    setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_DROP_MEMBERSHIP, mreq)
628}
629
630#[inline]
631pub(crate) fn ipv6_unicast_hops(fd: BorrowedFd<'_>) -> io::Result<u8> {
632    getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_UNICAST_HOPS).map(|hops: c::c_int| hops as u8)
633}
634
635#[inline]
636pub(crate) fn set_ipv6_unicast_hops(fd: BorrowedFd<'_>, hops: Option<u8>) -> io::Result<()> {
637    let hops = match hops {
638        Some(hops) => hops.into(),
639        None => -1,
640    };
641    setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_UNICAST_HOPS, hops)
642}
643
644#[inline]
645pub(crate) fn set_ip_tos(fd: BorrowedFd<'_>, value: u8) -> io::Result<()> {
646    setsockopt(fd, c::IPPROTO_IP, c::IP_TOS, i32::from(value))
647}
648
649#[inline]
650pub(crate) fn ip_tos(fd: BorrowedFd<'_>) -> io::Result<u8> {
651    let value: i32 = getsockopt(fd, c::IPPROTO_IP, c::IP_TOS)?;
652    Ok(value as u8)
653}
654
655#[inline]
656pub(crate) fn set_ip_recvtos(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
657    setsockopt(fd, c::IPPROTO_IP, c::IP_RECVTOS, from_bool(value))
658}
659
660#[inline]
661pub(crate) fn ip_recvtos(fd: BorrowedFd<'_>) -> io::Result<bool> {
662    getsockopt(fd, c::IPPROTO_IP, c::IP_RECVTOS).map(to_bool)
663}
664
665#[inline]
666pub(crate) fn set_ipv6_recvtclass(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
667    setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_RECVTCLASS, from_bool(value))
668}
669
670#[inline]
671pub(crate) fn ipv6_recvtclass(fd: BorrowedFd<'_>) -> io::Result<bool> {
672    getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_RECVTCLASS).map(to_bool)
673}
674
675#[inline]
676pub(crate) fn set_ip_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
677    setsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND, from_bool(value))
678}
679
680#[inline]
681pub(crate) fn ip_freebind(fd: BorrowedFd<'_>) -> io::Result<bool> {
682    getsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND).map(to_bool)
683}
684
685#[inline]
686pub(crate) fn set_ipv6_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
687    setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND, from_bool(value))
688}
689
690#[inline]
691pub(crate) fn ipv6_freebind(fd: BorrowedFd<'_>) -> io::Result<bool> {
692    getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND).map(to_bool)
693}
694
695#[inline]
696pub(crate) fn ip_original_dst(fd: BorrowedFd<'_>) -> io::Result<SocketAddrV4> {
697    let level = c::IPPROTO_IP;
698    let optname = c::SO_ORIGINAL_DST;
699    let mut addr = SocketAddrBuf::new();
700
701    getsockopt_raw(fd, level, optname, &mut addr.storage, &mut addr.len)?;
702
703    Ok(unsafe { addr.into_any() }.try_into().unwrap())
704}
705
706#[inline]
707pub(crate) fn ipv6_original_dst(fd: BorrowedFd<'_>) -> io::Result<SocketAddrV6> {
708    let level = c::IPPROTO_IPV6;
709    let optname = c::IP6T_SO_ORIGINAL_DST;
710    let mut addr = SocketAddrBuf::new();
711
712    getsockopt_raw(fd, level, optname, &mut addr.storage, &mut addr.len)?;
713
714    Ok(unsafe { addr.into_any() }.try_into().unwrap())
715}
716
717#[inline]
718pub(crate) fn set_ipv6_tclass(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
719    setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS, value)
720}
721
722#[inline]
723pub(crate) fn ipv6_tclass(fd: BorrowedFd<'_>) -> io::Result<u32> {
724    getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS)
725}
726
727#[inline]
728pub(crate) fn set_tcp_nodelay(fd: BorrowedFd<'_>, nodelay: bool) -> io::Result<()> {
729    setsockopt(fd, c::IPPROTO_TCP, c::TCP_NODELAY, from_bool(nodelay))
730}
731
732#[inline]
733pub(crate) fn tcp_nodelay(fd: BorrowedFd<'_>) -> io::Result<bool> {
734    getsockopt(fd, c::IPPROTO_TCP, c::TCP_NODELAY).map(to_bool)
735}
736
737#[inline]
738pub(crate) fn set_tcp_keepcnt(fd: BorrowedFd<'_>, count: u32) -> io::Result<()> {
739    setsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPCNT, count)
740}
741
742#[inline]
743pub(crate) fn tcp_keepcnt(fd: BorrowedFd<'_>) -> io::Result<u32> {
744    getsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPCNT)
745}
746
747#[inline]
748pub(crate) fn set_tcp_keepidle(fd: BorrowedFd<'_>, duration: Duration) -> io::Result<()> {
749    let secs: c::c_uint = duration_to_secs(duration)?;
750    setsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPIDLE, secs)
751}
752
753#[inline]
754pub(crate) fn tcp_keepidle(fd: BorrowedFd<'_>) -> io::Result<Duration> {
755    let secs: c::c_uint = getsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPIDLE)?;
756    Ok(Duration::from_secs(secs.into()))
757}
758
759#[inline]
760pub(crate) fn set_tcp_keepintvl(fd: BorrowedFd<'_>, duration: Duration) -> io::Result<()> {
761    let secs: c::c_uint = duration_to_secs(duration)?;
762    setsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPINTVL, secs)
763}
764
765#[inline]
766pub(crate) fn tcp_keepintvl(fd: BorrowedFd<'_>) -> io::Result<Duration> {
767    let secs: c::c_uint = getsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPINTVL)?;
768    Ok(Duration::from_secs(secs.into()))
769}
770
771#[inline]
772pub(crate) fn set_tcp_user_timeout(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
773    setsockopt(fd, c::IPPROTO_TCP, c::TCP_USER_TIMEOUT, value)
774}
775
776#[inline]
777pub(crate) fn tcp_user_timeout(fd: BorrowedFd<'_>) -> io::Result<u32> {
778    getsockopt(fd, c::IPPROTO_TCP, c::TCP_USER_TIMEOUT)
779}
780
781#[inline]
782pub(crate) fn set_tcp_quickack(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
783    setsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK, from_bool(value))
784}
785
786#[inline]
787pub(crate) fn tcp_quickack(fd: BorrowedFd<'_>) -> io::Result<bool> {
788    getsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK).map(to_bool)
789}
790
791#[inline]
792pub(crate) fn set_tcp_congestion(fd: BorrowedFd<'_>, value: &str) -> io::Result<()> {
793    let level = c::IPPROTO_TCP;
794    let optname = c::TCP_CONGESTION;
795    let optlen = value.len().try_into().unwrap();
796    setsockopt_raw(fd, level, optname, value.as_ptr(), optlen)
797}
798
799#[cfg(feature = "alloc")]
800#[inline]
801pub(crate) fn tcp_congestion(fd: BorrowedFd<'_>) -> io::Result<String> {
802    const OPTLEN: c::socklen_t = 16;
803
804    let level = c::IPPROTO_TCP;
805    let optname = c::TCP_CONGESTION;
806    let mut value = MaybeUninit::<[MaybeUninit<u8>; OPTLEN as usize]>::uninit();
807    let mut optlen = OPTLEN;
808    getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;
809    unsafe {
810        let value = value.assume_init();
811        let slice: &[u8] = core::mem::transmute(&value[..optlen as usize]);
812        assert!(slice.contains(&b'\0'));
813        Ok(
814            core::str::from_utf8(CStr::from_ptr(slice.as_ptr().cast()).to_bytes())
815                .unwrap()
816                .to_owned(),
817        )
818    }
819}
820
821#[inline]
822pub(crate) fn set_tcp_thin_linear_timeouts(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
823    setsockopt(
824        fd,
825        c::IPPROTO_TCP,
826        c::TCP_THIN_LINEAR_TIMEOUTS,
827        from_bool(value),
828    )
829}
830
831#[inline]
832pub(crate) fn tcp_thin_linear_timeouts(fd: BorrowedFd<'_>) -> io::Result<bool> {
833    getsockopt(fd, c::IPPROTO_TCP, c::TCP_THIN_LINEAR_TIMEOUTS).map(to_bool)
834}
835
836#[inline]
837pub(crate) fn set_tcp_cork(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
838    setsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK, from_bool(value))
839}
840
841#[inline]
842pub(crate) fn tcp_cork(fd: BorrowedFd<'_>) -> io::Result<bool> {
843    getsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK).map(to_bool)
844}
845
846#[inline]
847pub(crate) fn socket_peercred(fd: BorrowedFd<'_>) -> io::Result<UCred> {
848    getsockopt(fd, c::SOL_SOCKET, linux_raw_sys::net::SO_PEERCRED)
849}
850
851#[cfg(target_os = "linux")]
852#[inline]
853pub(crate) fn set_xdp_umem_reg(fd: BorrowedFd<'_>, value: XdpUmemReg) -> io::Result<()> {
854    setsockopt(fd, c::SOL_XDP, c::XDP_UMEM_REG, value)
855}
856
857#[cfg(target_os = "linux")]
858#[inline]
859pub(crate) fn set_xdp_umem_fill_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
860    setsockopt(fd, c::SOL_XDP, c::XDP_UMEM_FILL_RING, value)
861}
862
863#[cfg(target_os = "linux")]
864#[inline]
865pub(crate) fn set_xdp_umem_completion_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
866    setsockopt(fd, c::SOL_XDP, c::XDP_UMEM_COMPLETION_RING, value)
867}
868
869#[cfg(target_os = "linux")]
870#[inline]
871pub(crate) fn set_xdp_tx_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
872    setsockopt(fd, c::SOL_XDP, c::XDP_TX_RING, value)
873}
874
875#[cfg(target_os = "linux")]
876#[inline]
877pub(crate) fn set_xdp_rx_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
878    setsockopt(fd, c::SOL_XDP, c::XDP_RX_RING, value)
879}
880
881#[cfg(target_os = "linux")]
882#[inline]
883pub(crate) fn xdp_mmap_offsets(fd: BorrowedFd<'_>) -> io::Result<XdpMmapOffsets> {
884    // The kernel will write `xdp_mmap_offsets` or `xdp_mmap_offsets_v1` to the
885    // supplied pointer, depending on the kernel version. Both structs only
886    // contain u64 values. By using the larger of both as the parameter, we can
887    // shuffle the values to the non-v1 version returned by
888    // `get_xdp_mmap_offsets` while keeping the return type unaffected by the
889    // kernel version. This works because C will layout all struct members one
890    // after the other.
891
892    let mut optlen = size_of::<xdp_mmap_offsets>().try_into().unwrap();
893    debug_assert!(
894        optlen as usize >= size_of::<c::c_int>(),
895        "Socket APIs don't ever use `bool` directly"
896    );
897    let mut value = MaybeUninit::<xdp_mmap_offsets>::zeroed();
898    getsockopt_raw(fd, c::SOL_XDP, c::XDP_MMAP_OFFSETS, &mut value, &mut optlen)?;
899
900    if optlen as usize == size_of::<c::xdp_mmap_offsets_v1>() {
901        // SAFETY: All members of xdp_mmap_offsets are `u64` and thus are
902        // correctly initialized by `MaybeUninit::<xdp_statistics>::zeroed()`.
903        let xpd_mmap_offsets = unsafe { value.assume_init() };
904        Ok(XdpMmapOffsets {
905            rx: XdpRingOffset {
906                producer: xpd_mmap_offsets.rx.producer,
907                consumer: xpd_mmap_offsets.rx.consumer,
908                desc: xpd_mmap_offsets.rx.desc,
909                flags: None,
910            },
911            tx: XdpRingOffset {
912                producer: xpd_mmap_offsets.rx.flags,
913                consumer: xpd_mmap_offsets.tx.producer,
914                desc: xpd_mmap_offsets.tx.consumer,
915                flags: None,
916            },
917            fr: XdpRingOffset {
918                producer: xpd_mmap_offsets.tx.desc,
919                consumer: xpd_mmap_offsets.tx.flags,
920                desc: xpd_mmap_offsets.fr.producer,
921                flags: None,
922            },
923            cr: XdpRingOffset {
924                producer: xpd_mmap_offsets.fr.consumer,
925                consumer: xpd_mmap_offsets.fr.desc,
926                desc: xpd_mmap_offsets.fr.flags,
927                flags: None,
928            },
929        })
930    } else {
931        assert_eq!(
932            optlen as usize,
933            size_of::<xdp_mmap_offsets>(),
934            "unexpected getsockopt size"
935        );
936        // SAFETY: All members of xdp_mmap_offsets are `u64` and thus are
937        // correctly initialized by `MaybeUninit::<xdp_statistics>::zeroed()`.
938        let xpd_mmap_offsets = unsafe { value.assume_init() };
939        Ok(XdpMmapOffsets {
940            rx: XdpRingOffset {
941                producer: xpd_mmap_offsets.rx.producer,
942                consumer: xpd_mmap_offsets.rx.consumer,
943                desc: xpd_mmap_offsets.rx.desc,
944                flags: Some(xpd_mmap_offsets.rx.flags),
945            },
946            tx: XdpRingOffset {
947                producer: xpd_mmap_offsets.tx.producer,
948                consumer: xpd_mmap_offsets.tx.consumer,
949                desc: xpd_mmap_offsets.tx.desc,
950                flags: Some(xpd_mmap_offsets.tx.flags),
951            },
952            fr: XdpRingOffset {
953                producer: xpd_mmap_offsets.fr.producer,
954                consumer: xpd_mmap_offsets.fr.consumer,
955                desc: xpd_mmap_offsets.fr.desc,
956                flags: Some(xpd_mmap_offsets.fr.flags),
957            },
958            cr: XdpRingOffset {
959                producer: xpd_mmap_offsets.cr.producer,
960                consumer: xpd_mmap_offsets.cr.consumer,
961                desc: xpd_mmap_offsets.cr.desc,
962                flags: Some(xpd_mmap_offsets.cr.flags),
963            },
964        })
965    }
966}
967
968#[cfg(target_os = "linux")]
969#[inline]
970pub(crate) fn xdp_statistics(fd: BorrowedFd<'_>) -> io::Result<XdpStatistics> {
971    let mut optlen = size_of::<xdp_statistics>().try_into().unwrap();
972    debug_assert!(
973        optlen as usize >= size_of::<c::c_int>(),
974        "Socket APIs don't ever use `bool` directly"
975    );
976    let mut value = MaybeUninit::<xdp_statistics>::zeroed();
977    getsockopt_raw(fd, c::SOL_XDP, c::XDP_STATISTICS, &mut value, &mut optlen)?;
978
979    if optlen as usize == size_of::<xdp_statistics_v1>() {
980        // SAFETY: All members of xdp_statistics are `u64` and thus are
981        // correctly initialized by `MaybeUninit::<xdp_statistics>::zeroed()`.
982        let xdp_statistics = unsafe { value.assume_init() };
983        Ok(XdpStatistics {
984            rx_dropped: xdp_statistics.rx_dropped,
985            rx_invalid_descs: xdp_statistics.rx_dropped,
986            tx_invalid_descs: xdp_statistics.rx_dropped,
987            rx_ring_full: None,
988            rx_fill_ring_empty_descs: None,
989            tx_ring_empty_descs: None,
990        })
991    } else {
992        assert_eq!(
993            optlen as usize,
994            size_of::<xdp_statistics>(),
995            "unexpected getsockopt size"
996        );
997        // SAFETY: All members of xdp_statistics are `u64` and thus are
998        // correctly initialized by `MaybeUninit::<xdp_statistics>::zeroed()`.
999        let xdp_statistics = unsafe { value.assume_init() };
1000        Ok(XdpStatistics {
1001            rx_dropped: xdp_statistics.rx_dropped,
1002            rx_invalid_descs: xdp_statistics.rx_invalid_descs,
1003            tx_invalid_descs: xdp_statistics.tx_invalid_descs,
1004            rx_ring_full: Some(xdp_statistics.rx_ring_full),
1005            rx_fill_ring_empty_descs: Some(xdp_statistics.rx_fill_ring_empty_descs),
1006            tx_ring_empty_descs: Some(xdp_statistics.tx_ring_empty_descs),
1007        })
1008    }
1009}
1010
1011#[cfg(target_os = "linux")]
1012#[inline]
1013pub(crate) fn xdp_options(fd: BorrowedFd<'_>) -> io::Result<XdpOptionsFlags> {
1014    getsockopt(fd, c::SOL_XDP, c::XDP_OPTIONS)
1015}
1016
1017#[inline]
1018fn from_in_addr(in_addr: c::in_addr) -> Ipv4Addr {
1019    Ipv4Addr::from(in_addr.s_addr.to_ne_bytes())
1020}
1021
1022#[inline]
1023fn to_ip_mreq(multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> c::ip_mreq {
1024    c::ip_mreq {
1025        imr_multiaddr: to_imr_addr(multiaddr),
1026        imr_interface: to_imr_addr(interface),
1027    }
1028}
1029
1030#[inline]
1031fn to_ip_mreqn(multiaddr: &Ipv4Addr, address: &Ipv4Addr, ifindex: i32) -> c::ip_mreqn {
1032    c::ip_mreqn {
1033        imr_multiaddr: to_imr_addr(multiaddr),
1034        imr_address: to_imr_addr(address),
1035        imr_ifindex: ifindex,
1036    }
1037}
1038
1039#[inline]
1040fn to_imr_source(
1041    multiaddr: &Ipv4Addr,
1042    interface: &Ipv4Addr,
1043    sourceaddr: &Ipv4Addr,
1044) -> c::ip_mreq_source {
1045    c::ip_mreq_source {
1046        imr_multiaddr: to_imr_addr(multiaddr).s_addr,
1047        imr_interface: to_imr_addr(interface).s_addr,
1048        imr_sourceaddr: to_imr_addr(sourceaddr).s_addr,
1049    }
1050}
1051
1052#[inline]
1053fn to_imr_addr(addr: &Ipv4Addr) -> c::in_addr {
1054    c::in_addr {
1055        s_addr: u32::from_ne_bytes(addr.octets()),
1056    }
1057}
1058
1059#[inline]
1060fn to_ipv6mr(multiaddr: &Ipv6Addr, interface: u32) -> c::ipv6_mreq {
1061    c::ipv6_mreq {
1062        ipv6mr_multiaddr: to_ipv6mr_multiaddr(multiaddr),
1063        ipv6mr_ifindex: to_ipv6mr_interface(interface),
1064    }
1065}
1066
1067#[inline]
1068fn to_ipv6mr_multiaddr(multiaddr: &Ipv6Addr) -> c::in6_addr {
1069    c::in6_addr {
1070        in6_u: linux_raw_sys::net::in6_addr__bindgen_ty_1 {
1071            u6_addr8: multiaddr.octets(),
1072        },
1073    }
1074}
1075
1076#[inline]
1077fn to_ipv6mr_interface(interface: u32) -> c::c_int {
1078    interface as c::c_int
1079}
1080
1081#[inline]
1082fn from_bool(value: bool) -> c::c_uint {
1083    c::c_uint::from(value)
1084}
1085
1086#[inline]
1087fn to_bool(value: c::c_uint) -> bool {
1088    value != 0
1089}
1090
1091/// Convert to seconds, rounding up if necessary.
1092#[inline]
1093fn duration_to_secs<T: TryFrom<u64>>(duration: Duration) -> io::Result<T> {
1094    let mut secs = duration.as_secs();
1095    if duration.subsec_nanos() != 0 {
1096        secs = secs.checked_add(1).ok_or(io::Errno::INVAL)?;
1097    }
1098    T::try_from(secs).map_err(|_e| io::Errno::INVAL)
1099}