经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Linux/Shell » 查看文章
Cisco 交换机利用CDP数据自动绘制网络拓扑图[drawio]-实践
来源:cnblogs  作者:颜天高佑  时间:2023/12/26 9:52:37  对本文有异议

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

现在,可以通过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.  
  1. 1 QIA.JSJZX.JKS>show cdp nei
  2. 2 -------------------------
  3. 3 Device ID: qia.b3.net.test.sw01
  4. 4 Entry address(es):
  5. 5 IP address: 192.17.190.225
  6. 6 Platform: cisco WS-C2960X-24TS-L, Capabilities: Switch IGMP
  7. 7 Interface: GigabitEthernet1/0/24, Port ID (outgoing port): GigabitEthernet1/0/23
  8. 8 Holdtime : 126 sec
  9. 9
  10. 10 Version :
  11. 11 Cisco IOS Software, C2960X Software (C2960X-UNIVERSALK9-M), Version 15.2(2)E6, RELEASE SOFTWARE (fc1)
  12. 12 Technical Support: http://www.cisco.com/techsupport
  13. 13 Copyright (c) 1986-2016 by Cisco Systems, Inc.
  14. 14 Compiled Fri 16-Dec-16 21:27 by prod_rel_team
  15. 15
  16. 16 advertisement version: 2
  17. 17 Protocol Hello: OUI=0x00000C, Protocol ID=0x0112; payload len=27, value=00000000FFFFFFFF010221FF000000000000F87B20311580FF0000
  18. 18 VTP Management Domain: ''
  19. 19 Native VLAN: 1
  20. 20 Duplex: full
  21. 21 Power Available TLV:
  22. 22
  23. 23 Power request id: 0, Power management id: 1, Power available: 0, Power management level: -1
  24. 24 Management address(es):
  25. 25 IP address: 192.17.190.225
  26. 26
  27. 27 -------------------------
  28. 28 Device ID: qia.b3.net.test.sw03
  29. 29 Entry address(es):
  30. 30 IP address: 192.17.191.132
  31. 31 Platform: cisco WS-C2960S-48TD-L, Capabilities: Switch IGMP
  32. 32 Interface: GigabitEthernet1/0/23, Port ID (outgoing port): GigabitEthernet1/0/48
  33. 33 Holdtime : 134 sec
  34. 34
  35. 35 Version :
  36. 36 Cisco IOS Software, C2960S Software (C2960S-UNIVERSALK9-M), Version 12.2(55)SE7, RELEASE SOFTWARE (fc1)
  37. 37 Technical Support: http://www.cisco.com/techsupport
  38. 38 Copyright (c) 1986-2013 by Cisco Systems, Inc.
  39. 39 Compiled Mon 28-Jan-13 10:28 by prod_rel_team
  40. 40
  41. 41 advertisement version: 2
  42. 42 Protocol Hello: OUI=0x00000C, Protocol ID=0x0112; payload len=27, value=00000000FFFFFFFF010221FF000000000000B000B4865F80FF0000
  43. 43 VTP Management Domain: 'default'
  44. 44 Native VLAN: 1
  45. 45 Duplex: full
  46. 46 Power Available TLV:
  47. 47
  48. 48 Power request id: 0, Power management id: 1, Power available: 0, Power management level: -1
  49. 49 Management address(es):
  50. 50 IP address: 192.19.191.132

  上述为邻居信息字段,标红部分为需要提取的信息内容,下面通过textfsm工具进行提取,获取到信息内容分别为:本机名称,邻居主机名称、邻居主机IP、本机接口、邻居接口;以下是自定义的textfsm模板,文件保存为cisco_tfm.template。

  1. 1 Value Local_hostname (\S+)
  2. 2 Value Key Local_port (\S+)
  3. 3 Value Device_name (\S+)
  4. 4 Value Device_module (\S+)
  5. 5 Value Device_IP (\S+)
  6. 6 Value Required Device_port (\S+)
  7. 7
  8. 8
  9. 9 Start
  10. 10 ^${Local_hostname}>
  11. 11 ^Device ID: ${Device_name}
  12. 12 ^\s+IP\saddress: ${Device_IP}
  13. 13 ^Platform: cisco ${Device_module},
  14. 14 ^Interface: ${Local_port},\s+Port\sID\s\(outgoing\sport\):\s${Device_port} -> Record

 代码实现:

  1. 1 import textfsm
  2. 2
  3. 3
  4. 4 #cisco :show cdp nei detail
  5. 5 data = """
  6. 6 QIA.JSJZX.JKS>show cdp nei
  7. 7 -------------------------
  8. 8 Device ID: qia.b3.net.test.sw01
  9. 9 Entry address(es):
  10. 10 IP address: 192.17.190.225
  11. 11 Platform: cisco WS-C2960X-24TS-L, Capabilities: Switch IGMP
  12. 12 Interface: GigabitEthernet1/0/24, Port ID (outgoing port): GigabitEthernet1/0/23
  13. 13 Holdtime : 126 sec
  14. 14
  15. 15 Version :
  16. 16 Cisco IOS Software, C2960X Software (C2960X-UNIVERSALK9-M), Version 15.2(2)E6, RELEASE SOFTWARE (fc1)
  17. 17 Technical Support: http://www.cisco.com/techsupport
  18. 18 Copyright (c) 1986-2016 by Cisco Systems, Inc.
  19. 19 Compiled Fri 16-Dec-16 21:27 by prod_rel_team
  20. 20
  21. 21 advertisement version: 2
  22. 22 Protocol Hello: OUI=0x00000C, Protocol ID=0x0112; payload len=27, value=00000000FFFFFFFF010221FF000000000000F87B20311580FF0000
  23. 23 VTP Management Domain: ''
  24. 24 Native VLAN: 1
  25. 25 Duplex: full
  26. 26 Power Available TLV:
  27. 27
  28. 28 Power request id: 0, Power management id: 1, Power available: 0, Power management level: -1
  29. 29 Management address(es):
  30. 30 IP address: 192.17.190.225
  31. 31
  32. 32 -------------------------
  33. 33 Device ID: qia.b3.net.test.sw03
  34. 34 Entry address(es):
  35. 35 IP address: 192.17.191.132
  36. 36 Platform: cisco WS-C2960S-48TD-L, Capabilities: Switch IGMP
  37. 37 Interface: GigabitEthernet1/0/23, Port ID (outgoing port): GigabitEthernet1/0/48
  38. 38 Holdtime : 134 sec
  39. 39
  40. 40 Version :
  41. 41 Cisco IOS Software, C2960S Software (C2960S-UNIVERSALK9-M), Version 12.2(55)SE7, RELEASE SOFTWARE (fc1)
  42. 42 Technical Support: http://www.cisco.com/techsupport
  43. 43 Copyright (c) 1986-2013 by Cisco Systems, Inc.
  44. 44 Compiled Mon 28-Jan-13 10:28 by prod_rel_team
  45. 45
  46. 46 advertisement version: 2
  47. 47 Protocol Hello: OUI=0x00000C, Protocol ID=0x0112; payload len=27, value=00000000FFFFFFFF010221FF000000000000B000B4865F80FF0000
  48. 48 VTP Management Domain: 'default'
  49. 49 Native VLAN: 1
  50. 50 Duplex: full
  51. 51 Power Available TLV:
  52. 52
  53. 53 Power request id: 0, Power management id: 1, Power available: 0, Power management level: -1
  54. 54 Management address(es):
  55. 55 IP address: 192.17.191.132
  56. 56 """
  57. 57
  58. 58 template_file = ".\cisco_tfm.template"
  59. 59
  60. 60 with open(template_file) as template:
  61. 61 fsm = textfsm.TextFSM(template)
  62. 62 result = fsm.ParseText(data)
  63. 63 # print(fsm.header)
  64. 64 print(result)
  65. 65 # print(len(result))

输出内容:

  1. ['Local_hostname', 'Local_port', 'Device_name', 'Device_module', 'Device_IP', 'Device_port']
  2. [['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. 1 import json
  2. 2
  3. 3 ip_address = '192.168.1.1'
  4. 4 # hostname = 'zh_cisco_2960'
  5. 5
  6. 6 cdp_data = [
  7. 7 ['9QI.JSJZX.JKS', 'GigabitEthernet1/0/24', '9qi.b3.net.test.sw01', 'WS-C2960X-24TS-L', '172.17.190.225', 'GigabitEthernet1/0/23'],
  8. 8 ['', 'GigabitEthernet1/0/23', '9qi.b3.net.test.sw03', 'WS-C2960S-48TD-L', '172.17.191.132', 'GigabitEthernet1/0/48']
  9. 9 ]
  10. 10
  11. 11 def data_format(ip_address,cdp_data):
  12. 12
  13. 13 hostname = cdp_data[0][0]#二层列表格式,获取主机名
  14. 14 result_data = {
  15. 15 ip_address: {
  16. 16 hostname: {item[1]: item[2:] for item in cdp_data}
  17. 17 }
  18. 18 }
  19. 19 json_data = json.dumps(result_data, indent=2)
  20. 20
  21. 21 return json_data
  22. 22 # return the JSON formatted string
  23. 23
  24. 24 if __name__ == "__main__":
  25. 25 json_data1 = data_format(ip_address,cdp_data)
  26. 26 print(json_data1)
Json_CDPInfo

 输出信息:

  1. 1 {
  2. 2 "192.168.1.1": {#当前设备IP
  3. 3 "QI.JSJZX.JKS": {#当前设备名称
  4. 4 "GigabitEthernet1/0/24": [#本地接口,
  5. 5 "qia.b3.net.test.sw01",#邻居信息
  6. 6 "WS-C2960X-24TS-L",
  7. 7 "192.17.190.225",
  8. 8 "GigabitEthernet1/0/23"
  9. 9 ],
  10. 10 "GigabitEthernet1/0/23": [
  11. 11 "qia.b3.net.test.sw03",
  12. 12 "WS-C2960S-48TD-L",
  13. 13 "192.17.191.132",
  14. 14 "GigabitEthernet1/0/48"
  15. 15 ]
  16. 16 }
  17. 17 }
  18. 18 }
View Code

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

遍历数据:

  1. 1 import json
  2. 2
  3. 3 # 读取 JSON 数据
  4. 4 json_data = '''
  5. 5 {
  6. 6 "192.168.1.1": {
  7. 7 "QIA.JSJZX.JKS": {
  8. 8 "GigabitEthernet1/0/24": [
  9. 9 "qia.b3.net.test.sw01",
  10. 10 "WS-C2960X-24TS-L",
  11. 11 "192.17.190.225",
  12. 12 "GigabitEthernet1/0/23"
  13. 13 ],
  14. 14 "GigabitEthernet1/0/23": [
  15. 15 "qia.b3.net.test.sw03",
  16. 16 "WS-C2960S-48TD-L",
  17. 17 "192.17.191.132",
  18. 18 "GigabitEthernet1/0/48"
  19. 19 ]
  20. 20 }
  21. 21 }
  22. 22 }
  23. 23
  24. 24 '''
  25. 25
  26. 26 # 解析 JSON 数据
  27. 27 parsed_data = json.loads(json_data)
  28. 28
  29. 29 # 遍历数据
  30. 30 for ip_address, inner_data in parsed_data.items():
  31. 31 print(f"IP Address: {ip_address}")# 获取主机IP地址
  32. 32 # print(f"host: {inner_data}")
  33. 33
  34. 34 for hostname, cdp_data in inner_data.items():
  35. 35 print(f"Hostname: {hostname}") #获取主机名称
  36. 36 # print(type(cdp_data))
  37. 37
  38. 38 for key, values in cdp_data.items():
  39. 39 print(values)
View Code

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

三、进行画图、

3.1 小试牛刀

网络拓扑图中, 最重要的信息就是节点和互联线路,其他都为辅助信息

在官方文档中,已经有详细的说明可以增加节点(addnode)、增加连线(addlink);在这个两个功能中,还有其他的选项,可以补充增加,官方提供的方法,可以通过help 查看文档说明help(N2G.plugins.diagrams.N2G_DrawIO.drawio_diagram)

这里只需要重点查看Quick start部分增加节点和连接就可以了。

  1. 1 from N2G import drawio_diagram
  2. 2
  3. 3 diagram = drawio_diagram()#
  4. 4 diagram.add_diagram("Page-1")
  5. 5 diagram.add_node(id="R1")#增加节点
  6. 6 diagram.add_node(id="R2")#增加节点
  7. 7 diagram.add_link("R1", "R2", label="DF", src_label="Gi1/1", trgt_label="GE23")#增加节点之间的连线,标签名称,src_labletrgt_lable 可以用来标注端口
  8. 8 diagram.layout(algo="kk")#图层,不重要
  9. 9 diagram.dump_file(filename="Sample_graph.drawio", folder="./Output/")#保存拓扑图

以上示例是官方最简单的画图程序,自己可以多增加几个节点进行练习。

但是在网络情况里,对每个邻居节点都登录检测检查邻居信息时,会遇到同一个连接,在两台设备上都能发现,那么创建节点和连接时会出现什么情况呢?

这里官方有说明,如果发现节点已经存在就直接跳过(也可以自定义),这样我们再写程序上就简单很多,链接里有说明。

3.2 完成程序输出

  1. 1 from N2G import drawio_diagram
  2. 2 import textfsm
  3. 3 import json
  4. 4 import telnetlib
  5. 5 import time
  6. 6
  7. 7
  8. 8 file_path = './host_1218.txt'
  9. 9 #主机IP清单,格式如下:
  10. 10 #cisco 192.168.1.1 cisco cisco
  11. 11
  12. 12
  13. 13 template_file = "./cisco_tfm.template"
  14. 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. 15 # 图标格式
  16. 16
  17. 17 def data_format(ip_address,cdp_data):
  18. 18
  19. 19 hostname = cdp_data[0][0]#二层列表格式,获取主机名
  20. 20 result_data = {
  21. 21 ip_address: {
  22. 22 hostname: {item[1]: item[2:] for item in cdp_data}
  23. 23 }
  24. 24 }
  25. 25 json_data = json.dumps(result_data, indent=2)
  26. 26
  27. 27 return json_data
  28. 28 # return the JSON formatted string
  29. 29
  30. 30
  31. 31 def cisco_telent(ip, username, password, cmd):
  32. 32 # 创建Telnet连接
  33. 33 tn = telnetlib.Telnet(ip)
  34. 34 time.sleep(0.1)
  35. 35
  36. 36 tn.read_until(b"Username:")
  37. 37 tn.write(username.encode('ascii') + b"\n")
  38. 38
  39. 39 tn.read_until(b"Password: ")
  40. 40 tn.write(password.encode('ascii') + b"\n")
  41. 41 tn.write(b"terminal length 0\n")
  42. 42
  43. 43 tn.write(cmd.encode('ascii') + b"\n")
  44. 44
  45. 45 time.sleep(0.5)
  46. 46 tn.write(b"exit\n")
  47. 47 # # 读取输出并打印
  48. 48 output = tn.read_very_eager().decode('ascii')
  49. 49
  50. 50 tn.close() # 关闭连接
  51. 51 return output
  52. 52
  53. 53
  54. 54 diagram = drawio_diagram()
  55. 55 diagram.add_diagram("Page-1")
  56. 56
  57. 57 host_info_dict = {}
  58. 58
  59. 59 node_num = 0
  60. 60 #节点计数
  61. 61 # 打开文件进行读取
  62. 62 with open(file_path, 'r') as file:
  63. 63 for line in file:
  64. 64 # 分割每一行以获取主机信息
  65. 65 host_info = line.strip().split()
  66. 66
  67. 67 # 检查是否有足够的信息
  68. 68 if len(host_info) == 4:
  69. 69 device_type, ip, username, password = host_info
  70. 70
  71. 71 # 构建设备字典
  72. 72 device = {
  73. 73 'device_type': device_type,
  74. 74 'ip': ip,
  75. 75 'username': username,
  76. 76 'password': password,
  77. 77 }
  78. 78
  79. 79 try:
  80. 80 # 执行telnet
  81. 81
  82. 82 cmd = 'show cdp neighbors detail'
  83. 83
  84. 84 command_output = cisco_telent(ip, username, password, cmd)
  85. 85 with open(template_file) as template:
  86. 86 #打开模板,进行数据提取
  87. 87 fsm = textfsm.TextFSM(template)
  88. 88 result = fsm.ParseText(command_output)
  89. 89 #列表格式数据,需要进行转换
  90. 90
  91. 91 nei_info_format = data_format(ip,result)
  92. 92 # print(nei_info_format)
  93. 93 parsed_data = json.loads(nei_info_format)
  94. 94
  95. 95 for ip_address, inner_data in parsed_data.items():
  96. 96 for local_hostname, cdp_data in inner_data.items():
  97. 97 if local_hostname not in host_info_dict:
  98. 98 #节点不存在,进行创建节点并保存节点信息
  99. 99 diagram.add_node(id=ip_address,name= local_hostname, style =style_cisco, width=60, height=60)
  100. 100 #增加本机节点
  101. 101 node_num += 1
  102. 102 # print(ip_address, local_hostname)
  103. 103 host_info_dict[local_hostname] = ip_address
  104. 104 # print(host_info_dict) 节点主机名和IP信息保存,后续进行校验节点是否已经存在
  105. 105
  106. 106 for src_label, values in cdp_data.items():
  107. 107 nei_host_name, nei_host_module, nei_host_id, trgt_label = values
  108. 108 #如果邻居节点不存在,直接创建;如果节点已经存在,则邻居ID信息要更新,更换成已经存在节点的IP地址()
  109. 109 if nei_host_name not in host_info_dict:
  110. 110 diagram.add_node(id=nei_host_id,name=nei_host_name,style=style_cisco, width=60, height=60)
  111. 111 #根据邻居信息,增加邻居节点
  112. 112 host_info_dict[nei_host_name] = nei_host_id
  113. 113 print("Add node " + nei_host_id + "secuessed.")
  114. 114 node_num += 1
  115. 115 diagram.add_link(ip_address, nei_host_id, src_label=src_label, trgt_label=trgt_label)
  116. 116 else:
  117. 117 nei_host_id = host_info_dict[nei_host_name]
  118. 118 diagram.add_link(ip_address, nei_host_id, src_label=src_label, trgt_label=trgt_label)
  119. 119 else:
  120. 120 print('节点已存在:' + local_hostname + ip_address)
  121. 121 #节点存在,检查邻居信息,根据邻居信息进行判断,是否添加邻居节点
  122. 122 for src_label, values in cdp_data.items():
  123. 123 nei_host_name, nei_host_module, nei_host_id, trgt_label = values
  124. 124 if nei_host_name not in host_info_dict:
  125. 125 diagram.add_node(id=nei_host_id, name=nei_host_name, style=style_cisco, width=60, height=60)
  126. 126 #根据邻居信息,增加邻居节点
  127. 127 host_info_dict[nei_host_name] = nei_host_id
  128. 128 print("Add node " + nei_host_id + "secuessed.")
  129. 129 node_num += 1
  130. 130 diagram.add_link(ip_address, nei_host_id, src_label=src_label, trgt_label=trgt_label)
  131. 131 else:
  132. 132 nei_host_id = host_info_dict[nei_host_name]
  133. 133 diagram.add_link(ip_address, nei_host_id, src_label=src_label, trgt_label=trgt_label)
  134. 134
  135. 135 # print("Add node " + ip_address + " sucessed.")
  136. 136
  137. 137 except Exception as e:
  138. 138 print(f"Failed to connect to {ip}: {e}")
  139. 139
  140. 140 else:
  141. 141 print(f"Invalid line in the file: {line.strip()}")
  142. 142 print(node_num)
  143. 143 print(host_info_dict,len(host_info_dict))
  144. 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在拓扑图中隐性的展示;右键成编辑节点数据,即可查看或编辑数据

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

 

原文链接:https://www.cnblogs.com/niu525/p/17925716.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号