#!/usr/bin/env python3 import requests import json from urllib.parse import urljoin class NetBoxClient: def __init__(self, url, token): self.base_url = url.rstrip('/') self.api_url = urljoin(self.base_url + '/', 'api/') self.headers = { 'Authorization': f'Token {token}', 'Content-Type': 'application/json', 'Accept': 'application/json' } self.session = requests.Session() self.session.headers.update(self.headers) def get(self, endpoint, params=None): """Make GET request to NetBox API""" url = urljoin(self.api_url, endpoint.lstrip('/')) response = self.session.get(url, params=params) response.raise_for_status() return response.json() def get_all(self, endpoint, params=None): """Get all results handling pagination""" if params is None: params = {} params['limit'] = 100 all_results = [] url = urljoin(self.api_url, endpoint.lstrip('/')) while url: response = self.session.get(url, params=params) response.raise_for_status() data = response.json() all_results.extend(data['results']) url = data['next'] params = None # Don't send params on subsequent requests return all_results # Site methods def get_sites(self, **kwargs): return self.get_all('dcim/sites/', params=kwargs) def get_site_by_name(self, name): sites = self.get('dcim/sites/', params={'name': name}) if sites['count'] > 0: return sites['results'][0] return None # Prefix methods def get_prefixes(self, **kwargs): return self.get_all('ipam/prefixes/', params=kwargs) def get_prefixes_by_site(self, site_name): """Get all prefixes associated with a site""" site = self.get_site_by_name(site_name) if not site: return [] return self.get_all('ipam/prefixes/', params={'site_id': site['id']}) # IP Address methods def get_ip_addresses(self, **kwargs): return self.get_all('ipam/ip-addresses/', params=kwargs) def get_ip_addresses_by_site(self, site_name): """Get all IP addresses associated with a site""" site = self.get_site_by_name(site_name) if not site: return [] # First try by site_id ips = self.get_all('ipam/ip-addresses/', params={'site_id': site['id']}) # Also get IPs assigned to devices at this site devices = self.get_all('dcim/devices/', params={'site_id': site['id']}) for device in devices: device_ips = self.get_all('ipam/ip-addresses/', params={'device_id': device['id']}) ips.extend(device_ips) # Remove duplicates seen = set() unique_ips = [] for ip in ips: if ip['id'] not in seen: seen.add(ip['id']) unique_ips.append(ip) return unique_ips # Device methods def get_devices_by_site(self, site_name): """Get all devices at a site""" site = self.get_site_by_name(site_name) if not site: return [] return self.get_all('dcim/devices/', params={'site_id': site['id']}) # VLAN methods def get_vlans_by_site(self, site_name): """Get all VLANs at a site""" site = self.get_site_by_name(site_name) if not site: return [] return self.get_all('ipam/vlans/', params={'site_id': site['id']}) def compare_subnets(): """Compare subnets from router with NetBox""" # NetBox connection nb = NetBoxClient('https://netbox.vntx.net/', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde') # Subnets found on router (from previous scan) router_subnets = [ {'address': '10.250.1.25/29', 'network': '10.250.1.24', 'interface': 'ether3-climax-11ghz'}, {'address': '10.254.254.101/32', 'network': '10.254.254.101', 'interface': 'loopback'}, {'address': '100.64.3.254/22', 'network': '100.64.0.0', 'interface': 'verona'}, {'address': '204.110.188.254/27', 'network': '204.110.188.224', 'interface': 'verona'}, {'address': '100.64.15.254/22', 'network': '100.64.12.0', 'interface': 'vlan_19_ether6'}, {'address': '10.10.95.254/20', 'network': '10.10.80.0', 'interface': 'vlan_10_ether6'}, {'address': '10.10.15.254/20', 'network': '10.10.0.0', 'interface': 'vlan_10_ether6'}, {'address': '10.0.101.254/24', 'network': '10.0.101.0', 'interface': 'sfp-sfpplus1-verona-tower-switch'}, {'address': '10.250.1.145/29', 'network': '10.250.1.144', 'interface': 'ether6-switch'}, {'address': '204.110.191.30/27', 'network': '204.110.191.0', 'interface': 'vlan9_sfpplus1'}, {'address': '10.25.1.254/24', 'network': '10.25.1.0', 'interface': 'ether1'}, {'address': '192.168.99.254/24', 'network': '192.168.99.0', 'interface': 'ether10-powerswitch'}, ] print("=== Checking Verona Site Subnets in NetBox ===\n") # Get site info site = nb.get_site_by_name('Verona') if site: print(f"Site: {site['name']} (ID: {site['id']})") print(f"Status: {site['status']['label']}") print(f"Address: {site['physical_address']}") print() # Get prefixes from NetBox print("Fetching NetBox data...") prefixes = nb.get_prefixes_by_site('Verona') ip_addresses = nb.get_ip_addresses_by_site('Verona') vlans = nb.get_vlans_by_site('Verona') devices = nb.get_devices_by_site('Verona') print(f"\nFound in NetBox:") print(f"- {len(prefixes)} prefixes") print(f"- {len(ip_addresses)} IP addresses") print(f"- {len(vlans)} VLANs") print(f"- {len(devices)} devices") # Check if we need to search more broadly if len(prefixes) == 0: print("\nNo prefixes found with site association. Searching by description/comments...") all_prefixes = nb.get_prefixes() prefixes = [p for p in all_prefixes if 'verona' in (p.get('description', '') + p.get('comments', '')).lower()] print(f"Found {len(prefixes)} prefixes with 'verona' in description/comments") # Display NetBox prefixes if prefixes: print("\n=== Prefixes in NetBox ===") for prefix in prefixes: status = prefix['status']['label'] if prefix.get('status') else 'Unknown' role = prefix['role']['name'] if prefix.get('role') else 'None' description = prefix.get('description', '') print(f"\n{prefix['prefix']}") print(f" Status: {status}") print(f" Role: {role}") if description: print(f" Description: {description}") # Display NetBox IP addresses if ip_addresses: print("\n\n=== IP Addresses in NetBox ===") for ip in ip_addresses: status = ip['status']['label'] if ip.get('status') else 'Unknown' role = ip['role']['name'] if ip.get('role') else 'None' interface = ip.get('assigned_object', {}).get('name', 'Not assigned') if ip.get('assigned_object') else 'Not assigned' print(f"\n{ip['address']}") print(f" Status: {status}") print(f" Role: {role}") print(f" Interface: {interface}") # Compare with router subnets print("\n\n=== Comparison Analysis ===") # Extract prefixes from NetBox data netbox_prefixes = set(p['prefix'] for p in prefixes) netbox_ips = set(ip['address'] for ip in ip_addresses) # Check each router subnet missing_subnets = [] missing_ips = [] for subnet in router_subnets: # Check if the subnet prefix exists subnet_cidr = f"{subnet['network']}/{subnet['address'].split('/')[-1]}" found_prefix = False found_ip = False # Check against prefixes for prefix in netbox_prefixes: if subnet_cidr == prefix or subnet['address'] == prefix: found_prefix = True break # Check against IP addresses if subnet['address'] in netbox_ips: found_ip = True if not found_prefix and not found_ip: if '/32' in subnet['address']: missing_ips.append(subnet) else: missing_subnets.append(subnet) # Report findings print(f"\nMissing Subnets (not in NetBox):") if missing_subnets: for subnet in missing_subnets: print(f" - {subnet['network']}/{subnet['address'].split('/')[-1]} ({subnet['interface']})") else: print(" None - all subnets are documented") print(f"\nMissing IP Addresses (not in NetBox):") if missing_ips: for ip in missing_ips: print(f" - {ip['address']} ({ip['interface']})") else: print(" None - all IPs are documented") # Summary total_router_subnets = len([s for s in router_subnets if '/32' not in s['address']]) total_router_ips = len([s for s in router_subnets if '/32' in s['address']]) print(f"\n=== Summary ===") print(f"Router has {total_router_subnets} subnets and {total_router_ips} host IPs") print(f"NetBox has {len(prefixes)} prefixes and {len(ip_addresses)} IP addresses for Verona") print(f"Missing from NetBox: {len(missing_subnets)} subnets and {len(missing_ips)} IPs") return missing_subnets, missing_ips if __name__ == "__main__": compare_subnets()