Je t’embrasse Salutations from Silicon Valley, California

25Nov/090

Calculating Network Addresses

I have frequently run up against the problem of generating network addresses (strictly speaking IPv4) but I am positive that the principles demonstrated below can carry over into IPv6.

The biggest issue is that in calculating addresses, the best way to find the "Nth address from here" is to convert to decimal and add. Well it's not so difficult to convert 4 octets into a 32-bit binary number, and crunch that into a single decimal number, add "N", convert back to binary, divide into octets, and convert back to decimal... but it takes forever. So why take forever when you don't need to. The trick? Divide & Conquer.

After walking through the theory, masks, addresses, ranges, compliments... I finally put together the following as a helper. Feel free to comment.

#!/usr/bin/env python

import re
import sys
from optparse import OptionParser

# ============================================================================
# Global Variables
# ============================================================================
use_string = "usage: %prog"
ver_string = "v.0.1.0"

# ============================================================================
# Global Functions
# ============================================================================
def is_ipv4(string):
    regex = r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$'
    i = re.finditer(regex,string)
    match = [m.groups() for m in i]
    if len(match) != 1: return False
    octets = match[0]
    for octet in octets:
        try: number = int(octet)
        except: return False
        if number < 0 or number > 255: return False
    return True

def dec2octets(number):
    original = number
    octets = 4
    answer = []
    remainder = 1
    while (remainder != 0):
        if octets == 0:
            raise Exception("'%s' is too large to be an IP."%original)
        octets = octets - 1
        value = number / (256**octets)
        remainder = number % (256**octets)
        answer.append(value)
        number = remainder
    while (len(answer) != 4): answer.append(0)
    return answer

def ip2list(ip):
    assert is_ipv4(ip)
    return [int(x) for x in ip.split('.')]

def ip2bin(ip):
    assert is_ipv4(ip)
    tmp = [[('0','1')[x>>j&1] for j in xrange(7,-1,-1)] \
           for x in [int(x) for x in ip.split('.')]]
    return ''.join([''.join(x) for x in tmp])

def bin2dec(x):
    assert type(x) == type("")
    assert x.strip('01') == ''
    return int(x,2)

def numify(n):
    assert type(n) == type(1)
    if str(n)[-1] == "1": return "%sst"%(n)
    elif str(n)[-1] == "2": return "%snd"%(n)
    elif str(n)[-1] == "3": return "%srd"%(n)
    else: return "%sth"%(n)

# ============================================================================
# Main Method
# ============================================================================
if __name__ == '__main__':
    parser = OptionParser(version=ver_string, usage=use_string)
    parser.add_option("-i","--ip",
                      action="store",
                      default=None,
                      dest="ipAddress",
                      help=("Use this to specify the IP address to use in "
                            "calculations. Alternatively, specify the IP address"
                            "in the 'x.x.x.x/N' form to specify both IP and "
                            "subnet mask."),
                      metavar="IPADDR",
                      )
    parser.add_option("-m","--mask",
                      action="store",
                      default=None,
                      dest="subnetMask",
                      help=("Use this to specify the subnet mask to use in "
                            "calculations."),
                      metavar="MASK",
                      )
    parser.add_option("-n","--find",
                      action="store",
                      default=0,
                      dest="lookFor",
                      help=("Use this to specify the IP address to "
                            "us in calculations"),
                      metavar="VALUE",
                      )

    (options, args) = parser.parse_args()

    # ========================================================================
    # Massage input...
    # ========================================================================
    if (options.ipAddress != None and options.subnetMask != None):
        assert is_ipv4(options.ipAddress)
        assert is_ipv4(options.subnetMask)
        addr = ip2list(options.ipAddress)
        mask = ip2list(options.subnetMask)
        bits = ip2bin(options.subnetMask).count('1')
        comp = [255-x for x in mask]
        netw = [addr[x] & mask[x] for x in range(len(addr))]
        cast = [comp[x] | netw[x] for x in range(len(comp))]
    elif (options.ipAddress != None and options.subnetMask == None):
        assert options.ipAddress.find('/') != -1
        (temp, bits) = options.ipAddress.split('/')
        assert is_ipv4(temp)
        assert int(bits) < 32
        assert int(bits) > 0
        addr = ip2list(temp)
        bits = int(bits)
        temp = "".join(["1"]*bits)+"".join(["0"]*(32-bits))
        mask = [bin2dec(temp[0:8]), bin2dec(temp[8:16]),
                bin2dec(temp[16:24]), bin2dec(temp[24:32])]
        comp = [255-x for x in mask]
        netw = [addr[x] & mask[x] for x in range(len(addr))]
        cast = [comp[x] | netw[x] for x in range(len(comp))]
    elif (options.ipAddress == None and options.subnetMask != None):
        assert is_ipv4(options.subnetMask)
        addr = [0,0,0,0]
        mask = ip2list(options.subnetMask)
        bits = ip2bin(options.subnetMask).count('1')
        comp = [255-x for x in mask]
        netw = [addr[x] & mask[x] for x in range(len(addr))]
        cast = [comp[x] | netw[x] for x in range(len(comp))]
    else: raise Exception("An IP or Subnet mask must be specified!")
    diff = dec2octets(int(options.lookFor))
    size = bin2dec("".join(["1"]*(32-bits)))-1

    # Is it valid? (aka. Is it in our specified range?)
    test =  & mask[x] for x in range(len(diff))]

    # ========================================================================
    # Massage output...
    # ========================================================================
    address = ".".join([str(x) for x in addr])
    network = ".".join([str(x) for x in netw])
    netmask = ".".join([str(x) for x in mask])
    brdcast = ".".join([str(x) for x in cast])
    print ("There are %s addresses available in the %s/%s network "
           "range."%(size, network, bits))

    print ("Network: %s  Netmask: %s  "
           "Broadcast: %s"%(network, netmask, brdcast))

    if (int(options.lookFor) <= 0): sys.exit()
    if (test == [0,0,0,0]):
        plus = ".".join([str(netw[x] + diff[x]) for x in range(len(netw))])
        print ("The %s address in the %s/%s network "
               "is %s"%(numify(int(options.lookFor)), network, bits, plus))
    else:
        print ("The %s address is beyond the %s addresses in the %s/%s "
               "network."%(numify(int(options.lookFor)),size,network,bits))
Filed under: Python Leave a comment
Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.