操作系统:Windows 8.1上跑CentOS 7(VMware虚拟机)
接触的LINUX版本就是CentOS,对比较热门的Ubuntu, Debian等并不熟,不同版本的LINUX有一些基本命令不一样,比如yum install和apt-get的区别,但这并不影响我们学习Python,如果读者对Linux不熟,希望你能花点时间学习下touch, chmod, cat, ls等等这些最基础的命令,以及掌握vim或者nano这些编辑文档的程序。
网络设备:GNS3运行的思科三层交换机
网络设备版本:思科IOS (vios_12-ADVENTERPRISEK9-M)
Python编辑器: Sublime Text 3.0 (很不错的编辑器,有朋友介绍Pycharm,还没来得及试用)
Python版本:2.7.5
最新的Python 3: Python 3.x是新的Python版本,将来终会淘汰Python 2成为最主流的版本。但是目前很多和计算机网络有关的模块比如Scapy, Trigger, easySNMP等在Python 3中并没得到很好的支持,就目前的趋势来看,离Python 3彻底淘汰Python 2至少还有10年的时间,而且2和3的区别虽然有,但是如果你彻底掌握了2的话,只需要一两天就能将3懂个80%,因此目前我们完全可以从2开始学起,另外2.7.5算是比较旧的版本,但是对初学者来说完全够用了。
网络实验拓扑:
局域网IP地址段:192.168.2.0 /24
运行Python的客户端: 192.168.2.1
Layer3Switch-1: 192.168.2.11
Layer3Switch-2: 192.168.2.12
Layer3Switch-3: 192.168.2.13
Layer3Switch-4: 192.168.2.14
Layer3Switch-5: 192.168.2.15
所有的交换机已经预配好了SSH,用户名: python 密码:123
网络实验拓扑
A. 先安装pip
curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
python get-pip.py
在CentOS 7里安装pip
B. 安装好pip后,接着安装paramiko
pip install paramiko
用pip安装Paramiko
C. 安装好Paramiko后,打开Python测试是否可以使用import paramiko来引用它,如果没报错,则说明安装成功。
测试Paramiko是否安装成功
实验目的:
执行代码前S1配置
#!/usr/bin/env python
import paramiko
import time
ip = "192.168.2.11"
username = "python"
password = "123"
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname=ip,username=username,password=password)
print "Sucessfully login to ", ip
command = ssh_client.invoke_shell()
command.send("configure terminal\n")
command.send("int loop 0\n")
command.send("ip address 1.1.1.1 255.255.255.255\n")
command.send("end\n")
command.send("wr mem\n")
time.sleep(1)
output = command.recv(65535)
print output
ssh_client.close
实验1代码部分讲解:
下面运行这段代码,看看效果:
代码执行效果
运行成功!
Python成功登录了192.168.2.11,帮我们执行了要配置的命令,现在再登录S1,看看变化, S1的loop 0端口已经成功配置了1.1.1.1 /32这个IP。
代码执行后S1配置
实验目的:
执行代码前S1配置
执行代码前S2配置
执行代码前S3配置
执行代码前S4配置
执行代码前S5配置
#!/usr/bin/env python
import paramiko
import time
import getpass
username = raw_input('Username: ')
password = getpass.getpass('Password: ')
for i in range(11,16):
ip = "192.168.2." + str(i)
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname=ip,username=username,password=password)
print "Successfully connect to ", ip
command = ssh_client.invoke_shell()
command.send("configure terminal\n")
for n in range (10,21):
print "Creating VLAN " + str(n)
command.send("vlan " + str(n) + "\n")
command.send("name Python_VLAN " + str(n) + "\n")
time.sleep(0.5)
command.send("end\n")
command.send("wr mem\n")
time.sleep(2)
output = command.recv(65535)
print output
ssh_client.close
实验2代码部分讲解:
执行代码来看效果
https://vdn.vzuu.com/SD/dbdaeb74-30d3-11e8-aacf-0242ac112a0a.mp4?auth_key=1541477647-0-0-c4aea7387f2867d63006cc94e3223824&expiration=1541477647&disable_local_cache=1
最后登陆所有交换机一一验证,检查代码是否创建了VLAN 10 至 20
https://vdn.vzuu.com/SD/7444c024-30d4-11e8-8948-0242ac112a0a.mp4?auth_key=1541477647-0-0-82fcc7ee4cb63b040efceba730388068&expiration=1541477647&disable_local_cache=1
实验目的:
开始实验3前,我们需要做两个准备:
把S5的地址改为192.168.2.55
创建一个ip_list.txt文件,把所有交换机的管理IP放进去
准备就绪后,老规矩创建lab3.py,把它改成可执行,然后放入下列代码
#!/usr/bin/env python
import paramiko
import time
import getpass
username = raw_input('Username: ')
password = getpass.getpass('password: ')
f = open("ip_list.txt","r")
for line in f.readlines():
ip = line.strip()
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname=ip,username=username,password=password)
print "Successfully connect to ", ip
remote_connection = ssh_client.invoke_shell()
remote_connection.send("conf t\n")
remote_connection.send("router eigrp 1\n")
remote_connection.send("end\n")
remote_connection.send("wr mem\n")
time.sleep(1)
output = remote_connection.recv(65535)
print output
f.close()
ssh_client.close
实验3代码部分讲解:
老规矩,执行代码前登陆所有交换机,确认目前没有EIGRP开启:
执行实验3代码前S1配置
执行实验3代码前S2配置
执行实验3代码前S3配置
执行实验3代码前S4配置
执行实验3代码前S5配置
一切就绪后,执行代码看效果:
https://vdn.vzuu.com/SD/bccb2d1c-328a-11e8-9b3f-0242ac112a0c.mp4?auth_key=1541477951-0-0-6368b1877b9c8d90c1c45ac369069e62&expiration=1541477951&disable_local_cache=1
最后登陆所有交换机一一验证EIGRP是否已经开启:
https://vdn.vzuu.com/SD/4bbae350-328b-11e8-88f4-0242ac112a0d.mp4?auth_key=1541477950-0-0-509508d68cdd38f7ead0a9887e1fa685&expiration=1541477950&disable_local_cache=1
前面的例子提到了,要使用Python来批量连接网络设备,可以把设备的IP地址写入一个文本文件,然后在代码里使用for循环配合open()函数以及readlines()函数逐行读取该文本文件里的IP地址,达到循环批量登录多台网络设备的目的。
在成功登录网络设备后,我们又可以配合command.send()来对网络设备发号施令,但在前面的例子中我们都是将要输入的命令预先写在了代码里面,比如command.send("conf t\n"),command.send("router eigrp 1\n"), command.send("end\n")。
举例如下:
# -- coding: UTF-8 --
import paramiko
import time
import getpass
username = raw_input('Username: ')
password = getpass.getpass('password: ')
f = open("ip_list.txt","r")
for line in f.readlines():
ip = line
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname=ip,username=username,password=password)
print username, " You have successfully connect to ", ip
command = ssh_client.invoke_shell()
#在代码里预设好要输入的命令
command.send("conf t\n")
command.send("router eigrp 1\n")
command.send("end\n")
time.sleep(1)
output = remote_connection.recv(65535)
print output
f.close()
ssh_client.close
这种将配置命令预设在代码里的方法便于初学者理解和学习,在只有几台设备的实验环境中常用。但是在有成千上万台网络设备需要管理的生产环境中,这种方法显得很笨拙,缺乏灵活性。举例来说,假设你的生产环境中有不同型号,不同操作系统,不同命令格式的设备各1000台,比如思科的3750和3850交换机,前者跑的是IOS,后者跑的是IOS-XE,你要分别给它们批量修改QoS的配置,因为两者的命令格式差异巨大(一个是MLS QOS,一个是MQC QOS),你必须反复修改command.send()这个部分的代码,如果只是简单数条命令还好办,一旦遇到大规模的配置,那这种方法的效率会很低下。
解决这个问题的思路是分别创建两个文本文件,一个用来存放配置3750要用到的命令集,另一个用来存放配置3850要用到的命令集,然后在Python脚本里通过for循环加open()来读取两个文件里的内容以达到分别给所有3750和3850交换机做QoS配置的目的,这样做的好处是无须修改command.send()这个部分的代码,因为所有的命令行已经在文本文件里预先设置好了。
实验背景:
假设你现在手边有3台管理ip地址在192.168.100.x /24网段的3750交换机以及3台管理ip地址在172.16.100.x/24网段的3850交换机,它们的hostname和管理ip地址如下:
3750_1: 192.168.100.11
3750_2: 192.168.100.22
3750_3: 192.168.100.33
3850_1: 172.16.100.11
3850_2: 172.16.100.22
3850_3: 172.16.100.33
实验目的:
你需要同时修改所有3750和3850的QoS配置,更改它们出队列(output queue)的队列参数集2(queue-set 2)的缓存(buffers)配置,给队列1,2,3,4分别分配15%, 25%, 40%, 20%的缓存 (默认状况下是25%,25%,25%,25%)。
实验步骤:
# -- coding: UTF-8 --
import paramiko
import time
import getpass
import sys
username = raw_input('Username: ')
password = getpass.getpass('password: ')
ip_file = sys.argv[1]
cmd_file = sys.argv[2]
iplist = open(ip_file, 'r')
for line in iplist.readlines():
ip = line.strip()
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname=ip,username=username,password=password)
print "You have successfully connect to ", ip
command = ssh_client.invoke_shell()
cmdlist = open(cmd_file, 'r')
cmdlist.seek(0)
for line in cmdlist.readlines():
command.send(line + "\n")
time.sleep(2)
cmdlist.close()
output = command.recv(65535)
print output
iplist.close()
ssh_client.close
实验4代码部分讲解:
imacs-iMac:book-example imac$ python lab4.py ip_3750.txt cmd_3750.txt
这时argv = ['lab4.py', 'ip_3750.txt', 'cmd_3750.txt']
我们代码里的:
ip_file = sys.argv[1],此时也就等同于ip_file = ip_3750.txt
cmd_file = sys.argv[2], 此时也就等同于cmd_file = cmd_3750.txt
同理,如果这时我们在linux执行命令:
imacs-iMac:book-example imac$ python lab4.py ip_3850.txt cmd_3850.txt
那么此时ip_file = ip_3850.txt, cmd_file = cmd_3850.txt
最后执行代码看效果:
imacs-iMac:book-example imac$ python lab4.py ip_3750.txt cmd_3750.txt
Username: python
password:
You have successfully connect to 192.168.100.11
3750_1#conf t
3750_1(config)#mls qos queue-set output 1 buffers 15 25 40 20
3750_1(config)#end
3750_1#wr mem
Building configuration...
[OK]
You have successfully connect to 192.168.100.22
3750_2#conf t
3750_2(config)#mls qos queue-set output 1 buffers 15 25 40 20
3750_2(config)#end
3750_2#wr mem
Building configuration...
[OK]
You have successfully connect to 192.168.100.33
3750_3#conf t
3750_3(config)#mls qos queue-set output 1 buffers 15 25 40 20
3750_3(config)#end
3750_3#wr mem
Building configuration...
[OK]
imacs-iMac:book-example imac$ python lab4.py ip_3850.txt cmd_3850.txt
Username: python
password:
You have successfully connect to 172.16.100.11
3850_1#configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
3850_1(config)#class-map match-any cos7
3850_1(config-cmap)#match cos 7
3850_1(config-cmap)#class-map match-any cos1
3850_1(config-cmap)#match cos 1
3850_1(config-cmap)#exit
3850_1(config)#policy-map queue-buffer
3850_1(config-pmap)#class cos7
3850_1(config-pmap-c)#bandwidth percent 10
3850_1(config-pmap-c)#queue-buffers ratio 15
3850_1(config-pmap-c)#class cos1
3850_1(config-pmap-c)#bandwidth percent 30
3850_1(config-pmap-c)#queue-buffers ratio 25
3850_1(config-pmap-c)#exit
3850_1(config-pmap)#exit
3850_1(config)#interface gi1/0/1
3850_1(config-if)#service-policy output queue-buffer
3850_1(config-if)#end
3850_1#wr mem
Building configuration...
Compressed configuration from 62654 bytes to 19670 bytes[OK]
You have successfully connect to 172.16.100.22
3850_2#configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
3850_2(config)#class-map match-any cos7
3850_2(config-cmap)#match cos 7
3850_2(config-cmap)#class-map match-any cos1
3850_2(config-cmap)#match cos 1
3850_2(config-cmap)#exit
3850_2(config)#policy-map queue-buffer
3850_2(config-pmap)#class cos7
3850_2(config-pmap-c)#bandwidth percent 10
3850_2(config-pmap-c)#queue-buffers ratio 15
3850_2(config-pmap-c)#class cos1
3850_2(config-pmap-c)#bandwidth percent 30
3850_2(config-pmap-c)#queue-buffers ratio 25
3850_2(config-pmap-c)#exit
3850_2(config-pmap)#exit
3850_2(config)#interface gi1/0/1
3850_2(config-if)#service-policy output queue-buffer
3850_2(config-if)#end
3850_2#wr mem
Building configuration...
Compressed configuration from 62654 bytes to 19670 bytes[OK]
You have successfully connect to 172.16.100.33
3850_3#configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
3850_3(config)#class-map match-any cos7
3850_3(config-cmap)#match cos 7
3850_3(config-cmap)#class-map match-any cos1
3850_3(config-cmap)#match cos 1
3850_3(config-cmap)#exit
3850_3(config)#policy-map queue-buffer
3850_3(config-pmap)#class cos7
3850_3(config-pmap-c)#bandwidth percent 10
3850_3(config-pmap-c)#queue-buffers ratio 15
3850_3(config-pmap-c)#class cos1
3850_3(config-pmap-c)#bandwidth percent 30
3850_3(config-pmap-c)#queue-buffers ratio 25
3850_3(config-pmap-c)#exit
3850_3(config-pmap)#exit
3850_3(config)#interface gi1/0/1
3850_3(config-if)#service-policy output queue-buffer
3850_3(config-if)#end
3850_3#wr mem
Building configuration...
Compressed configuration from 62654 bytes to 19670 bytes[OK]
在网络设备数量超过千台甚至上万台的大型企业网中,难免会遇到某些设备管理IP不通,SSH连接失败的情况,设备数量越多,这种情况发生的几率越大。这个时候如果你想用Python批量配置所有的设备,就一定要注意这种情况,很有可能你的脚本跑了还不到一半就因为中间某一个连接不通的设备而停止了。比如说你有3000台交换机需要统一更改本地用户名和密码,前500台交换机的连通性都没问题,第501台交换机因为某个网络问题导致管理IP不可达,SSH连不上了,这个时候Python会返回一个socket.error: [Errno 10060] A connection
attempt failed because the connected party did not properly respond after a
period of time, or established connection failed because connected host has
failed to respond 的错误(如下图),然后你的脚本就此停住了!脚本不会再对剩下的2500台交换机做配置,“挂机"失败!
Socket.error报错
同样的问题也会发生在当你输入了错误的交换机用户名和密码之后,或者某些交换机和其他大部分交换机用户名和密码不一致的时候(因为我们只能输入一次用户名和密码,用户名/密码不一致会导致个别交换机无法登陆的情况发生),也许你会问大型企业网不都是统一配置AAA配合TACACS或者RADIUS做用户访问管理吗,怎么还会出现登陆账号和密码不一样的问题?这个现象就发生在笔者目前所任职的沙特阿卜杜拉国王科技大学,学校里的TACACS服务器(思科ACS)已经服役9年,现在的问题是每天早晨该ACS会“失效”,必须手动重启ACS所在的服务器才能解决问题,在ACS无法正常工作期间,我们只能通过网络设备的本地账号和密码登录,鉴于此,我们已经部署了思科的ISE来替代ACS做TACACS服务器, 但由于学校网络过于庞大,迁徙过程漫长,这就导致了部分设备已经迁徙,使用上了ISE上配置的账号和密码,而另一部分还没有迁徙的设备在ACS出问题的时候只能用本地的账号和密码,这就出现了两套账号和密码的情况,后果就是使用paramiko来SSH登陆网络设备的python会返回paramiko.ssh_exception.AuthenticationException:
Authentication failed这个错误(如下图),导致你的脚本戛然而止,无法继续运行。
用户名/密码不匹配造成Authentication failed报错
要解决上述两个问题也很简单,这里我们可以用到Python中的try, except异常处理语句,来让你的脚本在遇到上述出现设备连通性故障以及用户认证无法通过的情况时,继续给后面的设备做配置,而不会因为某个故障的设备而停下来。
实验背景:
用回Lab1 - 3里用到的网络拓扑,将交换机S3 (192.168.2.13)的密码从123改为456, 将S4(192.168.2.14)的e0/0端口断掉。
实验目的:
创建一个带有try,except异常处理语句的python脚本来批量在交换机S1-S5上执行show clock命令,让python脚本在S3,S4分别因为用户名密码不匹配,以及连通性出现故障的情况下,依然可以不受干扰,接着配置S5。
实验步骤:
1. 首先将S3的密码从123改为456
2. 再将S4的端口e0/0断掉
3. 在运行python的客户端上创建一个名为ip_list.txt的文本文件,内含S1-S5的IP
4. 在运行python的客户端上继续创建一个名为cmd.txt的文本文件,写入我们要在S1-S5上执行的命令:show clock
5. 创建一个lab5.py脚本, 然后放入下面代码
实验5代码:
import paramiko
import time
import getpass
import sys
import socket
username = raw_input('Username: ')
password = getpass.getpass('password: ')
ip_file = sys.argv[1]
cmd_file = sys.argv[2]
switch_with_authentication_issue = []
switch_not_reachable = []
iplist = open(ip_file, 'r')
for line in iplist.readlines():
try:
ip = line.strip()
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname=ip,username=username,password=password,look_for_keys=False)
print "You have successfully connect to ", ip
command = ssh_client.invoke_shell()
cmdlist = open(cmd_file, 'r')
cmdlist.seek(0)
for line in cmdlist.readlines():
command.send(line + "\n")
time.sleep(2)
cmdlist.close()
output = command.recv(65535)
print output
except paramiko.ssh_exception.AuthenticationException:
print "User authentication failed for " + ip + "."
switch_with_authentication_issue.append(ip)
except socket.error:
print ip + " is not reachable."
switch_not_reachable.append(ip)
iplist.close()
ssh_client.close
print '\nUser authentication failed for below switches: '
for i in switch_with_authentication_issue:
print i
print '\nBelow switches are not reachable: '
for i in switch_not_reachable:
print i
实验5代码部分讲解:
最后执行代码看效果:
注意重点部分已经用红线标注: