#!/usr/bin/env /opt/miniconda3/envs/py37/bin/python import sys import json from pysnmp.hlapi import * from mylog import mylog as mylog log = mylog("./zabbix.log") def snmp_walk(host, oid, format="str", strip_prefix=True, community="zabbix"): res = {} # 使用正确的 nextCmd 函数 for errorIndication, errorStatus, errorIndex, varBinds in nextCmd( SnmpEngine(), CommunityData(community), UdpTransportTarget((host, 161), timeout=4.0, retries=3), ContextData(), ObjectType(ObjectIdentity(oid)), lookupMib=False, lexicographicMode=False, ): if errorIndication: raise ConnectionError( f'SNMP error: "{str(errorIndication)}". Status={str(errorStatus)}' ) elif errorStatus: raise ConnectionError( f'SNMP error: "{str(errorStatus)}" at index {str(errorIndex)}' ) else: for varBind in varBinds: oid_str = str(varBind[0]) value = varBind[1] # 如果指定了去除前缀 if strip_prefix: oid_str = oid_str.replace(oid, "", 1).lstrip(".") # 根据格式处理值 if format == "hex": res[oid_str] = value.asOctets().hex().upper() elif format == "str": res[oid_str] = str(value) else: res[oid_str] = value return res def machex(mac_part): """将OID格式的MAC转换为标准格式""" parts = [int(x) for x in mac_part.split(".")] return "-".join(f"{x:02x}" for x in parts[-6:]) def read_ipv4_from_oid_tail(oid, with_len=True): parts = [int(x) for x in oid.split(".")] if with_len: assert parts[-5] == 4 # number of elements return ".".join([str(x) for x in parts[-4:]]) from datetime import timedelta def ms_to_timedelta(milliseconds): delta = timedelta(milliseconds=milliseconds) hours = delta.seconds // 3600 minutes = (delta.seconds % 3600) // 60 seconds = delta.seconds % 60 return f"{hours}:{minutes:02d}:{seconds:02d}" def format_mac(mac_str: str) -> str: # 每2个字符分割一次,生成子字符串列表 segments = [mac_str[i : i + 2] for i in range(0, len(mac_str), 2)] # 用连字符连接子字符串,并添加末尾换行符 return "-".join(segments) def main(): log.info(str(sys.argv)) if len(sys.argv) < 4: print("Usage: ./switch_monitor.py [port]") sys.exit(1) mode = sys.argv[1] switch_ip = sys.argv[2] community = sys.argv[3] port_filter = sys.argv[4] if len(sys.argv) >= 5 else None log.info(sys.argv) # 1. 获取端口映射表 port_mapping = {} dot1dBasePortIfIndex = snmp_walk( switch_ip, "1.3.6.1.2.1.17.1.4.1.2", "int", True, community ) ifDescr = snmp_walk(switch_ip, "1.3.6.1.2.1.2.2.1.2", "str", True, community) for bridge_index, if_index in dot1dBasePortIfIndex.items(): if str(if_index) in ifDescr: port_mapping[str(bridge_index)] = ifDescr[str(if_index)] # 2. 获取MAC表和ip地址 mac_table = snmp_walk(switch_ip, "1.3.6.1.2.1.17.4.3.1.2", "int", True, community) # 3. 统计端口MAC数量 port_counts = {} mac_details = {} for mac_oid, bridge_index in mac_table.items(): standard_mac = machex(mac_oid).upper() if str(bridge_index) in port_mapping: port_name = port_mapping[str(bridge_index)] port_counts[port_name] = port_counts.get(port_name, 0) + 1 if mode == "list": atPhysAddress = snmp_walk( switch_ip, "1.3.6.1.2.1.3.1.1.2", "hex", True, community ) oid_mac = snmp_walk( switch_ip, "1.3.6.1.2.1.55.1.12.1.2.719.254.128.0.0.0.0.0.0", "hex", True, community, ) oid_hour = snmp_walk( switch_ip, "1.3.6.1.2.1.55.1.12.1.5.719.254.128.0.0.0.0.0.0", "str", True, community, ) mac_time = {} # ms_to_timedelta for oid, mac in oid_mac.items(): f_mac = format_mac(mac) time = oid_hour.get(oid, "0") time = ms_to_timedelta(int(time + "0")) mac_time[f_mac] = time mac_ip_table = {} for oid, mac in atPhysAddress.items(): ip = read_ipv4_from_oid_tail(oid, with_len=False) mac_ip_table[format_mac(mac)] = ip for mac_oid, bridge_index in mac_table.items(): standard_mac = machex(mac_oid).upper() if str(bridge_index) in port_mapping: port_name = port_mapping[str(bridge_index)] ip = mac_ip_table.get(standard_mac, False) mac_details.setdefault(port_name, []).append( { "ip": ip, "mac": standard_mac, "time": mac_time.get(standard_mac, "N/A"), } ) # 3. 如果指定了端口过滤,进行过滤 # 4. 根据模式输出 if mode == "discovery": # 输出端口自动发现数据 discovery = [] for port in port_counts.keys(): discovery.append( { "{#SWITCH_IP}": switch_ip, "{#PORT_NAME}": port, "{#COMMUNITY}": community, } ) print(json.dumps({"data": discovery})) elif mode == "count": # 输出特定端口的MAC数量 if not port_filter: print("0") return print(str(port_counts.get(port_filter, 0))) elif mode == "list": # 输出特定端口的MAC列表 if not port_filter: print("[]") return if port_filter in mac_details: pass print(json.dumps(mac_details[port_filter])) else: print("[]") elif mode == "details": # 输出详细MAC信息(用于调试) details = {} for port, macs in mac_details.items(): details[port] = {"count": len(macs), "macs": macs} print(json.dumps(details)) log.info(json.dumps(details)) if __name__ == "__main__": sys.argv = [ "/usr/lib/zabbix/externalscripts/switch_monitor.py", "list", "192.168.0.112", "zabbix", "GigabitEthernet1/0/5", ] main()