import ipaddress from typing import List, Dict, Set from Decipher.models import NetworkTopology def validate_topology(topology: NetworkTopology) -> List[str]: """ Performs logical validation on the network topology and returns a list of warnings/errors. """ issues = [] device_names = {d.name for d in topology.devices} ip_to_interface = {} # ip -> (device, interface) # 1. Check for IP Conflicts and basic interface sanity for device in topology.devices: for intf in device.interfaces: if intf.ip: # Check for duplicate IPs if intf.ip in ip_to_interface: other_dev, other_intf = ip_to_interface[intf.ip] issues.append(f"IP Conflict: {intf.ip} is assigned to both {device.name} ({intf.name}) and {other_dev} ({other_intf})") else: ip_to_interface[intf.ip] = (device.name, intf.name) # Basic IP format validation try: if "/" in intf.ip: ipaddress.ip_interface(intf.ip) else: ipaddress.ip_address(intf.ip) except ValueError: issues.append(f"Invalid IP Format: '{intf.ip}' on {device.name} ({intf.name})") # 2. Ensure Link Endpoints Exist for link in topology.physical_links: if link.from_device not in device_names: issues.append(f"Broken Link: Source device '{link.from_device}' does not exist.") if link.to_device not in device_names: issues.append(f"Broken Link: Destination device '{link.to_device}' does not exist.") # 3. Verify Routing Protocols (Basic Sanity) for device in topology.devices: if device.routing and device.routing.ospf: device_ips = [intf.ip for intf in device.interfaces if intf.ip] for ospf in device.routing.ospf: for net_statement in ospf.networks: # Check if the OSPF network statement matches at least one interface IP segment try: # network statement is usually "network 10.0.0.0 0.0.0.255 area 0" # we extract the network and wildcard mask parts = net_statement.split() if "network" in parts: net_addr = parts[parts.index("network") + 1] # Simple check: does any device IP start with the same prefix? # This is a heuristic check found_match = False for ip in device_ips: if ip.split('.')[0:2] == net_addr.split('.')[0:2]: found_match = True break if not found_match: issues.append(f"OSPF Warning: Network statement '{net_statement}' on {device.name} doesn't seem to match any local interface IPs.") except Exception: pass return issues