diff options
author | Brian Christopher Raaen <brian@brianraaen.com> | 2015-09-02 15:51:29 -0400 |
---|---|---|
committer | Brian Christopher Raaen <brian@brianraaen.com> | 2015-09-02 15:51:29 -0400 |
commit | 8b51cbe99c6f40aa51ed66f422eaba098711ba7c (patch) | |
tree | 36d4628d5cb4db18b063ab6bf7bf52c1a5ded7da | |
parent | c7f1f4607d2c37e56937f4889c1481e245139714 (diff) | |
parent | 9f2c51a1a6231b3c8ce82f18e06ed563ab13ee9b (diff) | |
download | netaddr-8b51cbe99c6f40aa51ed66f422eaba098711ba7c.tar.gz |
Merge https://github.com/drkjam/netaddr into rel-0.7.x
-rw-r--r-- | CHANGELOG | 11 | ||||
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | docs/source/changes.rst | 2 | ||||
-rw-r--r-- | docs/source/conf.py | 4 | ||||
-rw-r--r-- | docs/source/index.rst | 2 | ||||
-rw-r--r-- | netaddr/__init__.py | 5 | ||||
-rw-r--r-- | netaddr/eui/__init__.py | 6 | ||||
-rw-r--r-- | netaddr/ip/__init__.py | 46 | ||||
-rwxr-xr-x | netaddr/ip/iana.py | 123 | ||||
-rw-r--r-- | netaddr/ip/ipv6-unicast-address-assignments.xml | 446 | ||||
-rw-r--r-- | netaddr/strategy/eui48.py | 3 | ||||
-rw-r--r-- | netaddr/strategy/eui64.py | 167 | ||||
-rw-r--r-- | netaddr/tests/eui/test_eui.py | 37 | ||||
-rw-r--r-- | netaddr/tests/ip/test_ip_categories.py | 12 | ||||
-rw-r--r-- | netaddr/tests/ip/test_ip_ranges.py | 2 | ||||
-rw-r--r-- | netaddr/tests/ip/test_ip_v6.py | 13 | ||||
-rw-r--r-- | netaddr/tests/test_netaddr.py | 11 | ||||
-rw-r--r-- | release.py | 11 | ||||
-rw-r--r-- | tutorials/2.x/ip/tutorial.txt | 2 |
19 files changed, 764 insertions, 142 deletions
@@ -1,7 +1,7 @@ --------------- Release: 0.7.16 --------------- -Date: ?????? 2015 +Date: 30 Aug 2015 ^^^^^^^^^^^^^^^^^^^^ Changes since 0.7.15 @@ -14,9 +14,18 @@ Changes since 0.7.15 Specific bug fixes addressed in this release ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +FIXED Issue 109: https://github.com/drkjam/netaddr/issues/109 + - Identify registry of global IPv6 unicast allocations + FIXED Issue 108: https://github.com/drkjam/netaddr/issues/108 - One part of docs unclear? +FIXED Issue 106: https://github.com/drkjam/netaddr/issues/106 + - Eui64 Updated (pull request for Issue 105) + +FIXED Issue 105: https://github.com/drkjam/netaddr/issues/105 + - Support dialects for EUI-64 addresses + FIXED Issue 102: https://github.com/drkjam/netaddr/issues/102 - 0.7.15 tarball is missing tests. @@ -51,6 +51,7 @@ download: cd netaddr/ip/ && wget -N http://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xml cd netaddr/ip/ && wget -N http://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xml cd netaddr/ip/ && wget -N http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xml + cd netaddr/ip/ && wget -N http://www.iana.org/assignments/ipv6-unicast-address-assignments/ipv6-unicast-address-assignments.xml register: @echo 'releasing netaddr' @@ -60,7 +61,7 @@ push_tags: @echo 'syncing tags' git push --tags -test: +test: clean @echo 'running test suite' python setup.py test @echo 'running doc tests (tutorials)' diff --git a/docs/source/changes.rst b/docs/source/changes.rst index 075be18..cb4d8f6 100644 --- a/docs/source/changes.rst +++ b/docs/source/changes.rst @@ -1,5 +1,5 @@ ============================ -What's new in netaddr 0.7.15 +What's new in netaddr 0.7.16 ============================ .. include:: ../../CHANGELOG diff --git a/docs/source/conf.py b/docs/source/conf.py index 66a4c71..83cddf8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -48,9 +48,9 @@ copyright = u'2008-2015, David P. D. Moss. All rights reserved' # built documents. # # The short X.Y version. -version = '0.7.15' +version = '0.7.16' # The full version, including alpha/beta/rc tags. -release = '0.7.15' +release = '0.7.16' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/index.rst b/docs/source/index.rst index 4b82de6..855806a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,5 +1,5 @@ ============================ -netaddr 0.7.15 documentation +netaddr 0.7.16 documentation ============================ .. toctree:: diff --git a/netaddr/__init__.py b/netaddr/__init__.py index 504535f..a707d97 100644 --- a/netaddr/__init__.py +++ b/netaddr/__init__.py @@ -6,7 +6,7 @@ """A Python library for manipulating IP and EUI network addresses.""" #: Version info (major, minor, maintenance, status) -VERSION = (0, 7, 15) +VERSION = (0, 7, 16) STATUS = '' __version__ = '%d.%d.%d' % VERSION[0:3] + STATUS @@ -42,6 +42,9 @@ from netaddr.strategy.ipv6 import (valid_str as valid_ipv6, ipv6_compact, from netaddr.strategy.eui48 import (mac_eui48, mac_unix, mac_unix_expanded, mac_cisco, mac_bare, mac_pgsql, valid_str as valid_mac) +from netaddr.strategy.eui64 import (eui64_base, eui64_unix, eui64_unix_expanded, + eui64_cisco, eui64_bare, valid_str as valid_eui64) + __all__ = [ # Constants. 'ZEROFILL', 'Z', 'INET_PTON', 'P', 'NOHOST', 'N', diff --git a/netaddr/eui/__init__.py b/netaddr/eui/__init__.py index 3affc21..11a050b 100644 --- a/netaddr/eui/__init__.py +++ b/netaddr/eui/__init__.py @@ -11,6 +11,7 @@ identifiers. from netaddr.core import NotRegisteredError, AddrFormatError, DictDotLookup from netaddr.strategy import eui48 as _eui48, eui64 as _eui64 from netaddr.strategy.eui48 import mac_eui48 +from netaddr.strategy.eui64 import eui64_base from netaddr.ip import IPAddress from netaddr.compat import _is_int, _is_str @@ -456,7 +457,10 @@ class EUI(BaseIdentifier): def _set_dialect(self, value): if value is None: - self._dialect = mac_eui48 + if self._module is _eui64: + self._dialect = eui64_base + else: + self._dialect = mac_eui48 else: if hasattr(value, 'word_size') and hasattr(value, 'word_fmt'): self._dialect = value diff --git a/netaddr/ip/__init__.py b/netaddr/ip/__init__.py index d8f001e..0141b26 100644 --- a/netaddr/ip/__init__.py +++ b/netaddr/ip/__init__.py @@ -1548,17 +1548,15 @@ def cidr_merge(ip_addrs): for ip in ip_addrs: cidr = IPNetwork(ip) # Since non-overlapping ranges are the common case, remember the original - ranges.append( (cidr.version, cidr.first, cidr.last, cidr) ) + ranges.append( (cidr.version, cidr.last, cidr.first, cidr) ) ranges.sort() - i = 1 - while i < len(ranges): - if ranges[i][0] == ranges[i - 1][0] and ranges[i][1] - 1 <= ranges[i - 1][2]: - ranges[i - 1] = (ranges[i][0], ranges[i - 1][1], max(ranges[i - 1][2], ranges[i][2])) + i = len(ranges) - 1 + while i > 0: + if ranges[i][0] == ranges[i - 1][0] and ranges[i][2] - 1 <= ranges[i - 1][1]: + ranges[i - 1] = (ranges[i][0], ranges[i][1], min(ranges[i - 1][2], ranges[i][2])) del ranges[i] - else: - i += 1 - + i -= 1 merged = [] for range_tuple in ranges: # If this range wasn't merged we can simply use the old cidr. @@ -1566,8 +1564,8 @@ def cidr_merge(ip_addrs): merged.append(range_tuple[3]) else: version = range_tuple[0] - range_start = IPAddress(range_tuple[1], version=version) - range_stop = IPAddress(range_tuple[2], version=version) + range_start = IPAddress(range_tuple[2], version=version) + range_stop = IPAddress(range_tuple[1], version=version) merged.extend(iprange_to_cidrs(range_start, range_stop)) return merged @@ -1850,14 +1848,15 @@ def all_matching_cidrs(ip, cidrs): #----------------------------------------------------------------------------- # Cached IPv4 address range lookups. #----------------------------------------------------------------------------- -IPV4_LOOPBACK = IPNetwork('127.0.0.0/8') +IPV4_LOOPBACK = IPNetwork('127.0.0.0/8') # Loopback addresses (RFC 990) IPV4_PRIVATE = ( - IPNetwork('10.0.0.0/8'), # Private-Use Networks - IPNetwork('100.64.0.0/10'), # Shared address space - IPNetwork('172.16.0.0/12'), # Private-Use Networks - IPNetwork('192.0.2.0/24'), # Test-Net - IPNetwork('192.168.0.0/16'), # Private-Use Networks + IPNetwork('10.0.0.0/8'), # Class A private network local communication (RFC 1918) + IPNetwork('100.64.0.0/10'), # Carrier grade NAT (RFC 6598) + IPNetwork('172.16.0.0/12'), # Private network - local communication (RFC 1918) + IPNetwork('192.0.0.0/24'), # IANA IPv4 Special Purpose Address Registry (RFC 5736) + IPNetwork('192.168.0.0/16'), # Class B private network local communication (RFC 1918) + IPNetwork('198.18.0.0/15'), # Testing of inter-network communications between subnets (RFC 2544) IPRange('239.0.0.0', '239.255.255.255'), # Administrative Multicast ) @@ -1865,19 +1864,20 @@ IPV4_LINK_LOCAL = IPNetwork('169.254.0.0/16') IPV4_MULTICAST = IPNetwork('224.0.0.0/4') -IPV4_6TO4 = IPNetwork('192.88.99.0/24') # 6to4 Relay Anycast +IPV4_6TO4 = IPNetwork('192.88.99.0/24') # 6to4 anycast relays (RFC 3068) IPV4_RESERVED = ( - IPNetwork('192.0.0.0/24'), # Reserved but subject to allocation - IPNetwork('240.0.0.0/4'), # Reserved for Future Use - IPNetwork('198.18.0.0/15'), # Benchmarking - IPNetwork('198.51.100.0/24'), # Examples for documentation - IPNetwork('203.0.113.0/24'), # Examples for documentation + IPNetwork('0.0.0.0/8'), # Broadcast message (RFC 1700) + IPNetwork('192.0.2.0/24'), # TEST-NET examples and documentation (RFC 5737) + IPNetwork('240.0.0.0/4'), # Reserved for multicast assignments (RFC 5771) + IPNetwork('198.51.100.0/24'), # TEST-NET-2 examples and documentation (RFC 5737) + IPNetwork('203.0.113.0/24'), # TEST-NET-3 examples and documentation (RFC 5737) # Reserved multicast + IPNetwork('233.252.0.0/24'), # Multicast test network IPRange('234.0.0.0', '238.255.255.255'), IPRange('225.0.0.0', '231.255.255.255'), -) +) + (IPV4_LOOPBACK, IPV4_6TO4) #----------------------------------------------------------------------------- # Cached IPv6 address range lookups. diff --git a/netaddr/ip/iana.py b/netaddr/ip/iana.py index 7e63ac1..6338ec2 100755 --- a/netaddr/ip/iana.py +++ b/netaddr/ip/iana.py @@ -29,22 +29,21 @@ More details can be found at the following URLs :- - IEEE Protocols Information Home Page - http://www.iana.org/protocols/ """ -import os as _os import os.path as _path import sys as _sys - from xml.sax import make_parser, handler -from netaddr.core import Publisher, Subscriber, dos2unix +from netaddr.core import Publisher, Subscriber from netaddr.ip import IPAddress, IPNetwork, IPRange, cidr_abbrev_to_verbose - from netaddr.compat import _dict_items, _callable + #: Topic based lookup dictionary for IANA information. IANA_INFO = { 'IPv4': {}, 'IPv6': {}, + 'IPv6_unicast': {}, 'multicast': {}, } @@ -221,6 +220,42 @@ class IPv6Parser(XMLRecordParser): return record +class IPv6UnicastParser(XMLRecordParser): + """ + A XMLRecordParser that understands how to parse and retrieve data records + from the IANA IPv6 unicast address assignments file. + + It can be found online here :- + + - http://www.iana.org/assignments/ipv6-unicast-address-assignments/ipv6-unicast-address-assignments.xml + """ + def __init__(self, fh, **kwargs): + """ + Constructor. + + fh - a valid, open file handle to an IANA IPv6 address space file. + + kwargs - additional parser options. + """ + super(IPv6UnicastParser, self).__init__(fh) + + def process_record(self, rec): + """ + Callback method invoked for every record. + + See base class method for more details. + """ + record = { + 'status': str(rec.get('status', '')).strip(), + 'description': str(rec.get('description', '')).strip(), + 'prefix': str(rec.get('prefix', '')).strip(), + 'date': str(rec.get('date', '')).strip(), + 'whois': str(rec.get('whois', '')).strip(), + } + + return record + + class MulticastParser(XMLRecordParser): """ A XMLRecordParser that knows how to process the IANA IPv4 multicast address @@ -305,6 +340,9 @@ class DictUpdater(Subscriber): elif self.topic == 'IPv6': cidr = IPNetwork(cidr_abbrev_to_verbose(data_id)) self.dct[cidr] = data + elif self.topic == 'IPv6_unicast': + cidr = IPNetwork(data_id) + self.dct[cidr] = data elif self.topic == 'multicast': iprange = None if '-' in data_id: @@ -334,6 +372,10 @@ def load_info(): ipv6.attach(DictUpdater(IANA_INFO['IPv6'], 'IPv6', 'prefix')) ipv6.parse() + ipv6ua = IPv6UnicastParser(open(_path.join(PATH, 'ipv6-unicast-address-assignments.xml'))) + ipv6ua.attach(DictUpdater(IANA_INFO['IPv6_unicast'], 'IPv6_unicast', 'prefix')) + ipv6ua.parse() + mcast = MulticastParser(open(_path.join(PATH, 'multicast-addresses.xml'))) mcast.attach(DictUpdater(IANA_INFO['multicast'], 'multicast', 'address')) mcast.parse() @@ -356,77 +398,46 @@ def pprint_info(fh=None): fh.write('%-45r' % (iprange) + details + "\n") -def query(ip_addr): - """ - Returns informational data specific to this IP address. - """ - info = {} +def _within_bounds(ip, ip_range): + # Boundary checking for multiple IP classes. + if hasattr(ip_range, 'first'): + # IP network or IP range. + return ip in ip_range + elif hasattr(ip_range, 'value'): + # IP address. + return ip == ip_range + + raise Exception('Unsupported IP range or address: %r!' % ip_range) - def within_bounds(ip, ip_range): - # Boundary checking for multiple IP classes. - if hasattr(ip_range, 'first'): - # IP network or IP range. - return ip in ip_range - elif hasattr(ip_range, 'value'): - # IP address. - return ip == ip_range - raise Exception('Unsupported IP range or address: %r!' % ip_range) +def query(ip_addr): + """Returns informational data specific to this IP address.""" + info = {} if ip_addr.version == 4: for cidr, record in _dict_items(IANA_INFO['IPv4']): - if within_bounds(ip_addr, cidr): + if _within_bounds(ip_addr, cidr): info.setdefault('IPv4', []) info['IPv4'].append(record) if ip_addr.is_multicast(): for iprange, record in _dict_items(IANA_INFO['multicast']): - if within_bounds(ip_addr, iprange): + if _within_bounds(ip_addr, iprange): info.setdefault('Multicast', []) info['Multicast'].append(record) elif ip_addr.version == 6: for cidr, record in _dict_items(IANA_INFO['IPv6']): - if within_bounds(ip_addr, cidr): + if _within_bounds(ip_addr, cidr): info.setdefault('IPv6', []) info['IPv6'].append(record) - return info + for cidr, record in _dict_items(IANA_INFO['IPv6_unicast']): + if _within_bounds(ip_addr, cidr): + info.setdefault('IPv6_unicast', []) + info['IPv6_unicast'].append(record) - -def get_latest_files(): - """Download the latest files from IANA""" - if _sys.version_info[0] == 3: - # Python 3.x - from urllib.request import Request, urlopen - else: - # Python 2.x - from urllib2 import Request, urlopen - - urls = [ - 'http://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xml', - 'http://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xml', - 'http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xml', - ] - - for url in urls: - _sys.stdout.write('downloading latest copy of %s\n' % url) - request = Request(url) - response = urlopen(request) - save_path = _path.dirname(__file__) - basename = _os.path.basename(response.geturl().rstrip('/')) - filename = _path.join(save_path, basename) - fh = open(filename, 'wb') - fh.write(response.read()) - fh.close() - - # Make sure the line endings are consistent across platforms. - dos2unix(filename) - - -if __name__ == '__main__': - # Generate indices when module is executed as a script. - get_latest_files() + return info # On module import, read IANA data files and populate lookups dict. load_info() diff --git a/netaddr/ip/ipv6-unicast-address-assignments.xml b/netaddr/ip/ipv6-unicast-address-assignments.xml new file mode 100644 index 0000000..4f62e9d --- /dev/null +++ b/netaddr/ip/ipv6-unicast-address-assignments.xml @@ -0,0 +1,446 @@ +<?xml version='1.0' encoding='UTF-8'?> +<?xml-stylesheet type="text/xsl" href="ipv6-unicast-address-assignments.xsl"?> +<?oxygen RNGSchema="ipv6-unicast-address-assignments.rng" type="xml"?> +<registry xmlns="http://www.iana.org/assignments" id="ipv6-unicast-address-assignments"> + <title>IPv6 Global Unicast Address Assignments</title> + <category>Internet Protocol version 6 (IPv6) Global Unicast Allocations</category> + <updated>2015-08-10</updated> + <xref type="rfc" data="rfc7249"/> + <registration_rule>Allocations to RIRs are made in line with the Global Policy published at <xref type="uri" data="http://www.icann.org/en/resources/policy/global-addressing"/>. +All other assignments require IETF Review.</registration_rule> + <description>The allocation of Internet Protocol version 6 (IPv6) unicast address space is listed +here. References to the various other registries detailing the use of the IPv6 address +space can be found in the <xref type="registry" data="ipv6-address-space">IPv6 Address Space registry</xref>.</description> + <note>The assignable Global Unicast Address space is defined in <xref type="rfc" data="rfc4291"/> as being the address +block defined by the prefix 2000::/3. All address space in this block not listed in the +table below is reserved by IANA for future allocation.</note> + <record date="1999-07-01"> + <prefix>2001:0000::/23</prefix> + <description>IANA</description> + <whois>whois.iana.org</whois> + <status>ALLOCATED</status> + <notes>2001:0000::/23 is reserved for IETF Protocol Assignments <xref type="rfc" data="rfc2928"/>. +2001:0000::/32 is reserved for TEREDO <xref type="rfc" data="rfc4380"/>. +2001:0002::/48 is reserved for Benchmarking <xref type="rfc" data="rfc5180"/>. +2001:10::/28 is reserved for ORCHID <xref type="rfc" data="rfc4843"/>. +For complete registration details, see <xref type="registry" data="iana-ipv6-special-registry"/>.</notes> + </record> + <record date="1999-07-01"> + <prefix>2001:0000::/23</prefix> + <description>APNIC</description> + <whois>whois.apnic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.apnic.net/</server> + </rdap> + <notes/> + </record> + <record date="1999-07-01"> + <prefix>2001:0400::/23</prefix> + <description>ARIN</description> + <whois>whois.arin.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.arin.net/registry</server> + <server>http://rdap.arin.net/registry</server> + </rdap> + <notes/> + </record> + <record date="1999-07-01"> + <prefix>2001:0600::/23</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2002-05-02"> + <prefix>2001:0800::/23</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2002-11-02"> + <prefix>2001:0a00::/23</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2002-05-02"> + <prefix>2001:0c00::/23</prefix> + <description>APNIC</description> + <whois>whois.apnic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.apnic.net/</server> + </rdap> + <notes>2001:db8::/32 reserved for Documentation <xref type="rfc" data="rfc3849"/>. +For complete registration details, see <xref type="registry" data="iana-ipv6-special-registry"/>.</notes> + </record> + <record date="2003-01-01"> + <prefix>2001:0e00::/23</prefix> + <description>APNIC</description> + <whois>whois.apnic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.apnic.net/</server> + </rdap> + <notes/> + </record> + <record date="2002-11-01"> + <prefix>2001:1200::/23</prefix> + <description>LACNIC</description> + <whois>whois.lacnic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.lacnic.net/rdap/</server> + </rdap> + <notes/> + </record> + <record date="2003-02-01"> + <prefix>2001:1400::/23</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2003-07-01"> + <prefix>2001:1600::/23</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2003-04-01"> + <prefix>2001:1800::/23</prefix> + <description>ARIN</description> + <whois>whois.arin.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.arin.net/registry</server> + <server>http://rdap.arin.net/registry</server> + </rdap> + <notes/> + </record> + <record date="2004-01-01"> + <prefix>2001:1a00::/23</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-05-04"> + <prefix>2001:1c00::/22</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-05-04"> + <prefix>2001:2000::/20</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-05-04"> + <prefix>2001:3000::/21</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-05-04"> + <prefix>2001:3800::/22</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record> + <prefix>2001:3c00::/22</prefix> + <description>IANA</description> + <whois/> + <status>RESERVED</status> + <notes>2001:3c00::/22 is reserved for possible future allocation to the RIPE NCC.</notes> + </record> + <record date="2004-06-11"> + <prefix>2001:4000::/23</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-06-01"> + <prefix>2001:4200::/23</prefix> + <description>AFRINIC</description> + <whois>whois.afrinic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.afrinic.net/rdap/</server> + <server>http://rdap.afrinic.net/rdap/</server> + </rdap> + <notes/> + </record> + <record date="2004-06-11"> + <prefix>2001:4400::/23</prefix> + <description>APNIC</description> + <whois>whois.apnic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.apnic.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-08-17"> + <prefix>2001:4600::/23</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-08-24"> + <prefix>2001:4800::/23</prefix> + <description>ARIN</description> + <whois>whois.arin.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.arin.net/registry</server> + <server>http://rdap.arin.net/registry</server> + </rdap> + <notes/> + </record> + <record date="2004-10-15"> + <prefix>2001:4a00::/23</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-12-17"> + <prefix>2001:4c00::/23</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-09-10"> + <prefix>2001:5000::/20</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-11-30"> + <prefix>2001:8000::/19</prefix> + <description>APNIC</description> + <whois>whois.apnic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.apnic.net/</server> + </rdap> + <notes/> + </record> + <record date="2004-11-30"> + <prefix>2001:a000::/20</prefix> + <description>APNIC</description> + <whois>whois.apnic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.apnic.net/</server> + </rdap> + <notes/> + </record> + <record date="2006-03-08"> + <prefix>2001:b000::/20</prefix> + <description>APNIC</description> + <whois>whois.apnic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.apnic.net/</server> + </rdap> + <notes/> + </record> + <record date="2001-02-01"> + <prefix>2002:0000::/16</prefix> + <description>6to4</description> + <whois/> + <status>ALLOCATED</status> + <notes>2002::/16 is reserved for 6to4 <xref type="rfc" data="rfc3056"/>. +For complete registration details, see <xref type="registry" data="iana-ipv6-special-registry"/>.</notes> + </record> + <record date="2005-01-12"> + <prefix>2003:0000::/18</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes/> + </record> + <record date="2006-10-03"> + <prefix>2400:0000::/12</prefix> + <description>APNIC</description> + <whois>whois.apnic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.apnic.net/</server> + </rdap> + <notes>2400:0000::/19 was allocated on 2005-05-20. 2400:2000::/19 was allocated on 2005-07-08. 2400:4000::/21 was +allocated on 2005-08-08. 2404:0000::/23 was allocated on 2006-01-19. The more recent allocation (2006-10-03) +incorporates all these previous allocations.</notes> + </record> + <record date="2006-10-03"> + <prefix>2600:0000::/12</prefix> + <description>ARIN</description> + <whois>whois.arin.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.arin.net/registry</server> + <server>http://rdap.arin.net/registry</server> + </rdap> + <notes>2600:0000::/22, 2604:0000::/22, 2608:0000::/22 and 260c:0000::/22 were allocated on 2005-04-19. The more +recent allocation (2006-10-03) incorporates all these previous allocations.</notes> + </record> + <record date="2005-11-17"> + <prefix>2610:0000::/23</prefix> + <description>ARIN</description> + <whois>whois.arin.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.arin.net/registry</server> + <server>http://rdap.arin.net/registry</server> + </rdap> + <notes/> + </record> + <record date="2006-09-12"> + <prefix>2620:0000::/23</prefix> + <description>ARIN</description> + <whois>whois.arin.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.arin.net/registry</server> + <server>http://rdap.arin.net/registry</server> + </rdap> + <notes/> + </record> + <record date="2006-10-03"> + <prefix>2800:0000::/12</prefix> + <description>LACNIC</description> + <whois>whois.lacnic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.lacnic.net/rdap/</server> + </rdap> + <notes>2800:0000::/23 was allocated on 2005-11-17. The more recent allocation (2006-10-03) incorporates the +previous allocation.</notes> + </record> + <record date="2006-10-03"> + <prefix>2a00:0000::/12</prefix> + <description>RIPE NCC</description> + <whois>whois.ripe.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.db.ripe.net/</server> + </rdap> + <notes>2a00:0000::/21 was originally allocated on 2005-04-19. 2a01:0000::/23 was allocated on 2005-07-14. +2a01:0000::/16 (incorporating the 2a01:0000::/23) was allocated on 2005-12-15. The more recent allocation +(2006-10-03) incorporates these previous allocations.</notes> + </record> + <record date="2006-10-03"> + <prefix>2c00:0000::/12</prefix> + <description>AFRINIC</description> + <whois>whois.afrinic.net</whois> + <status>ALLOCATED</status> + <rdap> + <server>https://rdap.afrinic.net/rdap/</server> + <server>http://rdap.afrinic.net/rdap/</server> + </rdap> + <notes/> + </record> + <record date="1999-07-01"> + <prefix>2d00:0000::/8</prefix> + <description>IANA</description> + <whois/> + <status>RESERVED</status> + <notes/> + </record> + <record date="1999-07-01"> + <prefix>2e00:0000::/7</prefix> + <description>IANA</description> + <whois/> + <status>RESERVED</status> + <notes/> + </record> + <record date="1999-07-01"> + <prefix>3000:0000::/4</prefix> + <description>IANA</description> + <whois/> + <status>RESERVED</status> + <notes/> + </record> + <record date="2008-04"> + <prefix>3ffe::/16</prefix> + <description>IANA</description> + <whois/> + <status>RESERVED</status> + <notes>3ffe:831f::/32 was used for Teredo in some old but widely distributed networking stacks. This usage is +deprecated in favor of 2001::/32, which was allocated for the purpose in <xref type="rfc" data="rfc4380"/>. +3ffe::/16 and 5f00::/8 were used for the 6bone but were returned. <xref type="rfc" data="rfc5156"/></notes> + </record> + <record date="2008-04"> + <prefix>5f00::/8</prefix> + <description>IANA</description> + <whois/> + <status>RESERVED</status> + <notes>3ffe::/16 and 5f00::/8 were used for the 6bone but were returned. <xref type="rfc" data="rfc5156"/></notes> + </record> + <people/> +</registry> diff --git a/netaddr/strategy/eui48.py b/netaddr/strategy/eui48.py index c960dd6..68c8232 100644 --- a/netaddr/strategy/eui48.py +++ b/netaddr/strategy/eui48.py @@ -19,6 +19,7 @@ except ImportError: AF_LINK = 48 from netaddr.core import AddrFormatError +from netaddr.compat import _is_str from netaddr.strategy import ( valid_words as _valid_words, int_to_words as _int_to_words, words_to_int as _words_to_int, valid_bits as _valid_bits, @@ -26,8 +27,6 @@ from netaddr.strategy import ( valid_bin as _valid_bin, int_to_bin as _int_to_bin, bin_to_int as _bin_to_int) -from netaddr.compat import _is_str - #: The width (in bits) of this address type. width = 48 diff --git a/netaddr/strategy/eui64.py b/netaddr/strategy/eui64.py index 0100a18..01c98ea 100644 --- a/netaddr/strategy/eui64.py +++ b/netaddr/strategy/eui64.py @@ -9,11 +9,7 @@ IEEE 64-bit EUI (Extended Unique Indentifier) logic. import struct as _struct import re as _re -# This is a fake constant that doesn't really exist. Here for completeness. -AF_EUI64 = 64 - from netaddr.core import AddrFormatError -from netaddr.compat import _is_str from netaddr.strategy import ( valid_words as _valid_words, int_to_words as _int_to_words, words_to_int as _words_to_int, valid_bits as _valid_bits, @@ -21,17 +17,12 @@ from netaddr.strategy import ( valid_bin as _valid_bin, int_to_bin as _int_to_bin, bin_to_int as _bin_to_int) -#: The width (in bits) of this address type. -width = 64 -#: The individual word size (in bits) of this address type. -word_size = 8 - -#: The format string to be used when converting words to string values. -word_fmt = '%.2X' +# This is a fake constant that doesn't really exist. Here for completeness. +AF_EUI64 = 64 -#: The separator character used between each word. -word_sep = '-' +#: The width (in bits) of this address type. +width = 64 #: The AF_* constant value of this address type. family = AF_EUI64 @@ -42,24 +33,88 @@ family_name = 'EUI-64' #: The version of this address type. version = 64 -#: The number base to be used when interpreting word values as integers. -word_base = 16 - #: The maximum integer value that can be represented by this address type. max_int = 2 ** width - 1 -#: The number of words in this address type. -num_words = width // word_size +#----------------------------------------------------------------------------- +# Dialect classes. +#----------------------------------------------------------------------------- + +class eui64_base(object): + """A standard IEEE EUI-64 dialect class.""" + #: The individual word size (in bits) of this address type. + word_size = 8 + + #: The number of words in this address type. + num_words = width // word_size + + #: The maximum integer value for an individual word in this address type. + max_word = 2 ** word_size - 1 + + #: The separator character used between each word. + word_sep = '-' + + #: The format string to be used when converting words to string values. + word_fmt = '%.2X' -#: The maximum integer value for an individual word in this address type. -max_word = 2 ** word_size - 1 + #: The number base to be used when interpreting word values as integers. + word_base = 16 -#: Compiled regular expression for detecting value EUI-64 identifiers. -RE_EUI64_FORMATS = [ - _re.compile('^' + ':'.join(['([0-9A-F]{1,2})'] * 8) + '$', _re.IGNORECASE), - _re.compile('^' + '-'.join(['([0-9A-F]{1,2})'] * 8) + '$', _re.IGNORECASE), - _re.compile('^(' + '[0-9A-F]' * 16 + ')$', _re.IGNORECASE), -] + +class eui64_unix(eui64_base): + """A UNIX-style MAC address dialect class.""" + word_size = 8 + num_words = width // word_size + word_sep = ':' + word_fmt = '%x' + word_base = 16 + + +class eui64_unix_expanded(eui64_unix): + """A UNIX-style MAC address dialect class with leading zeroes.""" + word_fmt = '%.2x' + + +class eui64_cisco(eui64_base): + """A Cisco 'triple hextet' MAC address dialect class.""" + word_size = 16 + num_words = width // word_size + word_sep = '.' + word_fmt = '%.4x' + word_base = 16 + + +class eui64_bare(eui64_base): + """A bare (no delimiters) MAC address dialect class.""" + word_size = 64 + num_words = width // word_size + word_sep = '' + word_fmt = '%.16X' + word_base = 16 + + +#: The default dialect to be used when not specified by the user. + +DEFAULT_EUI64_DIALECT = eui64_base + +#----------------------------------------------------------------------------- +#: Regular expressions to match all supported MAC address formats. +RE_EUI64_FORMATS = ( + # 2 bytes x 8 (UNIX, Windows, EUI-64) + '^' + ':'.join(['([0-9A-F]{1,2})'] * 8) + '$', + '^' + '-'.join(['([0-9A-F]{1,2})'] * 8) + '$', + + # 4 bytes x 4 (Cisco like) + '^' + ':'.join(['([0-9A-F]{1,4})'] * 4) + '$', + '^' + '-'.join(['([0-9A-F]{1,4})'] * 4) + '$', + '^' + '\.'.join(['([0-9A-F]{1,4})'] * 4) + '$', + + # 16 bytes (bare, no delimiters) + '^(' + ''.join(['[0-9A-F]'] * 16) + ')$', +) +# For efficiency, each string regexp converted in place to its compiled +# counterpart. +RE_EUI64_FORMATS = [_re.compile(_, _re.IGNORECASE) for _ in RE_EUI64_FORMATS] def _get_match_result(address, formats): @@ -89,7 +144,7 @@ def str_to_int(addr): :param addr: An IEEE EUI-64 indentifier in string form. :return: An unsigned integer that is equivalent to value represented - by EUI-64 string identifier. + by EUI-64 string address formatted according to the dialect """ words = [] @@ -100,13 +155,25 @@ def str_to_int(addr): except TypeError: raise AddrFormatError('invalid IEEE EUI-64 identifier: %r!' % addr) - if _is_str(words): - return int(words, 16) - if len(words) != num_words: + if isinstance(words, tuple): + pass + else: + words = (words,) + + if len(words) == 8: + # 2 bytes x 8 (UNIX, Windows, EUI-48) + int_val = int(''.join(['%.2x' % int(w, 16) for w in words]), 16) + elif len(words) == 4: + # 4 bytes x 4 (Cisco like) + int_val = int(''.join(['%.4x' % int(w, 16) for w in words]), 16) + elif len(words) == 1: + # 16 bytes (bare, no delimiters) + int_val = int('%016x' % int(words[0], 16), 16) + else: raise AddrFormatError( 'bad word count for EUI-64 identifier: %r!' % addr) - return int(''.join(['%.2x' % int(w, 16) for w in words]), 16) + return int_val def int_to_str(int_val, dialect=None): @@ -114,13 +181,14 @@ def int_to_str(int_val, dialect=None): :param int_val: An unsigned integer. :param dialect: (optional) a Python class defining formatting options - (Please Note - not currently in use). :return: An IEEE EUI-64 identifier that is equivalent to unsigned integer. """ - words = int_to_words(int_val) - tokens = [word_fmt % i for i in words] - addr = word_sep.join(tokens) + if dialect is None: + dialect = eui64_base + words = int_to_words(int_val, dialect) + tokens = [dialect.word_fmt % i for i in words] + addr = dialect.word_sep.join(tokens) return addr @@ -155,30 +223,45 @@ def packed_to_int(packed_int): def valid_words(words, dialect=None): - return _valid_words(words, word_size, num_words) + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _valid_words(words, dialect.word_size, dialect.num_words) def int_to_words(int_val, dialect=None): - return _int_to_words(int_val, word_size, num_words) + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _int_to_words(int_val, dialect.word_size, dialect.num_words) def words_to_int(words, dialect=None): - return _words_to_int(words, word_size, num_words) + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _words_to_int(words, dialect.word_size, dialect.num_words) def valid_bits(bits, dialect=None): - return _valid_bits(bits, width, word_sep) + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _valid_bits(bits, width, dialect.word_sep) def bits_to_int(bits, dialect=None): - return _bits_to_int(bits, width, word_sep) + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _bits_to_int(bits, width, dialect.word_sep) def int_to_bits(int_val, dialect=None): - return _int_to_bits(int_val, word_size, num_words, word_sep) + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _int_to_bits( + int_val, dialect.word_size, dialect.num_words, dialect.word_sep) -def valid_bin(bin_val): +def valid_bin(bin_val, dialect=None): + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT return _valid_bin(bin_val, width) diff --git a/netaddr/tests/eui/test_eui.py b/netaddr/tests/eui/test_eui.py index 48ef7d2..c806028 100644 --- a/netaddr/tests/eui/test_eui.py +++ b/netaddr/tests/eui/test_eui.py @@ -4,7 +4,9 @@ import random import pytest -from netaddr import EUI, mac_unix, mac_unix_expanded, mac_cisco, mac_bare, mac_pgsql, OUI, IAB, IPAddress +from netaddr import (EUI, mac_unix, mac_unix_expanded, mac_cisco, + mac_bare, mac_pgsql, eui64_unix, eui64_unix_expanded, + eui64_cisco, eui64_bare, OUI, IAB, IPAddress) def test_mac_address_properties(): @@ -99,6 +101,39 @@ def test_eui_custom_dialect(): assert str(mac) == '00:1B:77:49:54:FD' +def test_eui64_dialects(): + mac = EUI('00-1B-77-49-54-FD-12-34') + assert str(mac) == '00-1B-77-49-54-FD-12-34' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_unix) + assert str(mac) == '0:1b:77:49:54:fd:12:34' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_unix_expanded) + assert str(mac) == '00:1b:77:49:54:fd:12:34' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_cisco) + assert str(mac) == '001b.7749.54fd.1234' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_bare) + assert str(mac) == '001B774954FD1234' + + +def test_eui64_dialect_property_assignment(): + mac = EUI('00-1B-77-49-54-FD-12-34') + assert str(mac) == '00-1B-77-49-54-FD-12-34' + + mac.dialect = eui64_cisco + assert str(mac) == '001b.7749.54fd.1234' + + +def test_eui64_custom_dialect(): + class eui64_custom(eui64_unix): + word_fmt = '%.2X' + + mac = EUI('00-1B-77-49-54-FD-12-34', dialect=eui64_custom) + assert str(mac) == '00:1B:77:49:54:FD:12:34' + + def test_eui_oui_information(): mac = EUI('00-1B-77-49-54-FD') oui = mac.oui diff --git a/netaddr/tests/ip/test_ip_categories.py b/netaddr/tests/ip/test_ip_categories.py index 89204fb..3aa0652 100644 --- a/netaddr/tests/ip/test_ip_categories.py +++ b/netaddr/tests/ip/test_ip_categories.py @@ -16,10 +16,22 @@ def test_is_private(): assert IPAddress('10.0.0.1').is_private() assert IPAddress('192.168.0.1').is_private() assert IPAddress('fc00::1').is_private() + assert IPAddress('198.18.0.0').is_private() + assert IPAddress('198.19.255.255').is_private() def test_is_reserved(): assert IPAddress('253.0.0.1').is_reserved() + assert IPAddress('192.0.2.0').is_reserved() + assert IPAddress('192.0.2.255').is_reserved() + assert IPAddress('127.0.0.0').is_reserved() + assert IPAddress('127.255.255.255').is_reserved() + assert IPAddress('192.88.99.0').is_reserved() + assert IPAddress('192.88.99.255').is_reserved() + assert IPAddress('0.0.0.0').is_reserved() + assert IPAddress('0.255.255.255').is_reserved() + assert IPAddress('233.252.0.0').is_reserved() + assert IPAddress('233.252.0.255').is_reserved() def test_is_public(): diff --git a/netaddr/tests/ip/test_ip_ranges.py b/netaddr/tests/ip/test_ip_ranges.py index b5cfd7f..cebe2be 100644 --- a/netaddr/tests/ip/test_ip_ranges.py +++ b/netaddr/tests/ip/test_ip_ranges.py @@ -229,7 +229,7 @@ def test_iprange_info_and_properties(): 'whois': 'whois.arin.net'}] } - assert iprange.is_private() + assert iprange.is_reserved() assert iprange.version == 4 diff --git a/netaddr/tests/ip/test_ip_v6.py b/netaddr/tests/ip/test_ip_v6.py index 2744844..fb19f19 100644 --- a/netaddr/tests/ip/test_ip_v6.py +++ b/netaddr/tests/ip/test_ip_v6.py @@ -118,3 +118,16 @@ def test_ipnetwork_pickling_v6(): 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' diff --git a/netaddr/tests/test_netaddr.py b/netaddr/tests/test_netaddr.py new file mode 100644 index 0000000..ee7ecd8 --- /dev/null +++ b/netaddr/tests/test_netaddr.py @@ -0,0 +1,11 @@ +from netaddr import valid_mac, valid_eui64 + + +def test_valid_mac(): + assert valid_mac('00-B0-D0-86-BB-F7') + assert not valid_mac('00-1B-77-49-54-FD-12-34') + + +def test_valid_eui64(): + assert valid_eui64('00-1B-77-49-54-FD-12-34') + assert not valid_eui64('00-B0-D0-86-BB-F7') @@ -10,7 +10,7 @@ name = 'netaddr' version = netaddr.__version__ -description = 'Pythonic manipulation of IPv4, IPv6, CIDR, EUI and MAC network addresses' +description = 'A Python library for representing and manipulating network addresses' keywords = [ 'Networking', 'Systems Administration', 'IANA', 'IEEE', 'CIDR', 'IP', @@ -36,9 +36,7 @@ packages = [ # Required by distutils only. package_data = { 'netaddr.ip': [ - 'ipv4-address-space.xml', - 'ipv6-address-space.xml', - 'multicast-addresses.xml' + '*.xml', ], 'netaddr.eui': [ '*.txt', @@ -53,10 +51,7 @@ license = 'BSD License' #------------------------------------------------------------------------ # NB - keep this text around 74 characters wide so it is viewable # in various fixed window sizes. -long_description = """ -A Python library for representing and manipulating network addresses. - -Provides support for: +long_description = """Provides provides support for: Layer 3 addresses diff --git a/tutorials/2.x/ip/tutorial.txt b/tutorials/2.x/ip/tutorial.txt index 858a445..2c577c8 100644 --- a/tutorials/2.x/ip/tutorial.txt +++ b/tutorials/2.x/ip/tutorial.txt @@ -61,7 +61,7 @@ Representing networks and subnets >>> ip.ip IPAddress('192.0.2.1') >>> ip.network, ip.broadcast -(IPAddress('192.0.2.1'), IPAddress('192.0.2.1')) +(IPAddress('192.0.2.1'), None) >>> ip.netmask, ip.hostmask (IPAddress('255.255.255.255'), IPAddress('0.0.0.0')) >>> ip.size |