251 lines
No EOL
9.5 KiB
Python
251 lines
No EOL
9.5 KiB
Python
#!/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() |