实验运行环境

主机操作系统:Windows 10 上运行 CentOS 8(VMware 虚拟机)

(也可以不用虚拟机,在GNS3中用绑定了VMnet1或VMnet8网卡的云也行)

网络设备:GNS3 模拟器上运行的思科三层交换机

网络设备 OS 版本:思科 IOS (vios_12-ADVENTERPRISEK9-M)

Python 版本:3.8.2

实验网络拓扑图如下所示。

python实验1 远程登录一台设备下发配置_netmiko

python实验1 远程登录一台设备下发配置_python实验_02

局域网 IP 地址段:192.168.2.0 /24

运行 Python 的 CentOS 主机:192.168.2.1

SW1:192.168.2.11

SW2:192.168.2.12

SW3:192.168.2.13

SW4:192.168.2.14

SW5:192.168.2.15

sw1~5的f0/0接⼝IP,当作设备的管理IP

所有交换机都已经预配好了 SSH 和 Telnet,用户名为 python,密码为 123,用户的权限为 15 级,SSH 或 Telnet 登录后直接进入特权模式,无须输入 enable 密码。

配置telnet:

conf t
line vty 0 4
login local
exit

username python privilege 15 password 123

配置ssh:

conf t
ip domain name aaa
crypto key generate rsa
1024
line vty 0 4
login local
transport input ssh
exit

username python privilege 15 password 123

在 Python 中,支持 Telnet/SSH 远程登录访问网络设备的模块很多,常见的有 Telnetlib、Ciscolib、Paramiko、Netmiko 和 Pexpect。其中,Telnetlib 和 Ciscolib 对应 Telnet 协议,后面 3 个对应 SSH 协议。

常⽤的是Telnetlib、Netmiko、Paramiko。

实验目的

通 过 Telnetlib、Netmiko 和 Paramiko 模块,分别登录交换机 SW1(192.168.2.11)、SW2(192.168.2.12)、SW3(192.168.2.13),给 SW1 的 loopback 1 端口配置 IP 地址 1.1.1.1/32,给 SW2 的 loopback 1 端口配置 IP 地址 2.2.2.2/32,给 SW3 的 loopback 1 端口配置 IP 地址 3.3.3.3/32。

Telnetlib

在 Python 中,我们使用 Telnetlib 模块来 Telnet 远程登录网络设备,Telnetlib 为 Python内建模块,不需要 pip 下载安装就能直接使用。

鉴于 Telnet 的安全性,通常不建议在生产网络中使用 Telnet,这里只举一例来讲解 Telnetlib 模块的使用方法。

Telnetlib 在 Python 2 和 Python 3 中有非常大的区别,虽然本书内容基于 Python 3.8,但是鉴于部分读者对 Telnet 还有需求,并且可能使用过 Python 2,这里将分别介绍 Telnetlib在 Python 2 和 Python 3 中的使用方法。

Telnetlib 在 Python 3 中的应用

首先手动登录 SW1,确认它此时没有 loopback 1 这个端口,再开启 debug  telnet 便于后面运行代码时做验证。

python实验1 远程登录一台设备下发配置_netmiko_03

在 Python 3 中,Telnetlib 模块下所有函数的返回值都变成字节型字符串(ByteStrings)。因此,在 Python 3 中使用 Telnetlib 需要注意下面几点。

1、在字符串的前面需要加上一个 b。

2、在变量和 Telnetlib 函数后面需要加上.encode('ascii')函数。

3、在 read_all()函数后面需要加上 decode(‘utf-8’)函数。

下面是在 Python 3 或 Python 3.8 中使用的 Telnetlib 脚本。

然后在运行 Python 的主机上(后面统称主机)创建下面的脚本,将其命名为 telnet.py。

[root@CentOS-Python ~]# cat telnet.py

import telnetlib

host="192.168.2.11" # sw1的管理IP
user="python" # telnet登录的用户名
password="123" # telnet登录的密码

tn=telnetlib.Telnet(host) # telnet登录主机host,并将这个telnet对象赋值给变量 tn
tn.read_until(b"Username: ") # 读取字节型字符串,直至遇到 Username:
tn.write(user.encode('utf-8')+b"\n") # 在字符串 Username: 的后面输入 用户名 user
tn.read_until(b"Password: ") # 读取字节型字符串,直至遇到 Password:
tn.write(password.encode('utf-8')+b"\n") # 在字符串 Password: 的后面输入 密码 password

#配置
tn.write(b"conf t\n")
tn.write(b"int loopback 1\n")
tn.write(b"ip address 1.1.1.1 255.255.255.255\n")
tn.write(b"end\n")

tn.write(b"exit\n") # 退出telnet登录

print(tn.read_all().decode('utf-8')) # read_all()记录对sw1的配置并打印输出,只有在退出telnet后才会生效

运行代码后看结果、做验证。

(1)在主机上输入 python3.8 telnet.py 用 Python 3 来运行代码,如下所示。

python实验1 远程登录一台设备下发配置_python实验_04

(3)最后在 SW1 上输入 show run int loop 1 做验证,确认端口 loopback 1 的 IP 地址配置正确,通过 Telnetlib 模块登录交换机修改配置的实验成功。

python实验1 远程登录一台设备下发配置_python实验_05

 Netmiko

Netmiko 为 Python 第三方模块,需要使用 pip 来下载安装,因为实验基于 Python 3.8,这里用命令 pip3.8 install Netmiko 来下载安装 Netmiko,如下图所示。

python实验1 远程登录一台设备下发配置_python实验_06

下载完毕后,进入 Python 3.8 解释器,如果 import netmiko 没有报错,则说明 Netmiko安装成功,如下图所示。

python实验1 远程登录一台设备下发配置_python实验_07

首先登录 SW2,确认 loopback 1 端口当前不存在,然后启用 debug ip ssh 来监督验证下面创建的 Netmiko 脚本是否以 SSH 协议登录访问 SW2,如下图所示。

python实验1 远程登录一台设备下发配置_远程登陆一台设备下发配置_08

接下来在主机上创建一个名为 ssh_Netmiko.py 的 Python 3.8 脚本,内容如下。

[root@CentOS-Python ~]# cat ssh_Netmiko.py

# 通过netmiko模块的链接库函数ConnectHandler()来SSH登录⽹络设备
from netmiko import ConnectHandler


# 创建⼀个名为sw2的字典,该字典的这四个键都是必须有的键
# #Netmiko⽀持SSH登录绝⼤多数主流⼚商的⽹络设备,并且⽀持各⼤⼚商不同OS类型的设备
# ⽐如针对Cisco的设备,Netmiko⽀持Cisco ASA、Cisco IOS、Cisco IOS-XE、Cisco IOS-XR、Cisco NX-OS、Cisco SG300共6种不同OS类型的设备
# 由于不同⼚商的设备登录后的命令⾏界⾯及命令是不⼀样的,所以必须通过”device_type“来指定ssh登录的设备的OS号
# 因为实验里我们用到的是思科 IOS 设备,因此'deivce_type'的键值为'cisco_ios'
SW2={
'device_type':'cisco_ios',
'ip':'192.168.2.12',
'username':'python',
'password':'123',
}

# 通过Netmiko的ConnectHandler()函数,用已经创建的字典 SW2 进行 SSH 连接,将它赋值给 connect 变量,注意 SW2 前面的**作标识用,不可以省去。
connect=ConnectHandler(**SW2)
# 如果 SSH 登录设备成功,则提示用户并告知所登录的交换机的 IP 地址
print("Successfully connected to "+SW2['ip'])

# 创建一个名为 config_commands 的列表,其元素为需要在交换机上依次执行的命令。
config_commands=['int loop 1','ip address 2.2.2.2 255.255.255.255']

# 以刚刚创建的 config_commands 列表为参数,调用 ConnectHandler()的send_config_set()函数来使用上述命令对 SW2 做配置,并将配置过程打印出来。
output=connect.send_config_set(config_commands)
print(output)

# 调用 ConnectHandler()的 send_command()函数,对交换机输入命令“show run int loop 1”并将回显内容打印出来。
# 需要注意的是,send_command()一次只能向设备输入一条命令,而 send_config_set()则可向设备输入多条命令。
result=connect.send_command('show run int loop 1')
print(result)

运行代码后看结果、做验证。

(1)在主机上输入 python3.8 ssh_Netmiko.py 运行代码,可以看到除了我们在代码里写的 int loop 1 和 ip address 2.2.2.2 255.255.255.255 两个命令,Netmiko 额外替我们输入了 3 个命令,一个是 config term,一个是 end,还有一个是 write memory。

最后我们能看到show run int loop 1 命令的回显内容,证实 loopback1 端口配置成功,如下所示。

python实验1 远程登录一台设备下发配置_paramiko_09

(2)同一时间在 SW2 上可以看到通过 Netmiko 来 SSH 登录 SW2 的详细日志(之前我们已经在 SW2 上开启了 debug ip ssh),如下所示。

python实验1 远程登录一台设备下发配置_python实验_10

(3)最后我们在 SW2 上输入 show run int loop 1 做验证,确认 loopback 1 端口的 IP地址配置正确,通过 Netmiko 模块登录交换机修改配置的实验成功,如下所示。

python实验1 远程登录一台设备下发配置_netmiko_11

注:在使用 Netmiko 的 ConnectHandler 的脚本中,不能将脚本命名为 netmiko.py,否则会遇到“ImportError: cannot import name 'ConnectHandler' from partially initialized module 'Netmiko' (most likely due to a circular import) (/root/Netmiko.py)”这个错误。Paramiko 也一样,脚本名字不能为 paramiko.py,如下所示。

python实验1 远程登录一台设备下发配置_paramiko_12

Paramiko

与 Netmiko 不同,Paramiko 不会在做配置的时候替我们自动加上 config term、end 和write memory 等命令,也不会在执行各种 show 命令后自动保存该命令的回显内容,一切都需要我们手动搞定。

另外,Python 不像人类,后者在手动输入每条命令后会间隔一定时间,再输入下一条命令。Python 是一次性执行所有脚本里的命令,中间没有间隔时间当你要一次性输入很多条命令时,便经常会发生 SSH 终端跟不上速度,导致某些命令缺失没有被输入的问题(用传统的“复制、粘贴”方法给网络设备做配置的人应该遇到过这个问题)。同样,在用 print()函数输入回显内容或者用 open.write()将回显内容写入文档中保存时,也会因为缺乏间隔时间而导致 Python“截屏”不完整,从而导致回显内容不完整。

Netmiko 自动帮我们解决了这个问题,也就是说,不管上面所举的 Netmiko 例子中config_commands 列表中的元素(命令)有多少个,都不会出现因为间隔时间不足而导致配置命令缺失的问题。

而在 Paramiko 中,我们必须导入 time 模块,使用该模块的 sleep()方法来解决这个问题,关于 time 模块和 sleep()方法的使用将在下面的实验中讲到。

下面我们用 Paramiko 来完成最后一个实验:用 Paramiko 来登录 SW3 并为它的loopback1 端口配置 IP 地址 3.3.3.3/32。

与 Netmiko 一样,Paramiko 也是第三方模块,需要使用 pip 来下载安装,方法如下所示。

python实验1 远程登录一台设备下发配置_远程登陆一台设备下发配置_13

下载完毕后,进入 Python 3.8 解释器,如果 import paramiko 没有报错,则说明 Paramiko安装成功,如下所示。

python实验1 远程登录一台设备下发配置_远程登陆一台设备下发配置_14

与前面两个实验一样,首先登录 SW3,确认 loopback 1 端口目前并不存在,然后启用debug ip ssh 来监督验证 Paramiko 脚本是否以 SSH 协议登录访问 SW3,如下所示。

python实验1 远程登录一台设备下发配置_netmiko_15

接下来在主机上创建一个名为 ssh_paramiko.py 的 Python 3.8 脚本,内容如下。

[root@CentOS-Python ~]# cat ssh_paramiko.py

# 通过 import 语句导入 paramiko 和 time 两个模块
import paramiko
import time

# 创建 3 个变量:ip、username 和 password,分别对应我们要登录的交换机(SW3)的管理 IP、SSH 用户名和密码
ip="192.168.2.13"
username="python"
password="123"

# 使⽤Paramiko模块的SSHClient()函数来建⽴⼀个SSH客户端对象,,将其赋值给变量 ssh_client。在这⾥运⾏脚本的主机就是SSH client端,SW3是SSH Server端
ssh_client=paramiko.SSHClient()

# 默认情况下, Paramiko 会拒绝任何未知的 SSH 公匙,这里我们需要使用下述命令来让 Paramiko 接受来自SSH 服务端(也就是 SW3)提供的公匙,这是任何时候使用 Paramiko 都要用到的标准配置。
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 必备命令

# 调用 Paramiko.SSHClient()的connect()函数进行 SSH 登录,该函数包含 3 个必选的参数 hostname、username 和 password,分别对应我们创建的 ip、username 和 password 3 个变量,也就是远程登录的设备的主机名/IP 地址、SSH 用户名和密码。
# 如果 SSH 登录设备成功,则提示用户并告知所登录的交换机的管理 IP 地址。
ssh_client.connect(hostname=ip,username=username,password=password)
print("Successfully connected to ",ip)

# 使⽤Paramiko SSH连接成功后,需要调用 Paramiko.SSHClient()的 invoke_shell()方法来唤醒shell,也就是思科设备 IOS 命令行,并将它赋值给变量 command。
command=ssh_client.invoke_shell()

# 调用 invoke_shell()的 command()函数来向 SW3“发号施令”。
# 这里注意需要手动在代码中输入 configure terminal、end 和 wr mem 3 个命令,这一点与 Netmiko不同。
command.send("configure terminal\n")
command.send("int loop 1\n")
command.send("ip address 3.3.3.3 255.255.255.255\n")
command.send("end\n")
command.send("wr mem\n")

# 防止Python⼀次性执⾏脚本命令时出现SSH终端跟不上命令速度的情况,需要调用 time 模块下的 sleep()函数,在合适的位置让Python休眠⼀会,保证代码能完整运⾏
time.sleep(2)

# 用 Paramiko 的 recv()函数将回显结果保存下来
# 这里的 command.recv(65535)中的 65535 代表截取 65535 个字符的回显内容,这也是 Paramiko 一次能截取的最大回显内容数。
output=command.recv(65535)

# 在 Python 3 中,Paramiko 截取的回显内容格式为字节型字符串,需要用 decode("utf-8")将其解析为 utf-8 编码,否则打印出来的 output 的内容格式会很难看
print(output.decode("utf-8"))

ssh_client.close # 配置完毕后,⽤close⽅法退出SSH

运行代码后看结果、做验证。

(1)在主机上输入 python ssh_paramiko.py 来运行代码,可以看到脚本提示登录192.168.2.13(SW3)成功,然后给 loopback1 端口配置 IP 地址 3.3.3.3,最后保存配置。

代码运行如下图所示。

python实验1 远程登录一台设备下发配置_远程登陆一台设备下发配置_16

注:

如果在 CentOS 中运行代码时遇到 SSH 用户名和密码都正确,但是一直出现“Authentication failed”验证失败的异常提示(如下所示),很可能是主机之前用 ssh-keygen命令生成过 RSA 密匙对用来做 SSH 免密码登录(比如在使用 Gitlab 时可以用到)。一旦在CentOS 上生成了本地 RSA 密匙对,Paramiko 就会一直尝试使用该密匙对来登录设备,原因是默认情况下 Paramiko.SSHClient().connect()中的 look_for_keys 可选参数默认为 True。通常情况下,我们登录交换机等网络设备时不是 SSH 免密登录,没有用到 RSA 密匙对,但是 Paramiko 又一直尝试使用该密匙对来登录设备,导致 Authentication failed 验证失败。

python实验1 远程登录一台设备下发配置_paramiko_17

在交换机上开启 debug ip ssh 后,能看到用户名 python 的公匙(publickey)缺失,导致验证失败的日志记录,如下所示。

python实验1 远程登录一台设备下发配置_paramiko_18

解决办法也很简单,将 look_for_keys 参数修改为 False 即可。

ssh_client.connect(hostname=ip, username=username,password=password,look_for_keys=False)

(3)最后我们在 SW3 上输入 show run int loop 1 做验证,确认 loopback 1 端口的 IP地址配置正确,通过 Paramiko 模块登录交换机修改配置的实验成功,如下图所示。

python实验1 远程登录一台设备下发配置_netmiko_19