fix: IP-Bereiche statt Prefixe automatisch verwalten

- nutzbare Hostbereiche aus der CIDR-Maske erzeugen
- Netzwerk- und Broadcastadressen bei IPv4 ausschließen
- Abgleich-Aktion in die IP-Bereichsliste verschieben
- Gerät oder VM in der IP-Adressansicht anzeigen
- Bestandsabgleich für IP-Bereiche ergänzen
- Plugin-Version auf 1.2.0 erhöhen
This commit is contained in:
2026-07-23 12:08:59 +02:00
parent acb4e449bc
commit 8be6978d81
14 changed files with 88 additions and 107 deletions
+24 -48
View File
@@ -1,9 +1,8 @@
import ipaddress
import logging
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from ipam.models import Prefix
from ipam.models import IPRange
from netbox.plugins import get_plugin_config
logger = logging.getLogger(__name__)
@@ -14,32 +13,6 @@ def network_for_address(address):
return ipaddress.ip_interface(str(address)).network
def _scope_from_ip(ip):
"""Derive the most specific supported Prefix scope from an IP assignment."""
assigned = getattr(ip, "assigned_object", None)
if assigned is None:
return None
parent = (
getattr(assigned, "parent_object", None)
or getattr(assigned, "device", None)
or getattr(assigned, "virtual_machine", None)
)
if parent is None:
return None
location = getattr(parent, "location", None)
if location is not None:
return location
site = getattr(parent, "site", None)
if site is not None:
return site
cluster = getattr(parent, "cluster", None)
return getattr(cluster, "scope", None) if cluster is not None else None
def _tenant_from_ip(ip):
tenant = getattr(ip, "tenant", None)
if tenant is not None:
@@ -55,37 +28,40 @@ def _tenant_from_ip(ip):
@transaction.atomic
def ensure_prefix_for_ip(ip, *, config=None):
"""Get or create the exact Prefix represented by an IP address and its mask.
NetBox derives hierarchy from address/prefix + VRF. There is deliberately no
parent FK to update on IPAddress; creating the missing Prefix is the assignment.
"""
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 (
"prefix_status", "inherit_scope", "inherit_tenant", "ignore_host_prefixes"
)
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
lookup = {"prefix": str(network), "vrf_id": ip.vrf_id}
existing = Prefix.objects.filter(**lookup).first()
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
start_address = f"{ipaddress.ip_address(first)}/{network.prefixlen}"
end_address = 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("prefix_status", "active")}
defaults = {"status": settings.get("range_status", "active")}
if settings.get("inherit_tenant", True):
defaults["tenant"] = _tenant_from_ip(ip)
if settings.get("inherit_scope", True):
scope = _scope_from_ip(ip)
if scope is not None:
defaults["scope_type"] = ContentType.objects.get_for_model(scope)
defaults["scope_id"] = scope.pk
prefix, created = Prefix.objects.get_or_create(defaults=defaults, **lookup)
ip_range, created = IPRange.objects.get_or_create(defaults=defaults, **lookup)
if created:
logger.info("Created missing prefix %s for IP address %s", prefix, ip)
return prefix, created
logger.info("Created missing IP range %s for IP address %s", ip_range, ip)
return ip_range, created