
简介本资源是一份面向网络管理工程师、高校网络课程学习者及SNMP协议初学者的原理与实践文档聚焦基于SNMP协议的网络层拓扑自动发现技术解决实际网络环境中子网识别、路由器定位及设备间连接关系构建等核心问题。文档系统阐述MIB-II标准中system、interfaces和ip三大关键MIB组的作用机制详解如何通过解析sysObjectID识别厂商设备、利用ifTable关联接口与物理地址、结合ipRouteTable提取直连/间接路由以还原真实拓扑结构并给出默认网关发现、子网范围计算及多跳路由设备递归探测等关键算法逻辑。资源为单文件Word文档.doc大小574KB内容结构清晰含体系架构图、MIB对象对照表及典型路由表分析示例便于理论理解与实验验证。目前已有417人学习下载适合需掌握SNMP拓扑发现底层原理与工程实现路径的中级网络技术人员。1. 这不是PPT里的拓扑图一份能跑通的SNMP网络拓扑发现实战笔记专治“看不见的交换机”和“路由表里藏猫腻”的玄学故障你有没有遇到过这种场景核心交换机明明配了OSPF三层互通没问题但一画拓扑——连它连了几台接入交换机都说不清或者网管平台显示某台路由器在线可它的路由表里压根没出现下游子网排查时翻遍日志却找不到物理连接断在哪一级这不是运维水平问题是拓扑信息缺失导致的黑匣子状态。这份《SNMP网络拓扑发现.doc》不是理论课件而是一份被我拆解、实测、踩坑、再重写的可执行技术方案它用标准MIB-IIRFC-1213做底座不依赖厂商私有协议靠ipRouteTable挖出路由器的真实邻居关系靠ipNetToMediaTableifPhysAddress补全哑设备MAC更关键的是——它给出了链路层交换机互联关系的数学判定逻辑直接连接定理/间接连接定理彻底解决“为什么Wireshark抓不到STP BPDU却要硬猜端口连线”的血泪经验。适合正在搭建自动化巡检系统、需要输出合规拓扑报告、或手头正卡在“交换机A的Port3到底连着B还是C”这类翻车现场的一线工程师。别被“.doc”后缀骗了这文档里藏着能直接抄进Python脚本的伪代码、XML Schema定义、以及MIB对象字段级映射表——接下来我们把它变成能跑起来的工具。2. 网络层拓扑发现从路由表里“扒”出真实连接关系不是读取ARP缓存那么简单2.1 为什么必须用ipRouteTable而不是pingtracert很多工程师第一反应是写个脚本ping全网段tracert探测路径但这在生产环境会触发安全策略告警且无法反映静态路由、策略路由等非动态学习的路径。而ipRouteTableOID:1.3.6.1.2.1.4.21是SNMP标准MIB-II中强制实现的路由表视图它记录的是设备当前生效的完整路由决策依据。关键在于ipRouteType字段值为3Direct/4Indirect直接暴露了“直连”与“非直连”的物理拓扑语义。例如当ipRouteDest192.168.10.0且ipRouteMask255.255.255.0且ipRouteType3时说明该子网物理上插在本设备某个接口上而ipRouteType4则意味着数据包必须发给ipRouteNextHop地址的设备——这个地址就是你的下一台路由器。这才是拓扑发现的黄金线索。提示ipRouteTable中的ipRouteNextHop值必须结合ipRouteType判断有效性。若ipRouteType2Invalid该条目不可用若ipRouteDest0.0.0.0缺省路由其ipRouteNextHop才是真正的默认网关地址需额外验证该设备ipForwarding1开启IP转发才确认是路由设备。2.2 实战用pysnmp批量提取路由表并构建初始拓扑节点以下Python代码基于pysnmp库v4.4.12实测通过思科IOS、华为VRP、H3C Comware设备。注意社区版pysnmp对大型路由表性能较差生产环境建议加超时和重试逻辑。from pysnmp.hlapi import * import ipaddress def get_ip_route_table(target_ip, communitypublic, port161): 获取目标设备的ipRouteTable返回结构化列表 :param target_ip: 设备IP地址 :param community: SNMP团体名 :param port: SNMP端口 :return: list of dict, each dict contains dest, next_hop, mask, type route_entries [] # ipRouteTable OID前缀: 1.3.6.1.2.1.4.21.1 base_oid ObjectIdentity(IP-MIB, ipRouteTable) # 使用bulkCmd一次性获取整张表比getnext快10倍 errorIndication, errorStatus, errorIndex, varBinds next( bulkCmd(SnmpEngine(), CommunityData(community), UdpTransportTarget((target_ip, port)), ContextData(), 0, 50, # non-repeaters, max-repetitions ObjectType(ObjectIdentity(IP-MIB, ipRouteDest)), ObjectType(ObjectIdentity(IP-MIB, ipRouteNextHop)), ObjectType(ObjectIdentity(IP-MIB, ipRouteMask)), ObjectType(ObjectIdentity(IP-MIB, ipRouteType)), lexicographicModeFalse) ) if errorIndication: print(fSNMP Error on {target_ip}: {errorIndication}) return [] # 解析返回的varBinds每4个一组对应一个路由条目 for i in range(0, len(varBinds), 4): if i 3 len(varBinds): break dest_oid, next_hop_oid, mask_oid, type_oid varBinds[i:i4] # 提取IP地址SNMP返回的是OctetString需转为点分十进制 try: dest_ip ..join([str(b) for b in dest_oid[1].asNumbers()]) next_hop_ip ..join([str(b) for b in next_hop_oid[1].asNumbers()]) mask_ip ..join([str(b) for b in mask_oid[1].asNumbers()]) route_type int(type_oid[1]) # 过滤无效条目如0.0.0.0/0的缺省路由需单独处理 if dest_ip 0.0.0.0 and mask_ip 0.0.0.0: continue # 缺省路由在后续步骤中单独提取 route_entries.append({ dest: dest_ip, next_hop: next_hop_ip, mask: mask_ip, type: route_type, network: str(ipaddress.ip_network(f{dest_ip}/{mask_ip}, strictFalse)) }) except Exception as e: continue # 跳过解析失败的条目 return route_entries # 示例获取核心路由器的路由表 core_router_ip 10.1.1.1 routes get_ip_route_table(core_router_ip, communityprivate) for r in routes[:5]: # 打印前5条 print(fNetwork: {r[network]} - NextHop: {r[next_hop]} (Type: {r[type]}))参数说明与逻辑延伸non-repeaters0, max-repetitions50启用SNMPv2c的Bulk操作避免逐条getnext的网络开销。若设备不支持Bulk如老旧Juniper需降级为nextCmd并循环。ipaddress.ip_network()将dest/mask组合转换为CIDR格式如192.168.5.0/24这是后续子网去重和范围校验的基础。关键边界ipRouteType3Direct的条目必须与ifTable中的接口IP比对。例如若ipRouteDest192.168.5.0且ipRouteMask255.255.255.0则需查询该设备ipAddrTable确认是否存在192.168.5.x的接口IP否则可能是错误配置的静态路由。2.3 构建网络层拓扑图从路由条目到节点-连接关系的映射规则伪代码中的CGateway gw GetDefaultGateway()在实际工程中需拆解为三步验证定位本机默认网关读取本地主机的ipRouteTableWindows用netstat -rn | findstr 0.0.0.0Linux用ip route | grep default获取ipRouteNextHop值确认网关身份对该IP发起SNMP查询1.3.6.1.2.1.4.1ipForwarding对象值为1才确认是路由设备递归发现邻居对每个ipRouteType4的ipRouteNextHop地址重复步骤2并将其加入待扫描队列。下表定义了从原始路由条目到拓扑节点Router/Subnet和连接Link的映射逻辑路由条目特征拓扑节点类型节点标识符生成规则连接关系生成规则ipRouteType3且ipRouteDest≠0.0.0.0Subnet子网f{dest}/{mask}如192.168.10.0/24(CurrentRouter, Subnet)其中CurrentRouter为当前设备的sysName或IPipRouteType4Router路由器ipRouteNextHop的IP地址需先SNMP验证ipForwarding1(CurrentRouter, NextHopRouter)双向连接需去重ipRouteDest0.0.0.0Gateway网关ipRouteNextHop同上(LocalHost, Gateway)仅在初始发现时添加注意ipRouteType3的条目中若ipRouteMask255.255.255.255即主机路由则ipRouteNextHop代表直连的另一台路由器如背靠背连接此时应生成(CurrentRouter, NextHopRouter)连接而非子网。这是文档中“1处”的核心逻辑也是新手最容易忽略的物理直连场景。3. 链路层拓扑发现用交换机FDB表破解“看不见的端口连线”告别肉眼查线3.1 为什么不能只靠LLDP/CDPFDB是最后的兜底方案LLDPIEEE 802.1AB和CDPCisco私有确实能快速发现邻居但它们有致命缺陷依赖双方设备都启用且配置一致。现实中哑交换机不支持SNMP、老旧打印机、甚至某些安全加固后的服务器会禁用这些协议。而FDBForwarding Database即MAC地址表是交换机硬件转发的底层数据结构只要设备收发过数据包FDB就必然有记录。MIB-II中的dot1dTpFdbTableBridge MIB提供了标准访问方式这才是异构网络拓扑发现的“后悔药”。3.2 直接连接定理的工程化实现如何判断两个交换机端口是否物理直连文档中的“直接连接定理”要求FxA ∩ FyB ∅且FxA ∪ FyB N全集但工程中“全集N”无法穷举。我的实践方案是用子网内所有已知活跃主机的MAC地址集合替代N。步骤如下先通过ipNetToMediaTableARP表或ifPhysAddress本机MAC收集子网内所有主机MAC对交换机A的每个端口x下载其FDB表1.3.6.1.2.1.17.4.3.1.2过滤出属于本子网的MAC对交换机B的每个端口y同样操作计算FxA与FyB的交集若为空且FxA ∪ FyB覆盖了95%以上的已知主机MAC则判定为直连。from pysnmp.hlapi import * import re def get_fdb_entries(switch_ip, community, port161, subnet_macsNone): 获取交换机FDB表返回端口索引-MAC列表的字典 :param switch_ip: 交换机IP :param community: SNMP团体名 :param subnet_macs: 子网内已知主机MAC集合set用于后续过滤 :return: dict, keyifIndex, valuelist of MAC strings fdb_dict {} # dot1dTpFdbTable OID: 1.3.6.1.2.1.17.4.3.1 base_oid ObjectIdentity(BRIDGE-MIB, dot1dTpFdbTable) errorIndication, errorStatus, errorIndex, varBinds next( bulkCmd(SnmpEngine(), CommunityData(community), UdpTransportTarget((switch_ip, port)), ContextData(), 0, 100, ObjectType(ObjectIdentity(BRIDGE-MIB, dot1dTpFdbPort)), ObjectType(ObjectIdentity(BRIDGE-MIB, dot1dTpFdbAddress)), lexicographicModeFalse) ) if errorIndication: return {} # varBinds按顺序成对出现port, mac, port, mac... for i in range(0, len(varBinds), 2): if i 1 len(varBinds): break port_oid, mac_oid varBinds[i:i2] try: port_idx int(port_oid[1]) # MAC地址是6字节OctetString转为标准格式xx:xx:xx:xx:xx:xx mac_bytes mac_oid[1].asNumbers() mac_str :.join([f{b:02x} for b in mac_bytes]) # 过滤只保留子网内主机MAC若提供subnet_macs if subnet_macs and mac_str not in subnet_macs: continue if port_idx not in fdb_dict: fdb_dict[port_idx] [] fdb_dict[port_idx].append(mac_str) except: continue return fdb_dict # 示例检查交换机A(Port1)与交换机B(Port2)是否直连 sw_a_ip 10.1.10.10 sw_b_ip 10.1.10.11 subnet_macs {00:11:22:33:44:55, aa:bb:cc:dd:ee:ff} # 已知子网主机MAC fdb_a get_fdb_entries(sw_a_ip, private, subnet_macssubnet_macs) fdb_b get_fdb_entries(sw_b_ip, private, subnet_macssubnet_macs) # 检查A的Port1和B的Port2 if 1 in fdb_a and 2 in fdb_b: set_a set(fdb_a[1]) set_b set(fdb_b[2]) intersection set_a set_b union set_a | set_b if len(intersection) 0 and len(union) 0.95 * len(subnet_macs): print(✅ A Port1 与 B Port2 极可能物理直连) else: print(❌ 不满足直连条件需用间接连接定理)关键参数说明dot1dTpFdbPortOID:1.3.6.1.2.1.17.4.3.1.2FDB表中每条MAC对应的端口索引ifIndex需与ifTable关联才能知道是哪个物理端口0.95 * len(subnet_macs)覆盖率阈值。实践中因MAC老化时间通常300秒和流量不均100%覆盖不可能95%是可靠下限避坑点dot1dTpFdbAddress返回的MAC是大端序字节数组pysnmp的asNumbers()方法直接可用无需手动反转。3.3 间接连接定理的落地当FDB不完整时如何用反证法锁定连接当FxA和FyB交集不为空时直接连接定理失效。此时启用文档中的“间接连接定理”条件2FxA中存在B的MAC且A上存在另一端口k使得FkA ∩ FyB ≠ ∅。这背后的逻辑是如果A的Port1和B的Port2间接相连中间有Hub或哑交换机那么A的Port1会学到B的MAC因B发包到A同时A的其他端口如Port2也会学到B的MAC因B广播包被Hub泛洪。而直连场景下B的MAC只出现在A的Port1。def check_indirect_connection(fdb_a, fdb_b, port_a, port_b, sw_b_mac): 检查交换机A的port_a与交换机B的port_b是否间接连接条件2 :param fdb_a: 交换机A的FDB字典 :param fdb_b: 交换机B的FDB字典 :param port_a: A的待测端口 :param port_b: B的待测端口 :param sw_b_mac: 交换机B自身的MAC地址需提前获取 :return: bool if port_a not in fdb_a or port_b not in fdb_b: return False # 条件2a: FxA中存在B的MAC if sw_b_mac not in fdb_a[port_a]: return False # 条件2b: A上存在另一端口k使得FkA ∩ FyB ≠ ∅ fdb_b_port set(fdb_b[port_b]) for other_port in fdb_a: if other_port port_a: continue if fdb_a[other_port] fdb_b_port: # 交集非空 return True return False # 获取交换机B的MAC从ifTable的ifPhysAddress def get_switch_mac(switch_ip, community, port_idx1): 获取指定端口的MAC地址 errorIndication, errorStatus, errorIndex, varBinds next( getCmd(SnmpEngine(), CommunityData(community), UdpTransportTarget((switch_ip, 161)), ContextData(), ObjectType(ObjectIdentity(IF-MIB, ifPhysAddress, port_idx))) ) if not errorIndication and len(varBinds) 0: mac_bytes varBinds[0][1].asNumbers() return :.join([f{b:02x} for b in mac_bytes]) return None # 示例调用 sw_b_mac get_switch_mac(sw_b_ip, private, port_idx1) if check_indirect_connection(fdb_a, fdb_b, 1, 2, sw_b_mac): print(⚠️ A Port1 与 B Port2 为间接连接中间有Hub/哑设备)工程要点sw_b_mac必须是交换机B自身的MAC通常取管理口或第一个以太口而非其FDB中的MAC。可通过ifTable的ifPhysAddress获取条件2b中的FkA ∩ FyB检测本质是寻找“泛洪证据”。若A的Port2也学到了B的Port2下的MAC说明B的Port2流量被Hub广播到了A的Port2证实中间有共享介质。4. 避坑指南SNMP拓扑发现中5个让老手也翻车的硬核问题4.1 现象ipRouteTable返回大量0.0.0.0条目但ipRouteNextHop是0.0.0.0无法识别真实网关原因这是SNMP代理实现差异。部分设备如某些Linux软路由将缺省路由的ipRouteNextHop设为0.0.0.0而非真实下一跳IP。RFC 1213规定ipRouteNextHop为0.0.0.0时应查ipRouteIfIndex指向的接口IP作为网关。解决先读ipRouteIfIndexOID:1.3.6.1.2.1.4.21.1.9再用该索引查ifTable的ifPhysAddress和ipAddrTable的接口IP。例如若ipRouteIfIndex2则查1.3.6.1.2.1.2.2.1.2.2接口描述和1.3.6.1.2.1.4.20.1.1.2接口IP。4.2 现象交换机FDB表为空或只返回少量MAC原因FDB只学习经过该交换机转发的数据包源MAC。若子网内主机长期静默无流量或交换机启用了端口安全Port Security限制MAC数量FDB将不完整。解决主动触发MAC学习。向子网广播ICMP Echoping -b 192.168.10.255或使用arping工具向每个主机IP发送ARP请求。注意需在交换机管理口所在VLAN执行否则广播域不匹配。4.3 现象sysObjectID解析失败无法识别设备厂商型号原因sysObjectIDOID:1.3.6.1.2.1.1.2.0是ASN.1编码的OID字符串直接转字符串会乱码。正确做法是解析为数字序列后查厂商树。解决用pysnmp的ObjectIdentifier类解析from pysnmp.proto.rfc1902 import ObjectIdentifier oid_obj ObjectIdentifier(1.3.6.1.4.1.9.1.1234) # 思科设备OID oid_nums oid_obj.asNumbers() # 返回(1,3,6,1,4,1,9,1,1234) # 查IANA企业编号数据库1.3.6.1.4.1.9 Cisco4.4 现象ipNetToMediaTableARP表返回的MAC与ifPhysAddress不一致导致主机归属错误原因ipNetToMediaTableOID:1.3.6.1.2.1.4.22.1存储的是ARP缓存可能包含过期条目或代理ARP条目而ifPhysAddress是接口真实MAC。解决优先用ifPhysAddress确定本机MACipNetToMediaTable仅用于发现不支持SNMP的哑主机。对ARP表条目增加存活验证用snmpset向该IP发送UDP包需设备支持或用socket发ICMP探测。4.5 现象多厂商混合网络中dot1dTpFdbTable无法获取报错noSuchName原因dot1dTpFdbTable属于Bridge MIB而部分厂商如华为默认不启用Bridge MIB需手动开启。解决登录设备CLI执行启用命令华为snmp-agent mib-view ViewAll iso includesnmp-agent sys-info version v2c v3H3Csnmp-agent mib-view included view_all iso若仍失败降级使用私有MIB华为1.3.6.1.4.1.2011.5.25.16.1.2hwL2IfMacTableH3C1.3.6.1.4.1.25506.2.6.1.1.1h3cL2IfMacTable。5. XML Schema驱动的拓扑持久化把发现结果存成可审计、可对接CMDB的标准格式5.1 为什么必须用XML Schema而非JSON——合规性与扩展性的硬需求金融、政务等强监管行业要求拓扑数据具备可验证的结构约束。JSON Schema虽能校验但XML SchemaXSD是ISO标准且天然支持命名空间、版本控制、注释文档xs:annotation这对生成符合等保2.0要求的《网络资产清单》至关重要。文档附录A的XSD定义了InternetType根元素其subnetList、l3ConnList、gwList三要素正是拓扑的核心骨架。5.2 从Python字典到XML用xml.etree.ElementTree生成合规文件以下代码将前述发现的路由表、FDB结果转换为XSD定义的XML结构重点处理age字段时间戳和objectinstance唯一实例标识import xml.etree.ElementTree as ET from datetime import datetime def create_topology_xml(routers, subnets, connections, output_pathtopology.xml): 生成符合附录A XSD的拓扑XML文件 :param routers: list of dict, keys: ip, name, sysDescr, sysObjectID :param subnets: list of dict, keys: network, mask, connected_routers :param connections: list of dict, keys: source, dest, type (3router-router, 4router-subnet) :param output_path: 输出文件路径 # 创建根元素 root ET.Element(Internet, { xmlns:xsi: http://www.w3.org/2001/XMLSchema-instance, xsi:noNamespaceSchemaLocation: topology.xsd # 指向XSD文件 }) # subnetList subnet_list ET.SubElement(root, subnetList) for subnet in subnets: subnet_node ET.SubElement(subnet_list, subnetNode) ET.SubElement(subnet_node, objectinstance).text fsubnet-{subnet[network].replace(/, -)} ET.SubElement(subnet_node, objectfatherinstance).text Internet ET.SubElement(subnet_node, subnetAddr).text subnet[network].split(/)[0] ET.SubElement(subnet_node, subnetMask).text subnet[mask] ET.SubElement(subnet_node, age).text str(int(datetime.now().timestamp())) # 关联的交换机此处简化实际需从FDB分析 sw_node ET.SubElement(subnet_node, swNode) ET.SubElement(sw_node, objectinstance).text sw-10.1.10.10 ET.SubElement(sw_node, objectfatherinstance).text fsubnet-{subnet[network].replace(/, -)} ET.SubElement(sw_node, swName).text Core-SW-A ET.SubElement(sw_node, swAddr).text 10.1.10.10 ET.SubElement(sw_node, commuStr).text private ET.SubElement(sw_node, age).text str(int(datetime.now().timestamp())) # l3ConnList l3_conn_list ET.SubElement(root, l3ConnList) for conn in connections: conn_node ET.SubElement(l3_conn_list, l3ConnNode) # 注意XSD中为l3ConnNode非l3ConnList ET.SubElement(conn_node, ObjectInstance).text fconn-{conn[source]}-{conn[dest]} ET.SubElement(conn_node, ObjectFatherInstance).text Internet ET.SubElement(conn_node, sourceInstance).text conn[source] ET.SubElement(conn_node, linkName).text f{conn[source]}-{conn[dest]} ET.SubElement(conn_node, linkType).text str(conn[type]) ET.SubElement(conn_node, age).text str(int(datetime.now().timestamp())) # gwList gw_list ET.SubElement(root, gwList) for router in routers: gw_node ET.SubElement(gw_list, gwNode) ET.SubElement(gw_node, ObjectInstance).text fgw-{router[ip]} ET.SubElement(gw_node, ObjectFatherInstance).text Internet ET.SubElement(gw_node, gwName).text router[name] or router[ip] ET.SubElement(gw_node, gwAddr).text router[ip] ET.SubElement(gw_node, age).text str(int(datetime.now().timestamp())) # 格式化输出美观缩进 rough_string ET.tostring(root, encodingutf-8) reparsed minidom.parseString(rough_string) with open(output_path, w, encodingutf-8) as f: f.write(reparsed.toprettyxml(indent )) print(f✅ 拓扑XML已生成: {output_path}) # 示例数据 routers [{ip: 10.1.1.1, name: CORE-RT, sysDescr: Cisco IOS Software...}] subnets [{network: 192.168.10.0/24, mask: 255.255.255.0, connected_routers: [10.1.1.1]}] connections [{source: 10.1.1.1, dest: 192.168.10.0/24, type: 4}] create_topology_xml(routers, subnets, connections)关键设计点objectinstance字段采用subnet-192.168.10.0-24格式确保全局唯一且可读避免UUID带来的可读性灾难age字段存Unix时间戳秒级而非日期字符串便于CMDB系统做时效性判断如age 3600表示1小时内未更新xsi:noNamespaceSchemaLocation属性指向本地XSD文件校验时用lxml.etree.XMLSchema加载即可无需网络依赖。5.3 用XSD校验XML防止手工编辑破坏结构生产环境中XML可能被人工修改如补充设备位置信息。必须用XSD强制校验from lxml import etree def validate_xml_with_xsd(xml_path, xsd_path): 用XSD校验XML文件 with open(xsd_path, rb) as f: schema_root etree.XML(f.read()) schema etree.XMLSchema(schema_root) with open(xml_path, rb) as f: xml_doc etree.parse(f) if schema.validate(xml_doc): print(✅ XML结构符合XSD规范) return True else: print(❌ XML校验失败:) for error in schema.error_log: print(f Line {error.line}: {error.message}) return False # 调用 validate_xml_with_xsd(topology.xml, topology.xsd)6. 终极技巧用SNMP Walk自动生成设备MIB支持矩阵精准规避“协议不兼容”陷阱6.1 为什么拓扑发现总在某台设备上失败——MIB支持度才是真正的拦路虎你以为设备支持SNMPv2c就万事大吉错。不同厂商、不同固件版本对MIB-II的支持程度天差地别。例如某款国产交换机实现了ipRouteTable但ipRouteType字段恒为0未实现另一款防火墙的dot1dTpFdbTable只返回前100条MAC。盲目调用会导致脚本静默失败。解决方案在发现前先用SNMP Walk扫描设备所有OID生成MIB支持矩阵。6.2 用snmpwalk命令行快速生成支持矩阵# 1. 获取设备所有OID树耗时较长生产环境建议限定范围 snmpwalk -v2c -c public 10.1.1.1 1.3.6.1.2.1 device_oids.txt # 2. 提取关键MIB组的支持情况用grep快速判断 echo MIB-II 支持度 grep -c 1.3.6.1.2.1.1.1 device_oids.txt echo ✓ system group grep -c 1.3.6.1.2.1.2.2.1.2 device_oids.txt echo ✓ interfaces group grep -c 1.3.6.1.2.1.4.21.1.1 device_oids.txt echo ✓ ipRouteTable grep -c 1.3.6.1.2.1.17.4.3.1.2 device_oids.txt echo ✓ dot1dTpFdbTable # 3. 输出为CSV供脚本读取 echo device_ip,system,interfaces,ipRouteTable,dot1dTpFdbTable support_matrix.csv echo 10.1.1.1,$(grep -c 1.3.6.1.2.1.1.1 device_oids.txt),$(grep -c 1.3.6.1.2.1.2.2.1.2 device_oids.txt),$(grep -c 1.3.6.1.2.1.4.21.1.1 device_oids.txt),$(grep -c 1.3.6.1.2.1.17.4.3.1.2 device_oids.txt) support_matrix.csv6本文还有配套的精品资源点击获取