Build a local Telnet lab
Start a loopback server
Create a tiny local TCP server that shows exactly which bytes a Telnet client sends without exposing a network service.
8 minute lesson
To study what a Telnet client actually sends, you need a server you fully control. Not a real telnetd, just something that accepts a TCP connection and shows you every byte. A few lines of Node.js are enough.
Create server.mjs:
import net from 'node:net'
const server = net.createServer(socket => {
socket.write('Welcome\r\n')
socket.on('data', data => {
console.log(data.toString('hex'))
socket.write(`Received ${data.length} bytes\r\n`)
})
})
server.listen(2323, '127.0.0.1', () => {
console.log('Listening on 127.0.0.1:2323')
})
The hexadecimal logging is the whole point. Printing incoming data as text would hide the bytes we care about most: line endings and Telnet protocol commands, which are not printable characters. In hex, nothing hides.
Run it in one terminal:
node server.mjs
You should see Listening on 127.0.0.1:2323. The process stays in the foreground, waiting. Leave it running; the next lesson connects to it.
Two deliberate choices in that listen call. Binding to 127.0.0.1 keeps the lab on your computer. The server is unreachable from the network, so you can experiment freely without exposing an unauthenticated service to your LAN. And port 2323 is an unprivileged alternative to the well-known Telnet port 23, which would require root to bind on Unix-like systems.
If the server exits immediately with EADDRINUSE, something already occupies port 2323, probably a previous run of the same script you forgot about. Find it with lsof -i :2323 and stop it.
One thing this server is not: a Telnet server. It never sends IAC negotiation and never parses commands. It accepts TCP bytes and echoes statistics back. That neutrality is useful, because whatever appears in the hex log came from the client, not from us.
Use netcat as a tiny local server in one terminal:
nc -l 127.0.0.1 2323
Connect from another terminal:
telnet 127.0.0.1 2323
Type on both sides. You are seeing a TCP byte stream, not a full remote-login service. Stop both programs, then repeat with tcpdump on the loopback interface. This separates the TCP connection from Telnet option negotiation.
Lesson completed