m³OS docs GitHub

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.

Complete

Learning goal

Understand the telnet protocol: IAC (Interpret As Command) sequences, option negotiation, and NVT (Network Virtual Terminal) mapping.

Category

infrastructure

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 SIGHUP to 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

kernel/src/arch/x86_64/syscall.rs L7580-L7603

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

kernel/src/net/tcp.rs L231-L231

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

userspace/telnetd/telnetd.c See file

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