# vim: set et sw=4 sts=4 fileencoding=utf-8:
#
# Copyright (c) 2013-2017 Dave Jones <dave@waveform.org.uk>
# Copyright (c) 2013 Mime Consulting Ltd. <info@mimeconsulting.co.uk>
# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
#
# The documentation for the IPv4Address, IPv4Network, IPv6Address, and
# IPv6Network classes in this module are derived from the ipaddress
# documentation sources which are subject to the following copyright and are
# licensed to the PSF under the contributor agreement which makes them subject
# to the PSF license stated above.
#
# Copyright (c) 2007 Google Inc.
"""
Defines the IP address related parts of :mod:`lars.datatypes`.
"""
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
import re
from functools import total_ordering
try:
import ipaddress
except ImportError:
import ipaddr as ipaddress
from lars import dns
from lars import geoip
native_str = str # pylint: disable=invalid-name
str = type('') # pylint: disable=redefined-builtin,invalid-name
[docs]def hostname(s):
"""
Returns a :class:`Hostname`, :class:`IPv4Address`, or :class:`IPv6Address`
object for the given string depending on whether it represents an IP
address or a hostname.
:param str s: The string containing the hostname to parse
:returns: A :class:`Hostname`, :class:`IPv4Address`, or
:class:`IPv6Address` instance
"""
if isinstance(s, bytes):
s = s.decode('utf-8')
try:
return IPv4Address(s)
except ValueError:
pass
try:
return IPv6Address(s)
except ValueError:
pass
return Hostname(s)
[docs]def network(s):
"""
Returns an :class:`IPv4Network` or :class:`IPv6Network` instance for the
given string.
:param str s: The string containing the IP network to parse
:returns: An :class:`IPv4Network` or :class:`IPv6Network` instance
"""
if isinstance(s, bytes):
s = s.decode('utf-8')
try:
return IPv4Network(s)
except ValueError:
pass
try:
return IPv6Network(s)
except ValueError:
pass
raise ValueError(
'%s does not appear to be a valid IPv4 or IPv6 network' % s)
[docs]def address(s):
"""
Returns an :class:`IPv4Address`, :class:`IPv6Address`, :class:`IPv4Port`,
or :class:`IPv6Port` instance for the given string.
:param str s: The string containing the IP address to parse
:returns: An :class:`IPv4Address`, :class:`IPv4Port`, :class:`IPv6Address`,
or :class:`IPv6Port` instance
"""
if isinstance(s, bytes):
s = s.decode('utf-8')
try:
return IPv4Address(s)
except ValueError:
pass
try:
return IPv6Address(s)
except ValueError:
pass
try:
return IPv4Port(s)
except ValueError:
pass
try:
return IPv6Port(s)
except ValueError:
pass
raise ValueError(
'%s does not appear to be a valid IPv4 or IPv6 address' % s)
[docs]@total_ordering
class Hostname(str):
"""
Represents an Internet hostname and provides attributes for DNS resolution.
This type is returned by the :func:`hostname` function and represents a DNS
hostname. The :attr:`address` property allows resolution of the hostname
to an IP address.
:param str hostname: The hostname to parse
"""
name_part_re = re.compile(r'^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$',
flags=re.UNICODE)
def __init__(self, s):
if len(s) > 255:
raise ValueError('DNS name %s is longer than 255 chars' % hostname)
for part in s.split('.'):
# XXX What about IPv6 addresses? Check with address_parse?
if not self.name_part_re.match(part):
raise ValueError('DNS label %s is invalid' % part)
super(Hostname, self).__init__()
@property
def address(self):
"""
Attempts to resolve the hostname into an IPv4 or IPv6 address
(returning an :class:`IPv4Address` or :class:`IPv6Address` object
repsectively). The result of the DNS query (including negative lookups
is cached, so repeated queries for the same hostname should be
extremely fast.
"""
ipaddr = dns.to_address(self)
if ipaddr is not None:
return address(ipaddr)
[docs]class IPv4Address(ipaddress.IPv4Address):
# pylint: disable=too-many-ancestors
"""
Represents an IPv4 address.
This type is returned by the :func:`address` function and represents an
IPv4 address and provides various attributes and comparison operators
relevant to such addresses.
For example, to test whether an address belongs to particular network you
can use the ``in`` operator with the result of the :func:`network`
function::
address('192.168.0.64') in network('192.168.0.0/16')
The :attr:`hostname` attribute will perform reverse DNS resolution to
determine a hostname associated with the address (if any). The result of
the query (including negative lookups) is cached so subsequent queries of
the same address should be extermely rapid.
If the :mod:`lars.geoip` module has been initialized with a database, the
GeoIP-related attributes :attr:`country`, :attr:`region`, :attr:`city`, and
:attr:`coords` will return the country, region, city and a (longitude,
latitude) tuple respectively.
.. attribute:: compressed
Returns the shorthand version of the IP address as a string (this is
the default string conversion).
.. attribute:: exploded
Returns the longhand version of the IP address as a string.
.. attribute:: is_link_local
Returns True if the address is reserved for link-local. See `RFC 3927`_
for details.
.. attribute:: is_loopback
Returns True if the address is a loopback address. See `RFC 3330`_ for
details.
.. attribute:: is_multicast
Returns True if the address is reserved for multicast use. See `RFC
3171`_ for details.
.. attribute:: is_private
Returns True if this address is allocated for private networks. See
`RFC 1918`_ for details.
.. attribute:: is_reserved
Returns True if the address is otherwise IETF reserved.
.. attribute:: is_unspecified
Returns True if the address is unspecified. See `RFC 5735 3`_ for
details.
.. attribute:: packed
Returns the binary representation of this address.
"""
# pylint: disable=too-many-ancestors
@property
def country(self):
"""
If :func:`~lars.geoip.init_databases` has been called to initialize
a GeoIP database, returns the country of the address.
"""
return geoip.country_code_by_addr(self)
@property
def region(self):
"""
If :func:`~lars.geoip.init_databases` has been called with a
region-level (or lower) GeoIP database, returns the region of the
address.
"""
return geoip.region_by_addr(self)
@property
def city(self):
"""
If :func:`~lars.geoip.init_databases` has been called with a
city-level GeoIP database, returns the city of the address.
"""
return geoip.city_by_addr(self)
@property
def coords(self):
"""
If :func:`~lars.geoip.init_databases` has been called with a
city-level GeoIP database, returns a (longitude, latitude) tuple
describing the approximate location of the address.
"""
return geoip.coords_by_addr(self)
@property
def isp(self):
"""
If :func:`~lars.geoip.init_databases` has been called with an ISP level
database, returns the ISP that provides connectivity for the address.
"""
return geoip.isp_by_addr(self)
@property
def org(self):
"""
If :func:`~lars.geoip.init_databases` has been called with an
organisation level database, returns the name of the organisation the
address belongs to.
"""
return geoip.org_by_addr(self)
@property
def hostname(self):
"""
Performs a reverse DNS lookup to attempt to determine a hostname for
the address. Lookups (including negative lookups) are cached so that
repeated lookups are extremely quick. Returns a :class:`Hostname`
object if the lookup is successful, or None.
"""
s = self.compressed
result = dns.from_address(s)
if result == s:
return None
return Hostname(result)
[docs]class IPv6Address(ipaddress.IPv6Address):
# pylint: disable=too-many-ancestors
"""
Represents an IPv6 address.
This type is returned by the :func:`address` function and represents an
IPv6 address and provides various attributes and comparison operators
relevant to such addresses.
For example, to test whether an address belongs to particular network you
can use the ``in`` operator with the result of the :func:`network`
function::
address('::1') in network('::/16')
The :attr:`hostname` attribute will perform reverse DNS resolution to
determine a hostname associated with the address (if any). The result of
the query (including negative lookups) is cached so subsequent queries of
the same address should be extermely rapid.
If the :mod:`lars.geoip` module has been initialized with a database, the
GeoIP-related attributes :attr:`country`, :attr:`region`, :attr:`city`, and
:attr:`coords` will return the country, region, city and a (longitude,
latitude) tuple respectively.
.. attribute:: compressed
Returns the shorthand version of the IP address as a string (this is
the default string conversion).
.. attribute:: exploded
Returns the longhand version of the IP address as a string.
.. attribute:: ipv4_mapped
Returns the IPv4 mapped address if the IPv6 address is a v4 mapped
address, or ``None`` otherwise.
.. attribute:: is_link_local
Returns True if the address is reserved for link-local. See `RFC 4291`_
for details.
.. attribute:: is_loopback
Returns True if the address is a loopback address. See `RFC 2373
2.5.3`_ for details.
.. attribute:: is_multicast
Returns True if the address is reserved for multicast use. See `RFC
2373 2.7`_ for details.
.. attribute:: is_private
Returns True if this address is allocated for private networks. See
`RFC 4193`_ for details.
.. attribute:: is_reserved
Returns True if the address is otherwise IETF reserved.
.. attribute:: is_site_local
Returns True if the address is reserved for site-local.
Note that the site-local address space has been deprecated by `RFC
3879`_. Use :attr:`is_private` to test if this address is in the space
of unique local addresses as defined by `RFC 4193`_. See `RFC 3513
2.5.6`_ for details.
.. attribute:: is_unspecified
Returns True if the address is unspecified. See `RFC 2373 2.5.2`_ for
details.
.. attribute:: packed
Returns the binary representation of this address.
.. attribute:: sixtofour
Returns the IPv4 6to4 embedded address if present, or ``None`` if the
address doesn't appear to contain a 6to4 embedded address.
.. attribute:: teredo
Returns a ``(server, client)`` tuple of embedded Teredo IPs, or
``None`` if the address doesn't appear to be a Teredo address (doesn't
start with ``2001::/32``).
"""
@property
def country(self):
"""
If :func:`~lars.geoip.init_databases` has been called to initialize
a GeoIP IPv6 database, returns the country of the address.
"""
return geoip.country_code_by_addr(self)
@property
def region(self):
"""
If :func:`~lars.geoip.init_databases` has been called with a
region-level (or lower) GeoIP IPv6 database, returns the region of the
address.
"""
return geoip.region_by_addr(self)
@property
def city(self):
"""
If :func:`~lars.geoip.init_databases` has been called with a
city-level GeoIP IPv6 database, returns the city of the address.
"""
return geoip.city_by_addr(self)
@property
def coords(self):
"""
If :func:`~lars.geoip.init_databases` has been called with a
city-level GeoIP IPv6 database, returns a (longitude, latitude) tuple
describing the approximate location of the address.
"""
return geoip.coords_by_addr(self)
@property
def isp(self):
"""
If :func:`~lars.geoip.init_databases` has been called with an ISP level
IPv6 database, returns the ISP that provides connectivity for the
address.
"""
return geoip.isp_by_addr(self)
@property
def org(self):
"""
If :func:`~lars.geoip.init_databases` has been called with an IPv6
organisation level database, returns the name of the organisation the
address belongs to.
"""
return geoip.org_by_addr(self)
@property
def hostname(self):
"""
Performs a reverse DNS lookup to attempt to determine a hostname for
the address. Lookups (including negative lookups) are cached so that
repeated lookups are extremely quick. Returns a :class:`Hostname`
object if the lookup is successful, or None.
"""
s = self.compressed
result = dns.from_address(s)
if result == s:
return None
return Hostname(result)
[docs]class IPv4Network(ipaddress.IPv4Network):
# pylint: disable=too-many-ancestors
"""
This type is returned by the :func:`network` function. This class
represents and manipulates 32-bit IPv4 networks.
Attributes: [examples for IPv4Network('192.0.2.0/27')]
* :attr:`network_address`: ``IPv4Address('192.0.2.0')``
* :attr:`hostmask`: ``IPv4Address('0.0.0.31')``
* :attr:`broadcast_address`: ``IPv4Address('192.0.2.32')``
* :attr:`netmask`: ``IPv4Address('255.255.255.224')``
* :attr:`prefixlen`: ``27``
.. method:: address_exclude(other)
Remove an address from a larger block.
For example::
addr1 = network('192.0.2.0/28')
addr2 = network('192.0.2.1/32')
addr1.address_exclude(addr2) = [
IPv4Network('192.0.2.0/32'), IPv4Network('192.0.2.2/31'),
IPv4Network('192.0.2.4/30'), IPv4Network('192.0.2.8/29'),
]
:param other: An IPv4Network object of the same type.
:returns: An iterator of the IPv4Network objects which is self minus
other.
.. method:: compare_networks(other)
Compare two IP objects.
This is only concerned about the comparison of the integer
representation of the network addresses. This means that the host bits
aren't considered at all in this method. If you want to compare host
bits, you can easily enough do a ``HostA._ip < HostB._ip``.
:param other: An IP object.
:returns: -1, 0, or 1 for less than, equal to or greater than
respectively.
.. method:: hosts()
Generate iterator over usable hosts in a network.
This is like :meth:`__iter__` except it doesn't return the network
or broadcast addresses.
.. method:: overlaps(other)
Tells if self is partly contained in *other*.
.. method:: subnets(prefixlen_diff=1, new_prefix=None)
The subnets which join to make the current subnet.
In the case that self contains only one IP (self._prefixlen == 32 for
IPv4 or self._prefixlen == 128 for IPv6), yield an iterator with just
ourself.
:param int prefixlen_diff: An integer, the amount the prefix length
should be increased by. This should not be set if *new_prefix* is
also set.
:param int new_prefix: The desired new prefix length. This must be a
larger number (smaller prefix) than the existing prefix. This
should not be set if *prefixlen_diff* is also set.
:returns: An iterator of IPv(4|6) objects.
.. method:: supernet(prefixlen_diff=1, new_prefix=None)
The supernet containing the current network.
:param int prefixlen_diff: An integer, the amount the prefix length of
the network should be decreased by. For example, given a ``/24``
network and a prefixlen_diff of ``3``, a supernet with a ``/21``
netmask is returned.
:param int new_prefix: The desired new prefix length. This must be a
smaller number (larger prefix) than the existing prefix. This
should not be set if *prefixlen_diff* is also set.
:returns: An IPv4Network object.
.. attribute:: is_link_local
Returns True if the address is reserved for link-local. See `RFC 4291`_
for details.
.. attribute:: is_loopback
Returns True if the address is a loopback address. See `RFC 2373
2.5.3`_ for details.
.. attribute:: is_multicast
Returns True if the address is reserved for multicast use. See `RFC
2373 2.7`_ for details.
.. attribute:: is_private
Returns True if this address is allocated for private networks. See
`RFC 4193`_ for details.
.. attribute:: is_reserved
Returns True if the address is otherwise IETF reserved.
.. attribute:: is_unspecified
Returns True if the address is unspecified. See `RFC 2373 2.5.2`_ for
details.
"""
pass
[docs]class IPv4Port(IPv4Address):
# pylint: disable=too-many-ancestors
"""
Represents an IPv4 address and port number.
This type is returned by the :func:`address` function and represents an
IPv4 address and port number. Other than this, all properties of the base
:class:`IPv4Address` class are equivalent.
.. attribute:: port
An integer representing the network port for a connection
"""
def __init__(self, address):
# pylint: disable=redefined-outer-name
port = None
if ':' in address:
address, port = address.rsplit(':', 1)
port = int(port)
if not 0 <= port <= 65535:
raise ValueError('Invalid port %d' % port)
super(IPv4Port, self).__init__(address)
self.port = port
def __str__(self):
result = super(IPv4Port, self).__str__()
if self.port is not None:
return '%s:%d' % (result, self.port)
return result
[docs]class IPv6Port(IPv6Address):
# pylint: disable=too-many-ancestors
"""
Represents an IPv6 address and port number.
This type is returned by the :func:`address` function an represents an IPv6
address and port number. The string representation of an IPv6 address with
port necessarily wraps the address portion in square brakcets as otherwise
the port number will make the address ambiguous. Other than this, all
properties of the base :class:`IPv6Address` class are equivalent.
.. attribute:: port
An integer representing the network port for a connection
"""
def __init__(self, address):
# pylint: disable=redefined-outer-name,unused-variable
addr, sep, port = address.rpartition(':')
if port.endswith(']'): # [IPv6addr]
addr = '%s:%s' % (addr[1:], port[:-1])
port = None
elif addr.endswith(']'): # [IPv6addr]:port
addr = addr[1:-1]
port = int(port)
if not 0 <= port <= 65535:
raise ValueError('Invalid port %d' % port)
else: # IPv6addr
addr = '%s:%s' % (addr, port)
port = None
super(IPv6Port, self).__init__(addr)
self.port = port
def __str__(self):
result = super(IPv6Port, self).__str__()
if self.port is not None:
return '[%s]:%d' % (result, self.port)
return result
[docs]class IPv6Network(ipaddress.IPv6Network):
# pylint: disable=too-many-ancestors
"""
This type is returned by the :func:`network` function. This class
represents and manipulates 128-bit IPv6 networks.
.. method:: address_exclude(other)
Remove an address from a larger block.
For example::
addr1 = network('192.0.2.0/28')
addr2 = network('192.0.2.1/32')
addr1.address_exclude(addr2) = [
IPv4Network('192.0.2.0/32'), IPv4Network('192.0.2.2/31'),
IPv4Network('192.0.2.4/30'), IPv4Network('192.0.2.8/29'),
]
:param other: An IPv4Network object of the same type.
:returns: An iterator of the IPv4Network objects which is self minus
other.
.. method:: compare_networks(other)
Compare two IP objects.
This is only concerned about the comparison of the integer
representation of the network addresses. This means that the host bits
aren't considered at all in this method. If you want to compare host
bits, you can easily enough do a ``HostA._ip < HostB._ip``.
:param other: An IP object.
:returns: -1, 0, or 1 for less than, equal to or greater than
respectively.
.. method:: hosts()
Generate iterator over usable hosts in a network.
This is like :meth:`__iter__` except it doesn't return the network
or broadcast addresses.
.. method:: overlaps(other)
Tells if self is partly contained in *other*.
.. method:: subnets(prefixlen_diff=1, new_prefix=None)
The subnets which join to make the current subnet.
In the case that self contains only one IP (self._prefixlen == 32 for
IPv4 or self._prefixlen == 128 for IPv6), yield an iterator with just
ourself.
:param int prefixlen_diff: An integer, the amount the prefix length
should be increased by. This should not be set if *new_prefix* is
also set.
:param int new_prefix: The desired new prefix length. This must be a
larger number (smaller prefix) than the existing prefix. This
should not be set if *prefixlen_diff* is also set.
:returns: An iterator of IPv(4|6) objects.
.. method:: supernet(prefixlen_diff=1, new_prefix=None)
The supernet containing the current network.
:param int prefixlen_diff: An integer, the amount the prefix length of
the network should be decreased by. For example, given a ``/24``
network and a prefixlen_diff of ``3``, a supernet with a ``/21``
netmask is returned.
:param int new_prefix: The desired new prefix length. This must be a
smaller number (larger prefix) than the existing prefix. This
should not be set if *prefixlen_diff* is also set.
:returns: An IPv4Network object.
.. attribute:: is_link_local
Returns True if the address is reserved for link-local. See `RFC 4291`_
for details.
.. attribute:: is_loopback
Returns True if the address is a loopback address. See `RFC 2373
2.5.3`_ for details.
.. attribute:: is_multicast
Returns True if the address is reserved for multicast use. See `RFC
2373 2.7`_ for details.
.. attribute:: is_private
Returns True if this address is allocated for private networks. See
`RFC 4193`_ for details.
.. attribute:: is_reserved
Returns True if the address is otherwise IETF reserved.
.. attribute:: is_unspecified
Returns True if the address is unspecified. See `RFC 2373 2.5.2`_ for
details.
"""
pass