Commits

buge committed 1509af8

Ongoing work on information retrieval from IPv6 addresses.

  • Participants
  • Parent commits 6224dd6

Comments (0)

Files changed (1)

File addrinfo/addresses.py

 __all__ = [
     'Address',
     'EUIAddress', 'EUI48Address', 'EUI64Address',
-    'IPAddress', 'IPv4Address', 'IPv6Address'
+    'IPAddress', 'IPv4Address', 'IPv6Address',
+    'AddressVisitor'
 ]
 
 
     def get_address(self):
         return self.__address
 
+    def accept(self, visitor):
+        method = "visit_%s" % self.__class__.__name__
+        if hasattr(visitor, method):
+            getattr(visitor, method)(self)
+
     def _has_prefix(self, prefix, length):
         bitsize = self.__class__.BIT_SIZE
         return prefix == self.get_address() >> (bitsize - length)
     def value_of(cls, value):
         return IPv4Address(value)
 
+    def is_special_use_address(self):
+        # TODO This is really just a quick solution, we really want to provide
+        # information about the type of network and make this more maintainable.
+        return self._has_prefix(0, 8) \
+            or self._has_prefix(10, 8) \
+            or self._has_prefix(127, 8) \
+            or self._has_prefix(43518, 16) \
+            or self._has_prefix(2753, 12) \
+            or self._has_prefix(12582912, 24) \
+            or self._has_prefix(12582914, 24) \
+            or self._has_prefix(12605539, 24) \
+            or self._has_prefix(49320, 16) \
+            or self._has_prefix(25353, 15) \
+            or self._has_prefix(12989284, 24) \
+            or self._has_prefix(13303921, 24) \
+            or self._has_prefix(14, 4) \
+            or self._has_prefix(15, 4) \
+            or self.get_address() == 0xffffffff
+
     def as_ipv4(self):
         return self
 
     def as_ipv6(self):
+        return self.as_ipv4_mapped_ipv6_address()
+
+    def as_ipv4_compatible_ipv6_address(self):
+        if self.is_special_use_address():
+            raise ValueError(
+                "Only globally-unique IPv4 addresses can be used for IPv4 " +
+                "compatible IPv6 addresses")
+        return IPv6Address(self.get_address())
+
+    def as_ipv4_mapped_ipv6_address(self):
         return IPv6Address(0xffff << 32 | self.get_address())
 
     def __str__(self):
 
 
 class IPv6Address(IPAddress):
+    u"""An IPv6 Address. IPv6 addresses are 128-bit identifiers of interfaces
+    (and sets of interfaces, that are not yet implemented in the current
+    release).
+
+    IPv6 greatly expands the address space over IPv4 addresses. While IPv4
+    addresses use 32 bit addresses and therefore support up to 2\ :sup:`32`
+    addresses (about 4.3 billion addresses), IPv6 addresses support up to
+    2\ :sup:`128` addresses (about 3.4 \u00D7 10\ :sup:`38` addresses).
+
+    Like other addresses, *IPv6Addresses* can be constructed from their
+    common text representations, such as:
+
+    >>> IPv6Address('fe80::202:b3ff:fe1e:8329')
+    IPv6Address('fe80::202:b3ff:fe1e:8329')
+    >>> IPv6Address('::1')
+    IPv6Address('::1')
+
+    *IPv6Addresses* can also be directly created using their numeric
+    address:
+
+    >>> IPv6Address(281473902969472)
+    IPv6Address('::ffff:192.0.2.128')
+    >>> IPv6Address(0)
+    IPv6Address('::')
+
+    Finally, constants such as :attr:`UNSPECIFIED_ADDRESS` and
+    :attr:`LOOPBACK_ADDRESS` can be used for common IPv6 addresses.
+
+    """
 
     BIT_SIZE = 128
     _PARSER = staticmethod(parser.parse_ipv6)
 
-    # Descriptions as defined in RFC 4291, section 2.4
-    TYPE_UNSPECIFIED = 'Unspecified'
-    TYPE_LOOPBACK = 'Loopback'
-    TYPE_MULTICAST = 'Multicast'
-    TYPE_LINK_LOCAL_UNICAST = 'Link-Local unicast'
-    TYPE_GLOBAL_UNICAST = 'Global Unicast'
-
     # Multicast scopes as defined in RFC 4291, section 2.7
     MULTICAST_SCOPE_INTERFACE_LOCAL = 0x1
     MULTICAST_SCOPE_LINK_LOCAL = 0x2
         elif not isinstance(eui, EUIAddress):
             eui = EUI64Address(interface_address)
         eui = eui.as_eui64()
-        return IPv6Address(0xfe80 << 112 | eui ^ 1 << 57)
+        return IPv6Address(0xfe80 << 112 | eui ^ (1 << 57))
 
-    def get_address_type(self):
-        """Returns the address type according to :rfc:`4291`.
+    def is_unspecified_address(self):
+        """Returns whether this address is the unspecified address according to
+        :rfc:`4291#section-2.5.2`.
 
-        The returned string corresponds to the `TYPE_*` constants defined in
-        this class.
+        >>> IPv6Address.UNSPECIFIED_ADDRESS.is_unspecified_address()
+        True
 
         """
-        if self.get_address() == 0:
-            return IPv6Address.TYPE_UNSPECIFIED
-        if self.get_address() == 1:
-            return IPv6Address.TYPE_LOOPBACK
-        if self._has_prefix(0b11111111, 8):
-            return IPv6Address.TYPE_MULTICAST
-        if self._has_prefix(0b1111111010, 10):
-            return IPv6Address.TYPE_LINK_LOCAL_UNICAST
-        return IPv6Address.TYPE_GLOBAL_UNICAST
+        return self.get_address() == 0
 
-    def is_type_unspecified(self):
-        """Returns whether the address is of type "Unspecified",
-        as defined by :rfc:`4291`.
+    def is_loopback_address(self):
+        """Returns whether this address is the loopbacl address according to
+        :rfc:`4291#section-2.5.3`.
+
+        >>> IPv6Address.LOOPBACK_ADDRESS.is_loopback_address()
+        True
 
         """
-        return self.get_address_type() == IPv6Address.TYPE_UNSPECIFIED
+        return self.get_address() == 1
 
-    def is_type_loopback(self):
-        """Returns whether the address is of type "Loopback",
-        as defined by :rfc:`4291`.
+    def is_embedded_ipv4_address(self):
+        """Returns whether this IPv6 address carries an IPv4 address in the
+        low-order 32 bits. This method will recognize the "IPv4-Compatible IPv6
+        address" and the "IPv4-mapped IPv6 address" defined in
+        :rfc:`4291#section-2.5.5`:
+
+        >>> # RFC 4299 IPv4-Compatible IPv6 address
+        >>> IPv6Address('::82.94.164.162').is_embedded_ipv4_address()
+        True
+        >>> # IPv4-mapped IPv6 address
+        >>> IPv6Address('::ffff:192.0.2.131').is_embedded_ipv4_address()
+        True
+
+        Additionally, this method will also recognize ISATAP addresses and IPv4
+        translatable addresses using the Well-Known Prefix:
+
+        >>> # ISATAP address
+        >>> IPv6Address('fe80::5efe:192.0.2.143').is_embedded_ipv4_address()
+        True
+        >>> # IPv4-Embedded IPv6 address using the Well-Known Prefix
+        >>> IPv6Address('64:ff9b::192.0.2.33').is_embedded_ipv4_address()
+        True
+
+        IPv4 embedded addresses will be represented with the IPv4 part in
+        dot-decimal notation by default when calling :func:`str` on the address.
+
+        .. seealso::
+
+            :meth:`is_ipv4_compatible_address`,
+            
+            :meth:`is_ipv4_mapped_address`,
+        
+            :meth:`is_isatap_address`, and
+            
+            :meth:`is_ipv4_translatable_address`
 
         """
-        return self.get_address_type() == IPv6Address.TYPE_LOOPBACK
+        return self.is_ipv4_compatible_address() \
+            or self.is_ipv4_mapped_address() \
+            or self.is_isatap_address() \
+            or self.is_ipv4_translatable_address()
 
-    def is_type_multicast(self):
-        """Returns whether the address is of type "Multicast",
-        as defined by :rfc:`4291`.
+    def is_ipv4_compatible_address(self):
+        """Returns whether this address is an IPv4-Compatible IPv6 Address
+        according to :rfc:`4291#section-2.5.5.1`.
+
+        >>> IPv6Address("::82.94.164.162").is_ipv4_compatible_address()
+        True
+
+        IPv4-Compatible IPv6 addresses are deprecated because the current IPv6
+        transition mechanisms no longer use these addresses.
 
         """
-        return self.get_address_type() == IPv6Address.TYPE_MULTICAST
 
-    def is_type_linklocal_unicast(self):
-        """Returns whether the address is of type "Link-Local Unicast",
-        as defined by :rfc:`4291`.
+        if not self._has_prefix(0, 96):
+            return False
+        ipv4 = IPv4Address(0xffffffff & self.get_address())
+        return not ipv4.is_special_use_address()
+
+    def is_ipv4_mapped_address(self):
+        """Returns whether this address is an IPv4-Mapped IPv6 Address
+        according to :rfc:`4291#section-2.5.5.2`.
+
+        >>> IPv6Address("::ffff:192.0.2.132").is_ipv4_mapped_address()
+        True
 
         """
-        return self.get_address_type() == IPv6Address.TYPE_LINK_LOCAL_UNICAST
+        return self._has_prefix(0xffff, 96)
 
-    def is_type_global_unicast(self):
-        """Returns whether the address is of type "Global Unicast",
-        as defined by :rfc:`4291`.
+    def is_isatap_address(self):
+        return self._has_prefix(0xfe80 << 80 | 0x5efe, 96)
+
+    def is_ipv4_translatable_address(self):
+        """Returns whether this IPv6 address uses the IPv4-embedded IPv6 well
+        known prefix. The prefix is currently defined in the draft RFC
+        [draft-ietf-behave-address-format-10]_.
+
+        .. [draft-ietf-behave-address-format-10]
+           http://tools.ietf.org/html/draft-ietf-behave-address-format-10
 
         """
-        return self.get_address_type() == IPv6Address.TYPE_GLOBAL_UNICAST
+        return self._has_prefix(0x64ff9b << 64, 96)
+
+    def is_linklocal_unicast_address(self):
+        """Returns whether this address is a link-local unicast address
+        according to :rfc:`4291#section-2.5.6`.
+
+        >>> IPv6Address("fe80::223:6cff:fe80:2131").is_linklocal_unicast_address()
+        True
+
+        Link-local IPv6 unicast addresses encode an interface ID that can be
+        extracted using :meth:`as_eui64` or even :meth:`as_eui48`.
+
+        """
+        return self._has_prefix(0xfe80 << 48, 64)
+
+    def is_site_local_unicast_address(self):
+        """Returns whether this address is a site-local unicast address
+        according to :rfc:`4291#section-2.5.7`.
+
+        >>> IPv6Address("fec0::cafe:babe:223:6cff:fe80:2131").is_site_local_unicast_address()
+        True
+
+        Site-local IPv6 unicast addresses encode an interface ID that can be
+        extracted using :meth:`as_eui64` or even :meth:`as_eui48`.
+
+        Site-local addresses are deprecated.
+
+        """
+        return self._has_prefix(0b1111111011, 10)
 
     def get_multicast_scope(self):
         if not self.is_type_multicast():
     def is_multicast_scope_global(self):
         return MULTICAST_SCOPE_GLOBAL == self.get_multicast_scope()
 
-    def is_ipv4_mapped_address(self):
-        return self._has_prefix(0xffff, 96)
-
-    def is_isatap_address(self):
-        if not self.is_type_linklocal_unicast():
-            return False
-        return self.get_address() >> 32 & 0xffff == 0x5efe
-
-    def is_ipv4_translatable_address(self):
-        """Returns whether this IPv6 address uses the IPv4-embedded IPv6 well
-        known prefix. The prefix is currently defined in the draft RFC
-        [draft-ietf-behave-address-format-10]_.
-
-        .. [draft-ietf-behave-address-format-10]
-           http://tools.ietf.org/html/draft-ietf-behave-address-format-10
-
-        """
-        return self._has_prefix(0x64ff9b << 64, 96)
-
-    def embeds_ipv4_address(self):
-        """Returns whether this IPv6 address embeds an IPv4 address in the lower
-        32 bits. Currently recognized as IPv4 embedded addresses are
-        IPv4-mapped IPv6 addresses (see :meth:`is_ipv4_mapped_address`), ISATAP
-        addresses (:meth:`is_isatap_address`) and IPv4 translatable addresses
-        using the well-known prefix (:meth:`is_ipv4_translatable_address`).
-
-        >>> # IPv4-mapped IPv6 address
-        >>> IPv6Address('::ffff:192.0.2.128').embeds_ipv4_address()
-        True
-        >>> # ISATAP address
-        >>> IPv6Address('fe80::5efe:192.0.2.143').embeds_ipv4_address()
-        True
-        >>> # IPv4-Embedded IPv6 addresses using the Well-Known Prefix
-        >>> IPv6Address('64:ff9b::192.0.2.33').embeds_ipv4_address()
-        True
-
-        IPv4 embedded addresses will be represented with the IPv4 part in
-        dot-decimal notation by default when calling :func:`str` on the address.
-
-        """
-        return self.is_ipv4_mapped_address() \
-            or self.is_isatap_address() \
-            or self.is_ipv4_translatable_address()
-
     def as_ipv4(self):
-        if not self.embeds_ipv4_address():
+        if not self.is_embedded_ipv4_address():
             raise ValueError("%s is not an IPv4-mapped IPv6 value" % self)
         return IPv4Address(0xffffffff & self.get_address())
 
     def as_ipv6(self):
         return self
 
+    def as_eui48(self):
+        pass
+
+    def as_eui64(self):
+        pass
+
     def __str__(self, omit_leading_zeros=True, zero_compress=True,
             lowercase=True, ipv4_aware=True):
         address = self.get_address()
 
         # Check if we need to encode the last 32 bit in IPv4 dotted decimal
         has_ipv4 = False
-        if ipv4_aware and self.embeds_ipv4_address():
+        if ipv4_aware and self.is_embedded_ipv4_address():
             has_ipv4 = True
             address >>= IPv4Address.BIT_SIZE
             address_size = IPv6Address.BIT_SIZE - IPv4Address.BIT_SIZE
 IPv6Address.LOOPBACK_ADDRESS = IPv6Address(1)
 
 
+class AddressVisitor(object):
+
+    def visit(self, address):
+        address.accept(self)
+
+    def visit_default(self, address):
+        pass
+
+    visit_EUI48Address = visit_default
+    visit_EUI64Address = visit_default
+    visit_IPv4Address = visit_default
+    visit_IPv6Address = visit_default
+
+