How Telnet works
Data and commands share one stream
Distinguish ordinary terminal data from Telnet control sequences when both travel through the same TCP byte stream.
Telnet puts terminal data and protocol commands in the same TCP stream. There is no separate control connection.
Compare this with FTP, which opens one connection for commands and another for file data. Telnet chose one stream for everything. So it needs a way to mark where data stops and a command begins.
The marker is the byte value 255. It means Interpret As Command, or IAC. The byte after it tells the receiver which Telnet command follows. Option negotiation commands add one more byte naming the option.
Here is what a receiver might pull out of the stream:
104 101 108 108 111 five data bytes: "hello"
255 253 1 IAC DO ECHO (a three-byte negotiation)
255 246 IAC AYT (a two-byte command)
119 111 114 108 100 five more data bytes: "world"
Every byte below 255 that is not part of a command sequence is plain terminal data. The receiver reads along, and the moment it hits 255 it switches to command parsing. Once the command is complete, it switches back.
Escaping byte 255
What if the terminal data genuinely contains byte 255? The sender writes it twice. The receiver reads 255 255 as one data byte instead of the start of a command:
sender wants to transmit: 200 255 13
sender actually writes: 200 255 255 13
This is the classic escaping trick. You see the same idea in \\ inside strings and %% in format strings. Any protocol that mixes data and control in one channel needs one, and this is Telnet’s.
TCP does not respect your boundaries
TCP promises you a stream of bytes. It does not promise anything about how those bytes are grouped when you read them. One read may contain half a command, several commands, or commands mixed with data. A read can even end exactly after an IAC byte, with the command code arriving in the next read.
A Telnet implementation must keep parsing until every complete sequence is available. In practice that means buffering: if the stream ends mid-sequence, hold the partial bytes and wait for more.
Be careful here. A naive parser that assumes “one read equals one message” works fine on a fast loopback connection. Then it breaks on a real network, where packets get split at arbitrary points. It will look like the protocol is randomly corrupting your session. It isn’t. Your parser is. That is exactly the kind of bug this lesson exists to spare you.
Lesson completed