summaryrefslogtreecommitdiffstats
path: root/netaddr/tests/ip
diff options
context:
space:
mode:
Diffstat (limited to 'netaddr/tests/ip')
-rw-r--r--netaddr/tests/ip/__init__.py0
-rw-r--r--netaddr/tests/ip/test_cidr_v4.py195
-rw-r--r--netaddr/tests/ip/test_cidr_v6.py106
-rw-r--r--netaddr/tests/ip/test_dns.py11
-rw-r--r--netaddr/tests/ip/test_ip.py19
-rw-r--r--netaddr/tests/ip/test_ip_categories.py112
-rw-r--r--netaddr/tests/ip/test_ip_comparisons.py38
-rw-r--r--netaddr/tests/ip/test_ip_globs.py50
-rw-r--r--netaddr/tests/ip/test_ip_network_categories.py26
-rw-r--r--netaddr/tests/ip/test_ip_ranges.py278
-rw-r--r--netaddr/tests/ip/test_ip_rfc1924.py17
-rw-r--r--netaddr/tests/ip/test_ip_sets.py581
-rw-r--r--netaddr/tests/ip/test_ip_splitter.py74
-rw-r--r--netaddr/tests/ip/test_ip_v4.py547
-rw-r--r--netaddr/tests/ip/test_ip_v4_v6_conversions.py33
-rw-r--r--netaddr/tests/ip/test_ip_v6.py168
-rw-r--r--netaddr/tests/ip/test_network_ops.py81
-rw-r--r--netaddr/tests/ip/test_nmap.py86
-rw-r--r--netaddr/tests/ip/test_old_specs.py281
-rw-r--r--netaddr/tests/ip/test_platform_osx.py156
-rw-r--r--netaddr/tests/ip/test_socket_module_fallback.py37
21 files changed, 2896 insertions, 0 deletions
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')