1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
use std::{array, io::ErrorKind, os::fd::RawFd};

use libc::{
    __c_anonymous_ifr_ifru, close, ifreq, ioctl, recv, socket, AF_INET, SIOCETHTOOL, SOCK_DGRAM,
};
use tokio::io::{unix::AsyncFd, Interest};

use crate::{cerr, control_message::zeroed_sockaddr_storage};

use super::InterfaceName;

#[repr(C)]
struct EthtoolTsInfo {
    cmd: u32,
    so_timestamping: u32,
    phc_index: i32,
    tx_types: u32,
    reserved1: [u32; 3],
    rx_filters: u32,
    reserved2: [u32; 3],
}

const ETHTOOL_GET_TS_INFO: u32 = 0x41;

pub fn lookup_phc(interface: InterfaceName) -> Option<u32> {
    // Safety: socket is safe to call with these constants as
    // arguments
    let fd = unsafe { socket(AF_INET, SOCK_DGRAM, 0) };
    if fd < 0 {
        tracing::error!("Could not open socket for looking up PHC index");
        return None;
    }

    let mut ethtool_ts_info = EthtoolTsInfo {
        cmd: ETHTOOL_GET_TS_INFO,
        so_timestamping: 0,
        phc_index: -1,
        tx_types: 0,
        reserved1: [0; 3],
        rx_filters: 0,
        reserved2: [0; 3],
    };

    let mut request = ifreq {
        ifr_name: array::from_fn(|i| interface.bytes[i] as _),
        ifr_ifru: __c_anonymous_ifr_ifru {
            ifru_data: &mut ethtool_ts_info as *mut _ as *mut _,
        },
    };

    // Safety: request and ethtool_ts_info are live for the duration of the call.
    let error = unsafe {
        ioctl(
            fd,
            SIOCETHTOOL as _,
            &mut request as *mut _ as *mut libc::c_void,
        )
    };

    // should always close fd
    // Safety: Safe to call close for this file descriptor
    unsafe {
        close(fd);
    }

    if error < 0 {
        None
    } else if ethtool_ts_info.phc_index >= 0 {
        Some(ethtool_ts_info.phc_index as u32)
    } else {
        None
    }
}

pub struct ChangeDetector {
    fd: AsyncFd<RawFd>,
}

impl ChangeDetector {
    pub fn new() -> std::io::Result<Self> {
        const _: () = assert!(
            std::mem::size_of::<libc::sockaddr_storage>()
                >= std::mem::size_of::<libc::sockaddr_nl>()
        );
        const _: () = assert!(
            std::mem::align_of::<libc::sockaddr_storage>()
                >= std::mem::align_of::<libc::sockaddr_nl>()
        );

        let mut address_buf = zeroed_sockaddr_storage();
        // Safety: the above assertions guarantee that alignment and size are correct.
        // the resulting reference won't outlast the function, and result lives the entire
        // duration of the function
        let address = unsafe {
            &mut *(&mut address_buf as *mut libc::sockaddr_storage as *mut libc::sockaddr_nl)
        };

        address.nl_family = libc::AF_NETLINK as _;
        address.nl_groups =
            (libc::RTMGRP_IPV4_IFADDR | libc::RTMGRP_IPV6_IFADDR | libc::RTMGRP_LINK) as _;

        // Safety: calling socket is safe
        let fd =
            cerr(unsafe { libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, libc::NETLINK_ROUTE) })?;
        // Safety: address is valid for the duration of the call
        cerr(unsafe {
            libc::bind(
                fd,
                address as *mut _ as *mut _,
                std::mem::size_of_val(address) as _,
            )
        })?;

        let nonblocking = 1 as libc::c_int;
        // Safety: nonblocking lives for the duration of the call, and is 4 bytes long as expected for FIONBIO
        cerr(unsafe { libc::ioctl(fd, libc::FIONBIO, &nonblocking) })?;

        Ok(ChangeDetector {
            fd: AsyncFd::new(fd)?,
        })
    }

    fn empty(fd: i32) {
        loop {
            // Safety: buf is valid for the duration of the call, and it's length is passed as the len argument
            let mut buf = [0u8; 16];
            match cerr(unsafe {
                recv(
                    fd,
                    &mut buf as *mut _ as *mut _,
                    std::mem::size_of_val(&buf) as _,
                    0,
                ) as _
            }) {
                Ok(_) => continue,
                Err(e) if e.kind() == ErrorKind::WouldBlock => break,
                Err(e) => {
                    tracing::error!("Could not receive on change socket: {}", e);
                    break;
                }
            }
        }
    }

    pub async fn wait_for_change(&mut self) {
        if let Err(e) = self
            .fd
            .async_io(Interest::READABLE, |fd| {
                // Safety: buf is valid for the duration of the call, and it's length is passed as the len argument
                let mut buf = [0u8; 16];
                cerr(unsafe {
                    recv(
                        *fd,
                        &mut buf as *mut _ as *mut _,
                        std::mem::size_of_val(&buf) as _,
                        0,
                    ) as _
                })?;
                Self::empty(*fd);
                Ok(())
            })
            .await
        {
            tracing::error!("Could not receive on change socket: {}", e);
        }
    }
}