summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Moody <pmoody@google.com>2014-07-26 09:15:00 -0700
committerPeter Moody <pmoody@google.com>2014-07-26 09:15:00 -0700
commitc813f4790d1092b1515ee18fe8270180ed3cc5cb (patch)
treeb5347307a1109d0400bfdfa7671d28868d6e499f
parent669c0368e3e4ad5dbfd611640b461018592436b2 (diff)
downloadipaddr-py-c813f4790d1092b1515ee18fe8270180ed3cc5cb.tar.gz
ipaddr: add the ability to create non-trivial IPv*Network objects via tuples.
patch by pmarks.
-rw-r--r--trunk/ipaddr.py217
-rwxr-xr-xtrunk/ipaddr_test.py60
2 files changed, 195 insertions, 82 deletions
diff --git a/trunk/ipaddr.py b/trunk/ipaddr.py
index c9aee63..33eb405 100644
--- a/trunk/ipaddr.py
+++ b/trunk/ipaddr.py
@@ -876,6 +876,26 @@ class _BaseNet(_IPAddrBase):
else:
raise NetmaskValueError('Bit pattern does not match /1*0*/')
+ def _prefix_from_prefix_int(self, prefixlen):
+ """Validate and return a prefix length integer.
+
+ Args:
+ prefixlen: An integer containing the prefix length.
+
+ Returns:
+ The input, possibly converted from long to int.
+
+ Raises:
+ NetmaskValueError: If the input is not an integer, or out of range.
+ """
+ if not isinstance(prefixlen, (int, long)):
+ raise NetmaskValueError('%r is not an integer' % prefixlen)
+ prefixlen = int(prefixlen)
+ if not (0 <= prefixlen <= self._max_prefixlen):
+ raise NetmaskValueError('%d is not a valid prefix length' %
+ prefixlen)
+ return prefixlen
+
def _prefix_from_prefix_string(self, prefixlen_str):
"""Turn a prefix length string into an integer.
@@ -893,12 +913,10 @@ class _BaseNet(_IPAddrBase):
if not _BaseV4._DECIMAL_DIGITS.issuperset(prefixlen_str):
raise ValueError
prefixlen = int(prefixlen_str)
- if not (0 <= prefixlen <= self._max_prefixlen):
- raise ValueError
except ValueError:
raise NetmaskValueError('%s is not a valid prefix length' %
prefixlen_str)
- return prefixlen
+ return self._prefix_from_prefix_int(prefixlen)
def _prefix_from_ip_string(self, ip_str):
"""Turn a netmask/hostmask string into a prefix length.
@@ -1239,6 +1257,11 @@ class IPv4Address(_BaseV4, _BaseIP):
"""
_BaseV4.__init__(self, address)
+ # Efficient copy constructor.
+ if isinstance(address, IPv4Address):
+ self._ip = address._ip
+ return
+
# Efficient constructor from integer.
if isinstance(address, (int, long)):
self._ip = address
@@ -1279,29 +1302,32 @@ class IPv4Network(_BaseV4, _BaseNet):
"""Instantiate a new IPv4 network object.
Args:
- address: A string or integer representing the IP [& network].
- '192.168.1.1/24'
- '192.168.1.1/255.255.255.0'
- '192.168.1.1/0.0.0.255'
- are all functionally the same in IPv4. Similarly,
- '192.168.1.1'
- '192.168.1.1/255.255.255.255'
- '192.168.1.1/32'
- are also functionaly equivalent. That is to say, failing to
- provide a subnetmask will create an object with a mask of /32.
-
- If the mask (portion after the / in the argument) is given in
- dotted quad form, it is treated as a netmask if it starts with a
- non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it
- starts with a zero field (e.g. 0.255.255.255 == /8), with the
- single exception of an all-zero mask which is treated as a
- netmask == /0. If no mask is given, a default of /32 is used.
-
- Additionally, an integer can be passed, so
- IPv4Network('192.168.1.1') == IPv4Network(3232235777).
- or, more generally
- IPv4Network(int(IPv4Network('192.168.1.1'))) ==
- IPv4Network('192.168.1.1')
+ address: The IPv4 network as a string, 2-tuple, or any format
+ supported by the IPv4Address constructor.
+
+ Strings typically use CIDR format, such as '192.0.2.0/24'.
+ If a dotted-quad is provided after the '/', it is treated as
+ a netmask if it starts with a nonzero bit (e.g. 255.0.0.0 == /8)
+ or a hostmask if it starts with a zero bit
+ (e.g. /0.0.0.255 == /8), with the single exception of an all-zero
+ mask which is treated as /0.
+
+ The 2-tuple format consists of an (ip, prefixlen), where ip is any
+ format recognized by the IPv4Address constructor, and prefixlen is
+ an integer from 0 through 32.
+
+ A plain IPv4 address (in any format) will be forwarded to the
+ IPv4Address constructor, with an implied prefixlen of 32.
+
+ For example, the following inputs are equivalent:
+ IPv4Network('192.0.2.1/32')
+ IPv4Network('192.0.2.1/255.255.255.255')
+ IPv4Network('192.0.2.1')
+ IPv4Network(0xc0000201)
+ IPv4Network(IPv4Address('192.0.2.1'))
+ IPv4Network(('192.0.2.1', 32))
+ IPv4Network((0xc0000201, 32))
+ IPv4Network((IPv4Address('192.0.2.1'), 32))
strict: A boolean. If true, ensure that we have been passed
A true network address, eg, 192.168.1.0/24 and not an
@@ -1318,41 +1344,51 @@ class IPv4Network(_BaseV4, _BaseNet):
_BaseNet.__init__(self, address)
_BaseV4.__init__(self, address)
- # Constructing from an integer or packed bytes.
- if isinstance(address, (int, long, Bytes)):
+ # Constructing from a single IP address.
+ if isinstance(address, (int, long, Bytes, IPv4Address)):
self.ip = IPv4Address(address)
self._ip = self.ip._ip
self._prefixlen = self._max_prefixlen
self.netmask = IPv4Address(self._ALL_ONES)
return
- # Assume input argument to be string or any object representation
- # which converts into a formatted IP prefix string.
- addr = str(address).split('/')
+ # Constructing from an (ip, prefixlen) tuple.
+ if isinstance(address, tuple):
+ try:
+ ip, prefixlen = address
+ except ValueError:
+ raise AddressValueError(address)
+ self.ip = IPv4Address(ip)
+ self._ip = self.ip._ip
+ self._prefixlen = self._prefix_from_prefix_int(prefixlen)
- if len(addr) > 2:
- raise AddressValueError(address)
+ else:
+ # Assume input argument to be string or any object representation
+ # which converts into a formatted IP prefix string.
+ addr = str(address).split('/')
- self._ip = self._ip_int_from_string(addr[0])
- self.ip = IPv4Address(self._ip)
+ if len(addr) > 2:
+ raise AddressValueError(address)
- if len(addr) == 2:
- try:
- # Check for a netmask in prefix length form.
- self._prefixlen = self._prefix_from_prefix_string(addr[1])
- except NetmaskValueError:
- # Check for a netmask or hostmask in dotted-quad form.
- # This may raise NetmaskValueError.
- self._prefixlen = self._prefix_from_ip_string(addr[1])
- else:
- self._prefixlen = self._max_prefixlen
+ self._ip = self._ip_int_from_string(addr[0])
+ self.ip = IPv4Address(self._ip)
+
+ if len(addr) == 2:
+ try:
+ # Check for a netmask in prefix length form.
+ self._prefixlen = self._prefix_from_prefix_string(addr[1])
+ except NetmaskValueError:
+ # Check for a netmask or hostmask in dotted-quad form.
+ # This may raise NetmaskValueError.
+ self._prefixlen = self._prefix_from_ip_string(addr[1])
+ else:
+ self._prefixlen = self._max_prefixlen
self.netmask = IPv4Address(self._ip_int_from_prefix(self._prefixlen))
if strict:
if self.ip != self.network:
- raise ValueError('%s has host bits set' %
- self.ip)
+ raise ValueError('%s has host bits set' % self.ip)
if self._prefixlen == (self._max_prefixlen - 1):
self.iterhosts = self.__iter__
@@ -1752,6 +1788,11 @@ class IPv6Address(_BaseV6, _BaseIP):
"""
_BaseV6.__init__(self, address)
+ # Efficient copy constructor.
+ if isinstance(address, IPv6Address):
+ self._ip = address._ip
+ return
+
# Efficient constructor from integer.
if isinstance(address, (int, long)):
self._ip = address
@@ -1771,9 +1812,6 @@ class IPv6Address(_BaseV6, _BaseIP):
# Assume input argument to be string or any object representation
# which converts into a formatted IP string.
addr_str = str(address)
- if not addr_str:
- raise AddressValueError('')
-
self._ip = self._ip_int_from_string(addr_str)
@@ -1793,28 +1831,34 @@ class IPv6Network(_BaseV6, _BaseNet):
def __init__(self, address, strict=False):
- """Instantiate a new IPv6 Network object.
+ """Instantiate a new IPv6 network object.
Args:
- address: A string or integer representing the IPv6 network or the IP
- and prefix/netmask.
- '2001:4860::/128'
- '2001:4860:0000:0000:0000:0000:0000:0000/128'
- '2001:4860::'
- are all functionally the same in IPv6. That is to say,
- failing to provide a subnetmask will create an object with
- a mask of /128.
+ address: The IPv6 network as a string, 2-tuple, or any format
+ supported by the IPv6Address constructor.
- Additionally, an integer can be passed, so
- IPv6Network('2001:4860::') ==
- IPv6Network(42541956101370907050197289607612071936L).
- or, more generally
- IPv6Network(IPv6Network('2001:4860::')._ip) ==
- IPv6Network('2001:4860::')
+ Strings should be in CIDR format, such as '2001:db8::/32'.
+
+ The 2-tuple format consists of an (ip, prefixlen), where ip is any
+ format recognized by the IPv6Address constructor, and prefixlen is
+ an integer from 0 through 128.
+
+ A plain IPv6 address (in any format) will be forwarded to the
+ IPv6Address constructor, with an implied prefixlen of 128.
+
+ For example, the following inputs are equivalent:
+ IPv6Network('2001:db8::/128')
+ IPv6Network('2001:db8:0:0:0:0:0:0/128')
+ IPv6Network('2001:db8::')
+ IPv6Network(0x20010db8 << 96)
+ IPv6Network(IPv6Address('2001:db8::'))
+ IPv6Network(('2001:db8::', 128))
+ IPv6Network((0x20010db8 << 96, 128))
+ IPv6Network((IPv6Address('2001:db8::'), 128))
strict: A boolean. If true, ensure that we have been passed
- A true network address, eg, 192.168.1.0/24 and not an
- IP address on a network, eg, 192.168.1.1/24.
+ A true network address, eg, 2001:db8::/32 and not an
+ IP address on a network, eg, 2001:db8::1/32.
Raises:
AddressValueError: If address isn't a valid IPv6 address.
@@ -1827,29 +1871,40 @@ class IPv6Network(_BaseV6, _BaseNet):
_BaseNet.__init__(self, address)
_BaseV6.__init__(self, address)
- # Constructing from an integer or packed bytes.
- if isinstance(address, (int, long, Bytes)):
+ # Constructing from a single IP address.
+ if isinstance(address, (int, long, Bytes, IPv6Address)):
self.ip = IPv6Address(address)
self._ip = self.ip._ip
self._prefixlen = self._max_prefixlen
self.netmask = IPv6Address(self._ALL_ONES)
return
- # Assume input argument to be string or any object representation
- # which converts into a formatted IP prefix string.
- addr = str(address).split('/')
+ # Constructing from an (ip, prefixlen) tuple.
+ if isinstance(address, tuple):
+ try:
+ ip, prefixlen = address
+ except ValueError:
+ raise AddressValueError(address)
+ self.ip = IPv6Address(ip)
+ self._ip = self.ip._ip
+ self._prefixlen = self._prefix_from_prefix_int(prefixlen)
- if len(addr) > 2:
- raise AddressValueError(address)
+ else:
+ # Assume input argument to be string or any object representation
+ # which converts into a formatted IP prefix string.
+ addr = str(address).split('/')
- self._ip = self._ip_int_from_string(addr[0])
- self.ip = IPv6Address(self._ip)
+ if len(addr) > 2:
+ raise AddressValueError(address)
- if len(addr) == 2:
- # This may raise NetmaskValueError
- self._prefixlen = self._prefix_from_prefix_string(addr[1])
- else:
- self._prefixlen = self._max_prefixlen
+ self._ip = self._ip_int_from_string(addr[0])
+ self.ip = IPv6Address(self._ip)
+
+ if len(addr) == 2:
+ # This may raise NetmaskValueError
+ self._prefixlen = self._prefix_from_prefix_string(addr[1])
+ else:
+ self._prefixlen = self._max_prefixlen
self.netmask = IPv6Address(self._ip_int_from_prefix(self._prefixlen))
diff --git a/trunk/ipaddr_test.py b/trunk/ipaddr_test.py
index 89a6893..79a18c4 100755
--- a/trunk/ipaddr_test.py
+++ b/trunk/ipaddr_test.py
@@ -158,6 +158,14 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertRaises(ipaddr.AddressValueError,
ipaddr.IPv4Address(1)._ip_int_from_string,
'1.a.2.3')
+ self.assertRaises(ipaddr.AddressValueError, ipaddr.IPv4Network,
+ ('1.2.3.0',))
+ self.assertRaises(ipaddr.AddressValueError, ipaddr.IPv6Network,
+ ('2001:db8::',))
+ self.assertRaises(ipaddr.AddressValueError, ipaddr.IPv4Network,
+ ('1.2.3.0', 24, 0))
+ self.assertRaises(ipaddr.AddressValueError, ipaddr.IPv6Network,
+ ('2001:db8::', 32, 0))
def testGetNetwork(self):
self.assertEqual(int(self.ipv4.network), 16909056)
@@ -443,6 +451,13 @@ class IpaddrUnitTest(unittest.TestCase):
net = ipaddr.IPv4Network(net_str)
self.assertEqual(str(net), net_str)
+ # Parse some 2-tuple inputs.
+ self.assertEqual(str(ipaddr.IPv4Network((0, i))), net_str)
+ self.assertEqual(str(ipaddr.IPv4Network(('0.0.0.0', i))), net_str)
+ self.assertEqual(
+ str(ipaddr.IPv4Network((ipaddr.IPAddress('0.0.0.0'), i))),
+ net_str)
+
# Generate and re-parse the expanded netmask.
self.assertEqual(
str(ipaddr.IPv4Network('0.0.0.0/%s' % net.netmask)),
@@ -470,6 +485,13 @@ class IpaddrUnitTest(unittest.TestCase):
net_str = '::/%d' % i
self.assertEqual(str(ipaddr.IPv6Network(net_str)), net_str)
+ # Parse some 2-tuple inputs.
+ self.assertEqual(str(ipaddr.IPv6Network((0, i))), net_str)
+ self.assertEqual(str(ipaddr.IPv6Network(('::', i))), net_str)
+ self.assertEqual(
+ str(ipaddr.IPv6Network((ipaddr.IPAddress('::'), i))),
+ net_str)
+
# Zero prefix is treated as decimal.
self.assertEqual(str(ipaddr.IPv6Network('::/0%d' % i)), net_str)
@@ -479,12 +501,16 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv4Network, '1.2.3.4/-1')
self.assertRaises(ipaddr.NetmaskValueError,
+ ipaddr.IPv4Network, ('1.2.3.4', -1))
+ self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv4Network, '1.2.3.4/+1')
self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv4Network, '1.2.3.4/0x1')
self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv4Network, '1.2.3.4/33')
self.assertRaises(ipaddr.NetmaskValueError,
+ ipaddr.IPv4Network, ('1.2.3.4', 33))
+ self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv4Network, '1.2.3.4/254.254.255.256')
self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv4Network, '1.1.1.1/240.255.0.0')
@@ -501,16 +527,43 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv6Network, '::1/-1')
self.assertRaises(ipaddr.NetmaskValueError,
+ ipaddr.IPv6Network, ('::1', -1))
+ self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv6Network, '::1/+1')
self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv6Network, '::1/0x1')
self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv6Network, '::1/129')
self.assertRaises(ipaddr.NetmaskValueError,
+ ipaddr.IPv6Network, ('::1', 129))
+ self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv6Network, '::1/1.2.3.4')
# IPv6 expanded form is currently not supported.
self.assertRaises(ipaddr.NetmaskValueError,
ipaddr.IPv6Network, '::/::')
+ # The 2-tuple constructors only accept integer prefixlens for now.
+ self.assertRaises(ipaddr.NetmaskValueError, ipaddr.IPv4Network,
+ ('0.0.0.0', '0'))
+ self.assertRaises(ipaddr.NetmaskValueError, ipaddr.IPv6Network,
+ ('::', '0'))
+
+ def testCopyConstructors(self):
+ # Create address types with unparseable str() outputs, to ensure that
+ # the copy constructors aren't taking the slow path.
+ class BadStringIPv4Address(ipaddr.IPv4Address):
+ def __str__(self):
+ return "<IPv4>"
+ class BadStringIPv6Address(ipaddr.IPv6Address):
+ def __str__(self):
+ return "<IPv6>"
+
+ v4addr = BadStringIPv4Address('1.2.3.4')
+ self.assertEqual("<IPv4>", str(v4addr))
+ self.assertEqual(v4addr, ipaddr.IPv4Address(v4addr))
+
+ v6addr = BadStringIPv6Address('2001:db8::')
+ self.assertEqual("<IPv6>", str(v6addr))
+ self.assertEqual(v6addr, ipaddr.IPv6Address(v6addr))
def testNth(self):
self.assertEqual(str(self.ipv4[5]), '1.2.3.5')
@@ -773,7 +826,12 @@ class IpaddrUnitTest(unittest.TestCase):
def testStrictNetworks(self):
self.assertRaises(ValueError, ipaddr.IPNetwork, '192.168.1.1/24',
strict=True)
- self.assertRaises(ValueError, ipaddr.IPNetwork, '::1/120', strict=True)
+ self.assertRaises(ValueError, ipaddr.IPNetwork, ('192.168.1.1', 24),
+ strict=True)
+ self.assertRaises(ValueError, ipaddr.IPNetwork, '::1/120',
+ strict=True)
+ self.assertRaises(ValueError, ipaddr.IPNetwork, ('::1', 120),
+ strict=True)
def testOverlaps(self):
other = ipaddr.IPv4Network('1.2.3.0/30')