Loading learning content...
When engineers at ARPANET nodes needed to access computers at distant sites in the early 1970s, they faced a fundamental challenge: how could diverse computer systems with different operating systems, character sets, and terminal conventions communicate transparently? The answer was Telnet—a protocol so elegantly designed that its core concepts remain active in networking decades later.
Telnet (short for TELetype NETwork, or TELecommunication NETwork) became the universal language of remote terminal access, providing the foundation upon which the Internet's culture of distributed computing was built. Understanding Telnet means understanding the original solutions to problems that every remote access protocol must solve.
By the end of this page, you will thoroughly understand Telnet's protocol architecture, message format, command structure, and option negotiation mechanism. You'll be able to trace a Telnet conversation at the byte level and understand how the protocol achieves its goal of universal remote terminal access.
Telnet is an application-layer protocol that provides bidirectional, byte-stream communication over TCP. It operates on port 23 by default and establishes virtual terminal connections between client and server systems. The protocol's design reflects a philosophy of simplicity, symmetry, and negotiated capability.
Core Design Principles:
1. Simplicity
Telnet uses a straightforward byte stream model. Everything transmitted is either regular data (the characters you type and see) or commands/options (protocol signaling). This simplicity made implementation practical on the limited computing resources of the 1970s and ensured widespread adoption.
2. Symmetry
Unlike many client-server protocols, Telnet treats both endpoints symmetrically. Either end can initiate option negotiations, send commands, or transmit data. This symmetry reflects that either endpoint might be the "user" in different scenarios (a terminal connecting to a host, or a host connecting to a printer).
3. Negotiated Capability
Rather than mandating specific features, Telnet provides a negotiation mechanism. Endpoints discuss which features they support and agree on common capabilities. This design allowed Telnet to work between vastly different systems while enabling enhanced features when both ends supported them.
4. Transparency with Escape
Telnet must transmit arbitrary data (what the user types) while maintaining the ability to send control information (commands). It achieves this through an escape mechanism: the byte 255 (0xFF), called IAC (Interpret As Command), signals that the following bytes are protocol commands, not user data.
| Attribute | Value | Notes |
|---|---|---|
| Protocol Layer | Application (Layer 7) | Runs atop TCP transport |
| Transport Protocol | TCP | Reliable, ordered byte stream |
| Default Port | 23 | Well-known port assignment |
| Connection Mode | Bidirectional, full-duplex | Both sides can send simultaneously |
| Data Encoding | NVT ASCII (7-bit) | Extended through negotiation |
| Escape Character | 255 (0xFF) = IAC | Signals command interpretation |
| Primary RFC | RFC 854 (1983) | Telnet Protocol Specification |
| Options RFC | RFC 855 (1983) | Telnet Option Specifications |
Telnet uses an 'in-band' signaling approach—commands and data share the same channel, distinguished by the IAC escape. This contrasts with protocols like FTP that use separate channels for control and data. The in-band approach simplifies the protocol but requires careful escape handling.
Telnet's message structure is remarkably simple yet flexible. All communication falls into two categories: data bytes and command sequences. Understanding this structure is fundamental to reading and analyzing Telnet traffic.
Data Bytes:
Most of what flows through a Telnet connection is ordinary data—characters typed by the user, output from the remote system. Data bytes are transmitted as-is, with values from 0 to 254 (0x00 to 0xFE) passing through unchanged.
The IAC Escape:
The byte 255 (0xFF), designated IAC (Interpret As Command), is special. When a receiver sees IAC, it interprets the following byte(s) as protocol commands rather than data. This creates a slight complication: what if the user wants to send the actual value 255?
The solution: to send literal 255, the sender transmits IAC IAC (255 255). The receiver sees IAC, reads the next byte, sees another IAC, and knows to interpret this as a single data byte 255.
Command Sequences:
Following IAC, various byte values indicate different operations:
| Code | Decimal | Name | Description |
|---|---|---|---|
| 0xFF | 255 | IAC | Interpret As Command (escape byte) |
| 0xFE | 254 | DONT | Request peer to disable an option |
| 0xFD | 253 | DO | Request peer to enable an option |
| 0xFC | 252 | WONT | Indicate refusal to enable an option |
| 0xFB | 251 | WILL | Indicate willingness to enable an option |
| 0xFA | 250 | SB | Subnegotiation Begin |
| 0xF9 | 249 | GA | Go Ahead (half-duplex signaling) |
| 0xF8 | 248 | EL | Erase Line |
| 0xF7 | 247 | EC | Erase Character |
| 0xF6 | 246 | AYT | Are You There (connection test) |
| 0xF5 | 245 | AO | Abort Output |
| 0xF4 | 244 | IP | Interrupt Process |
| 0xF3 | 243 | BRK | Break (NVT Break character) |
| 0xF2 | 242 | DMRK | Data Mark (for Synch) |
| 0xF1 | 241 | NOP | No Operation |
| 0xF0 | 240 | SE | Subnegotiation End |
Message Structure Examples:
Simple Data: H e l l o
48 65 6C 6C 6F
Option Request: IAC DO ECHO
FF FD 01
Option Accept: IAC WILL ECHO
FF FB 01
Literal 255: IAC IAC
FF FF
Subnegotiation: IAC SB TERMTYPE IS VT100 IAC SE
FF FA 18 00 V T 1 0 0 FF F0
The Synch Mechanism:
Telnet defines a mechanism called Synch for urgent signaling. When a user presses an interrupt key (like Ctrl+C) during heavy output, that signal needs to reach the remote system quickly. Synch uses TCP's urgent data mechanism: the sender transmits a Data Mark command and sets TCP's urgent pointer. The receiver, upon seeing the urgent notification, scans forward to locate the Data Mark, discarding intervening data.
When analyzing packet captures, every 0xFF byte in a Telnet stream requires attention. Check the following byte to determine whether it's a command (0xF0-0xFE), subnegotiation (0xFA), or an escaped data byte (0xFF). This pattern recognition becomes automatic with practice.
Telnet's option negotiation is a masterpiece of protocol design, enabling heterogeneous systems to discover common ground without prior coordination. The mechanism is symmetric, preventing deadlocks, and reflexive, ensuring eventual agreement.
The Four Commands:
Option negotiation uses four commands, each carrying an option code that identifies the capability under discussion:
The Negotiation Dance:
Negotiations follow predictable patterns:
Enabling an Option (initiator wants peer to enable):
DO optionWILL option (agrees) or WONT option (refuses)Announcing Capability (initiator wants to enable for itself):
WILL optionDO option (accepts) or DONT option (rejects)Disabling an Option:
DONT option or WONT optionWONT option or responds confirminglyRejection is Always Final:
A critical design principle: a WONT or DONT response must be honored. If you send DO ECHO and receive WONT ECHO, you cannot keep asking. This prevents negotiation loops.
Common Telnet Options:
| Code | Name | RFC | Purpose |
|---|---|---|---|
| 0 | Binary | 856 | Enable 8-bit data transmission |
| 1 | Echo | 857 | Control which side echoes characters |
| 3 | Suppress Go Ahead | 858 | Suppress half-duplex signaling |
| 5 | Status | 859 | Query current option state |
| 24 | Terminal Type | 1091 | Exchange terminal type information |
| 31 | Window Size (NAWS) | 1073 | Communicate terminal dimensions |
| 32 | Terminal Speed | 1079 | Exchange terminal speed information |
| 34 | Linemode | 1184 | Local line editing mode |
| 36 | Environment | 1408 | Exchange environment variables |
| 37 | Authentication | 2941 | Negotiate authentication methods |
| 38 | Encryption | 2946 | Negotiate encryption (rarely used) |
A typical Telnet session begins with a flurry of option negotiations as client and server discover common capabilities. This 'option soup' can confuse newcomers analyzing traffic. Remember: it's just both sides announcing what they can do and requesting what they want.
While the WILL/WONT/DO/DONT mechanism handles binary decisions (enable or not), many options require additional parameters. Subnegotiation provides a mechanism for exchanging arbitrary option-specific data after an option has been enabled.
Subnegotiation Format:
IAC SB <option> <data...> IAC SE
Where:
IAC SB (255 250) marks subnegotiation begin<option> is the option code being parameterized<data...> is option-specific payload (any bytes except unescaped IAC)IAC SE (255 240) marks subnegotiation endTerminal Type Subnegotiation Example:
The terminal type option (option 24) is a perfect example. After negotiating support:
DO TERMINAL-TYPEWILL TERMINAL-TYPEIAC SB TERMINAL-TYPE SEND IAC SE
IAC SB TERMINAL-TYPE IS VT100 IAC SE
Window Size (NAWS) Subnegotiation:
The Negotiate About Window Size option allows the client to inform the server when terminal dimensions change:
IAC SB NAWS <width-high> <width-low> <height-high> <height-low> IAC SE
For an 80×24 terminal:
IAC SB NAWS 00 50 00 18 IAC SE
(80 = 0x0050, 24 = 0x0018)
1234567891011121314151617181920212223
// Packet capture showing terminal type exchange// (Bytes shown in hexadecimal) // Client requests terminal type negotiationClient -> Server: FF FD 18 // IAC DO TERMINAL-TYPE // Server agrees to provide terminal typeServer -> Client: FF FB 18 // IAC WILL TERMINAL-TYPE // Client asks: "Send me your terminal type"Client -> Server: FF FA 18 01 FF F0 // FF FA = IAC SB (subneg begin) // 18 = TERMINAL-TYPE option // 01 = SEND command // FF F0 = IAC SE (subneg end) // Server responds: "My terminal type is XTERM-256COLOR"Server -> Client: FF FA 18 00 58 54 45 52 4D 2D 32 35 36 43 4F 4C 4F 52 FF F0 // FF FA = IAC SB // 18 = TERMINAL-TYPE option // 00 = IS command // 58... = "XTERM-256COLOR" in ASCII // FF F0 = IAC SEEnvironment Variable Subnegotiation:
The environment option allows clients to pass variables like USER, DISPLAY, or custom settings:
IAC SB ENVIRONMENT IS VAR <name> VALUE <value> ... IAC SE
This capability became important for maintaining consistent behavior across diverse client environments.
If subnegotiation data contains the byte 255 (0xFF), it must be escaped as IAC IAC (255 255). The parser knows that IAC within subnegotiation is either escaped (followed by IAC) or marking the end (followed by SE).
Beyond option negotiation, Telnet defines control functions for managing the remote session. These commands handle situations where normal data flow is insufficient—interrupting runaway processes, testing connections, or signaling urgency.
Interrupt Process (IP):
Perhaps the most important control function, IP (code 244) signals the remote system to interrupt the currently running process. When you press Ctrl+C during a Telnet session, your client transmits IP to the server, which translates it to a SIGINT signal (or equivalent) on the remote system.
Critically, IP is often sent with the Synch mechanism to ensure it reaches the remote system even if output buffers are full.
Abort Output (AO):
AO (code 245) tells the remote system to stop sending output from the current process. Unlike IP, AO doesn't terminate the process—it just suppresses output. This is useful when you've accidentally triggered enormous output (like cat /dev/zero) and want to stop the flood without killing the process.
Are You There (AYT):
AYT (code 246) is a connection liveness test. When sent, the server should respond with a visible message confirming it's still responsive. This is useful when a connection seems hung—is the remote system alive, or has the network failed?
Erase Character (EC) and Erase Line (EL):
These functions (codes 247 and 248) provide line-editing capabilities when the remote system handles echoing. EC erases the most recently sent character; EL erases the entire current line. Their necessity decreased as local line editing became more common.
Break (BRK):
BRK (code 243) sends the NVT "Break" signal, historically used for attention signaling on terminals. Its meaning varies by system—on some, it's similar to IP; on others, it has specific functions.
| Function | User Action | Protocol Sequence | Server Response |
|---|---|---|---|
| Interrupt | Ctrl+C | IAC IP (255 244) | SIGINT to foreground process |
| Abort Output | Ctrl+O (typically) | IAC AO (255 245) | Flush output buffers |
| Are You There | Special key/command | IAC AYT (255 246) | Print confirmation message |
| Erase Character | Backspace (when remote echo) | IAC EC (255 247) | Delete last character |
| Erase Line | Ctrl+U (when remote echo) | IAC EL (255 248) | Delete current line |
| Break | Break key | IAC BRK (255 243) | System-dependent |
The Go Ahead (GA) Signal:
GA (code 249) is an artifact of early half-duplex terminals that could only send or receive at any moment. GA signals "I'm done sending, your turn." Modern terminals are full-duplex, so GA is almost always suppressed via the Suppress Go Ahead option (option 3). Virtually all modern Telnet implementations negotiate this option immediately.
When troubleshooting remote sessions, AYT is invaluable. If you suspect a connection is frozen, sending AYT should produce an immediate response. No response indicates a true network or server problem. Many Telnet clients allow sending AYT via a special key sequence or escape command.
Implementations of Telnet must maintain a state machine to correctly parse the byte stream. The parser operates in different modes depending on what it has recently seen, transitioning between states as special bytes are encountered.
Parser States:
DATA State: The normal state. Bytes are treated as data unless they equal IAC (255), which triggers a transition to IAC state. All bytes 0-254 are passed as data.
IAC State: Entered upon seeing IAC. The next byte determines the action:
OPTION State: Expecting an option code after WILL/WONT/DO/DONT. Read one byte as the option code, process the negotiation, return to DATA.
SUBNEG State: Collecting subnegotiation data. Accumulate bytes until IAC is seen:
1234567891011121314151617181920212223242526272829303132333435363738394041424344
function parseTelnetStream(stream): state = DATA subnegBuffer = [] for each byte in stream: switch state: case DATA: if byte == 255: // IAC state = IAC else: outputData(byte) case IAC: if byte == 255: // Escaped IAC outputData(255) state = DATA else if byte == 250: // SB subnegBuffer = [] state = SUBNEG else if byte in [251, 252, 253, 254]: // WILL/WONT/DO/DONT commandType = byte state = OPTION else if byte >= 240 and byte <= 249: // Commands executeCommand(byte) state = DATA case OPTION: optionCode = byte processNegotiation(commandType, optionCode) state = DATA case SUBNEG: if byte == 255: state = SUBNEG_IAC else: subnegBuffer.append(byte) case SUBNEG_IAC: if byte == 255: // Escaped IAC in subneg subnegBuffer.append(255) state = SUBNEG else if byte == 240: // SE processSubnegotiation(subnegBuffer) state = DATARobust Telnet implementations must handle malformed input gracefully. What if IAC is followed by an unknown byte? What if SE appears without a preceding SB? Production parsers need explicit handling for unexpected transitions to avoid crashes or security vulnerabilities.
Let's trace a complete Telnet session from connection through authentication and a simple command execution. This demonstrates how all the pieces—negotiation, data transfer, and control—work together.
Scenario: A client connects to a server, negotiates options, logs in, executes whoami, and logs out.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
// Phase 1: ConnectionClient: TCP SYN to server:23Server: TCP SYN-ACKClient: TCP ACK// TCP connection established // Phase 2: Option Negotiation (typical exchange)Server: FF FD 18 // IAC DO TERMINAL-TYPE (request term type)Server: FF FD 20 // IAC DO TERMINAL-SPEEDServer: FF FD 23 // IAC DO X-DISPLAY-LOCATIONServer: FF FD 27 // IAC DO NEW-ENVIRONServer: FF FB 03 // IAC WILL SUPPRESS-GO-AHEADServer: FF FD 01 // IAC DO ECHO (client should not echo)Server: FF FB 01 // IAC WILL ECHO (server will echo)Server: FF FB 05 // IAC WILL STATUS Client: FF FB 18 // IAC WILL TERMINAL-TYPEClient: FF FC 20 // IAC WONT TERMINAL-SPEEDClient: FF FC 23 // IAC WONT X-DISPLAY-LOCATIONClient: FF FC 27 // IAC WONT NEW-ENVIRONClient: FF FD 03 // IAC DO SUPPRESS-GO-AHEADClient: FF FC 01 // IAC WONT ECHO (client won't echo)Client: FF FD 01 // IAC DO ECHO (server should echo)Client: FF FD 05 // IAC DO STATUS // Terminal Type SubnegotiationServer: FF FA 18 01 FF F0 // IAC SB TERMINAL-TYPE SEND IAC SEClient: FF FA 18 00 78 74 65 72 6D FF F0 // IAC SB TERMINAL-TYPE IS xterm IAC SE // Phase 3: Authentication (plaintext!)Server: "login: "Client: "alice\r\n" // Username sent in clear textServer: "Password: "Client: "secret123\r\n" // PASSWORD IN CLEAR TEXT!Server: "Last login: Mon Jan 13 10:30:22\r\n"Server: "alice@server:~$ " // Phase 4: Command ExecutionClient: "whoami\r\n"Server: "alice\r\n"Server: "alice@server:~$ " // Phase 5: LogoutClient: "exit\r\n"Server: "logout\r\n"Server: TCP FINClient: TCP FIN-ACK// Connection closedNotice the password ("secret123") transmitted in plain text at line 34. Anyone monitoring the network—via packet capture, man-in-the-middle attack, or compromised router—can trivially read these credentials. This fundamental flaw is Telnet's fatal security weakness and the primary reason SSH replaced it.
We've thoroughly examined the Telnet protocol—from its design philosophy through message format, option negotiation, subnegotiation, control functions, and state machine implementation. Let's consolidate this understanding:
What's Next:
Having understood the Telnet protocol's mechanics, we'll next explore the Network Virtual Terminal (NVT) concept—Telnet's elegant solution to the heterogeneity problem that allowed terminals with vastly different capabilities to communicate through a standardized abstraction.
You now possess deep understanding of Telnet's protocol architecture, message format, negotiation mechanism, and operational characteristics. This knowledge forms the foundation for understanding both Telnet's historical significance and SSH's design improvements.