rust-yamux
repository·master·Indexed 42 days ago
https://github.com/libp2p/rust-yamuxA Rust implementation of the Yamux stream multiplexing protocol, allowing multiple logical streams to run over a single reliable, ordered transport connection such as TCP. It provides a `Connection` abstraction for managing the connection lifecycle and a `Stream` abstraction implementing `AsyncRead` and `AsyncWrite` for data transfer.
What's inside rust-yamux
- Yamux is a stream multiplexer designed to run over reliable, ordered connections like TCP/IP. It allows multiple independent streams to be multiplexed over a single underlying connection. This implementation follows the official Yamux specification: https://github.com/hashicorp/yamux/blob/master/spec.md
How Yamux works: Connection and Stream abstractions
masterYamux multiplexes independent I/O streams over a single reliable, ordered connection (like TCP/IP). The library provides two primary abstractions:
Connection: Wraps the underlying I/O resource (e.g., a socket). It is used to manage the connection lifecycle and provides methods to either open new outbound streams or accept incoming inbound streams.Stream: Represents an individual multiplexed stream. It implementsfutures::io::AsyncReadandfutures::io::AsyncWrite, allowing you to treat a multiplexed stream like any other asynchronous I/O object.
Understand the Yamux Connection State Machine
masterA
Connectionmoves through several internal states that affect how it responds to polling:Active: The connection is healthy and actively multiplexing streams.Closing: A graceful shutdown has been requested; the connection is working on finishing pending tasks.Cleanup: An unrecoverable error occurred, and the connection is cleaning up resources.Closed: The connection is no longer active and cannot be reused.Poisoned: An internal error occurred during state transitions (should not be reachable by users).
Manage Yamux stream lifecycle and state
masterA Yamux
Streammoves through several states that determine its capabilities. You can check the current state usingis_write_closed(),is_closed(), andis_pending_ack().Stream States:
Open { acknowledged: bool }: The stream is active. For outbound streams,acknowledgedtracks if the remote has acknowledged our stream. For inbound streams, it tracks if we have acknowledged the stream to the remote.SendClosed: The stream is closed for outgoing messages but remains open for incoming messages.RecvClosed: The stream is closed for incoming messages but remains open for outgoing messages.Closed: The terminal state; no further reading or writing is possible.
Use
can_read()andcan_write()to check if operations are permitted in the current state.Configure read_after_close
masterTheset_read_after_closemethod determines whether streams are allowed to read from buffered data after the connection has been closed.Send messages on a single stream with `send_on_single_stream`
masterThe
send_on_single_streamfunction takes a singleyamux::Streamand an iterator ofMsgobjects, sending each message sequentially on that specific stream before closing it.send_on_single_stream(stream, messages).await?;Get the Stream identifier
masterTo identify a specific stream within a connection, use theid()method, which returns aStreamId.Read and write data using the Stream API
masterThe
Streamstruct provides standard asynchronous I/O interfaces for multiplexed data transfer. It can be used in two ways:- As
AsyncReadandAsyncWrite: Usepoll_readandpoll_writeto interact with the stream using a byte buffer. This is the standard way to handle raw byte streams. - As
futures::stream::Stream: Usepoll_nextto receive data asPacketobjects. EachPacketcontains aVec<u8>representing a chunk of data received from the remote.
Note that
Streamhandles flow control (window updates) automatically during these operations.- As
Configure max_connection_receive_window
masterThe
set_max_connection_receive_windowmethod sets the upper limit for the total receive window size across all streams in a connection.Constraints:
- The value must be
>= 256 KiB * max_num_streamsto ensure every stream can at least accommodate the Yamux default window size. - Setting it to
Nonedisables the limit, allowing streams to grow their receive windows based on bandwidth-delay-product without restriction.
Security Note: Setting a reasonable limit helps mitigate DOS attacks where an attacker attempts to exhaust memory by forcing the allocation of large stream receive windows.
- The value must be
Use `MessageSender` to stress test message exchange
masterThe
MessageSenderstruct is aFutureused to simulate a client sending a sequence of messages over multiple outbound streams. It allows for fine-grained control over how messages are sent and how tasks are managed.Key features:
with_message_multiplier(u64): Repeats each message a specified number of times.with_strategy(MessageSenderStrategy): Sets the sending behavior:MessageSenderStrategy::SendRecv: Sends a message and waits for a response (requires the server to respond).MessageSenderStrategy::Send: Only writes the message to the stream.
spawn_tasks: When initializing withnew, setting this totruewill spawn a new Tokio task for each stream being processed.
let sender = MessageSender::new(connection, messages, true) .with_message_multiplier(2) .with_strategy(MessageSenderStrategy::SendRecv); let streams_processed = sender.await?;Configure Yamux using the Config struct
masterYou can customize the behavior of a Yamux connection using the
Configstruct. The default configuration is:max_connection_receive_window:Some(1 GiB)max_num_streams:512read_after_close:truesplit_send_size:16 KiB
Use the builder-style methods on
Configto adjust these values before initializing your connection.let mut config = Config::default(); config .set_max_num_streams(100) .set_split_send_size(32 * 1024) .set_read_after_close(false);Implement an echo server with `echo_server`
masterThe
echo_serverfunction takes ayamux::Connectionand, for every incoming stream, echoes the received data back to the sender. It handles concurrent streams automatically.echo_server(connection).await?;