Repository: netbox-community/netbox

Issue:
Assigned network IP possible via bulk edit
### Deployment Type

Self-hosted

### NetBox Version

v3.7.2

### Python Version

3.11

### Steps to Reproduce

1. Create multiple IP addresses in 192.168.0.0/24, including 192.168.0.128/24 assigned to an interface
2. Mark the IP addresses in the overview and click „Edit selected“
3. Set „Mask length“ to 25 and click „Apply“

### Expected Behavior

I would expect the same behavior like creating 192.168.0.128/25 directly. This is not possible because it is a network address. NetBox complains about that with a warning popup and doesn't save the IP address.

### Observed Behavior

There now is a IP address 192.168.0.128/25

![screenshot_ip_addresses](https://github.com/netbox-community/netbox/assets/12829297/e1079eb9-eafa-4dee-8973-7151a301a4f1)



Discussion:

Resolving patch:
diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py
--- a/netbox/ipam/forms/model_forms.py
+++ b/netbox/ipam/forms/model_forms.py
@@ -367,20 +367,6 @@ def clean(self):
                 'primary_for_parent', _("Only IP addresses assigned to an interface can be designated as primary IPs.")
             )
 
-        # Do not allow assigning a network ID or broadcast address to an interface.
-        if interface and (address := self.cleaned_data.get('address')):
-            if address.ip == address.network:
-                msg = _("{ip} is a network ID, which may not be assigned to an interface.").format(ip=address.ip)
-                if address.version == 4 and address.prefixlen not in (31, 32):
-                    raise ValidationError(msg)
-                if address.version == 6 and address.prefixlen not in (127, 128):
-                    raise ValidationError(msg)
-            if address.version == 4 and address.ip == address.broadcast and address.prefixlen not in (31, 32):
-                msg = _("{ip} is a broadcast address, which may not be assigned to an interface.").format(
-                    ip=address.ip
-                )
-                raise ValidationError(msg)
-
     def save(self, *args, **kwargs):
         ipaddress = super().save(*args, **kwargs)
 
diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py
--- a/netbox/ipam/models/ip.py
+++ b/netbox/ipam/models/ip.py
@@ -844,6 +844,25 @@ def clean(self):
                     'address': _("Cannot create IP address with /0 mask.")
                 })
 
+            # Do not allow assigning a network ID or broadcast address to an interface.
+            if self.assigned_object:
+                if self.address.ip == self.address.network:
+                    msg = _("{ip} is a network ID, which may not be assigned to an interface.").format(
+                        ip=self.address.ip
+                    )
+                    if self.address.version == 4 and self.address.prefixlen not in (31, 32):
+                        raise ValidationError(msg)
+                    if self.address.version == 6 and self.address.prefixlen not in (127, 128):
+                        raise ValidationError(msg)
+                if (
+                        self.address.version == 4 and self.address.ip == self.address.broadcast and
+                        self.address.prefixlen not in (31, 32)
+                ):
+                    msg = _("{ip} is a broadcast address, which may not be assigned to an interface.").format(
+                        ip=self.address.ip
+                    )
+                    raise ValidationError(msg)
+
             # Enforce unique IP space (if applicable)
             if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique):
                 duplicate_ips = self.get_duplicates()


Test patch: