Last active
April 2, 2024 00:07
-
-
Save candlerb/5380a7cdd03b60fbd02a664feb266d44 to your computer and use it in GitHub Desktop.
Netbox report to check for missing Primary IP Addresses
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from extras.reports import Report | |
from dcim.models import Device | |
from virtualization.models import VirtualMachine | |
from ipam.constants import * | |
from ipam.models import IPAddress, Prefix | |
LOOPBACK_ROLES = [ | |
IPADDRESS_ROLE_LOOPBACK, | |
IPADDRESS_ROLE_ANYCAST, | |
IPADDRESS_ROLE_VIP, | |
] | |
class CheckPrimaryAddress(Report): | |
description = "Check that every device and VM has a primary IP address assigned" | |
def test_device_primary_ips(self): | |
for device in Device.objects.prefetch_related('interfaces__ip_addresses').all(): | |
fail = False | |
all_addrs = {4: [], 6: []} | |
for interface in device.interfaces.all(): | |
if not interface.mgmt_only: | |
for addr in interface.ip_addresses.exclude(status=IPADDRESS_STATUS_DEPRECATED).all(): | |
all_addrs[addr.family].append(addr) | |
# There may be dumb devices with no interfaces / IP addresses, that's OK | |
if not device.primary_ip4 and all_addrs[4]: | |
self.log_failure(device, "Device has no primary IPv4 address (could be %s)" % | |
" ".join([str(a) for a in all_addrs[4]])) | |
fail = True | |
if not device.primary_ip6 and all_addrs[6]: | |
self.log_failure(device, "Device has no primary IPv6 address (could be %s)" % | |
" ".join([str(a) for a in all_addrs[6]])) | |
fail = True | |
if not fail: | |
self.log_success(device) | |
def test_vm_primary_ips(self): | |
for vm in VirtualMachine.objects.prefetch_related('interfaces__ip_addresses').all(): | |
fail = False | |
all_addrs = {4: [], 6: []} | |
for interface in vm.interfaces.all(): | |
if not interface.mgmt_only: | |
for addr in interface.ip_addresses.exclude(status=IPADDRESS_STATUS_DEPRECATED).all(): | |
all_addrs[addr.family].append(addr) | |
# A VM is useless without an IP address | |
if not all_addrs[4] and not all_addrs[6]: | |
self.log_failure(vm, "Virtual machine has no IP addresses") | |
continue | |
if not vm.primary_ip4 and all_addrs[4]: | |
self.log_failure(vm, "Virtual machine has no primary IPv4 address (could be %s)" % | |
" ".join([str(a) for a in all_addrs[4]])) | |
fail = True | |
if not vm.primary_ip6 and all_addrs[6]: | |
self.log_failure(vm, "Virtual machine has no primary IPv6 address (could be %s)" % | |
" ".join([str(a) for a in all_addrs[6]])) | |
fail = True | |
if not fail: | |
self.log_success(vm) | |
class CheckPrefixLength(Report): | |
description = "Check each IP address has the prefix length of the enclosing subnet" | |
def test_prefix_lengths(self): | |
prefixes = list(Prefix.objects.all()) | |
prefixes.sort(key=lambda k: k.prefix) # overlapping subnets sort in order from largest to smallest | |
for ipaddr in IPAddress.objects.all(): | |
a = ipaddr.address | |
if ipaddr.family != a.version: | |
self.log_failure(ipaddr, "family (%d) inconsistent with address.version (%d)" % | |
(ipaddr.family, a.version)) | |
continue | |
# We allow loopback-like things to be single address *or* have the parent prefix length | |
if ipaddr.role in LOOPBACK_ROLES and ( | |
(a.version == 4 and a.prefixlen == 32) or | |
(a.version == 6 and a.prefixlen == 128)): | |
self.log_success(ipaddr) | |
continue | |
parents = [p for p in prefixes if | |
(p.vrf and p.vrf.id) == (ipaddr.vrf and ipaddr.vrf.id) and | |
p.prefix.version == a.version and a.ip in p.prefix] | |
if not parents: | |
self.log_info(ipaddr, "No parent prefix") | |
continue | |
parent = parents[-1] | |
if a.prefixlen != parent.prefix.prefixlen: | |
self.log_failure(ipaddr, "prefixlen (%d) inconsistent with parent prefix (%s)" % | |
(a.prefixlen, str(parent.prefix))) | |
continue | |
# if the parent prefix also contains child prefixes, that probably means that | |
# an intermediate parent prefix is missing | |
pchildren = [p for p in prefixes if | |
(p.vrf and p.vrf.id) == (parent.vrf and parent.vrf.id) and | |
p.prefix.version == parent.prefix.version and | |
p.prefix != parent.prefix and | |
p.prefix in parent.prefix] | |
if pchildren: | |
self.log_warning(ipaddr, "parent prefix (%s) contains %d other child prefix(es)" % | |
(str(parent.prefix), len(pchildren))) | |
continue | |
self.log_success(ipaddr) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment