pyATS Network Topology Discovery
Live demo: automated ABR discovery, traceroute mapping, and hostname enrichment
This demo shows an automated discovery workflow I built with Python, pyATS, and Genie. The script connects to Cisco routers, identifies OSPF Area Border Routers, runs traceroutes, and then enriches the results with hostnames from an external inventory source. That last step matters more than it sounds. Raw traceroute output full of IP addresses is useful, but once those hops are mapped to recognizable names, the topology becomes much easier to review and share. Source routers appear on the left, discovered ABRs on the right, and intermediate hops in the middle. Toggle between IP and hostname views, click a node for details, or run the animation to watch the workflow unfold.
Example Python code
# Load inventory & hostname map from SharePoint / Excel inventory_df = pd.read_csv("Device Inventory.csv") hostname_df = pd.read_excel("Node_to_IP.xlsx") ip_to_name = dict(zip(hostname_df["IP"], hostname_df["Hostname"])) # Load pyATS testbed and iterate over all source routers testbed = load("testbed.yaml") all_abr_dfs, all_trace_dfs = [], [] for device_name, device in testbed.devices.items(): try: device.connect(log_stdout=False) # Step 1: discover ABRs from OSPF table raw_abr = device.execute( "show ip ospf border-routers | i ABR" ) abr_df = abr_cmd_parser(raw_abr, device_name, ip_to_name) all_abr_dfs.append(abr_df) # Step 2: traceroute to each discovered ABR for _, row in abr_df.iterrows(): raw_trace = device.execute( f"traceroute {row['abr_ip']}" ) trace_df = traceroute_parser( raw_trace, ip_to_name, row["abr_ip"], row["abr_name"], device_name ) all_trace_dfs.append(trace_df) except Exception as e: print(f"Failed on {device_name}: {e}") finally: device.disconnect() # Export enriched results to Excel + SharePoint final_df = pd.concat(all_trace_dfs, ignore_index=True) final_df.to_excel("traceroute_results.xlsx", index=False)
def abr_cmd_parser(raw_output, device_name, ip_to_short_name_dict): """Parse 'show ip ospf border-routers | i ABR' output. Extracts ABR IPs via regex, resolves hostnames from inventory dict.""" ip_pattern = r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' records = [] for line in raw_output.splitlines(): ips = re.findall(ip_pattern, line) for ip in ips: hostname = ip_to_short_name_dict.get(ip, "UNKNOWN") records.append({ "device": device_name, "abr_ip": ip, "abr_name": hostname, }) return pd.DataFrame(records)
def traceroute_parser(traceroute_output, ip_to_short_name_dict, destination_ip, destination_abr, device_name): """Parse traceroute output. Enrich each hop IP with hostname from the inventory dict, turning opaque IPs into readable names.""" ip_pattern = r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' records = [] hop_num = 0 for line in traceroute_output.splitlines(): ips = re.findall(ip_pattern, line) if ips: hop_num += 1 hop_ip = ips[0] hostname = ip_to_short_name_dict.get(hop_ip, "UNKNOWN") records.append({ "device": device_name, "hop": hop_num, "hop_ip": hop_ip, "hop_name": hostname, "dest_ip": destination_ip, "dest_abr": destination_abr, }) return pd.DataFrame(records)