diff -r e1fdd79cfb01 Lib/ipaddress.py --- a/Lib/ipaddress.py Wed Aug 21 19:45:19 2013 +0100 +++ b/Lib/ipaddress.py Thu Aug 22 01:21:13 2013 +0100 @@ -1019,9 +1019,6 @@ _ALL_ONES = (2**IPV4LENGTH) - 1 _DECIMAL_DIGITS = frozenset('0123456789') - # the valid octets for host and netmasks. only useful for IPv4. - _valid_mask_octets = frozenset((255, 254, 252, 248, 240, 224, 192, 128, 0)) - def __init__(self, address): self._version = 4 self._max_prefixlen = IPV4LENGTH @@ -1114,19 +1111,21 @@ netmask. """ - mask = netmask.split('.') - if len(mask) == 4: + if netmask.count('.') == 3: + # Looks like a netmask. (Definitely not a prefixlen). + # Try to parse it. try: - for x in mask: - if int(x) not in self._valid_mask_octets: - return False - except ValueError: - # Found something that isn't an integer or isn't valid + netmask = self._ip_int_from_string(netmask) + except AddressValueError: return False - for idx, y in enumerate(mask): - if idx > 0 and y > mask[idx - 1]: - return False - return True + + # Easier to validate hostmask than netmask, so convert it. + hostmask = netmask ^ self._ALL_ONES + + # Valid hostmasks must be either zero, or 1 less than a power of 2. + return ((hostmask + 1) & hostmask) == 0 + + # Not a valid netmask. Try it as a prefix length. try: netmask = int(netmask) except ValueError: @@ -1143,16 +1142,14 @@ A boolean, True if the IP string is a hostmask. """ - bits = ip_str.split('.') + # Try to parse it. try: - parts = [x for x in map(int, bits) if x in self._valid_mask_octets] - except ValueError: + hostmask = self._ip_int_from_string(ip_str) + except AddressValueError: return False - if len(parts) != len(bits): - return False - if parts[0] < parts[-1]: - return True - return False + + # Valid hostmasks must be either zero, or 1 less than a power of 2. + return ((hostmask + 1) & hostmask) == 0 @property def max_prefixlen(self): diff -r e1fdd79cfb01 Lib/test/test_ipaddress.py --- a/Lib/test/test_ipaddress.py Wed Aug 21 19:45:19 2013 +0100 +++ b/Lib/test/test_ipaddress.py Thu Aug 22 01:21:13 2013 +0100 @@ -410,6 +410,9 @@ assertBadNetmask("1.1.1.1", "254.xyz.2.3") assertBadNetmask("1.1.1.1", "240.255.0.0") assertBadNetmask("1.1.1.1", "pudding") + assertBadNetmask("254.192.128.0", "254.192.128.0") + assertBadNetmask("254.192.128.0", "0.255.0.255") + assertBadNetmask("254.192.0.0", "0.0.0255.255") class InterfaceTestCase_v4(BaseTestCase, NetmaskTestMixin_v4): factory = ipaddress.IPv4Interface @@ -417,6 +420,197 @@ class NetworkTestCase_v4(BaseTestCase, NetmaskTestMixin_v4): factory = ipaddress.IPv4Network + # Test strategy for netmask/hostmask/prefixlen: + # - Test the two edge cases that are netmask and prefixlen only: /0, /32 + # - Test the following for all ways of specifying it + # - Test edge cases: /1, /31 + # - Test the common cases: /8, /16, /24 + # - Test one general case in the middle (/21) + # Total: 2*2+3*(2+3+1) == 22 test cases + # + # Since IPv4Interface calls IPv4Network to do the actual parsing of this, + # it's enough to test just IPv4Network. + + def test_good_netmask_0(self): + # The documentation says that 0.0.0.0 is interpreted as a netmask, + # not a hostmask. + addr = ipaddress.IPv4Network('1.2.3.4/0.0.0.0', False) + self.assertEqual(addr.prefixlen, 0) + self.assertEqual(str(addr.netmask), '0.0.0.0') + self.assertEqual(str(addr.hostmask), '255.255.255.255') + self.assertEqual(str(addr.network_address), '0.0.0.0') + self.assertEqual(str(addr.broadcast_address), '255.255.255.255') + + def test_good_netmask_1(self): + addr = ipaddress.IPv4Network('129.2.3.4/128.0.0.0', False) + self.assertEqual(addr.prefixlen, 1) + self.assertEqual(str(addr.netmask), '128.0.0.0') + self.assertEqual(str(addr.hostmask), '127.255.255.255') + self.assertEqual(str(addr.network_address), '128.0.0.0') + self.assertEqual(str(addr.broadcast_address), '255.255.255.255') + + def test_good_netmask_8(self): + addr = ipaddress.IPv4Network('1.2.3.4/255.0.0.0', False) + self.assertEqual(addr.prefixlen, 8) + self.assertEqual(str(addr.netmask), '255.0.0.0') + self.assertEqual(str(addr.hostmask), '0.255.255.255') + self.assertEqual(str(addr.network_address), '1.0.0.0') + self.assertEqual(str(addr.broadcast_address), '1.255.255.255') + + def test_good_netmask_16(self): + addr = ipaddress.IPv4Network('1.2.3.4/255.255.0.0', False) + self.assertEqual(addr.prefixlen, 16) + self.assertEqual(str(addr.netmask), '255.255.0.0') + self.assertEqual(str(addr.hostmask), '0.0.255.255') + self.assertEqual(str(addr.network_address), '1.2.0.0') + self.assertEqual(str(addr.broadcast_address), '1.2.255.255') + + def test_good_netmask_21(self): + addr = ipaddress.IPv4Network('1.2.9.4/255.255.248.0', False) + self.assertEqual(addr.prefixlen, 21) + self.assertEqual(str(addr.netmask), '255.255.248.0') + self.assertEqual(str(addr.hostmask), '0.0.7.255') + self.assertEqual(str(addr.network_address), '1.2.8.0') + self.assertEqual(str(addr.broadcast_address), '1.2.15.255') + + def test_good_netmask_24(self): + addr = ipaddress.IPv4Network('1.2.3.4/255.255.255.0', False) + self.assertEqual(addr.prefixlen, 24) + self.assertEqual(str(addr.netmask), '255.255.255.0') + self.assertEqual(str(addr.hostmask), '0.0.0.255') + self.assertEqual(str(addr.network_address), '1.2.3.0') + self.assertEqual(str(addr.broadcast_address), '1.2.3.255') + + def test_good_netmask_31(self): + addr = ipaddress.IPv4Network('1.2.3.5/255.255.255.254', False) + self.assertEqual(addr.prefixlen, 31) + self.assertEqual(str(addr.netmask), '255.255.255.254') + self.assertEqual(str(addr.hostmask), '0.0.0.1') + self.assertEqual(str(addr.network_address), '1.2.3.4') + self.assertEqual(str(addr.broadcast_address), '1.2.3.5') + + def test_good_netmask_32(self): + # The documentation says that 255.255.255.255 is interpreted as a + # netmask, not a hostmask. + addr = ipaddress.IPv4Network('1.2.3.4/255.255.255.255', False) + self.assertEqual(addr.prefixlen, 32) + self.assertEqual(str(addr.netmask), '255.255.255.255') + self.assertEqual(str(addr.hostmask), '0.0.0.0') + self.assertEqual(str(addr.network_address), '1.2.3.4') + self.assertEqual(str(addr.broadcast_address), '1.2.3.4') + + def test_good_hostmask_1(self): + addr = ipaddress.IPv4Network('129.2.3.4/127.255.255.255', False) + self.assertEqual(addr.prefixlen, 1) + self.assertEqual(str(addr.netmask), '128.0.0.0') + self.assertEqual(str(addr.hostmask), '127.255.255.255') + self.assertEqual(str(addr.network_address), '128.0.0.0') + self.assertEqual(str(addr.broadcast_address), '255.255.255.255') + + def test_good_hostmask_8(self): + addr = ipaddress.IPv4Network('1.2.3.4/0.255.255.255', False) + self.assertEqual(addr.prefixlen, 8) + self.assertEqual(str(addr.netmask), '255.0.0.0') + self.assertEqual(str(addr.hostmask), '0.255.255.255') + self.assertEqual(str(addr.network_address), '1.0.0.0') + self.assertEqual(str(addr.broadcast_address), '1.255.255.255') + + def test_good_hostmask_16(self): + addr = ipaddress.IPv4Network('1.2.3.4/0.0.255.255', False) + self.assertEqual(addr.prefixlen, 16) + self.assertEqual(str(addr.netmask), '255.255.0.0') + self.assertEqual(str(addr.hostmask), '0.0.255.255') + self.assertEqual(str(addr.network_address), '1.2.0.0') + self.assertEqual(str(addr.broadcast_address), '1.2.255.255') + + def test_good_hostmask_21(self): + addr = ipaddress.IPv4Network('1.2.9.4/0.0.7.255', False) + self.assertEqual(addr.prefixlen, 21) + self.assertEqual(str(addr.netmask), '255.255.248.0') + self.assertEqual(str(addr.hostmask), '0.0.7.255') + self.assertEqual(str(addr.network_address), '1.2.8.0') + self.assertEqual(str(addr.broadcast_address), '1.2.15.255') + + def test_good_hostmask_24(self): + addr = ipaddress.IPv4Network('1.2.3.4/0.0.0.255', False) + self.assertEqual(addr.prefixlen, 24) + self.assertEqual(str(addr.netmask), '255.255.255.0') + self.assertEqual(str(addr.hostmask), '0.0.0.255') + self.assertEqual(str(addr.network_address), '1.2.3.0') + self.assertEqual(str(addr.broadcast_address), '1.2.3.255') + + def test_good_hostmask_31(self): + addr = ipaddress.IPv4Network('1.2.3.5/0.0.0.1', False) + self.assertEqual(addr.prefixlen, 31) + self.assertEqual(str(addr.netmask), '255.255.255.254') + self.assertEqual(str(addr.hostmask), '0.0.0.1') + self.assertEqual(str(addr.network_address), '1.2.3.4') + self.assertEqual(str(addr.broadcast_address), '1.2.3.5') + + def test_good_prefixlen_0(self): + addr = ipaddress.IPv4Network('1.2.3.4/0', False) + self.assertEqual(addr.prefixlen, 0) + self.assertEqual(str(addr.netmask), '0.0.0.0') + self.assertEqual(str(addr.hostmask), '255.255.255.255') + self.assertEqual(str(addr.network_address), '0.0.0.0') + self.assertEqual(str(addr.broadcast_address), '255.255.255.255') + + def test_good_prefixlen_1(self): + addr = ipaddress.IPv4Network('129.2.3.4/1', False) + self.assertEqual(addr.prefixlen, 1) + self.assertEqual(str(addr.netmask), '128.0.0.0') + self.assertEqual(str(addr.hostmask), '127.255.255.255') + self.assertEqual(str(addr.network_address), '128.0.0.0') + self.assertEqual(str(addr.broadcast_address), '255.255.255.255') + + def test_good_prefixlen_8(self): + addr = ipaddress.IPv4Network('1.2.3.4/8', False) + self.assertEqual(addr.prefixlen, 8) + self.assertEqual(str(addr.netmask), '255.0.0.0') + self.assertEqual(str(addr.hostmask), '0.255.255.255') + self.assertEqual(str(addr.network_address), '1.0.0.0') + self.assertEqual(str(addr.broadcast_address), '1.255.255.255') + + def test_good_prefixlen_16(self): + addr = ipaddress.IPv4Network('1.2.3.4/16', False) + self.assertEqual(addr.prefixlen, 16) + self.assertEqual(str(addr.netmask), '255.255.0.0') + self.assertEqual(str(addr.hostmask), '0.0.255.255') + self.assertEqual(str(addr.network_address), '1.2.0.0') + self.assertEqual(str(addr.broadcast_address), '1.2.255.255') + + def test_good_prefixlen_21(self): + addr = ipaddress.IPv4Network('1.2.9.4/21', False) + self.assertEqual(addr.prefixlen, 21) + self.assertEqual(str(addr.netmask), '255.255.248.0') + self.assertEqual(str(addr.hostmask), '0.0.7.255') + self.assertEqual(str(addr.network_address), '1.2.8.0') + self.assertEqual(str(addr.broadcast_address), '1.2.15.255') + + def test_good_prefixlen_24(self): + addr = ipaddress.IPv4Network('1.2.3.4/24', False) + self.assertEqual(addr.prefixlen, 24) + self.assertEqual(str(addr.netmask), '255.255.255.0') + self.assertEqual(str(addr.hostmask), '0.0.0.255') + self.assertEqual(str(addr.network_address), '1.2.3.0') + self.assertEqual(str(addr.broadcast_address), '1.2.3.255') + + def test_good_prefixlen_31(self): + addr = ipaddress.IPv4Network('1.2.3.5/31', False) + self.assertEqual(addr.prefixlen, 31) + self.assertEqual(str(addr.netmask), '255.255.255.254') + self.assertEqual(str(addr.hostmask), '0.0.0.1') + self.assertEqual(str(addr.network_address), '1.2.3.4') + self.assertEqual(str(addr.broadcast_address), '1.2.3.5') + + def test_good_prefixlen_32(self): + addr = ipaddress.IPv4Network('1.2.3.4/32', False) + self.assertEqual(addr.prefixlen, 32) + self.assertEqual(str(addr.netmask), '255.255.255.255') + self.assertEqual(str(addr.hostmask), '0.0.0.0') + self.assertEqual(str(addr.network_address), '1.2.3.4') + self.assertEqual(str(addr.broadcast_address), '1.2.3.4') + class NetmaskTestMixin_v6(CommonTestMixin_v6): """Input validation on interfaces and networks is very similar""" @@ -708,14 +902,16 @@ def testIPv4NetAndHostmasks(self): net = self.ipv4_network self.assertFalse(net._is_valid_netmask('invalid')) - self.assertTrue(net._is_valid_netmask('128.128.128.128')) + self.assertFalse(net._is_valid_netmask('128.128.128.128')) self.assertFalse(net._is_valid_netmask('128.128.128.127')) self.assertFalse(net._is_valid_netmask('128.128.128.255')) - self.assertTrue(net._is_valid_netmask('255.128.128.128')) + self.assertFalse(net._is_valid_netmask('255.128.128.128')) + self.assertTrue(net._is_valid_netmask('255.255.192.0')) self.assertFalse(net._is_hostmask('invalid')) - self.assertTrue(net._is_hostmask('128.255.255.255')) - self.assertFalse(net._is_hostmask('255.255.255.255')) + self.assertFalse(net._is_hostmask('128.255.255.255')) + self.assertTrue(net._is_hostmask('127.255.255.255')) + self.assertTrue(net._is_hostmask('255.255.255.255')) self.assertFalse(net._is_hostmask('1.2.3.4')) net = ipaddress.IPv4Network('127.0.0.0/0.0.0.255')