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}