1use std::{
4 pin::Pin,
5 sync::{Arc, atomic::AtomicBool},
6 task::{Context, Poll},
7};
8
9use n0_error::{ensure, stack_error};
10use n0_future::{FutureExt, Sink, Stream, ready, time};
11use tokio::{
12 io::{AsyncRead, AsyncWrite},
13 sync::watch,
14};
15use tracing::{instrument, warn};
16
17use super::{ClientRateLimit, Metrics};
18use crate::{
19 ExportKeyingMaterial, KeyCache, MAX_PACKET_SIZE,
20 protos::{
21 relay::{ClientToRelayMsg, Error as ProtoError, RelayToClientMsg},
22 streams::{StreamError, WsBytesFramed},
23 },
24};
25
26#[derive(Debug)]
34pub struct RelayedStream<S> {
35 pub(crate) inner: S,
36 pub(crate) key_cache: KeyCache,
37}
38
39impl<S> RelayedStream<S> {
40 pub fn new(inner: S, key_cache: KeyCache) -> Self {
45 Self { inner, key_cache }
46 }
47}
48
49#[allow(dead_code)]
51pub(super) type ServerRelayedStream = RelayedStream<WsBytesFramed<RateLimited<MaybeTlsStream>>>;
52
53#[cfg(test)]
54impl ServerRelayedStream {
55 pub(crate) fn test(stream: tokio::io::DuplexStream) -> Self {
56 let stream = MaybeTlsStream::Test(stream);
57 let stream = RateLimited::unlimited(stream, Arc::new(Metrics::default()));
58 Self {
59 inner: WsBytesFramed {
60 io: tokio_websockets::ServerBuilder::new()
61 .limits(Self::limits())
62 .serve(stream),
63 },
64 key_cache: KeyCache::test(),
65 }
66 }
67
68 pub(crate) fn test_limited(
69 stream: tokio::io::DuplexStream,
70 max_burst_bytes: u32,
71 bytes_per_second: u32,
72 ) -> Result<(Self, watch::Receiver<u64>), InvalidBucketConfig> {
73 let stream = MaybeTlsStream::Test(stream);
74 let stream = RateLimited::new(
75 stream,
76 max_burst_bytes,
77 bytes_per_second,
78 Arc::new(Metrics::default()),
79 )?;
80 let limited_watcher = stream.limited_watcher();
81 let stream = Self {
82 inner: WsBytesFramed {
83 io: tokio_websockets::ServerBuilder::new()
84 .limits(Self::limits())
85 .serve(stream),
86 },
87 key_cache: KeyCache::test(),
88 };
89 Ok((stream, limited_watcher))
90 }
91
92 fn limits() -> tokio_websockets::Limits {
93 tokio_websockets::Limits::default()
94 .max_payload_len(Some(crate::protos::relay::MAX_FRAME_SIZE))
95 }
96}
97
98#[stack_error(derive, add_meta)]
100#[non_exhaustive]
101pub enum SendError {
102 #[error(transparent)]
104 StreamError {
105 #[error(from, std_err)]
106 source: StreamError,
108 },
109 #[error("Packet exceeds max packet size")]
111 ExceedsMaxPacketSize {
112 size: usize,
114 },
115 #[error("Attempted to send empty packet")]
117 EmptyPacket {},
118}
119
120impl<S> Sink<RelayToClientMsg> for RelayedStream<S>
121where
122 S: Sink<bytes::Bytes, Error = StreamError> + Unpin,
123{
124 type Error = SendError;
125
126 fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
127 Pin::new(&mut self.inner).poll_ready(cx).map_err(Into::into)
128 }
129
130 fn start_send(mut self: Pin<&mut Self>, item: RelayToClientMsg) -> Result<(), Self::Error> {
131 let size = item.encoded_len();
132 ensure!(
133 size <= MAX_PACKET_SIZE,
134 SendError::ExceedsMaxPacketSize { size }
135 );
136 if let RelayToClientMsg::Datagrams { datagrams, .. } = &item {
137 ensure!(!datagrams.contents.is_empty(), SendError::EmptyPacket);
138 }
139
140 Pin::new(&mut self.inner)
141 .start_send(item.to_bytes().freeze())
142 .map_err(Into::into)
143 }
144
145 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
146 Pin::new(&mut self.inner).poll_flush(cx).map_err(Into::into)
147 }
148
149 fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
150 Pin::new(&mut self.inner).poll_close(cx).map_err(Into::into)
151 }
152}
153
154#[stack_error(derive, add_meta, from_sources)]
156#[non_exhaustive]
157pub enum RecvError {
158 #[error(transparent)]
160 Proto {
161 source: ProtoError,
163 },
164 #[error(transparent)]
166 StreamError {
167 #[error(std_err)]
168 source: StreamError,
170 },
171}
172
173impl<S> Stream for RelayedStream<S>
174where
175 S: Stream<Item = Result<bytes::Bytes, StreamError>> + Unpin,
176{
177 type Item = Result<ClientToRelayMsg, RecvError>;
178
179 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
180 Poll::Ready(match ready!(Pin::new(&mut self.inner).poll_next(cx)) {
181 Some(Ok(msg)) => {
182 Some(ClientToRelayMsg::from_bytes(msg, &self.key_cache).map_err(Into::into))
183 }
184 Some(Err(e)) => Some(Err(e.into())),
185 None => None,
186 })
187 }
188}
189
190#[derive(Debug)]
194#[allow(clippy::large_enum_variant)]
195#[non_exhaustive]
196pub enum MaybeTlsStream {
197 Plain(tokio::net::TcpStream),
199 Tls(tokio_rustls::server::TlsStream<tokio::net::TcpStream>),
201 #[cfg(test)]
203 Test(tokio::io::DuplexStream),
204}
205
206impl MaybeTlsStream {
207 pub(super) fn disable_nagle(&self) {
214 let stream = match self {
215 #[cfg(test)]
216 Self::Test(_) => return,
217 Self::Plain(stream) => stream,
218 Self::Tls(tls_stream) => tls_stream.get_ref().0,
219 };
220
221 if stream.set_nodelay(true).is_err() {
222 use std::sync::atomic::Ordering::Relaxed;
223
224 static FAILED_NO_DELAY: AtomicBool = AtomicBool::new(false);
225 if !FAILED_NO_DELAY.swap(true, Relaxed) {
226 warn!(
227 "Failed to set TCP socket to NO_DELAY (turning off Nagle failed). This will impair relay performance."
228 );
229 }
230 }
231 }
232}
233
234impl ExportKeyingMaterial for MaybeTlsStream {
235 fn export_keying_material<T: AsMut<[u8]>>(
236 &self,
237 output: T,
238 label: &[u8],
239 context: Option<&[u8]>,
240 ) -> Option<T> {
241 let Self::Tls(tls) = self else {
242 return None;
243 };
244
245 tls.get_ref()
246 .1
247 .export_keying_material(output, label, context)
248 .ok()
249 }
250}
251
252impl AsyncRead for MaybeTlsStream {
253 fn poll_read(
254 mut self: Pin<&mut Self>,
255 cx: &mut Context<'_>,
256 buf: &mut tokio::io::ReadBuf<'_>,
257 ) -> Poll<std::io::Result<()>> {
258 match &mut *self {
259 MaybeTlsStream::Plain(s) => Pin::new(s).poll_read(cx, buf),
260 MaybeTlsStream::Tls(s) => Pin::new(s).poll_read(cx, buf),
261 #[cfg(test)]
262 MaybeTlsStream::Test(s) => Pin::new(s).poll_read(cx, buf),
263 }
264 }
265}
266
267impl AsyncWrite for MaybeTlsStream {
268 fn poll_flush(
269 mut self: Pin<&mut Self>,
270 cx: &mut Context<'_>,
271 ) -> Poll<std::result::Result<(), std::io::Error>> {
272 match &mut *self {
273 MaybeTlsStream::Plain(s) => Pin::new(s).poll_flush(cx),
274 MaybeTlsStream::Tls(s) => Pin::new(s).poll_flush(cx),
275 #[cfg(test)]
276 MaybeTlsStream::Test(s) => Pin::new(s).poll_flush(cx),
277 }
278 }
279
280 fn poll_shutdown(
281 mut self: Pin<&mut Self>,
282 cx: &mut Context<'_>,
283 ) -> Poll<std::result::Result<(), std::io::Error>> {
284 match &mut *self {
285 MaybeTlsStream::Plain(s) => Pin::new(s).poll_shutdown(cx),
286 MaybeTlsStream::Tls(s) => Pin::new(s).poll_shutdown(cx),
287 #[cfg(test)]
288 MaybeTlsStream::Test(s) => Pin::new(s).poll_shutdown(cx),
289 }
290 }
291
292 fn poll_write(
293 mut self: Pin<&mut Self>,
294 cx: &mut Context<'_>,
295 buf: &[u8],
296 ) -> Poll<std::result::Result<usize, std::io::Error>> {
297 match &mut *self {
298 MaybeTlsStream::Plain(s) => Pin::new(s).poll_write(cx, buf),
299 MaybeTlsStream::Tls(s) => Pin::new(s).poll_write(cx, buf),
300 #[cfg(test)]
301 MaybeTlsStream::Test(s) => Pin::new(s).poll_write(cx, buf),
302 }
303 }
304
305 fn poll_write_vectored(
306 mut self: Pin<&mut Self>,
307 cx: &mut Context<'_>,
308 bufs: &[std::io::IoSlice<'_>],
309 ) -> Poll<std::result::Result<usize, std::io::Error>> {
310 match &mut *self {
311 MaybeTlsStream::Plain(s) => Pin::new(s).poll_write_vectored(cx, bufs),
312 MaybeTlsStream::Tls(s) => Pin::new(s).poll_write_vectored(cx, bufs),
313 #[cfg(test)]
314 MaybeTlsStream::Test(s) => Pin::new(s).poll_write_vectored(cx, bufs),
315 }
316 }
317
318 fn is_write_vectored(&self) -> bool {
319 match self {
320 MaybeTlsStream::Plain(s) => s.is_write_vectored(),
321 MaybeTlsStream::Tls(s) => s.is_write_vectored(),
322 #[cfg(test)]
323 MaybeTlsStream::Test(s) => s.is_write_vectored(),
324 }
325 }
326}
327
328#[derive(Debug)]
335pub(crate) struct RateLimited<S> {
336 inner: S,
337 bucket: Option<Bucket>,
338 bucket_refilled: Option<Pin<Box<time::Sleep>>>,
339 limited_tx: watch::Sender<u64>,
344 rate_limit_watcher: Option<watch::Receiver<Option<ClientRateLimit>>>,
354 metrics: Arc<Metrics>,
355}
356
357#[derive(Debug)]
368pub struct Bucket {
369 fill: i64,
371 max: i64,
373 last_fill: time::Instant,
375 refill_period: time::Duration,
377 refill: i64,
379}
380
381#[allow(missing_docs)]
382#[stack_error(derive, add_meta)]
383pub struct InvalidBucketConfig {
384 max: i64,
385 bytes_per_second: i64,
386 refill_period: time::Duration,
387}
388
389impl Bucket {
390 pub fn new(
399 max: i64,
400 bytes_per_second: i64,
401 refill_period: time::Duration,
402 ) -> Result<Self, InvalidBucketConfig> {
403 let refill = bytes_per_second.saturating_mul(refill_period.as_millis() as i64) / 1000;
405 ensure!(
406 max > 0 && bytes_per_second > 0 && refill_period.as_millis() as u32 > 0 && refill > 0,
407 InvalidBucketConfig {
408 max,
409 bytes_per_second,
410 refill_period
411 }
412 );
413 Ok(Self {
414 fill: max,
415 max,
416 last_fill: time::Instant::now(),
417 refill_period,
418 refill,
419 })
420 }
421
422 fn from_config(cfg: Option<ClientRateLimit>) -> Result<Option<Self>, InvalidBucketConfig> {
423 match cfg {
424 Some(cfg) => {
425 let bytes_per_second = u32::from(cfg.bytes_per_second);
426 let max_burst_bytes = cfg.max_burst_bytes.map_or(bytes_per_second / 10, u32::from);
427 Ok(Some(Bucket::new(
428 max_burst_bytes as i64,
429 bytes_per_second as i64,
430 time::Duration::from_millis(100),
431 )?))
432 }
433 None => Ok(None),
434 }
435 }
436
437 fn update_state(&mut self) {
438 let now = time::Instant::now();
439 let refill_periods = now.saturating_duration_since(self.last_fill).as_millis() as u32
441 / self.refill_period.as_millis() as u32;
442 if refill_periods == 0 {
443 return;
445 }
446
447 self.fill = self
448 .fill
449 .saturating_add(refill_periods as i64 * self.refill);
450 self.fill = std::cmp::min(self.fill, self.max);
451 self.last_fill += self.refill_period * refill_periods;
452 }
453
454 pub fn consume(&mut self, bytes: usize) -> Result<(), time::Instant> {
463 let bytes = i64::try_from(bytes).unwrap_or(i64::MAX);
464 self.update_state();
465
466 self.fill = self.fill.saturating_sub(bytes);
467
468 if self.fill > 0 {
469 return Ok(());
470 }
471
472 let missing = self.fill.saturating_neg();
473
474 let periods_needed = (missing / self.refill) + 1;
475 let periods_needed = u32::try_from(periods_needed).unwrap_or(u32::MAX);
476
477 Err(self.last_fill + periods_needed * self.refill_period)
478 }
479}
480
481impl<S> RateLimited<S> {
482 pub(crate) fn from_watcher(
487 io: S,
488 mut rate_limit_watcher: watch::Receiver<Option<ClientRateLimit>>,
489 metrics: Arc<Metrics>,
490 ) -> Result<Self, InvalidBucketConfig> {
491 let initial_cfg = *rate_limit_watcher.borrow_and_update();
492 let bucket = Bucket::from_config(initial_cfg)?;
493 Ok(Self {
494 inner: io,
495 bucket,
496 bucket_refilled: None,
497 limited_tx: watch::Sender::new(0),
498 rate_limit_watcher: Some(rate_limit_watcher),
499 metrics,
500 })
501 }
502
503 #[cfg(test)]
504 pub(crate) fn new(
505 inner: S,
506 max_burst_bytes: u32,
507 bytes_per_second: u32,
508 metrics: Arc<Metrics>,
509 ) -> Result<Self, InvalidBucketConfig> {
510 Ok(Self {
511 inner,
512 bucket: Some(Bucket::new(
513 max_burst_bytes as i64,
514 bytes_per_second as i64,
515 time::Duration::from_millis(100),
516 )?),
517 bucket_refilled: None,
518 limited_tx: watch::Sender::new(0),
519 rate_limit_watcher: None,
520 metrics,
521 })
522 }
523
524 #[cfg(test)]
525 pub(crate) fn unlimited(inner: S, metrics: Arc<Metrics>) -> Self {
526 Self {
527 inner,
528 bucket: None,
529 bucket_refilled: None,
530 limited_tx: watch::Sender::new(0),
531 rate_limit_watcher: None,
532 metrics,
533 }
534 }
535
536 pub(crate) fn limited_watcher(&self) -> watch::Receiver<u64> {
538 self.limited_tx.subscribe()
539 }
540
541 fn record_rate_limited(&mut self, bytes: usize) {
543 self.metrics.bytes_rx_ratelimited_total.inc_by(bytes as u64);
545 self.limited_tx.send_modify(|count| {
546 if *count == 0 {
547 self.metrics.conns_rx_ratelimited_total.inc();
548 }
549 *count += 1;
550 });
551 }
552}
553
554impl<S: ExportKeyingMaterial> ExportKeyingMaterial for RateLimited<S> {
555 fn export_keying_material<T: AsMut<[u8]>>(
556 &self,
557 output: T,
558 label: &[u8],
559 context: Option<&[u8]>,
560 ) -> Option<T> {
561 self.inner.export_keying_material(output, label, context)
562 }
563}
564
565impl<S: AsyncRead + Unpin> AsyncRead for RateLimited<S> {
566 #[instrument(name = "rate_limited_poll_read", skip_all)]
567 fn poll_read(
568 mut self: Pin<&mut Self>,
569 cx: &mut std::task::Context<'_>,
570 buf: &mut tokio::io::ReadBuf<'_>,
571 ) -> Poll<std::io::Result<()>> {
572 let this = &mut *self;
573
574 if let Some(rate_limit_watcher) = this.rate_limit_watcher.as_mut()
578 && rate_limit_watcher.has_changed().unwrap_or(false)
579 {
580 let cfg = rate_limit_watcher.borrow_and_update();
581 match Bucket::from_config(*cfg) {
582 Ok(bucket) => {
583 this.bucket = bucket;
584 this.bucket_refilled = None;
587 }
588 Err(err) => warn!(%err, "ignoring invalid live rate-limit update"),
589 }
590 }
591
592 let Some(bucket) = &mut this.bucket else {
593 return Pin::new(&mut this.inner).poll_read(cx, buf);
595 };
596
597 if let Some(bucket_refilled) = &mut this.bucket_refilled {
599 ready!(bucket_refilled.poll(cx));
600 this.bucket_refilled = None;
601 }
602
603 let bytes_before = buf.remaining();
607 ready!(Pin::new(&mut this.inner).poll_read(cx, buf))?;
608 let bytes_read = bytes_before - buf.remaining();
609
610 if let Err(refill_time) = bucket.consume(bytes_read) {
612 this.record_rate_limited(bytes_read);
613 this.bucket_refilled = Some(Box::pin(time::sleep_until(refill_time)));
614 }
615
616 Poll::Ready(Ok(()))
617 }
618}
619
620impl<S: AsyncWrite + Unpin> AsyncWrite for RateLimited<S> {
621 fn poll_write(
622 mut self: Pin<&mut Self>,
623 cx: &mut std::task::Context<'_>,
624 buf: &[u8],
625 ) -> Poll<Result<usize, std::io::Error>> {
626 Pin::new(&mut self.inner).poll_write(cx, buf)
627 }
628
629 fn poll_flush(
630 mut self: Pin<&mut Self>,
631 cx: &mut std::task::Context<'_>,
632 ) -> Poll<Result<(), std::io::Error>> {
633 Pin::new(&mut self.inner).poll_flush(cx)
634 }
635
636 fn poll_shutdown(
637 mut self: Pin<&mut Self>,
638 cx: &mut std::task::Context<'_>,
639 ) -> Poll<Result<(), std::io::Error>> {
640 Pin::new(&mut self.inner).poll_shutdown(cx)
641 }
642}
643
644#[cfg(test)]
645mod tests {
646 use std::sync::Arc;
647
648 use n0_error::{Result, StdResultExt};
649 use n0_future::time;
650 use n0_tracing_test::traced_test;
651 use tokio::io::{AsyncReadExt, AsyncWriteExt};
652
653 use super::Bucket;
654 use crate::server::{Metrics, streams::RateLimited};
655
656 #[tokio::test(start_paused = true)]
657 #[traced_test]
658 async fn test_ratelimiter() -> Result {
659 let (read, mut write) = tokio::io::duplex(4096);
660
661 let send_total = 10 * 1024 * 1024; let send_data = vec![42u8; send_total];
663
664 let bytes_per_second = 12_345;
665
666 let mut rate_limited = RateLimited::new(
667 read,
668 bytes_per_second / 10,
669 bytes_per_second,
670 Arc::new(Metrics::default()),
671 )?;
672
673 let before = time::Instant::now();
674 n0_future::future::try_zip(
675 async {
676 let mut remaining = send_total;
677 let mut buf = [0u8; 4096];
678 while remaining > 0 {
679 remaining -= rate_limited.read(&mut buf).await?;
680 }
681 Ok(())
682 },
683 async {
684 write.write_all(&send_data).await?;
685 write.flush().await
686 },
687 )
688 .await
689 .anyerr()?;
690
691 let duration = time::Instant::now().duration_since(before);
692 assert_ne!(duration.as_millis(), 0);
693
694 let actual_bytes_per_second = send_total as f64 / duration.as_secs_f64();
695 println!("{actual_bytes_per_second}");
696 assert_eq!(actual_bytes_per_second.round() as u32, bytes_per_second);
697
698 Ok(())
699 }
700
701 #[tokio::test(start_paused = true)]
706 #[traced_test]
707 async fn test_ratelimiter_live_update() -> Result {
708 use crate::server::ClientRateLimit;
709
710 let (read, mut write) = tokio::io::duplex(4096);
711
712 let bytes_per_second = 12_345u32;
713
714 let unlimited_total = 1024 * 1024; let limited_total = 10 * 1024 * 1024; let send_data = vec![42u8; unlimited_total + limited_total];
719
720 let (rate_tx, rate_rx) = tokio::sync::watch::channel(None);
722 let mut rate_limited =
723 RateLimited::from_watcher(read, rate_rx, Arc::new(Metrics::default()))?;
724 let mut limit = ClientRateLimit::new(bytes_per_second.try_into().anyerr()?);
725 limit.max_burst_bytes = Some((bytes_per_second / 10).try_into().anyerr()?);
726
727 let mut unlimited_duration = time::Duration::ZERO;
728 let mut limited_duration = time::Duration::ZERO;
729 n0_future::future::try_zip(
730 async {
731 let mut buf = [0u8; 4096];
732
733 let before = time::Instant::now();
735 let mut remaining = unlimited_total;
736 while remaining > 0 {
737 let len = remaining.min(buf.len());
738 let n = rate_limited.read(&mut buf[..len]).await?;
739 remaining -= n;
740 }
741 unlimited_duration = time::Instant::now().duration_since(before);
742
743 rate_tx.send_replace(Some(limit));
745
746 let before = time::Instant::now();
748 let mut remaining = limited_total;
749 while remaining > 0 {
750 let len = remaining.min(buf.len());
751 let n = rate_limited.read(&mut buf[..len]).await?;
752 remaining -= n;
753 }
754 limited_duration = time::Instant::now().duration_since(before);
755 Ok(())
756 },
757 async {
758 write.write_all(&send_data).await?;
759 write.flush().await
760 },
761 )
762 .await
763 .anyerr()?;
764
765 assert_eq!(
767 unlimited_duration.as_millis(),
768 0,
769 "reading before the limit is installed runs at full speed",
770 );
771
772 let actual_bytes_per_second = limited_total as f64 / limited_duration.as_secs_f64();
773 assert_eq!(
774 actual_bytes_per_second.round() as u32,
775 bytes_per_second,
776 "the live-installed limit governs throughput once applied mid-connection",
777 );
778
779 Ok(())
780 }
781
782 #[tokio::test(start_paused = true)]
783 async fn test_bucket_high_refill() -> Result {
784 let bytes_per_second = i64::MAX;
785 let mut bucket = Bucket::new(i64::MAX, bytes_per_second, time::Duration::from_millis(100))?;
786 for _ in 0..100 {
787 time::sleep(time::Duration::from_millis(100)).await;
788 assert!(bucket.consume(1_000_000).is_ok());
789 }
790
791 Ok(())
792 }
793
794 #[tokio::test(start_paused = true)]
795 async fn smoke_test_bucket_high_consume() -> Result {
796 let bytes_per_second = 123_456;
797 let mut bucket = Bucket::new(
798 bytes_per_second / 10,
799 bytes_per_second,
800 time::Duration::from_millis(100),
801 )?;
802 for _ in 0..100 {
803 let Err(until) = bucket.consume(usize::MAX) else {
804 panic!("i64::MAX shouldn't be within limits");
805 };
806 time::sleep_until(until).await;
807 }
808
809 Ok(())
810 }
811}