Skip to main content

timestamped_socket/interface/
linux.rs

1use std::{array, io::ErrorKind, os::fd::RawFd};
2
3use libc::{
4    __c_anonymous_ifr_ifru, close, ifreq, ioctl, recv, socket, AF_INET, SIOCETHTOOL, SOCK_DGRAM,
5};
6use tokio::io::{unix::AsyncFd, Interest};
7
8use crate::{cerr, control_message::zeroed_sockaddr_storage};
9
10use super::InterfaceName;
11
12#[repr(C)]
13struct EthtoolTsInfo {
14    cmd: u32,
15    so_timestamping: u32,
16    phc_index: i32,
17    tx_types: u32,
18    reserved1: [u32; 3],
19    rx_filters: u32,
20    reserved2: [u32; 3],
21}
22
23const ETHTOOL_GET_TS_INFO: u32 = 0x41;
24
25/// Get the hardware clock index associated with the given interface.
26#[allow(
27    clippy::cast_possible_wrap,
28    reason = "Interface indices are always positive."
29)]
30#[expect(
31    clippy::cast_sign_loss,
32    reason = "Interface indices are always positive."
33)]
34#[must_use]
35pub fn lookup_phc(interface: InterfaceName) -> Option<u32> {
36    // Safety: socket is safe to call with these constants as
37    // arguments
38    let fd = unsafe { socket(AF_INET, SOCK_DGRAM, 0) };
39    if fd < 0 {
40        tracing::error!("Could not open socket for looking up PHC index");
41        return None;
42    }
43
44    let mut ethtool_ts_info = EthtoolTsInfo {
45        cmd: ETHTOOL_GET_TS_INFO,
46        so_timestamping: 0,
47        phc_index: -1,
48        tx_types: 0,
49        reserved1: [0; 3],
50        rx_filters: 0,
51        reserved2: [0; 3],
52    };
53
54    let mut request = ifreq {
55        ifr_name: array::from_fn(|i| interface.bytes[i] as _),
56        ifr_ifru: __c_anonymous_ifr_ifru {
57            ifru_data: (&raw mut ethtool_ts_info).cast(),
58        },
59    };
60
61    // Safety: request and ethtool_ts_info are live for the duration of the call.
62    let error = unsafe {
63        ioctl(
64            fd,
65            SIOCETHTOOL as _,
66            (&raw mut request).cast::<libc::c_void>(),
67        )
68    };
69
70    // should always close fd
71    // Safety: Safe to call close for this file descriptor
72    unsafe {
73        close(fd);
74    }
75
76    if error < 0 {
77        None
78    } else if ethtool_ts_info.phc_index >= 0 {
79        Some(ethtool_ts_info.phc_index as u32)
80    } else {
81        None
82    }
83}
84
85/// A detector for changes to which network interfaces are available on the system.
86pub struct ChangeDetector {
87    fd: AsyncFd<RawFd>,
88}
89
90impl ChangeDetector {
91    /// Create a new detector for changes to which network interfaces are available on the system.
92    ///
93    /// # Errors
94    /// May fail if the system does not allow a new change detector to be created.
95    #[expect(
96        clippy::cast_possible_truncation,
97        reason = "Network addresses are small enought that their size fits in an integer."
98    )]
99    pub fn new() -> std::io::Result<Self> {
100        const _: () = assert!(
101            std::mem::size_of::<libc::sockaddr_storage>()
102                >= std::mem::size_of::<libc::sockaddr_nl>()
103        );
104        const _: () = assert!(
105            std::mem::align_of::<libc::sockaddr_storage>()
106                >= std::mem::align_of::<libc::sockaddr_nl>()
107        );
108
109        let mut address_buf = zeroed_sockaddr_storage();
110        // Safety: the above assertions guarantee that alignment and size are correct.
111        // the resulting reference won't outlast the function, and result lives the entire
112        // duration of the function
113        let address = unsafe { &mut *(&raw mut address_buf).cast::<libc::sockaddr_nl>() };
114
115        address.nl_family = libc::AF_NETLINK as _;
116        address.nl_groups =
117            (libc::RTMGRP_IPV4_IFADDR | libc::RTMGRP_IPV6_IFADDR | libc::RTMGRP_LINK) as _;
118
119        // Safety: calling socket is safe
120        let fd =
121            cerr(unsafe { libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, libc::NETLINK_ROUTE) })?;
122        // Safety: address is valid for the duration of the call
123        cerr(unsafe {
124            libc::bind(
125                fd,
126                std::ptr::from_mut(address).cast(),
127                std::mem::size_of_val(address) as _,
128            )
129        })?;
130
131        let nonblocking = 1 as libc::c_int;
132        // Safety: nonblocking lives for the duration of the call, and is 4 bytes long as expected for FIONBIO
133        cerr(unsafe { libc::ioctl(fd, libc::FIONBIO, &nonblocking) })?;
134
135        // Safety: We own the file descriptor and so can transfer it, and it is
136        // guaranteed to be open by the earlier error checking.
137        Ok(ChangeDetector {
138            fd: unsafe { AsyncFd::register(fd) }?,
139        })
140    }
141
142    #[expect(
143        clippy::cast_possible_truncation,
144        reason = "The message buffer is small enought that its size fits in an integer."
145    )]
146    fn empty(fd: i32) {
147        loop {
148            // Safety: buf is valid for the duration of the call, and it's length is passed as the len argument
149            let mut buf = [0u8; 16];
150            match cerr(unsafe {
151                recv(
152                    fd,
153                    (&raw mut buf).cast(),
154                    std::mem::size_of_val(&buf) as _,
155                    0,
156                ) as _
157            }) {
158                Ok(_) => {}
159                Err(e) if e.kind() == ErrorKind::WouldBlock => break,
160                Err(e) => {
161                    tracing::error!("Could not receive on change socket: {}", e);
162                    break;
163                }
164            }
165        }
166    }
167
168    /// Wait for a change to which network interfaces are present.
169    #[expect(
170        clippy::cast_possible_truncation,
171        reason = "The message buffer is small enought that its size fits in an integer."
172    )]
173    pub async fn wait_for_change(&mut self) {
174        if let Err(e) = self
175            .fd
176            .async_io(Interest::READABLE, |fd| {
177                // Safety: buf is valid for the duration of the call, and it's length is passed as the len argument
178                let mut buf = [0u8; 16];
179                cerr(unsafe {
180                    recv(
181                        *fd,
182                        (&raw mut buf).cast(),
183                        std::mem::size_of_val(&buf) as _,
184                        0,
185                    ) as _
186                })?;
187                Self::empty(*fd);
188                Ok(())
189            })
190            .await
191        {
192            tracing::error!("Could not receive on change socket: {}", e);
193        }
194    }
195}