Repository: netbox-community/netbox

Issue:
IPAM allows creation of IPAddresses with '0' mask length
<!--
    NOTE: This form is only for reproducible bugs. If you need assistance with
    NetBox installation, or if you have a general question, DO NOT open an
    issue. Instead, post to our mailing list:

        https://groups.google.com/forum/#!forum/netbox-discuss

    Please describe the environment in which you are running NetBox. Be sure
    that you are running an unmodified instance of the latest stable release
    before submitting a bug report.
-->
### Environment
* Python version:  3.6.8<!-- Example: 3.5.4 -->
* NetBox version:  v2.6.7<!-- Example: 2.5.2 -->

<!--
    Describe in detail the exact steps that someone else can take to reproduce
    this bug using the current stable release of NetBox (or the current beta
    release where applicable). Begin with the creation of any necessary
    database objects and call out every operation being performed explicitly.
    If reporting a bug in the REST API, be sure to reconstruct the raw HTTP
    request(s) being made: Don't rely on a wrapper like pynetbox.
-->
### Steps to Reproduce
1. Create an IP address with a `/0` mask

<!-- What did you expect to happen? -->
### Expected Behavior
Validation error returned in form when trying to create an IP address with a mask of 0.

<!-- What happened instead? -->
### Observed Behavior
Netbox GUI times out and returns HTTP 504 while trying to load IPAddress detail page. You can see the IP is successfully created in the changelog, but any attempt to view the detail page times out with a reasonable nginx/gunicorn worker timeout (<60 seconds).


Discussion:
I couldn't replicate this on a fresh install of v2.6.7 or v2.6.11 (httpd/gunicorn). I was able to create an IP address with a `/0` mask and I can browse to it and do everything as usual. Anything special about the deployment?
I suspect you may not see this behavior on a fresh install. The issue (I'm guessing) is that since all the IP/prefix relationships are done in CPU - and when you have a large number of prefixes and IP addresses, the calculation for a `/0` takes an enormous amount of time.

And since the calculation is single-threaded, there's no speeding it up by increasing worker counts. The only solution would then be to increase the gunicorn/nginx worker timeout to some absurdly high number - but anything higher than 60s for a synchronous user-interactive webapp is... not ideal.

This dev environment has the gunicorn worker set for 120s timeout and it's getting hit every time.
I'll generate some dummy addresses and give it another try. How many addresses do you have?
```
IP Addresses:     169,115
Prefixes:          36,082
```
The API returns a response immediately so this is definitely an issue with the IP/Prefix calculations. Those aren't shown in the IP address detail output.
Now that I've got 200k+ addresses, I can see what you're referring to. It doesn't even need to be a `/0` address, though that one is particularly dangerous as it includes everything.

It's the related IPs table that kills it as it has no limit on the table size, so it tries to render every single address.

Adding a limit or paginating the table minimizes these effects. The former is faster (practically the same as having no related IPs) than the latter (probably because there's far less data to process from the DB).

Personally, I'd go with the limit because it's pretty odd for someone to accurately use that table for anything functional. Like if we paginated the table, who's gonna be like "yeah, let me just go to page 9 of 80, which I'm sure is where that one related IP that I'm looking for is at".

Resolving patch:
diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py
--- a/netbox/ipam/models.py
+++ b/netbox/ipam/models.py
@@ -177,6 +177,12 @@ def clean(self):
             # Clear host bits from prefix
             self.prefix = self.prefix.cidr
 
+            # /0 masks are not acceptable
+            if self.prefix.prefixlen == 0:
+                raise ValidationError({
+                    'prefix': "Cannot create aggregate with /0 mask."
+                })
+
             # Ensure that the aggregate being added is not covered by an existing aggregate
             covering_aggregates = Aggregate.objects.filter(prefix__net_contains_or_equals=str(self.prefix))
             if self.pk:
@@ -347,6 +353,12 @@ def clean(self):
 
         if self.prefix:
 
+            # /0 masks are not acceptable
+            if self.prefix.prefixlen == 0:
+                raise ValidationError({
+                    'prefix': "Cannot create prefix with /0 mask."
+                })
+
             # Disallow host masks
             if self.prefix.version == 4 and self.prefix.prefixlen == 32:
                 raise ValidationError({
@@ -622,6 +634,12 @@ def clean(self):
 
         if self.address:
 
+            # /0 masks are not acceptable
+            if self.address.prefixlen == 0:
+                raise ValidationError({
+                    'address': "Cannot create IP address with /0 mask."
+                })
+
             # Enforce unique IP space (if applicable)
             if self.role not in IPADDRESS_ROLES_NONUNIQUE and ((
                 self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE


Test patch: