1use anyhow::{Context, Result};
5use nix::sys::{
6 socket::{AddressFamily, Backlog, SockFlag, SockType, UnixAddr, bind, listen, socket},
7 stat::{Mode, fchmod},
8};
9use std::{
10 error::Error,
11 ffi::OsStr,
12 fmt,
13 fs::{self, File},
14 io,
15 os::{fd::OwnedFd, unix::io::AsRawFd},
16 path::{Path, PathBuf},
17 time::{Duration, Instant},
18};
19use tokio::time;
20use tracing::{debug, error};
21
22const ACCEPT_BACKOFF: Duration = Duration::from_millis(100);
24
25const ACCEPT_ERROR_INTERVAL: Duration = Duration::from_secs(10);
29
30#[derive(Debug, Default)]
32pub(crate) struct AcceptErrors {
33 last_logged: Option<Instant>,
35}
36
37impl AcceptErrors {
38 pub(crate) async fn failed(&mut self, e: &io::Error) {
43 if connection_failed(e) {
44 debug!("Unable to accept connection: {e}");
45 return;
46 }
47 if self.log_error_now(Instant::now()) {
48 error!("Unable to accept connection: {e}");
49 } else {
50 debug!("Unable to accept connection: {e}");
51 }
52 time::sleep(ACCEPT_BACKOFF).await;
53 }
54
55 fn log_error_now(&mut self, now: Instant) -> bool {
58 if self
59 .last_logged
60 .is_some_and(|last| now.saturating_duration_since(last) < ACCEPT_ERROR_INTERVAL)
61 {
62 return false;
63 }
64 self.last_logged = Some(now);
65 true
66 }
67}
68
69fn connection_failed(e: &io::Error) -> bool {
72 matches!(
73 e.raw_os_error(),
74 Some(libc::ECONNABORTED | libc::ECONNREFUSED | libc::ECONNRESET | libc::EPROTO)
75 )
76}
77
78#[derive(Debug)]
82pub(crate) struct SocketInUse(pub(crate) String);
83
84impl fmt::Display for SocketInUse {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 f.write_str(&self.0)
87 }
88}
89
90impl Error for SocketInUse {}
91
92pub(crate) fn private_socket(typ: SockType) -> io::Result<OwnedFd> {
97 let fd = socket(
98 AddressFamily::Unix,
99 typ,
100 SockFlag::SOCK_NONBLOCK | SockFlag::SOCK_CLOEXEC,
101 None,
102 )?;
103 fchmod(&fd, Mode::S_IRWXU)?;
104 Ok(fd)
105}
106
107pub(crate) fn listen_private(path: &Path) -> io::Result<OwnedFd> {
110 let fd = private_socket(SockType::Stream)?;
111 bind(fd.as_raw_fd(), &UnixAddr::new(path)?)?;
112 listen(&fd, Backlog::MAXCONN)?;
113 Ok(fd)
114}
115
116pub(crate) fn bind_long_path<L>(
120 path: &Path,
121 bind: impl FnOnce(PathBuf) -> io::Result<L>,
122) -> Result<L> {
123 let (path, _parent_dir) = shorten_socket_path(path)?;
125 Ok(bind(path)?)
126}
127
128pub(crate) fn shorten_socket_path(path: &Path) -> Result<(PathBuf, File)> {
131 let (parent, _) = split_socket_path(path)?;
132 fs::create_dir_all(parent).context("create parent directory")?;
133 shorten_path(path)
134}
135
136pub(crate) fn shorten_path(path: &Path) -> Result<(PathBuf, File)> {
141 let (parent, name) = split_socket_path(path)?;
142 let parent = File::open(parent).context("open parent directory")?;
143 let fd = parent.as_raw_fd();
144
145 Ok((
146 PathBuf::from("/proc/self/fd")
147 .join(fd.to_string())
148 .join(name),
149 parent,
150 ))
151}
152
153fn split_socket_path(path: &Path) -> Result<(&Path, &OsStr)> {
156 let parent = path.parent().with_context(|| {
157 format!(
158 "tried to specify / as socket to bind to: {}",
159 path.display()
160 )
161 })?;
162 let parent = if parent.as_os_str().is_empty() {
163 Path::new(".")
164 } else {
165 parent
166 };
167 let name = path.file_name().with_context(|| {
168 format!(
169 "tried to specify '..' as socket to bind to: {}",
170 path.display(),
171 )
172 })?;
173 Ok((parent, name))
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use std::os::unix::fs::PermissionsExt;
180 use tokio::net::UnixListener;
181
182 #[test]
183 fn accept_errors_get_logged_once_per_interval() {
184 let mut errors = AcceptErrors::default();
185 let start = Instant::now();
186 assert!(errors.log_error_now(start));
187 assert!(!errors.log_error_now(start + ACCEPT_BACKOFF));
188 assert!(AcceptErrors::default().log_error_now(start + ACCEPT_BACKOFF));
190 assert!(errors.log_error_now(start + ACCEPT_ERROR_INTERVAL));
191 }
192
193 #[tokio::test(start_paused = true)]
194 async fn only_failed_connections_accept_at_once() {
195 let start = time::Instant::now();
196 let mut errors = AcceptErrors::default();
197 for errno in [
198 libc::ECONNABORTED,
199 libc::ECONNREFUSED,
200 libc::ECONNRESET,
201 libc::EPROTO,
202 ] {
203 errors.failed(&io::Error::from_raw_os_error(errno)).await;
204 }
205 assert_eq!(start.elapsed(), Duration::ZERO);
206 errors
207 .failed(&io::Error::from_raw_os_error(libc::EMFILE))
208 .await;
209 assert_eq!(start.elapsed(), ACCEPT_BACKOFF);
210 }
211
212 #[tokio::test]
213 async fn bind_long_path_creates_the_directory() -> Result<()> {
214 let dir = tempfile::tempdir()?;
215 let parent = dir.path().join("x".repeat(100)).join("y".repeat(100));
217 let path = parent.join("socket");
218 let listener = bind_long_path(&path, UnixListener::bind)?;
219 assert!(path.exists());
220 drop(listener);
221
222 assert!(bind_long_path(&path, UnixListener::bind).is_err());
224 Ok(())
225 }
226
227 #[tokio::test]
228 async fn listen_private_binds_private_listeners() -> Result<()> {
229 let dir = tempfile::tempdir()?;
230 let path = dir.path().join("socket");
231 let fd = bind_long_path(&path, |path| listen_private(&path))?;
232 assert_eq!(fs::metadata(&path)?.permissions().mode() & 0o077, 0);
233 let listener = UnixListener::from_std(fd.into())?;
234 let _client = tokio::net::UnixStream::connect(&path).await?;
235 listener.accept().await?;
236 Ok(())
237 }
238
239 #[test]
240 fn shorten_socket_path_refers_to_the_parent() -> Result<()> {
241 let dir = tempfile::tempdir()?;
242 let (path, parent) = shorten_socket_path(&dir.path().join("new").join("socket"))?;
243 assert!(path.ends_with(format!("{}/socket", parent.as_raw_fd())));
244 assert!(dir.path().join("new").is_dir());
245
246 assert!(split_socket_path(Path::new("socket"))?.0 == Path::new("."));
248 assert!(shorten_socket_path(Path::new("/")).is_err());
249 Ok(())
250 }
251
252 #[test]
253 fn shorten_socket_path_failures() -> Result<()> {
254 let dir = tempfile::tempdir()?;
255 let file = dir.path().join("file");
256 fs::write(&file, "")?;
257 assert!(shorten_socket_path(&file.join("socket")).is_err());
259 assert!(shorten_path(&dir.path().join("missing").join("socket")).is_err());
261 Ok(())
262 }
263
264 #[test]
265 fn private_socket_is_created_private() -> Result<()> {
266 let dir = tempfile::tempdir()?;
267 let path = dir.path().join("socket");
268 let fd = private_socket(SockType::Stream)?;
269 bind(fd.as_raw_fd(), &UnixAddr::new(&path)?)?;
272 assert_eq!(fs::metadata(&path)?.permissions().mode() & 0o077, 0);
273 Ok(())
274 }
275}