pvl/dns/reverse.py
author Tero Marttila <terom@paivola.fi>
Tue, 10 Mar 2015 00:26:31 +0200
changeset 740 74352351d6f5
parent 499 51bab9649e77
permissions -rw-r--r--
replace ipaddr with ipaddress
import ipaddress
import math

def reverse_ipv4 (ip) :
    """
        Return in-addr.arpa reverse for given IPv4 prefix.
    """

    # parse
    octets = tuple(int(part) for part in ip.split('.'))

    for octet in octets :
        assert 0 <= octet <= 255

    return '.'.join([str(octet) for octet in reversed(octets)] + ['in-addr', 'arpa'])

def reverse_ipv6 (ip6) :
    """
        Return ip6.arpa reverse for given IPv6 prefix.
    """

    # XXX: this is broken for fd80::1?
    parts = [int(part, 16) for part in ip6.split(':')]
    parts = ['{0:04x}'.format(part) for part in parts]
    parts = ''.join(parts)

    return '.'.join(tuple(reversed(parts)) + ( 'ip6', 'arpa'))

def reverse_label (prefix, address) :
    """
        Determine the correct label for the given IP address within the reverse zone for the given prefix.

        This includes all suffix octets (partially) covered by the prefix.
    """

    assert prefix.version == address.version
    assert address in prefix
    
    hostbits = prefix.max_prefixlen - prefix.prefixlen

    if prefix.version == 4 :
        # pack into octets
        octets = [ord(x) for x in address.packed]

        # take the suffix
        octets = octets[-int(math.ceil(hostbits / 8.0)):]
        
        # reverse in decimal
        return '.'.join(reversed(["{0:d}".format(x) for x in octets]))

    elif prefix.version == 6 :
        # pack into nibbles
        nibbles = [((ord(x) >> 4) & 0xf, ord(x) & 0xf) for x in address.packed]
        nibbles = [nibble for nibblepair in nibbles for nibble in nibblepair]

        # take the suffix
        nibbles = nibbles[-(hostbits / 4):]
        
        # reveurse in hex
        return '.'.join(reversed(["{0:x}".format(x) for x in nibbles]))

    else :
        raise ValueError("unsupported address version: %s" % (prefix, ))

def _split_ipv6_parts (prefix) :
    """
        Split a partial IPv6 address into hexadecimal nibbles
    """

    if prefix.endswith('::'):
        prefix = prefix[:-2]

    for hextet in prefix.split(':') :
        for nibble in hextet.rjust(4, '0') :
            yield nibble

def _build_ipv6_parts (parts) :
    """
        Group an iterable of hexadecimal nibbles into hextets.
    """

    for i in xrange(0, len(parts), 4) :
        yield u''.join(parts[i:i+4])
    
    # suffix ::
    if len(parts) < 32 :
        yield u''
        yield u''

def parse_prefix (prefix) :
    """
        Return an ipaddress.IPNetwork from given filesystem-compatbile IPv4/IPv6 prefix-ish variant.

        Supports partial IPv4 prefixes on octet boundaries.
        Supports partial IPv6 prefixes on nibble boundaries.
        Supports IPv4 prefxies using "-" as the prefix separator in place of "/".

        >>> print parse_prefix('127.0.0.0/8')
        127.0.0.0/8
        >>> print parse_prefix('192.0.2.128/26')
        192.0.2.128/26
        >>> print parse_prefix('192.0.2.128-26')
        192.0.2.128/26
        >>> print parse_prefix('127.')
        127.0.0.0/8
        >>> print parse_prefix('10')
        10.0.0.0/8
        >>> print parse_prefix('192.168')
        192.168.0.0/16
        >>> print parse_prefix('fe80::')
        fe80::/16
        >>> print parse_prefix('2001:db8::')
        2001:db8::/32
        >>> print parse_prefix('2001:db8:1:2')
        2001:db8:1:2::/64
    """

    prefix = unicode(prefix)

    if '/' in prefix :
        return ipaddress.ip_network(prefix)
    
    elif '-' in prefix :
        return ipaddress.ip_network(prefix.replace('-', '/'))

    elif '.' in prefix or prefix.isdigit() :
        parts = prefix.rstrip('.').split('.')
        prefixlen = len(parts) * 8
        
        return ipaddress.IPv4Network(u'{prefix}/{prefixlen}'.format(
            prefix      = u'.'.join(parts + [u'0' for i in xrange(4 - len(parts))]),
            prefixlen   = prefixlen,
        ))
   
    elif ':' in prefix :
        parts = list(_split_ipv6_parts(prefix))
        prefixlen = len(parts) * 4

        return ipaddress.IPv6Network(u'{prefix}/{prefixlen}'.format(
            prefix      = u':'.join(_build_ipv6_parts(parts)),
            prefixlen   = prefixlen,
        ))

    else :
        raise ValueError("Unrecognized IP prefix string: %s" % (prefix, ))