1use super::{AttachListener, ListenerCleanup, SharedContainerAttach};
5use crate::{
6 async_fd,
7 container_io::{DRAIN_MAX_WAIT, Drain, Lag, Message, READ_BUFFER_SIZE},
8 fs_util::FileId,
9 listener,
10 mutex::lock,
11 util::with_scratch,
12};
13use anyhow::{Context, Result, bail};
14use bytes::Bytes;
15use nix::{
16 errno::Errno,
17 poll::{PollFd, PollFlags, PollTimeout, poll},
18 sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags},
19 sys::socket::{Backlog, SockType, UnixAddr, bind, listen},
20};
21use std::{
22 fs, future,
23 io::{self, IoSlice},
24 os::{
25 fd::{AsFd, AsRawFd},
26 unix::fs::FileTypeExt,
27 },
28 path::Path,
29 pin::Pin,
30 sync::{Arc, atomic::Ordering},
31 task::{Poll, ready},
32};
33use tokio::{
34 io::{ErrorKind, Interest},
35 select,
36 sync::broadcast::{Receiver, error::RecvError},
37 task, time,
38};
39use tokio_seqpacket::{UnixSeqpacket, UnixSeqpacketListener};
40use tokio_util::sync::CancellationToken;
41use tracing::{Instrument, debug, debug_span, error, trace, warn};
42
43#[expect(unsafe_code, reason = "the FIONREAD ioctl")]
48fn input_ended(socket: &UnixSeqpacket) -> io::Result<bool> {
49 let events = PollFlags::from_bits_retain(libc::POLLRDHUP);
53 let read_closed = loop {
54 match poll(
55 &mut [PollFd::new(socket.as_fd(), events)],
56 PollTimeout::ZERO,
57 ) {
58 Err(Errno::EINTR) => {}
59 res => break res? > 0,
60 }
61 };
62 if !read_closed {
63 return Ok(false);
64 }
65
66 let mut queued: libc::c_int = 0;
68 if unsafe { libc::ioctl(socket.as_raw_fd(), libc::FIONREAD, &mut queued) } < 0 {
70 return Err(io::Error::last_os_error());
71 }
72 Ok(queued == 0)
73}
74
75pub(super) const PACKET_BUF_SIZE: usize = 8192;
79
80const _: () = assert!(READ_BUFFER_SIZE <= PACKET_BUF_SIZE);
82
83pub(super) const STDIN_PACKET_MAX_SIZE: usize = 33 * 1024;
89
90pub(super) const DONE_PACKET: &[u8; 1] = &[0];
92
93pub(super) fn create_listener(
96 path: &Path,
97 attach: SharedContainerAttach,
98 token: CancellationToken,
99) -> Result<()> {
100 let mut listeners = lock(&attach.listeners);
103 if let Some(listener) = listeners.remove(path) {
104 if !listener.stop.is_cancelled() && listener.is_bound_at(path) {
105 debug!("Already listening on attach socket {}", path.display());
106 listeners.insert(path.to_path_buf(), listener);
107 return Ok(());
108 }
109 debug!(
113 "Attach socket {} got removed or replaced, listening again",
114 path.display()
115 );
116 listener.stop.cancel();
117 }
118
119 match fs::symlink_metadata(path) {
122 Ok(m) if m.file_type().is_socket() => {
123 debug!("Removing stale attach socket {}", path.display());
124 fs::remove_file(path).context("remove stale attach socket")?;
125 }
126 Ok(_) => bail!(
127 "attach socket path {} exists and is no socket",
128 path.display()
129 ),
130 Err(e) if e.kind() == ErrorKind::NotFound => {}
131 Err(e) => return Err(e).context("check attach socket path"),
132 }
133
134 debug!("Creating attach socket: {}", path.display());
135 let fd = listener::private_socket(SockType::SeqPacket).context("create attach socket")?;
136
137 let (shortened_path, _parent_dir) = listener::shorten_socket_path(path)?;
139 let addr = UnixAddr::new(&shortened_path).context("create socket addr")?;
140 bind(fd.as_raw_fd(), &addr).context("bind socket fd")?;
141
142 let remove = |e: anyhow::Error| {
145 if let Err(e) = fs::remove_file(path) {
146 debug!("Unable to remove attach socket {}: {e:#}", path.display());
147 }
148 e
149 };
150 let held = fd
151 .try_clone()
152 .context("duplicate attach socket")
153 .map_err(remove)?;
154 let setup = || -> Result<(fs::Metadata, UnixSeqpacketListener)> {
155 let metadata = fs::symlink_metadata(path).context("get attach socket metadata")?;
157 listen(&fd, Backlog::MAXCONN).context("listen on socket fd")?;
158 let listener = UnixSeqpacketListener::try_from(fd).context("create seqpacket listener")?;
159 Ok((metadata, listener))
160 };
161 let (metadata, mut listener) = setup().map_err(remove)?;
162 drop(held);
163
164 let stop = CancellationToken::new();
165 let id = AttachListener::next_id();
166 listeners.insert(
167 path.to_path_buf(),
168 AttachListener {
169 id,
170 file: FileId::new(&metadata),
171 stop: stop.clone(),
172 },
173 );
174 drop(listeners);
175
176 let cleanup = ListenerCleanup {
177 attach,
178 path: path.to_path_buf(),
179 id,
180 };
181 task::spawn(
182 async move {
183 accept_connections(&mut listener, &cleanup.path, &cleanup.attach, token, stop).await;
184 drop(cleanup);
186 drop(listener);
187 }
188 .instrument(debug_span!("attach"))
189 .in_current_span(),
190 );
191
192 Ok(())
193}
194
195async fn accept_connections(
198 listener: &mut UnixSeqpacketListener,
199 path: &Path,
200 attach: &SharedContainerAttach,
201 token: CancellationToken,
202 stop: CancellationToken,
203) {
204 debug!("Start listening on attach socket {}", path.display());
205 let mut accept_errors = listener::AcceptErrors::default();
206 loop {
207 let accepted = select! {
208 accepted = listener.accept() => accepted,
209 () = token.cancelled() => {
210 debug!("Stopping attach listener because token cancelled");
211 return;
212 }
213 () = stop.cancelled() => {
214 debug!("Stopping attach listener because it got replaced");
215 return;
216 }
217 };
218 match accepted {
219 Ok(socket) => {
220 debug!("Got new attach stream connection");
221 let read = Arc::new(socket);
222 let write = read.clone();
223
224 let attach_clone = attach.clone();
225 let token_clone = token.clone();
226 task::spawn(
227 async move {
228 if let Err(e) = read_loop(read, attach_clone, token_clone).await {
229 error!("Attach read loop failure: {e:#}");
230 }
231 }
232 .instrument(debug_span!("read_loop"))
233 .in_current_span(),
234 );
235
236 let (write_half_rx, output_ended) = attach.subscribe();
237 let attach_clone = attach.clone();
238 let token_clone = token.clone();
239 task::spawn(
240 async move {
241 if let Err(e) = write_loop(
242 write,
243 attach_clone,
244 write_half_rx,
245 output_ended,
246 token_clone,
247 )
248 .await
249 {
250 error!("Attach write loop failure: {e:#}");
251 }
252 }
253 .instrument(debug_span!("write_loop"))
254 .in_current_span(),
255 );
256 }
257 Err(e) => accept_errors.failed(&e).await,
258 }
259 }
260}
261
262pub(super) async fn read_loop(
265 socket: Arc<UnixSeqpacket>,
266 attach: SharedContainerAttach,
267 token: CancellationToken,
268) -> Result<()> {
269 let stop_after_stdin_eof = attach.stop_after_stdin_eof.load(Ordering::Acquire);
270 let stopped = async || {
271 select! {
272 () = token.cancelled() => debug!("Exiting because token cancelled"),
273 () = attach.stdin_closed.cancelled() => debug!("Exiting because stdin got closed"),
274 }
275 };
276
277 loop {
278 let read = select! {
281 read = recv_stdin(&socket) => read,
282 () = stopped() => return Ok(()),
283 };
284
285 match read {
286 Ok((data, truncated)) if !data.is_empty() => {
287 if truncated {
288 warn!("Truncated attach stdin packet to {} bytes", data.len());
289 }
290 debug!("Read {} stdin bytes from client", data.len());
291 select! {
294 () = attach.send_stdin(data) => {}
295 () = stopped() => return Ok(()),
296 }
297 }
298 Ok(_) => {
299 if !input_ended(&socket).context("check attach socket for more input")? {
303 trace!("Ignoring empty packet");
304 continue;
305 }
306 debug!("Stopping read loop because there is nothing more to read");
310 if stop_after_stdin_eof {
311 attach.close_container_stdin();
312 }
313 return Ok(());
314 }
315 Err(e) if e.kind() == ErrorKind::ConnectionReset => {
316 debug!("Attach client reset the connection");
320 }
321 Err(e) => return Err(e).context("read from attach socket"),
322 }
323 }
324}
325
326async fn recv_stdin(socket: &UnixSeqpacket) -> io::Result<(Bytes, bool)> {
332 future::poll_fn(|cx| {
333 with_scratch(STDIN_PACKET_MAX_SIZE, |buf| {
334 let info = ready!(socket.poll_recv(cx, buf))?;
335 let data = Bytes::copy_from_slice(&buf[..info.bytes_read()]);
336 Poll::Ready(Ok((data, info.truncated())))
337 })
338 })
339 .await
340}
341
342fn client_gone(e: &io::Error) -> bool {
345 matches!(e.kind(), ErrorKind::BrokenPipe | ErrorKind::ConnectionReset)
346}
347
348pub(super) async fn write_loop(
354 socket: Arc<UnixSeqpacket>,
355 attach: SharedContainerAttach,
356 mut rx: Receiver<Message>,
357 output_ended: bool,
358 token: CancellationToken,
359) -> Result<()> {
360 let hangup = hangup(&socket);
361 tokio::pin!(hangup);
362 let mut drain: Option<Drain> = None;
363 let mut lag = Lag::default();
364 loop {
365 let message = if output_ended {
368 rx.try_recv().unwrap_or(Message::Done)
369 } else {
370 let res = select! {
371 biased;
374 () = token.cancelled(), if drain.is_none() => {
375 debug!("Draining the output because token cancelled");
376 drain = Some(Drain::default());
377 continue;
378 }
379 () = &mut hangup => {
380 debug!("Exiting because the client hung up");
381 return Ok(());
382 }
383 res = next_message(&attach, &mut rx, drain.as_mut(), &mut lag) => res,
384 };
385 match res {
386 Ok(message) => message,
387 Err(RecvError::Lagged(n)) => {
388 lag.skipped(n);
389 continue;
390 }
391 Err(RecvError::Closed) => {
392 debug!("Exiting because container I/O is gone");
393 return Ok(());
394 }
395 }
396 };
397 let draining = drain.is_some();
398 match message {
399 Message::Done => {
400 debug!("Exiting because the container output ended");
401 let packet = [IoSlice::new(DONE_PACKET)];
402 match send_packet(&socket, &packet, hangup.as_mut(), &token, draining).await {
403 Ok(true) => debug!("Wrote done packet to client"),
404 Ok(false) => {}
405 Err(e) => return Err(e).context("write done packet"),
406 }
407 return Ok(());
408 }
409 Message::Data(buf, pipe) => {
410 debug_assert!(buf.len() <= PACKET_BUF_SIZE);
415 let header = [pipe.attach_packet_byte()];
416 let packet = [IoSlice::new(&header), IoSlice::new(&buf)];
417 match send_packet(&socket, &packet, hangup.as_mut(), &token, draining).await {
418 Ok(true) => debug!("Wrote {pipe} packet to client"),
419 Ok(false) => return Ok(()),
422 Err(e) => return Err(e).context("write packet"),
423 }
424 }
425 }
426 }
427}
428
429async fn send_packet(
435 socket: &UnixSeqpacket,
436 packet: &[IoSlice<'_>],
437 hangup: Pin<&mut impl Future<Output = ()>>,
438 token: &CancellationToken,
439 draining: bool,
440) -> io::Result<bool> {
441 let stuck = async {
442 if !draining {
443 token.cancelled().await;
444 }
445 time::sleep(DRAIN_MAX_WAIT).await;
446 };
447 select! {
448 res = socket.send_vectored(packet) => match res {
449 Ok(_) => Ok(true),
450 Err(ref e) if client_gone(e) => {
451 debug!("Exiting because the client closed the connection");
452 Ok(false)
453 }
454 Err(e) => Err(e),
455 },
456 () = hangup => {
457 debug!("Exiting because the client hung up");
458 Ok(false)
459 }
460 () = stuck => {
461 debug!("Exiting because the client does not take the output after the exit");
462 Ok(false)
463 }
464 }
465}
466
467async fn next_message(
471 attach: &SharedContainerAttach,
472 rx: &mut Receiver<Message>,
473 drain: Option<&mut Drain>,
474 lag: &mut Lag,
475) -> Result<Message, RecvError> {
476 match drain {
477 Some(drain) => Ok(drain
478 .recv_attach(attach, rx, Some(lag))
479 .await
480 .unwrap_or(Message::Done)),
481 None => {
482 let res = rx.recv().await;
483 if res.is_ok() {
484 lag.received(rx);
485 }
486 res
487 }
488 }
489}
490
491async fn hangup(socket: &UnixSeqpacket) {
500 let wait = async || -> Result<()> {
501 let epoll = Epoll::new(EpollCreateFlags::EPOLL_CLOEXEC).context("create epoll")?;
502 epoll
503 .add(socket.as_fd(), EpollEvent::new(EpollFlags::EPOLLET, 0))
504 .context("watch attach socket")?;
505 let ready =
506 async_fd::register(epoll.0.as_fd(), Interest::READABLE).context("register epoll")?;
507 let mut events = [EpollEvent::empty()];
508 loop {
509 let mut guard = ready.readable().await.context("wait for epoll")?;
510 let n = epoll
511 .wait(&mut events, PollTimeout::ZERO)
512 .context("read epoll events")?;
513 if n > 0 {
514 return Ok(());
515 }
516 guard.clear_ready();
517 }
518 };
519 if let Err(e) = wait().await {
520 warn!("Unable to watch for the attach client to hang up: {e:#}");
521 future::pending::<()>().await;
522 }
523}