#!/usr/bin/python
import sys, socket, struct, binascii

__author__ = "Matthew Kruer"
__copyright__ = "Copyright 2014, Ericsson"
__credits__ = ["Matthew Kruer"]
__license__ = "Commercial"
__version__ = "0.1"
__maintainer__ = "Matthew Kruer"
__email__ = "matthew.kruer@ericsson.com"
__status__ = "Development"

# usage command.py + <See Below>
# IPv4                => IPv4
# IPv4/CIDR           => IPv4 Subnet <Suggested Gateway>
# IPv4/CIDR Gateway   => IPv4 Subnet Gateway
# IPv4 Subnet         => IPv4 Subnet <Suggested Gateway>
# IPv4 Subnet Gateway => IPv4 Subnet Gateway
# IPv6                => IPv6 <CIDR might be required>
# IPv6/CIDR           => IPv6/CIDR
#
# Invalid values will be returned "False"
# Known Corner Case that will fail are if IPv4 with CIDR/Subnet of 2 hosts are less (CIDR, 31 and 32)
# There may be some IPv6 cases that are not supported at this time, but the major ones will work.

#### Debugging ####
# print ''
# print 'Number of arguments:', len(sys.argv), 'arguments.'
# print 'Argument List:', str(sys.argv)
# print ''

# if len(sys.argv) >= 1:
    # print ' Argument [0]                                 ', str(sys.argv[0])
# if len(sys.argv) >= 2:
    # print ' Argument [1]                                 ', str(sys.argv[1])
# if len(sys.argv) >= 3:
    # print ' Argument [2]                                 ', str(sys.argv[2])
# if len(sys.argv) >= 4:
    # print ' Argument [3]                                 ', str(sys.argv[3])
# if len(sys.argv) >= 5:
    # print ' Argument [4]                                 ', str(sys.argv[4])
# print''

# Convert IPv4 netmask to CIDR
def netmask_to_CIDR(ip4):
    if validate_netmask(ip4):
        return int_to_binary(ip4_to_integer(ip4), 32).count('1')
    
# Convert CIDR to IPv4 netmask
def CIDR_to_netmask(CIDR):
    if validate_CIDR(CIDR,4):
        return integer_to_ip4(CIDR_to_integer(CIDR,4))

# Convert IPv4 to an Integer
def ip4_to_integer(ip4):
    try:
        return struct.unpack("!L", socket.inet_aton(ip4))[0]
    except socket.error:
        return False

# Convert 32bit Integer to IPv4
def integer_to_ip4(ip4n):
    if 0 <= ip4n <= 4294967296:
        return socket.inet_ntop(socket.AF_INET, struct.pack("!I", ip4n))
    else:
        return False

# Convert IPv6 to an Integer
def ip6_to_integer(ip6):
    try:
        return int(binascii.hexlify(socket.inet_pton(socket.AF_INET6, ip6)), 16)
    except socket.error:
        return False

# Convert 128bit Integer to an IPv6
def integer_to_ip6(ip6n):
    if 0 <= ip6n <= 340282366920938463463374607431768211456:
        ipv6String = format(ip6n, 'x').zfill(32)
        return ":".join(ipv6String[x : x + 4] for x in xrange(0, len(ipv6String), 4))
    else:
        return False

# Converts a integer into binary
def int_to_binary(raw_netmask_int, max_CIDR):
    return bin(raw_netmask_int)[2:].zfill(max_CIDR)

# Convert Binary back to Integer
def binary_to_int(CIDR):
    return int(CIDR, 2)

# Validate Bitmask
def validate_bitmask(CIDR, max_CIDR):
    CIDR_bitmask = ''.rjust(CIDR, '1').ljust(max_CIDR, '0')
    return CIDR_bitmask

# Convert CIDR to Integer
def CIDR_to_integer(valid_CIDR, ip_ver):
    max_CIDR = CIDR_limit(ip_ver)
    valid_netmask_int = binary_to_int(validate_bitmask(valid_CIDR, max_CIDR))
    return valid_netmask_int

# Bitwise version of Power of 2 Check
def power_of_two(number):
    return (number & (number-1) == 0) and (number != 0)

# Determine if address is a real IPv4 or IPv6 address
def ip4_or_ip6(raw_ip):
    if ip4_to_integer(raw_ip) is not False:
        return ip4_to_integer(raw_ip), 4
    elif  ip6_to_integer(raw_ip) is not False:
        return ip6_to_integer(raw_ip), 6
    else:
        return False, False

# Set Max CIDR limit
def CIDR_limit(ip_ver):
    if ip_ver is 4:
        return 32
    elif ip_ver is 6:
        return 128
    else:
        return False

# Address format check to make sure that it meets the basic formatting of an IP address
def parse_raw(raw):
    if raw.count(".") == 3 or raw.count(":") == 7 or raw.count("::") == 1:
        if "/" in raw:
            raw_ip = raw.split("/")[0]
            raw_CIDR = raw.split("/")[1]
            if raw_CIDR:
                return raw_ip, raw_CIDR
            else:
                return False, None
        else:
            raw_ip = raw
            raw_CIDR = None
            return  raw_ip, raw_CIDR
    else:
        return False, None

# Validate IP
def validate_ip(raw):
    raw_ip, raw_CIDR = parse_raw(raw)
    if raw_ip is not False and raw_CIDR is not None:
        valid_ip_int, ip_ver = ip4_or_ip6(raw_ip)
        valid_CIDR = validate_CIDR(raw_CIDR, ip_ver)
        return valid_ip_int, valid_CIDR, ip_ver
    elif raw_ip is not False:
        valid_ip_int, ip_ver = ip4_or_ip6(raw_ip)
        return valid_ip_int, None, ip_ver
    else:
        return False, False, False

# Validate CIDR (subnet mask)
def validate_CIDR(raw_CIDR, ip_ver):
    if isinstance(raw_CIDR,int):
        CIDR=raw_CIDR
    elif raw_CIDR.isdigit() is not False:
        CIDR=int(raw_CIDR)
    else:
        return False
        
    if ip_ver is 4:
        if CIDR:
            if 0 <= CIDR <= CIDR_limit(ip_ver):
                return CIDR
            else:
                return False
    elif ip_ver is 6:
        if CIDR:
            if 0 <= CIDR <= CIDR_limit(ip_ver):
                return CIDR
            else:
                return False
    else:
        return False

# Validate Subnet mask
def validate_netmask(netmask):
    return validate_binary_netmask(ip4_to_integer(netmask), 4)

# Validate Binary Subnet mask
def validate_binary_netmask(raw_netmask_int, ip_ver):
    max_CIDR = CIDR_limit(ip_ver)
    binary_netmask = int_to_binary(raw_netmask_int, max_CIDR)
    valid_CIDR = binary_netmask.count('1')
    if binary_netmask.find('01') != -1:
        return False
    else:
        return valid_CIDR

# Validate Gateway
def validate_gateway_ip(valid_ip_int, valid_CIDR, ip_ver, raw_gateway_int):
    hosts_int = num_of_hosts(valid_CIDR, ip_ver)
    valid_network_ip_int = network_ip_int(valid_ip_int, hosts_int)
    valid_broadcast_ip_int = broadcast_ip_int(valid_ip_int, hosts_int)
    if raw_gateway_int == valid_ip_int:
        return False
    elif valid_network_ip_int >= raw_gateway_int:
        return False
    elif valid_broadcast_ip_int <= raw_gateway_int:
        return False
    else:
        return raw_gateway_int

# Calculate Network IP (will always be the lowest IP in the range)
def network_ip_int(valid_ip_int, hosts_int):
    if power_of_two(hosts_int) is True:
        return valid_ip_int/hosts_int*hosts_int
    else:
        return False

# Calculate Broadcast (will always be the highest IP in the range)
def broadcast_ip_int(valid_ip_int, hosts_int):
    if power_of_two(hosts_int) is True:
        return (valid_ip_int/hosts_int*hosts_int)+hosts_int-1
    else:
        return False

# Determine Default Gateway (if not supplied)
def default_gateway(valid_ip_int, hosts_int):
    if power_of_two(hosts_int) is True:
        return (valid_ip_int/hosts_int*hosts_int)+1
    else:
        return False

# Calculate Number of Hosts based upon CIDR (Subnet)
def num_of_hosts(valid_CIDR, ip_ver):
    max_CIDR = CIDR_limit(ip_ver)
    hosts_int = pow(2, (max_CIDR-valid_CIDR))
    return hosts_int

# Check IP
def ip_info(raw_ip):
    valid_ip_int, valid_CIDR, ip_ver = validate_ip(raw_ip)
    return valid_ip_int, valid_CIDR, ip_ver

# Check Subnet
def subnet_info(raw_subnet):
    raw_netmask_int, junk, ip_ver = validate_ip(raw_subnet)
    if raw_netmask_int is not False and junk is not False:
        valid_CIDR = validate_binary_netmask(raw_netmask_int, ip_ver)
        return valid_CIDR
    else:
        return False

# Check Gateway
def gateway_info(valid_ip_int, valid_CIDR, ip_ver, raw_gateway):
    max_CIDR = CIDR_limit(ip_ver)
    raw_gateway_int, junk, ip_ver = validate_ip(raw_gateway)
    if raw_gateway_int is not False and junk is not False:
        valid_gateway_ip_int = validate_gateway_ip(valid_ip_int, valid_CIDR, ip_ver, raw_gateway_int)
        return valid_gateway_ip_int
    else:
        return False

# Define Default Values
valid_ip = ''
valid_netmask = ''
valid_gateway_ip = ''

# Main Loop
if len(sys.argv) >= 2:
    valid_ip_int, valid_CIDR, ip_ver = ip_info(sys.argv[1])
    if valid_CIDR is not False and ip_ver == 4:
        valid_ip = integer_to_ip4(valid_ip_int)
        if valid_CIDR:
            valid_netmask = integer_to_ip4(CIDR_to_integer(valid_CIDR, ip_ver))
            if len(sys.argv) >= 3:
                valid_gateway_ip_int = gateway_info(valid_ip_int, valid_CIDR, ip_ver, sys.argv[2])
                if valid_gateway_ip_int is not False:
                    valid_gateway_ip = integer_to_ip4(valid_gateway_ip_int)
                else:
                    valid_gateway_ip = False
            else:
                hosts_int = num_of_hosts(valid_CIDR, ip_ver)
                default_gateway_ip = integer_to_ip4(default_gateway(valid_ip_int, hosts_int))
                valid_gateway_ip_int = gateway_info(valid_ip_int, valid_CIDR, ip_ver, default_gateway_ip)
                if valid_gateway_ip_int is not False:
                    valid_gateway_ip = integer_to_ip4(valid_gateway_ip_int)
                else:
                    valid_gateway_ip = False
        elif valid_CIDR is None:
            if len(sys.argv) >= 3:
                valid_CIDR = subnet_info(sys.argv[2])
                if valid_CIDR is not False:
                    valid_netmask = integer_to_ip4(CIDR_to_integer(valid_CIDR, ip_ver))
                    if len(sys.argv) >= 4:
                        valid_netmask = integer_to_ip4(CIDR_to_integer(valid_CIDR, ip_ver))
                        valid_gateway_ip_int = gateway_info(valid_ip_int, valid_CIDR, ip_ver, sys.argv[3])
                        if valid_gateway_ip_int is not False:
                            valid_gateway_ip = integer_to_ip4(valid_gateway_ip_int)
                        else:
                            valid_gateway_ip = False
                    else:
                        hosts_int = num_of_hosts(valid_CIDR, ip_ver)
                        default_gateway_ip = integer_to_ip4(default_gateway(valid_ip_int, hosts_int))
                        valid_gateway_ip_int = gateway_info(valid_ip_int, valid_CIDR, ip_ver, default_gateway_ip)
                        if valid_gateway_ip_int is not False:
                            valid_gateway_ip = integer_to_ip4(valid_gateway_ip_int)
                        else:
                            valid_gateway_ip = False
                else:
                    valid_netmask = False
    else:
        valid_ip = False
    if valid_CIDR is not False and ip_ver == 6:
        if valid_CIDR is None:
            valid_ip = integer_to_ip6(valid_ip_int)
        else:
            valid_ip = integer_to_ip6(valid_ip_int) + "/" + str(valid_CIDR)

# Output
print valid_ip, valid_netmask, valid_gateway_ip
