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
25pub fn lookup_phc(interface: InterfaceName) -> Option<u32> {
26    // Safety: socket is safe to call with these constants as
27    // arguments
28    let fd = unsafe { socket(AF_INET, SOCK_DGRAM, 0) };
29    if fd < 0 {
30        tracing::error!("Could not open socket for looking up PHC index");
31        return None;
32    }
33
34    let mut ethtool_ts_info = EthtoolTsInfo {
35        cmd: ETHTOOL_GET_TS_INFO,
36        so_timestamping: 0,
37        phc_index: -1,
38        tx_types: 0,
39        reserved1: [0; 3],
40        rx_filters: 0,
41        reserved2: [0; 3],
42    };
43
44    let mut request = ifreq {
45        ifr_name: array::from_fn(|i| interface.bytes[i] as _),
46        ifr_ifru: __c_anonymous_ifr_ifru {
47            ifru_data: &mut ethtool_ts_info as *mut _ as *mut _,
48        },
49    };
50
51    // Safety: request and ethtool_ts_info are live for the duration of the call.
52    let error = unsafe {
53        ioctl(
54            fd,
55            SIOCETHTOOL as _,
56            &mut request as *mut _ as *mut libc::c_void,
57        )
58    };
59
60    // should always close fd
61    // Safety: Safe to call close for this file descriptor
62    unsafe {
63        close(fd);
64    }
65
66    if error < 0 {
67        None
68    } else if ethtool_ts_info.phc_index >= 0 {
69        Some(ethtool_ts_info.phc_index as u32)
70    } else {
71        None
72    }
73}
74
75pub struct ChangeDetector {
76    fd: AsyncFd<RawFd>,
77}
78
79impl ChangeDetector {
80    pub fn new() -> std::io::Result<Self> {
81        const _: () = assert!(
82            std::mem::size_of::<libc::sockaddr_storage>()
83                >= std::mem::size_of::<libc::sockaddr_nl>()
84        );
85        const _: () = assert!(
86            std::mem::align_of::<libc::sockaddr_storage>()
87                >= std::mem::align_of::<libc::sockaddr_nl>()
88        );
89
90        let mut address_buf = zeroed_sockaddr_storage();
91        // Safety: the above assertions guarantee that alignment and size are correct.
92        // the resulting reference won't outlast the function, and result lives the entire
93        // duration of the function
94        let address = unsafe {
95            &mut *(&mut address_buf as *mut libc::sockaddr_storage as *mut libc::sockaddr_nl)
96        };
97
98        address.nl_family = libc::AF_NETLINK as _;
99        address.nl_groups =
100            (libc::RTMGRP_IPV4_IFADDR | libc::RTMGRP_IPV6_IFADDR | libc::RTMGRP_LINK) as _;
101
102        // Safety: calling socket is safe
103        let fd =
104            cerr(unsafe { libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, libc::NETLINK_ROUTE) })?;
105        // Safety: address is valid for the duration of the call
106        cerr(unsafe {
107            libc::bind(
108                fd,
109                address as *mut _ as *mut _,
110                std::mem::size_of_val(address) as _,
111            )
112        })?;
113
114        let nonblocking = 1 as libc::c_int;
115        // Safety: nonblocking lives for the duration of the call, and is 4 bytes long as expected for FIONBIO
116        cerr(unsafe { libc::ioctl(fd, libc::FIONBIO, &nonblocking) })?;
117
118        Ok(ChangeDetector {
119            fd: AsyncFd::new(fd)?,
120        })
121    }
122
123    fn empty(fd: i32) {
124        loop {
125            // Safety: buf is valid for the duration of the call, and it's length is passed as the len argument
126            let mut buf = [0u8; 16];
127            match cerr(unsafe {
128                recv(
129                    fd,
130                    &mut buf as *mut _ as *mut _,
131                    std::mem::size_of_val(&buf) as _,
132                    0,
133                ) as _
134            }) {
135                Ok(_) => continue,
136                Err(e) if e.kind() == ErrorKind::WouldBlock => break,
137                Err(e) => {
138                    tracing::error!("Could not receive on change socket: {}", e);
139                    break;
140                }
141            }
142        }
143    }
144
145    pub async fn wait_for_change(&mut self) {
146        if let Err(e) = self
147            .fd
148            .async_io(Interest::READABLE, |fd| {
149                // Safety: buf is valid for the duration of the call, and it's length is passed as the len argument
150                let mut buf = [0u8; 16];
151                cerr(unsafe {
152                    recv(
153                        *fd,
154                        &mut buf as *mut _ as *mut _,
155                        std::mem::size_of_val(&buf) as _,
156                        0,
157                    ) as _
158                })?;
159                Self::empty(*fd);
160                Ok(())
161            })
162            .await
163        {
164            tracing::error!("Could not receive on change socket: {}", e);
165        }
166    }
167}