diff -r c0e1fb1b0145 -r a4aead244f75 VirtualMailManager/network.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/network.py Thu Jun 28 19:26:50 2012 +0000 @@ -0,0 +1,100 @@ +# -*- coding: UTF-8 -*- +# Copyright (c) 2011 - 2012, Pascal Volk +# See COPYING for distribution information. +""" + VirtualMailManager.network + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Network/IP address related class and function +""" + +import socket + + +class NetInfo(object): + """Simple class for CIDR network addresses an IP addresses.""" + __slots__ = ('_addr', '_prefix', '_bits_max', '_family', '_nw_addr') + + def __init__(self, nw_address): + """Creates a new `NetInfo` instance. + + Argument: + + `nw_address` : basestring + string representation of an IPv4/IPv6 address or network address. + E.g. 192.0.2.13, 192.0.2.0/24, 2001:db8::/32 or ::1 + When the address has no netmask the prefix length will be set to + 32 for IPv4 addresses and 128 for IPv6 addresses. + """ + self._addr = None + self._prefix = 0 + self._bits_max = 0 + self._family = 0 + self._nw_addr = nw_address + self._parse_net_range() + + def __hash__(self): + return hash((self._addr, self._family, self._prefix)) + + def __repr__(self): + return "NetInfo('%s')" % self._nw_addr + + def _parse_net_range(self): + """Parse the network range of `self._nw_addr and assign values + to the class attributes. + `""" + sep = '/' + if self._nw_addr.count(sep): + ip_address, sep, self._prefix = self._nw_addr.partition(sep) + self._family, self._addr = get_ip_addr_info(ip_address) + else: + self._family, self._addr = get_ip_addr_info(self._nw_addr) + self._bits_max = (128, 32)[self._family is socket.AF_INET] + if self._prefix is 0: + self._prefix = self._bits_max + else: + try: + self._prefix = int(self._prefix) + except ValueError: + raise ValueError('Invalid prefix length: %r' % self._prefix) + if self._prefix > self._bits_max or self._prefix < 0: + raise ValueError('Invalid prefix length: %r' % self._prefix) + + @property + def family(self): + """Address family: `socket.AF_INET` or `socket.AF_INET6`""" + return self._family + + def address_in_net(self, ip_address): + """Checks if the `ip_address` belongs to the same subnet.""" + family, address = get_ip_addr_info(ip_address) + if family != self._family: + return False + return address >> self._bits_max - self._prefix == \ + self._addr >> self._bits_max - self._prefix + + +def get_ip_addr_info(ip_address): + """Checks if the string `ip_address` is a valid IPv4 or IPv6 address. + + When the `ip_address` could be validated successfully a tuple + `(address_family, address_as_long)` will be returned. The + `address_family`will be either `socket.AF_INET` or `socket.AF_INET6`. + """ + if not isinstance(ip_address, basestring) or not ip_address: + raise TypeError('ip_address must be a non empty string.') + if not ip_address.count(':'): + family = socket.AF_INET + try: + address = socket.inet_aton(ip_address) + except socket.error: + raise ValueError('Not a valid IPv4 address: %r' % ip_address) + elif not socket.has_ipv6: + raise ValueError('Unsupported IP address (IPv6): %r' % ip_address) + else: + family = socket.AF_INET6 + try: + address = socket.inet_pton(family, ip_address) + except socket.error: + raise ValueError('Not a valid IPv6 address: %r' % ip_address) + return (family, long(address.encode('hex'), 16))