terom@740: import ipaddress tero@456: import math tero@456: tero@456: def reverse_ipv4 (ip) : tero@456: """ tero@456: Return in-addr.arpa reverse for given IPv4 prefix. tero@456: """ tero@456: tero@456: # parse tero@456: octets = tuple(int(part) for part in ip.split('.')) tero@456: tero@456: for octet in octets : tero@456: assert 0 <= octet <= 255 tero@456: tero@456: return '.'.join([str(octet) for octet in reversed(octets)] + ['in-addr', 'arpa']) tero@456: tero@456: def reverse_ipv6 (ip6) : tero@456: """ tero@456: Return ip6.arpa reverse for given IPv6 prefix. tero@456: """ tero@456: tero@456: # XXX: this is broken for fd80::1? tero@456: parts = [int(part, 16) for part in ip6.split(':')] tero@456: parts = ['{0:04x}'.format(part) for part in parts] tero@456: parts = ''.join(parts) tero@456: tero@456: return '.'.join(tuple(reversed(parts)) + ( 'ip6', 'arpa')) tero@456: tero@456: def reverse_label (prefix, address) : tero@456: """ tero@456: Determine the correct label for the given IP address within the reverse zone for the given prefix. tero@456: tero@456: This includes all suffix octets (partially) covered by the prefix. tero@456: """ tero@456: tero@456: assert prefix.version == address.version tero@456: assert address in prefix tero@456: tero@456: hostbits = prefix.max_prefixlen - prefix.prefixlen tero@456: tero@456: if prefix.version == 4 : tero@456: # pack into octets tero@456: octets = [ord(x) for x in address.packed] tero@456: tero@456: # take the suffix tero@456: octets = octets[-int(math.ceil(hostbits / 8.0)):] tero@456: tero@456: # reverse in decimal tero@456: return '.'.join(reversed(["{0:d}".format(x) for x in octets])) tero@456: tero@456: elif prefix.version == 6 : tero@456: # pack into nibbles tero@456: nibbles = [((ord(x) >> 4) & 0xf, ord(x) & 0xf) for x in address.packed] tero@456: nibbles = [nibble for nibblepair in nibbles for nibble in nibblepair] tero@456: tero@456: # take the suffix tero@456: nibbles = nibbles[-(hostbits / 4):] tero@456: terom@740: # reveurse in hex tero@456: return '.'.join(reversed(["{0:x}".format(x) for x in nibbles])) tero@456: tero@456: else : tero@456: raise ValueError("unsupported address version: %s" % (prefix, )) tero@456: tero@456: def _split_ipv6_parts (prefix) : tero@456: """ tero@456: Split a partial IPv6 address into hexadecimal nibbles tero@456: """ tero@456: tero@456: if prefix.endswith('::'): tero@456: prefix = prefix[:-2] tero@456: tero@456: for hextet in prefix.split(':') : tero@456: for nibble in hextet.rjust(4, '0') : tero@456: yield nibble tero@456: tero@456: def _build_ipv6_parts (parts) : tero@456: """ tero@456: Group an iterable of hexadecimal nibbles into hextets. tero@456: """ tero@456: tero@456: for i in xrange(0, len(parts), 4) : terom@740: yield u''.join(parts[i:i+4]) tero@456: tero@456: # suffix :: tero@456: if len(parts) < 32 : terom@740: yield u'' terom@740: yield u'' tero@456: tero@456: def parse_prefix (prefix) : tero@456: """ terom@740: Return an ipaddress.IPNetwork from given filesystem-compatbile IPv4/IPv6 prefix-ish variant. tero@499: tero@499: Supports partial IPv4 prefixes on octet boundaries. tero@499: Supports partial IPv6 prefixes on nibble boundaries. tero@499: Supports IPv4 prefxies using "-" as the prefix separator in place of "/". tero@456: terom@740: >>> print parse_prefix('127.0.0.0/8') terom@740: 127.0.0.0/8 terom@740: >>> print parse_prefix('192.0.2.128/26') terom@740: 192.0.2.128/26 terom@740: >>> print parse_prefix('192.0.2.128-26') terom@740: 192.0.2.128/26 terom@740: >>> print parse_prefix('127.') terom@740: 127.0.0.0/8 terom@740: >>> print parse_prefix('10') terom@740: 10.0.0.0/8 terom@740: >>> print parse_prefix('192.168') terom@740: 192.168.0.0/16 terom@740: >>> print parse_prefix('fe80::') terom@740: fe80::/16 terom@740: >>> print parse_prefix('2001:db8::') terom@740: 2001:db8::/32 terom@740: >>> print parse_prefix('2001:db8:1:2') terom@740: 2001:db8:1:2::/64 tero@456: """ tero@456: terom@740: prefix = unicode(prefix) terom@740: tero@456: if '/' in prefix : terom@740: return ipaddress.ip_network(prefix) tero@456: tero@456: elif '-' in prefix : terom@740: return ipaddress.ip_network(prefix.replace('-', '/')) tero@456: tero@456: elif '.' in prefix or prefix.isdigit() : tero@456: parts = prefix.rstrip('.').split('.') tero@456: prefixlen = len(parts) * 8 tero@456: terom@740: return ipaddress.IPv4Network(u'{prefix}/{prefixlen}'.format( terom@740: prefix = u'.'.join(parts + [u'0' for i in xrange(4 - len(parts))]), tero@456: prefixlen = prefixlen, tero@456: )) tero@456: tero@456: elif ':' in prefix : tero@456: parts = list(_split_ipv6_parts(prefix)) tero@456: prefixlen = len(parts) * 4 tero@456: terom@740: return ipaddress.IPv6Network(u'{prefix}/{prefixlen}'.format( terom@740: prefix = u':'.join(_build_ipv6_parts(parts)), tero@456: prefixlen = prefixlen, tero@456: )) tero@456: tero@456: else : tero@456: raise ValueError("Unrecognized IP prefix string: %s" % (prefix, )) tero@456: