conmonrs/streaming_server/spdy.rs
1//! A SPDY/3.1 server for the streaming protocols of Kubernetes. The kubelet
2//! proxies the upgrade of a client to the streaming server of a container
3//! runtime unchanged. kubectl still arrives with SPDY when it connected with a
4//! WebSocket, the API server translates its `v5.channel.k8s.io` and port
5//! forwarding WebSockets to SPDY.
6//!
7//! Only what moby/spdystream, the SPDY implementation of Kubernetes, uses is
8//! supported: the client opens all streams, the server answers pings. There
9//! is no flow control, spdystream ignores window updates, so the server
10//! bounds what it buffers itself and stops reading the connection when a
11//! stream does not take its data, or when the streams together hold
12//! [`DATA_BUFFER`] bytes. Streams have to take their data while they wait for
13//! something else, otherwise they hold up all others. A single stream can
14//! hold only a part of the budget, so that a slow stream alone does not stop
15//! the connection earlier than without it, but a few slow streams together
16//! do.
17
18mod frame;
19mod reader;
20mod stream;
21
22use crate::{
23 mutex::lock,
24 streaming_server::{
25 budget::{Budget, Held},
26 idle::{Activity, STALL_TIMEOUT},
27 },
28};
29use anyhow::{Context, Result, anyhow, bail};
30use bytes::{Bytes, BytesMut};
31pub(super) use frame::Headers;
32use frame::{Encoder, Frame, GOAWAY_OK, STATUS_CANCEL};
33use reader::Reader;
34use std::{
35 collections::HashMap,
36 io, mem,
37 sync::{
38 Arc, Mutex, MutexGuard,
39 atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering},
40 },
41};
42pub(super) use stream::{Stream, StreamReceiver, StreamSender};
43use tokio::{
44 io::{AsyncRead, AsyncWrite, AsyncWriteExt, WriteHalf},
45 select,
46 sync::{Notify, mpsc},
47 task,
48 time::{self, Duration, Instant},
49};
50use tokio_util::sync::CancellationToken;
51use tracing::{Instrument, debug, trace};
52
53/// How many control frames wait for the connection.
54const CONTROL_QUEUE: usize = 64;
55
56/// How many data frames of all streams wait for the connection.
57const DATA_QUEUE: usize = 16;
58
59/// How many data frames of a stream wait for the stream to take them, before
60/// the connection is not read anymore. The decoder hands on the data frames
61/// of the client in parts of at most 64 KiB, also larger ones, so a stream
62/// holds at most 1 MiB.
63const STREAM_QUEUE: usize = 16;
64
65/// How many bytes of the client all streams of a connection hold together,
66/// before the connection is not read anymore. Without it, a client could
67/// make the server hold [`STREAM_QUEUE`] frames for each of [`MAX_STREAMS`]
68/// streams which do not take their data, like the data streams of port
69/// forward requests waiting for their error streams. The part which the
70/// reader waits to place comes on top.
71const DATA_BUFFER: u32 = 4 * 1024 * 1024;
72
73/// How many accepted streams wait for [`Connection::accept`].
74const ACCEPT_QUEUE: usize = 16;
75
76/// The most streams a client may have open at once. A Kubernetes port forward
77/// uses two per forwarded connection. A stream counts until the server let
78/// go of it, also after the client reset it, so that a client which opens
79/// and resets streams cannot make the server hold more of them.
80pub(super) const MAX_STREAMS: usize = 1024;
81
82/// How many bytes the writer collects before writing them to the connection.
83const WRITE_BUFFER: usize = 64 * 1024;
84
85/// How long the client may take to close the connection once the server
86/// ended it, see [`Reader::drain`].
87const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
88
89/// How often the server pings the client. The kubelet sends no pings, but
90/// client-go pings the server as often, see `PingPeriod` of its SPDY
91/// transport. A client which went away shows up as a failed write, also
92/// while the reader waits for a stream which does not take its data and
93/// misses the end of the connection.
94pub(super) const PING_INTERVAL: Duration = Duration::from_secs(5);
95
96/// A connection a client upgraded to SPDY/3.1.
97pub(super) struct Connection {
98 streams: mpsc::Receiver<Stream>,
99 shared: Arc<Shared>,
100}
101
102impl Connection {
103 /// Serve SPDY on the connection. The client opens streams, `accept`
104 /// decides about each by its headers, rejected ones get reset. The
105 /// connection ends when the client closes it, violates the protocol, a
106 /// write fails, like of a ping, or when it was idle for the timeout.
107 /// Nothing waits for the echoes of the pings. Only the frames of the
108 /// client keep the connection active, except for these echoes, and the
109 /// data of the server, not its control frames, see [`Self::is_activity`].
110 pub(super) fn serve<T, F>(
111 io: T,
112 idle_timeout: Option<Duration>,
113 ping_interval: Option<Duration>,
114 accept: F,
115 ) -> Self
116 where
117 T: AsyncRead + AsyncWrite + Send + 'static,
118 F: Fn(&Headers) -> Result<()> + Send + 'static,
119 {
120 let encoder = Encoder::default();
121 let (reader, writer) = tokio::io::split(io);
122 let (control_tx, control_rx) = mpsc::channel(CONTROL_QUEUE);
123 let (data_tx, data_rx) = mpsc::channel(DATA_QUEUE);
124 let (streams_tx, streams_rx) = mpsc::channel(ACCEPT_QUEUE);
125
126 let shared = Arc::new(Shared {
127 control: control_tx,
128 data: data_tx,
129 streams: Mutex::default(),
130 budget: Budget::new(DATA_BUFFER),
131 resets: Mutex::default(),
132 resets_ready: Notify::new(),
133 closing: CancellationToken::new(),
134 done: CancellationToken::new(),
135 activity: Activity::new(),
136 written: Activity::new(),
137 last_stream_id: AtomicU32::new(0),
138 live_streams: AtomicUsize::new(0),
139 });
140 // In the span of the caller, like the one of a session, which names
141 // its container in what the tasks log, like a protocol error.
142 task::spawn(
143 Reader {
144 shared: shared.clone(),
145 reader,
146 accept,
147 streams: streams_tx,
148 }
149 .run()
150 .in_current_span(),
151 );
152 // On its own, the reader may wait for a stream which does not take
153 // its data. Ends the connection once it was idle for the timeout.
154 shared.activity.watch(idle_timeout, shared.done.clone(), {
155 let shared = shared.clone();
156 move || {
157 debug!("Closing idle SPDY connection");
158 shared.go_away(GOAWAY_OK);
159 }
160 });
161 if let Some(interval) = ping_interval {
162 task::spawn(Self::ping(shared.clone(), interval).in_current_span());
163 }
164 task::spawn(
165 Self::write(shared.clone(), writer, encoder, control_rx, data_rx).in_current_span(),
166 );
167
168 Self {
169 streams: streams_rx,
170 shared,
171 }
172 }
173
174 /// The next stream the client opened, `None` once the connection ended.
175 pub(super) async fn accept(&mut self) -> Option<Stream> {
176 self.streams.recv().await
177 }
178
179 /// When the connection was active the last time, for an idle timeout
180 /// which its user applies instead of [`Connection::serve`], see there.
181 pub(super) fn activity(&self) -> Activity {
182 self.shared.activity.clone()
183 }
184
185 /// A handle to close the connection, while the connection itself still
186 /// accepts streams.
187 pub(super) fn handle(&self) -> ConnectionHandle {
188 ConnectionHandle(self.shared.clone())
189 }
190
191 /// Ping the client until the connection ends. The pings of the server
192 /// have even IDs, the client echoes them.
193 async fn ping(shared: Arc<Shared>, interval: Duration) {
194 let mut interval = time::interval(interval);
195 interval.set_missed_tick_behavior(time::MissedTickBehavior::Delay);
196 interval.tick().await;
197 let mut id: u32 = 0;
198 loop {
199 select! {
200 _ = interval.tick() => {}
201 () = shared.done.cancelled() => return,
202 }
203 id = id.wrapping_add(2) & 0x7fff_ffff;
204 // A full queue goes out anyway, which tells as much.
205 let _ = shared.control.try_send(Frame::Ping { id });
206 }
207 }
208
209 /// Whether a frame of the client keeps the connection from being idle,
210 /// which its answers to the pings of the server do not. Of the frames of
211 /// the server only data counts, see [`Self::write`].
212 fn is_activity(frame: &Frame) -> bool {
213 !matches!(frame, Frame::Ping { id } if id % 2 == 0)
214 }
215
216 /// Write the queued frames: control frames first, then the resets of
217 /// streams without queued data, then data. The data frames of a stream
218 /// keep their order, its FIN goes through the same queue, and a reset of a
219 /// stream with queued data follows its last frame. A reset follows the
220 /// SYN_REPLY of its stream, which is queued before the stream exists.
221 async fn write<T>(
222 shared: Arc<Shared>,
223 mut writer: WriteHalf<T>,
224 mut encoder: Encoder,
225 mut control_rx: mpsc::Receiver<Frame>,
226 mut data_rx: mpsc::Receiver<QueuedData>,
227 ) where
228 T: AsyncWrite,
229 {
230 let mut buf = BytesMut::with_capacity(WRITE_BUFFER);
231 let mut closing = false;
232 let result = async {
233 loop {
234 // Only data keeps the connection active, not the control
235 // frames of the server, like resets of the streams of a
236 // client which keeps failing.
237 let mut active = false;
238 let data = select! {
239 biased;
240 () = shared.done.cancelled() => break,
241 Some(frame) = control_rx.recv() => {
242 encoder.encode(&frame, &mut buf)?;
243 None
244 }
245 () = shared.resets_ready.notified() => None,
246 Some(data) = data_rx.recv() => Some(data),
247 // The server closes the connection, the frames queued
248 // until now go out first.
249 () = shared.closing.cancelled(), if !closing => {
250 closing = true;
251 None
252 }
253 else => break,
254 };
255 // Whatever else is queued already goes out with it.
256 while let Ok(frame) = control_rx.try_recv() {
257 encoder.encode(&frame, &mut buf)?;
258 }
259 for stream_id in shared.take_resets() {
260 encoder.encode(&Self::reset_frame(stream_id), &mut buf)?;
261 }
262 if let Some(data) = data {
263 active = true;
264 Self::encode_data(&mut encoder, &data, &mut buf)?;
265 }
266 while buf.len() < WRITE_BUFFER {
267 let Ok(data) = data_rx.try_recv() else {
268 break;
269 };
270 active = true;
271 Self::encode_data(&mut encoder, &data, &mut buf)?;
272 }
273 if !buf.is_empty() {
274 select! {
275 biased;
276 result = Self::write_buf(&mut writer, &buf, &shared.written) => {
277 result.context("write frames")?;
278 }
279 // A client which does not read anymore must not keep
280 // the connection open.
281 () = shared.done.cancelled() => return Ok(()),
282 }
283 buf.clear();
284 if active {
285 shared.activity.touch();
286 }
287 }
288 // The rest goes out with the shutdown below.
289 if closing {
290 break;
291 }
292 }
293
294 // The reader queues a GOAWAY or resets before it ends the
295 // connection, the streams may have queued their last data. Data
296 // of existing streams may follow the GOAWAY.
297 while let Ok(frame) = control_rx.try_recv() {
298 encoder.encode(&frame, &mut buf)?;
299 }
300 for stream_id in shared.take_resets() {
301 encoder.encode(&Self::reset_frame(stream_id), &mut buf)?;
302 }
303 while let Ok(data) = data_rx.try_recv() {
304 Self::encode_data(&mut encoder, &data, &mut buf)?;
305 }
306 // A slow client still gets them, only one which does not take
307 // anything anymore gets cut off.
308 shared
309 .written
310 .until_stalled(Instant::now(), STALL_TIMEOUT, async {
311 Self::write_buf(&mut writer, &buf, &shared.written)
312 .await
313 .context("write frames")?;
314 writer.shutdown().await.context("shut down connection")
315 })
316 .await
317 .context("client did not take the last frames")?
318 }
319 .await;
320
321 if let Err(e) = result {
322 debug!("SPDY connection ended: {e:#}");
323 }
324 shared.done.cancel();
325 }
326
327 /// Write all of `buf`, `written` gets touched by every part which went
328 /// out, so that the end of the connection waits for a client which takes
329 /// the frames slowly, see [`ConnectionHandle::written`].
330 async fn write_buf<W>(writer: &mut W, buf: &[u8], written: &Activity) -> io::Result<()>
331 where
332 W: AsyncWrite + Unpin,
333 {
334 let mut rest = buf;
335 while !rest.is_empty() {
336 let n = writer.write(rest).await?;
337 if n == 0 {
338 return Err(io::ErrorKind::WriteZero.into());
339 }
340 written.touch();
341 rest = &rest[n..];
342 }
343 Ok(())
344 }
345
346 /// Encode a data frame, followed by the reset of its stream if it was the
347 /// last data the stream queued before its reset.
348 fn encode_data(encoder: &mut Encoder, data: &QueuedData, buf: &mut BytesMut) -> Result<()> {
349 encoder.encode(&data.frame, buf)?;
350 if data.queued.remove()
351 && let Frame::Data { stream_id, .. } = data.frame
352 {
353 encoder.encode(&Self::reset_frame(stream_id), buf)?;
354 }
355 Ok(())
356 }
357
358 fn reset_frame(stream_id: u32) -> Frame {
359 Frame::RstStream {
360 stream_id,
361 status: STATUS_CANCEL,
362 }
363 }
364}
365
366/// A data frame in the data queue, with the count of its stream.
367struct QueuedData {
368 frame: Frame,
369 queued: Arc<Queued>,
370}
371
372/// The data frames a stream has in the data queue, and whether it got reset
373/// after them. Both share one word, so that the writer and the reset agree
374/// on who sends the RST_STREAM.
375#[derive(Default)]
376struct Queued(AtomicUsize);
377
378impl Queued {
379 const RESET: usize = 1 << (usize::BITS - 1);
380
381 /// A frame goes into the data queue.
382 fn add(&self) {
383 self.0.fetch_add(1, Ordering::AcqRel);
384 }
385
386 /// A frame left the data queue or did not make it, whether the reset of
387 /// the stream is due now.
388 fn remove(&self) -> bool {
389 self.0.fetch_sub(1, Ordering::AcqRel) == Self::RESET | 1
390 }
391
392 /// Reset the stream after its queued frames, whether the reset is due
393 /// right away because there are none.
394 fn reset(&self) -> bool {
395 self.0.fetch_or(Self::RESET, Ordering::AcqRel) & !Self::RESET == 0
396 }
397
398 #[cfg(test)]
399 fn frames(&self) -> usize {
400 self.0.load(Ordering::Acquire) & !Self::RESET
401 }
402}
403
404/// Closes or ends a connection, see [`Connection::handle`]. Once the
405/// [`Connection`] is gone, the streams the client opens get refused, the ones
406/// nobody took yet get reset.
407#[derive(Clone)]
408pub(super) struct ConnectionHandle(Arc<Shared>);
409
410impl ConnectionHandle {
411 /// Close the connection once the frames queued so far went out.
412 pub(super) fn close(&self) {
413 self.0.closing.cancel();
414 }
415
416 /// End the connection right away.
417 pub(super) fn abort(&self) {
418 self.0.done.cancel();
419 }
420
421 /// Cancelled once the connection ended.
422 pub(super) fn done(&self) -> CancellationToken {
423 self.0.done.clone()
424 }
425
426 /// Touched whenever a part of the frames of the server went out, which
427 /// tells that the client still takes them, see
428 /// [`Activity::until_stalled`].
429 pub(super) fn written(&self) -> Activity {
430 self.0.written.clone()
431 }
432}
433
434/// What the reader, the writer and the streams of a connection share.
435struct Shared {
436 control: mpsc::Sender<Frame>,
437 data: mpsc::Sender<QueuedData>,
438 streams: Mutex<HashMap<u32, StreamState>>,
439
440 /// The bytes of the client all streams hold, see [`DATA_BUFFER`].
441 budget: Budget,
442
443 /// The streams the server reset without queued data, which the writer
444 /// sends next. Unlike the control queue it cannot be full, a stream gets
445 /// reset only once.
446 resets: Mutex<Vec<u32>>,
447 resets_ready: Notify,
448
449 /// Cancelled once the server closes the connection after the frames
450 /// queued until then.
451 closing: CancellationToken,
452
453 /// Cancelled once the connection ended.
454 done: CancellationToken,
455
456 /// When the last frame got read or written, for the idle timeout.
457 activity: Activity,
458
459 /// When a part of the frames of the server went out the last time, see
460 /// [`ConnectionHandle::written`].
461 written: Activity,
462
463 /// The last stream the client opened, for the GOAWAY.
464 last_stream_id: AtomicU32,
465
466 /// The streams the server holds, see [`Live`].
467 live_streams: AtomicUsize,
468}
469
470/// An open stream as the reader sees it.
471struct StreamState {
472 /// The data of the client, `None` once the client closed its side.
473 data: Option<mpsc::Sender<Held>>,
474
475 /// Cancelled when the stream got reset or the connection ended.
476 reset: CancellationToken,
477
478 /// Set once the client closed its side, shared with the receiver.
479 remote_closed: Arc<AtomicBool>,
480
481 /// Whether the server closed its side.
482 local_closed: bool,
483}
484
485impl Shared {
486 fn lock_streams(&self) -> MutexGuard<'_, HashMap<u32, StreamState>> {
487 lock(&self.streams)
488 }
489
490 /// Queue the reset of a stream for the writer.
491 fn queue_reset(&self, id: u32) {
492 lock(&self.resets).push(id);
493 self.resets_ready.notify_one();
494 }
495
496 fn take_resets(&self) -> Vec<u32> {
497 mem::take(&mut *lock(&self.resets))
498 }
499
500 /// The client closed its side of the stream.
501 fn remote_closed(&self, id: u32) {
502 let mut streams = self.lock_streams();
503 if let Some(state) = streams.get_mut(&id) {
504 // Before the data ends, which the receiver may see right away.
505 state.remote_closed.store(true, Ordering::Release);
506 state.data = None;
507 if state.local_closed {
508 streams.remove(&id);
509 }
510 }
511 }
512
513 /// The server closed its side of the stream.
514 fn local_closed(&self, id: u32) {
515 let mut streams = self.lock_streams();
516 if let Some(state) = streams.get_mut(&id) {
517 state.local_closed = true;
518 if state.data.is_none() {
519 streams.remove(&id);
520 }
521 }
522 }
523
524 /// Hand the data of the client to its stream. Waits while the stream
525 /// does not take it or the streams hold all of the budget, which stops
526 /// reading the connection.
527 async fn deliver(&self, stream_id: u32, data: Bytes) {
528 let Some((tx, reset)) = self
529 .lock_streams()
530 .get(&stream_id)
531 .and_then(|s| Some((s.data.clone()?, s.reset.clone())))
532 else {
533 trace!("Dropping data for closed SPDY stream {stream_id}");
534 return;
535 };
536 if data.is_empty() {
537 return;
538 }
539 select! {
540 // A stream which went away drops its data, which gives its share
541 // of the budget back.
542 result = async { tx.send(self.budget.hold(data).await).await } => {
543 // A stream which takes its data slowly keeps the connection
544 // active, only one which does not take it at all lets it
545 // go idle.
546 if result.is_ok() {
547 self.activity.touch();
548 }
549 }
550 // A stream which went away while waiting for the budget.
551 () = tx.closed() => {}
552 () = reset.cancelled() => {}
553 }
554 }
555
556 async fn send_control(&self, frame: Frame) -> Result<()> {
557 select! {
558 result = self.control.send(frame) => result.map_err(|_| anyhow!("connection closed")),
559 () = self.done.cancelled() => bail!("connection closed"),
560 }
561 }
562
563 /// End the connection with a GOAWAY, which the writer sends on its way
564 /// out unless the client does not read anymore.
565 fn go_away(&self, status: u32) {
566 let _ = self.control.try_send(Frame::GoAway {
567 last_stream_id: self.last_stream_id.load(Ordering::Relaxed),
568 status,
569 });
570 self.done.cancel();
571 }
572
573 /// Forget a stream which got reset by either side.
574 fn remove(&self, id: u32) {
575 // Not under the lock, which the reader and all streams take.
576 let state = self.lock_streams().remove(&id);
577 if let Some(state) = state {
578 state.reset.cancel();
579 }
580 }
581
582 /// Whether the client may open another stream, see [`MAX_STREAMS`].
583 fn has_room(&self) -> bool {
584 self.lock_streams().len() < MAX_STREAMS
585 && self.live_streams.load(Ordering::Acquire) < MAX_STREAMS
586 }
587}
588
589/// Counts a stream in [`Shared::live_streams`] until both of its halves got
590/// dropped, the stream may hold its headers and data until then.
591struct Live(Arc<Shared>);
592
593impl Live {
594 fn new(shared: &Arc<Shared>) -> Arc<Self> {
595 shared.live_streams.fetch_add(1, Ordering::AcqRel);
596 Arc::new(Self(shared.clone()))
597 }
598}
599
600impl Drop for Live {
601 fn drop(&mut self) {
602 self.0.live_streams.fetch_sub(1, Ordering::AcqRel);
603 }
604}
605
606/// The entry points of the fuzz targets.
607#[cfg(any(test, feature = "fuzzing"))]
608pub(super) mod fuzzing;
609
610/// A SPDY client for the tests of the protocols on top.
611#[cfg(test)]
612pub(super) mod testing;
613
614#[cfg(test)]
615mod tests;