Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Splitting a network into subnets of multiple prefixes

I'm using the netaddr module and trying to figure out how/if I can split a network into subnets of varying prefixes. For example take a /16 and split it into X /23s and Y /24s.

From what I can tell we can use the subnet function to split a network in X number of a given prefix but it only takes 1 prefix.

The above would slice out 4 /23s out of the /16 which is great, but how do I take the remaining space and slice that into a differing prefix?

ip = IPNetwork('172.24.0.0/16')
subnets = list(ip.subnet(23, count=4))

Is there a way that I can achieve what I'm trying to do with netaddr?

like image 625
alanwill Avatar asked Aug 10 '16 04:08

alanwill


3 Answers

Try the below

from netaddr import IPNetwork, cidr_merge, cidr_exclude

class IPSplitter(object):
    def __init__(self, base_range):
        self.avail_ranges = set((IPNetwork(base_range),))

    def get_subnet(self, prefix, count=None):
        for ip_network in self.get_available_ranges():
            subnets = list(ip_network.subnet(prefix, count=count))
            if not subnets:
                continue
            self.remove_avail_range(ip_network)
            self.avail_ranges = self.avail_ranges.union(set(cidr_exclude(ip_network, cidr_merge(subnets)[0])))
            return subnets

    def get_available_ranges(self):
        return sorted(self.avail_ranges, key=lambda x: x.prefixlen, reverse=True)

    def remove_avail_range(self, ip_network):
        self.avail_ranges.remove(ip_network)

You can use it like below. With some more math you can make it more efficient,like chopping of subnets from two different blocks when count is not satisfied from a single block.

In [1]: from ip_splitter import IPSplitter

In [2]: s = IPSplitter('172.24.0.0/16')

In [3]: s.get_available_ranges()
Out[3]: [IPNetwork('172.24.0.0/16')]

In [4]: s.get_subnet(23, count=4)
Out[4]: 
[IPNetwork('172.24.0.0/23'),
 IPNetwork('172.24.2.0/23'),
 IPNetwork('172.24.4.0/23'),
 IPNetwork('172.24.6.0/23')]

In [5]: s.get_available_ranges()
Out[5]: 
[IPNetwork('172.24.8.0/21'),
 IPNetwork('172.24.16.0/20'),
 IPNetwork('172.24.32.0/19'),
 IPNetwork('172.24.64.0/18'),
 IPNetwork('172.24.128.0/17')]

In [6]: s.get_subnet(28, count=10)
Out[6]: 
[IPNetwork('172.24.8.0/28'),
 IPNetwork('172.24.8.16/28'),
 IPNetwork('172.24.8.32/28'),
 IPNetwork('172.24.8.48/28'),
 IPNetwork('172.24.8.64/28'),
 IPNetwork('172.24.8.80/28'),
 IPNetwork('172.24.8.96/28'),
 IPNetwork('172.24.8.112/28'),
 IPNetwork('172.24.8.128/28'),
 IPNetwork('172.24.8.144/28')]

In [7]: s.get_available_ranges()
Out[7]: 
[IPNetwork('172.24.8.128/25'),
 IPNetwork('172.24.9.0/24'),
 IPNetwork('172.24.10.0/23'),
 IPNetwork('172.24.12.0/22'),
 IPNetwork('172.24.16.0/20'),
 IPNetwork('172.24.32.0/19'),
 IPNetwork('172.24.64.0/18'),
 IPNetwork('172.24.128.0/17')]
like image 71
Akilesh Avatar answered Oct 19 '22 04:10

Akilesh


In my case the prefixes aren't completely arbitrary and they'll always result in an even split. For example for a /24 I'll always ask for 6 /27s and 4 /28s and in a /23 I'll always ask for 2 /25s, 2 /26s, 2 /27s and 4 /28s and I'll always be starting with either a /24 or /23 so the scheme is fairly predictable.

This is what I'm going with for the /24 and similarly for the /23:

ip = IPNetwork('10.40.72.0/24')
network28s = list(ip.subnet(28, count=16))
presentation1 = list(network28s[0:1])
presentation2 = list(network28s[1:2])
database1 = list(network28s[2:3])
database2 = list(network28s[3:4])
internetlb1 = cidr_merge(list(network28s[4:6]))
internetlb2 = cidr_merge(list(network28s[6:8]))
internet1 = cidr_merge(list(network28s[8:10]))
internet2 = cidr_merge(list(network28s[10:12]))
application1 = cidr_merge(list(network28s[12:14]))
application2 = cidr_merge(list(network28s[14:16]))

I could only do this because of the fact that the scheme is so predictable and by predictable I mean, those 10 subnets will always be requested and they'll always be the same size the only variance is the CIDR itself.

like image 38
alanwill Avatar answered Oct 19 '22 04:10

alanwill


You could first split it into a number of /23 and then split a certain number of those into the number of /24 you want. Quick example code off the top of my head.

>>> from netaddr import *
>>> ip = IPNetwork('172.24.0.0/16')
>>> subnet_23 = list(ip.subnet(23))
>>> subnet_23[:6]
[IPNetwork('172.24.0.0/23'), IPNetwork('172.24.2.0/23'), IPNetwork('172.24.4.0/23'), IPNetwork('172.24.6.0/23'), IPNetwork('172.24.8.0/23'), IPNetwork('172.24.10.0/23')]
>>> len(subnet_23)
128
>>> final_subnets = []
>>> number_of_24s_wanted = 10
>>> for s in subnet_23:
...   if len(final_subnets) < number_of_24s_wanted:
...     final_subnets.extend(list(s.subnet(24)))
...   else:
...     final_subnets.append(s)
... 
>>> final_subnets[:20]
[IPNetwork('172.24.0.0/24'), IPNetwork('172.24.1.0/24'), IPNetwork('172.24.2.0/24'), IPNetwork('172.24.3.0/24'), IPNetwork('172.24.4.0/24'), IPNetwork('172.24.5.0/24'), IPNetwork('172.24.6.0/24'), IPNetwork('172.24.7.0/24'), IPNetwork('172.24.8.0/24'), IPNetwork('172.24.9.0/24'), IPNetwork('172.24.10.0/23'), IPNetwork('172.24.12.0/23'), IPNetwork('172.24.14.0/23'), IPNetwork('172.24.16.0/23'), IPNetwork('172.24.18.0/23'), IPNetwork('172.24.20.0/23'), IPNetwork('172.24.22.0/23'), IPNetwork('172.24.24.0/23'), IPNetwork('172.24.26.0/23'), IPNetwork('172.24.28.0/23')]
>>> len(final_subnets)
133
like image 45
RyPeck Avatar answered Oct 19 '22 06:10

RyPeck