Phase 30
Telnet Server
A telnet server runs inside the OS, allowing remote users to log in over the network and get an interactive shell session. This is the first demonstration of the OS as a networked, multi-user system — a machine you can connect to from another computer (or another terminal on the host) and do real work.
Learning goal
Understand the telnet protocol: IAC (Interpret As Command) sequences, option negotiation, and NVT (Network Virtual Terminal) mapping.
Category
infrastructure
Components touched
Builds on
Unlocked by this phase
Why this phase matters
A telnet server runs inside the OS, allowing remote users to log in over the network and get an interactive shell session. This is the first demonstration of the OS as a networked, multi-user system — a machine you can connect to from another computer (or another terminal on the host) and do real work.
How it works in m3OS
Phase 30 adds telnetd, a telnet server that listens on TCP port 23, accepts remote connections, allocates a PTY pair per session, and relays data between the TCP socket and the terminal. This is the first demonstration of m3OS as a networked, multi-user system — a machine you can log into from another computer.
How it builds on earlier phases
This phase builds on PTY Subsystem and turns that foundation into Telnet Server.
Implementation outline
- Write a minimal telnet server in C (cross-compiled with musl).
- Implement IAC parsing: strip telnet commands from the data stream.
- Implement option negotiation for ECHO, SGA, and NAWS.
- On accept: fork, allocate PTY, exec login on the slave side.
- Main loop:
poll()on both the socket fd and the PTY master fd, relay data. - Handle connection close: send
SIGHUPto the session, clean up. - Add telnetd to init’s startup sequence.
- Configure QEMU with port forwarding:
-nic user,hostfwd=tcp::2323-:23. - Test from host:
telnet localhost 2323.
Task progress snapshot
65 of 81 tracked tasks are complete.
Documentation targets
- Documentation deliverables have not been listed yet.
Deferred follow-on work
- Encryption (that’s SSH, Phase 43)
- inetd/xinetd super-server
- TCP wrappers or IP-based access control
- Telnet LINEMODE option
- Environment variable passing (ENVIRON option)
- Keep-alive / idle timeout
Source materials
Important code
Code spotlight cards
Code spotlight
poll() support for PTY master FDs
poll() support for PTY master FDs. Why it matters: telnetd's relay loop multiplexes both the TCP socket and PTY master FD using poll(); without PTY master support the relay would require busy-waiting. Focus on `sys_poll` and `poll` in `kernel/src/arch/x86_64/syscall.rs` while you trace the snippet.
/// poll(fds, nfds, timeout) — check fd readiness.
///
/// For pipe-read fds: report POLLIN if data available or writer closed (EOF).
/// For TTY fds: report POLLIN if stdin has data.
/// Other fds: report requested events (optimistic).
/// If no fds are ready and timeout != 0, yield once and re-check.
fn sys_poll(fds_ptr: u64, nfds: u64, timeout: u64) -> u64 {
const POLLIN: i16 = 0x001;
let nfds = nfds as usize;
if nfds > 256 {
return NEG_EINVAL;
}
let timeout_i = timeout as i64;
let pid = crate::process::CURRENT_PID.load(core::sync::atomic::Ordering::Relaxed);
let saved_user_rsp = unsafe { SYSCALL_USER_RSP };
loop {
let mut ready_count = 0u64;
for i in 0..nfds {
let base = match fds_ptr.checked_add((i * 8) as u64) {
Some(a) => a,
None => return NEG_EFAULT,
}; Code spotlight
Increase MAX_TCP_CONNECTIONS
Increase MAX_TCP_CONNECTIONS. Why it matters: 1 listening slot plus ≥4 client slots are needed for the multi-session acceptance criteria; the previous limit of 4 was insufficient. Focus on `MAX_TCP_CONNECTIONS` in `kernel/src/net/tcp.rs` while you trace the snippet.
const MAX_TCP_CONNECTIONS: usize = 8; Code spotlight
Create telnetd/ and xtask build step
Create telnetd/ and xtask build step. Why it matters: integrating telnetd into the existing musl build pipeline ensures it is automatically compiled and placed in the initrd alongside other C userspace binaries. Focus on `build_musl_bins` and `telnetd.c` in `userspace/telnetd/telnetd.c` while you trace the snippet.
Key files
- userspace/telnetd/telnetd.c — Telnet server implementation
- kernel/src/net/mod.rs — Socket reference counting
- kernel/src/net/tcp.rs — TCP connection limit increase
- kernel/src/arch/x86_64/syscall.rs — poll() for PTY master + listening sockets
- kernel/src/process/mod.rs — Socket refcount in fork/close/cloexec
- kernel/src/fs/ramdisk.rs — telnetd.elf in initrd
- userspace/init/src/main.rs — telnetd daemon spawn at boot
- xtask/src/main.rs — Build + QEMU port forwarding
How real OSes differ
- inetd-style super-server launching (one daemon for all network services)
- TCP wrappers for access control
- Kerberos authentication
- Environment variable passing
- Full telnet option negotiation (dozens of options)
- The protocol is dramatically simpler (no crypto).
- It demonstrates all the same OS concepts (sockets + PTY + login + fork).
- It provides a working remote access prototype that SSH will later replace.
- QEMU virtual networking (never leaves the host)
- Trusted local networks during development
- Learning the protocol concepts
Success criteria
- `telnetd` starts at boot and listens on port 23.
- Connecting via `telnet` from the host reaches a `login:` prompt.
- After login, the remote shell is fully functional (commands, pipes, editing).
- The text editor from Phase 26 works over the telnet connection.
- Multiple simultaneous telnet sessions work (at least 4).
- Closing the telnet client cleanly terminates the remote session.
- `who` or `w` (stretch) shows active telnet sessions.
Related phases
Keep exploring the roadmap
Phase 29
CompletePTY Subsystem
Implement real pseudo-terminal (PTY) pairs so that multiple independent terminal sessions can run simultaneously. A PTY master/slave pair connects a terminal emulator (or network server) to a shell process, enabling remote access (telnet, SSH) and local terminal multiplexing.
Phase 31
CompleteCompiler Bootstrap
Run a C compiler natively inside the OS. A C source file written at the shell prompt (using the editor from Phase 26) can be compiled and executed without leaving the OS. The ultimate target is self-hosting: the compiler compiles itself.