1. 程式人生 > >Python在大型網路管理中的應用案例

Python在大型網路管理中的應用案例

Python實驗執行環境:

作業系統: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

網路實驗拓撲

  • 要讓Python通過SSH遠端登陸網路裝置,主要有Paramiko和Netmiko兩種模組可以使用(telnet的話需要使用telnetlib模組,但是鑑於telnet的安全性,不建議使用,關於telnet的應用以後有機會再講)。Paramiko和Netmiko的區別在於後者是前者的命令簡化版本,且支援多廠商裝置,但是筆者本人更偏愛Paramiko,因此程式碼裡將使用Paramiko來SSH登陸裝置。
  • Paramiko並不是Python自帶的package,我們必須下載並安裝Paramiko,要安裝Paramiko,首先要安裝pip(yum install不支援), pip的作用是用來安裝和管理Python pacakges的,在CentOS中安裝Paramiko的步驟如下(因為是單純的實驗環境,我是直接用的root賬戶):

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是否安裝成功

實驗1:

實驗目的:

  1. 用Python實現SSH登陸單個交換機(192.168.2.11),為其loop0埠配置1.1.1.1 /32這個IP。
  2. 因為是第一個實驗,為求最簡單,最直觀的程式碼,這裡我們在程式碼裡預設IP,username, password,不使用raw_input()函式和getpass模組。
  • 執行程式碼前,首先確認S1上的配置,此時S1上的loop0埠並沒有IP

執行程式碼前S1配置

  • 在CentOS上建立一個名為lab1.py的檔案,將其改為可執行(否則等會兒你無法執行該script檔案),然後用vim開啟該檔案,然後放入下列程式碼

#!/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程式碼部分講解:

  • 除了Paramiko外,我們還import了time這個Python自帶的模組,它的作用後面會講到
  • ip, username, password這三個變數很直接明瞭,只需注意它們的型別必須是string(字串)。
  • sshclient = paramiko.SSHClient(), 呼叫paramiko的SSHClient()方法將其assign給ssh_client這個變數。顧名思義,這裡我們的CentOS主機是做SSH client,而SSH server則是我們要登陸的S1交換機(192.168.2.11)。
  • 預設情況下,Paramiko會拒絕任何未知的SSH public keys,這裡我們使用ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 來讓Paramiko接受來自SSH Server端(也就是S1)提供的public key。
  • ssh_client.connect(hostname=ip,username=username,password=password),很好理解,呼叫Paramiko的connect()函式,使用我們預設好的ip, username, password來登陸S1。
  • 如果登陸成功,用print "Sucessfully login to ", ip來提示使用者登入成功,並顯示所登陸的交換機的ip地址,這裡為192.168.2.11
  • command = ssh_client.invokeshell(),呼叫paramiko的invoke_shell()方法,將其assign給command這個變數。
  • 現在可以讓command配合send()這個函式來對交換機發號施令了,後面的命令就不講了,這些對網工來說應該是整個script裡面最熟悉的部分了。
  • time.sleep(1),前面提到了我們import了time這個模組。有時候系統執行script時會有延遲,它的作用是讓系統稍侯1秒鐘,再執行下面的語句。
  • output = command.recv(65535),python截圖本次執行script後的所有輸出記錄,將其assign給output這個變數。
  • print output, 再將output打印出來,這樣你在執行python的過程中就能清楚看到python sciprt對你的交換機做了些什麼。
  • ssh_client.close,最後記得養成好習慣,退出SSH。

下面執行這段程式碼,看看效果:

程式碼執行效果

執行成功!Python成功登入了192.168.2.11,幫我們執行了要配置的命令,現在再登入S1,看看變化, S1的loop 0埠已經成功配置了1.1.1.1 /32這個IP。

程式碼執行後S1配置

實驗2:

實驗目的:

  1. 配合getpass模組和raw_input()函式實現互動式的SSH使用者名稱和密碼輸入。
  2. 配合for loop同時給5臺交換機配置VLAN 10至VLAN 20。
  • 執行程式碼前,首先確認5臺交換機上的配置,確認它們都沒有VLAN 10至VLAN 20。

執行程式碼前S1配置

執行程式碼前S2配置

執行程式碼前S3配置

執行程式碼前S4配置

執行程式碼前S5配置

  • 在CentOS上建立一個名為lab2.py的檔案,將其改為可執行,然後用vim開啟該檔案,然後放入下列程式碼

#!/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程式碼部分講解:

  • 這裡import了getpass這個模組,用來提示使用者輸入密碼。它和raw_input()函式一樣,都是python的互動式功能,區別是,如果用rawinput()來提示輸入密碼的話,使用者輸入的密碼是明文可見的,如果你身邊坐了他人,密碼就這麼暴露了。而getpass輸入密碼時,則是不可見的,安全性很高,所以強烈建議使用getpass來輸入密碼,使用raw_input()來輸入使用者名稱
  • 由於這裡S1-S5五個交換機的ip是連續的,192.168.2.11 - 15, 這樣我們可以配合for i in range(11,16)做一個簡單的for loop,然後以此配合下一行程式碼ip = "192.168.2." + str(i)來實現迴圈(批量)登入交換機S1至S5。注意:這裡的i是整數,整數不能和字串相“+”,所以要用str(i)先將i轉化成字串。
  • for n in range (10,21): 同樣的道理,我們要建立VLAN 10 至 VLAN 20,VLAN id是連續的,所以這裡又可以配合一個簡單的for loop達到迴圈配置VLAN10 - 20。

執行程式碼來看效果

最後登陸所有交換機一一驗證,檢查程式碼是否建立了VLAN 10 至 20

實驗3:

實驗目的:

  1. 在生產環境中,交換機的管理ip地址基本不可能像實驗環境中這樣11到15連續的,有些交換機的管理ip甚至在不同的網段,這種情況下,我們就不能簡單的用for loop來迴圈ip地址的最後一段來登入交換機了。這裡我們要額外開一個文字檔案,把我們需要登入的交換機ip全部寫進去,然後用for loop配合open()函式來批量登入所有交換機。
  2. 用上面的方法登入所有交換機,開啟EIGRP .

開始實驗3前,我們需要做兩個準備:

  1. 把S5的管理地址從192.168.2.15改成192.168.2.55
  2. 建立一個名為ip_list.txt的檔案,把S1,S2,S3,S4,S5交換機的管理IP地址放進去

把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程式碼部分講解:

  • open("ip_list.txt", "r")來開啟我們實驗前建立好了的ip_list.txt這個儲存所有交換機管理地址的文件,後面的"r"表示只讀,可寫可不寫,因為預設就是它。
  • for line in f.readlines(): readlines()方法返回的是一個列表正好可以用for loop讀取ip_list.txt文件裡的每一行內容,每一行都是交換機的管理IP地址。
  • ip = line.strip(),用strip()去掉多於的空格然後把結果assign給ip這個變數。
  • f.close(), 檔案有開有關,養成好習慣,用完後記得關閉。也可以用with as的語句來寫,這樣不需要用close()來關閉已開啟的檔案,這個看個人喜好,初學者用簡單的 f = open(),f.close()就夠用了。

老規矩,執行程式碼前登陸所有交換機,確認目前沒有EIGRP開啟:

執行實驗3程式碼前S1配置

執行實驗3程式碼前S2配置

執行實驗3程式碼前S3配置

執行實驗3程式碼前S4配置

執行實驗3程式碼前S5配置

一切就緒後,執行程式碼看效果:

最後登陸所有交換機一一驗證EIGRP是否已經開啟:

實驗4:

前面的例子提到了,要使用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%)。實驗步驟:

  1. 建立lab4.py,這步就略過不表了。
  2. 首先建立兩個名為command_3750.txt和ip_3750.txt的文字檔案,分別用來儲存我們將要配置3750的QoS命令,以及所有3750交換機的IP地址。 imacs-iMac:book-example imac$ cat command_3750.txt mls qos queue-set output 1 buffers 15 25 40 20 imacs-iMac:book-example imac$ cat ip_3750.txt  192.168.100.11 192.168.100.22 192.168.100.33 同理,再建立兩個名為command_3850.txt和ip_3850.txt的文字檔案,分別用來儲存我們將要配置3850的QoS命令,以及所有3850交換機的IP地址。 imacs-iMac:book-example imac$ cat command_3850.txt configure terminal class-map match-any cos7 match cos 7 class-map match-any cos1 match cos 1 exit policy-map queue-buffer class cos7 bandwidth percent 10 queue-buffers ratio 15 class cos1 bandwidth percent 30 queue-buffers ratio 25 exit exit interface gi1/0/1 service-policy output queue-buffer end wr mem imacs-iMac:book-example imac$ cat ip_3850.txt  172.16.100.11 172.16.100.22 172.16.100.33 不過這時新的問題又來了,每次配備不同型號的裝置,我們必須手動修改open()函式所開啟的配置文字檔案以及ip地址檔案,比如在給3750做配置的時候,我們要open('command_3750.txt')以及open('ip_3750.txt'), 給3850做配置的時候,我們又要open('command_3850.txt')以及open('ip_3850.txt'),這樣一來二去修改配置指令碼的做法大大缺乏靈活性。 如果說只有兩種不同型號,不同命令格式的裝置還能應付的話,那麼當你的生產環境中有3750(IOS),3850(IOS-XE), Nexus 3k/5k/7k/9k (NX-OS), CRS3/ASR9K (IOS-XR)甚至其他廠商的裝置,而你又接到任務要對所有這些裝置同時修改某個共有的配置(比如網路新添加了某臺TACACS伺服器,要統一給所有裝置修改AAA配置,亦或者網路新添加了某臺NMS系統,要統一給所有裝置修改SNMP配置),因為不同OS的配置命令完全不同,這時你就能體會到痛苦了。這時可以用到sys.argv來解決這個問題。實驗4程式碼:
# -- 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程式碼部分講解:

  • 因為要用到sys.argv,所以這裡import了sys模組。
  • 「argv」是「argument variable」引數變數的簡寫形式,這個變數返回的是一個列表,argv[0] 一般是被呼叫的指令碼的檔名或全路徑,從argv[1]開始就是傳入的資料了。舉個例子,我們現在返回linux,執行下面這個命令:

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

  • 由此可見,配合sys.argv,我們可以很靈活地選用我們指令碼需要呼叫的文字檔案,而無需反反覆覆地修改指令碼程式碼。

最後執行程式碼看效果:

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]
 

實驗5:

在網路裝置數量超過千臺甚至上萬臺的大型企業網中,難免會遇到某些裝置管理IP不通,SSH連線失敗的情況,裝置數量越多,這種情況發生的機率越大。這個時候如果你想用Python批量配置所有的裝置,就一定要注意這種情況,很有可能你的指令碼跑了還不到一半就因為中間某一個連線不通的裝置而停止了。比如說你有3000臺交換機需要統一更改本地使用者名稱和密碼,前500臺交換機的連通性都沒問題,第501臺交換機因為某個網路問題導致管理IP不可達,SSH連不上了,這個時候Python會返回一個socket.error: [Errno 10060] A connectionattempt failed because the connected party did not properly respond after aperiod of time, or established connection failed because connected host hasfailed 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程式碼部分講解:

  • 為了使用try-except來應對網路裝置不可達引起的socket.error,這裡我們必須import socket
  • 建立兩個空列表,取名為switch_with_authentication_issueswitch_not_reachable, 分別在指令碼最後配合for迴圈來統計有哪些裝置是因為認證問題無法登入,有哪些是因為裝置本身不可達而無法登入。
  • 在for迴圈下使用try-except, 用excpet paramiko.ssh_exception.AuthenticationException:來應對使用者名稱/密碼不匹配時返回的錯誤程式碼,將出現該錯誤的交換機的管理IP地址用.append(ip)方法放入switch_with_authentication_issue這個列表中,同理用except socket.error:來應對交換機不可達時返回的錯誤程式碼,將出現該錯誤的交換機的管理IP地址用append(ip)方法放入switch_not_reachable這個列表中。

最後執行程式碼看效果:

注意重點部分已經用紅線標註:

  1. 本應出現的paramiko.ssh_exception.AuthenticationException錯誤已經被User authentication failed for 192.168.2.13取代,並且python指令碼並未就此停止執行,而是繼續嘗試登入下一個交換機S4,也就是192.168.2.14。
  2. 本應出現的paramiko.ssh_exception.AuthenticationException:Authentication failed錯誤已經被192.168.2.14 is not reachable取代。並且python指令碼並未就此停止執行,而是繼續嘗試登入下一個交換機S5,也就是192.168.2.15。
  3. 在指令碼的最後標註出了有哪些交換機出現了使用者認證失敗的情況,有哪些交換機出現了不可達的情況。