timestamped_socket/interface/
linux.rs1use 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#[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 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 let error = unsafe {
63 ioctl(
64 fd,
65 SIOCETHTOOL as _,
66 (&raw mut request).cast::<libc::c_void>(),
67 )
68 };
69
70 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
85pub struct ChangeDetector {
87 fd: AsyncFd<RawFd>,
88}
89
90impl ChangeDetector {
91 #[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 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 let fd =
121 cerr(unsafe { libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, libc::NETLINK_ROUTE) })?;
122 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 cerr(unsafe { libc::ioctl(fd, libc::FIONBIO, &nonblocking) })?;
134
135 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 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 #[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 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}