進行網路運維,必須對網路拓撲情況進行詳細的掌握,但是網路改動後,更新網路拓撲比較繁瑣,維護人員容易懈怠,久而久之,透過人工繪製的網路拓撲很容易與現有網路出現偏差。
現在,可以透過python 豐富的庫,結合CDP鄰居資訊,自動繪製網路拓撲資訊,以下是實現思路:
1、登入裝置,獲取鄰居資訊;
工具:python(telnetlib、paramiko、netmiko庫)
2、篩選需要的資訊,裝置本地IP,本地名稱,本地介面,對端裝置名稱,對端介面,對端IP
工具:python(textfsm、json庫)
3、根據獲取進行進行畫圖
工具:python(N2G庫)
文件說明:https://n2g.readthedocs.io/en/latest/diagram_plugins/DrawIo%20Module.html
4、調整圖形。
一、透過python登入交換機裝置的案例很多,在此不再贅述,各位可以在網上找到透過telnet、ssh的方式登入交換機,根據實際情況進行調整;這裡後續透過telnet方式登入裝置,輸入show cdp nei detali 獲取資訊。
二、鄰居資訊處理
2.1獲取鄰居資訊
輸入命令後,獲取交換機鄰居資訊如下:
1 QIA.JSJZX.JKS>show cdp nei 2 ------------------------- 3 Device ID: qia.b3.net.test.sw01 4 Entry address(es): 5 IP address: 192.17.190.225 6 Platform: cisco WS-C2960X-24TS-L, Capabilities: Switch IGMP 7 Interface: GigabitEthernet1/0/24, Port ID (outgoing port): GigabitEthernet1/0/23 8 Holdtime : 126 sec 9 10 Version : 11 Cisco IOS Software, C2960X Software (C2960X-UNIVERSALK9-M), Version 15.2(2)E6, RELEASE SOFTWARE (fc1) 12 Technical Support: http://www.cisco.com/techsupport 13 Copyright (c) 1986-2016 by Cisco Systems, Inc. 14 Compiled Fri 16-Dec-16 21:27 by prod_rel_team 15 16 advertisement version: 2 17 Protocol Hello: OUI=0x00000C, Protocol ID=0x0112; payload len=27, value=00000000FFFFFFFF010221FF000000000000F87B20311580FF0000 18 VTP Management Domain: '' 19 Native VLAN: 1 20 Duplex: full 21 Power Available TLV: 22 23 Power request id: 0, Power management id: 1, Power available: 0, Power management level: -1 24 Management address(es): 25 IP address: 192.17.190.225 26 27 ------------------------- 28 Device ID: qia.b3.net.test.sw03 29 Entry address(es): 30 IP address: 192.17.191.132 31 Platform: cisco WS-C2960S-48TD-L, Capabilities: Switch IGMP 32 Interface: GigabitEthernet1/0/23, Port ID (outgoing port): GigabitEthernet1/0/48 33 Holdtime : 134 sec 34 35 Version : 36 Cisco IOS Software, C2960S Software (C2960S-UNIVERSALK9-M), Version 12.2(55)SE7, RELEASE SOFTWARE (fc1) 37 Technical Support: http://www.cisco.com/techsupport 38 Copyright (c) 1986-2013 by Cisco Systems, Inc. 39 Compiled Mon 28-Jan-13 10:28 by prod_rel_team 40 41 advertisement version: 2 42 Protocol Hello: OUI=0x00000C, Protocol ID=0x0112; payload len=27, value=00000000FFFFFFFF010221FF000000000000B000B4865F80FF0000 43 VTP Management Domain: 'default' 44 Native VLAN: 1 45 Duplex: full 46 Power Available TLV: 47 48 Power request id: 0, Power management id: 1, Power available: 0, Power management level: -1 49 Management address(es): 50 IP address: 192.19.191.132
上述為鄰居資訊欄位,標紅部分為需要提取的資訊內容,下面透過textfsm工具進行提取,獲取到資訊內容分別為:本機名稱,鄰居主機名稱、鄰居主機IP、本機介面、鄰居介面;以下是自定義的textfsm模板,檔案儲存為cisco_tfm.template。
1 Value Local_hostname (\S+) 2 Value Key Local_port (\S+) 3 Value Device_name (\S+) 4 Value Device_module (\S+) 5 Value Device_IP (\S+) 6 Value Required Device_port (\S+) 7 8 9 Start 10 ^${Local_hostname}> 11 ^Device ID: ${Device_name} 12 ^\s+IP\saddress: ${Device_IP} 13 ^Platform: cisco ${Device_module}, 14 ^Interface: ${Local_port},\s+Port\sID\s\(outgoing\sport\):\s${Device_port} -> Record
程式碼實現:
1 import textfsm 2 3 4 #cisco :show cdp nei detail 5 data = """ 6 QIA.JSJZX.JKS>show cdp nei 7 ------------------------- 8 Device ID: qia.b3.net.test.sw01 9 Entry address(es): 10 IP address: 192.17.190.225 11 Platform: cisco WS-C2960X-24TS-L, Capabilities: Switch IGMP 12 Interface: GigabitEthernet1/0/24, Port ID (outgoing port): GigabitEthernet1/0/23 13 Holdtime : 126 sec 14 15 Version : 16 Cisco IOS Software, C2960X Software (C2960X-UNIVERSALK9-M), Version 15.2(2)E6, RELEASE SOFTWARE (fc1) 17 Technical Support: http://www.cisco.com/techsupport 18 Copyright (c) 1986-2016 by Cisco Systems, Inc. 19 Compiled Fri 16-Dec-16 21:27 by prod_rel_team 20 21 advertisement version: 2 22 Protocol Hello: OUI=0x00000C, Protocol ID=0x0112; payload len=27, value=00000000FFFFFFFF010221FF000000000000F87B20311580FF0000 23 VTP Management Domain: '' 24 Native VLAN: 1 25 Duplex: full 26 Power Available TLV: 27 28 Power request id: 0, Power management id: 1, Power available: 0, Power management level: -1 29 Management address(es): 30 IP address: 192.17.190.225 31 32 ------------------------- 33 Device ID: qia.b3.net.test.sw03 34 Entry address(es): 35 IP address: 192.17.191.132 36 Platform: cisco WS-C2960S-48TD-L, Capabilities: Switch IGMP 37 Interface: GigabitEthernet1/0/23, Port ID (outgoing port): GigabitEthernet1/0/48 38 Holdtime : 134 sec 39 40 Version : 41 Cisco IOS Software, C2960S Software (C2960S-UNIVERSALK9-M), Version 12.2(55)SE7, RELEASE SOFTWARE (fc1) 42 Technical Support: http://www.cisco.com/techsupport 43 Copyright (c) 1986-2013 by Cisco Systems, Inc. 44 Compiled Mon 28-Jan-13 10:28 by prod_rel_team 45 46 advertisement version: 2 47 Protocol Hello: OUI=0x00000C, Protocol ID=0x0112; payload len=27, value=00000000FFFFFFFF010221FF000000000000B000B4865F80FF0000 48 VTP Management Domain: 'default' 49 Native VLAN: 1 50 Duplex: full 51 Power Available TLV: 52 53 Power request id: 0, Power management id: 1, Power available: 0, Power management level: -1 54 Management address(es): 55 IP address: 192.17.191.132 56 """ 57 58 template_file = ".\cisco_tfm.template" 59 60 with open(template_file) as template: 61 fsm = textfsm.TextFSM(template) 62 result = fsm.ParseText(data) 63 # print(fsm.header) 64 print(result) 65 # print(len(result))
輸出內容:
['Local_hostname', 'Local_port', 'Device_name', 'Device_module', 'Device_IP', 'Device_port'] [['QIA.JSJZX.JKS', 'GigabitEthernet1/0/24', 'qia.b3.net.test.sw01', 'WS-C2960X-24TS-L', '192.17.190.225', 'GigabitEthernet1/0/23'],
['', 'GigabitEthernet1/0/23', 'qia.b3.net.test.sw03', 'WS-C2960S-48TD-L', '192.17.191.132', 'GigabitEthernet1/0/48']]
注意:以上內容中,本地主機名僅在第一個鄰居資訊表中顯示,所以需要當前裝置鄰居進行資料進行格式化,因此就需要用到json庫。
2.2 鄰居資訊資料格式化
透過資料格式轉換,可以得到當前主機IP、主機名稱、主機介面;鄰居IP、鄰居名稱、鄰居介面資訊;
程式碼實現:
1 import json 2 3 ip_address = '192.168.1.1' 4 # hostname = 'zh_cisco_2960' 5 6 cdp_data = [ 7 ['9QI.JSJZX.JKS', 'GigabitEthernet1/0/24', '9qi.b3.net.test.sw01', 'WS-C2960X-24TS-L', '172.17.190.225', 'GigabitEthernet1/0/23'], 8 ['', 'GigabitEthernet1/0/23', '9qi.b3.net.test.sw03', 'WS-C2960S-48TD-L', '172.17.191.132', 'GigabitEthernet1/0/48'] 9 ] 10 11 def data_format(ip_address,cdp_data): 12 13 hostname = cdp_data[0][0]#二層列表格式,獲取主機名 14 result_data = { 15 ip_address: { 16 hostname: {item[1]: item[2:] for item in cdp_data} 17 } 18 } 19 json_data = json.dumps(result_data, indent=2) 20 21 return json_data 22 # return the JSON formatted string 23 24 if __name__ == "__main__": 25 json_data1 = data_format(ip_address,cdp_data) 26 print(json_data1)
輸出資訊:
1 { 2 "192.168.1.1": {#當前裝置IP 3 "QI.JSJZX.JKS": {#當前裝置名稱 4 "GigabitEthernet1/0/24": [#本地介面, 5 "qia.b3.net.test.sw01",#鄰居資訊 6 "WS-C2960X-24TS-L", 7 "192.17.190.225", 8 "GigabitEthernet1/0/23" 9 ], 10 "GigabitEthernet1/0/23": [ 11 "qia.b3.net.test.sw03", 12 "WS-C2960S-48TD-L", 13 "192.17.191.132", 14 "GigabitEthernet1/0/48" 15 ] 16 } 17 } 18 }
格式化以上鄰居資料後,便於後續對資料進行遍歷,在第三步進行讀取資料進行增加節點。
遍歷資料:
1 import json 2 3 # 讀取 JSON 資料 4 json_data = ''' 5 { 6 "192.168.1.1": { 7 "QIA.JSJZX.JKS": { 8 "GigabitEthernet1/0/24": [ 9 "qia.b3.net.test.sw01", 10 "WS-C2960X-24TS-L", 11 "192.17.190.225", 12 "GigabitEthernet1/0/23" 13 ], 14 "GigabitEthernet1/0/23": [ 15 "qia.b3.net.test.sw03", 16 "WS-C2960S-48TD-L", 17 "192.17.191.132", 18 "GigabitEthernet1/0/48" 19 ] 20 } 21 } 22 } 23 24 ''' 25 26 # 解析 JSON 資料 27 parsed_data = json.loads(json_data) 28 29 # 遍歷資料 30 for ip_address, inner_data in parsed_data.items(): 31 print(f"IP Address: {ip_address}")# 獲取主機IP地址 32 # print(f"host: {inner_data}") 33 34 for hostname, cdp_data in inner_data.items(): 35 print(f"Hostname: {hostname}") #獲取主機名稱 36 # print(type(cdp_data)) 37 38 for key, values in cdp_data.items(): 39 print(values)
這裡在每個節點獲取到鄰居資訊,就可以根據資訊,在拓撲圖中增加節點資訊了。
三、進行畫圖、
3.1 小試牛刀
網路拓撲圖中, 最重要的資訊就是節點和互聯線路,其他都為輔助資訊
在官方文件中,已經有詳細的說明可以增加節點(addnode)、增加連線(addlink);在這個兩個功能中,還有其他的選項,可以補充增加,官方提供的方法,可以透過help 檢視文件說明help(N2G.plugins.diagrams.N2G_DrawIO.drawio_diagram)
這裡只需要重點檢視Quick start部分增加節點和連線就可以了。
1 from N2G import drawio_diagram 2 3 diagram = drawio_diagram()# 4 diagram.add_diagram("Page-1") 5 diagram.add_node(id="R1")#增加節點 6 diagram.add_node(id="R2")#增加節點 7 diagram.add_link("R1", "R2", label="DF", src_label="Gi1/1", trgt_label="GE23")#增加節點之間的連線,標籤名稱,src_lable和trgt_lable 可以用來標註埠 8 diagram.layout(algo="kk")#圖層,不重要 9 diagram.dump_file(filename="Sample_graph.drawio", folder="./Output/")#儲存拓撲圖
以上示例是官方最簡單的畫圖程式,自己可以多增加幾個節點進行練習。
但是在網路情況裡,對每個鄰居節點都登入檢測檢查鄰居資訊時,會遇到同一個連線,在兩臺裝置上都能發現,那麼建立節點和連線時會出現什麼情況呢?
這裡官方有說明,如果發現節點已經存在就直接跳過(也可以自定義),這樣我們再寫程式上就簡單很多,連結裡有說明。
3.2 完成程式輸出
1 from N2G import drawio_diagram 2 import textfsm 3 import json 4 import telnetlib 5 import time 6 7 8 file_path = './host_1218.txt' 9 #主機IP清單,格式如下: 10 #cisco 192.168.1.1 cisco cisco 11 12 13 template_file = "./cisco_tfm.template" 14 style_cisco = "verticalLabelPosition=bottom;html=1;verticalAlign=top;aspect=fixed;align=center;pointerEvents=1;shape=mxgraph.cisco19.rect;prIcon=l2_switch;fillColor=#FAFAFA;strokeColor=#005073;" 15 # 圖示格式 16 17 def data_format(ip_address,cdp_data): 18 19 hostname = cdp_data[0][0]#二層列表格式,獲取主機名 20 result_data = { 21 ip_address: { 22 hostname: {item[1]: item[2:] for item in cdp_data} 23 } 24 } 25 json_data = json.dumps(result_data, indent=2) 26 27 return json_data 28 # return the JSON formatted string 29 30 31 def cisco_telent(ip, username, password, cmd): 32 # 建立Telnet連線 33 tn = telnetlib.Telnet(ip) 34 time.sleep(0.1) 35 36 tn.read_until(b"Username:") 37 tn.write(username.encode('ascii') + b"\n") 38 39 tn.read_until(b"Password: ") 40 tn.write(password.encode('ascii') + b"\n") 41 tn.write(b"terminal length 0\n") 42 43 tn.write(cmd.encode('ascii') + b"\n") 44 45 time.sleep(0.5) 46 tn.write(b"exit\n") 47 # # 讀取輸出並列印 48 output = tn.read_very_eager().decode('ascii') 49 50 tn.close() # 關閉連線 51 return output 52 53 54 diagram = drawio_diagram() 55 diagram.add_diagram("Page-1") 56 57 host_info_dict = {} 58 59 node_num = 0 60 #節點計數 61 # 開啟檔案進行讀取 62 with open(file_path, 'r') as file: 63 for line in file: 64 # 分割每一行以獲取主機資訊 65 host_info = line.strip().split() 66 67 # 檢查是否有足夠的資訊 68 if len(host_info) == 4: 69 device_type, ip, username, password = host_info 70 71 # 構建裝置字典 72 device = { 73 'device_type': device_type, 74 'ip': ip, 75 'username': username, 76 'password': password, 77 } 78 79 try: 80 # 執行telnet 81 82 cmd = 'show cdp neighbors detail' 83 84 command_output = cisco_telent(ip, username, password, cmd) 85 with open(template_file) as template: 86 #開啟模板,進行資料提取 87 fsm = textfsm.TextFSM(template) 88 result = fsm.ParseText(command_output) 89 #列表格式資料,需要進行轉換 90 91 nei_info_format = data_format(ip,result) 92 # print(nei_info_format) 93 parsed_data = json.loads(nei_info_format) 94 95 for ip_address, inner_data in parsed_data.items(): 96 for local_hostname, cdp_data in inner_data.items(): 97 if local_hostname not in host_info_dict: 98 #節點不存在,進行建立節點並儲存節點資訊 99 diagram.add_node(id=ip_address,name= local_hostname, style =style_cisco, width=60, height=60) 100 #增加本機節點 101 node_num += 1 102 # print(ip_address, local_hostname) 103 host_info_dict[local_hostname] = ip_address 104 # print(host_info_dict) 節點主機名和IP資訊儲存,後續進行校驗節點是否已經存在 105 106 for src_label, values in cdp_data.items(): 107 nei_host_name, nei_host_module, nei_host_id, trgt_label = values 108 #如果鄰居節點不存在,直接建立;如果節點已經存在,則鄰居ID資訊要更新,更換成已經存在節點的IP地址() 109 if nei_host_name not in host_info_dict: 110 diagram.add_node(id=nei_host_id,name=nei_host_name,style=style_cisco, width=60, height=60) 111 #根據鄰居資訊,增加鄰居節點 112 host_info_dict[nei_host_name] = nei_host_id 113 print("Add node " + nei_host_id + "secuessed.") 114 node_num += 1 115 diagram.add_link(ip_address, nei_host_id, src_label=src_label, trgt_label=trgt_label) 116 else: 117 nei_host_id = host_info_dict[nei_host_name] 118 diagram.add_link(ip_address, nei_host_id, src_label=src_label, trgt_label=trgt_label) 119 else: 120 print('節點已存在:' + local_hostname + ip_address) 121 #節點存在,檢查鄰居資訊,根據鄰居資訊進行判斷,是否新增鄰居節點 122 for src_label, values in cdp_data.items(): 123 nei_host_name, nei_host_module, nei_host_id, trgt_label = values 124 if nei_host_name not in host_info_dict: 125 diagram.add_node(id=nei_host_id, name=nei_host_name, style=style_cisco, width=60, height=60) 126 #根據鄰居資訊,增加鄰居節點 127 host_info_dict[nei_host_name] = nei_host_id 128 print("Add node " + nei_host_id + "secuessed.") 129 node_num += 1 130 diagram.add_link(ip_address, nei_host_id, src_label=src_label, trgt_label=trgt_label) 131 else: 132 nei_host_id = host_info_dict[nei_host_name] 133 diagram.add_link(ip_address, nei_host_id, src_label=src_label, trgt_label=trgt_label) 134 135 # print("Add node " + ip_address + " sucessed.") 136 137 except Exception as e: 138 print(f"Failed to connect to {ip}: {e}") 139 140 else: 141 print(f"Invalid line in the file: {line.strip()}") 142 print(node_num) 143 print(host_info_dict,len(host_info_dict)) 144 diagram.dump_file(filename="Sample_campus_00.drawio", folder="./Output/")
以上指令碼,除路徑本人修改過以外,整個程式在網路環境中進行的驗證。
說明:
1、在第57行,建立了1個字典,用來儲存發現的鄰居資料,主機名、主機名稱,用來後續對新發現節點進行判斷。為什麼還需要進行判斷?
因為當一臺裝置上有多個IP地址時,與之互聯的裝置鄰居資訊顯示的IP資訊可能是不一樣的,所以再次透過主機名再次進行判斷;當然這裡也不是很嚴謹,因為裝置名稱可能存在全域性不唯一的情況,所以在cisco NX-OS中,CDP鄰居資訊會顯示裝置的序列號,這樣就避免前面的情況。
2、因為網路規模較大,輸出的拓撲檔案也比較大,所以在第59行,增加了一個計數,在每增加1個節點,都進行一個儲存資訊,最終輸出看有多少網路裝置。
3、為什麼add_node 方法中有非官方說明的欄位“name”.
答:非標準欄位,生成的拓撲圖會以屬性欄位描述節點,滑鼠放到節點上就可顯示,不會直接顯示出來,包括前面已經提取到的裝置型號,也可以透過增加欄位module在拓撲圖中隱性的展示;右鍵成編輯節點資料,即可檢視或編輯資料
四、因為在程式中沒有指定每個節點的座標位置,所以生成的拓撲圖,所有的圖示節點都是在預設位置,所以會出現重疊的情況,只需要在調整圖形-佈局,選擇“垂直流“就可自動重新排列。