conmonrs/streaming_server/session.rs
1//! What exec and attach sessions share: the connection to the client, its
2//! input and the final status.
3
4mod pending_stdin;
5
6use pending_stdin::PendingStdin;
7
8use super::{Session, StreamingServer};
9use crate::{
10 attach::SharedContainerAttach,
11 container_io::SharedContainerIO,
12 streaming_server::{
13 idle::Activity,
14 remotecommand::{
15 ClientEvent, ClientInput, ExpectedStreams, Framing, MESSAGE_TOO_LARGE, Operation,
16 Output, SpdyStreams, Status, Stdin, Version, WebSocketInput,
17 },
18 spdy::{self, Connection},
19 web::{self, WebSocket},
20 },
21};
22use anyhow::{Context, Result, bail};
23use bytes::Bytes;
24use futures::stream::StreamExt;
25use serde::{
26 Deserialize, Deserializer,
27 de::{IgnoredAny, MapAccess, Visitor},
28};
29use std::{
30 fmt,
31 future::Future,
32 ops::ControlFlow,
33 sync::{
34 Arc,
35 atomic::{AtomicBool, Ordering},
36 },
37};
38use tokio::{
39 select,
40 sync::{
41 mpsc::{self, Receiver as MpscReceiver, Sender as MpscSender},
42 watch,
43 },
44 task::{self, JoinHandle},
45 time::{self, Duration},
46};
47use tokio_util::sync::CancellationToken;
48use tracing::{Instrument, Span, debug, error, error_span, info, trace, warn};
49
50/// How much stdin the read task holds while the container does not take it,
51/// on top of the stdin channel. The socket is still read meanwhile, so that
52/// resize events and the close do not wait behind it. The message which
53/// fills it comes on top, also when it is larger than all of it.
54pub(super) const PENDING_STDIN_MAX_BYTES: usize = 1024 * 1024;
55
56/// How many entries of stdin wait for the session loop. Each is at most
57/// [`STDIN_CHUNK_SIZE`](pending_stdin::STDIN_CHUNK_SIZE), more of them would
58/// only hold more stdin which the container did not take.
59const STDIN_QUEUE_SIZE: usize = 1;
60
61/// How often a session pings the client, to notice a client which went away
62/// also while its stdin waits for the container. A WebSocket session pings
63/// only while stdin waits, a SPDY connection all the time.
64pub(super) const PING_INTERVAL: Duration = if cfg!(test) {
65 Duration::from_millis(100)
66} else {
67 spdy::PING_INTERVAL
68};
69
70/// How long the stdin which the client sent right before closing may wait for
71/// the container to take a part of it, see [`ClientRx::stdin_taken`]. A slow
72/// container gets all of it, only one which stopped reading its stdin holds
73/// up the end of the session this long.
74pub(super) const STDIN_FLUSH_TIMEOUT: Duration = if cfg!(test) {
75 Duration::from_secs(2)
76} else {
77 Duration::from_secs(5)
78};
79
80/// The latest resize event of a client, older ones are replaced.
81pub(super) type ResizeRx = watch::Receiver<Option<Bytes>>;
82
83/// How the input of a client ended.
84enum InputEnd<I> {
85 /// The client closed the connection cleanly.
86 Closed,
87
88 /// The client went away.
89 Gone,
90
91 /// The client sent a message larger than the server takes, its input
92 /// still has to drain the connection, see [`ClientInput::drain`].
93 TooLarge(I),
94}
95
96/// How a single event of a client ended its input, see
97/// [`StreamingServer::read_event`].
98#[derive(Debug, PartialEq)]
99enum EventEnd {
100 Closed,
101 TooLarge,
102}
103
104/// What the input of a client hands to the loop of its session.
105pub(super) struct ClientRx {
106 stdin: MpscReceiver<Stdin>,
107 resize: ResizeRx,
108
109 /// Cancelled once the client sent a message larger than the server
110 /// takes, which fails the session.
111 too_large: CancellationToken,
112
113 /// Touched whenever the container took a part of the stdin, at the clean
114 /// close of the client, and while the runtime starts an exec process
115 /// with stdin.
116 /// The stdin before the close gets forwarded until the container did not
117 /// take any of it for [`STDIN_FLUSH_TIMEOUT`].
118 stdin_taken: Activity,
119}
120
121#[cfg(test)]
122impl ClientRx {
123 /// The input of a client which sends nothing while the returned senders
124 /// live.
125 pub(super) fn idle() -> (Self, (MpscSender<Stdin>, watch::Sender<Option<Bytes>>)) {
126 let (stdin_tx, stdin) = mpsc::channel(STDIN_QUEUE_SIZE);
127 let (resize_tx, resize) = watch::channel(None);
128 let client = Self {
129 stdin,
130 resize,
131 too_large: CancellationToken::new(),
132 stdin_taken: Activity::new(),
133 };
134 (client, (stdin_tx, resize_tx))
135 }
136}
137
138/// Terminal resize event for exec and attach. Kubernetes decodes it with
139/// Go, which matches the names of the fields regardless of their case, so
140/// clients send `{"width":80}` as well as `{"Width":80}`, and leaves a
141/// missing or null field at zero, and a null event all zero. For `Width` and
142/// `Height`, ASCII case folding accepts the same names as the Unicode case
143/// folding of Go, which only adds the Kelvin sign for `k` and the long s for
144/// `s`.
145#[derive(Debug, Default, PartialEq)]
146struct ResizeEvent {
147 width: u16,
148 height: u16,
149}
150
151impl ResizeEvent {
152 fn parse(data: &[u8]) -> Result<Self> {
153 let event: Option<Self> = serde_json::from_slice(data).context("unmarshal resize event")?;
154 Ok(event.unwrap_or_default())
155 }
156}
157
158impl<'de> Deserialize<'de> for ResizeEvent {
159 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
160 /// The fields in the order of the document, like Go, where the last
161 /// one of the names which match wins.
162 struct Fields;
163
164 impl<'de> Visitor<'de> for Fields {
165 type Value = ResizeEvent;
166
167 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 f.write_str("a terminal size")
169 }
170
171 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<ResizeEvent, A::Error> {
172 let mut event = ResizeEvent::default();
173 while let Some(name) = map.next_key::<String>()? {
174 let field = if name.eq_ignore_ascii_case("width") {
175 &mut event.width
176 } else if name.eq_ignore_ascii_case("height") {
177 &mut event.height
178 } else {
179 map.next_value::<IgnoredAny>()?;
180 continue;
181 };
182 if let Some(value) = map.next_value()? {
183 *field = value;
184 }
185 }
186 Ok(event)
187 }
188 }
189
190 deserializer.deserialize_map(Fields)
191 }
192}
193
194impl StreamingServer {
195 /// Handle a single WebSocket connection, in the span of its session.
196 pub(super) async fn handle_websocket(socket: WebSocket, session: Session, framing: Framing) {
197 let span = Self::session_span(&session);
198 Self::run_websocket(socket, session, framing)
199 .instrument(span)
200 .await;
201 }
202
203 /// Run the session of a WebSocket connection.
204 async fn run_websocket(socket: WebSocket, session: Session, framing: Framing) {
205 let discard = socket.get_ref().discarder();
206 let written = socket.get_ref().written();
207 let (sender, messages) = socket.split();
208 let activity = Activity::new();
209 let mut output = Output::websocket(
210 sender,
211 framing,
212 activity.clone(),
213 written.clone(),
214 session.common().idle_timeout,
215 );
216 // A client which went away already ends the session in its loop,
217 // which tears it down.
218 if let Err(e) = output
219 .announce(session.common().stdout, session.common().stderr)
220 .await
221 {
222 debug!("Unable to announce the session: {e:#}");
223 }
224 let input = WebSocketInput {
225 messages,
226 framing,
227 activity: activity.clone(),
228 written,
229 discard,
230 };
231 Self::run_session(output, input, session, activity).await;
232 debug!("Closing WebSocket connection");
233 }
234
235 /// Handle a connection which got upgraded to SPDY, in the span of its
236 /// session, which the tasks of the connection inherit: a protocol error
237 /// of the client names the container as well.
238 pub(super) async fn handle_spdy<T>(io: T, session: Session, version: Version)
239 where
240 T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static,
241 {
242 let span = Self::session_span(&session);
243 Self::run_spdy(io, session, version).instrument(span).await;
244 }
245
246 /// Wait for the streams of the client of a SPDY connection, then run the
247 /// session on them.
248 async fn run_spdy<T>(io: T, session: Session, version: Version)
249 where
250 T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static,
251 {
252 let terminal = session.terminal();
253 // A terminal merges stderr into stdout, Kubernetes clients do not
254 // open a stream for it then.
255 let expected = ExpectedStreams {
256 stdin: session.common().stdin,
257 stdout: session.common().stdout,
258 stderr: session.common().stderr && !terminal,
259 };
260 // The session ends the connection once it was idle, which reports
261 // the timeout to the client first.
262 let mut connection = Connection::serve(io, None, Some(PING_INTERVAL), move |headers| {
263 SpdyStreams::accept(headers, version)
264 });
265 let activity = connection.activity();
266 let streams = match SpdyStreams::wait(&mut connection, expected).await {
267 Ok(streams) => streams,
268 Err(e) => {
269 debug!("Unable to start the session: {e:#}");
270 connection.handle().abort();
271 return;
272 }
273 };
274 let (input, output) = streams.into_parts(connection, version).await;
275 let output = Output::spdy(output, session.common().idle_timeout);
276 Self::run_session(output, input, session, activity).await;
277 debug!("Closing SPDY connection");
278 }
279
280 /// The span of a session and of the tasks it starts, which shows the
281 /// container ID at every log level, like the span of a request for a
282 /// container does, see `enter_request` of the RPC interface.
283 fn session_span(session: &Session) -> Span {
284 error_span!("session", container_id = &*session.common().container_id)
285 }
286
287 /// Run a session until it ended or the client went away. `activity`
288 /// tells when the connection was active the last time, for the idle
289 /// timeout of the session.
290 async fn run_session(
291 output: Output,
292 input: impl ClientInput,
293 session: Session,
294 activity: Activity,
295 ) {
296 let peer_closed = output.peer_closed();
297 let (stdin_tx, stdin_rx) = mpsc::channel(STDIN_QUEUE_SIZE);
298 // Resize events go through their own channel, so that they do not
299 // queue behind stdin the container does not read. Only the latest
300 // size matters.
301 let (resize_tx, resize_rx) = watch::channel(None);
302 let too_large = CancellationToken::new();
303 let stdin_taken = Activity::new();
304 let client = ClientRx {
305 stdin: stdin_rx,
306 resize: resize_rx,
307 too_large: too_large.clone(),
308 stdin_taken: stdin_taken.clone(),
309 };
310
311 let mut send_task = Self::write_task(output, client, session, activity.clone());
312 let mut recv_task = Self::read_task(
313 input,
314 stdin_tx,
315 resize_tx,
316 peer_closed,
317 too_large,
318 stdin_taken.clone(),
319 );
320
321 tokio::select! {
322 rv_a = (&mut send_task) => {
323 let output = Self::sent(rv_a);
324 // The client answers the close of a WebSocket, the read task
325 // drops what it sends until then, see
326 // [`web::await_close_answer`]. A close which did not go out
327 // gets no answer. A SPDY connection ended already, which ends
328 // the read task. A client which sent a message which was too
329 // large, also one whose session failed for it, cannot answer
330 // anymore, the connection gets drained instead.
331 if let Some(output) = output {
332 match time::timeout(web::CLOSE_TIMEOUT, &mut recv_task).await {
333 Ok(Ok(InputEnd::TooLarge(input))) => input.drain(output).await,
334 Ok(_) => {}
335 Err(_) => debug!("Client did not answer the close in time"),
336 }
337 }
338 recv_task.abort();
339 },
340 rv_b = (&mut recv_task) => {
341 match rv_b {
342 // After a clean close the session forwards the stdin which
343 // the client sent before, while the container takes it.
344 // The read task forwarded it since the close already, the
345 // wait goes on from the last part the container took.
346 Ok(InputEnd::Closed) => {
347 debug!("Client closed the connection cleanly, or after its stdin ended");
348 if stdin_taken
349 .until_stalled(stdin_taken.last(), STDIN_FLUSH_TIMEOUT, &mut send_task)
350 .await
351 .is_none()
352 {
353 debug!("Session did not end after the close, stopping it");
354 }
355 }
356 // The session fails, the client still gets its status and
357 // the close, which tells why, and what it still sends
358 // gets drained, so that they do not get lost. A client
359 // still busy with the rest of its message reads them only
360 // once it got rid of it, see [`web::Discard`]. The loop
361 // of the session ends right away, the status and the
362 // close end once the client stopped reading, see
363 // [`Output::close`].
364 Ok(InputEnd::TooLarge(input)) => {
365 info!("Client sent a message larger than the limit");
366 if let Ok(Some(output)) = (&mut send_task).await {
367 input.drain(output).await;
368 }
369 }
370 // The session ends a SPDY connection once it sent
371 // everything, which ends the input as well.
372 Ok(InputEnd::Gone) if send_task.is_finished() => {
373 Self::sent((&mut send_task).await);
374 }
375 Ok(InputEnd::Gone) => debug!("Client connection ended"),
376 Err(e) => error!("Unable to receive messages: {e}"),
377 }
378 send_task.abort();
379 }
380 }
381 }
382
383 /// Log how the write task ended, return its output once the close went
384 /// out.
385 fn sent(result: Result<Option<Output>, task::JoinError>) -> Option<Output> {
386 match result {
387 Ok(output) => {
388 debug!("All messages sent");
389 output
390 }
391 Err(e) => {
392 error!("Unable to send messages: {e}");
393 None
394 }
395 }
396 }
397
398 /// Build a common write task based on the session type. The loop of the
399 /// session only decides its final status, which goes out after it, so
400 /// that the idle timeout never cuts a status off: a client gets exactly
401 /// one, or none if it went away. A session whose connection was idle for
402 /// its timeout fails, like any other session which did not end, so that
403 /// the client does not take it for a success, unless the process exited
404 /// already. An exec process gets killed then, nothing is left to end it.
405 /// The task returns the output once the close went out, see
406 /// [`Output::close`].
407 fn write_task(
408 sender: Output,
409 client: ClientRx,
410 session: Session,
411 activity: Activity,
412 ) -> JoinHandle<Option<Output>> {
413 let idle_timeout = session.common().idle_timeout;
414 let too_large = client.too_large.clone();
415 let task = async move {
416 let mut sender = sender;
417 let mut status = None;
418 let (result, operation) = match session {
419 Session::Exec(s) => (
420 async {
421 // The runtime runs in a task of its own. A client
422 // which goes away meanwhile must not kill it halfway,
423 // the process it started still gets registered, and
424 // torn down once the task finished. A client which
425 // sent too much fails the session meanwhile, it
426 // does not wait for its status. The terminal gets
427 // the size of the client meanwhile. The stdin which
428 // the client sent before a close waits for the
429 // process, the runtime does not count as a container
430 // which stopped taking it while it runs (it gets
431 // killed after its deadline). A clean close without
432 // pending stdin waits for the runtime as well then,
433 // the process gets torn down in order afterwards.
434 // Without stdin, nothing waits.
435 let stdin = s.common.stdin;
436 let start = Self::start_exec(*s, client.resize.clone());
437 let mut start = task::spawn(start.in_current_span());
438 let period = STDIN_FLUSH_TIMEOUT / 2;
439 let mut starting = time::interval_at(time::Instant::now() + period, period);
440 let started = loop {
441 select! {
442 started = &mut start => break started.context("join exec start")??,
443 () = too_large.cancelled() => bail!(MESSAGE_TOO_LARGE),
444 _ = starting.tick(), if stdin => client.stdin_taken.touch(),
445 }
446 };
447 // The client waited for the runtime, which is no
448 // idleness of its own, and the stdin gets its time
449 // from the start of the process.
450 activity.touch();
451 client.stdin_taken.touch();
452 let kill = started.teardown.kill.clone();
453 let exec = Self::exec_loop(started, &mut sender, client, &mut status);
454 let exec = Self::until_too_large(exec, &too_large);
455 Self::until_idle(exec, &activity, idle_timeout, || {
456 kill.store(true, Ordering::Release);
457 })
458 .await
459 }
460 .await,
461 Operation::Exec,
462 ),
463 Session::Attach(s) => (
464 Self::until_idle(
465 Self::until_too_large(
466 Self::attach_loop(s, &mut sender, client, &mut status),
467 &too_large,
468 ),
469 &activity,
470 idle_timeout,
471 || {},
472 )
473 .await,
474 Operation::Attach,
475 ),
476 };
477
478 // The close tells clients why, also ones which do not read the
479 // status. Their session failed, its end does not wait for the
480 // idle timeout, see [`Output::close_too_large`].
481 if too_large.is_cancelled() {
482 sender.close_too_large();
483 }
484 if let Some(status) = Self::final_status(result, status, operation)
485 && let Err(e) = sender.status(&status).await
486 {
487 debug!("Unable to send the status: {e:#}");
488 }
489
490 // Also for a message which came while the status went out.
491 if too_large.is_cancelled() {
492 sender.close_too_large();
493 }
494 sender.close().await.then_some(sender)
495 };
496 task::spawn(task.in_current_span())
497 }
498
499 /// The one status a client gets, out of how the loop of its session
500 /// ended and the status the loop decided on, none for a client which
501 /// went away.
502 fn final_status(
503 result: Result<()>,
504 status: Option<Status>,
505 operation: Operation,
506 ) -> Option<Status> {
507 match result {
508 Ok(()) => status,
509 // The process exited before, its status tells the client.
510 Err(e) if status.is_some() => {
511 debug!("Sending the exit status despite: {e:#}");
512 status
513 }
514 // Otherwise the client sees a clean close and assumes success.
515 // Mostly the client caused it: a command which the container
516 // does not have, a container which is gone or a connection
517 // which was idle. The span of the session names the container.
518 Err(e) => {
519 warn!("Unable to run {operation}: {e:#}");
520 Some(Status::failed(operation, format_args!("{e:#}")))
521 }
522 }
523 }
524
525 /// Run the loop of a session until the client sent a message larger
526 /// than the server takes, which fails it. The loop may wait for a client
527 /// which does not read its output then, it gets dropped, so that the
528 /// status and the close go out right away.
529 async fn until_too_large(
530 run: impl Future<Output = Result<()>>,
531 too_large: &CancellationToken,
532 ) -> Result<()> {
533 select! {
534 result = run => result,
535 () = too_large.cancelled() => bail!(MESSAGE_TOO_LARGE),
536 }
537 }
538
539 /// Run the loop of a session until the connection was idle for the
540 /// timeout, which fails it. `on_idle` runs before the loop gets dropped.
541 async fn until_idle(
542 run: impl Future<Output = Result<()>>,
543 activity: &Activity,
544 timeout: Option<Duration>,
545 on_idle: impl FnOnce(),
546 ) -> Result<()> {
547 if let Some(result) = activity.until_idle(timeout, run, on_idle).await {
548 result
549 } else {
550 debug!("Closing idle connection");
551 bail!(
552 "no activity on the connection for the idle timeout of {:?}",
553 timeout.unwrap_or_default()
554 )
555 }
556 }
557
558 /// Build a common read task. Every message of the client it reads counts
559 /// as activity, also stdin. Once stdin backs up behind a process which
560 /// does not take it, the client is not read anymore and the connection
561 /// may go idle, like a spdystream connection whose reader waits. Its
562 /// pings get no answer meanwhile either, a WebSocket client which fails
563 /// without pongs, like the one of client-go after about a minute, ends
564 /// the session once the process does not take stdin for that long. The
565 /// pings sit behind the stdin in the socket, reading them would mean
566 /// buffering the stdin without a bound.
567 fn read_task<I>(
568 mut input: I,
569 stdin_tx: MpscSender<Stdin>,
570 resize_tx: watch::Sender<Option<Bytes>>,
571 peer_closed: Arc<AtomicBool>,
572 too_large: CancellationToken,
573 stdin_taken: Activity,
574 ) -> JoinHandle<InputEnd<I>>
575 where
576 I: ClientInput,
577 {
578 let task = async move {
579 let mut pending = PendingStdin::default();
580 let end = loop {
581 select! {
582 permit = stdin_tx.reserve(), if !pending.is_empty() => match permit {
583 Ok(permit) => permit.send(pending.pop()),
584 // The session ended, nothing takes stdin anymore.
585 Err(_) => pending.clear(),
586 },
587 // Once stdin got closed, nothing gets added anymore, so
588 // keep reading for the end of the connection.
589 event = input.next(), if !pending.is_full() || pending.closed => match event {
590 Some(event) => {
591 if let ControlFlow::Break(end) =
592 Self::read_event(event, &mut pending, &resize_tx)
593 {
594 break Some(end);
595 }
596 }
597 None => break None,
598 },
599 }
600 };
601 match end {
602 Some(EventEnd::Closed) => peer_closed.store(true, Ordering::Release),
603 Some(EventEnd::TooLarge) => too_large.cancel(),
604 None => {}
605 }
606
607 // Stdin sent right before a clean close, like the close of the
608 // stream itself, still has to reach the container. Only while
609 // the container takes it, a container which does not read it
610 // must not delay the end of the session for long. A client which
611 // went away or failed the session gets nothing forwarded
612 // anymore. The client gets the answer to its close meanwhile,
613 // also when the session gets stopped after.
614 if end == Some(EventEnd::Closed) {
615 stdin_taken.touch();
616 let flush = async {
617 while !pending.is_empty() {
618 if stdin_tx.send(pending.pop()).await.is_err() {
619 break;
620 }
621 }
622 };
623 let flush =
624 stdin_taken.until_stalled(time::Instant::now(), STDIN_FLUSH_TIMEOUT, flush);
625 if tokio::join!(flush, input.answer_close()).0.is_none() {
626 debug!("Dropping stdin the container did not take after the close");
627 }
628 }
629 match end {
630 Some(EventEnd::Closed) => InputEnd::Closed,
631 Some(EventEnd::TooLarge) => InputEnd::TooLarge(input),
632 None => InputEnd::Gone,
633 }
634 };
635 task::spawn(task.in_current_span())
636 }
637
638 /// Handle a single event of the client, break once its input ended.
639 fn read_event(
640 event: ClientEvent,
641 pending: &mut PendingStdin,
642 resize_tx: &watch::Sender<Option<Bytes>>,
643 ) -> ControlFlow<EventEnd> {
644 match event {
645 ClientEvent::Stdin(stdin) => pending.push(stdin),
646 ClientEvent::Resize(data) => {
647 resize_tx.send_replace(Some(data));
648 }
649 ClientEvent::Closed => return ControlFlow::Break(EventEnd::Closed),
650 ClientEvent::TooLarge => return ControlFlow::Break(EventEnd::TooLarge),
651 }
652 ControlFlow::Continue(())
653 }
654}
655
656/// Forwards the stdin and the resize events of a client to the process, for
657/// the loops of exec and attach sessions, which forward the output
658/// meanwhile.
659pub(super) struct SessionInput {
660 io: SharedContainerIO,
661 attach: SharedContainerAttach,
662 client: ClientRx,
663
664 /// Stdin which waits for the container.
665 pending: Option<Bytes>,
666
667 /// Pings the client while stdin waits.
668 ping: time::Interval,
669
670 /// Whether stdin gets forwarded, until the client closed it.
671 pub(super) stdin_open: bool,
672}
673
674/// What the loop of a session has to handle.
675#[derive(Debug, PartialEq)]
676pub(super) enum InputEvent {
677 /// A resize event of the client, see [`SessionInput::resize`]. Resizing
678 /// does not depend on stdin, a terminal without it still has a size.
679 Resize(Bytes),
680
681 /// The stdin of the client is not read while it waits for the container,
682 /// so a client which goes away meanwhile is only noticed by a failing
683 /// send. The session pings it.
684 Ping,
685
686 /// The client closed its stdin.
687 StdinClosed,
688
689 /// The client closed the connection.
690 Ended,
691
692 /// The client sent a message larger than the server takes, which fails
693 /// the session.
694 TooLarge,
695}
696
697impl SessionInput {
698 /// Forward the input of `client` to the process of `io` and `attach`,
699 /// stdin only if `stdin` is set.
700 pub(super) fn new(
701 io: SharedContainerIO,
702 attach: SharedContainerAttach,
703 client: ClientRx,
704 stdin: bool,
705 ) -> Self {
706 let mut ping = time::interval_at(time::Instant::now() + PING_INTERVAL, PING_INTERVAL);
707 ping.set_missed_tick_behavior(time::MissedTickBehavior::Delay);
708 Self {
709 io,
710 attach,
711 client,
712 pending: None,
713 ping,
714 stdin_open: stdin,
715 }
716 }
717
718 /// Forward stdin until the session has something to handle. The session
719 /// may cancel it for its output, nothing gets lost then.
720 pub(super) async fn next(&mut self) -> InputEvent {
721 loop {
722 select! {
723 () = self.client.too_large.cancelled() => return InputEvent::TooLarge,
724 Ok(()) = self.client.resize.changed() => {
725 if let Some(data) = self.client.resize.borrow_and_update().clone() {
726 return InputEvent::Resize(data);
727 }
728 }
729 // Wait for the container to take the stdin without blocking
730 // the output meanwhile.
731 () = self.attach.send_stdin(self.pending.clone().unwrap_or_default()), if self.pending.is_some() => {
732 self.pending = None;
733 self.client.stdin_taken.touch();
734 }
735 _ = self.ping.tick(), if self.pending.is_some() => return InputEvent::Ping,
736 stdin = self.client.stdin.recv(), if self.pending.is_none() => match stdin {
737 // The input fails the session before it ends.
738 None if self.client.too_large.is_cancelled() => return InputEvent::TooLarge,
739 None => return InputEvent::Ended,
740 Some(_) if !self.stdin_open => {}
741 Some(Stdin::Data(data)) => self.pending = Some(data),
742 Some(Stdin::Close) => {
743 self.stdin_open = false;
744 return InputEvent::StdinClosed;
745 }
746 },
747 }
748 }
749 }
750
751 /// Close the stdin of the container after the stdin before. A terminal
752 /// cannot be closed without ending its output, it gets its end of file
753 /// character instead, like from a user pressing Ctrl-D. A process
754 /// reading lines sees the end of its input then, one after a line
755 /// without its ending needs a second one, like a user would type it.
756 pub(super) fn close_container_stdin(&mut self) {
757 // Right after `next` returned `StdinClosed`, which it only does while
758 // no stdin waits, so the end of file character replaces nothing.
759 debug_assert!(self.pending.is_none(), "stdin waits for the container");
760 match self.io.terminal_eof() {
761 Some(eof) => self.pending = Some(Bytes::from(vec![eof])),
762 None => {
763 self.attach.close_container_stdin();
764 }
765 }
766 }
767
768 /// Handle a resize event of the client, its JSON.
769 pub(super) fn resize(&self, data: &[u8]) {
770 Self::resize_with(data, |width, height| self.io.try_resize(width, height));
771 }
772
773 /// Handle a resize event of the client, its JSON, with `resize`, which
774 /// returns whether there is a terminal. Clients send them regardless of
775 /// a terminal, a broken one does not end the session.
776 pub(super) fn resize_with(data: &[u8], resize: impl FnOnce(u16, u16) -> Result<bool>) {
777 if let Err(e) = Self::try_resize(data, resize) {
778 debug!("Ignoring resize event: {e:#}");
779 }
780 }
781
782 fn try_resize(data: &[u8], resize: impl FnOnce(u16, u16) -> Result<bool>) -> Result<()> {
783 let e = ResizeEvent::parse(data)?;
784 trace!("Got resize message: {e:?}");
785 // Like CRI-O, a terminal never gets a size without rows or columns,
786 // which clients send before they know their own.
787 if e.width == 0 || e.height == 0 {
788 debug!("Ignoring resize event without a size: {e:?}");
789 return Ok(());
790 }
791 if !resize(e.width, e.height)? {
792 debug!("Ignoring resize event, container has no terminal");
793 }
794 Ok(())
795 }
796}
797
798/// The entry points of the fuzz targets.
799#[cfg(any(test, feature = "fuzzing"))]
800pub(super) mod fuzzing;
801
802#[cfg(test)]
803mod tests;