From 6782aa47d10ab8a249455d6538b07b3b5da2c1ab Mon Sep 17 00:00:00 2001 From: Xavier Rodriguez Date: Mon, 9 Mar 2026 15:21:48 +0100 Subject: [PATCH] Add interface information handling and update Zabbix formatting - Introduced InterfaceInfo class to encapsulate interface details. - Modified CiscoMacTableParser to gather and store interface information. - Updated ZabbixLLDFormatter to include interface status and descriptions in the output. - Adjusted test_new_format.py to validate new interface information integration. - Enhanced run.sh for improved container management. --- __pycache__/cisco_mac_monitor.cpython-314.pyc | Bin 0 -> 31488 bytes cisco_mac_http_service.py | 10 +- cisco_mac_monitor.py | 183 ++++++++++++++---- run.sh | 4 + test_new_format.py | 13 +- 5 files changed, 160 insertions(+), 50 deletions(-) create mode 100644 __pycache__/cisco_mac_monitor.cpython-314.pyc diff --git a/__pycache__/cisco_mac_monitor.cpython-314.pyc b/__pycache__/cisco_mac_monitor.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..817a31307e36f6119482ee25ba136d0e11476bf7 GIT binary patch literal 31488 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 z List[MacEntry]: """Obtiene la tabla MAC del dispositivo""" try: - # Primero obtenemos la lista de interfaces - interfaces = self._get_interface_list(ssh_connector) - self.logger.info(f"Detectadas {len(interfaces)} interfaces") + # Primero obtenemos la información completa de interfaces (nombre, estado, descripción) + self._get_interface_info(ssh_connector) + self.logger.info(f"Detectadas {len(self.interface_info)} interfaces") # CRUCIAL: Reconectar para dispositivos Cisco antiguos # Algunos switches Cisco antiguos corrompen la sesión SSH después de comandos largos @@ -154,9 +164,6 @@ class CiscoMacTableParser: mac_entries = self._parse_mac_table_output(output) self.logger.info(f"Obtenidas {len(mac_entries)} entradas de tabla MAC") - # Almacenar las interfaces para usarlas en el formatter - self.detected_interfaces = interfaces - return mac_entries except Exception as e: @@ -165,36 +172,36 @@ class CiscoMacTableParser: self.logger.error(f"Traceback: {traceback.format_exc()}") return [] - def _get_interface_list(self, ssh_connector: CiscoSSHConnector) -> List[str]: - """Obtiene la lista de todas las interfaces del dispositivo""" + def _get_interface_info(self, ssh_connector: CiscoSSHConnector): + """Obtiene información completa de todas las interfaces del dispositivo""" try: - # Comando para obtener interfaces (funciona en IOS 12.x y 15.x) + # Primero intentamos con show interface status (incluye estado) command = "show interface status" - output = ssh_connector.execute_command(command) + status_output = ssh_connector.execute_command(command) # Si no funciona, probar comando alternativo - if "Invalid input" in output or not output.strip(): + if "Invalid input" in status_output or not status_output.strip(): command = "show ip interface brief" - output = ssh_connector.execute_command(command) + status_output = ssh_connector.execute_command(command) - interfaces = self._parse_interface_list(output) + # Parsear información de estado + self._parse_interface_status(status_output) - # Si aún no obtenemos interfaces, usar un comando más básico - if not interfaces: + # Si no obtuvimos interfaces, usar un comando más básico + if not self.interface_info: command = "show interface | include line protocol" output = ssh_connector.execute_command(command) - interfaces = self._parse_interface_line_protocol(output) + self._parse_interface_line_protocol_to_info(output) - return interfaces + # Ahora obtenemos las descripciones + if self.interface_info: + self._get_interface_descriptions(ssh_connector) except Exception as e: - self.logger.warning(f"Error obteniendo interfaces: {str(e)}") - return [] + self.logger.warning(f"Error obteniendo información de interfaces: {str(e)}") - def _parse_interface_list(self, output: str) -> List[str]: + def _parse_interface_status(self, output: str): """Parsea la salida de show interface status o show ip interface brief""" - interfaces = [] - lines = output.split('\n') for line in lines: line = line.strip() @@ -204,28 +211,105 @@ class CiscoMacTableParser: # Patrón para show interface status # Format: Port Name Status Vlan Duplex Speed Type parts = line.split() - if len(parts) >= 2: + if len(parts) >= 3: interface = parts[0] # Filtrar solo interfaces físicas relevantes if self._is_physical_interface(interface): - interfaces.append(interface) - - return list(set(interfaces)) # Eliminar duplicados + # El estado generalmente está en la posición después del nombre/descripción + # Formato típico: Port Name Status Vlan... + # O: Port Status Vlan... (sin nombre) + status_raw = parts[1] if len(parts) > 1 else "unknown" + + # Si parts[1] parece ser un estado válido, úsalo + # De lo contrario, asume que parts[1] es el nombre y parts[2] es el estado + if status_raw.lower() in ['connected', 'notconnect', 'disabled', 'up', 'down', 'admin']: + status = self._normalize_status(status_raw) + elif len(parts) > 2: + status = self._normalize_status(parts[2]) + else: + status = "unknown" + + self.interface_info[interface] = InterfaceInfo( + name=interface, + status=status, + description="" + ) - def _parse_interface_line_protocol(self, output: str) -> List[str]: + def _parse_interface_line_protocol_to_info(self, output: str): """Parsea la salida de show interface | include line protocol""" - interfaces = [] - lines = output.split('\n') for line in lines: # Format: FastEthernet0/1 is up, line protocol is up if ' is ' in line and 'line protocol' in line: - interface = line.split(' is ')[0].strip() + parts = line.split(' is ') + interface = parts[0].strip() if self._is_physical_interface(interface): - interfaces.append(interface) + # Extraer el estado + status = "unknown" + if len(parts) > 1: + status_part = parts[1].split(',')[0].strip() + status = self._normalize_status(status_part) + + self.interface_info[interface] = InterfaceInfo( + name=interface, + status=status, + description="" + ) + + def _get_interface_descriptions(self, ssh_connector: CiscoSSHConnector): + """Obtiene las descripciones de las interfaces""" + try: + command = "show interface description" + output = ssh_connector.execute_command(command) + + # Si el comando no es válido, intentar alternativa + if "Invalid input" in output or not output.strip(): + # Intentar obtener descripciones una por una (más lento pero funciona en IOS antiguos) + self.logger.debug("show interface description no disponible, usando método alternativo") + return + + self._parse_interface_descriptions(output) + + except Exception as e: + self.logger.warning(f"Error obteniendo descripciones de interfaces: {str(e)}") + + def _parse_interface_descriptions(self, output: str): + """Parsea la salida de show interface description""" + lines = output.split('\n') + for line in lines: + line_stripped = line.strip() + if not line_stripped or line_stripped.startswith('-') or 'Interface' in line_stripped: + continue + + # Format: Interface Status Protocol Description + # Fa0/1 up up User Port + # Usamos split con máximo 3 splits para preservar la descripción completa + parts = line.split(None, 3) # Split en whitespace, máximo 4 partes + + if len(parts) >= 1: + interface = parts[0] + description = "" + + if len(parts) >= 4: + description = parts[3].strip() + + # Si esta interfaz ya existe en nuestro diccionario, actualizar descripción + if interface in self.interface_info: + self.interface_info[interface].description = description + + def _normalize_status(self, status_raw: str) -> str: + """Normaliza el estado de la interfaz a up/down/disabled/unknown""" + status_lower = status_raw.lower() - return list(set(interfaces)) + if status_lower in ['up', 'connected']: + return "up" + elif status_lower in ['down', 'notconnect', 'notconnected']: + return "down" + elif status_lower in ['disabled', 'admin', 'administratively']: + return "disabled" + else: + return "unknown" def _is_physical_interface(self, interface: str) -> bool: """Verifica si una interfaz es física y relevante para monitorización""" @@ -343,7 +427,7 @@ class ZabbixLLDFormatter: self.device_hostname = device_hostname self.logger = logging.getLogger(__name__) - def format_for_zabbix(self, mac_entries: List[MacEntry], all_interfaces: List[str] = None) -> Dict[str, Any]: + def format_for_zabbix(self, mac_entries: List[MacEntry], interface_info: Dict[str, Any] = None) -> Dict[str, Any]: """Formatea datos en formato JSON estructurado por puertos""" discovery_data = [] # Usar timestamp Unix para compatibilidad con Zabbix @@ -362,7 +446,9 @@ class ZabbixLLDFormatter: "device_hostname": self.device_hostname, "mac_addresses": [], "mac_count": 0, - "vlans": set() + "vlans": set(), + "status": "unknown", + "description": "" } ports_data[port]["mac_addresses"].append({ @@ -372,17 +458,23 @@ class ZabbixLLDFormatter: }) ports_data[port]["vlans"].add(entry.vlan) - # Añadir interfaces sin MACs si tenemos la lista de todas las interfaces - if all_interfaces: - for interface in all_interfaces: - if interface not in ports_data: - ports_data[interface] = { - "port_name": interface, + # Añadir todas las interfaces conocidas (con o sin MACs) + if interface_info: + for interface_name, iface_info in interface_info.items(): + if interface_name not in ports_data: + ports_data[interface_name] = { + "port_name": interface_name, "device_hostname": self.device_hostname, "mac_addresses": [], "mac_count": 0, - "vlans": set() + "vlans": set(), + "status": iface_info.status, + "description": iface_info.description } + else: + # Actualizar estado y descripción para puertos que ya tienen MACs + ports_data[interface_name]["status"] = iface_info.status + ports_data[interface_name]["description"] = iface_info.description # Crear entradas en el nuevo formato for port, data in ports_data.items(): @@ -405,6 +497,8 @@ class ZabbixLLDFormatter: "count": data["mac_count"], "vlans": ",".join(data["vlans"]) if data["vlans"] else "", "addresses": mac_addresses_text, + "status": data.get("status", "unknown"), + "description": data.get("description", ""), "timestamp": unix_timestamp } } @@ -520,8 +614,11 @@ Salida: if not mac_entries: logger.warning("No se obtuvieron entradas de tabla MAC") - # Formatear para Zabbix - zabbix_data = zabbix_formatter.format_for_zabbix(mac_entries) + # Formatear para Zabbix incluyendo información de interfaces + zabbix_data = zabbix_formatter.format_for_zabbix( + mac_entries, + mac_parser.interface_info + ) # Generar salida JSON json_output = json.dumps(zabbix_data, indent=2, ensure_ascii=False) diff --git a/run.sh b/run.sh index ddeba6d..9b74804 100755 --- a/run.sh +++ b/run.sh @@ -1,5 +1,9 @@ #!/bin/bash +docker stop cisco-friends + +docker rm cisco-friends + docker run -d --platform linux/amd64 \ --restart unless-stopped \ -p 127.0.0.1:8443:8443 \ diff --git a/test_new_format.py b/test_new_format.py index 625c330..0bdfb5e 100755 --- a/test_new_format.py +++ b/test_new_format.py @@ -7,7 +7,7 @@ import sys import os sys.path.append(os.path.dirname(os.path.abspath(__file__))) -from cisco_mac_monitor import MacEntry, ZabbixLLDFormatter +from cisco_mac_monitor import MacEntry, InterfaceInfo, ZabbixLLDFormatter import json # Crear datos de prueba @@ -17,9 +17,16 @@ test_mac_entries = [ MacEntry(vlan="151", mac_address="00:50:56:a6:91:69", type="DYNAMIC", port="Gi1/0/10"), ] +# Crear información de interfaces de prueba (incluye estado y descripción) +test_interface_info = { + "Gi1/0/1": InterfaceInfo(name="Gi1/0/1", status="up", description="User Port - Office"), + "Gi1/0/10": InterfaceInfo(name="Gi1/0/10", status="up", description="Server Link"), + "Gi1/0/11": InterfaceInfo(name="Gi1/0/11", status="down", description="Unused Port"), +} + # Crear formatter y formatear datos formatter = ZabbixLLDFormatter("10.150.250.2") -result = formatter.format_for_zabbix(test_mac_entries, ["Gi1/0/1", "Gi1/0/10", "Gi1/0/11"]) +result = formatter.format_for_zabbix(test_mac_entries, test_interface_info) # Mostrar resultado print(json.dumps(result, indent=2, ensure_ascii=False)) @@ -39,5 +46,7 @@ for item in data: print(f" - Count: {port_data['count']}") print(f" - VLANs: '{port_data['vlans']}'") print(f" - Addresses: '{port_data['addresses']}'") + print(f" - Status: '{port_data['status']}'") + print(f" - Description: '{port_data['description']}'") print(f" - Timestamp: {port_data['timestamp']}") print() \ No newline at end of file