#telnet #tokio-codec #mud #tcp-stream #gmcp #mssp #mccp


Telnet protocol (RFC 854) implementation via a Tokio codec. Includes support for various MUD protocol extensions.

4 releases (breaking)

0.4.0 May 27, 2024
0.3.0 Apr 7, 2024
0.2.0 Mar 30, 2024
0.1.0 Feb 18, 2023

#311 in Network programming

Download history 9/week @ 2024-02-26 1/week @ 2024-03-11 197/week @ 2024-03-25 150/week @ 2024-04-01 66/week @ 2024-04-08 1/week @ 2024-04-15 14/week @ 2024-05-20 186/week @ 2024-05-27 12/week @ 2024-06-03 6/week @ 2024-06-10

218 downloads per month




nectar is a Tokio codec providing a partial Telnet protocol (RFC 854) implementation.

Supports primary negotiation options: DO, DONT, WILL, WONT. Supports subnegotiation (NAWS, custom byte sequences). Aims to implement some of the popular MUD protocol extensions.


Simply use cargo add nectar in your root project directory.

You must also be using tokio-utils with the codec feature enabled, as well as tokio.

See the Tokio docs if you wish to dive deeper into codecs. Essentially it is just a way to encode and decode the underlying TCP Stream in a structured manner - in this case, our structure being the Telnet protocol.


See the echo_server directory for a working example with commented code. If you have cloned the nectar repository, you can run the echo server with cargo and then connect with telnet localhost 5000.

Note: Make sure you check the dependencies in the example Cargo.toml file.

async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = SocketAddr::from(([127, 0, 0, 1], 5000));
    let listener = TcpListener::bind(addr).await?;

    println!("telnet server started on: {}", addr);

    // Standard Tokio setup - we accept new connections and pass the
    // stream off to our handler function where the real work happens.
    loop {
        while let Ok((stream, _)) = listener.accept().await {
            tokio::spawn(async move {
                if let Err(e) = handler(stream).await {
                    eprintln!("error: {}", e);

async fn handler(stream: TcpStream) -> Result<(), Box<dyn std::error::Error>> {
    // We construct a 'Frame', which is just a wrapper around the underlying
    // stream that is decoded by the `nectar::TelnetCodec`.
    let mut frame = Framed::new(stream, TelnetCodec::new(1024));

    // In a real application, you would want to handle Some(Err(_)) and None
    // variants, but for this example we'll be succinct for simplicities sake.
    while let Some(Ok(msg)) = frame.next().await {
        match msg {
            // We'll keep it simple and only match against the Message event.
            TelnetEvent::Message(string) => {
                // Let's echo back what we received.
            _ => break,

You can check out the Blossom source code for an example of nectar in a more complex, real-world scenario.


nectar source code is dual-licensed under either

at your option.


~54K SLoC