telnetlib 简介
telnetlib 是 Python 标准库的一部分,它提供了一个 Telnet 客户端,使用它,你可以编写 Python 脚本来连接到 Telnet 服务器,并自动执行命令。

基本连接和登录流程
一个典型的 Telnet 自动登录流程如下:
- 导入
telnetlib模块。 - 创建
Telnet对象并连接到服务器,指定主机名/IP 地址和端口号(默认为 23)。 - 读取服务器返回的初始信息(如欢迎信息或登录提示)。
- 发送用户名。
- 读取服务器返回的密码提示。
- 发送密码。
- 登录成功后,发送命令。
- 读取命令的输出。
- 关闭连接。
关键方法
telnetlib.Telnet(host, port=23): 创建一个 Telnet 对象并尝试连接。read_until(expected_string, timeout=None): 读取数据,直到遇到expected_string或超时,这对于等待登录提示非常有用。read_all(): 读取缓冲区中所有的剩余数据。write(buffer): 向服务器发送数据。注意:发送的数据需要是字节串 (bytes),所以字符串通常需要用.encode('ascii')或.encode('utf-8')转换。expect(list_of_expected_strings, timeout=None): 这是一个更强大的方法,它会读取数据,直到匹配到list_of_expected_strings中的任何一个,并返回一个元组(index, match, bytes_read)。index是匹配到的字符串在列表中的索引,非常适合处理 "Login:" 和 "Password:" 这两种不同的提示。close(): 关闭 Telnet 连接。
示例代码
示例 1:简单直接的登录
这个例子假设服务器的登录流程非常简单:先收到 "login:",然后收到 "password:"。
import telnetlib
import time
# --- 配置 ---
HOST = "your_telnet_server_ip" # 替换为你的 Telnet 服务器 IP
PORT = 23 # 默认 Telnet 端口
USER = "your_username" # 替换为你的用户名
PASSWORD = "your_password" # 替换为你的密码
TIMEOUT = 10 # 设置超时时间,单位:秒
try:
# 1. 连接到 Telnet 服务器
print(f"正在连接到 {HOST}...")
tn = telnetlib.Telnet(HOST, PORT, timeout=TIMEOUT)
# 2. 读取初始信息,直到出现 "login:" 提示
# 注意:有些服务器可能提示 "Username:" 或 "Login:"
tn.read_until(b"login: ", timeout=TIMEOUT)
print("已收到 'login:' 提示。")
# 3. 发送用户名
# .encode('ascii') 将字符串转换为字节串
tn.write(USER.encode('ascii') + b"\n")
print(f"已发送用户名: {USER}")
# 4. 读取密码提示 "password:"
tn.read_until(b"password: ", timeout=TIMEOUT)
print("已收到 'password:' 提示。")
# 5. 发送密码
tn.write(PASSWORD.encode('ascii') + b"\n")
print("已发送密码。")
# 6. 等待登录成功,可以读取一个命令提示符,'$', '>', '#' 等
# 这里我们简单等待几秒,让系统稳定
time.sleep(2)
# 读取登录后的输出,清空缓冲区
output = tn.read_very_eager()
print("登录成功!")
print("服务器返回信息:", output.decode('ascii'))
# 7. 发送一个命令,'ls -l'
print("\n正在执行命令: 'ls -l'")
tn.write(b"ls -l\n") # 命令后也要加换行符 \n
# 8. 读取命令的输出,直到再次看到命令提示符
# 这是一个更健壮的方法,等待出现 '$' 或 '>' 或 '#'
tn.read_until(b"$ ", timeout=TIMEOUT) # 假设提示符是 '$ '
output = tn.read_very_eager()
print("命令 'ls -l' 的输出:")
print(output.decode('ascii'))
except ConnectionRefusedError:
print(f"错误: 无法连接到 {HOST}:{PORT},服务器可能未运行或地址错误。")
except EOFError:
print("错误: 连接被服务器意外关闭。")
except Exception as e:
print(f"发生未知错误: {e}")
finally:
# 9. 确保连接被关闭
if 'tn' in locals() and tn.get_socket() is not None:
tn.close()
print("\n连接已关闭。")
示例 2:使用 expect() 的更健壮方法
expect() 方法比 read_until() 更灵活,因为它可以等待多个可能的提示,这在处理不同提示符(如 "Login:" vs "Username:")或登录失败时非常有用。
import telnetlib
# --- 配置 ---
HOST = "your_telnet_server_ip"
PORT = 23
USER = "your_username"
PASSWORD = "your_password"
TIMEOUT = 10
try:
print(f"正在连接到 {HOST}...")
tn = telnetlib.Telnet(HOST, PORT, timeout=TIMEOUT)
# 定义我们期望的提示符列表
# 匹配顺序很重要,会按顺序检查
login_prompts = [b"login: ", b"Username: ", b"Login: "]
password_prompt = b"password: "
shell_prompt = b"$ " # 假设命令行提示符是 $
# 1. 等待用户名提示
# expect 返回 (匹配的索引, 匹配的对象, 读取到的字节)
index, match, text = tn.expect(login_prompts, timeout=TIMEOUT)
if index == -1:
raise Exception("未收到预期的用户名提示。")
print(f"已收到用户名提示: {match.group().decode('ascii')}")
# 2. 发送用户名
tn.write(USER.encode('ascii') + b"\n")
print(f"已发送用户名: {USER}")
# 3. 等待密码提示
index, match, text = tn.expect([password_prompt], timeout=TIMEOUT)
if index == -1:
raise Exception("未收到预期的密码提示。")
print(f"已收到密码提示: {match.group().decode('ascii')}")
# 4. 发送密码
tn.write(PASSWORD.encode('ascii') + b"\n")
print("已发送密码。")
# 5. 等待登录成功,出现命令行提示符
# 在真实环境中,登录过程可能会输出一些信息,所以用 expect 来捕获提示符
index, match, text = tn.expect([shell_prompt, b"Login incorrect", b"Permission denied"], timeout=TIMEOUT)
if match.group() == b"Login incorrect" or match.group() == b"Permission denied":
raise Exception("登录失败:用户名或密码错误。")
print("登录成功!")
# 6. 发送命令并读取输出
command = "hostname"
print(f"\n正在执行命令: '{command}'")
tn.write(command.encode('ascii') + b"\n")
# 等待命令执行完毕并再次看到提示符
tn.expect([shell_prompt], timeout=TIMEOUT)
# 读取命令的输出
output = tn.read_very_eager()
print(f"命令 '{command}' 的输出:")
print(output.decode('ascii'))
except Exception as e:
print(f"发生错误: {e}")
finally:
if 'tn' in locals() and tn.get_socket() is not None:
tn.close()
print("\n连接已关闭。")
重要注意事项和最佳实践
-
安全性:Telnet 协议是明文传输的,这意味着你的用户名、密码和所有命令都会以明文形式在网络中传输。强烈建议不要在生产环境或任何不信任的网络中使用 Telnet,对于需要远程管理的场景,请优先使用 SSH(可以通过
paramiko库实现)。
(图片来源网络,侵删) -
编码问题:
telnetlib处理的是字节流,当你从服务器读取数据时,得到的是bytes对象,你需要使用.decode('ascii')或.decode('utf-8', 'ignore')将其转换为字符串。'ignore'可以忽略无法解码的字符,避免程序崩溃。- 向服务器发送数据时,字符串也需要用
.encode()转换为bytes。
-
超时设置:
timeout参数非常重要,如果服务器没有响应,你的脚本可能会无限期地等待下去,为所有可能阻塞的操作(如read_until,expect)设置一个合理的超时值。 -
提示符的多样性:不同操作系统(Linux, Cisco IOS, Juniper Junos 等)的提示符可能不同,登录成功后的提示符可能是 ,
>, 或自定义的字符串,你需要根据你的目标设备调整等待的提示符。 -
处理交互式命令:对于
more/less这样的分页命令,telnetlib无法直接处理,你需要先发送命令(如set pager 0)来禁用分页,或者模拟按键(如空格键或q键)来退出分页界面。 -
调试:如果脚本行为不符合预期,可以添加打印语句来查看服务器返回的原始字节流,这有助于你确定正确的提示符和流程。
# 在读取数据后打印 raw_data = tn.read_until(b"login: ") print(f"原始接收数据: {raw_data}")
