import ipaddress import logging import netaddr from django.db import transaction from ipam.models import IPRange from netbox.plugins import get_plugin_config logger = logging.getLogger(__name__) def network_for_address(address): """Return the canonical network encoded by an IPAddress.address value.""" return ipaddress.ip_interface(str(address)).network def _tenant_from_ip(ip): tenant = getattr(ip, "tenant", None) if tenant is not None: return tenant assigned = getattr(ip, "assigned_object", None) parent = ( getattr(assigned, "parent_object", None) or getattr(assigned, "device", None) or getattr(assigned, "virtual_machine", None) or assigned ) return getattr(parent, "tenant", None) @transaction.atomic def ensure_range_for_ip(ip, *, config=None): """Get or create the usable-host IPRange represented by an IP and its mask.""" settings = config or { key: get_plugin_config("netbox_better_ips", key) for key in ("range_status", "inherit_tenant", "ignore_host_prefixes") } network = network_for_address(ip.address) if settings.get("ignore_host_prefixes", True) and network.prefixlen == network.max_prefixlen: return None, False first = int(network.network_address) last = int(network.broadcast_address) if network.version == 4 and network.prefixlen < 31: first += 1 last -= 1 elif network.version == 6 and network.prefixlen < 127: first += 1 # IPRange.save() performs arithmetic on the field values before Django's # field conversion runs, so these must be netaddr objects rather than strings. start_address = netaddr.IPNetwork(f"{ipaddress.ip_address(first)}/{network.prefixlen}") end_address = netaddr.IPNetwork(f"{ipaddress.ip_address(last)}/{network.prefixlen}") lookup = { "start_address": start_address, "end_address": end_address, "vrf_id": ip.vrf_id, } existing = IPRange.objects.filter(**lookup).first() if existing: return existing, False defaults = {"status": settings.get("range_status", "active")} if settings.get("inherit_tenant", True): defaults["tenant"] = _tenant_from_ip(ip) ip_range, created = IPRange.objects.get_or_create(defaults=defaults, **lookup) if created: logger.info("Created missing IP range %s for IP address %s", ip_range, ip) return ip_range, created