yamux/
lib.rs

1// Copyright (c) 2018-2019 Parity Technologies (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 or MIT license, at your option.
4//
5// A copy of the Apache License, Version 2.0 is included in the software as
6// LICENSE-APACHE and a copy of the MIT license is included in the software
7// as LICENSE-MIT. You may also obtain a copy of the Apache License, Version 2.0
8// at https://www.apache.org/licenses/LICENSE-2.0 and a copy of the MIT license
9// at https://opensource.org/licenses/MIT.
10
11//! This crate implements the [Yamux specification][1].
12//!
13//! It multiplexes independent I/O streams over reliable, ordered connections,
14//! such as TCP/IP.
15//!
16//! The two primary objects, clients of this crate interact with, are:
17//!
18//! - [`Connection`], which wraps the underlying I/O resource, e.g. a socket, and
19//!   provides methods for opening outbound or accepting inbound streams.
20//! - [`Stream`], which implements [`futures::io::AsyncRead`] and
21//!   [`futures::io::AsyncWrite`].
22//!
23//! [1]: https://github.com/hashicorp/yamux/blob/master/spec.md
24
25#![forbid(unsafe_code)]
26
27mod chunks;
28mod error;
29mod frame;
30
31pub(crate) mod connection;
32mod tagged_stream;
33
34pub use crate::connection::{Connection, Mode, Packet, Stream};
35pub use crate::error::ConnectionError;
36pub use crate::frame::{
37    header::{HeaderDecodeError, StreamId},
38    FrameDecodeError,
39};
40
41const KIB: usize = 1024;
42const MIB: usize = KIB * 1024;
43const GIB: usize = MIB * 1024;
44
45pub const DEFAULT_CREDIT: u32 = 256 * KIB as u32; // as per yamux specification
46
47pub type Result<T> = std::result::Result<T, ConnectionError>;
48
49/// The maximum number of streams we will open without an acknowledgement from the other peer.
50///
51/// This enables a very basic form of backpressure on the creation of streams.
52const MAX_ACK_BACKLOG: usize = 256;
53
54/// Default maximum number of bytes a Yamux data frame might carry as its
55/// payload when being send. Larger Payloads will be split.
56///
57/// The data frame payload size is not restricted by the yamux specification.
58/// Still, this implementation restricts the size to:
59///
60/// 1. Reduce delays sending time-sensitive frames, e.g. window updates.
61/// 2. Minimize head-of-line blocking across streams.
62/// 3. Enable better interleaving of send and receive operations, as each is
63///    carried out atomically instead of concurrently with its respective
64///    counterpart.
65///
66/// For details on why this concrete value was chosen, see
67/// https://github.com/paritytech/yamux/issues/100.
68const DEFAULT_SPLIT_SEND_SIZE: usize = 16 * KIB;
69
70/// Yamux configuration.
71///
72/// The default configuration values are as follows:
73///
74/// - max. for the total receive window size across all streams of a connection = 1 GiB
75/// - max. number of streams = 512
76/// - read after close = true
77/// - split send size = 16 KiB
78#[derive(Debug, Clone)]
79pub struct Config {
80    max_connection_receive_window: Option<usize>,
81    max_num_streams: usize,
82    read_after_close: bool,
83    split_send_size: usize,
84}
85
86impl Default for Config {
87    fn default() -> Self {
88        Config {
89            max_connection_receive_window: Some(GIB),
90            max_num_streams: 512,
91            read_after_close: true,
92            split_send_size: DEFAULT_SPLIT_SEND_SIZE,
93        }
94    }
95}
96
97impl Config {
98    /// Set the upper limit for the total receive window size across all streams of a connection.
99    ///
100    /// Must be `>= 256 KiB * max_num_streams` to allow each stream at least the Yamux default
101    /// window size.
102    ///
103    /// The window of a stream starts at 256 KiB and is increased (auto-tuned) based on the
104    /// connection's round-trip time and the stream's bandwidth (striving for the
105    /// bandwidth-delay-product).
106    ///
107    /// Set to `None` to disable limit, i.e. allow each stream to grow receive window based on
108    /// connection's round-trip time and stream's bandwidth without limit.
109    ///
110    /// ## DOS attack mitigation
111    ///
112    /// A remote node (attacker) might trick the local node (target) into allocating large stream
113    /// receive windows, trying to make the local node run out of memory.
114    ///
115    /// This attack is difficult, as the local node only increases the stream receive window up to
116    /// 2x the bandwidth-delay-product, where bandwidth is the amount of bytes read, not just
117    /// received. In other words, the attacker has to send (and have the local node read)
118    /// significant amount of bytes on a stream over a long period of time to increase the stream
119    /// receive window. E.g. on a 60ms 10Gbit/s connection the bandwidth-delay-product is ~75 MiB
120    /// and thus the local node will at most allocate ~150 MiB (2x bandwidth-delay-product) per
121    /// stream.
122    ///
123    /// Despite the difficulty of the attack one should choose a reasonable
124    /// `max_connection_receive_window` to protect against this attack, especially since an attacker
125    /// might use more than one stream per connection.
126    pub fn set_max_connection_receive_window(&mut self, n: Option<usize>) -> &mut Self {
127        self.max_connection_receive_window = n;
128
129        assert!(
130            self.max_connection_receive_window.unwrap_or(usize::MAX)
131                >= self.max_num_streams * DEFAULT_CREDIT as usize,
132            "`max_connection_receive_window` must be `>= 256 KiB * max_num_streams` to allow each
133            stream at least the Yamux default window size"
134        );
135
136        self
137    }
138
139    /// Set the max. number of streams per connection.
140    pub fn set_max_num_streams(&mut self, n: usize) -> &mut Self {
141        self.max_num_streams = n;
142
143        assert!(
144            self.max_connection_receive_window.unwrap_or(usize::MAX)
145                >= self.max_num_streams * DEFAULT_CREDIT as usize,
146            "`max_connection_receive_window` must be `>= 256 KiB * max_num_streams` to allow each
147            stream at least the Yamux default window size"
148        );
149
150        self
151    }
152
153    /// Allow or disallow streams to read from buffered data after
154    /// the connection has been closed.
155    pub fn set_read_after_close(&mut self, b: bool) -> &mut Self {
156        self.read_after_close = b;
157        self
158    }
159
160    /// Set the max. payload size used when sending data frames. Payloads larger
161    /// than the configured max. will be split.
162    pub fn set_split_send_size(&mut self, n: usize) -> &mut Self {
163        self.split_send_size = n;
164        self
165    }
166}
167
168// Check that we can safely cast a `usize` to a `u64`.
169static_assertions::const_assert! {
170    std::mem::size_of::<usize>() <= std::mem::size_of::<u64>()
171}
172
173// Check that we can safely cast a `u32` to a `usize`.
174static_assertions::const_assert! {
175    std::mem::size_of::<u32>() <= std::mem::size_of::<usize>()
176}
177
178#[cfg(test)]
179impl quickcheck::Arbitrary for Config {
180    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
181        use quickcheck::GenRange;
182
183        let max_num_streams = g.gen_range(0..u16::MAX as usize);
184
185        Config {
186            max_connection_receive_window: if bool::arbitrary(g) {
187                Some(g.gen_range((DEFAULT_CREDIT as usize * max_num_streams)..usize::MAX))
188            } else {
189                None
190            },
191            max_num_streams,
192            read_after_close: bool::arbitrary(g),
193            split_send_size: g.gen_range(DEFAULT_SPLIT_SEND_SIZE..usize::MAX),
194        }
195    }
196}