diff options
Diffstat (limited to 'netaddr/tests')
35 files changed, 3731 insertions, 0 deletions
diff --git a/netaddr/tests/__init__.py b/netaddr/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/netaddr/tests/__init__.py diff --git a/netaddr/tests/core/__init__.py b/netaddr/tests/core/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/netaddr/tests/core/__init__.py diff --git a/netaddr/tests/core/test_compat.py b/netaddr/tests/core/test_compat.py new file mode 100644 index 0000000..de40411 --- /dev/null +++ b/netaddr/tests/core/test_compat.py @@ -0,0 +1,80 @@ +import sys + +import pytest + +from netaddr.compat import _sys_maxint, _is_str, _is_int, _callable, _iter_next +from netaddr.compat import _dict_keys, _dict_items +from netaddr.compat import _bytes_join, _zip, _range +from netaddr.compat import _iter_range + + +@pytest.mark.skipif(sys.version_info < (3,), reason="requires python 3.x") +def test_compat_py3(): + assert _is_str(''.encode()) + + # byte string join tests. + str_8bit = _bytes_join(['a'.encode(), 'b'.encode(), 'c'.encode()]) + + assert str_8bit == 'abc'.encode() + assert "b'abc'" == '%r' % str_8bit + + +@pytest.mark.skipif(sys.version_info > (3,), reason="requires python 2.x") +def test_compat_py2(): + assert _is_str(unicode('')) + + # Python 2.x - 8 bit strings are just regular strings + str_8bit = _bytes_join(['a', 'b', 'c']) + assert str_8bit == 'abc'.encode() + assert "'abc'" == '%r' % str_8bit + + +def test_compat_string_and_int_detection(): + assert _is_int(_sys_maxint) + assert not _is_str(_sys_maxint) + assert _is_str('') + assert _is_str(''.encode()) + + +def test_compat_dict_operations(): + d = { 'a' : 0, 'b' : 1, 'c' : 2 } + assert sorted(_dict_keys(d)) == ['a', 'b', 'c'] + assert sorted(_dict_items(d)) == [('a', 0), ('b', 1), ('c', 2)] + + +def test_compat_zip(): + l2 = _zip([0], [1]) + assert hasattr(_zip(l2), 'pop') + assert l2 == [(0, 1)] + + +def test_compat_range(): + l1 = _range(3) + assert isinstance(l1, list) + assert hasattr(l1, 'pop') + assert l1 == [0, 1, 2] + + it = _iter_range(3) + assert not isinstance(it, list) + assert hasattr(it, '__iter__') + assert it != [0, 1, 2] + assert list(it) == [0, 1, 2] + + +def test_compat_callable(): + i = 1 + + def f1(): + """docstring""" + pass + + f2 = lambda x: x + assert not _callable(i) + + assert _callable(f1) + assert _callable(f2) + + +def test_iter_next(): + it = iter([42]) + assert _iter_next(it) == 42 diff --git a/netaddr/tests/core/test_pubsub.py b/netaddr/tests/core/test_pubsub.py new file mode 100644 index 0000000..1125cc8 --- /dev/null +++ b/netaddr/tests/core/test_pubsub.py @@ -0,0 +1,29 @@ +import pytest +from netaddr.core import Publisher, Subscriber +import pprint + +class Subject(Publisher): + pass + +class Observer(Subscriber): + def __init__(self, id): + self.id = id + + def update(self, data): + return repr(self), pprint.pformat(data) + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, self.id) + +def test_pubsub(): + s = Subject() + s.attach(Observer('foo')) + s.attach(Observer('bar')) + + data = [{'foo': 42}, {'list': [1,'2', list(range(10))]}, {'strings': ['foo', 'bar', 'baz', 'quux']}] + s.notify(data) + + s.notify(['foo', 'bar', 'baz']) + + with pytest.raises(TypeError): + s.attach('foo') diff --git a/netaddr/tests/eui/__init__.py b/netaddr/tests/eui/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/netaddr/tests/eui/__init__.py diff --git a/netaddr/tests/eui/sample_iab.txt b/netaddr/tests/eui/sample_iab.txt new file mode 100644 index 0000000..4742477 --- /dev/null +++ b/netaddr/tests/eui/sample_iab.txt @@ -0,0 +1,6 @@ + +00-50-C2 (hex) ACME CORPORATION +ABC000-ABCFFF (base 16) ACME CORPORATION + 1 MAIN STREET + SPRINGFIELD + UNITED STATES diff --git a/netaddr/tests/eui/sample_oui.txt b/netaddr/tests/eui/sample_oui.txt new file mode 100644 index 0000000..fd33050 --- /dev/null +++ b/netaddr/tests/eui/sample_oui.txt @@ -0,0 +1,6 @@ + +00-CA-FE (hex) ACME CORPORATION +00CAFE (base 16) ACME CORPORATION + 1 MAIN STREET + SPRINGFIELD + UNITED STATES diff --git a/netaddr/tests/eui/test_eui.py b/netaddr/tests/eui/test_eui.py new file mode 100644 index 0000000..c17d0ce --- /dev/null +++ b/netaddr/tests/eui/test_eui.py @@ -0,0 +1,342 @@ +import sys +import pickle +import random + +import pytest + +from netaddr import (EUI, mac_unix, mac_unix_expanded, mac_cisco, + mac_bare, mac_pgsql, eui64_unix, eui64_unix_expanded, + eui64_cisco, eui64_bare, OUI, IAB, IPAddress) + + +def test_mac_address_properties(): + mac = EUI('00-1B-77-49-54-FD') + assert repr(mac) == "EUI('00-1B-77-49-54-FD')" + assert str(mac) + assert str(mac.oui) == '00-1B-77' + assert mac.ei == '49-54-FD' + assert mac.version == 48 + + +def test_mac_address_numerical_operations(): + mac = EUI('00-1B-77-49-54-FD') + assert int(mac) == 117965411581 + assert hex(mac) == '0x1b774954fd' + assert mac.bits() == '00000000-00011011-01110111-01001001-01010100-11111101' + assert mac.bin == '0b1101101110111010010010101010011111101' + + +@pytest.mark.skipif(sys.version_info > (3,), reason="requires python 2.x") +def test_eui_oct_format_py2(): + assert oct(EUI('00-1B-77-49-54-FD')) == '01556722252375' + + +@pytest.mark.skipif(sys.version_info < (3,), reason="requires python 3.x") +def test_eui_oct_format_py3(): + assert oct(EUI('00-1B-77-49-54-FD')) == '0o1556722252375' + + +def test_eui_constructor(): + assert str(EUI('00-1B-77-49-54-FD')) == '00-1B-77-49-54-FD' + assert str(EUI('00-1b-77-49-54-fd')) == '00-1B-77-49-54-FD' + assert str(EUI('0:1b:77:49:54:fd')) == '00-1B-77-49-54-FD' + assert str(EUI('001b:7749:54fd')) == '00-1B-77-49-54-FD' + assert str(EUI('1b:7749:54fd')) == '00-1B-77-49-54-FD' + assert str(EUI('1B:7749:54FD')) == '00-1B-77-49-54-FD' + assert str(EUI('001b774954fd')) == '00-1B-77-49-54-FD' + assert str(EUI('01B774954FD')) == '00-1B-77-49-54-FD' + assert str(EUI('001B77:4954FD')) == '00-1B-77-49-54-FD' + + eui = EUI('00-90-96-AF-CC-39') + + assert eui == EUI('0-90-96-AF-CC-39') + assert eui == EUI('00-90-96-af-cc-39') + assert eui == EUI('00:90:96:AF:CC:39') + assert eui == EUI('00:90:96:af:cc:39') + assert eui == EUI('0090-96AF-CC39') + assert eui == EUI('0090:96af:cc39') + assert eui == EUI('009096-AFCC39') + assert eui == EUI('009096:AFCC39') + assert eui == EUI('009096AFCC39') + assert eui == EUI('009096afcc39') + assert EUI('01-00-00-00-00-00') == EUI('010000000000') + assert EUI('01-00-00-00-00-00') == EUI('10000000000') + assert EUI('01-00-00-01-00-00') == EUI('010000:010000') + assert EUI('01-00-00-01-00-00') == EUI('10000:10000') + + +def test_eui_dialects(): + mac = EUI('00-1B-77-49-54-FD') + assert str(mac) == '00-1B-77-49-54-FD' + + mac = EUI('00-1B-77-49-54-FD', dialect=mac_unix) + assert str(mac) == '0:1b:77:49:54:fd' + + mac = EUI('00-1B-77-49-54-FD', dialect=mac_unix_expanded) + assert str(mac) == '00:1b:77:49:54:fd' + + mac = EUI('00-1B-77-49-54-FD', dialect=mac_cisco) + assert str(mac) == '001b.7749.54fd' + + mac = EUI('00-1B-77-49-54-FD', dialect=mac_bare) + assert str(mac) == '001B774954FD' + + mac = EUI('00-1B-77-49-54-FD', dialect=mac_pgsql) + assert str(mac) == '001b77:4954fd' + + +def test_eui_dialect_property_assignment(): + mac = EUI('00-1B-77-49-54-FD') + assert str(mac) == '00-1B-77-49-54-FD' + + mac.dialect = mac_pgsql + assert str(mac) == '001b77:4954fd' + + +def test_eui_format(): + mac = EUI('00-1B-77-49-54-FD') + assert mac.format() == '00-1B-77-49-54-FD' + assert mac.format(mac_pgsql) == '001b77:4954fd' + assert mac.format(mac_unix_expanded) == '00:1b:77:49:54:fd' + + +def test_eui_custom_dialect(): + class mac_custom(mac_unix): + word_fmt = '%.2X' + + mac = EUI('00-1B-77-49-54-FD', dialect=mac_custom) + assert str(mac) == '00:1B:77:49:54:FD' + + +def test_eui64_dialects(): + mac = EUI('00-1B-77-49-54-FD-12-34') + assert str(mac) == '00-1B-77-49-54-FD-12-34' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_unix) + assert str(mac) == '0:1b:77:49:54:fd:12:34' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_unix_expanded) + assert str(mac) == '00:1b:77:49:54:fd:12:34' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_cisco) + assert str(mac) == '001b.7749.54fd.1234' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_bare) + assert str(mac) == '001B774954FD1234' + + +def test_eui64_dialect_property_assignment(): + mac = EUI('00-1B-77-49-54-FD-12-34') + assert str(mac) == '00-1B-77-49-54-FD-12-34' + + mac.dialect = eui64_cisco + assert str(mac) == '001b.7749.54fd.1234' + + +def test_eui64_custom_dialect(): + class eui64_custom(eui64_unix): + word_fmt = '%.2X' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_custom) + assert str(mac) == '00:1B:77:49:54:FD:12:34' + + +def test_eui_oui_information(): + mac = EUI('00-1B-77-49-54-FD') + oui = mac.oui + assert str(oui) == '00-1B-77' + + assert oui.registration().address == [ + 'Lot 8, Jalan Hi-Tech 2/3', + 'Kulim Kedah 09000', + 'MY' + ] + + assert oui.registration().org == 'Intel Corporate' + + +def test_oui_constructor(): + oui = OUI(524336) + assert str(oui) == '08-00-30' + assert oui == OUI('08-00-30') + + assert oui.registration(0).address == [ + '2380 N. ROSE AVENUE', + 'OXNARD CA 93010', + 'US' + ] + assert oui.registration(0).org == 'NETWORK RESEARCH CORPORATION' + assert oui.registration(0).oui == '08-00-30' + + assert oui.registration(1).address == [ + 'GPO BOX 2476V', + 'MELBOURNE VIC 3001', + 'AU', + ] + assert oui.registration(1).org == 'ROYAL MELBOURNE INST OF TECH' + assert oui.registration(1).oui == '08-00-30' + + assert oui.registration(2).address == [ + 'CH-1211', + 'GENEVE SUISSE/SWITZ 023', + 'CH' + ] + assert oui.registration(2).org == 'CERN' + assert oui.registration(2).oui == '08-00-30' + assert oui.reg_count == 3 + + +def test_oui_hash(): + oui0 = OUI(0) + oui1 = OUI(1) + oui_dict = {oui0: None, oui1: None} + + assert list(oui_dict.keys()) == [OUI(0), OUI(1)] + + +def test_eui_iab(): + mac = EUI('00-50-C2-00-0F-01') + assert mac.is_iab() + + iab = mac.iab + assert str(iab) == '00-50-C2-00-00-00' + assert iab == IAB('00-50-C2-00-00-00') + + reg_info = iab.registration() + + assert reg_info.address == [ + '1241 Superieor Ave E', + 'Cleveland OH 44114', + 'US', + ] + + assert reg_info.iab == '00-50-C2-00-00-00' + assert reg_info.org == 'T.L.S. Corp.' + + +def test_eui64(): + eui = EUI('00-1B-77-FF-FE-49-54-FD') + assert eui == EUI('00-1B-77-FF-FE-49-54-FD') + assert eui.oui == OUI('00-1B-77') + assert eui.ei == 'FF-FE-49-54-FD' + assert eui.eui64() == EUI('00-1B-77-FF-FE-49-54-FD') + + + +def test_mac_to_ipv6_link_local(): + mac = EUI('00-0F-1F-12-E7-33') + ip = mac.ipv6_link_local() + assert ip == IPAddress('fe80::20f:1fff:fe12:e733') + assert ip.is_link_local() + assert mac.eui64() == EUI('00-0F-1F-FF-FE-12-E7-33') + + +def test_iab(): + eui = EUI('00-50-C2-05-C0-00') + + assert eui.is_iab() + assert str(eui.oui) == '00-50-C2' + assert str(eui.iab) == '00-50-C2-05-C0-00' + assert eui.ei == '05-C0-00' + assert int(eui.oui) == 0x0050c2 + assert int(eui.iab) == 0x0050c205c + + assert IAB(eui.value) == eui.iab + + +def test_new_iab(): + eui = EUI('40-D8-55-13-10-00') + + assert eui.is_iab() + assert str(eui.oui) == '40-D8-55' + assert str(eui.iab) == '40-D8-55-13-10-00' + assert eui.ei == '13-10-00' + assert int(eui.oui) == 0x40d855 + assert int(eui.iab) == 0x40d855131 + + assert IAB(eui.value) == eui.iab + + +def test_eui48_vs_eui64(): + eui48 = EUI('01-00-00-01-00-00') + assert int(eui48) == 1099511693312 + + eui64 = EUI('00-00-01-00-00-01-00-00') + assert int(eui64) == 1099511693312 + assert eui48 != eui64 + + +def test_eui_sort_order(): + eui_list = [ + EUI(0, 64), + EUI(0), + EUI(0xffffffffffff, dialect=mac_unix), + EUI(0x1000000000000), + ] + + random.shuffle(eui_list) + eui_list.sort() + + assert [(str(eui), eui.version) for eui in eui_list] == [ + ('00-00-00-00-00-00', 48), + ('ff:ff:ff:ff:ff:ff', 48), + ('00-00-00-00-00-00-00-00', 64), + ('00-01-00-00-00-00-00-00', 64), + ] + + +def test_eui_pickle_support(): + eui1 = EUI('00-00-00-01-02-03') + eui2 = pickle.loads(pickle.dumps(eui1)) + assert eui1 == eui2 + + eui1 = EUI('00-00-00-01-02-03', dialect=mac_cisco) + eui2 = pickle.loads(pickle.dumps(eui1)) + assert eui1 == eui2 + assert eui1.dialect == eui2.dialect + + oui1 = EUI('00-00-00-01-02-03').oui + oui2 = pickle.loads(pickle.dumps(oui1)) + assert oui1 == oui2 + assert oui1.records == oui2.records + + iab1 = EUI('00-50-C2-00-1F-FF').iab + iab2 = pickle.loads(pickle.dumps(iab1)) + assert iab1 == iab2 + assert iab1.record == iab2.record + + +def test_mac_to_eui64_conversion(): + mac = EUI('00-1B-77-49-54-FD') + assert mac == EUI('00-1B-77-49-54-FD') + + eui = mac.eui64() + assert eui == EUI('00-1B-77-FF-FE-49-54-FD') + eui.eui64() == EUI('00-1B-77-FF-FE-49-54-FD') + + assert int(eui) == 7731765737772285 + assert eui.packed == b'\x00\x1bw\xff\xfeIT\xfd' + assert eui.bin == '0b11011011101111111111111111110010010010101010011111101' + assert eui.bits() == '00000000-00011011-01110111-11111111-11111110-01001001-01010100-11111101' + + +def test_mac_to_ipv6(): + mac = EUI('00-1B-77-49-54-FD') + eui = mac.eui64() + assert mac == EUI('00-1B-77-49-54-FD') + assert eui == EUI('00-1B-77-FF-FE-49-54-FD') + + assert mac.modified_eui64() == EUI('02-1B-77-FF-FE-49-54-FD') + assert mac.ipv6_link_local() == IPAddress('fe80::21b:77ff:fe49:54fd') + assert eui.ipv6_link_local() == IPAddress('fe80::21b:77ff:fe49:54fd') + + assert mac.ipv6(0x12340000000000000000000000000000) == IPAddress('1234::21b:77ff:fe49:54fd') + assert eui.ipv6(0x12340000000000000000000000000000) == IPAddress('1234::21b:77ff:fe49:54fd') + + +def test_eui64_constructor(): + addr_colons = EUI('00:1B:77:49:54:FD:BB:34') + assert addr_colons == EUI('00-1B-77-49-54-FD-BB-34') + + addr_no_delimiter = EUI('001B774954FDBB34') + assert addr_no_delimiter == EUI('00-1B-77-49-54-FD-BB-34') diff --git a/netaddr/tests/eui/test_ieee_parsers.py b/netaddr/tests/eui/test_ieee_parsers.py new file mode 100644 index 0000000..69dc014 --- /dev/null +++ b/netaddr/tests/eui/test_ieee_parsers.py @@ -0,0 +1,51 @@ +import contextlib +import sys + +import pytest + +from netaddr.compat import _open_binary +from netaddr.eui.ieee import OUIIndexParser, IABIndexParser, FileIndexer + + +@pytest.mark.skipif(sys.version_info > (3,), reason="requires python 2.x") +def test_oui_parser_py2(): + from cStringIO import StringIO + outfile = StringIO() + with contextlib.closing(_open_binary(__package__, 'sample_oui.txt')) as infile: + iab_parser = OUIIndexParser(infile) + iab_parser.attach(FileIndexer(outfile)) + iab_parser.parse() + assert outfile.getvalue() == '51966,1,138\n' + + +@pytest.mark.skipif(sys.version_info > (3,), reason="requires python 2.x") +def test_iab_parser_py2(): + from cStringIO import StringIO + outfile = StringIO() + with contextlib.closing(_open_binary(__package__, 'sample_iab.txt')) as infile: + iab_parser = IABIndexParser(infile) + iab_parser.attach(FileIndexer(outfile)) + iab_parser.parse() + assert outfile.getvalue() == '84683452,1,181\n' + + +@pytest.mark.skipif(sys.version_info < (3,), reason="requires python 3.x") +def test_oui_parser_py3(): + from io import StringIO + outfile = StringIO() + with contextlib.closing(_open_binary(__package__, 'sample_oui.txt')) as infile: + iab_parser = OUIIndexParser(infile) + iab_parser.attach(FileIndexer(outfile)) + iab_parser.parse() + assert outfile.getvalue() == '51966,1,138\n' + + +@pytest.mark.skipif(sys.version_info < (3,), reason="requires python 3.x") +def test_iab_parser_py3(): + from io import StringIO + outfile = StringIO() + with contextlib.closing(_open_binary(__package__, 'sample_iab.txt')) as infile: + iab_parser = IABIndexParser(infile) + iab_parser.attach(FileIndexer(outfile)) + iab_parser.parse() + assert outfile.getvalue() == '84683452,1,181\n' diff --git a/netaddr/tests/ip/__init__.py b/netaddr/tests/ip/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/netaddr/tests/ip/__init__.py diff --git a/netaddr/tests/ip/test_cidr_v4.py b/netaddr/tests/ip/test_cidr_v4.py new file mode 100644 index 0000000..30d7826 --- /dev/null +++ b/netaddr/tests/ip/test_cidr_v4.py @@ -0,0 +1,195 @@ +import random + +from netaddr import iprange_to_cidrs, IPNetwork, cidr_merge, cidr_exclude, largest_matching_cidr, smallest_matching_cidr, \ + all_matching_cidrs + + +def test_iprange_to_cidrs_worst_case_v4(): + networks = iprange_to_cidrs('0.0.0.1', '255.255.255.254') + assert networks == [ + IPNetwork('0.0.0.1/32'), + IPNetwork('0.0.0.2/31'), + IPNetwork('0.0.0.4/30'), + IPNetwork('0.0.0.8/29'), + IPNetwork('0.0.0.16/28'), + IPNetwork('0.0.0.32/27'), + IPNetwork('0.0.0.64/26'), + IPNetwork('0.0.0.128/25'), + IPNetwork('0.0.1.0/24'), + IPNetwork('0.0.2.0/23'), + IPNetwork('0.0.4.0/22'), + IPNetwork('0.0.8.0/21'), + IPNetwork('0.0.16.0/20'), + IPNetwork('0.0.32.0/19'), + IPNetwork('0.0.64.0/18'), + IPNetwork('0.0.128.0/17'), + IPNetwork('0.1.0.0/16'), + IPNetwork('0.2.0.0/15'), + IPNetwork('0.4.0.0/14'), + IPNetwork('0.8.0.0/13'), + IPNetwork('0.16.0.0/12'), + IPNetwork('0.32.0.0/11'), + IPNetwork('0.64.0.0/10'), + IPNetwork('0.128.0.0/9'), + IPNetwork('1.0.0.0/8'), + IPNetwork('2.0.0.0/7'), + IPNetwork('4.0.0.0/6'), + IPNetwork('8.0.0.0/5'), + IPNetwork('16.0.0.0/4'), + IPNetwork('32.0.0.0/3'), + IPNetwork('64.0.0.0/2'), + IPNetwork('128.0.0.0/2'), + IPNetwork('192.0.0.0/3'), + IPNetwork('224.0.0.0/4'), + IPNetwork('240.0.0.0/5'), + IPNetwork('248.0.0.0/6'), + IPNetwork('252.0.0.0/7'), + IPNetwork('254.0.0.0/8'), + IPNetwork('255.0.0.0/9'), + IPNetwork('255.128.0.0/10'), + IPNetwork('255.192.0.0/11'), + IPNetwork('255.224.0.0/12'), + IPNetwork('255.240.0.0/13'), + IPNetwork('255.248.0.0/14'), + IPNetwork('255.252.0.0/15'), + IPNetwork('255.254.0.0/16'), + IPNetwork('255.255.0.0/17'), + IPNetwork('255.255.128.0/18'), + IPNetwork('255.255.192.0/19'), + IPNetwork('255.255.224.0/20'), + IPNetwork('255.255.240.0/21'), + IPNetwork('255.255.248.0/22'), + IPNetwork('255.255.252.0/23'), + IPNetwork('255.255.254.0/24'), + IPNetwork('255.255.255.0/25'), + IPNetwork('255.255.255.128/26'), + IPNetwork('255.255.255.192/27'), + IPNetwork('255.255.255.224/28'), + IPNetwork('255.255.255.240/29'), + IPNetwork('255.255.255.248/30'), + IPNetwork('255.255.255.252/31'), + IPNetwork('255.255.255.254/32'), + ] + + +def test_cidr_exclude_v4(): + assert cidr_exclude('192.0.2.1/32', '192.0.2.1/32') == [] + assert cidr_exclude('192.0.2.0/31', '192.0.2.1/32') == [IPNetwork('192.0.2.0/32')] + assert cidr_exclude('192.0.2.0/24', '192.0.2.128/25') == [IPNetwork('192.0.2.0/25')] + assert cidr_exclude('192.0.2.0/24', '192.0.2.128/27') == [ + IPNetwork('192.0.2.0/25'), + IPNetwork('192.0.2.160/27'), + IPNetwork('192.0.2.192/26'), + ] + + assert cidr_exclude('192.0.2.1/32', '192.0.2.0/24') == [] + assert cidr_exclude('192.0.2.0/28', '192.0.2.16/32') == [IPNetwork('192.0.2.0/28')] + assert cidr_exclude('192.0.1.255/32', '192.0.2.0/28') == [IPNetwork('192.0.1.255/32')] + + +def test_cidr_merge_v4(): + assert cidr_merge(['192.0.128.0/24', '192.0.129.0/24']) == [IPNetwork('192.0.128.0/23')] + assert cidr_merge(['192.0.129.0/24', '192.0.130.0/24']) == [IPNetwork('192.0.129.0/24'), IPNetwork('192.0.130.0/24')] + assert cidr_merge(['192.0.2.112/30', '192.0.2.116/31', '192.0.2.118/31']) == [IPNetwork('192.0.2.112/29')] + assert cidr_merge(['192.0.2.112/30', '192.0.2.116/32', '192.0.2.118/31']) == [IPNetwork('192.0.2.112/30'), IPNetwork('192.0.2.116/32'), IPNetwork('192.0.2.118/31')] + assert cidr_merge(['192.0.2.112/31', '192.0.2.116/31', '192.0.2.118/31']) == [IPNetwork('192.0.2.112/31'), IPNetwork('192.0.2.116/30')] + + assert cidr_merge([ + '192.0.1.254/31', + '192.0.2.0/28', + '192.0.2.16/28', + '192.0.2.32/28', + '192.0.2.48/28', + '192.0.2.64/28', + '192.0.2.80/28', + '192.0.2.96/28', + '192.0.2.112/28', + '192.0.2.128/28', + '192.0.2.144/28', + '192.0.2.160/28', + '192.0.2.176/28', + '192.0.2.192/28', + '192.0.2.208/28', + '192.0.2.224/28', + '192.0.2.240/28', + '192.0.3.0/28']) == [ + IPNetwork('192.0.1.254/31'), + IPNetwork('192.0.2.0/24'), + IPNetwork('192.0.3.0/28'), + ] + + +def test_extended_cidr_merge(): + + orig_cidr_ipv4 = IPNetwork('192.0.2.0/23') + orig_cidr_ipv6 = IPNetwork('::192.0.2.0/120') + + cidr_subnets = [str(c) for c in orig_cidr_ipv4.subnet(28)] + \ + list(orig_cidr_ipv4.subnet(28)) + \ + [str(c) for c in orig_cidr_ipv6.subnet(124)] + \ + list(orig_cidr_ipv6.subnet(124)) + \ + ['192.0.2.1/32', '192.0.2.128/25', '::192.0.2.92/128'] + + random.shuffle(cidr_subnets) + + merged_cidrs = cidr_merge(cidr_subnets) + + assert merged_cidrs == [IPNetwork('192.0.2.0/23'), IPNetwork('::192.0.2.0/120')] + assert merged_cidrs == [orig_cidr_ipv4, orig_cidr_ipv6] + + +def test_whole_network_cidr_merge_v4(): + assert cidr_merge(['0.0.0.0/0', '0.0.0.0']) == [IPNetwork('0.0.0.0/0')] + assert cidr_merge(['0.0.0.0/0', '255.255.255.255']) == [IPNetwork('0.0.0.0/0')] + assert cidr_merge(['0.0.0.0/0', '192.0.2.0/24', '10.0.0.0/8']) == [IPNetwork('0.0.0.0/0')] + + +def test_largest_matching_cidr_v4(): + assert largest_matching_cidr('192.0.2.0', ['192.0.2.0']) == IPNetwork('192.0.2.0/32') + assert largest_matching_cidr('192.0.2.0', ['10.0.0.1', '192.0.2.0']) == IPNetwork('192.0.2.0/32') + assert largest_matching_cidr('192.0.2.0', ['10.0.0.1', '192.0.2.0', '224.0.0.1']) == IPNetwork('192.0.2.0/32') + assert largest_matching_cidr('192.0.2.0', ['10.0.0.1', '224.0.0.1']) is None + + +def test_smallest_matching_cidr_v4(): + assert smallest_matching_cidr('192.0.2.0', ['10.0.0.1', '192.0.2.0', '224.0.0.1']) == IPNetwork('192.0.2.0/32') + assert smallest_matching_cidr('192.0.2.32', ['0.0.0.0/0', '10.0.0.0/8', '192.0.0.0/8', '192.0.1.0/24', '192.0.2.0/24', '192.0.3.0/24']) == IPNetwork('192.0.2.0/24') + assert smallest_matching_cidr('192.0.2.0', ['10.0.0.1', '224.0.0.1']) is None + + +def test_all_matching_cidrs_v4(): + assert all_matching_cidrs('192.0.2.32', ['0.0.0.0/0', '10.0.0.0/8', '192.0.0.0/8', '192.0.1.0/24', '192.0.2.0/24', '192.0.3.0/24']) == [ + IPNetwork('0.0.0.0/0'), + IPNetwork('192.0.0.0/8'), + IPNetwork('192.0.2.0/24'), + ] + +def test_cidr_matching_v4(): + networks = [str(c) for c in IPNetwork('192.0.2.128/27').supernet(22)] + + assert networks == [ + '192.0.0.0/22', + '192.0.2.0/23', + '192.0.2.0/24', + '192.0.2.128/25', + '192.0.2.128/26', + ] + + assert all_matching_cidrs('192.0.2.0', networks) == [ + IPNetwork('192.0.0.0/22'), + IPNetwork('192.0.2.0/23'), + IPNetwork('192.0.2.0/24'), + ] + + assert smallest_matching_cidr('192.0.2.0', networks) == IPNetwork('192.0.2.0/24') + assert largest_matching_cidr('192.0.2.0', networks) == IPNetwork('192.0.0.0/22') + +# {{{ +# >>> all_matching_cidrs('192.0.2.0', ['192.0.2.0/24']) +# [IPNetwork('192.0.2.0/24')] +# +# >>> all_matching_cidrs('192.0.2.0', ['::/96']) +# [] +# +# +# }}} diff --git a/netaddr/tests/ip/test_cidr_v6.py b/netaddr/tests/ip/test_cidr_v6.py new file mode 100644 index 0000000..449bec7 --- /dev/null +++ b/netaddr/tests/ip/test_cidr_v6.py @@ -0,0 +1,106 @@ +from netaddr import iprange_to_cidrs, IPNetwork, cidr_merge, all_matching_cidrs + + +def test_iprange_to_cidrs_worst_case_v6(): + networks = iprange_to_cidrs('::ffff:1', '::ffff:255.255.255.254') + assert networks == [ + IPNetwork('::255.255.0.1/128'), + IPNetwork('::255.255.0.2/127'), + IPNetwork('::255.255.0.4/126'), + IPNetwork('::255.255.0.8/125'), + IPNetwork('::255.255.0.16/124'), + IPNetwork('::255.255.0.32/123'), + IPNetwork('::255.255.0.64/122'), + IPNetwork('::255.255.0.128/121'), + IPNetwork('::255.255.1.0/120'), + IPNetwork('::255.255.2.0/119'), + IPNetwork('::255.255.4.0/118'), + IPNetwork('::255.255.8.0/117'), + IPNetwork('::255.255.16.0/116'), + IPNetwork('::255.255.32.0/115'), + IPNetwork('::255.255.64.0/114'), + IPNetwork('::255.255.128.0/113'), + IPNetwork('::1:0:0/96'), + IPNetwork('::2:0:0/95'), + IPNetwork('::4:0:0/94'), + IPNetwork('::8:0:0/93'), + IPNetwork('::10:0:0/92'), + IPNetwork('::20:0:0/91'), + IPNetwork('::40:0:0/90'), + IPNetwork('::80:0:0/89'), + IPNetwork('::100:0:0/88'), + IPNetwork('::200:0:0/87'), + IPNetwork('::400:0:0/86'), + IPNetwork('::800:0:0/85'), + IPNetwork('::1000:0:0/84'), + IPNetwork('::2000:0:0/83'), + IPNetwork('::4000:0:0/82'), + IPNetwork('::8000:0:0/82'), + IPNetwork('::c000:0:0/83'), + IPNetwork('::e000:0:0/84'), + IPNetwork('::f000:0:0/85'), + IPNetwork('::f800:0:0/86'), + IPNetwork('::fc00:0:0/87'), + IPNetwork('::fe00:0:0/88'), + IPNetwork('::ff00:0:0/89'), + IPNetwork('::ff80:0:0/90'), + IPNetwork('::ffc0:0:0/91'), + IPNetwork('::ffe0:0:0/92'), + IPNetwork('::fff0:0:0/93'), + IPNetwork('::fff8:0:0/94'), + IPNetwork('::fffc:0:0/95'), + IPNetwork('::fffe:0:0/96'), + IPNetwork('::ffff:0.0.0.0/97'), + IPNetwork('::ffff:128.0.0.0/98'), + IPNetwork('::ffff:192.0.0.0/99'), + IPNetwork('::ffff:224.0.0.0/100'), + IPNetwork('::ffff:240.0.0.0/101'), + IPNetwork('::ffff:248.0.0.0/102'), + IPNetwork('::ffff:252.0.0.0/103'), + IPNetwork('::ffff:254.0.0.0/104'), + IPNetwork('::ffff:255.0.0.0/105'), + IPNetwork('::ffff:255.128.0.0/106'), + IPNetwork('::ffff:255.192.0.0/107'), + IPNetwork('::ffff:255.224.0.0/108'), + IPNetwork('::ffff:255.240.0.0/109'), + IPNetwork('::ffff:255.248.0.0/110'), + IPNetwork('::ffff:255.252.0.0/111'), + IPNetwork('::ffff:255.254.0.0/112'), + IPNetwork('::ffff:255.255.0.0/113'), + IPNetwork('::ffff:255.255.128.0/114'), + IPNetwork('::ffff:255.255.192.0/115'), + IPNetwork('::ffff:255.255.224.0/116'), + IPNetwork('::ffff:255.255.240.0/117'), + IPNetwork('::ffff:255.255.248.0/118'), + IPNetwork('::ffff:255.255.252.0/119'), + IPNetwork('::ffff:255.255.254.0/120'), + IPNetwork('::ffff:255.255.255.0/121'), + IPNetwork('::ffff:255.255.255.128/122'), + IPNetwork('::ffff:255.255.255.192/123'), + IPNetwork('::ffff:255.255.255.224/124'), + IPNetwork('::ffff:255.255.255.240/125'), + IPNetwork('::ffff:255.255.255.248/126'), + IPNetwork('::ffff:255.255.255.252/127'), + IPNetwork('::ffff:255.255.255.254/128'), + ] + + +def test_rfc_4291(): + assert str(IPNetwork('2001:0DB8:0000:CD30:0000:0000:0000:0000/60')) == '2001:db8:0:cd30::/60' + assert str(IPNetwork('2001:0DB8::CD30:0:0:0:0/60')) == '2001:db8:0:cd30::/60' + assert str(IPNetwork('2001:0DB8:0:CD30::/60')) == '2001:db8:0:cd30::/60' + + +def test_whole_network_cidr_merge_v6(): + assert cidr_merge(['::/0', 'fe80::1']) == [IPNetwork('::/0')] + assert cidr_merge(['::/0', '::']) == [IPNetwork('::/0')] + assert cidr_merge(['::/0', '::192.0.2.0/124', 'ff00::101']) == [IPNetwork('::/0')] + assert cidr_merge(['0.0.0.0/0', '0.0.0.0', '::/0', '::']) == [IPNetwork('0.0.0.0/0'), IPNetwork('::/0')] + + +def test_all_matching_cidrs_v6(): + assert all_matching_cidrs('::ffff:192.0.2.1', ['::ffff:192.0.2.0/96']) == [IPNetwork('::ffff:192.0.2.0/96')] + assert all_matching_cidrs('::192.0.2.1', ['::192.0.2.0/96']) == [IPNetwork('::192.0.2.0/96')] + assert all_matching_cidrs('::192.0.2.1', ['192.0.2.0/23']) == [] + assert all_matching_cidrs('::192.0.2.1', ['192.0.2.0/24', '::192.0.2.0/120']) == [IPNetwork('::192.0.2.0/120')] + assert all_matching_cidrs('::192.0.2.1', [IPNetwork('192.0.2.0/24'), IPNetwork('::192.0.2.0/120')]) == [IPNetwork('::192.0.2.0/120')] diff --git a/netaddr/tests/ip/test_dns.py b/netaddr/tests/ip/test_dns.py new file mode 100644 index 0000000..700fb66 --- /dev/null +++ b/netaddr/tests/ip/test_dns.py @@ -0,0 +1,11 @@ +from netaddr import IPAddress + + +def test_reverse_dns_v4(): + assert IPAddress('172.24.0.13').reverse_dns == '13.0.24.172.in-addr.arpa.' + + +def test_reverse_dns_v6(): + assert IPAddress('fe80::feeb:daed').reverse_dns == ('d.e.a.d.b.e.e.f.0.0.0.0.0.0.0.0.' + '0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.' + 'ip6.arpa.') diff --git a/netaddr/tests/ip/test_ip.py b/netaddr/tests/ip/test_ip.py new file mode 100644 index 0000000..94cfc0c --- /dev/null +++ b/netaddr/tests/ip/test_ip.py @@ -0,0 +1,19 @@ +import weakref + +import pytest + +from netaddr import INET_ATON, INET_PTON, IPAddress, IPNetwork, IPRange, NOHOST + +def test_ip_classes_are_weak_referencable(): + weakref.ref(IPAddress('10.0.0.1')) + weakref.ref(IPNetwork('10.0.0.1/8')) + weakref.ref(IPRange('10.0.0.1', '10.0.0.10')) + +@pytest.mark.parametrize('flags', [NOHOST, INET_ATON | INET_PTON]) +def test_invalid_ipaddress_flags_are_rejected(flags): + with pytest.raises(ValueError): + IPAddress('1.2.3.4', flags=flags) + +def test_invalid_ipnetwork_flags_are_rejected(): + with pytest.raises(ValueError): + IPNetwork('1.2.0.0/16', flags=INET_PTON) diff --git a/netaddr/tests/ip/test_ip_categories.py b/netaddr/tests/ip/test_ip_categories.py new file mode 100644 index 0000000..b5318f3 --- /dev/null +++ b/netaddr/tests/ip/test_ip_categories.py @@ -0,0 +1,112 @@ +import pytest + +from netaddr import IPAddress + +# Excluding is_ipv4_compat as we'll likely be dropping it +unicast = 1 << 0 +multicast = 1 << 1 +loopback = 1 << 2 +private = 1 << 3 +link_local = 1 << 4 +reserved = 1 << 5 +ipv4_mapped = 1 << 6 +hostmask = 1 << 7 +netmask = 1 << 8 +ipv4_private_use = 1 << 9 +global_ = 1 << 10 +ipv6_unique_local = 1 << 11 + +flags = { + 'unicast': unicast, + 'multicast': multicast, + 'loopback': loopback, + 'private': private, + 'link_local': link_local, + 'reserved': reserved, + 'ipv4_mapped': ipv4_mapped, + 'hostmask': hostmask, + 'netmask': netmask, + 'ipv4_private_use': ipv4_private_use, + 'global': global_, + 'ipv6_unique_local': ipv6_unique_local, +} + + +@pytest.mark.parametrize('text_address,categories', [ + # IPv4 + ['0.0.0.0', reserved | hostmask | netmask | unicast], + ['0.0.1.255', hostmask | reserved | unicast | hostmask], + ['0.255.255.255', reserved | hostmask | unicast], + ['10.0.0.1', ipv4_private_use | private | unicast], + ['62.125.24.5', global_ | unicast], + ['100.64.0.0', private | unicast], + ['127.0.0.0', reserved | loopback | unicast | reserved], + ['127.0.0.1', loopback | reserved | unicast], + ['172.24.0.1', ipv4_private_use | private | unicast], + ['127.255.255.255', reserved | hostmask | loopback | unicast], + ['169.254.0.0', link_local | private | unicast], + ['192.0.0.0', netmask | private | unicast], + ['192.0.0.8', private | unicast], + ['192.0.0.9', global_ | private | unicast], + ['192.0.0.10', global_ | private | unicast], + ['192.0.0.11', private | unicast], + ['192.0.0.170', private | unicast], + ['192.0.0.171', private | unicast], + ['192.0.2.0', reserved | unicast], + ['192.0.2.1', reserved | unicast], + ['192.0.2.255', reserved | unicast], + ['192.31.196.0', global_ | unicast], + ['192.52.193.0', global_ | unicast], + ['192.88.99.0', global_ | reserved | unicast], + ['192.88.99.255', global_ | reserved | unicast], + ['192.168.0.1', ipv4_private_use | private | unicast], + ['192.175.48.0', global_ | unicast], + ['198.18.0.0', private | unicast], + ['198.19.255.255', private | unicast], + ['198.51.100.0', reserved | unicast], + ['203.0.113.0', reserved | unicast], + ['233.252.0.0', global_ | reserved | multicast], + ['233.252.0.255', global_ | reserved | multicast], + ['239.192.0.1', global_ | private | multicast], + ['253.0.0.1', reserved | unicast], + ['255.255.254.0', netmask | reserved | unicast], + # IPv6 + ['::', hostmask | netmask | reserved | unicast], + ['::1', loopback | hostmask | reserved | unicast], + ['::ffff:0.0.0.0', ipv4_mapped | reserved | unicast], + ['::ffff:1.1.1.1', ipv4_mapped | reserved | unicast], + ['64:ff9b::', global_ | reserved | unicast], + ['64:ff9b:1::', reserved | unicast], + ['100::', reserved | unicast], + ['2001::', unicast], + ['2001:1::1', global_ | unicast], + ['2001:1::2', global_ | unicast], + ['2001:2::', unicast], + ['2001:3::', global_ | unicast], + ['2001:4:112::', global_ | unicast], + ['2001:10::', unicast], + ['2001:20::', global_ | unicast], + ['2001:30::', global_ | unicast], + ['2001:db8::', unicast], + ['2002::', unicast], + ['2620:4f:8000::', global_ | unicast], + ['fc00::1', ipv6_unique_local | private | unicast], + ['fe80::1', private | unicast | link_local], + ['ff00::1', global_ | reserved | multicast], +]) +def test_ip_categories(text_address, categories): + address = IPAddress(text_address) + methods = [ + getattr(address, name) + for name in dir(address) if name.startswith('is_') and name != 'is_ipv4_compat' + ] + for method in methods: + name = method.__name__.replace('is_', '') + flag = flags[name] + got_value = method() + expected_value = bool(categories & flag) + assert got_value == expected_value, 'Expected is_%s() value to be %s' % (name, expected_value) + categories &= ~flag + + # Just one final check to make sure we haven't missed any flags + assert categories == 0 diff --git a/netaddr/tests/ip/test_ip_comparisons.py b/netaddr/tests/ip/test_ip_comparisons.py new file mode 100644 index 0000000..3dcb036 --- /dev/null +++ b/netaddr/tests/ip/test_ip_comparisons.py @@ -0,0 +1,38 @@ +from netaddr import IPAddress, IPNetwork + + +def test_basic_comparisons(): + assert IPAddress('192.0.2.1') == IPAddress('192.0.2.1') + assert not IPAddress('192.0.2.1') != IPAddress('192.0.2.1') + + assert IPAddress('192.0.2.2') > IPAddress('192.0.2.1') + assert IPAddress('192.0.2.1') >= IPAddress('192.0.2.1') + assert IPAddress('192.0.2.2') >= IPAddress('192.0.2.1') + + assert IPAddress('192.0.2.1') < IPAddress('192.0.2.2') + assert IPAddress('192.0.2.1') <= IPAddress('192.0.2.1') + assert IPAddress('192.0.2.1') <= IPAddress('192.0.2.2') + +def test_advanced_comparisons(): + assert IPNetwork('192.0.2.0/24') == IPNetwork('192.0.2.112/24') + + assert IPNetwork('192.0.2.0/24').ip != IPNetwork('192.0.2.112/24').ip + assert IPNetwork('192.0.2.0/24').ip < IPNetwork('192.0.2.112/24').ip + + assert IPNetwork('192.0.2.0/24').cidr == IPNetwork('192.0.2.112/24').cidr + + assert IPNetwork('192.0.2.0/24') != IPNetwork('192.0.3.0/24') + + assert IPNetwork('192.0.2.0/24') < IPNetwork('192.0.3.0/24') + + assert IPAddress('192.0.2.0') != IPNetwork('192.0.2.0/32') + + assert IPAddress('192.0.2.0') == IPNetwork('192.0.2.0/32')[0] + assert IPAddress('192.0.2.0') == IPNetwork('192.0.2.0/32')[-1] + + assert IPAddress('192.0.2.0') == IPNetwork('192.0.2.0/32')[0] + + assert IPAddress('192.0.2.0') == IPNetwork('192.0.2.0/32').ip + + assert IPAddress('192.0.2.0') == IPNetwork('192.0.2.0/32').ip + diff --git a/netaddr/tests/ip/test_ip_globs.py b/netaddr/tests/ip/test_ip_globs.py new file mode 100644 index 0000000..2f924bb --- /dev/null +++ b/netaddr/tests/ip/test_ip_globs.py @@ -0,0 +1,50 @@ +from netaddr import IPGlob, IPNetwork, cidr_to_glob, glob_to_cidrs, glob_to_iptuple, iprange_to_globs, IPAddress, \ + valid_glob, glob_to_iprange, IPRange + + +def test_ipglob_basic(): + #TODO: do the same testing on IPGlob as IPRange. + assert IPGlob('192.0.2.*') == IPNetwork('192.0.2.0/24') + + +def test_ipglob_boolean_evaluation(): + assert bool(IPGlob('*.*.*.*')) + assert bool(IPGlob('0.0.0.0')) + + +def test_cidr_to_glob(): + assert cidr_to_glob('10.0.0.1/32') == '10.0.0.1' + assert cidr_to_glob('192.0.2.0/24') == '192.0.2.*' + assert cidr_to_glob('172.16.0.0/12') == '172.16-31.*.*' + assert cidr_to_glob('0.0.0.0/0') == '*.*.*.*' + + +def test_glob_to_cidrs(): + assert glob_to_cidrs('10.0.0.1') == [IPNetwork('10.0.0.1/32')] + assert glob_to_cidrs('192.0.2.*') == [IPNetwork('192.0.2.0/24')] + assert glob_to_cidrs('172.16-31.*.*') == [IPNetwork('172.16.0.0/12')] + assert glob_to_cidrs('*.*.*.*') == [IPNetwork('0.0.0.0/0')] + + +def test_glob_to_iptuple(): + assert glob_to_iptuple('*.*.*.*') == (IPAddress('0.0.0.0'), IPAddress('255.255.255.255')) + + +def test_iprange_to_globs(): + assert iprange_to_globs('192.0.2.0', '192.0.2.255') == ['192.0.2.*'] + assert iprange_to_globs('192.0.2.1', '192.0.2.15') == ['192.0.2.1-15'] + assert iprange_to_globs('192.0.2.255', '192.0.4.1') == ['192.0.2.255', '192.0.3.*', '192.0.4.0-1'] + assert iprange_to_globs('10.0.1.255', '10.0.255.255') == ['10.0.1.255', '10.0.2-3.*', '10.0.4-7.*', '10.0.8-15.*', '10.0.16-31.*', '10.0.32-63.*', '10.0.64-127.*', '10.0.128-255.*'] + + +def test_glob_to_iprange(): + assert glob_to_iprange('192.0.2.*') == IPRange('192.0.2.0', '192.0.2.255') + assert glob_to_iprange('192.0.2.1-15') == IPRange('192.0.2.1', '192.0.2.15') + assert glob_to_iprange('192.0.1-3.*') == IPRange('192.0.1.0', '192.0.3.255') + + +def test_invalid_glob(): + assert not valid_glob('1.1.1.a') + assert not valid_glob('1.1.1.1/32') + assert not valid_glob('1.1.1.a-b') + assert not valid_glob('1.1.a-b.*') diff --git a/netaddr/tests/ip/test_ip_network_categories.py b/netaddr/tests/ip/test_ip_network_categories.py new file mode 100644 index 0000000..9f9961e --- /dev/null +++ b/netaddr/tests/ip/test_ip_network_categories.py @@ -0,0 +1,26 @@ +from netaddr import IPNetwork
+
+
+def test_is_unicast():
+ assert IPNetwork('192.0.2.0/24').is_unicast()
+ assert IPNetwork('fe80::1/48').is_unicast()
+
+
+def test_is_multicast():
+ assert IPNetwork('239.192.0.1/24').is_multicast()
+ assert IPNetwork('ff00::/8').is_multicast()
+
+
+def test_is_private():
+ assert IPNetwork('10.0.0.0/24').is_private()
+ assert IPNetwork('fc00::/7').is_private()
+
+
+def test_is_reserved():
+ assert IPNetwork('240.0.0.0/24').is_reserved()
+ assert IPNetwork('0::/48').is_reserved()
+
+
+def test_is_loopback():
+ assert IPNetwork('127.0.0.0/8').is_loopback()
+ assert IPNetwork('::1/128').is_loopback()
diff --git a/netaddr/tests/ip/test_ip_ranges.py b/netaddr/tests/ip/test_ip_ranges.py new file mode 100644 index 0000000..f17f669 --- /dev/null +++ b/netaddr/tests/ip/test_ip_ranges.py @@ -0,0 +1,278 @@ +from ast import literal_eval +import pickle +import pytest + +from netaddr import iter_iprange, IPAddress, cidr_merge, IPNetwork, IPRange, ZEROFILL, AddrFormatError +from netaddr.compat import _sys_maxint + + +def test_ip_range(): + ip_list = list(iter_iprange('192.0.2.1', '192.0.2.14')) + + assert len(ip_list) == 14 + + assert ip_list == [ + IPAddress('192.0.2.1'), + IPAddress('192.0.2.2'), + IPAddress('192.0.2.3'), + IPAddress('192.0.2.4'), + IPAddress('192.0.2.5'), + IPAddress('192.0.2.6'), + IPAddress('192.0.2.7'), + IPAddress('192.0.2.8'), + IPAddress('192.0.2.9'), + IPAddress('192.0.2.10'), + IPAddress('192.0.2.11'), + IPAddress('192.0.2.12'), + IPAddress('192.0.2.13'), + IPAddress('192.0.2.14'), + ] + + assert cidr_merge(ip_list) == [ + IPNetwork('192.0.2.1/32'), + IPNetwork('192.0.2.2/31'), + IPNetwork('192.0.2.4/30'), + IPNetwork('192.0.2.8/30'), + IPNetwork('192.0.2.12/31'), + IPNetwork('192.0.2.14/32'), + ] + +def test_iprange(): + range1 = IPRange('192.0.2.1', '192.0.2.15') + assert range1 == IPRange('192.0.2.1', '192.0.2.15') + + assert range1.cidrs() == [ + IPNetwork('192.0.2.1/32'), + IPNetwork('192.0.2.2/31'), + IPNetwork('192.0.2.4/30'), + IPNetwork('192.0.2.8/29'), + ] + + assert IPRange('192.0.2.0', '192.0.2.255') == IPNetwork('192.0.2.0/24') + + range2 = IPRange('192.0.2.1', '192.0.2.15') + addrs = list(range2) + + assert addrs == [ + IPAddress('192.0.2.1'), + IPAddress('192.0.2.2'), + IPAddress('192.0.2.3'), + IPAddress('192.0.2.4'), + IPAddress('192.0.2.5'), + IPAddress('192.0.2.6'), + IPAddress('192.0.2.7'), + IPAddress('192.0.2.8'), + IPAddress('192.0.2.9'), + IPAddress('192.0.2.10'), + IPAddress('192.0.2.11'), + IPAddress('192.0.2.12'), + IPAddress('192.0.2.13'), + IPAddress('192.0.2.14'), + IPAddress('192.0.2.15'), + ] + assert range2 != addrs + + assert list(range2) == addrs + + subnets = range2.cidrs() + assert subnets == [ + IPNetwork('192.0.2.1/32'), IPNetwork('192.0.2.2/31'), IPNetwork('192.0.2.4/30'), IPNetwork('192.0.2.8/29')] + + assert range2 != subnets + + assert range2.cidrs() == subnets + + +def test_iprange_boundaries(): + assert list(iter_iprange('192.0.2.0', '192.0.2.7')) == [ + IPAddress('192.0.2.0'), + IPAddress('192.0.2.1'), + IPAddress('192.0.2.2'), + IPAddress('192.0.2.3'), + IPAddress('192.0.2.4'), + IPAddress('192.0.2.5'), + IPAddress('192.0.2.6'), + IPAddress('192.0.2.7'), + ] + + assert list(iter_iprange('::ffff:192.0.2.0', '::ffff:192.0.2.7')) == [ + IPAddress('::ffff:192.0.2.0'), + IPAddress('::ffff:192.0.2.1'), + IPAddress('::ffff:192.0.2.2'), + IPAddress('::ffff:192.0.2.3'), + IPAddress('::ffff:192.0.2.4'), + IPAddress('::ffff:192.0.2.5'), + IPAddress('::ffff:192.0.2.6'), + IPAddress('::ffff:192.0.2.7'), + ] + + +def test_iprange_boolean_evaluation(): + assert bool(IPRange('0.0.0.0', '255.255.255.255')) + assert bool(IPRange('0.0.0.0', '0.0.0.0')) + + +def test_iprange_sorting(): + ranges = ( + (IPAddress('::'), IPAddress('::')), + (IPAddress('0.0.0.0'), IPAddress('255.255.255.255')), + (IPAddress('::'), IPAddress('::255.255.255.255')), + (IPAddress('0.0.0.0'), IPAddress('0.0.0.0')), + ) + + assert sorted(ranges) == [ + (IPAddress('0.0.0.0'), IPAddress('0.0.0.0')), + (IPAddress('0.0.0.0'), IPAddress('255.255.255.255')), + (IPAddress('::'), IPAddress('::')), + (IPAddress('::'), IPAddress('::255.255.255.255')), + ] + + +def test_iprange_constructor(): + iprange = IPRange('192.0.2.1', '192.0.2.254') + + assert iprange == IPRange('192.0.2.1', '192.0.2.254') + assert '%s' % iprange == '192.0.2.1-192.0.2.254' + assert IPRange('::ffff:192.0.2.1', '::ffff:192.0.2.254') == IPRange('::ffff:192.0.2.1', '::ffff:192.0.2.254') + assert IPRange('192.0.2.1', '192.0.2.1') == IPRange('192.0.2.1', '192.0.2.1') + assert IPRange('208.049.164.000', '208.050.066.255', flags=ZEROFILL) == IPRange('208.49.164.0', '208.50.66.255') + + with pytest.raises(AddrFormatError): + IPRange('192.0.2.2', '192.0.2.1') + + with pytest.raises(AddrFormatError): + IPRange('::', '0.0.0.1') + + with pytest.raises(AddrFormatError): + IPRange('0.0.0.0', '::1') + + +def test_iprange_indexing(): + iprange = IPRange('192.0.2.1', '192.0.2.254') + + assert len(iprange) == 254 + assert iprange.first == 3221225985 + assert iprange.last == 3221226238 + assert iprange[0] == IPAddress('192.0.2.1') + assert iprange[-1] == IPAddress('192.0.2.254') + + with pytest.raises(IndexError): + iprange[512] + + +def test_iprange_slicing(): + iprange = IPRange('192.0.2.1', '192.0.2.254') + + assert list(iprange[0:3]) == [ + IPAddress('192.0.2.1'), + IPAddress('192.0.2.2'), + IPAddress('192.0.2.3'), + ] + + assert list(iprange[0:10:2]) == [ + IPAddress('192.0.2.1'), + IPAddress('192.0.2.3'), + IPAddress('192.0.2.5'), + IPAddress('192.0.2.7'), + IPAddress('192.0.2.9'), + ] + + assert list(iprange[0:1024:512]) == [IPAddress('192.0.2.1')] + + +def test_iprange_ipv6_unsupported_slicing(): + with pytest.raises(TypeError): + IPRange('::ffff:192.0.2.1', '::ffff:192.0.2.254')[0:10:2] + + +def test_iprange_membership(): + assert IPRange('192.0.2.5', '192.0.2.10') in IPRange('192.0.2.1', '192.0.2.254') + assert IPRange('fe80::1', 'fe80::fffe') in IPRange('fe80::', 'fe80::ffff:ffff:ffff:ffff') + assert IPRange('192.0.2.5', '192.0.2.10') not in IPRange('::', '::255.255.255.255') + + +def test_more_iprange_sorting(): + ipranges = (IPRange('192.0.2.40', '192.0.2.50'), IPRange('192.0.2.20', '192.0.2.30'), IPRange('192.0.2.1', '192.0.2.254'),) + + assert sorted(ipranges) == [IPRange('192.0.2.1', '192.0.2.254'), IPRange('192.0.2.20', '192.0.2.30'), IPRange('192.0.2.40', '192.0.2.50')] + + ipranges = list(ipranges) + ipranges.append(IPRange('192.0.2.45', '192.0.2.49')) + + assert sorted(ipranges) == [ + IPRange('192.0.2.1', '192.0.2.254'), + IPRange('192.0.2.20', '192.0.2.30'), + IPRange('192.0.2.40', '192.0.2.50'), + IPRange('192.0.2.45', '192.0.2.49'), + ] + + +def test_iprange_cidr_interoperability(): + assert IPRange('192.0.2.5', '192.0.2.10').cidrs() == [ + IPNetwork('192.0.2.5/32'), + IPNetwork('192.0.2.6/31'), + IPNetwork('192.0.2.8/31'), + IPNetwork('192.0.2.10/32'), + ] + + assert IPRange('fe80::', 'fe80::ffff:ffff:ffff:ffff').cidrs() == [IPNetwork('fe80::/64')] + + +def test_iprange_info_and_properties(): + iprange = IPRange('192.0.2.1', '192.0.2.254') + + assert literal_eval(str(iprange.info)) == { + 'IPv4': [{ + 'date': '1993-05', + 'designation': 'Administered by ARIN', + 'prefix': '192/8', + 'status': 'Legacy', + 'whois': 'whois.arin.net'}] + } + + assert iprange.is_reserved() + + assert iprange.version == 4 + +def test_iprange_invalid_len_and_alternative(): + range1 = IPRange(IPAddress("::0"), IPAddress(_sys_maxint, 6)) + + with pytest.raises(IndexError): + len(range1) + + range2 = IPRange(IPAddress("::0"), IPAddress(_sys_maxint - 1, 6)) + assert len(range2) == _sys_maxint + + +def test_iprange_pickling_v4(): + iprange = IPRange('192.0.2.1', '192.0.2.254') + assert iprange == IPRange('192.0.2.1', '192.0.2.254') + assert iprange.first == 3221225985 + assert iprange.last == 3221226238 + assert iprange.version == 4 + + buf = pickle.dumps(iprange) + iprange2 = pickle.loads(buf) + assert iprange2 == iprange + assert id(iprange2) != id(iprange) + assert iprange2.first == 3221225985 + assert iprange2.last == 3221226238 + assert iprange2.version == 4 + + +def test_iprange_pickling_v6(): + iprange = IPRange('::ffff:192.0.2.1', '::ffff:192.0.2.254') + + assert iprange == IPRange('::ffff:192.0.2.1', '::ffff:192.0.2.254') + assert iprange.first == 281473902969345 + assert iprange.last == 281473902969598 + assert iprange.version == 6 + + buf = pickle.dumps(iprange) + + iprange2 = pickle.loads(buf) + + assert iprange2 == iprange + assert iprange2.first == 281473902969345 + assert iprange2.last == 281473902969598 + assert iprange2.version == 6 diff --git a/netaddr/tests/ip/test_ip_rfc1924.py b/netaddr/tests/ip/test_ip_rfc1924.py new file mode 100644 index 0000000..8e2761f --- /dev/null +++ b/netaddr/tests/ip/test_ip_rfc1924.py @@ -0,0 +1,17 @@ +import pytest + +from netaddr import AddrFormatError +from netaddr.ip.rfc1924 import ipv6_to_base85, base85_to_ipv6 + + +def test_RFC_1924(): + ip_addr = '1080::8:800:200c:417a' + base85 = ipv6_to_base85(ip_addr) + assert base85 == '4)+k&C#VzJ4br>0wv%Yp' + assert base85_to_ipv6(base85) == '1080::8:800:200c:417a' + + # RFC specifies that "leading zeroes are never omitted" + ipv6_to_base85("::1") == '00000000000000000001' + + with pytest.raises(AddrFormatError): + base85_to_ipv6('not 20 chars') diff --git a/netaddr/tests/ip/test_ip_sets.py b/netaddr/tests/ip/test_ip_sets.py new file mode 100644 index 0000000..d7fdc4f --- /dev/null +++ b/netaddr/tests/ip/test_ip_sets.py @@ -0,0 +1,581 @@ +import pickle +import weakref + +import pytest + +from netaddr import IPAddress, IPNetwork, IPRange, IPSet, cidr_exclude +from netaddr.compat import _sys_maxint + + +def test_ipset_basic_api(): + range1 = IPRange('192.0.2.1', '192.0.2.15') + + ip_list = [ + IPAddress('192.0.2.1'), + '192.0.2.2/31', + IPNetwork('192.0.2.4/31'), + IPAddress('192.0.2.6'), + IPAddress('192.0.2.7'), + '192.0.2.8', + '192.0.2.9', + IPAddress('192.0.2.10'), + IPAddress('192.0.2.11'), + IPNetwork('192.0.2.12/30'), + ] + + set1 = IPSet(range1.cidrs()) + + set2 = IPSet(ip_list) + + assert set2 == IPSet([ + '192.0.2.1/32', + '192.0.2.2/31', + '192.0.2.4/30', + '192.0.2.8/29', + ]) + + assert set1 == set2 + assert set2.pop() in set1 + assert set1 != set2 + + +def test_ipset_empty(): + assert IPSet() == IPSet([]) + empty_set = IPSet([]) + assert IPSet([]) == empty_set + assert len(empty_set) == 0 + + +def test_ipset_constructor(): + assert IPSet(['192.0.2.0']) == IPSet(['192.0.2.0/32']) + assert IPSet([IPAddress('192.0.2.0')]) == IPSet(['192.0.2.0/32']) + assert IPSet([IPNetwork('192.0.2.0')]) == IPSet(['192.0.2.0/32']) + assert IPSet(IPNetwork('1234::/32')) == IPSet(['1234::/32']) + assert IPSet([IPNetwork('192.0.2.0/24')]) == IPSet(['192.0.2.0/24']) + assert IPSet(IPSet(['192.0.2.0/32'])) == IPSet(['192.0.2.0/32']) + assert IPSet(IPRange("10.0.0.0", "10.0.1.31")) == IPSet(['10.0.0.0/24', '10.0.1.0/27']) + assert IPSet(IPRange('0.0.0.0', '255.255.255.255')) == IPSet(['0.0.0.0/0']) + assert IPSet([IPRange("10.0.0.0", "10.0.1.31")]) == IPSet(IPRange("10.0.0.0", "10.0.1.31")) + + +def test_ipset_iteration(): + assert list(IPSet(['192.0.2.0/28', '::192.0.2.0/124'])) == [ + IPAddress('192.0.2.0'), + IPAddress('192.0.2.1'), + IPAddress('192.0.2.2'), + IPAddress('192.0.2.3'), + IPAddress('192.0.2.4'), + IPAddress('192.0.2.5'), + IPAddress('192.0.2.6'), + IPAddress('192.0.2.7'), + IPAddress('192.0.2.8'), + IPAddress('192.0.2.9'), + IPAddress('192.0.2.10'), + IPAddress('192.0.2.11'), + IPAddress('192.0.2.12'), + IPAddress('192.0.2.13'), + IPAddress('192.0.2.14'), + IPAddress('192.0.2.15'), + IPAddress('::192.0.2.0'), + IPAddress('::192.0.2.1'), + IPAddress('::192.0.2.2'), + IPAddress('::192.0.2.3'), + IPAddress('::192.0.2.4'), + IPAddress('::192.0.2.5'), + IPAddress('::192.0.2.6'), + IPAddress('::192.0.2.7'), + IPAddress('::192.0.2.8'), + IPAddress('::192.0.2.9'), + IPAddress('::192.0.2.10'), + IPAddress('::192.0.2.11'), + IPAddress('::192.0.2.12'), + IPAddress('::192.0.2.13'), + IPAddress('::192.0.2.14'), + IPAddress('::192.0.2.15'), + ] + + +def test_ipset_member_insertion_and_deletion(): + s1 = IPSet() + s1.add('192.0.2.0') + assert s1 == IPSet(['192.0.2.0/32']) + + s1.remove('192.0.2.0') + assert s1 == IPSet([]) + + s1.add(IPRange("10.0.0.0", "10.0.0.255")) + assert s1 == IPSet(['10.0.0.0/24']) + + s1.remove(IPRange("10.0.0.128", "10.10.10.10")) + assert s1 == IPSet(['10.0.0.0/25']) + + +def test_ipset_membership(): + iprange = IPRange('192.0.1.255', '192.0.2.16') + + assert iprange.cidrs() == [ + IPNetwork('192.0.1.255/32'), + IPNetwork('192.0.2.0/28'), + IPNetwork('192.0.2.16/32'), + ] + + ipset = IPSet(['192.0.2.0/28']) + + assert [(str(ip), ip in ipset) for ip in iprange] == [ + ('192.0.1.255', False), + ('192.0.2.0', True), + ('192.0.2.1', True), + ('192.0.2.2', True), + ('192.0.2.3', True), + ('192.0.2.4', True), + ('192.0.2.5', True), + ('192.0.2.6', True), + ('192.0.2.7', True), + ('192.0.2.8', True), + ('192.0.2.9', True), + ('192.0.2.10', True), + ('192.0.2.11', True), + ('192.0.2.12', True), + ('192.0.2.13', True), + ('192.0.2.14', True), + ('192.0.2.15', True), + ('192.0.2.16', False), + ] + +def test_ipset_membership_largest(): + ipset = IPSet(['0.0.0.0/0']) + + assert IPAddress("10.0.0.1") in ipset + assert IPAddress("0.0.0.0") in ipset + assert IPAddress("255.255.255") in ipset + assert IPNetwork("10.0.0.0/24") in ipset + assert IPAddress("::1") not in ipset + + +def test_set_membership_smallest(): + ipset = IPSet(["10.0.0.42/32"]) + + assert IPAddress("10.0.0.42") in ipset + assert IPNetwork("10.0.0.42/32") in ipset + + assert IPAddress("10.0.0.41") not in ipset + assert IPAddress("10.0.0.43") not in ipset + assert IPNetwork("10.0.0.42/31") not in ipset + + +def test_ipset_unions(): + assert IPSet(['192.0.2.0']) == IPSet(['192.0.2.0/32']) + assert IPSet(['192.0.2.0']) | IPSet(['192.0.2.1']) == IPSet(['192.0.2.0/31']) + assert IPSet(['192.0.2.0']) | IPSet(['192.0.2.1']) | IPSet(['192.0.2.3']) == IPSet(['192.0.2.0/31', '192.0.2.3/32']) + assert IPSet(['192.0.2.0']) | IPSet(['192.0.2.1']) | IPSet(['192.0.2.3/30']) == IPSet(['192.0.2.0/30']) + assert IPSet(['192.0.2.0']) | IPSet(['192.0.2.1']) | IPSet(['192.0.2.3/31']) == IPSet(['192.0.2.0/30']) + assert IPSet(['192.0.2.0/24']) | IPSet(['192.0.3.0/24']) | IPSet(['192.0.4.0/24']) == IPSet(['192.0.2.0/23', '192.0.4.0/24']) + + +def test_ipset_unions_intersections_differences(): + adj_cidrs = list(IPNetwork('192.0.2.0/24').subnet(28)) + even_cidrs = adj_cidrs[::2] + evens = IPSet(even_cidrs) + + assert evens == IPSet([ + '192.0.2.0/28', '192.0.2.32/28', '192.0.2.64/28', + '192.0.2.96/28', '192.0.2.128/28', '192.0.2.160/28', + '192.0.2.192/28', '192.0.2.224/28', + ]) + + assert IPSet(['192.0.2.0/24']) & evens == IPSet([ + '192.0.2.0/28', '192.0.2.32/28', '192.0.2.64/28', + '192.0.2.96/28', '192.0.2.128/28', '192.0.2.160/28', + '192.0.2.192/28', '192.0.2.224/28']) + + odds = IPSet(['192.0.2.0/24']) ^ evens + assert odds == IPSet([ + '192.0.2.16/28', '192.0.2.48/28', '192.0.2.80/28', + '192.0.2.112/28', '192.0.2.144/28', '192.0.2.176/28', + '192.0.2.208/28', '192.0.2.240/28']) + + assert evens | odds == IPSet(['192.0.2.0/24']) + assert evens & odds == IPSet([]) + assert evens ^ odds == IPSet(['192.0.2.0/24']) + + +def test_ipset_supersets_and_subsets(): + s1 = IPSet(['192.0.2.0/24', '192.0.4.0/24']) + s2 = IPSet(['192.0.2.0', '192.0.4.0']) + + assert s1.issuperset(s2) + assert s2.issubset(s1) + assert not s2.issuperset(s1) + assert not s1.issubset(s2) + + ipv4_addr_space = IPSet(['0.0.0.0/0']) + private = IPSet(['10.0.0.0/8', '172.16.0.0/12', '192.0.2.0/24', + '192.168.0.0/16', '239.192.0.0/14']) + reserved = IPSet(['225.0.0.0/8', '226.0.0.0/7', '228.0.0.0/6', '234.0.0.0/7', + '236.0.0.0/7', '238.0.0.0/8', '240.0.0.0/4']) + unavailable = reserved | private + available = ipv4_addr_space ^ unavailable + + assert [tuple(map(str, (cidr, cidr[0], cidr[-1]))) for cidr in available.iter_cidrs()] == [ + ('0.0.0.0/5', '0.0.0.0', '7.255.255.255'), + ('8.0.0.0/7', '8.0.0.0', '9.255.255.255'), + ('11.0.0.0/8', '11.0.0.0', '11.255.255.255'), + ('12.0.0.0/6', '12.0.0.0', '15.255.255.255'), + ('16.0.0.0/4', '16.0.0.0', '31.255.255.255'), + ('32.0.0.0/3', '32.0.0.0', '63.255.255.255'), + ('64.0.0.0/2', '64.0.0.0', '127.255.255.255'), + ('128.0.0.0/3', '128.0.0.0', '159.255.255.255'), + ('160.0.0.0/5', '160.0.0.0', '167.255.255.255'), + ('168.0.0.0/6', '168.0.0.0', '171.255.255.255'), + ('172.0.0.0/12', '172.0.0.0', '172.15.255.255'), + ('172.32.0.0/11', '172.32.0.0', '172.63.255.255'), + ('172.64.0.0/10', '172.64.0.0', '172.127.255.255'), + ('172.128.0.0/9', '172.128.0.0', '172.255.255.255'), + ('173.0.0.0/8', '173.0.0.0', '173.255.255.255'), + ('174.0.0.0/7', '174.0.0.0', '175.255.255.255'), + ('176.0.0.0/4', '176.0.0.0', '191.255.255.255'), + ('192.0.0.0/23', '192.0.0.0', '192.0.1.255'), + ('192.0.3.0/24', '192.0.3.0', '192.0.3.255'), + ('192.0.4.0/22', '192.0.4.0', '192.0.7.255'), + ('192.0.8.0/21', '192.0.8.0', '192.0.15.255'), + ('192.0.16.0/20', '192.0.16.0', '192.0.31.255'), + ('192.0.32.0/19', '192.0.32.0', '192.0.63.255'), + ('192.0.64.0/18', '192.0.64.0', '192.0.127.255'), + ('192.0.128.0/17', '192.0.128.0', '192.0.255.255'), + ('192.1.0.0/16', '192.1.0.0', '192.1.255.255'), + ('192.2.0.0/15', '192.2.0.0', '192.3.255.255'), + ('192.4.0.0/14', '192.4.0.0', '192.7.255.255'), + ('192.8.0.0/13', '192.8.0.0', '192.15.255.255'), + ('192.16.0.0/12', '192.16.0.0', '192.31.255.255'), + ('192.32.0.0/11', '192.32.0.0', '192.63.255.255'), + ('192.64.0.0/10', '192.64.0.0', '192.127.255.255'), + ('192.128.0.0/11', '192.128.0.0', '192.159.255.255'), + ('192.160.0.0/13', '192.160.0.0', '192.167.255.255'), + ('192.169.0.0/16', '192.169.0.0', '192.169.255.255'), + ('192.170.0.0/15', '192.170.0.0', '192.171.255.255'), + ('192.172.0.0/14', '192.172.0.0', '192.175.255.255'), + ('192.176.0.0/12', '192.176.0.0', '192.191.255.255'), + ('192.192.0.0/10', '192.192.0.0', '192.255.255.255'), + ('193.0.0.0/8', '193.0.0.0', '193.255.255.255'), + ('194.0.0.0/7', '194.0.0.0', '195.255.255.255'), + ('196.0.0.0/6', '196.0.0.0', '199.255.255.255'), + ('200.0.0.0/5', '200.0.0.0', '207.255.255.255'), + ('208.0.0.0/4', '208.0.0.0', '223.255.255.255'), + ('224.0.0.0/8', '224.0.0.0', '224.255.255.255'), + ('232.0.0.0/7', '232.0.0.0', '233.255.255.255'), + ('239.0.0.0/9', '239.0.0.0', '239.127.255.255'), + ('239.128.0.0/10', '239.128.0.0', '239.191.255.255'), + ('239.196.0.0/14', '239.196.0.0', '239.199.255.255'), + ('239.200.0.0/13', '239.200.0.0', '239.207.255.255'), + ('239.208.0.0/12', '239.208.0.0', '239.223.255.255'), + ('239.224.0.0/11', '239.224.0.0', '239.255.255.255'), + ] + + assert ipv4_addr_space ^ available == IPSet([ + '10.0.0.0/8', '172.16.0.0/12', '192.0.2.0/24', '192.168.0.0/16', + '225.0.0.0/8', '226.0.0.0/7', '228.0.0.0/6', '234.0.0.0/7', + '236.0.0.0/7', '238.0.0.0/8', '239.192.0.0/14', '240.0.0.0/4', + ]) + +def test_combined_ipv4_and_ipv6_ipsets(): + s1 = IPSet(['192.0.2.0', '::192.0.2.0', '192.0.2.2', '::192.0.2.2']) + s2 = IPSet(['192.0.2.2', '::192.0.2.2', '192.0.2.4', '::192.0.2.4']) + + assert s1 | s2 == IPSet([ + '192.0.2.0/32', '192.0.2.2/32', '192.0.2.4/32', + '::192.0.2.0/128', '::192.0.2.2/128', '::192.0.2.4/128', + ]) + + assert s2 | s1 == IPSet([ + '192.0.2.0/32', '192.0.2.2/32', '192.0.2.4/32', + '::192.0.2.0/128', '::192.0.2.2/128', '::192.0.2.4/128', + ]) + + assert s1 & s2 == IPSet(['192.0.2.2/32', '::192.0.2.2/128']) + assert s1 - s2 == IPSet(['192.0.2.0/32', '::192.0.2.0/128']) + assert s2 - s1 == IPSet(['192.0.2.4/32', '::192.0.2.4/128']) + assert s1 ^ s2 == IPSet(['192.0.2.0/32', '192.0.2.4/32', '::192.0.2.0/128', '::192.0.2.4/128']) + + +def test_disjointed_ipsets(): + s1 = IPSet(['192.0.2.0', '192.0.2.1', '192.0.2.2']) + s2 = IPSet(['192.0.2.2', '192.0.2.3', '192.0.2.4']) + + assert s1 & s2 == IPSet(['192.0.2.2/32']) + assert not s1.isdisjoint(s2) + + s3 = IPSet(['192.0.2.0', '192.0.2.1']) + s4 = IPSet(['192.0.2.3', '192.0.2.4']) + + assert s3 & s4 == IPSet([]) + assert s3.isdisjoint(s4) + + +def test_ipset_updates(): + s1 = IPSet(['192.0.2.0/25']) + s2 = IPSet(['192.0.2.128/25']) + + s1.update(s2) + assert s1 == IPSet(['192.0.2.0/24']) + + s1.update(['192.0.0.0/24', '192.0.1.0/24', '192.0.3.0/24']) + assert s1 == IPSet(['192.0.0.0/22']) + + expected = IPSet(['192.0.1.0/24', '192.0.2.0/24']) + + s3 = IPSet(['192.0.1.0/24']) + s3.update(IPRange('192.0.2.0', '192.0.2.255')) + assert s3 == expected + + s4 = IPSet(['192.0.1.0/24']) + s4.update([IPRange('192.0.2.0', '192.0.2.100'), IPRange('192.0.2.50', '192.0.2.255')]) + assert s4 == expected + + +def test_ipset_clear(): + ipset = IPSet(['10.0.0.0/16']) + ipset.update(IPRange('10.1.0.0', '10.1.255.255')) + assert ipset == IPSet(['10.0.0.0/15']) + + ipset.clear() + assert ipset == IPSet([]) + + +def test_ipset_cidr_fracturing(): + s1 = IPSet(['0.0.0.0/0']) + s1.remove('255.255.255.255') + assert s1 == IPSet([ + '0.0.0.0/1', '128.0.0.0/2', '192.0.0.0/3', + '224.0.0.0/4', '240.0.0.0/5', '248.0.0.0/6', + '252.0.0.0/7', '254.0.0.0/8', '255.0.0.0/9', + '255.128.0.0/10', '255.192.0.0/11', '255.224.0.0/12', + '255.240.0.0/13', '255.248.0.0/14', '255.252.0.0/15', + '255.254.0.0/16', '255.255.0.0/17', '255.255.128.0/18', + '255.255.192.0/19', '255.255.224.0/20', '255.255.240.0/21', + '255.255.248.0/22', '255.255.252.0/23', '255.255.254.0/24', + '255.255.255.0/25', '255.255.255.128/26', '255.255.255.192/27', + '255.255.255.224/28', '255.255.255.240/29', '255.255.255.248/30', + '255.255.255.252/31', '255.255.255.254/32']) + + cidrs = s1.iter_cidrs() + assert len(cidrs) == 32 + assert list(cidrs) == [ + IPNetwork('0.0.0.0/1'), IPNetwork('128.0.0.0/2'), IPNetwork('192.0.0.0/3'), + IPNetwork('224.0.0.0/4'), IPNetwork('240.0.0.0/5'), IPNetwork('248.0.0.0/6'), + IPNetwork('252.0.0.0/7'), IPNetwork('254.0.0.0/8'), IPNetwork('255.0.0.0/9'), + IPNetwork('255.128.0.0/10'), IPNetwork('255.192.0.0/11'), IPNetwork('255.224.0.0/12'), + IPNetwork('255.240.0.0/13'), IPNetwork('255.248.0.0/14'), IPNetwork('255.252.0.0/15'), + IPNetwork('255.254.0.0/16'), IPNetwork('255.255.0.0/17'), IPNetwork('255.255.128.0/18'), + IPNetwork('255.255.192.0/19'), IPNetwork('255.255.224.0/20'), IPNetwork('255.255.240.0/21'), + IPNetwork('255.255.248.0/22'), IPNetwork('255.255.252.0/23'), IPNetwork('255.255.254.0/24'), + IPNetwork('255.255.255.0/25'), IPNetwork('255.255.255.128/26'), IPNetwork('255.255.255.192/27'), + IPNetwork('255.255.255.224/28'), IPNetwork('255.255.255.240/29'), IPNetwork('255.255.255.248/30'), + IPNetwork('255.255.255.252/31'), IPNetwork('255.255.255.254/32') + ] + + + assert cidrs == cidr_exclude('0.0.0.0/0', '255.255.255.255') + + s1.remove('0.0.0.0') + + assert s1 == IPSet([ + '0.0.0.1/32', '0.0.0.2/31', '0.0.0.4/30', + '0.0.0.8/29', '0.0.0.16/28', '0.0.0.32/27', + '0.0.0.64/26', '0.0.0.128/25', '0.0.1.0/24', + '0.0.2.0/23', '0.0.4.0/22', '0.0.8.0/21', + '0.0.16.0/20', '0.0.32.0/19', '0.0.64.0/18', + '0.0.128.0/17', '0.1.0.0/16', '0.2.0.0/15', + '0.4.0.0/14', '0.8.0.0/13', '0.16.0.0/12', + '0.32.0.0/11', '0.64.0.0/10', '0.128.0.0/9', + '1.0.0.0/8', '2.0.0.0/7', '4.0.0.0/6', + '8.0.0.0/5', '16.0.0.0/4', '32.0.0.0/3', + '64.0.0.0/2', '128.0.0.0/2', '192.0.0.0/3', + '224.0.0.0/4', '240.0.0.0/5', '248.0.0.0/6', + '252.0.0.0/7', '254.0.0.0/8', '255.0.0.0/9', + '255.128.0.0/10', '255.192.0.0/11', '255.224.0.0/12', + '255.240.0.0/13', '255.248.0.0/14', '255.252.0.0/15', + '255.254.0.0/16', '255.255.0.0/17', '255.255.128.0/18', + '255.255.192.0/19', '255.255.224.0/20', '255.255.240.0/21', + '255.255.248.0/22', '255.255.252.0/23', '255.255.254.0/24', + '255.255.255.0/25', '255.255.255.128/26', '255.255.255.192/27', + '255.255.255.224/28', '255.255.255.240/29', '255.255.255.248/30', + '255.255.255.252/31', '255.255.255.254/32', + ]) + + assert len(list(s1.iter_cidrs())) == 62 + + s1.add('255.255.255.255') + s1.add('0.0.0.0') + + assert s1 == IPSet(['0.0.0.0/0']) + + +def test_ipset_with_iprange(): + s1 = IPSet(['10.0.0.0/25', '10.0.0.128/25']) + assert s1.iprange() == IPRange('10.0.0.0', '10.0.0.255') + + assert s1.iscontiguous() + + s1.remove('10.0.0.16') + assert s1 == IPSet([ + '10.0.0.0/28', '10.0.0.17/32', '10.0.0.18/31', + '10.0.0.20/30', '10.0.0.24/29', '10.0.0.32/27', + '10.0.0.64/26', '10.0.0.128/25', + ]) + + assert not s1.iscontiguous() + + with pytest.raises(ValueError): + s1.iprange() + + assert list(s1.iter_ipranges()) == [ + IPRange('10.0.0.0', '10.0.0.15'), + IPRange('10.0.0.17', '10.0.0.255'), + ] + + s2 = IPSet(['0.0.0.0/0']) + assert s2.iscontiguous() + assert s2.iprange() == IPRange('0.0.0.0', '255.255.255.255') +# + s3 = IPSet() + assert s3.iscontiguous() + assert s3.iprange() is None + + s4 = IPSet(IPRange('10.0.0.0', '10.0.0.8')) + assert s4.iscontiguous() + + +def test_ipset_pickling(): + ip_data = IPSet(['10.0.0.0/16', 'fe80::/64']) + buf = pickle.dumps(ip_data) + ip_data_unpickled = pickle.loads(buf) + assert ip_data == ip_data_unpickled + + +def test_ipset_comparison(): + s1 = IPSet(['fc00::/2']) + s2 = IPSet(['fc00::/3']) + + assert s1 > s2 + assert not s1 < s2 + assert s1 != s2 + + +def test_ipset_adding_and_removing_members_ip_addresses_as_ints(): + s1 = IPSet(['10.0.0.0/25']) + + s1.add('10.0.0.0/24') + assert s1 == IPSet(['10.0.0.0/24']) + + integer1 = int(IPAddress('10.0.0.1')) + integer2 = int(IPAddress('fe80::')) + integer3 = int(IPAddress('10.0.0.2')) + + s2 = IPSet([integer1, integer2]) + assert s2 == IPSet(['10.0.0.1/32', 'fe80::/128']) + + s2.add(integer3) + assert s2 == IPSet(['10.0.0.1/32', '10.0.0.2/32', 'fe80::/128']) + + s2.remove(integer2) + assert s2 == IPSet(['10.0.0.1/32', '10.0.0.2/32']) + + s2.update([integer2]) + assert s2 == IPSet(['10.0.0.1/32', '10.0.0.2/32', 'fe80::/128']) + + +def test_ipset_operations_with_combined_ipv4_and_ipv6(): + s1 = IPSet(['192.0.2.0', '::192.0.2.0', '192.0.2.2', '::192.0.2.2']) + s2 = IPSet(['192.0.2.2', '::192.0.2.2', '192.0.2.4', '::192.0.2.4']) + s3 = IPSet(['0.0.0.1', '10.0.0.64/30', '255.255.255.1']) + s4 = IPSet(['10.0.0.64', '10.0.0.66']) + s4b = IPSet(['10.0.0.64', '10.0.0.66', '111.111.111.111']) + s5 = IPSet(['10.0.0.65', '10.0.0.67']) + s6 = IPSet(['2405:8100::/32']) + + assert bool(s6) + assert not bool(IPSet()) + + # set intersection + assert s2 & s1 == IPSet(['192.0.2.2/32', '::192.0.2.2/128']) + assert s3 & s4 == IPSet(['10.0.0.64/32', '10.0.0.66/32']) + assert s4 & s3 == IPSet(['10.0.0.64/32', '10.0.0.66/32']) + assert s3 & s5 == IPSet(['10.0.0.65/32', '10.0.0.67/32']) + assert s5 & s3 == IPSet(['10.0.0.65/32', '10.0.0.67/32']) + + # set difference + assert s3 - s4 == IPSet(['0.0.0.1/32', '10.0.0.65/32', '10.0.0.67/32', '255.255.255.1/32']) + assert s4 - s3 == IPSet([]) + assert s3 - s4b == IPSet(['0.0.0.1/32', '10.0.0.65/32', '10.0.0.67/32', '255.255.255.1/32']) + assert s3 - s5 == IPSet(['0.0.0.1/32', '10.0.0.64/32', '10.0.0.66/32', '255.255.255.1/32']) + assert s5 - s3 == IPSet([]) + + # set symmetric difference + assert s2 ^ s1 == IPSet(['192.0.2.0/32', '192.0.2.4/32', '::192.0.2.0/128', '::192.0.2.4/128']) + assert IPSet([]) ^ IPSet([]) == IPSet([]) + assert IPSet(['0.0.0.1/32']) ^ IPSet([]) == IPSet(['0.0.0.1/32']) + assert IPSet(['0.0.0.1/32']) ^ IPSet(['0.0.0.1/32']) == IPSet([]) + assert s3 ^ s4 == IPSet(['0.0.0.1/32', '10.0.0.65/32', '10.0.0.67/32', '255.255.255.1/32']) + assert s4 ^ s3 == IPSet(['0.0.0.1/32', '10.0.0.65/32', '10.0.0.67/32', '255.255.255.1/32']) + assert s3 ^ s4b == IPSet(['0.0.0.1/32', '10.0.0.65/32', '10.0.0.67/32', '111.111.111.111/32', '255.255.255.1/32']) + assert s3 ^ s5 == IPSet(['0.0.0.1/32', '10.0.0.64/32', '10.0.0.66/32', '255.255.255.1/32']) + assert s5 ^ s3 == IPSet(['0.0.0.1/32', '10.0.0.64/32', '10.0.0.66/32', '255.255.255.1/32']) + + +def test_converting_ipsets_to_ipranges(): + assert list(IPSet().iter_ipranges()) == [] + assert list(IPSet([IPAddress('10.0.0.1')]).iter_ipranges()) == [IPRange('10.0.0.1', '10.0.0.1')] + assert list(IPSet([IPAddress('10.0.0.1'), IPAddress('10.0.0.2')]).iter_ipranges()) == [IPRange('10.0.0.1', '10.0.0.2')] + + +def test_len_on_ipset_failure_with_large_ipv6_addresses(): + s1 = IPSet(IPRange(IPAddress("::0"), IPAddress(_sys_maxint, 6))) + with pytest.raises(IndexError): + len(s1) + + s2 = IPSet(IPRange(IPAddress("::0"), IPAddress(_sys_maxint - 1, 6))) + assert len(s2) == _sys_maxint + + +def test_ipset_ipv4_and_ipv4_separation(): + assert list(IPSet([IPAddress(1, 4), IPAddress(1, 6)]).iter_ipranges()) == [IPRange('0.0.0.1', '0.0.0.1'), IPRange('::1', '::1')] + + +def test_ipset_exceptions(): + s1 = IPSet(['10.0.0.1']) + + # IPSet objects are not hashable. + with pytest.raises(TypeError): + hash(s1) + + # Bad update argument type. + with pytest.raises(TypeError): + s1.update(42) + + +def test_ipset_comparison_with_int_is_invalid(): + s1 = IPSet(['10.0.0.1']) + assert not s1 == 42 + s1 != 42 + + +def test_ipset_converts_to_cidr_networks_v4(): + s1 = IPSet(IPNetwork('10.1.2.3/8')) + s1.add(IPNetwork('192.168.1.2/16')) + assert list(s1.iter_cidrs()) == [ + IPNetwork('10.0.0.0/8'), + IPNetwork('192.168.0.0/16'), + ] + + +def test_ipset_converts_to_cidr_networks_v6(): + s1 = IPSet(IPNetwork('fe80::4242/64')) + s1.add(IPNetwork('fe90::4343/64')) + assert list(s1.iter_cidrs()) == [ + IPNetwork('fe80::/64'), + IPNetwork('fe90::/64'), + ] + + +def test_ipset_is_weak_referencable(): + weakref.ref(IPSet()) diff --git a/netaddr/tests/ip/test_ip_splitter.py b/netaddr/tests/ip/test_ip_splitter.py new file mode 100644 index 0000000..0db4bf7 --- /dev/null +++ b/netaddr/tests/ip/test_ip_splitter.py @@ -0,0 +1,74 @@ +import pytest + +from netaddr.ip import IPNetwork +from netaddr.contrib.subnet_splitter import SubnetSplitter + + +def test_ip_splitter(): + splitter = SubnetSplitter('172.24.0.0/16') + assert splitter.available_subnets() == [IPNetwork('172.24.0.0/16')] + + assert splitter.extract_subnet(23, count=4) == [ + IPNetwork('172.24.0.0/23'), + IPNetwork('172.24.2.0/23'), + IPNetwork('172.24.4.0/23'), + IPNetwork('172.24.6.0/23'), + ] + + assert splitter.available_subnets() == [ + IPNetwork('172.24.8.0/21'), + IPNetwork('172.24.16.0/20'), + IPNetwork('172.24.32.0/19'), + IPNetwork('172.24.64.0/18'), + IPNetwork('172.24.128.0/17'), + ] + + assert splitter.extract_subnet(28, count=10) == [ + IPNetwork('172.24.8.0/28'), + IPNetwork('172.24.8.16/28'), + IPNetwork('172.24.8.32/28'), + IPNetwork('172.24.8.48/28'), + IPNetwork('172.24.8.64/28'), + IPNetwork('172.24.8.80/28'), + IPNetwork('172.24.8.96/28'), + IPNetwork('172.24.8.112/28'), + IPNetwork('172.24.8.128/28'), + IPNetwork('172.24.8.144/28'), + ] + + splitter.available_subnets() == [ + IPNetwork('172.24.8.128/25'), + IPNetwork('172.24.9.0/24'), + IPNetwork('172.24.10.0/23'), + IPNetwork('172.24.12.0/22'), + IPNetwork('172.24.16.0/20'), + IPNetwork('172.24.32.0/19'), + IPNetwork('172.24.64.0/18'), + IPNetwork('172.24.128.0/17'), + ] + + +def test_ip_splitter_remove_same_input_range(): + s = SubnetSplitter('172.24.0.0/16') + assert s.available_subnets() == [IPNetwork('172.24.0.0/16')] + + assert s.extract_subnet(16, count=1) == [ + IPNetwork('172.24.0.0/16'), + ] + + assert s.available_subnets() == [] + + +def test_ip_splitter_remove_more_than_input_range(): + s = SubnetSplitter('172.24.0.0/16') + assert s.available_subnets() == [IPNetwork('172.24.0.0/16')] + + with pytest.raises(ValueError): + s.extract_subnet(16, count=2) + + +def test_ip_splitter_remove_prefix_larger_than_input_range(): + s = SubnetSplitter('172.24.0.0/16') + assert s.available_subnets() == [IPNetwork('172.24.0.0/16')] + assert s.extract_subnet(15, count=1) == [] + assert s.available_subnets() == [IPNetwork('172.24.0.0/16')] diff --git a/netaddr/tests/ip/test_ip_v4.py b/netaddr/tests/ip/test_ip_v4.py new file mode 100644 index 0000000..ea6cc31 --- /dev/null +++ b/netaddr/tests/ip/test_ip_v4.py @@ -0,0 +1,547 @@ +import pickle +import types +import random +import sys + +import pytest + +from netaddr import IPAddress, IPNetwork, INET_ATON, INET_PTON, spanning_cidr, AddrFormatError, ZEROFILL, Z, P, NOHOST + + +def test_ipaddress_v4(): + ip = IPAddress('192.0.2.1') + assert ip.version == 4 + assert repr(ip) == "IPAddress('192.0.2.1')" + assert str(ip) == '192.0.2.1' + assert ip.format() == '192.0.2.1' + assert int(ip) == 3221225985 + assert hex(ip) == '0xc0000201' + if sys.version_info[0] > 2: + assert bytes(ip) == b'\xc0\x00\x02\x01' + assert ip.bin == '0b11000000000000000000001000000001' + assert ip.bits() == '11000000.00000000.00000010.00000001' + assert ip.words == (192, 0, 2, 1) + + +@pytest.mark.parametrize( + ('value', 'ipaddr', 'network', 'cidr', 'broadcast', 'netmask', 'hostmask', 'size'), [ + ( + '192.0.2.1', + IPAddress('192.0.2.1'), + IPAddress('192.0.2.1'), + IPNetwork('192.0.2.1/32'), + None, + IPAddress('255.255.255.255'), + IPAddress('0.0.0.0'), + 1, + ), + ( + '192.0.2.0/24', + IPAddress('192.0.2.0'), + IPAddress('192.0.2.0'), + IPNetwork('192.0.2.0/24'), + IPAddress('192.0.2.255'), + IPAddress('255.255.255.0'), + IPAddress('0.0.0.255'), + 256 + ), + ( + '192.0.3.112/22', + IPAddress('192.0.3.112'), + IPAddress('192.0.0.0'), + IPNetwork('192.0.0.0/22'), + IPAddress('192.0.3.255'), + IPAddress('255.255.252.0'), + IPAddress('0.0.3.255'), + 1024 + ), + ]) +def test_ipnetwork_v4(value, ipaddr, network, cidr, broadcast, netmask, hostmask, size): + net = IPNetwork(value) + assert net.ip == ipaddr + assert net.network == network + assert net.cidr == cidr + assert net.broadcast == broadcast + assert net.netmask == netmask + assert net.hostmask == hostmask + assert net.size == size + + +def test_ipnetwork_list_operations_v4(): + ip = IPNetwork('192.0.2.16/29') + assert len(ip) == 8 + + ip_list = list(ip) + assert len(ip_list) == 8 + + assert ip_list == [ + IPAddress('192.0.2.16'), + IPAddress('192.0.2.17'), + IPAddress('192.0.2.18'), + IPAddress('192.0.2.19'), + IPAddress('192.0.2.20'), + IPAddress('192.0.2.21'), + IPAddress('192.0.2.22'), + IPAddress('192.0.2.23'), + ] + + +def test_ipnetwork_index_operations_v4(): + ip = IPNetwork('192.0.2.16/29') + assert ip[0] == IPAddress('192.0.2.16') + assert ip[1] == IPAddress('192.0.2.17') + assert ip[-1] == IPAddress('192.0.2.23') + + +def test_ipnetwork_slice_operations_v4(): + ip = IPNetwork('192.0.2.16/29') + + assert isinstance(ip[0:4], types.GeneratorType) + + assert list(ip[0:4]) == [ + IPAddress('192.0.2.16'), + IPAddress('192.0.2.17'), + IPAddress('192.0.2.18'), + IPAddress('192.0.2.19'), + ] + + assert list(ip[0::2]) == [ + IPAddress('192.0.2.16'), + IPAddress('192.0.2.18'), + IPAddress('192.0.2.20'), + IPAddress('192.0.2.22'), + ] + + assert list(ip[-1::-1]) == [ + IPAddress('192.0.2.23'), + IPAddress('192.0.2.22'), + IPAddress('192.0.2.21'), + IPAddress('192.0.2.20'), + IPAddress('192.0.2.19'), + IPAddress('192.0.2.18'), + IPAddress('192.0.2.17'), + IPAddress('192.0.2.16'), +] + + +def test_ipnetwork_sort_order(): + ip_list = list(IPNetwork('192.0.2.128/28')) + random.shuffle(ip_list) + assert sorted(ip_list) == [ + IPAddress('192.0.2.128'), + IPAddress('192.0.2.129'), + IPAddress('192.0.2.130'), + IPAddress('192.0.2.131'), + IPAddress('192.0.2.132'), + IPAddress('192.0.2.133'), + IPAddress('192.0.2.134'), + IPAddress('192.0.2.135'), + IPAddress('192.0.2.136'), + IPAddress('192.0.2.137'), + IPAddress('192.0.2.138'), + IPAddress('192.0.2.139'), + IPAddress('192.0.2.140'), + IPAddress('192.0.2.141'), + IPAddress('192.0.2.142'), + IPAddress('192.0.2.143'), + ] + +def test_ipaddress_and_ipnetwork_canonical_sort_order_by_version(): + ip_list = [ + IPAddress('192.0.2.130'), + IPNetwork('192.0.2.128/28'), + IPAddress('::'), + IPNetwork('192.0.3.0/24'), + IPNetwork('192.0.2.0/24'), + IPNetwork('fe80::/64'), + IPNetwork('172.24/12'), + IPAddress('10.0.0.1'), + ] + + random.shuffle(ip_list) + ip_list.sort() + + assert ip_list == [ + IPAddress('10.0.0.1'), + IPNetwork('172.24.0.0/12'), + IPNetwork('192.0.2.0/24'), + IPNetwork('192.0.2.128/28'), + IPAddress('192.0.2.130'), + IPNetwork('192.0.3.0/24'), + IPAddress('::'), + IPNetwork('fe80::/64'), + ] + + +def test_ipnetwork_v4_constructor(): + assert IPNetwork('192.168/16') == IPNetwork('192.168.0.0/16') + assert IPNetwork('192.168.0.15') == IPNetwork('192.168.0.15/32') + assert IPNetwork('192.168') == IPNetwork('192.168.0.0/32') + assert IPNetwork('192.168', implicit_prefix=True) == IPNetwork('192.168.0.0/24') + assert IPNetwork('192.168', True) == IPNetwork('192.168.0.0/24') + assert IPNetwork('10.0.0.1', True) == IPNetwork('10.0.0.1/8') + + +def test_ipaddress_integer_operations_v4(): + assert IPAddress('192.0.2.0') + 1 == IPAddress('192.0.2.1') + assert 1 + IPAddress('192.0.2.0') == IPAddress('192.0.2.1') + assert IPAddress('192.0.2.1') - 1 == IPAddress('192.0.2.0') + assert IPAddress('192.0.0.0') + IPAddress('0.0.0.42') == IPAddress('192.0.0.42') + assert IPAddress('192.0.0.42') - IPAddress('0.0.0.42') == IPAddress('192.0.0.0') + + with pytest.raises(IndexError): + 1 - IPAddress('192.0.2.1') + + ip = IPAddress('10.0.0.1') + ip += 1 + assert ip == IPAddress('10.0.0.2') + + ip -= 1 + assert ip == IPAddress('10.0.0.1') + + ip += IPAddress('0.0.0.42') + assert ip == IPAddress('10.0.0.43') + + ip -= IPAddress('0.0.0.43') + assert ip == IPAddress('10.0.0.0') + + # Negative increments around address range boundaries. + ip = IPAddress('0.0.0.0') + with pytest.raises(IndexError): + ip += -1 + + ip = IPAddress('255.255.255.255') + with pytest.raises(IndexError): + ip -= -1 + + +def test_ipaddress_binary_operations_v4(): + assert IPAddress('192.0.2.15') & IPAddress('255.255.255.0') == IPAddress('192.0.2.0') + assert IPAddress('255.255.0.0') | IPAddress('0.0.255.255') == IPAddress('255.255.255.255') + assert IPAddress('255.255.0.0') ^ IPAddress('255.0.0.0') == IPAddress('0.255.0.0') + assert IPAddress('1.2.3.4').packed == '\x01\x02\x03\x04'.encode('ascii') + + +def test_ipnetwork_slices_v4(): + assert list(IPNetwork('192.0.2.0/29')[0:-1]) == [ + IPAddress('192.0.2.0'), + IPAddress('192.0.2.1'), + IPAddress('192.0.2.2'), + IPAddress('192.0.2.3'), + IPAddress('192.0.2.4'), + IPAddress('192.0.2.5'), + IPAddress('192.0.2.6'), + ] + + assert list(IPNetwork('192.0.2.0/29')[::-1]) == [ + IPAddress('192.0.2.7'), + IPAddress('192.0.2.6'), + IPAddress('192.0.2.5'), + IPAddress('192.0.2.4'), + IPAddress('192.0.2.3'), + IPAddress('192.0.2.2'), + IPAddress('192.0.2.1'), + IPAddress('192.0.2.0'), + ] + +def test_iterhosts_v4(): + assert list(IPNetwork('192.0.2.0/29').iter_hosts()) == [ + IPAddress('192.0.2.1'), + IPAddress('192.0.2.2'), + IPAddress('192.0.2.3'), + IPAddress('192.0.2.4'), + IPAddress('192.0.2.5'), + IPAddress('192.0.2.6'), + ] + + + assert list(IPNetwork("192.168.0.0/31")) == [ + IPAddress('192.168.0.0'), + IPAddress('192.168.0.1'), + ] + + assert list(IPNetwork("192.168.0.0/31").iter_hosts()) == [IPAddress('192.168.0.0'),IPAddress('192.168.0.1')] + assert list(IPNetwork("192.168.0.0/32").iter_hosts()) == [IPAddress('192.168.0.0')] + + +def test_ipaddress_boolean_evaluation_v4(): + assert not bool(IPAddress('0.0.0.0')) + assert bool(IPAddress('0.0.0.1')) + assert bool(IPAddress('255.255.255.255')) + + +def test_ipnetwork_boolean_evaluation_v4(): + assert bool(IPNetwork('0.0.0.0/0')) + + +def test_ipnetwork_equality_v4(): + assert IPNetwork('192.0.2.0/255.255.254.0') == IPNetwork('192.0.2.0/23') + assert IPNetwork('192.0.2.65/255.255.254.0') == IPNetwork('192.0.2.0/23') + assert IPNetwork('192.0.2.65/255.255.254.0') == IPNetwork('192.0.2.65/23') + assert IPNetwork('192.0.2.65/255.255.255.0') != IPNetwork('192.0.2.0/23') + assert IPNetwork('192.0.2.65/255.255.254.0') != IPNetwork('192.0.2.65/24') + + +def test_ipnetwork_slicing_v4(): + ip = IPNetwork('192.0.2.0/23') + + assert ip.first == 3221225984 + assert ip.last == 3221226495 + + assert ip[0] == IPAddress('192.0.2.0') + assert ip[-1] == IPAddress('192.0.3.255') + + assert list(ip[::128]) == [ + IPAddress('192.0.2.0'), + IPAddress('192.0.2.128'), + IPAddress('192.0.3.0'), + IPAddress('192.0.3.128'), + ] + + +def test_ip_network_membership_v4(): + for what, network, result in [ + (IPAddress('192.0.2.1'), IPNetwork('192.0.2.0/24'), True), + (IPAddress('192.0.2.255'), IPNetwork('192.0.2.0/24'), True), + (IPNetwork('192.0.2.0/24'), IPNetwork('192.0.2.0/23'), True), + (IPNetwork('192.0.2.0/24'), IPNetwork('192.0.2.0/24'), True), + (IPNetwork('192.0.2.0/23'), IPNetwork('192.0.2.0/24'), False), + ]: + assert (what in network) is result + assert (str(what) in network) is result + + +def test_ip_network_equality_v4(): + assert IPNetwork('192.0.2.0/24') == IPNetwork('192.0.2.0/24') + assert IPNetwork('192.0.2.0/24') is not IPNetwork('192.0.2.0/24') + + assert not IPNetwork('192.0.2.0/24') != IPNetwork('192.0.2.0/24') + assert not IPNetwork('192.0.2.0/24') is IPNetwork('192.0.2.0/24') + + +def test_ipaddress_integer_constructor_v4(): + assert IPAddress(1) == IPAddress('0.0.0.1') + assert IPAddress(1, 4) == IPAddress('0.0.0.1') + assert IPAddress(1, 6) == IPAddress('::1') + assert IPAddress(10) == IPAddress('0.0.0.10') + + +def test_ipaddress_integer_constructor_v6(): + assert IPAddress(0x1ffffffff) == IPAddress('::1:ffff:ffff') + assert IPAddress(0xffffffff, 6) == IPAddress('::255.255.255.255') + assert IPAddress(0x1ffffffff) == IPAddress('::1:ffff:ffff') + assert IPAddress(2 ** 128 - 1) == IPAddress('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff') + + +def test_ipaddress_inet_aton_constructor_v4(): + assert IPAddress('0x7f.0x1') == IPAddress('127.0.0.1') + assert IPAddress('0x7f.0x0.0x0.0x1') == IPAddress('127.0.0.1') + assert IPAddress('0177.01') == IPAddress('127.0.0.1') + assert IPAddress('0x7f.0.01') == IPAddress('127.0.0.1') + + # Partial addresses - pretty weird, but valid ... + assert IPAddress('127') == IPAddress('0.0.0.127') + assert IPAddress('127') == IPAddress('0.0.0.127') + assert IPAddress('127.1') == IPAddress('127.0.0.1') + assert IPAddress('127.0.1') == IPAddress('127.0.0.1') + + # Verify explicit INET_ATON is the same as the current default + assert IPAddress('127', flags=INET_ATON) == IPAddress('127') + + +def test_ipaddress_inet_pton_constructor_v4(): + with pytest.raises(AddrFormatError): + IPAddress('0177.01', flags=INET_PTON) + + with pytest.raises(AddrFormatError): + IPAddress('0x7f.0.01', flags=INET_PTON) + + with pytest.raises(AddrFormatError): + IPAddress('10', flags=INET_PTON) + + with pytest.raises(AddrFormatError): + IPAddress('10.1', flags=INET_PTON) + + with pytest.raises(AddrFormatError): + IPAddress('10.0.1', flags=INET_PTON) + + assert IPAddress('10.0.0.1', flags=INET_PTON) == IPAddress('10.0.0.1') + + +def test_ipaddress_constructor_zero_filled_octets_v4(): + assert IPAddress('010.000.000.001') == IPAddress('8.0.0.1') + assert IPAddress('010.000.000.001', flags=ZEROFILL) == IPAddress('10.0.0.1') + assert IPAddress('010.000.001', flags=ZEROFILL) == IPAddress('10.0.0.1') + # Verify explicit INET_ATON is the same as the current default + assert IPAddress('010.000.000.001', flags=INET_ATON | ZEROFILL) == IPAddress('10.0.0.1') + + with pytest.raises(AddrFormatError): + assert IPAddress('010.000.001', flags=INET_PTON|ZEROFILL) + + assert IPAddress('010.000.000.001', flags=INET_PTON|ZEROFILL) == IPAddress('10.0.0.1') + + # Short flags. + assert IPAddress('010.000.000.001', flags=P|Z) == IPAddress('10.0.0.1') + + +def test_ipnetwork_constructor_v4(): + assert IPNetwork('192.0.2.0/24') == IPNetwork('192.0.2.0/24') + assert IPNetwork('192.0.2.0/255.255.255.0') == IPNetwork('192.0.2.0/24') + assert IPNetwork('192.0.2.0/0.0.0.255') == IPNetwork('192.0.2.0/24') + assert IPNetwork(IPNetwork('192.0.2.0/24')) == IPNetwork('192.0.2.0/24') + assert IPNetwork(IPNetwork('192.0.2.0/24')) == IPNetwork('192.0.2.0/24') + + +def test_ip_network_cosntructor_implicit_prefix_flag_v4(): + assert IPNetwork('192.0.2.0', implicit_prefix=True) == IPNetwork('192.0.2.0/24') + assert IPNetwork('231.192.0.15', implicit_prefix=True) == IPNetwork('231.192.0.15/4') + assert IPNetwork('10', implicit_prefix=True) == IPNetwork('10.0.0.0/8') + + +def test_ipnetwork_constructor_other_flags_v4(): + assert IPNetwork('172.24.200') == IPNetwork('172.24.200.0/32') + assert IPNetwork('172.24.200', implicit_prefix=True) == IPNetwork('172.24.200.0/16') + assert IPNetwork('172.24.200', implicit_prefix=True, flags=NOHOST) == IPNetwork('172.24.0.0/16') + + +def test_ipnetwork_bad_string_constructor(): + with pytest.raises(AddrFormatError): + IPNetwork('foo') + + +def test_ipaddress_netmask_v4(): + assert IPAddress('0.0.0.0').netmask_bits() == 0 + assert IPAddress('128.0.0.0').netmask_bits() == 1 + assert IPAddress('255.0.0.0').netmask_bits() == 8 + assert IPAddress('255.255.0.0').netmask_bits() == 16 + assert IPAddress('255.255.255.0').netmask_bits() == 24 + assert IPAddress('255.255.255.254').netmask_bits() == 31 + assert IPAddress('255.255.255.255').netmask_bits() == 32 + + assert IPAddress('1.1.1.1').netmask_bits() == 32 + + +def test_ipaddress_hex_format(): + assert hex(IPAddress(0)) == '0x0' + assert hex(IPAddress(0xffffffff)) == '0xffffffff' + + +@pytest.mark.skipif('sys.version_info > (2,)', reason="requires python 2.x behaviour") +def test_ipaddress_oct_format_py2(): + assert oct(IPAddress(0xffffffff)) == '037777777777' + assert oct(IPAddress(0)) == '0' + + +@pytest.mark.skipif('sys.version_info < (3,)', reason="python 3.x behaviour") +def test_ipaddress_oct_format_py3(): + assert oct(IPAddress(0xffffffff)) == '0o37777777777' + assert oct(IPAddress(0)) == '0o0' + + +def test_multicast_info(): + ip = IPAddress('224.0.1.173') + assert ip.info.IPv4[0].designation == 'Multicast' + assert ip.info.IPv4[0].prefix == '224/8' + assert ip.info.IPv4[0].status == 'Reserved' + assert ip.info.Multicast[0].address == '224.0.1.173' + + +def test_ipaddress_pickling_v4(): + ip = IPAddress(3221225985) + assert ip == IPAddress('192.0.2.1') + + buf = pickle.dumps(ip) + ip2 = pickle.loads(buf) + + assert ip2 == ip + assert id(ip2) != id(ip) + assert ip2.value == 3221225985 + assert ip2.version == 4 + + +def test_ipnetwork_pickling_v4(): + cidr = IPNetwork('192.0.2.0/24') + assert cidr == IPNetwork('192.0.2.0/24') + + buf = pickle.dumps(cidr) + cidr2 = pickle.loads(buf) + + assert cidr2 == cidr + assert id(cidr2) != id(cidr) + assert cidr2.value == 3221225984 + assert cidr2.prefixlen == 24 + assert cidr2.version == 4 + + +def test_ipnetwork_incrementing_by_int(): + ip = IPNetwork('192.0.2.0/28') + results = [] + for i in range(16): + results.append(str(ip)) + ip += 1 + + assert results == [ + '192.0.2.0/28', + '192.0.2.16/28', + '192.0.2.32/28', + '192.0.2.48/28', + '192.0.2.64/28', + '192.0.2.80/28', + '192.0.2.96/28', + '192.0.2.112/28', + '192.0.2.128/28', + '192.0.2.144/28', + '192.0.2.160/28', + '192.0.2.176/28', + '192.0.2.192/28', + '192.0.2.208/28', + '192.0.2.224/28', + '192.0.2.240/28' + ] + + +def test_rfc3021_subnets(): + # Tests for /31 subnet + assert IPNetwork('192.0.2.0/31').network == IPAddress('192.0.2.0') + assert IPNetwork('192.0.2.0/31').broadcast is None + assert list(IPNetwork('192.0.2.0/31').iter_hosts()) == [IPAddress('192.0.2.0'), IPAddress('192.0.2.1')] + + # Tests for /32 subnet + assert IPNetwork('192.0.2.0/32').network == IPAddress('192.0.2.0') + assert IPNetwork('192.0.2.0/32').broadcast is None + assert list(IPNetwork('192.0.2.0/32').iter_hosts()) == [IPAddress('192.0.2.0')] + + +def test_ipnetwork_change_prefixlen(): + ip = IPNetwork('192.168.0.0/16') + assert ip.prefixlen == 16 + ip.prefixlen = 8 + assert ip.prefixlen == 8 + + ip = IPNetwork('dead:beef::/16') + assert ip.prefixlen == 16 + ip.prefixlen = 64 + assert ip.prefixlen == 64 + + +def test_ipnetwork_change_netmask(): + ip = IPNetwork('192.168.0.0/16') + ip.netmask = '255.0.0.0' + assert ip.prefixlen == 8 + + ip = IPNetwork('dead:beef::/16') + ip.netmask = 'ffff:ffff:ffff:ffff::' + assert ip.prefixlen == 64 + + +def test_spanning_cidr_handles_strings(): + # This that a regression introduced in 0fda41a is fixed. The regression caused an error when str + # addresses were passed to the function. + addresses = [ + IPAddress('10.0.0.1'), + IPAddress('10.0.0.2'), + '10.0.0.3', + '10.0.0.4', + ] + assert spanning_cidr(addresses) == IPNetwork('10.0.0.0/29') + assert spanning_cidr(reversed(addresses)) == IPNetwork('10.0.0.0/29') diff --git a/netaddr/tests/ip/test_ip_v4_v6_conversions.py b/netaddr/tests/ip/test_ip_v4_v6_conversions.py new file mode 100644 index 0000000..3ee23cb --- /dev/null +++ b/netaddr/tests/ip/test_ip_v4_v6_conversions.py @@ -0,0 +1,33 @@ +from netaddr import IPAddress, IPNetwork + + +def test_ip_v4_to_ipv6_mapped(): + ip = IPAddress('192.0.2.15').ipv6() + assert ip == IPAddress('::ffff:192.0.2.15') + assert ip.is_ipv4_mapped() + assert not ip.is_ipv4_compat() + + +def test_ip_v4_to_ipv4(): + assert IPAddress('192.0.2.15').ipv4() == IPAddress('192.0.2.15') + + +def test_ip_v4_to_ipv6_compatible(): + assert IPAddress('192.0.2.15').ipv6(ipv4_compatible=True) == IPAddress('::192.0.2.15') + assert IPAddress('192.0.2.15').ipv6(ipv4_compatible=True).is_ipv4_compat() + assert IPAddress('192.0.2.15').ipv6(True) == IPAddress('::192.0.2.15') + + ip = IPNetwork('192.0.2.1/23') + assert ip.ipv4() == IPNetwork('192.0.2.1/23') + assert ip.ipv6() == IPNetwork('::ffff:192.0.2.1/119') + assert ip.ipv6(ipv4_compatible=True) == IPNetwork('::192.0.2.1/119') + + +def test_ip_v6_to_ipv4(): + assert IPNetwork('::ffff:192.0.2.1/119').ipv6(ipv4_compatible=True) == IPNetwork('::192.0.2.1/119') + assert IPNetwork('::ffff:192.0.2.1/119').ipv4() == IPNetwork('192.0.2.1/23') + assert IPNetwork('::192.0.2.1/119').ipv4() == IPNetwork('192.0.2.1/23') + + +def test_ip_v6_to_ipv6(): + assert IPNetwork('::ffff:192.0.2.1/119').ipv6() == IPNetwork('::ffff:192.0.2.1/119') diff --git a/netaddr/tests/ip/test_ip_v6.py b/netaddr/tests/ip/test_ip_v6.py new file mode 100644 index 0000000..395dcf3 --- /dev/null +++ b/netaddr/tests/ip/test_ip_v6.py @@ -0,0 +1,168 @@ +import pickle +import sys +import pytest +from netaddr import IPAddress, IPNetwork + + +def test_ipaddress_v6(): + ip = IPAddress('fe80::dead:beef') + assert ip.version == 6 + assert repr(ip) == "IPAddress('fe80::dead:beef')" + assert str(ip) == 'fe80::dead:beef' + assert ip.format() == 'fe80::dead:beef' + assert int(ip) == 338288524927261089654018896845083623151 + assert hex(ip) == '0xfe8000000000000000000000deadbeef' + if sys.version_info[0] > 2: + assert bytes(ip) == b'\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xad\xbe\xef' + assert ip.bin == '0b11111110100000000000000000000000000000000000000000000000000000000000000000000000000000000000000011011110101011011011111011101111' + assert ip.bits() == '1111111010000000:0000000000000000:0000000000000000:0000000000000000:0000000000000000:0000000000000000:1101111010101101:1011111011101111' + assert ip.words == (65152, 0, 0, 0, 0, 0, 57005, 48879) + + +@pytest.mark.parametrize( + ('value', 'ipaddr', 'network', 'cidr', 'broadcast', 'netmask', 'hostmask', 'size'), [ + ( + 'fe80::dead:beef/64', + IPAddress('fe80::dead:beef'), + IPAddress('fe80::'), + IPNetwork('fe80::/64'), + IPAddress('fe80::ffff:ffff:ffff:ffff'), + IPAddress('ffff:ffff:ffff:ffff::'), + IPAddress('::ffff:ffff:ffff:ffff'), + 18446744073709551616, + ), + ]) +def test_ipnetwork_v6(value, ipaddr, network, cidr, broadcast, netmask, hostmask, size): + net = IPNetwork(value) + assert net.ip == ipaddr + assert net.network == network + assert net.cidr == cidr + assert net.broadcast == broadcast + assert net.netmask == netmask + assert net.hostmask == hostmask + assert net.size == size + + +def test_iterhosts_v6(): + assert list(IPNetwork('::ffff:192.0.2.0/125').iter_hosts()) == [ + IPAddress('::ffff:192.0.2.1'), + IPAddress('::ffff:192.0.2.2'), + IPAddress('::ffff:192.0.2.3'), + IPAddress('::ffff:192.0.2.4'), + IPAddress('::ffff:192.0.2.5'), + IPAddress('::ffff:192.0.2.6'), + IPAddress('::ffff:192.0.2.7'), + ] + +def test_ipnetwork_boolean_evaluation_v6(): + assert bool(IPNetwork('::/0')) + + +def test_ipnetwork_slice_v6(): + ip = IPNetwork('fe80::/10') + assert ip[0] == IPAddress('fe80::') + assert ip[-1] == IPAddress('febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff') + assert ip.size == 332306998946228968225951765070086144 + + with pytest.raises(TypeError): + list(ip[0:5:1]) + + +def test_ip_network_membership_v6(): + for what, network, result in [ + (IPAddress('ffff::1'), IPNetwork('ffff::/127'), True), + (IPNetwork('ffff::/127'), IPNetwork('ffff::/127'), True), + (IPNetwork('fe80::/10'), IPNetwork('ffff::/127'), False), + ]: + assert (what in network) is result + assert (str(what) in network) is result + + +def test_ip_network_equality_v6(): + assert IPNetwork('fe80::/10') == IPNetwork('fe80::/10') + assert IPNetwork('fe80::/10') is not IPNetwork('fe80::/10') + + assert not IPNetwork('fe80::/10') != IPNetwork('fe80::/10') + assert not IPNetwork('fe80::/10') is IPNetwork('fe80::/10') + + +def test_ipnetwork_constructor_v6(): + assert IPNetwork(IPNetwork('::192.0.2.0/120')) == IPNetwork('::192.0.2.0/120') + assert IPNetwork('::192.0.2.0/120') == IPNetwork('::192.0.2.0/120') + assert IPNetwork('::192.0.2.0/120', 6) == IPNetwork('::192.0.2.0/120') + + +def test_ipaddress_netmask_v6(): + assert IPAddress('::').netmask_bits() == 0 + assert IPAddress('8000::').netmask_bits() == 1 + assert IPAddress('ffff:ffff:ffff:ffff::').netmask_bits() == 64 + assert IPAddress('ffff:ffff:ffff:ffff:ffff:ffff:ffff::').netmask_bits() == 112 + assert IPAddress('ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe').netmask_bits() == 127 + assert IPAddress('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff').netmask_bits() == 128 + + assert IPAddress('fe80::1').netmask_bits() == 128 + + +def test_objects_use_slots(): + assert not hasattr(IPNetwork("::/64"), "__dict__") + assert not hasattr(IPAddress("::"), "__dict__") + + +def test_ipaddress_pickling_v6(): + ip = IPAddress('::ffff:192.0.2.1') + assert ip == IPAddress('::ffff:192.0.2.1') + + assert ip.value == 281473902969345 + + buf = pickle.dumps(ip) + ip2 = pickle.loads(buf) + assert ip2 == ip + assert ip2.value == 281473902969345 + assert ip2.version == 6 + + +def test_ipnetwork_pickling_v6(): + cidr = IPNetwork('::ffff:192.0.2.0/120') + assert cidr == IPNetwork('::ffff:192.0.2.0/120') + assert cidr.value == 281473902969344 + assert cidr.prefixlen == 120 + + buf = pickle.dumps(cidr) + cidr2 = pickle.loads(buf) + + assert cidr2 == cidr + assert cidr2.value == 281473902969344 + assert cidr2.prefixlen == 120 + assert cidr2.version == 6 + + +def test_ipv6_unicast_address_allocation_info(): + ip = IPNetwork('2001:1200::/23') + + assert ip.info.IPv6[0].allocation == 'Global Unicast' + assert ip.info.IPv6[0].prefix == '2000::/3' + assert ip.info.IPv6[0].reference == 'rfc4291' + + assert ip.info.IPv6_unicast[0].prefix == '2001:1200::/23' + assert ip.info.IPv6_unicast[0].description == 'LACNIC' + assert ip.info.IPv6_unicast[0].whois == 'whois.lacnic.net' + assert ip.info.IPv6_unicast[0].status == 'ALLOCATED' + +def test_rfc6164_subnets(): + # Tests for /127 subnet + assert list(IPNetwork('1234::/127')) == [ + IPAddress('1234::'), + IPAddress('1234::1'), + ] + assert list(IPNetwork('1234::/127').iter_hosts()) == [ + IPAddress('1234::'), + IPAddress('1234::1'), + ] + assert IPNetwork('1234::/127').network == IPAddress('1234::') + assert IPNetwork('1234::').broadcast is None + + # Tests for /128 subnet + assert IPNetwork("1234::/128").network == IPAddress('1234::') + assert IPNetwork("1234::/128").broadcast is None + assert list(IPNetwork("1234::/128")) == [IPAddress('1234::')] + assert list(IPNetwork("1234::/128").iter_hosts()) == [IPAddress('1234::')] diff --git a/netaddr/tests/ip/test_network_ops.py b/netaddr/tests/ip/test_network_ops.py new file mode 100644 index 0000000..f682c4f --- /dev/null +++ b/netaddr/tests/ip/test_network_ops.py @@ -0,0 +1,81 @@ +import types + +from netaddr import IPNetwork, cidr_merge + +def test_ipnetwork_cidr_merge(): + ip_list = ( + list(IPNetwork('fe80::/120')) + + [ + IPNetwork('192.0.2.0/24'), + IPNetwork('192.0.4.0/25'), + IPNetwork('192.0.4.128/25'), + ] + + list(map(str, IPNetwork('192.0.3.0/24'))) + ) + assert len(ip_list) == 515 + + assert cidr_merge(ip_list) == [ + IPNetwork('192.0.2.0/23'), + IPNetwork('192.0.4.0/24'), + IPNetwork('fe80::/120'), + ] + +def test_subnetting(): + ip = IPNetwork('172.24.0.0/23') + assert isinstance(ip.subnet(28), types.GeneratorType) + + subnets = list(ip.subnet(28)) + assert len(subnets) == 32 + + assert subnets == [ + IPNetwork('172.24.0.0/28'), + IPNetwork('172.24.0.16/28'), + IPNetwork('172.24.0.32/28'), + IPNetwork('172.24.0.48/28'), + IPNetwork('172.24.0.64/28'), + IPNetwork('172.24.0.80/28'), + IPNetwork('172.24.0.96/28'), + IPNetwork('172.24.0.112/28'), + IPNetwork('172.24.0.128/28'), + IPNetwork('172.24.0.144/28'), + IPNetwork('172.24.0.160/28'), + IPNetwork('172.24.0.176/28'), + IPNetwork('172.24.0.192/28'), + IPNetwork('172.24.0.208/28'), + IPNetwork('172.24.0.224/28'), + IPNetwork('172.24.0.240/28'), + IPNetwork('172.24.1.0/28'), + IPNetwork('172.24.1.16/28'), + IPNetwork('172.24.1.32/28'), + IPNetwork('172.24.1.48/28'), + IPNetwork('172.24.1.64/28'), + IPNetwork('172.24.1.80/28'), + IPNetwork('172.24.1.96/28'), + IPNetwork('172.24.1.112/28'), + IPNetwork('172.24.1.128/28'), + IPNetwork('172.24.1.144/28'), + IPNetwork('172.24.1.160/28'), + IPNetwork('172.24.1.176/28'), + IPNetwork('172.24.1.192/28'), + IPNetwork('172.24.1.208/28'), + IPNetwork('172.24.1.224/28'), + IPNetwork('172.24.1.240/28'), + ] + + +def test_supernetting(): + ip = IPNetwork('192.0.2.114') + supernets = ip.supernet(22) + + assert supernets == [ + IPNetwork('192.0.0.0/22'), + IPNetwork('192.0.2.0/23'), + IPNetwork('192.0.2.0/24'), + IPNetwork('192.0.2.0/25'), + IPNetwork('192.0.2.64/26'), + IPNetwork('192.0.2.96/27'), + IPNetwork('192.0.2.112/28'), + IPNetwork('192.0.2.112/29'), + IPNetwork('192.0.2.112/30'), + IPNetwork('192.0.2.114/31'), + ] diff --git a/netaddr/tests/ip/test_nmap.py b/netaddr/tests/ip/test_nmap.py new file mode 100644 index 0000000..7b5caf8 --- /dev/null +++ b/netaddr/tests/ip/test_nmap.py @@ -0,0 +1,86 @@ +import pytest +from netaddr import valid_nmap_range, iter_nmap_range, IPAddress, AddrFormatError + + +def test_valid_nmap_range_with_valid_target_specs(): + assert valid_nmap_range('192.0.2.1') + assert valid_nmap_range('192.0.2.0-31') + assert valid_nmap_range('192.0.2-3.1-254') + assert valid_nmap_range('0-255.0-255.0-255.0-255') + assert valid_nmap_range('192.168.3-5,7.1') + assert valid_nmap_range('192.168.3-5,7,10-12,13,14.1') + assert valid_nmap_range('fe80::1') + assert valid_nmap_range('::') + assert valid_nmap_range('192.0.2.0/24') + + +def test_valid_nmap_range_with_invalid_target_specs(): + assert not valid_nmap_range('192.0.2.0/255.255.255.0') + assert not valid_nmap_range(1) + assert not valid_nmap_range('1') + assert not valid_nmap_range([]) + assert not valid_nmap_range({}) + assert not valid_nmap_range('fe80::/64') + assert not valid_nmap_range('255.255.255.256') + assert not valid_nmap_range('0-255.0-255.0-255.0-256') + assert not valid_nmap_range('0-255.0-255.0-255.-1-0') + assert not valid_nmap_range('0-255.0-255.0-255.256-0') + assert not valid_nmap_range('0-255.0-255.0-255.255-0') + assert not valid_nmap_range('a.b.c.d-e') + assert not valid_nmap_range('255.255.255.a-b') + + +def test_iter_nmap_range(): + assert list(iter_nmap_range('192.0.2.1')) == [IPAddress('192.0.2.1')] + + ip_list = list(iter_nmap_range('192.0.2.0-31')) + assert len(ip_list) == 32 + assert ip_list == [ + IPAddress('192.0.2.0'), IPAddress('192.0.2.1'), IPAddress('192.0.2.2'), + IPAddress('192.0.2.3'), IPAddress('192.0.2.4'), IPAddress('192.0.2.5'), + IPAddress('192.0.2.6'), IPAddress('192.0.2.7'), IPAddress('192.0.2.8'), IPAddress('192.0.2.9'), + IPAddress('192.0.2.10'), IPAddress('192.0.2.11'), IPAddress('192.0.2.12'), IPAddress('192.0.2.13'), + IPAddress('192.0.2.14'), IPAddress('192.0.2.15'), IPAddress('192.0.2.16'), IPAddress('192.0.2.17'), + IPAddress('192.0.2.18'), IPAddress('192.0.2.19'), IPAddress('192.0.2.20'), IPAddress('192.0.2.21'), + IPAddress('192.0.2.22'), IPAddress('192.0.2.23'), IPAddress('192.0.2.24'), IPAddress('192.0.2.25'), + IPAddress('192.0.2.26'), IPAddress('192.0.2.27'), IPAddress('192.0.2.28'), IPAddress('192.0.2.29'), + IPAddress('192.0.2.30'), IPAddress('192.0.2.31')] + + assert len(list(iter_nmap_range('192.0.2-3.1-7'))) == 14 + + assert list(iter_nmap_range('192.0.2.1-3,5,7-9')) == [ + IPAddress('192.0.2.1'), + IPAddress('192.0.2.2'), + IPAddress('192.0.2.3'), + IPAddress('192.0.2.5'), + IPAddress('192.0.2.7'), + IPAddress('192.0.2.8'), + IPAddress('192.0.2.9'), + ] + + +def test_iter_nmap_range_with_multiple_targets_including_cidr(): + assert list(iter_nmap_range('192.168.0.0/29', '192.168.3-5,7.1', 'fe80::1')) == [ + IPAddress('192.168.0.0'), + IPAddress('192.168.0.1'), + IPAddress('192.168.0.2'), + IPAddress('192.168.0.3'), + IPAddress('192.168.0.4'), + IPAddress('192.168.0.5'), + IPAddress('192.168.0.6'), + IPAddress('192.168.0.7'), + IPAddress('192.168.3.1'), + IPAddress('192.168.4.1'), + IPAddress('192.168.5.1'), + IPAddress('192.168.7.1'), + IPAddress('fe80::1'), + ] + + +def test_iter_nmap_range_invalid(): + with pytest.raises(AddrFormatError): + list(iter_nmap_range('fe80::/64')) + + +def test_iter_nmap_range_remove_duplicates(): + assert list(iter_nmap_range('10.0.0.42,42-42')) == [IPAddress('10.0.0.42')] diff --git a/netaddr/tests/ip/test_old_specs.py b/netaddr/tests/ip/test_old_specs.py new file mode 100644 index 0000000..7f4ebca --- /dev/null +++ b/netaddr/tests/ip/test_old_specs.py @@ -0,0 +1,281 @@ +import pytest + +from netaddr import cidr_abbrev_to_verbose +from netaddr.strategy.ipv4 import expand_partial_address + + +def test_cidr_abbrev_to_verbose(): + octets = range(0, 256) + + cidrs = [cidr_abbrev_to_verbose(octet) for octet in octets] + + assert len(cidrs) == 256 + + assert cidrs == [ + '0.0.0.0/8', + '1.0.0.0/8', + '2.0.0.0/8', + '3.0.0.0/8', + '4.0.0.0/8', + '5.0.0.0/8', + '6.0.0.0/8', + '7.0.0.0/8', + '8.0.0.0/8', + '9.0.0.0/8', + '10.0.0.0/8', + '11.0.0.0/8', + '12.0.0.0/8', + '13.0.0.0/8', + '14.0.0.0/8', + '15.0.0.0/8', + '16.0.0.0/8', + '17.0.0.0/8', + '18.0.0.0/8', + '19.0.0.0/8', + '20.0.0.0/8', + '21.0.0.0/8', + '22.0.0.0/8', + '23.0.0.0/8', + '24.0.0.0/8', + '25.0.0.0/8', + '26.0.0.0/8', + '27.0.0.0/8', + '28.0.0.0/8', + '29.0.0.0/8', + '30.0.0.0/8', + '31.0.0.0/8', + '32.0.0.0/8', + '33.0.0.0/8', + '34.0.0.0/8', + '35.0.0.0/8', + '36.0.0.0/8', + '37.0.0.0/8', + '38.0.0.0/8', + '39.0.0.0/8', + '40.0.0.0/8', + '41.0.0.0/8', + '42.0.0.0/8', + '43.0.0.0/8', + '44.0.0.0/8', + '45.0.0.0/8', + '46.0.0.0/8', + '47.0.0.0/8', + '48.0.0.0/8', + '49.0.0.0/8', + '50.0.0.0/8', + '51.0.0.0/8', + '52.0.0.0/8', + '53.0.0.0/8', + '54.0.0.0/8', + '55.0.0.0/8', + '56.0.0.0/8', + '57.0.0.0/8', + '58.0.0.0/8', + '59.0.0.0/8', + '60.0.0.0/8', + '61.0.0.0/8', + '62.0.0.0/8', + '63.0.0.0/8', + '64.0.0.0/8', + '65.0.0.0/8', + '66.0.0.0/8', + '67.0.0.0/8', + '68.0.0.0/8', + '69.0.0.0/8', + '70.0.0.0/8', + '71.0.0.0/8', + '72.0.0.0/8', + '73.0.0.0/8', + '74.0.0.0/8', + '75.0.0.0/8', + '76.0.0.0/8', + '77.0.0.0/8', + '78.0.0.0/8', + '79.0.0.0/8', + '80.0.0.0/8', + '81.0.0.0/8', + '82.0.0.0/8', + '83.0.0.0/8', + '84.0.0.0/8', + '85.0.0.0/8', + '86.0.0.0/8', + '87.0.0.0/8', + '88.0.0.0/8', + '89.0.0.0/8', + '90.0.0.0/8', + '91.0.0.0/8', + '92.0.0.0/8', + '93.0.0.0/8', + '94.0.0.0/8', + '95.0.0.0/8', + '96.0.0.0/8', + '97.0.0.0/8', + '98.0.0.0/8', + '99.0.0.0/8', + '100.0.0.0/8', + '101.0.0.0/8', + '102.0.0.0/8', + '103.0.0.0/8', + '104.0.0.0/8', + '105.0.0.0/8', + '106.0.0.0/8', + '107.0.0.0/8', + '108.0.0.0/8', + '109.0.0.0/8', + '110.0.0.0/8', + '111.0.0.0/8', + '112.0.0.0/8', + '113.0.0.0/8', + '114.0.0.0/8', + '115.0.0.0/8', + '116.0.0.0/8', + '117.0.0.0/8', + '118.0.0.0/8', + '119.0.0.0/8', + '120.0.0.0/8', + '121.0.0.0/8', + '122.0.0.0/8', + '123.0.0.0/8', + '124.0.0.0/8', + '125.0.0.0/8', + '126.0.0.0/8', + '127.0.0.0/8', + '128.0.0.0/16', + '129.0.0.0/16', + '130.0.0.0/16', + '131.0.0.0/16', + '132.0.0.0/16', + '133.0.0.0/16', + '134.0.0.0/16', + '135.0.0.0/16', + '136.0.0.0/16', + '137.0.0.0/16', + '138.0.0.0/16', + '139.0.0.0/16', + '140.0.0.0/16', + '141.0.0.0/16', + '142.0.0.0/16', + '143.0.0.0/16', + '144.0.0.0/16', + '145.0.0.0/16', + '146.0.0.0/16', + '147.0.0.0/16', + '148.0.0.0/16', + '149.0.0.0/16', + '150.0.0.0/16', + '151.0.0.0/16', + '152.0.0.0/16', + '153.0.0.0/16', + '154.0.0.0/16', + '155.0.0.0/16', + '156.0.0.0/16', + '157.0.0.0/16', + '158.0.0.0/16', + '159.0.0.0/16', + '160.0.0.0/16', + '161.0.0.0/16', + '162.0.0.0/16', + '163.0.0.0/16', + '164.0.0.0/16', + '165.0.0.0/16', + '166.0.0.0/16', + '167.0.0.0/16', + '168.0.0.0/16', + '169.0.0.0/16', + '170.0.0.0/16', + '171.0.0.0/16', + '172.0.0.0/16', + '173.0.0.0/16', + '174.0.0.0/16', + '175.0.0.0/16', + '176.0.0.0/16', + '177.0.0.0/16', + '178.0.0.0/16', + '179.0.0.0/16', + '180.0.0.0/16', + '181.0.0.0/16', + '182.0.0.0/16', + '183.0.0.0/16', + '184.0.0.0/16', + '185.0.0.0/16', + '186.0.0.0/16', + '187.0.0.0/16', + '188.0.0.0/16', + '189.0.0.0/16', + '190.0.0.0/16', + '191.0.0.0/16', + '192.0.0.0/24', + '193.0.0.0/24', + '194.0.0.0/24', + '195.0.0.0/24', + '196.0.0.0/24', + '197.0.0.0/24', + '198.0.0.0/24', + '199.0.0.0/24', + '200.0.0.0/24', + '201.0.0.0/24', + '202.0.0.0/24', + '203.0.0.0/24', + '204.0.0.0/24', + '205.0.0.0/24', + '206.0.0.0/24', + '207.0.0.0/24', + '208.0.0.0/24', + '209.0.0.0/24', + '210.0.0.0/24', + '211.0.0.0/24', + '212.0.0.0/24', + '213.0.0.0/24', + '214.0.0.0/24', + '215.0.0.0/24', + '216.0.0.0/24', + '217.0.0.0/24', + '218.0.0.0/24', + '219.0.0.0/24', + '220.0.0.0/24', + '221.0.0.0/24', + '222.0.0.0/24', + '223.0.0.0/24', + '224.0.0.0/4', + '225.0.0.0/4', + '226.0.0.0/4', + '227.0.0.0/4', + '228.0.0.0/4', + '229.0.0.0/4', + '230.0.0.0/4', + '231.0.0.0/4', + '232.0.0.0/4', + '233.0.0.0/4', + '234.0.0.0/4', + '235.0.0.0/4', + '236.0.0.0/4', + '237.0.0.0/4', + '238.0.0.0/4', + '239.0.0.0/4', + '240.0.0.0/32', + '241.0.0.0/32', + '242.0.0.0/32', + '243.0.0.0/32', + '244.0.0.0/32', + '245.0.0.0/32', + '246.0.0.0/32', + '247.0.0.0/32', + '248.0.0.0/32', + '249.0.0.0/32', + '250.0.0.0/32', + '251.0.0.0/32', + '252.0.0.0/32', + '253.0.0.0/32', + '254.0.0.0/32', + '255.0.0.0/32', + ] + + +def test_cidr_abbrev_to_verbose_invalid_prefixlen(): + assert cidr_abbrev_to_verbose('192.0.2.0/33') == '192.0.2.0/33' + + +def test_expand_partial_address(): + assert expand_partial_address('10') == '10.0.0.0' + assert expand_partial_address('10.1') == '10.1.0.0' + assert expand_partial_address('192.168.1') == '192.168.1.0' diff --git a/netaddr/tests/ip/test_platform_osx.py b/netaddr/tests/ip/test_platform_osx.py new file mode 100644 index 0000000..c28bceb --- /dev/null +++ b/netaddr/tests/ip/test_platform_osx.py @@ -0,0 +1,156 @@ +import platform + +import pytest + +from netaddr import iprange_to_cidrs, IPNetwork, IPAddress, INET_PTON, AddrFormatError +from netaddr.strategy.ipv6 import int_to_str + + +@pytest.mark.skipif('sys.platform != "darwin"') +def test_ip_behaviour_osx(): + assert iprange_to_cidrs('::1', '::255.255.255.254') == [ + IPNetwork('::1/128'), + IPNetwork('::0.0.0.2/127'), + IPNetwork('::0.0.0.4/126'), + IPNetwork('::0.0.0.8/125'), + IPNetwork('::0.0.0.16/124'), + IPNetwork('::0.0.0.32/123'), + IPNetwork('::0.0.0.64/122'), + IPNetwork('::0.0.0.128/121'), + IPNetwork('::0.0.1.0/120'), + IPNetwork('::0.0.2.0/119'), + IPNetwork('::0.0.4.0/118'), + IPNetwork('::0.0.8.0/117'), + IPNetwork('::0.0.16.0/116'), + IPNetwork('::0.0.32.0/115'), + IPNetwork('::0.0.64.0/114'), + IPNetwork('::0.0.128.0/113'), + IPNetwork('::0.1.0.0/112'), + IPNetwork('::0.2.0.0/111'), + IPNetwork('::0.4.0.0/110'), + IPNetwork('::0.8.0.0/109'), + IPNetwork('::0.16.0.0/108'), + IPNetwork('::0.32.0.0/107'), + IPNetwork('::0.64.0.0/106'), + IPNetwork('::0.128.0.0/105'), + IPNetwork('::1.0.0.0/104'), + IPNetwork('::2.0.0.0/103'), + IPNetwork('::4.0.0.0/102'), + IPNetwork('::8.0.0.0/101'), + IPNetwork('::16.0.0.0/100'), + IPNetwork('::32.0.0.0/99'), + IPNetwork('::64.0.0.0/98'), + IPNetwork('::128.0.0.0/98'), + IPNetwork('::192.0.0.0/99'), + IPNetwork('::224.0.0.0/100'), + IPNetwork('::240.0.0.0/101'), + IPNetwork('::248.0.0.0/102'), + IPNetwork('::252.0.0.0/103'), + IPNetwork('::254.0.0.0/104'), + IPNetwork('::255.0.0.0/105'), + IPNetwork('::255.128.0.0/106'), + IPNetwork('::255.192.0.0/107'), + IPNetwork('::255.224.0.0/108'), + IPNetwork('::255.240.0.0/109'), + IPNetwork('::255.248.0.0/110'), + IPNetwork('::255.252.0.0/111'), + IPNetwork('::255.254.0.0/112'), + IPNetwork('::255.255.0.0/113'), + IPNetwork('::255.255.128.0/114'), + IPNetwork('::255.255.192.0/115'), + IPNetwork('::255.255.224.0/116'), + IPNetwork('::255.255.240.0/117'), + IPNetwork('::255.255.248.0/118'), + IPNetwork('::255.255.252.0/119'), + IPNetwork('::255.255.254.0/120'), + IPNetwork('::255.255.255.0/121'), + IPNetwork('::255.255.255.128/122'), + IPNetwork('::255.255.255.192/123'), + IPNetwork('::255.255.255.224/124'), + IPNetwork('::255.255.255.240/125'), + IPNetwork('::255.255.255.248/126'), + IPNetwork('::255.255.255.252/127'), + IPNetwork('::255.255.255.254/128'), + ] + + # inet_pton has to be different on Mac OSX *sigh*... + assert IPAddress('010.000.000.001', flags=INET_PTON) == IPAddress('10.0.0.1') + # ...but at least Apple changed inet_ntop in Mac OS 10.15 (Catalina) so it's compatible with Linux + if platform.mac_ver()[0] >= '10.15': + assert int_to_str(0xffff) == '::ffff' + else: + assert int_to_str(0xffff) == '::0.0.255.255' + + +@pytest.mark.skipif('sys.platform == "darwin"') +def test_ip_behaviour_non_osx(): + assert iprange_to_cidrs('::1', '::255.255.255.254') == [ + IPNetwork('::1/128'), + IPNetwork('::2/127'), + IPNetwork('::4/126'), + IPNetwork('::8/125'), + IPNetwork('::10/124'), + IPNetwork('::20/123'), + IPNetwork('::40/122'), + IPNetwork('::80/121'), + IPNetwork('::100/120'), + IPNetwork('::200/119'), + IPNetwork('::400/118'), + IPNetwork('::800/117'), + IPNetwork('::1000/116'), + IPNetwork('::2000/115'), + IPNetwork('::4000/114'), + IPNetwork('::8000/113'), + IPNetwork('::0.1.0.0/112'), + IPNetwork('::0.2.0.0/111'), + IPNetwork('::0.4.0.0/110'), + IPNetwork('::0.8.0.0/109'), + IPNetwork('::0.16.0.0/108'), + IPNetwork('::0.32.0.0/107'), + IPNetwork('::0.64.0.0/106'), + IPNetwork('::0.128.0.0/105'), + IPNetwork('::1.0.0.0/104'), + IPNetwork('::2.0.0.0/103'), + IPNetwork('::4.0.0.0/102'), + IPNetwork('::8.0.0.0/101'), + IPNetwork('::16.0.0.0/100'), + IPNetwork('::32.0.0.0/99'), + IPNetwork('::64.0.0.0/98'), + IPNetwork('::128.0.0.0/98'), + IPNetwork('::192.0.0.0/99'), + IPNetwork('::224.0.0.0/100'), + IPNetwork('::240.0.0.0/101'), + IPNetwork('::248.0.0.0/102'), + IPNetwork('::252.0.0.0/103'), + IPNetwork('::254.0.0.0/104'), + IPNetwork('::255.0.0.0/105'), + IPNetwork('::255.128.0.0/106'), + IPNetwork('::255.192.0.0/107'), + IPNetwork('::255.224.0.0/108'), + IPNetwork('::255.240.0.0/109'), + IPNetwork('::255.248.0.0/110'), + IPNetwork('::255.252.0.0/111'), + IPNetwork('::255.254.0.0/112'), + IPNetwork('::255.255.0.0/113'), + IPNetwork('::255.255.128.0/114'), + IPNetwork('::255.255.192.0/115'), + IPNetwork('::255.255.224.0/116'), + IPNetwork('::255.255.240.0/117'), + IPNetwork('::255.255.248.0/118'), + IPNetwork('::255.255.252.0/119'), + IPNetwork('::255.255.254.0/120'), + IPNetwork('::255.255.255.0/121'), + IPNetwork('::255.255.255.128/122'), + IPNetwork('::255.255.255.192/123'), + IPNetwork('::255.255.255.224/124'), + IPNetwork('::255.255.255.240/125'), + IPNetwork('::255.255.255.248/126'), + IPNetwork('::255.255.255.252/127'), + IPNetwork('::255.255.255.254/128'), + ] + + # Sadly, inet_pton cannot help us here ... + with pytest.raises(AddrFormatError): + IPAddress('010.000.000.001', flags=INET_PTON) + + assert int_to_str(0xffff) == '::ffff' diff --git a/netaddr/tests/ip/test_socket_module_fallback.py b/netaddr/tests/ip/test_socket_module_fallback.py new file mode 100644 index 0000000..3174972 --- /dev/null +++ b/netaddr/tests/ip/test_socket_module_fallback.py @@ -0,0 +1,37 @@ +import pytest + +from netaddr.fbsocket import (inet_ntop, inet_pton, inet_ntoa, AF_INET6) + + +@pytest.mark.parametrize(('actual', 'expected'), [ + ('0:0:0:0:0:0:0:0', '::'), + ('0:0:0:0:0:0:0:A', '::a'), + ('A:0:0:0:0:0:0:0', 'a::'), + ('A:0:A:0:0:0:0:0', 'a:0:a::'), + ('A:0:0:0:0:0:0:A', 'a::a'), + ('0:A:0:0:0:0:0:A', '0:a::a'), + ('A:0:A:0:0:0:0:A', 'a:0:a::a'), + ('0:0:0:A:0:0:0:A', '::a:0:0:0:a'), + ('0:0:0:0:A:0:0:A', '::a:0:0:a'), + ('A:0:0:0:0:A:0:A', 'a::a:0:a'), + ('A:0:0:A:0:0:A:0', 'a::a:0:0:a:0'), + ('A:0:A:0:A:0:A:0', 'a:0:a:0:a:0:a:0'), + ('0:A:0:A:0:A:0:A', '0:a:0:a:0:a:0:a'), + ('1080:0:0:0:8:800:200C:417A', '1080::8:800:200c:417a'), + ('FEDC:BA98:7654:3210:FEDC:BA98:7654:3210', 'fedc:ba98:7654:3210:fedc:ba98:7654:3210'), +]) +def test_inet_ntop_and_inet_pton_ipv6_conversion(actual, expected): + assert inet_ntop(AF_INET6, inet_pton(AF_INET6, actual)) == expected + + +def test_inet_ntoa_ipv4_exceptions(): + with pytest.raises(TypeError): + inet_ntoa(1) + + with pytest.raises(ValueError): + inet_ntoa('\x00') + + +def test_inet_pton_ipv6_exceptions(): + with pytest.raises(ValueError): + inet_pton(AF_INET6, '::0x07f') diff --git a/netaddr/tests/strategy/__init__.py b/netaddr/tests/strategy/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/netaddr/tests/strategy/__init__.py diff --git a/netaddr/tests/strategy/test_eui48_strategy.py b/netaddr/tests/strategy/test_eui48_strategy.py new file mode 100644 index 0000000..adea7a9 --- /dev/null +++ b/netaddr/tests/strategy/test_eui48_strategy.py @@ -0,0 +1,58 @@ +import sys + +import pytest + +from netaddr.strategy import eui48 + + +def test_strategy_eui48(): + b = '00000000-00001111-00011111-00010010-11100111-00110011' + i = 64945841971 + t = (0x0, 0x0f, 0x1f, 0x12, 0xe7, 0x33) + s = '00-0F-1F-12-E7-33' + + assert eui48.bits_to_int(b) == i + assert eui48.int_to_bits(i) == b + + assert eui48.int_to_str(i) == s + assert eui48.str_to_int(s) == i + + assert eui48.int_to_words(i) == t + assert eui48.words_to_int(t) == i + assert eui48.words_to_int(list(t)) == i + + +@pytest.mark.skipif(sys.version_info > (3,), reason="requires python 2.x") +def test_strategy_eui48_py2(): + i = 64945841971 + p = '\x00\x0f\x1f\x12\xe73' + assert eui48.int_to_packed(i) == p + assert eui48.packed_to_int(p) == i + + +@pytest.mark.skipif(sys.version_info < (3,), reason="requires python 3.x") +def test_strategy_eui48_py3(): + i = 64945841971 + p = b'\x00\x0f\x1f\x12\xe73' + assert eui48.int_to_packed(i) == p + assert eui48.packed_to_int(p) == i + + +def test_strategy_eui48_alternate_dialect(): + b = '00000000:00001111:00011111:00010010:11100111:00110011' + i = 64945841971 + t = (0x0, 0x0f, 0x1f, 0x12, 0xe7, 0x33) + s = '0:f:1f:12:e7:33' + + assert eui48.bits_to_int(b, eui48.mac_unix) == i + assert eui48.int_to_bits(i, eui48.mac_unix) == b + + assert eui48.int_to_str(i, eui48.mac_unix) == s + assert eui48.int_to_str(i, eui48.mac_cisco) == '000f.1f12.e733' + assert eui48.int_to_str(i, eui48.mac_unix) == '0:f:1f:12:e7:33' + assert eui48.int_to_str(i, eui48.mac_unix_expanded) == '00:0f:1f:12:e7:33' + assert eui48.str_to_int(s) == i + + assert eui48.int_to_words(i, eui48.mac_unix) == t + assert eui48.words_to_int(t, eui48.mac_unix) == i + assert eui48.words_to_int(list(t), eui48.mac_unix) == i diff --git a/netaddr/tests/strategy/test_ipv4_strategy.py b/netaddr/tests/strategy/test_ipv4_strategy.py new file mode 100644 index 0000000..607d816 --- /dev/null +++ b/netaddr/tests/strategy/test_ipv4_strategy.py @@ -0,0 +1,83 @@ +import sys + +import pytest + +from netaddr import INET_PTON, AddrFormatError +from netaddr.strategy import ipv4 + + +def test_strategy_ipv4(): + b = '11000000.00000000.00000010.00000001' + i = 3221225985 + t = (192, 0, 2, 1) + s = '192.0.2.1' + bin_val = '0b11000000000000000000001000000001' + + assert ipv4.bits_to_int(b) == i + assert ipv4.int_to_bits(i) == b + assert ipv4.int_to_str(i) == s + assert ipv4.int_to_words(i) == t + assert ipv4.int_to_bin(i) == bin_val + assert ipv4.int_to_bin(i) == bin_val + assert ipv4.bin_to_int(bin_val) == i + assert ipv4.words_to_int(t) == i + assert ipv4.words_to_int(list(t)) == i + assert ipv4.valid_bin(bin_val) + + +@pytest.mark.skipif(sys.version_info > (3,), reason="requires python 2.x") +def test_strategy_ipv4_py2(): + i = 3221225985 + p = '\xc0\x00\x02\x01' + assert ipv4.int_to_packed(i) == p + assert ipv4.packed_to_int(p) == i + + +@pytest.mark.skipif(sys.version_info < (3,), reason="requires python 3.x") +def test_strategy_ipv4_py3(): + i = 3221225985 + p = b'\xc0\x00\x02\x01' + assert ipv4.int_to_packed(i) == p + assert ipv4.packed_to_int(p) == i + + +def test_strategy_inet_aton_behaviour(): + # inet_aton() is a very old system call and is very permissive with + # regard to what is assume is a valid IPv4 address. Unfortunately, it + # is also the most widely used by system software used in software today, + # so netaddr supports this behaviour by default. + + assert ipv4.str_to_int('127') == 127 + assert ipv4.str_to_int('0x7f') == 127 + assert ipv4.str_to_int('0177') == 127 + assert ipv4.str_to_int('127.1') == 2130706433 + assert ipv4.str_to_int('0x7f.1') == 2130706433 + assert ipv4.str_to_int('0177.1') == 2130706433 + assert ipv4.str_to_int('127.0.0.1') == 2130706433 + + +def test_strategy_inet_pton_behaviour(): + # inet_pton() is a newer system call that supports both IPv4 and IPv6. + # It is a lot more strict about what it deems to be a valid IPv4 address + # and doesn't support many of the features found in inet_aton() such as + # support for non- decimal octets, partial numbers of octets, etc. + + with pytest.raises(AddrFormatError): + ipv4.str_to_int('127', flags=INET_PTON) + + with pytest.raises(AddrFormatError): + ipv4.str_to_int('0x7f', flags=INET_PTON) + + with pytest.raises(AddrFormatError): + ipv4.str_to_int('0177', flags=INET_PTON) + + with pytest.raises(AddrFormatError): + ipv4.str_to_int('127.1', flags=INET_PTON) + + with pytest.raises(AddrFormatError): + ipv4.str_to_int('0x7f.1', flags=INET_PTON) + + with pytest.raises(AddrFormatError): + ipv4.str_to_int('0177.1', flags=INET_PTON) + + assert ipv4.str_to_int('127.0.0.1', flags=INET_PTON) == 2130706433 diff --git a/netaddr/tests/strategy/test_ipv6_strategy.py b/netaddr/tests/strategy/test_ipv6_strategy.py new file mode 100644 index 0000000..34c278e --- /dev/null +++ b/netaddr/tests/strategy/test_ipv6_strategy.py @@ -0,0 +1,169 @@ +import platform +import sys + +import pytest + +from netaddr import AddrFormatError +from netaddr.strategy import ipv6 + + +def test_strategy_ipv6(): + b = '0000000000000000:0000000000000000:0000000000000000:0000000000000000:0000000000000000:0000000000000000:1111111111111111:1111111111111110' + i = 4294967294 + t = (0, 0, 0, 0, 0, 0, 0xffff, 0xfffe) + s = '::255.255.255.254' + + assert ipv6.bits_to_int(b) == i + assert ipv6.int_to_bits(i) == b + + assert ipv6.int_to_str(i) == s + assert ipv6.str_to_int(s) == i + + assert ipv6.int_to_words(i) == t + assert ipv6.words_to_int(t) == i + assert ipv6.words_to_int(list(t)) == i + + +@pytest.mark.skipif(sys.version_info > (3,), reason="requires python 2.x") +def test_strategy_ipv6_py2(): + i = 4294967294 + p = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xfe' + assert ipv6.int_to_packed(i) == p + assert ipv6.packed_to_int(p) == 4294967294 + + +@pytest.mark.skipif(sys.version_info < (3,), reason="requires python 3.x") +def test_strategy_ipv6_py3(): + i = 4294967294 + p = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xfe' + assert ipv6.int_to_packed(i) == p + assert ipv6.packed_to_int(p) == 4294967294 + + +@pytest.mark.parametrize('str_value', ( + '2001:0db8:0000:0000:0000:0000:1428:57ab', + '2001:0db8:0000:0000:0000::1428:57ab', + '2001:0db8:0:0:0:0:1428:57ab', + '2001:0db8:0:0::1428:57ab', + '2001:0db8::1428:57ab', + '2001:0DB8:0000:0000:0000:0000:1428:57AB', + '2001:DB8::1428:57AB', +)) +def test_strategy_ipv6_equivalent_variants(str_value): + assert ipv6.str_to_int(str_value) == 42540766411282592856903984951992014763 + + +@pytest.mark.parametrize('str_value', ( + # Long forms. + 'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210', + '1080:0:0:0:8:800:200C:417A', # a unicast address + 'FF01:0:0:0:0:0:0:43', # a multicast address + '0:0:0:0:0:0:0:1', # the loopback address + '0:0:0:0:0:0:0:0', # the unspecified addresses + + # Short forms. + '1080::8:800:200C:417A', # a unicast address + 'FF01::43', # a multicast address + '::1', # the loopback address + '::', # the unspecified addresses + + # IPv4 compatible forms. + '::192.0.2.1', + '::ffff:192.0.2.1', + '0:0:0:0:0:0:192.0.2.1', + '0:0:0:0:0:FFFF:192.0.2.1', + '0:0:0:0:0:0:13.1.68.3', + '0:0:0:0:0:FFFF:129.144.52.38', + '::13.1.68.3', + '::FFFF:129.144.52.38', + + # Other tests. + '1::', + '::ffff', + 'ffff::', + 'ffff::ffff', + '0:1:2:3:4:5:6:7', + '8:9:a:b:c:d:e:f', + '0:0:0:0:0:0:0:0', + 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', +)) +def test_strategy_ipv6_valid_str(str_value): + assert ipv6.valid_str(str_value) + + +@pytest.mark.parametrize('str_value', ( + 'g:h:i:j:k:l:m:n', # bad chars. + '0:0:0:0:0:0:0:0:0', # too long, + # Unexpected types. + [], + (), + {}, + True, + False, +)) +def test_strategy_ipv6_is_not_valid_str(str_value): + assert not ipv6.valid_str(str_value) + + +def test_strategy_ipv6_valid_str_exception_on_empty_string(): + with pytest.raises(AddrFormatError): + ipv6.valid_str('') + + +@pytest.mark.parametrize(('long_form', 'short_form'), ( + ('FEDC:BA98:7654:3210:FEDC:BA98:7654:3210', 'fedc:ba98:7654:3210:fedc:ba98:7654:3210'), + ('1080:0:0:0:8:800:200C:417A', '1080::8:800:200c:417a'), # unicast address + ('FF01:0:0:0:0:0:0:43', 'ff01::43'), # multicast address + ('0:0:0:0:0:0:0:1', '::1'), # loopback address + ('0:0:0:0:0:0:0:0', '::'), # unspecified addresses +)) +def test_strategy_ipv6_string_compaction(long_form, short_form): + int_val = ipv6.str_to_int(long_form) + calc_short_form = ipv6.int_to_str(int_val) + assert calc_short_form == short_form + + +def test_strategy_ipv6_mapped_and_compatible_ipv4_string_formatting(): + assert ipv6.int_to_str(0xffffff) == '::0.255.255.255' + assert ipv6.int_to_str(0xffffffff) == '::255.255.255.255' + assert ipv6.int_to_str(0x1ffffffff) == '::1:ffff:ffff' + assert ipv6.int_to_str(0xffffffffffff) == '::ffff:255.255.255.255' + assert ipv6.int_to_str(0xfffeffffffff) == '::fffe:ffff:ffff' + assert ipv6.int_to_str(0xffffffffffff) == '::ffff:255.255.255.255' + assert ipv6.int_to_str(0xfffffffffff1) == '::ffff:255.255.255.241' + assert ipv6.int_to_str(0xfffffffffffe) == '::ffff:255.255.255.254' + assert ipv6.int_to_str(0xffffffffff00) == '::ffff:255.255.255.0' + assert ipv6.int_to_str(0xffffffff0000) == '::ffff:255.255.0.0' + assert ipv6.int_to_str(0xffffff000000) == '::ffff:255.0.0.0' + assert ipv6.int_to_str(0xffff000000) == '::ff:ff00:0' + assert ipv6.int_to_str(0x1ffff00000000) == '::1:ffff:0:0' + # So this is strange. Even though on Windows we get decimal notation in a lot of the addresses above, + # in case of 0.0.0.0 we get hex instead, unless it's Python 2, then we get decimal... unless it's + # actually PyPy Python 2, then we always get hex (again, only on Windows). Worth investigating, putting + # the conditional assert here for now to make this visible. + if platform.system() == 'Windows' and ( + platform.python_version() >= '3.0' or platform.python_implementation() == 'PyPy' + ): + assert ipv6.int_to_str(0xffff00000000) == '::ffff:0:0' + else: + assert ipv6.int_to_str(0xffff00000000) == '::ffff:0.0.0.0' + + +def test_strategy_ipv6_str_to_int_behaviour_legacy_mode(): + assert ipv6.str_to_int('::127') == 295 + + with pytest.raises(AddrFormatError): + ipv6.str_to_int('::0x7f') + + assert ipv6.str_to_int('::0177') == 375 + + with pytest.raises(AddrFormatError): + ipv6.str_to_int('::127.1') + + with pytest.raises(AddrFormatError): + ipv6.str_to_int('::0x7f.1') + + with pytest.raises(AddrFormatError): + ipv6.str_to_int('::0177.1') + + assert ipv6.str_to_int('::127.0.0.1') == 2130706433 diff --git a/netaddr/tests/test_netaddr.py b/netaddr/tests/test_netaddr.py new file mode 100644 index 0000000..ee7ecd8 --- /dev/null +++ b/netaddr/tests/test_netaddr.py @@ -0,0 +1,11 @@ +from netaddr import valid_mac, valid_eui64 + + +def test_valid_mac(): + assert valid_mac('00-B0-D0-86-BB-F7') + assert not valid_mac('00-1B-77-49-54-FD-12-34') + + +def test_valid_eui64(): + assert valid_eui64('00-1B-77-49-54-FD-12-34') + assert not valid_eui64('00-B0-D0-86-BB-F7') |