Index ¦ Archives ¦ Atom

Complement my nets

Calculating IP range reversals with Python 3's ipaddress

In a random Slack, far far away, somebody asked "has anyone seen a tool that will give you the complement set of CIDR ip address range that you give it? e.g. I have an allow-list :arrow_right: magic tool :arrow_right: I now have a deny-list".

I replied hrrm.. that's going to get a bit messy, but you could use the ipaddress module in python3 to do this..

Then:

>>> from ipaddress import ip_network
>>> n1 = ip_network('0.0.0.0/0')
>>> n2 = ip_network('192.168.0.0/24')
>>> list(n1.address_exclude(n2))
[IPv4Network('0.0.0.0/1'), IPv4Network('128.0.0.0/2'), IPv4Network('224.0.0.0/3'), IPv4Network('208.0.0.0/4'), IPv4Network('200.0.0.0/5'), IPv4Network('196.0.0.0/6'), IPv4Network('194.0.0.0/7'), IPv4Network('193.0.0.0/8'), IPv4Network('192.0.0.0/9'), IPv4Network('192.192.0.0/10'), IPv4Network('192.128.0.0/11'), IPv4Network('192.176.0.0/12'), IPv4Network('192.160.0.0/13'), IPv4Network('192.172.0.0/14'), IPv4Network('192.170.0.0/15'), IPv4Network('192.169.0.0/16'), IPv4Network('192.168.128.0/17'), IPv4Network('192.168.64.0/18'), IPv4Network('192.168.32.0/19'), IPv4Network('192.168.16.0/20'), IPv4Network('192.168.8.0/21'), IPv4Network('192.168.4.0/22'), IPv4Network('192.168.2.0/23'), IPv4Network('192.168.1.0/24')]

And after some further thought:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python

import sys
from ipaddress import ip_network

base_net = ip_network('0.0.0.0/0')
complement_nets = set()

for input_net in sys.argv[1].split(','):
    candidate_net = ip_network(input_net)
    supernet_already_seen = False
    for complement_net in list(complement_nets):
        if candidate_net.subnet_of(complement_net):
            supernet_already_seen = True
            complement_nets.remove(complement_net)
            for new_complement in complement_net.address_exclude(candidate_net):
                complement_nets.add(new_complement)
    if supernet_already_seen is False:
        for complement_net in base_net.address_exclude(candidate_net):
            complement_nets.add(complement_net)

print('=> {}'.format(','.join([str(net) for net in sorted(complement_nets)])))

... which looks like this when run:

$ ./complement-my-nets.py 1.0.0.0/8,2.0.0.0/8,10.0.0.0/8,192.168.0.0/24
=> 0.0.0.0/8,3.0.0.0/8,4.0.0.0/6,8.0.0.0/7,11.0.0.0/8,12.0.0.0/6,16.0.0.0/4,32.0.0.0/3,64.0.0.0/2,128.0.0.0/2,192.0.0.0/9,192.128.0.0/11,192.160.0.0/13,192.168.1.0/24,192.168.2.0/23,192.168.4.0/22,192.168.8.0/21,192.168.16.0/20,192.168.32.0/19,192.168.64.0/18,192.168.128.0/17,192.169.0.0/16,192.170.0.0/15,192.172.0.0/14,192.176.0.0/12,192.192.0.0/10,193.0.0.0/8,194.0.0.0/7,196.0.0.0/6,200.0.0.0/5,208.0.0.0/4,224.0.0.0/3
$ ./complement-my-nets.py 0.0.0.0/8,3.0.0.0/8,4.0.0.0/6,8.0.0.0/7,11.0.0.0/8,12.0.0.0/6,16.0.0.0/4,32.0.0.0/3,64.0.0.0/2,128.0.0.0/2,192.0.0.0/9,192.128.0.0/11,192.160.0.0/13,192.168.1.0/24,192.168.2.0/23,192.168.4.0/22,192.168.8.0/21,192.168.16.0/20,192.168.32.0/19,192.168.64.0/18,192.168.128.0/17,192.169.0.0/16,192.170.0.0/15,192.172.0.0/14,192.176.0.0/12,192.192.0.0/10,193.0.0.0/8,194.0.0.0/7,196.0.0.0/6,200.0.0.0/5,208.0.0.0/4,224.0.0.0/3
=> 1.0.0.0/8,2.0.0.0/8,10.0.0.0/8,192.168.0.0/24

The final message in that thread? "nice nets".

For more on Python 3's ipaddress, see https://docs.python.org/3/library/ipaddress.html.

© Jamie Finnigan; opinions my own and not my employers. Built using Pelican. Modified from theme by Giulio Fidente on github.