From 5f548725ee6ded9cb1c2a67fa1efc5e4e945c17e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 2 Apr 2025 09:35:21 -0400 Subject: [PATCH] Update utilized/reserved logic for Prefix and IPRange --- netbox/ipam/models/ip.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 97c154ed6..6a145397a 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -407,15 +407,16 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary """ Return all available IPs within this prefix as an IPSet. """ - if self.mark_utilized: - return netaddr.IPSet() + # TODO: Add mark_reserved prefix = netaddr.IPSet(self.prefix) - child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()]) - child_ranges = [] - for iprange in self.get_child_ranges(): - child_ranges.append(iprange.range) - available_ips = prefix - child_ips - netaddr.IPSet(child_ranges) + child_ips = netaddr.IPSet([ + ip.address.ip for ip in self.get_child_ips() + ]) + child_ranges = netaddr.IPSet([ + iprange.range for iprange in self.get_child_ranges().filter(mark_reserved=True) + ]) + available_ips = prefix - child_ips - child_ranges # IPv6 /127's, pool, or IPv4 /31-/32 sets are fully usable if (self.family == 6 and self.prefix.prefixlen >= 127) or self.is_pool or ( @@ -433,6 +434,7 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary # For IPv6 prefixes, omit the Subnet-Router anycast address # per RFC 4291 available_ips -= netaddr.IPSet([netaddr.IPAddress(self.prefix.first)]) + return available_ips def get_first_available_ip(self): @@ -461,9 +463,11 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary utilization = float(child_prefixes.size) / self.prefix.size * 100 else: # Compile an IPSet to avoid counting duplicate IPs - child_ips = netaddr.IPSet( - [_.range for _ in self.get_child_ranges()] + [_.address.ip for _ in self.get_child_ips()] - ) + child_ips = netaddr.IPSet() + for iprange in self.get_child_ranges().filter(mark_utilized=True): + child_ips.add(iprange.range) + for ip in self.get_child_ips(): + child_ips.add(ip.address.ip) prefix_size = self.prefix.size if self.prefix.version == 4 and self.prefix.prefixlen < 31 and not self.is_pool: @@ -668,6 +672,9 @@ class IPRange(ContactsMixin, PrimaryModel): """ Return all available IPs within this range as an IPSet. """ + if self.mark_reserved: + return netaddr.IPSet() + range = netaddr.IPRange(self.start_address.ip, self.end_address.ip) child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()])