我们曾经提到过在 Python 中用来执行 ping 命令的模块有很多种,os、subprocess 及 pyping 都可以用来 ping 指定的 IP 地址或者 URL。

三者的区别是 os 和subprocess 在执行 ping 命令时脚本会将系统执行 ping 时的回显内容显示出来,有时这些回显内容并不是必要的,因为我们真正关心的其实是最后一句用 Python 打印出来的通知用户目标 IP 地址或者 URL 是否可达的内容“www.cisco.com is reachable”,而用 pyping 来执行 ping 命令则不会有回显内容过多的问题。很遗憾的是,pyping 只支持 Python 2,在 Python 3.8 里,我们可以使用 pythonping 作为 pyping 的替代品。

本实验将详细介绍 pythonping 的使用方法。

实验背景

本实验将在真机下完成。

某公司有 48 口的思科 3750 交换机共 1 000 台,分别分布在 5 个子网掩码为/24的 B 类网络子网下:172.16.0.x /24,172.16.1.x /24,172.16.2.x /24,172.16.3.x /24,172.16.4.x /24。

实验目的

使用 Python 脚本依次 ping 所有交换机的管理 IP 地址,来确定当前(需要记录下运行脚本时的时间,要求精确到年月日和时分秒)有哪些交换机可达,并且统计当前每个交换机有多少用户端的物理端口是 Up 的(级联端口不算),以及这 1 000 台交换机所有Up 的用户端物理端口的总数,并统计网络里的端口使用率(也就是物理端口的Up 率)。

实验思路

根据实验目的,我们可以写两个脚本,脚本 1 通过导入 pythonping 模块来扫描这 5 个网段下所有交换机的管理 IP 地址,看哪些管理 IP 地址是可达的。

因为子网掩码是 255.255.255.0,意味着每个网段下的管理 IP 地址的前三位都是固定不变的,只有最后一位会在 1~254 中变化。我们可以在第一个脚本中使用 for 循环来ping .1 到.254,然后将所有该网段下可达的交换机管理 IP 地址都写入并保存在一个名为 reachable_ip.txt 的文本文件中。

因为我们这里有 5 个连续的/24 的网段需要扫描(从 172.16.0.x 到 172.16.4.x),我们又可以在脚本 1 中再写一个 for 循环来连续 ping 这 5 个网段,然后把上一个ping .1 到.254 的 for 循环嵌入这一个 for 循环里,这样就能让 Python 一次性把 5 个/24 网段下总共 1270 个可用 IP 地址(254×5 = 1270)全部 ping 一遍。

在用脚本 1 生成 reachable_ip.txt 文件后,我们可以再写一个脚本 2 来读取该文本文件中所有可达的交换机的管理 IP 地址,依次登录所有这些可达的交换机,输入命令“show ip int brief | i up”查看有哪些端口是 Up 的,再配合正则表达式(re模块),在回显内容中匹配我们所要的用户端物理端口号(Gix/x/x),统计它们的总数,即可得到当前一个交换机有多少个物理端口是 Up 的。(注:因为“show ip int brief | i up”的回显内容里也会出现 10GB 的级联端口 Tex/x/x 及虚拟端口,比如 vlan 或者 loopback 端口,所以这里强调的是用正则表达式来匹配用户端物理端口 Gix/x/x)。

实验准备 - 脚本 1

(1)pyping 为第三方模块,使用前需要通过 pip 下载安装,安装完成后进入 Python 3.8编辑器,如果 import pythonping 没有报错,则说明安装成功,如下所示。

python实验6 pythonping综合实验_python实验

(2)在主机上创建一个新的文件夹,取名为 lab2,在该文件夹下创建实验 6 的脚本 1文件 lab2_1.py,如下所示。

python实验6 pythonping综合实验_统计up端口_02

实验代码 - 脚本 1

将下列代码写入脚本 lab2_1.py。

# 导入 pythonping 和 os 两个模块
# 在 pythonping 中,最核心的函数显然是 ping(),因为 pythonping 的模块名长度偏长,这里用 from…import…将 ping()函数导入后,后面调用时就能省去使用 pythonping.ping()完整函数路径的麻烦,体会到直接使用 ping()的便利
from pythonping import ping
import os

# 每次我们运行脚本 1,都不希望保留上一次运行脚本时生成的 reachable_ip.txt 文件,因为在有成千上万台设备的大型网络里,每时每刻可达交换机的数量都有可能在改变
# 这时可以用 os 模块下的 os.path.exists()方法来判断该文件是否存在,如果存在,则用os.remove()方法将该文件删除
# 这样可以保证每次运行脚本 1 时,reachabe_ip.txt 文件中只会包含本次运行脚本后所有可达的交换机管理 IP 地址
if os.path.exists('reachable_ip.txt'):
os.remove('reachable_ip.txt')

# 通过 range(5)和 range(1,255)分别创建两个整数列表来囊括管理 IP 地址的第三位和第四位,为后面的两个 for 循环做准备
third_octet=range(5)
last_octet=range(1,255)

for ip3 in third_octet:
for ip4 in last_octet:
ip='172.16.' +str(ip3)+'.'+str(ip4)
ping_result=ping(ip) # 配合ping()函数来依次ping这些管理IP
f=open('reachable_ip.txt','a')
if 'Reply' in str(ping_result):
print(ip+' is reachable.')
f.write(ip+"\n") # 将所有可达的管理 IP 地址以追加模式(a)写入reachable_ip.txt 文件
else:
print(ip+' is not reachable.')

f.close()

这里重点解释下 if 'Reply' in str(ping_result):的用法和原理。

在使用 os、subprocess 和 pyping 等模块做 ping 测试时,如果目标 IP 地址可达,则它们会返回整数 0;如果不可达,则返回非 0 的整数。

而 pythonping 不同,在 pythonping 中,ping()函数默认对目标 IP 地址 ping 4 次,当目标 IP 地址可达时,ping()函数返回的是“Reply from x.x.x.x, x bytes in xx.xx ms”;如果不可达,则返回的是“Request timed out”,如下所示。

python实验6 pythonping综合实验_ping_03

也许你会问:既然 pythonping 的 ping()函数返回的不再是 0 或非 0 的整数,那么我们怎么将上面代表目标 IP 地址可达的“Reply from x.x.x.x, x bytes in xx.xx ms”和代表目标IP 地址不可达的“Request timed out”通过 if 语句将可达的目标 IP 地址打印出来并写入reachable_ip.txt 文件呢?

也许你猜到用成员运算符 in 来判断返回值中是否有 Reply 和Request 这两个字符串。如果有 Reply,则说明目标可达;如果没有,则说明目标不可达,但前提是 ping()函数返回值的类型必须是字符串。

而实际情况是 ping()函数返回值的类型是一个叫作 pythonping.executor.ResponseList 的特殊类型,如下所示。

python实验6 pythonping综合实验_python实验_04

因此,必须通过 str()函数将它转换成字符串后才能使用成员运算符 in 来做判断(即这里的 str(ping_result)),否则 if 'Reply' in str(ping_result):会永远返回布尔值 False,表示目标不可达。

脚本 1 验证

(1)运行脚本 1 前,确认/root/lab2 文件夹下只有 lab2_1.py 这一个文件。

python实验6 pythonping综合实验_ping_05

(2)运行脚本 1,因为 1270 个管理 IP 地址实在太多,不方便截图演示,所示这里将last_octet = range(1,255)改为 last_octet = range(1,4),只 ping 每个网段下前 3 个管理 IP地址,也就是总共 15 个管理 IP 地址。运行脚本后的回显内容如下所示。

python实验6 pythonping综合实验_统计up端口_06

(3)再次查看/root/lab2 文件夹,可以看到这时多出来了一个 reachable_ip.txt 的文本文件,该文件正是脚本自动生成的,用来保存所有可达交换机的管理 IP 地址。可以看到只有 172.16.0.1 这一个可达的管理 IP 地址被写入 reachable_ip.txt,至此证明脚本 1 运行成功,如下所示。

python实验6 pythonping综合实验_统计up端口_07

实验准备 - 脚本 2

讲解脚本 2 之前,先来看下在一个 48 口的思科 3750 交换机里输入命令       show ip int brief | i up 后能得到什么样的回显内容,如下图所示。

python实验6 pythonping综合实验_python实验_08

上 图 是在 某个生 产 网 络中的 交换机 里得 到 的回 显 内 容。 可以看 到 , 除 了GigabitEthernet 用户端物理端口,还有 VLAN 虚拟端口和两个万兆的级联端口 Te1/0/1 和Te1/0/2。在实验目的中已经明确说明不考虑虚拟端口和级联端口,只统计共有多少用户端物理端口是 Up 的。

创建实验 2 的第二个脚本,将它取名为 lab2_2.py,如下所示。

python实验6 pythonping综合实验_python实验_09

实验代码 - 脚本 2

将下列代码写入脚本 lab2_2.py。

# 这里导入了 datetime 模块,这个 Python 内置的模块可以用来显示运行脚本时的系统日期和时间,因为实验目的里提到需要记录下运行脚本时的时间(精确到年月日和时分秒)
import paramiko
import time
import re
from datetime import datetime
import getpass
import socket

username=input('Enter your SSH username: ')
password=getpass.getpass('Enter your SSH password: ')

# 记录当前时间可以调用 datetime.now()方法,我们将它赋值给变量 now
# datetime.now()方法下面又包含了.year()(年)、.month()(月)、.day()(日)、.hour()(时)、.minute()(分)、.second()(秒)几个子方法,这里将“月-日-年”赋值给变量 date,将“时:分:秒”赋值给变量 time_now。
now=datetime.now()
date="%s-%s-%s" % (now.month, now.day, now.year)
time_now="%s:%s:%s" % (now.hour, now.minute, now.second)

switch_with_tacacs_issue=[] # 统计有哪些交换机的 TACACS 失效导致用户验证失败
switch_not_reachable=[] # 统计哪些交换机的管理 IP 地址不可达
tocal_number_of_up_port=0 # total_number_of_up_port 变量用来统计所有可达的交换机上状态为Up的端口的总数

iplist=open('reachable_ip.txt') # 用 open()函数打开脚本 1 创建的 reachable_ip.txt 文件
number_of_switch=len(iplist.readlines()) # 用 readlines()将其内容以列表形式返回,再配合 len()函数得到可达交换机的数量
tocal_number_of_ports=number_of_switch*48 # 因为每个交换机都有48个端口,所以通过交换机数量×48 可以得到端口总数(无论端口状态是否为 Up)。

iplist.seek(0) # 因为已经用 open()函数打开过一次 reachable_ip.txt 文件,所以要用 seek(0)回到文件的起始位置
for line in iplist.readlines(): # 这里需要注意用来应对交换机登录失败的问题,异常处理语句 try 要写在for 循环的下面
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)
print ("\n You have successfully connect to ",ip)

command=ssh_client.invoke_shell()
command.send('term len 0\n') # 因为是 48 口的交换机,show ip int brief | i up 的回显内容会比较长,无法一次性完整地显示,所以这里首先要用命令 term len 0 完整地显示所有的回显内容
command.send('show ip int b | i up\n')
time.sleep(1)
output=command.recv(65535)
#print(output.decode('ascii'))

search_up_port=re.findall(r'GigabitEthernet',output) # 因为我们只想统计有多少用户端的物理端口(GigabitEthernet)是 Up 的,所以可以用正则表达式的 findall()方法去精确匹配 GigabitEthernet,将 findall()返回的列表赋值给变量 search_up_port
number_of_up_port=len(search_up_port) # 通过 len(search_up_port)即可得到 Up 的物理端口的数量,并将该数量赋值给变量 number_of_up_port
print(ip+" has "+str(number_of_up_port)+" ports up.") # 打印出每个交换机有多少个用户端物理端口是 Up的
tocal_number_of_up_port+=number_of_up_port # 将每个交换机 Up 的物理端口数量累加起来,最后得到整个网络下 Up 的物理端口的总数
except Paramiko.ssh_exception.AuthenticationException:
print("TACACS is not working for "+ip+".")
switch_with_tacacs_issue.append(ip)
except socket.error:
print(ip+" is not reachable.")
switch_not_reachable.append(ip)
iplist.close()

# 将统计信息各种打印出来
print("\n")
print("There are tocally "+str(tocal_number_of_ports)+" ports available in the network.")
print(str(tocal_number_of_up_port)+" ports are currently up.")
print("Port up rate is %.2f%%" % (tocal_number_of_up_port/float(tocal_number_of_ports)*100))
print('\nTACACS is not working for below switches: ')
for i in switch_with_tacacs_issue:
print(i)
print('\nBelow switches are not reachable: ')
for i in switch_not_reachable:
print(i)

# 另外创建一个文件,通过 f = open(date + ".txt","a")将运行脚本时的日期用作该脚本的名字,将统计信息写入,方便以后调阅查看
f=open(date+".txt","a+")
f.write('As of '+date+" "+time_now) # 可以清晰直观地看到我们是在哪一天的几时几分几秒运行的脚本
f.write("\n\nThere are totally "+str(tocal_number_of_ports)+" ports available in the network.")
f.write("\n"+str(tocal_number_of_up_port)+" ports are currently up.")
f.write("\nPort up rate is %.2f%%" % (tocal_number_of_up_port/float(tocal_number_of_ports)*100))
f.write("\n**********************************************************************\n\n")
f.close()

为什么要用日期作为文件名?

这样做的好处是一旦运行脚本时的日期不同,脚本就会自动创建一个新的文件,比如 2018年 6 月 16 号运行了一次脚本,Python 创建了一个名为 6-16-2018.txt 的文件,如果第二天再运行一次脚本,Python 又会创建一个名为 6-17-2018.txt 的文件。如果在同一天里数次运行脚本,则多次运行的结果会以追加的形式写入同一个.txt 文件,不会创建新文件。

这么做可以让我们配合 Windows 的 Task Scheduler 或者 Linux 的 Crontab 来定期自动执行脚本,每天自动生成当天的端口使用量的统计情况,方便公司管理层随时观察网络里交换机的端口使用情况。

脚本 2 验证

(1)移动到/root/lab2,运行脚本 lab2_2.py,让脚本读取 reachable_ip.txt 文件自动登录所有可达的交换机(出于演示的目的,这里将之前除 172.16.0.1 外所有不可达的交换机全部开启,并将它们的管理 IP 地址写入 reachable_ip.txt),并查看每个交换机当前有多少端口是 Up 的,在最后给出统计数据,如下所示。

python实验6 pythonping综合实验_python实验_10

(2)再次查看/root/lab2 文件夹,可以看到这时多出来一个名为 4-22-2019.txt 的文本文件,该文件是脚本 2 自动生成的,用来保存每次运行脚本后的统计信息。文件名即运行该脚本的日期。

如下所示。

python实验6 pythonping综合实验_统计up端口_11

在 Linux 中配合 Crontab,在 Windows Server 中配合任务计划程序(Task Scheduler)定期每天运行该脚本,即可达到每天自动化监控交换机端口使用率的目的。