chrony_candm/
net.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: GPL-2.0-only
3
4use bytes::BytesMut;
5use rand::Rng;
6
7use crate::reply::Reply;
8use crate::request::{increment_attempt, Request, RequestBody};
9
10use std::ffi::OsStr;
11use std::fs::Permissions;
12use std::net::Ipv6Addr;
13use std::ops::{Deref, DerefMut};
14use std::time::Duration;
15
16#[cfg(unix)]
17use std::os::unix::ffi::OsStrExt;
18#[cfg(unix)]
19use std::os::unix::fs::PermissionsExt;
20#[cfg(unix)]
21use std::os::unix::net::UnixDatagram;
22
23pub const DEFAULT_PORT: u16 = 323;
24
25/// Options for configuring a Chrony client
26#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub struct ClientOptions {
28    /// How long to wait for a reply before assuming the request was dropped (default: 1s)
29    pub timeout: Duration,
30    /// Number of times to try a request before giving up (default: 3)
31    pub n_tries: u16,
32}
33
34impl Default for ClientOptions {
35    fn default() -> Self {
36        Self {
37            n_tries: 3,
38            timeout: Duration::from_secs(1),
39        }
40    }
41}
42
43///Common interface to `UdpSocket` and `UnixDatagram` to avoid copypasta between our two `blocking_query` functions
44trait DgramSocket {
45    fn send(&self, buf: &[u8]) -> std::io::Result<usize>;
46    fn recv(&self, buf: &mut [u8]) -> std::io::Result<usize>;
47}
48
49impl DgramSocket for std::net::UdpSocket {
50    fn send(&self, buf: &[u8]) -> std::io::Result<usize> {
51        std::net::UdpSocket::send(self, buf)
52    }
53
54    fn recv(&self, buf: &mut [u8]) -> std::io::Result<usize> {
55        std::net::UdpSocket::recv(self, buf)
56    }
57}
58
59#[cfg(unix)]
60impl DgramSocket for UnixDatagram {
61    fn send(&self, buf: &[u8]) -> std::io::Result<usize> {
62        UnixDatagram::send(self, buf)
63    }
64
65    fn recv(&self, buf: &mut [u8]) -> std::io::Result<usize> {
66        UnixDatagram::recv(self, buf)
67    }
68}
69
70#[cfg(unix)]
71#[derive(Debug)]
72struct UnixDatagramClient(UnixDatagram);
73
74#[cfg(unix)]
75impl AsRef<UnixDatagram> for UnixDatagramClient {
76    fn as_ref(&self) -> &UnixDatagram {
77        &self.0
78    }
79}
80
81#[cfg(unix)]
82impl AsMut<UnixDatagram> for UnixDatagramClient {
83    fn as_mut(&mut self) -> &mut UnixDatagram {
84        &mut self.0
85    }
86}
87
88#[cfg(unix)]
89impl Deref for UnixDatagramClient {
90    type Target = UnixDatagram;
91    fn deref(&self) -> &UnixDatagram {
92        &self.0
93    }
94}
95
96#[cfg(unix)]
97impl DerefMut for UnixDatagramClient {
98    fn deref_mut(&mut self) -> &mut UnixDatagram {
99        &mut self.0
100    }
101}
102
103#[cfg(unix)]
104impl Drop for UnixDatagramClient {
105    fn drop(&mut self) {
106        if let Ok(addr) = self.0.local_addr() {
107            if let Some(path) = addr.as_pathname() {
108                let _ = self.0.shutdown(std::net::Shutdown::Both);
109                let _ = std::fs::remove_file(path);
110            }
111        }
112    }
113}
114
115#[cfg(unix)]
116impl UnixDatagramClient {
117    fn new() -> std::io::Result<UnixDatagramClient> {
118        let id: [u8; 16] = rand::random();
119        let mut path = b"/var/run/chrony/client-000102030405060708090a0b0c0d0e0f.sock".clone();
120        hex::encode_to_slice(id, &mut path[23..55]).unwrap();
121        let path_str = OsStr::from_bytes(&path);
122        let sock = UnixDatagram::bind(path_str)?;
123        let client = UnixDatagramClient(sock);
124        std::fs::set_permissions(path_str, Permissions::from_mode(0o777))?;
125        client.connect("/var/run/chrony/chronyd.sock")?;
126        Ok(client)
127    }
128}
129
130fn blocking_query_loop<Sock: DgramSocket>(
131    sock: &Sock,
132    request_body: RequestBody,
133    options: ClientOptions,
134) -> std::io::Result<Reply> {
135    let request = Request {
136        sequence: rand::thread_rng().gen(),
137        attempt: 0,
138        body: request_body,
139    };
140
141    let mut send_buf = BytesMut::with_capacity(request.length());
142    request.serialize(&mut send_buf);
143    let mut recv_buf = [0; 1500];
144    let mut attempt = 0;
145
146    loop {
147        sock.send(send_buf.as_ref())?;
148        match sock.recv(&mut recv_buf) {
149            Ok(len) => {
150                let mut msg = &recv_buf[0..len];
151                match Reply::deserialize(&mut msg) {
152                    Ok(reply) => {
153                        if reply.sequence == request.sequence {
154                            return Ok(reply);
155                        } else {
156                            return Err(std::io::Error::new(
157                                std::io::ErrorKind::InvalidData,
158                                "Bad sequence number",
159                            ));
160                        }
161                    }
162                    Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)),
163                }
164            }
165            Err(e) => {
166                if e.kind() == std::io::ErrorKind::TimedOut
167                    || e.kind() == std::io::ErrorKind::WouldBlock
168                {
169                    attempt += 1;
170                    if attempt == options.n_tries {
171                        return Err(e);
172                    }
173                    increment_attempt(send_buf.as_mut());
174                } else {
175                    return Err(e);
176                }
177            }
178        }
179    }
180}
181
182/// Sends a request to a server via UDP and waits for a reply
183pub fn blocking_query<Server: std::net::ToSocketAddrs>(
184    request_body: RequestBody,
185    options: ClientOptions,
186    server: &Server,
187) -> std::io::Result<Reply> {
188    let sock = std::net::UdpSocket::bind((Ipv6Addr::UNSPECIFIED, 0))?;
189    sock.connect(server)?;
190    sock.set_read_timeout(Some(options.timeout))?;
191
192    blocking_query_loop(&sock, request_body, options)
193}
194
195/// Sends a request to a server via a domain socket and waits for a reply
196#[cfg(unix)]
197pub fn blocking_query_uds(
198    request_body: RequestBody,
199    options: ClientOptions,
200) -> std::io::Result<Reply> {
201    let sock = UnixDatagramClient::new()?;
202    sock.set_read_timeout(Some(options.timeout))?;
203    blocking_query_loop(sock.as_ref(), request_body, options)
204}