Python’s telnetlib module implements the Telnet protocol client, primarily used for automating legacy network device CLI access, serial console servers, and expect-style automation. import telnetlib. Connect: t = telnetlib.Telnet(host, port=23, timeout=10) or use as context manager. Read: t.read_until(b"expected", timeout) → bytes (returns everything up to and including the match, or everything received if timeout); t.read_all() → bytes until EOF; t.read_some() → at least 1 byte; t.read_very_eager() → everything available. Write: t.write(b"command\n") — automatically escapes IAC bytes. Expect: t.expect([re_pat1, re_pat2], timeout) → (match_index, match_obj, text_before). Option negotiation: t.set_option_negotiation_callback(fn) — fn(sock, cmd, option) called for each IAC negotiation. Interactive: t.interact() — passes stdin/stdout. Telnet control: telnetlib.IAC, WILL, WONT, DO, DONT, SB, SE, NOP — byte constants for IAC sequences. Encoding: Telnet is byte-oriented; decode received bytes with .decode("ascii", errors="replace"). Close: t.close(). Note: deprecated 3.11, removed 3.13 — include compatibility guard; use asyncio + raw TCP or paramiko (SSH) for new code. Claude Code generates automated login scripts, CLI scrapers, expect-style automation tools, and legacy device configuration managers.
CLAUDE.md for telnetlib
## telnetlib Stack
- Stdlib: import telnetlib (deprecated 3.11, removed 3.13 — guard with try/except)
- Connect: t = telnetlib.Telnet(host, port=23, timeout=10)
- Read: t.read_until(b"login: ", timeout=5) # blocks until match or timeout
- t.read_very_eager() # non-blocking, whatever's buffered
- Write: t.write(b"admin\r\n") # \r\n for Telnet line endings
- Expect: idx, match, text = t.expect([rb"\\$", rb"#"], timeout=10)
- Close: t.close() (or use as context manager)
telnetlib Telnet Automation Pipeline
# app/telnetlibutil.py — connect, login, expect, scrape, option negotiate
from __future__ import annotations
import io
import re
import socket
import time
from dataclasses import dataclass, field
from typing import Callable
# Guard for Python 3.13+ where telnetlib is removed
try:
import telnetlib as _telnetlib
_TELNETLIB_AVAILABLE = True
except ImportError:
_TELNETLIB_AVAILABLE = False
# ─────────────────────────────────────────────────────────────────────────────
# 1. Connection and basic I/O
# ─────────────────────────────────────────────────────────────────────────────
def connect(
host: str,
port: int = 23,
timeout: int = 10,
):
"""
Open a Telnet connection. Returns a Telnet object (use as context manager).
Example:
with connect("192.168.1.1") as t:
data = t.read_until(b"login: ", timeout=5)
"""
if not _TELNETLIB_AVAILABLE:
raise ImportError("telnetlib not available (Python 3.13+)")
return _telnetlib.Telnet(host, port, timeout)
def read_until_decoded(
t,
expected: str | bytes,
timeout: float = 10.0,
encoding: str = "ascii",
) -> str:
"""
Call read_until and decode the received bytes.
Example:
prompt = read_until_decoded(t, "login:", timeout=5)
"""
if isinstance(expected, str):
expected = expected.encode(encoding)
raw = t.read_until(expected, timeout)
return raw.decode(encoding, errors="replace")
def writeline(t, text: str, encoding: str = "ascii") -> None:
"""
Write a line followed by CR+LF (Telnet standard line ending).
Automatically escapes IAC bytes.
Example:
writeline(t, "admin")
"""
t.write(text.encode(encoding) + b"\r\n")
def read_flush(t, pause: float = 0.1) -> str:
"""
Wait briefly then read all buffered data. Returns decoded ASCII string.
Example:
writeline(t, "show version")
output = read_flush(t, pause=0.5)
"""
time.sleep(pause)
raw = t.read_very_eager()
return raw.decode("ascii", errors="replace")
# ─────────────────────────────────────────────────────────────────────────────
# 2. Expect-style automation
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class ExpectResult:
matched_index: int # which pattern matched (-1 if timeout)
matched_text: str # text before and including the match
match_obj: re.Match | None # regex match object
@property
def timed_out(self) -> bool:
return self.matched_index == -1
def __str__(self) -> str:
if self.timed_out:
return f"ExpectResult(TIMEOUT, got={self.matched_text[:60]!r})"
return (f"ExpectResult(match={self.matched_index}, "
f"text={self.matched_text[:60]!r})")
def expect(
t,
patterns: list[str | bytes],
timeout: float = 10.0,
encoding: str = "ascii",
) -> ExpectResult:
"""
Wait for one of several regex patterns to appear in the input.
Returns an ExpectResult.
Example:
result = expect(t, [r"\\$", r"#", r"login failed"], timeout=10)
if result.matched_index == 0:
print("shell prompt")
elif result.timed_out:
print("no prompt received")
"""
compiled = []
for p in patterns:
if isinstance(p, str):
compiled.append(re.compile(p.encode(encoding)))
else:
compiled.append(re.compile(p))
idx, m, text = t.expect(compiled, timeout)
return ExpectResult(
matched_index=idx,
matched_text=text.decode(encoding, errors="replace"),
match_obj=m,
)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Login helper
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class LoginConfig:
username: str
password: str
login_prompt: str = "login:"
password_prompt: str = "Password:"
shell_prompt: str = r"[\\$#>%] ?$" # regex
encoding: str = "ascii"
timeout: float = 10.0
def telnet_login(t, cfg: LoginConfig) -> bool:
"""
Perform a standard Telnet login sequence.
Returns True on success (shell prompt seen), False on failure.
Example:
cfg = LoginConfig(username="admin", password="secret")
if telnet_login(t, cfg):
writeline(t, "show interfaces")
else:
print("Login failed")
"""
# Wait for login prompt
r = expect(t, [cfg.login_prompt, "refused", "error"],
timeout=cfg.timeout, encoding=cfg.encoding)
if r.timed_out or r.matched_index != 0:
return False
# Send username
writeline(t, cfg.username, encoding=cfg.encoding)
# Wait for password prompt
r = expect(t, [cfg.password_prompt, "incorrect", "denied"],
timeout=cfg.timeout, encoding=cfg.encoding)
if r.timed_out or r.matched_index != 0:
return False
# Send password
writeline(t, cfg.password, encoding=cfg.encoding)
# Wait for shell prompt
r = expect(t, [cfg.shell_prompt, "incorrect", "denied", "failed"],
timeout=cfg.timeout, encoding=cfg.encoding)
return not r.timed_out and r.matched_index == 0
# ─────────────────────────────────────────────────────────────────────────────
# 4. Command scraper
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class CommandResult:
command: str
output: str
elapsed: float
def __str__(self) -> str:
preview = self.output.strip()[:80].replace("\n", "↵")
return f"[{self.elapsed:.2f}s] {self.command!r}: {preview!r}"
def run_command(
t,
command: str,
prompt_pattern: str = r"[\\$#>%] ?$",
timeout: float = 15.0,
encoding: str = "ascii",
) -> CommandResult:
"""
Send a command and collect output until the prompt reappears.
Example:
result = run_command(t, "show version")
print(result.output)
"""
t_start = time.monotonic()
writeline(t, command, encoding=encoding)
r = expect(t, [prompt_pattern], timeout=timeout, encoding=encoding)
elapsed = time.monotonic() - t_start
return CommandResult(
command=command,
output=r.matched_text,
elapsed=elapsed,
)
def run_commands(
t,
commands: list[str],
prompt_pattern: str = r"[\\$#>%] ?$",
timeout: float = 15.0,
) -> list[CommandResult]:
"""
Run a sequence of commands, collecting output after each.
Example:
results = run_commands(t, ["hostname", "uptime", "df -h"])
for r in results:
print(r)
"""
return [
run_command(t, cmd, prompt_pattern=prompt_pattern, timeout=timeout)
for cmd in commands
]
# ─────────────────────────────────────────────────────────────────────────────
# 5. IAC option negotiation helper
# ─────────────────────────────────────────────────────────────────────────────
def refuse_all_options(sock, cmd: bytes, option: bytes) -> None:
"""
Option negotiation callback that refuses all Telnet option offers.
Pass as: t.set_option_negotiation_callback(refuse_all_options)
Example:
t = telnetlib.Telnet(host)
t.set_option_negotiation_callback(refuse_all_options)
"""
if not _TELNETLIB_AVAILABLE:
return
IAC = _telnetlib.IAC
DO = _telnetlib.DO
DONT = _telnetlib.DONT
WILL = _telnetlib.WILL
WONT = _telnetlib.WONT
if cmd == DO:
sock.sendall(IAC + WONT + option)
elif cmd == WILL:
sock.sendall(IAC + DONT + option)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== telnetlib demo ===")
if not _TELNETLIB_AVAILABLE:
print(" telnetlib not available (Python 3.13+)")
print(" Demonstrating LoginConfig and CommandResult only:")
cfg = LoginConfig(username="admin", password="secret",
login_prompt="login:", password_prompt="Password:")
print(f" LoginConfig: {cfg}")
r = CommandResult(command="show version",
output="Device version 1.0\nUptime: 5 days\n$ ",
elapsed=0.42)
print(f" CommandResult: {r}")
raise SystemExit(0)
# Try towel.blinkenlights.nl (a famous public Telnet ASCII art server)
HOST = "towel.blinkenlights.nl"
PORT = 23
print(f"\n Attempting to connect to {HOST}:{PORT} (ASCII Star Wars) ...")
print(" (If no network, skipping live demo)")
try:
with connect(HOST, PORT, timeout=8) as t:
t.set_option_negotiation_callback(refuse_all_options)
# Read the opening banner (first 500 bytes)
time.sleep(1.0)
data = t.read_very_eager()
banner = data.decode("ascii", errors="replace")
print(f"\n--- banner (first {min(len(banner), 200)} chars) ---")
print(banner[:200])
print(f"\n (total received: {len(data)} bytes)")
except (socket.timeout, socket.gaierror, ConnectionRefusedError, OSError) as e:
print(f" Network unavailable: {e}")
print(" (telnetlib is available, just no network in this environment)")
# ── demonstrate expect patterns (offline) ─────────────────────────────────
print("\n--- ExpectResult demo (no network needed) ---")
results = [
ExpectResult(matched_index=0, matched_text="root@server:~$ ",
match_obj=re.search(rb"\$", b"root@server:~$ ")),
ExpectResult(matched_index=-1, matched_text="",
match_obj=None),
]
for r in results:
print(f" {r}")
print("\n=== done ===")
For the paramiko (PyPI) alternative — paramiko.SSHClient().connect(host, username, password) followed by chan = client.invoke_shell() provides an SSH-encrypted interactive session with the same read/write/expect loop that telnetlib uses for Telnet — use paramiko for all new remote CLI automation since SSH is encrypted and Telnet transmits credentials in plaintext; use telnetlib only when the target device genuinely speaks Telnet on port 23 (legacy routers, serial-to-Ethernet adapters, mainframe consoles) and SSH is not available. For the asyncio + raw TCP alternative — asyncio.open_connection(host, port) gives you an async reader/writer pair for any TCP protocol, and asyncio.wait_for(reader.readuntil(sep), timeout) replicates read_until() — use asyncio.open_connection() for new Telnet-protocol code in Python 3.13+ since telnetlib is removed; the IAC protocol negotiation and byte escaping need to be reimplemented manually, but the basic send/expect loop maps directly. The Claude Skills 360 bundle includes telnetlib skill sets covering connect() with refuse_all_options() IAC callback, read_until_decoded()/writeline()/read_flush() I/O helpers, ExpectResult with expect() pattern matching, LoginConfig with telnet_login() automated login, and CommandResult with run_command()/run_commands() CLI scraper. Start with the free tier to try Telnet automation patterns and telnetlib pipeline code generation.