Skip to main content

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;