• Cisco 交换机利用CDP数据自动绘制网络拓扑图[drawio]-实践


    进行网络运维,必须对网络拓扑情况进行详细的掌握,但是网络改动后,更新网络拓扑比较繁琐,维护人员容易懈怠,久而久之,通过人工绘制的网络拓扑很容易与现有网络出现偏差。

    现在,可以通过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)
    Json_CDPInfo

     输出信息:

     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 }
    View Code

    格式化以上邻居数据后,便于后续对数据进行遍历,在第三步进行读取数据进行增加节点。

    遍历数据:

     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)
    View Code

    这里在每个节点获取到邻居信息,就可以根据信息,在拓扑图中增加节点信息了。

    三、进行画图、

    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/")
    draw_topology_N2G

     

    以上脚本,除路径本人修改过以外,整个程序在网络环境中进行的验证。

    说明:

    1、在第57行,创建了1个字典,用来保存发现的邻居数据,主机名、主机名称,用来后续对新发现节点进行判断。为什么还需要进行判断?

        因为当一台设备上有多个IP地址时,与之互联的设备邻居信息显示的IP信息可能是不一样的,所以再次通过主机名再次进行判断;当然这里也不是很严谨,因为设备名称可能存在全局不唯一的情况,所以在cisco NX-OS中,CDP邻居信息会显示设备的序列号,这样就避免前面的情况。

    2、因为网络规模较大,输出的拓扑文件也比较大,所以在第59行,增加了一个计数,在每增加1个节点,都进行一个保存信息,最终输出看有多少网络设备。

    3、为什么add_node 方法中有非官方说明的字段“name”.

        答:非标准字段,生成的拓扑图会以属性字段描述节点,鼠标放到节点上就可显示,不会直接显示出来,包括前面已经提取到的设备型号,也可以通过增加字段module在拓扑图中隐性的展示;右键成编辑节点数据,即可查看或编辑数据

     四、因为在程序中没有指定每个节点的坐标位置,所以生成的拓扑图,所有的图标节点都是在默认位置,所以会出现重叠的情况,只需要在调整图形-布局,选择“垂直流“就可自动重新排列。

     

  • 相关阅读:
    学生DW静态网页设计——html+css+javascript+jquery+bootstarp马尔代夫旅游响应式网站
    vue、react和小程序的状态管理
    【跟小嘉学 Apache Flink】二、Flink 快速上手
    9. SQL中Insert into/Update/Delete的用法
    分布式系统常用的模式
    ARMAAAAA
    【FastCAE源码阅读9】鼠标框选网格、节点的实现
    Cron表达式的Js插件
    tiup dm audit
    LINQ详解二(C#)
  • 原文地址:https://www.cnblogs.com/niu525/p/17925716.html