diff options
Diffstat (limited to 'tags/1.1.1')
-rw-r--r-- | tags/1.1.1/COPYING | 202 | ||||
-rw-r--r-- | tags/1.1.1/MANIFEST.in | 2 | ||||
-rw-r--r-- | tags/1.1.1/OWNERS | 4 | ||||
-rw-r--r-- | tags/1.1.1/README | 8 | ||||
-rw-r--r-- | tags/1.1.1/ipaddr.py | 1394 | ||||
-rwxr-xr-x | tags/1.1.1/ipaddr_test.py | 627 | ||||
-rwxr-xr-x | tags/1.1.1/setup.py | 35 | ||||
-rwxr-xr-x | tags/1.1.1/test-2to3.sh | 15 |
8 files changed, 2287 insertions, 0 deletions
diff --git a/tags/1.1.1/COPYING b/tags/1.1.1/COPYING new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/tags/1.1.1/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tags/1.1.1/MANIFEST.in b/tags/1.1.1/MANIFEST.in new file mode 100644 index 0000000..4c16e20 --- /dev/null +++ b/tags/1.1.1/MANIFEST.in @@ -0,0 +1,2 @@ +include COPYING +include ipaddr_test.py diff --git a/tags/1.1.1/OWNERS b/tags/1.1.1/OWNERS new file mode 100644 index 0000000..501673e --- /dev/null +++ b/tags/1.1.1/OWNERS @@ -0,0 +1,4 @@ +pmoody +harro +mshields +smart diff --git a/tags/1.1.1/README b/tags/1.1.1/README new file mode 100644 index 0000000..1b54294 --- /dev/null +++ b/tags/1.1.1/README @@ -0,0 +1,8 @@ +ipaddr.py is a library for working with IP addresses, both IPv4 and IPv6. +It was developed by Google for internal use, and is now open source. + +Project home page: http://code.google.com/p/ipaddr-py/ + +Please send contributions to ipaddr-py-dev@googlegroups.com. Code should +include unit tests and follow the Google Python style guide: +http://code.google.com/p/soc/wiki/PythonStyleGuide diff --git a/tags/1.1.1/ipaddr.py b/tags/1.1.1/ipaddr.py new file mode 100644 index 0000000..af00d87 --- /dev/null +++ b/tags/1.1.1/ipaddr.py @@ -0,0 +1,1394 @@ +#!/usr/bin/python +# +# Copyright 2007 Google Inc. +# Licensed to PSF under a Contributor Agreement. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""An IPv4/IPv6 manipulation library in Python. + +This library is used to create/poke/manipulate IPv4 and IPv6 addresses +and prefixes. + +""" + +__version__ = '1.1.1' + +import struct + +class Error(Exception): + + """Base class for exceptions.""" + + +class IPTypeError(Error): + + """Tried to perform a v4 action on v6 object or vice versa.""" + + +class IPAddressExclusionError(Error): + + """An Error we should never see occurred in address exclusion.""" + + +class IPv4IpValidationError(Error): + + """Raised when an IPv4 address is invalid.""" + + def __init__(self, ip): + Error.__init__(self) + self.ip = ip + + def __str__(self): + return repr(self.ip) + ' is not a valid IPv4 address' + + +class IPv4NetmaskValidationError(Error): + + """Raised when a netmask is invalid.""" + + def __init__(self, netmask): + Error.__init__(self) + self.netmask = netmask + + def __str__(self): + return repr(self.netmask) + ' is not a valid IPv4 netmask' + + +class IPv6IpValidationError(Error): + + """Raised when an IPv6 address is invalid.""" + + def __init__(self, ip): + Error.__init__(self) + self.ip = ip + + def __str__(self): + return repr(self.ip) + ' is not a valid IPv6 address' + + +class IPv6NetmaskValidationError(Error): + + """Raised when an IPv6 netmask is invalid.""" + + def __init__(self, netmask): + Error.__init__(self) + self.netmask = netmask + + def __str__(self): + return repr(self.netmask) + ' is not a valid IPv6 netmask' + + +class PrefixlenDiffInvalidError(Error): + + """Raised when Sub/Supernets is called with a bad prefixlen_diff.""" + + def __init__(self, error_str): + Error.__init__(self) + self.error_str = error_str + + +def IP(ipaddr): + """Take an IP string/int and return an object of the correct type. + + Args: + ipaddr: A string or integer, the IP address. Either IPv4 or + IPv6 addresses may be supplied; integers less than 2**32 will + be considered to be IPv4. + + Returns: + An IPv4 or IPv6 object. + + Raises: + ValueError: if the string passed isn't either a v4 or a v6 + address. + + """ + + try: + return IPv4(ipaddr) + except (IPv4IpValidationError, IPv4NetmaskValidationError): + pass + + try: + return IPv6(ipaddr) + except (IPv6IpValidationError, IPv6NetmaskValidationError): + pass + + raise ValueError('%r does not appear to be an IPv4 or IPv6 address' % + ipaddr) + + +def _collapse_address_list_recursive(addresses): + """Loops through the addresses, collapsing concurrent netblocks. + + Example: + + ip1 = IPv4('1.1.0.0/24') + ip2 = IPv4('1.1.1.0/24') + ip3 = IPv4('1.1.2.0/24') + ip4 = IPv4('1.1.3.0/24') + ip5 = IPv4('1.1.4.0/24') + ip6 = IPv4('1.1.0.1/22') + + _collapse_address_list_recursive([ip1, ip2, ip3, ip4, ip5, ip6]) -> + [IPv4('1.1.0.0/22'), IPv4('1.1.4.0/24')] + + This shouldn't be called directly; it is called via + collapse_address_list([]). + + Args: + addresses: A list of IPv4 or IPv6 objects. + + Returns: + A list of IPv4 or IPv6 objects depending on what we were passed. + + """ + ret_array = [] + optimized = False + + for cur_addr in addresses: + if not ret_array: + ret_array.append(cur_addr) + continue + if cur_addr in ret_array[-1]: + optimized = True + elif cur_addr == ret_array[-1].supernet().subnet()[1]: + ret_array.append(ret_array.pop().supernet()) + optimized = True + else: + ret_array.append(cur_addr) + + if optimized: + return _collapse_address_list_recursive(ret_array) + + return ret_array + + +def collapse_address_list(addresses): + """Collapse a list of IP objects. + + Example: + collapse_address_list([IPv4('1.1.0.0/24'), IPv4('1.1.1.0/24')]) -> + [IPv4('1.1.0.0/23')] + + Args: + addresses: A list of IPv4 or IPv6 objects. + + Returns: + A list of IPv4 or IPv6 objects depending on what we were passed. + + """ + return _collapse_address_list_recursive( + sorted(addresses, key=BaseIP._get_networks_key)) + +# backwards compatibility +CollapseAddrList = collapse_address_list + +# Test whether this Python implementation supports byte objects that +# are not identical to str ones. +# We need to exclude platforms where bytes == str so that we can +# distinguish between packed representations and strings, for example +# b'12::' (the IPv4 address 49.50.58.58) and '12::' (an IPv6 address). +try: + _compat_has_real_bytes = bytes != str +except NameError: # <Python2.6 + _compat_has_real_bytes = False + +class BaseIP(object): + + """A generic IP object. + + This IP class contains most of the methods which are used by + the IPv4 and IPv6 classes. + + """ + + def __getitem__(self, n): + if n >= 0: + if self.network + n > self.broadcast: + raise IndexError + return self._string_from_ip_int(self.network + n) + else: + n += 1 + if self.broadcast + n < self.network: + raise IndexError + return self._string_from_ip_int(self.broadcast + n) + + def __lt__(self, other): + try: + if self.version != other.version: + return self.version < other.version + if self.ip != other.ip: + return self.ip < other.ip + if self.netmask != other.netmask: + return self.netmask < other.netmask + return False + except AttributeError: + return NotImplemented + + def __gt__(self, other): + try: + if self.version != other.version: + return self.version > other.version + if self.ip != other.ip: + return self.ip > other.ip + if self.netmask != other.netmask: + return self.netmask > other.netmask + return False + except AttributeError: + return NotImplemented + + def __eq__(self, other): + try: + return (self.version == other.version + and self.ip == other.ip + and self.netmask == other.netmask) + except AttributeError: + return NotImplemented + + def __ne__(self, other): + eq = self.__eq__(other) + if eq is NotImplemented: + return NotImplemented + return not eq + + def __le__(self, other): + gt = self.__gt__(other) + if gt is NotImplemented: + return NotImplemented + return not gt + + def __ge__(self, other): + lt = self.__lt__(other) + if lt is NotImplemented: + return NotImplemented + return not lt + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, str(self)) + + def __index__(self): + return self.ip + + def __int__(self): + return self.ip + + def __hex__(self): + return hex(int(self)) + + def address_exclude(self, other): + """Remove an address from a larger block. + + For example: + + addr1 = IP('10.1.1.0/24') + addr2 = IP('10.1.1.0/26') + addr1.address_exclude(addr2) = + [IP('10.1.1.64/26'), IP('10.1.1.128/25')] + + or IPv6: + + addr1 = IP('::1/32') + addr2 = IP('::1/128') + addr1.address_exclude(addr2) = [IP('::0/128'), + IP('::2/127'), + IP('::4/126'), + IP('::8/125'), + ... + IP('0:0:8000::/33')] + + Args: + other: An IP object of the same type. + + Returns: + A sorted list of IP objects addresses which is self minus + other. + + Raises: + IPTypeError: If self and other are of difffering address + versions. + IPAddressExclusionError: There was some unknown error in the + address exclusion process. This likely points to a bug + elsewhere in this code. + ValueError: If other is not completely contained by self. + + """ + if not self.version == other.version: + raise IPTypeError("%s and %s aren't of the same version" % ( + str(self), str(other))) + + if other not in self: + raise ValueError('%s not contained in %s' % (str(other), + str(self))) + + ret_addrs = [] + + # Make sure we're comparing the network of other. + other = IP(other.network_ext + '/' + str(other.prefixlen)) + + s1, s2 = self.subnet() + while s1 != other and s2 != other: + if other in s1: + ret_addrs.append(s2) + s1, s2 = s1.subnet() + elif other in s2: + ret_addrs.append(s1) + s1, s2 = s2.subnet() + else: + # If we got here, there's a bug somewhere. + raise IPAddressExclusionError('Error performing exclusion: ' + 's1: %s s2: %s other: %s' % + (str(s1), str(s2), str(other))) + if s1 == other: + ret_addrs.append(s2) + elif s2 == other: + ret_addrs.append(s1) + else: + # If we got here, there's a bug somewhere. + raise IPAddressExclusionError('Error performing exclusion: ' + 's1: %s s2: %s other: %s' % + (str(s1), str(s2), str(other))) + + return sorted(ret_addrs, key=BaseIP._get_networks_key) + + def compare_networks(self, other): + """Compare two IP objects. + + This is only concerned about the comparison of the integer + representation of the network addresses. This means that the + host bits aren't considered at all in this method. If you want + to compare host bits, you can easily enough do a + 'HostA.ip < HostB.ip' + + Args: + other: An IP object. + + Returns: + If the IP versions of self and other are the same, returns: + + -1 if self < other: + eg: IPv4('1.1.1.0/24') < IPv4('1.1.2.0/24') + IPv6('1080::200C:417A') < IPv6('1080::200B:417B') + 0 if self == other + eg: IPv4('1.1.1.1/24') == IPv4('1.1.1.2/24') + IPv6('1080::200C:417A/96') == IPv6('1080::200C:417B/96') + 1 if self > other + eg: IPv4('1.1.1.0/24') > IPv4('1.1.0.0/24') + IPv6('1080::1:200C:417A/112') > + IPv6('1080::0:200C:417A/112') + + If the IP versions of self and other are different, returns: + + -1 if self.version < other.version + eg: IPv4('10.0.0.1/24') < IPv6('::1/128') + 1 if self.version > other.version + eg: IPv6('::1/128') > IPv4('255.255.255.0/24') + + """ + if self.version < other.version: + return -1 + if self.version > other.version: + return 1 + # self.version == other.version below here: + if self.network < other.network: + return -1 + if self.network > other.network: + return 1 + # self.network == other.network below here: + if self.netmask < other.netmask: + return -1 + if self.netmask > other.netmask: + return 1 + # self.network == other.network and self.netmask == other.netmask + return 0 + + def _get_networks_key(self): + """Network-only key function. + + Returns an object that identifies this address' network and + netmask. This function is a suitable "key" argument for sorted() + and list.sort(). + + """ + return (self.version, self.network, self.netmask) + + prefixlen = property( + fget=lambda self: self._prefixlen, + fset=lambda self, prefixlen: self._set_prefix(prefixlen)) + + def __str__(self): + return '%s/%s' % (self._string_from_ip_int(self.ip), + str(self.prefixlen)) + + def __hash__(self): + return hash(self.ip ^ self.netmask) + + def __contains__(self, other): + return self.network <= other.ip and self.broadcast >= other.broadcast + + @property + def ip_ext(self): + """Dotted decimal or colon string version of the IP address.""" + return self._string_from_ip_int(self.ip) + + @property + def ip_ext_full(self): + """Canonical string version of the IP address.""" + return self.ip_ext + + @property + def broadcast(self): + """Integer representation of the broadcast address.""" + return self.ip | self.hostmask + + @property + def broadcast_ext(self): + """Dotted decimal or colon string version of the broadcast.""" + return self._string_from_ip_int(self.broadcast) + + @property + def hostmask(self): + """Integer representation of the hostmask.""" + return self.netmask ^ self._ALL_ONES + + @property + def hostmask_ext(self): + """Dotted decimal or colon string version of the hostmask.""" + return self._string_from_ip_int(self.hostmask) + + @property + def network(self): + """Integer representation of the network.""" + return self.ip & self.netmask + + @property + def network_ext(self): + """Dotted decimal or colon string version of the network.""" + return self._string_from_ip_int(self.network) + + @property + def netmask_ext(self): + """Dotted decimal or colon string version of the netmask.""" + return self._string_from_ip_int(self.netmask) + + @property + def numhosts(self): + """Number of hosts in the current subnet.""" + return self.broadcast - self.network + 1 + + @property + def version(self): + raise NotImplementedError('BaseIP has no version') + + def _ip_int_from_prefix(self, prefixlen=None): + """Turn the prefix length netmask into a int for comparison. + + Args: + prefixlen: An integer, the prefix length. + + Returns: + An integer. + + """ + if not prefixlen and prefixlen != 0: + prefixlen = self.prefixlen + return self._ALL_ONES ^ (self._ALL_ONES >> prefixlen) + + def _prefix_from_ip_int(self, ip_int, mask=32): + """Return prefix length from the decimal netmask. + + Args: + ip_int: An integer, the IP address. + mask: The netmask. Defaults to 32. + + Returns: + An integer, the prefix length. + + """ + while mask: + if ip_int & 1 == 1: + break + ip_int >>= 1 + mask -= 1 + + return mask + + def _ip_string_from_prefix(self, prefixlen=None): + """Turn a prefix length into a dotted decimal string. + + Args: + prefixlen: An integer, the netmask prefix length. + + Returns: + A string, the dotted decimal netmask string. + + """ + if not prefixlen: + prefixlen = self.prefixlen + return self._string_from_ip_int(self._ip_int_from_prefix(prefixlen)) + + # backwards compatibility + AddressExclude = address_exclude + CompareNetworks = compare_networks + Contains = __contains__ + def set_prefix(self, prefixlen): self.prefixlen = prefixlen + SetPrefix = set_prefix + def get_prefix(self): return self.prefixlen + + +class IPv4(BaseIP): + + """This class represents and manipulates 32-bit IPv4 addresses. + + Attributes: [examples for IPv4('1.2.3.4/27')] + .ip: 16909060 + .ip_ext: '1.2.3.4' + .ip_ext_full: '1.2.3.4' + .network: 16909056L + .network_ext: '1.2.3.0' + .hostmask: 31L (0x1F) + .hostmask_ext: '0.0.0.31' + .broadcast: 16909087L (0x102031F) + .broadcast_ext: '1.2.3.31' + .netmask: 4294967040L (0xFFFFFFE0) + .netmask_ext: '255.255.255.224' + .prefixlen: 27 + + """ + + # Equivalent to 255.255.255.255 or 32 bits of 1's. + _ALL_ONES = (2**32) - 1 + + def __init__(self, ipaddr): + """Instantiate a new IPv4 object. + + Args: + ipaddr: A string or integer representing the IP [& network]. + '192.168.1.1/32' + '192.168.1.1/255.255.255.255' + '192.168.1.1/0.0.0.255' + '192.168.1.1' + are all functionally the same in IPv4. That is to say, + failing to provide a subnetmask will create an object with + a mask of /32. A netmask of '255.255.255.255' is assumed + to be /32 and '0.0.0.0' is assumed to be /0, even though + other netmasks can be expressed both as host- and + net-masks. (255.0.0.0 == 0.255.255.255) + + Additionally, an integer can be passed, so + IPv4('192.168.1.1') == IPv4(3232235777). + or, more generally + IPv4(IPv4('192.168.1.1').ip) == IPv4('192.168.1.1') + + Raises: + IPv4IpValidationError: If ipaddr isn't a valid IPv4 address. + IPv4NetmaskValidationError: If the netmask isn't valid for + an IPv4 address. + + """ + BaseIP.__init__(self) + self._version = 4 + + # Efficient constructor from integer. + if isinstance(ipaddr, int) or isinstance(ipaddr, long): + self.ip = ipaddr + self._prefixlen = 32 + self.netmask = self._ALL_ONES + if ipaddr < 0 or ipaddr > self._ALL_ONES: + raise IPv4IpValidationError(ipaddr) + return + + # Constructing from a packed address + if _compat_has_real_bytes: + if isinstance(ipaddr, bytes) and len(ipaddr) == 4: + self.ip = struct.unpack('!I', ipaddr)[0] + self._prefixlen = 32 + self.netmask = self._ALL_ONES + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP prefix string. + addr = str(ipaddr).split('/') + + if len(addr) > 2: + raise IPv4IpValidationError(ipaddr) + + if not self._is_valid_ip(addr[0]): + raise IPv4IpValidationError(addr[0]) + + self.ip = self._ip_int_from_string(addr[0]) + + if len(addr) == 2: + mask = addr[1].split('.') + if len(mask) == 4: + # We have dotted decimal netmask. + if not self._is_valid_netmask(addr[1]): + raise IPv4NetmaskValidationError(addr[1]) + if self._is_hostmask(addr[1]): + self.netmask = ( + self._ip_int_from_string(addr[1]) ^ self._ALL_ONES) + else: + self.netmask = self._ip_int_from_string(addr[1]) + self._prefixlen = self._prefix_from_ip_int(self.netmask) + else: + # We have a netmask in prefix length form. + if not self._is_valid_netmask(addr[1]): + raise IPv4NetmaskValidationError(addr[1]) + self._prefixlen = int(addr[1]) + self.netmask = self._ip_int_from_prefix(self._prefixlen) + else: + self._prefixlen = 32 + self.netmask = self._ip_int_from_prefix(self._prefixlen) + + def _set_prefix(self, prefixlen): + """Change the prefix length. + + Args: + prefixlen: An integer, the new prefix length. + + Raises: + IPv4NetmaskValidationError: If prefixlen is out of bounds. + + """ + if not 0 <= prefixlen <= 32: + raise IPv4NetmaskValidationError(prefixlen) + self._prefixlen = prefixlen + self.netmask = self._ip_int_from_prefix(self._prefixlen) + + def subnet(self, prefixlen_diff=1): + """The subnets which join to make the current subnet. + + In the case that self contains only one IP + (self._prefixlen == 32), return a list with just ourself. + + Args: + prefixlen_diff: An integer, the amount the prefix length + should be increased by. Given a /24 network and a + prefixlen_diff of 3, for example, 8 subnets of size /27 + will be returned. The default value of 1 splits the + current network into two halves. + + Returns: + A list of IPv4 objects. + + Raises: + PrefixlenDiffInvalidError: The prefixlen_diff is too small + or too large. + + """ + if self._prefixlen == 32: + return [self] + + if prefixlen_diff < 0: + raise PrefixlenDiffInvalidError('prefix length diff must be > 0') + new_prefixlen = self.prefixlen + prefixlen_diff + + if not self._is_valid_netmask(str(new_prefixlen)): + raise PrefixlenDiffInvalidError( + 'prefix length diff %d is invalid for netblock %s' % ( + new_prefixlen, str(self))) + + first = IPv4( + self._string_from_ip_int(self.network) + '/' + + str(self._prefixlen + prefixlen_diff)) + subnets = [first] + current = first + while True: + broadcast = current.broadcast + if broadcast == self.broadcast: + break + current = IPv4(self._string_from_ip_int(broadcast + 1) + '/' + + str(new_prefixlen)) + subnets.append(current) + + return subnets + + def supernet(self, prefixlen_diff=1): + """The supernet containing the current network. + + Args: + prefixlen_diff: An integer, the amount the prefix length of + the network should be decreased by. For example, given a + /24 network and a prefixlen_diff of 3, a supernet with a + /21 netmask is returned. + + Returns: + An IPv4 object. + + Raises: + PrefixlenDiffInvalidError: If + self.prefixlen - prefixlen_diff < 0. I.e., you have a + negative prefix length. + + """ + if self.prefixlen == 0: + return self + if self.prefixlen - prefixlen_diff < 0: + raise PrefixlenDiffInvalidError( + 'current prefixlen is %d, cannot have a prefixlen_diff of %d' % + (self.prefixlen, prefixlen_diff)) + return IPv4(self.ip_ext + '/' + str(self.prefixlen - prefixlen_diff)) + + @property + def is_private(self): + """Test if this address is allocated for private networks. + + Returns: + A boolean, True if the address is reserved per RFC 1918. + + """ + return (self in IPv4('10.0.0.0/8') or + self in IPv4('172.16.0.0/12') or + self in IPv4('192.168.0.0/16')) + + @property + def is_multicast(self): + """Test if the address is reserved for multicast use. + + Returns: + A boolean, True if the address is multicast. + See RFC 3171 for details. + + """ + return self in IPv4('224.0.0.0/4') + + @property + def is_loopback(self): + """Test if the address is a loopback adddress. + + Returns: + A boolean, True if the address is a loopback per RFC 3330. + + """ + return self in IPv4('127.0.0.0/8') + + @property + def is_link_local(self): + """Test if the address is reserved for link-local. + + Returns: + A boolean, True if the address is link-local per RFC 3927. + + """ + return self in IPv4('169.254.0.0/16') + + @property + def version(self): + return self._version + + @property + def packed(self): + """The binary representation of this address.""" + return struct.pack('!I', self.ip) + + def _is_hostmask(self, ip_str): + """Test if the IP string is a hostmask (rather than a netmask). + + Args: + ip_str: A string, the potential hostmask. + + Returns: + A boolean, True if the IP string is a hostmask. + + """ + parts = [int(x) for x in ip_str.split('.')] + if parts[0] < parts[-1]: + return True + return False + + def _ip_int_from_string(self, ip_str): + """Turn the given IP string into an integer for comparison. + + Args: + ip_str: A string, the IP address. + + Returns: + The IP address as an integer. + + """ + packed_ip = 0 + for oc in ip_str.split('.'): + packed_ip = (packed_ip << 8) | int(oc) + return packed_ip + + def _string_from_ip_int(self, ip_int): + """Turns a 32-bit integer into dotted decimal notation. + + Args: + ip_int: An integer, the IP address. + + Returns: + The IP address as a string in dotted decimal notation. + + """ + octets = [] + for _ in xrange(4): + octets.insert(0, str(ip_int & 0xFF)) + ip_int >>= 8 + return '.'.join(octets) + + def _is_valid_ip(self, ip_str): + """Validate the dotted decimal notation IP/netmask string. + + Args: + ip_str: A string, the IP address. + + Returns: + A boolean, True if the string is a valid dotted decimal IP + string. + + """ + octets = ip_str.split('.') + if len(octets) == 1: + # We have an integer rather than a dotted decimal IP. + try: + return int(ip_str) >= 0 and int(ip_str) <= self._ALL_ONES + except ValueError: + return False + + if len(octets) != 4: + return False + + for octet in octets: + try: + if not 0 <= int(octet) <= 255: + return False + except ValueError: + return False + return True + + def _is_valid_netmask(self, netmask): + """Verify that the netmask is valid. + + Args: + netmask: A string, either a prefix or dotted decimal + netmask. + + Returns: + A boolean, True if the prefix represents a valid IPv4 + netmask. + + """ + if len(netmask.split('.')) == 4: + return self._is_valid_ip(netmask) + try: + netmask = int(netmask) + except ValueError: + return False + return 0 <= netmask <= 32 + + # backwards compatibility + Subnet = subnet + Supernet = supernet + IsRFC1918 = lambda self: self.is_private + IsMulticast = lambda self: self.is_multicast + IsLoopback = lambda self: self.is_loopback + IsLinkLocal = lambda self: self.is_link_local + +class IPv6(BaseIP): + + """This class respresents and manipulates 128-bit IPv6 addresses. + + Attributes: [examples for IPv6('2001:658:22A:CAFE:200::1/64')] + .ip: 42540616829182469433547762482097946625L + .ip_ext: '2001:658:22a:cafe:200::1' + .ip_ext_full: '2001:0658:022a:cafe:0200:0000:0000:0001' + .network: 42540616829182469433403647294022090752L + .network_ext: '2001:658:22a:cafe::' + .hostmask: 18446744073709551615L + .hostmask_ext: '::ffff:ffff:ffff:ffff' + .broadcast: 42540616829182469451850391367731642367L + .broadcast_ext: '2001:658:22a:cafe:ffff:ffff:ffff:ffff' + .netmask: 340282366920938463444927863358058659840L + .netmask_ext: 64 + .prefixlen: 64 + + """ + + _ALL_ONES = (2**128) - 1 + + def __init__(self, ipaddr): + """Instantiate a new IPv6 object. + + Args: + ipaddr: A string or integer representing the IP or the IP + and prefix/netmask. + '2001:4860::/128' + '2001:4860:0000:0000:0000:0000:0000:0000/128' + '2001:4860::' + are all functionally the same in IPv6. That is to say, + failing to provide a subnetmask will create an object with + a mask of /128. + + Additionally, an integer can be passed, so + IPv6('2001:4860::') == + IPv6(42541956101370907050197289607612071936L). + or, more generally + IPv6(IPv6('2001:4860::').ip) == IPv6('2001:4860::') + + Raises: + IPv6IpValidationError: If ipaddr isn't a valid IPv6 address. + IPv6NetmaskValidationError: If the netmask isn't valid for + an IPv6 address. + + """ + BaseIP.__init__(self) + self._version = 6 + + # Efficient constructor from integer. + if isinstance(ipaddr, long) or isinstance(ipaddr, int): + self.ip = ipaddr + self._prefixlen = 128 + self.netmask = self._ALL_ONES + if ipaddr < 0 or ipaddr > self._ALL_ONES: + raise IPv6IpValidationError(ipaddr) + return + + # Constructing from a packed address + if _compat_has_real_bytes: + if isinstance(ipaddr, bytes) and len(ipaddr) == 16: + tmp = struct.unpack('!QQ', ipaddr) + self.ip = (tmp[0] << 64) | tmp[1] + self._prefixlen = 128 + self.netmask = self._ALL_ONES + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP prefix string. + addr_str = str(ipaddr) + if not addr_str: + raise IPv6IpValidationError('') + addr = addr_str.split('/') + if len(addr) > 1: + if self._is_valid_netmask(addr[1]): + self._prefixlen = int(addr[1]) + else: + raise IPv6NetmaskValidationError(addr[1]) + else: + self._prefixlen = 128 + + self.netmask = self._ip_int_from_prefix(self._prefixlen) + + if not self._is_valid_ip(addr[0]): + raise IPv6IpValidationError(addr[0]) + + self.ip = self._ip_int_from_string(addr[0]) + + @property + def ip_ext_full(self): + """Returns the expanded version of the IPv6 string.""" + return self._explode_shorthand_ip_string(self.ip_ext) + + def _set_prefix(self, prefixlen): + """Change the prefix length. + + Args: + prefixlen: An integer, the new prefix length. + + Raises: + IPv6NetmaskValidationError: If prefixlen is out of bounds. + + """ + if not 0 <= prefixlen <= 128: + raise IPv6NetmaskValidationError(prefixlen) + self._prefixlen = prefixlen + self.netmask = self._ip_int_from_prefix(self.prefixlen) + + def subnet(self, prefixlen_diff=1): + """The subnets which join to make the current subnet. + + In the case that self contains only one IP + (self._prefixlen == 128), return a list with just ourself. + + Args: + prefixlen_diff: An integer, the amount the prefix length + should be increased by. + + Returns: + A list of IPv6 objects. + + Raises: + PrefixlenDiffInvalidError: The prefixlen_diff is too small + or too large. + + """ + # Preserve original functionality (return [self] if + # self.prefixlen == 128). + if self.prefixlen == 128: + return [self] + + if prefixlen_diff < 0: + raise PrefixlenDiffInvalidError('Prefix length diff must be > 0') + new_prefixlen = self.prefixlen + prefixlen_diff + if not self._is_valid_netmask(str(new_prefixlen)): + raise PrefixlenDiffInvalidError( + 'Prefix length diff %d is invalid for netblock %s' % ( + new_prefixlen, str(self))) + first = IPv6( + self._string_from_ip_int(self.network) + '/' + + str(self._prefixlen + prefixlen_diff)) + subnets = [first] + current = first + while True: + broadcast = current.broadcast + if current.broadcast == self.broadcast: + break + current = IPv6(self._string_from_ip_int(broadcast + 1) + '/' + + str(new_prefixlen)) + subnets.append(current) + + return subnets + + def supernet(self, prefixlen_diff=1): + """The supernet containing the current network. + + Args: + prefixlen_diff: An integer, the amount the prefix length of the + network should be decreased by. For example, given a /96 + network and a prefixlen_diff of 3, a supernet with a /93 + netmask is returned. + + Returns: + An IPv6 object. + + Raises: + PrefixlenDiffInvalidError: If + self._prefixlen - prefixlen_diff < 0. I.e., you have a + negative prefix length. + + """ + if self.prefixlen == 0: + return self + if self.prefixlen - prefixlen_diff < 0: + raise PrefixlenDiffInvalidError( + 'current prefixlen is %d, cannot have a prefixlen_diff of %d' % + (self.prefixlen, prefixlen_diff)) + return IPv6(self.ip_ext + '/' + str(self.prefixlen - prefixlen_diff)) + + @property + def is_multicast(self): + """Test if the address is reserved for multicast use. + + Returns: + A boolean, True if the address is a multicast address. + See RFC 2373 2.7 for details. + + """ + return self in IPv6('ff00::/8') + + @property + def is_unspecified(self): + """Test if the address is unspecified. + + Returns: + A boolean, True if this is the unspecified address as defined in + RFC 2373 2.5.2. + + """ + return self == IPv6('::') + + @property + def is_loopback(self): + """Test if the address is a loopback adddress. + + Returns: + A boolean, True if the address is a loopback address as defined in + RFC 2373 2.5.3. + + """ + return self == IPv6('::1') + + @property + def is_link_local(self): + """Test if the address is reserved for link-local. + + Returns: + A boolean, True if the address is reserved per RFC 4291. + + """ + return self in IPv6('fe80::/10') + + @property + def is_site_local(self): + """Test if the address is reserved for site-local. + + Note that the site-local address space has been deprecated by RFC 3879. + Use is_private to test if this address is in the space of unique local + addresses as defined by RFC 4193. + + Returns: + A boolean, True if the address is reserved per RFC 3513 2.5.6. + + """ + return self in IPv6('fec0::/10') + + @property + def is_private(self): + """Test if this address is allocated for private networks. + + Returns: + A boolean, True if the address is reserved per RFC 4193. + + """ + return self in IPv6('fc00::/7') + + @property + def version(self): + return self._version + + @property + def packed(self): + """The binary representation of this address.""" + return struct.pack('!QQ', self.ip >> 64, self.ip & (2**64 - 1)) + + def _is_shorthand_ip(self, ip_str=None): + """Determine if the address is shortened. + + Args: + ip_str: A string, the IPv6 address. + + Returns: + A boolean, True if the address is shortened. + + """ + if ip_str.count('::') == 1: + return True + return False + + def _explode_shorthand_ip_string(self, ip_str): + """Expand a shortened IPv6 address. + + Args: + ip_str: A string, the IPv6 address. + + Returns: + A string, the expanded IPv6 address. + + """ + if self._is_shorthand_ip(ip_str): + new_ip = [] + hextet = ip_str.split('::') + sep = len(hextet[0].split(':')) + len(hextet[1].split(':')) + new_ip = hextet[0].split(':') + + for _ in xrange(8 - sep): + new_ip.append('0000') + new_ip += hextet[1].split(':') + + # Now need to make sure every hextet is 4 lower case characters. + # If a hextet is < 4 characters, we've got missing leading 0's. + ret_ip = [] + for hextet in new_ip: + ret_ip.append(('0' * (4 - len(hextet)) + hextet).lower()) + return ':'.join(ret_ip) + # We've already got a longhand ip_str. + return ip_str + + def _is_valid_ip(self, ip_str=None): + """Ensure we have a valid IPv6 address. + + Probably not as exhaustive as it should be. + + Args: + ip_str: A string, the IPv6 address. + + Returns: + A boolean, True if this is a valid IPv6 address. + + """ + if not ip_str: + ip_str = self.ip_ext + + # We need to have at least one ':'. + if ':' not in ip_str: + return False + + # We can only have one '::' shortener. + if ip_str.count('::') > 1: + return False + + # '::' should be encompassed by start, digits or end. + if ':::' in ip_str: + return False + + # A single colon can neither start nor end an address. + if ((ip_str.startswith(':') and not ip_str.startswith('::')) or + (ip_str.endswith(':') and not ip_str.endswith('::'))): + return False + + # If we have no concatenation, we need to have 8 fields with 7 ':'. + if '::' not in ip_str and ip_str.count(':') != 7: + # We might have an IPv4 mapped address. + if ip_str.count('.') != 3: + return False + + ip_str = self._explode_shorthand_ip_string(ip_str) + + # Now that we have that all squared away, let's check that each of the + # hextets are between 0x0 and 0xFFFF. + for hextet in ip_str.split(':'): + if hextet.count('.') == 3: + # If we have an IPv4 mapped address, the IPv4 portion has to be + # at the end of the IPv6 portion. + if not ip_str.split(':')[-1] == hextet: + return False + try: + IPv4(hextet) + except IPv4IpValidationError: + return False + elif int(hextet, 16) < 0x0 or int(hextet, 16) > 0xFFFF: + return False + return True + + def _is_valid_netmask(self, prefixlen): + """Verify that the netmask/prefixlen is valid. + + Args: + prefixlen: A string, the netmask in prefix length format. + + Returns: + A boolean, True if the prefix represents a valid IPv6 + netmask. + + """ + try: + prefixlen = int(prefixlen) + except ValueError: + return False + return 0 <= prefixlen <= 128 + + def _ip_int_from_string(self, ip_str=None): + """Turn an IPv6 address into an integer. + + Args: + ip_str: A string, the IPv6 address. + + Returns: + A long, the IPv6 address. + + """ + if not ip_str: + ip_str = self.ip_ext + + ip_int = 0 + + fields = self._explode_shorthand_ip_string(ip_str).split(':') + + # Do we have an IPv4 mapped (::ffff:a.b.c.d) or compact (::a.b.c.d) + # address? + if fields[-1].count('.') == 3: + ipv4_string = fields.pop() + ipv4_int = IPv4(ipv4_string).ip + octets = [] + for _ in xrange(2): + octets.append(hex(ipv4_int & 0xFFFF).lstrip('0x').rstrip('L')) + ipv4_int >>= 16 + fields.extend(reversed(octets)) + + for field in fields: + ip_int = (ip_int << 16) + int(field, 16) + + return ip_int + + def _compress_hextets(self, hextets): + """Compresses a list of hextets. + + Compresses a list of strings, replacing the longest continuous + sequence of "0" in the list with "" and adding empty strings at + the beginning or at the end of the string such that subsequently + calling ":".join(hextets) will produce the compressed version of + the IPv6 address. + + Args: + hextets: A list of strings, the hextets to compress. + + Returns: + A list of strings. + + """ + best_doublecolon_start = -1 + best_doublecolon_len = 0 + doublecolon_start = -1 + doublecolon_len = 0 + for index in range(len(hextets)): + if hextets[index] == '0': + doublecolon_len += 1 + if doublecolon_start == -1: + # Start of a sequence of zeros. + doublecolon_start = index + if doublecolon_len > best_doublecolon_len: + # This is the longest sequence of zeros so far. + best_doublecolon_len = doublecolon_len + best_doublecolon_start = doublecolon_start + else: + doublecolon_len = 0 + doublecolon_start = -1 + + if best_doublecolon_len > 1: + best_doublecolon_end = (best_doublecolon_start + + best_doublecolon_len) + # For zeros at the end of the address. + if best_doublecolon_end == len(hextets): + hextets += [''] + hextets[best_doublecolon_start:best_doublecolon_end] = [''] + # For zeros at the beginning of the address. + if best_doublecolon_start == 0: + hextets = [''] + hextets + + return hextets + + def _string_from_ip_int(self, ip_int=None): + """Turns a 128-bit integer into hexadecimal notation. + + Args: + ip_int: An integer, the IP address. + + Returns: + A string, the hexadecimal representation of the address. + + Raises: + ValueError: The address is bigger than 128 bits of all ones. + + """ + if not ip_int and ip_int != 0: + ip_int = self.ip + + if ip_int > self._ALL_ONES: + raise ValueError('IPv6 address is too large') + + hex_str = '%032x' % ip_int + hextets = [] + for x in range(0, 32, 4): + hextets.append('%x' % int(hex_str[x:x+4], 16)) + + hextets = self._compress_hextets(hextets) + return ':'.join(hextets) + + @property + def netmask_ext(self): + """IPv6 extended netmask. + + We don't deal with netmasks in IPv6 like we do in IPv4. This is + here strictly for IPv4 compatibility. We simply return the + prefix length. + + Returns: + An integer. + + """ + return self.prefixlen + + # backwards compatibility + Subnet = subnet + Supernet = supernet diff --git a/tags/1.1.1/ipaddr_test.py b/tags/1.1.1/ipaddr_test.py new file mode 100755 index 0000000..255bb2e --- /dev/null +++ b/tags/1.1.1/ipaddr_test.py @@ -0,0 +1,627 @@ +#!/usr/bin/python +# +# Copyright 2007 Google Inc. +# Licensed to PSF under a Contributor Agreement. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unittest for ipaddr module.""" + + +import unittest + +import ipaddr + +# Compatibility function to cast str to bytes objects +if ipaddr._compat_has_real_bytes: + _cb = lambda bytestr: bytes(bytestr, 'charmap') +else: + _cb = str + +class IpaddrUnitTest(unittest.TestCase): + + def setUp(self): + self.ipv4 = ipaddr.IPv4('1.2.3.4/24') + self.ipv4_hostmask = ipaddr.IPv4('10.0.0.1/0.255.255.255') + self.ipv6 = ipaddr.IPv6('2001:658:22a:cafe:200:0:0:1/64') + + def testRepr(self): + self.assertEqual("IPv4('1.2.3.4/32')", repr(ipaddr.IPv4('1.2.3.4'))) + self.assertEqual("IPv6('::1/128')", repr(ipaddr.IPv6('::1'))) + + def testInvalidStrings(self): + self.assertRaises(ValueError, ipaddr.IP, '') + self.assertRaises(ValueError, ipaddr.IP, 'www.google.com') + self.assertRaises(ValueError, ipaddr.IP, '1.2.3') + self.assertRaises(ValueError, ipaddr.IP, '1.2.3.4.5') + self.assertRaises(ValueError, ipaddr.IP, '301.2.2.2') + self.assertRaises(ValueError, ipaddr.IP, '1:2:3:4:5:6:7') + self.assertRaises(ValueError, ipaddr.IP, '1:2:3:4:5:6:7:') + self.assertRaises(ValueError, ipaddr.IP, ':2:3:4:5:6:7:8') + self.assertRaises(ValueError, ipaddr.IP, '1:2:3:4:5:6:7:8:9') + self.assertRaises(ValueError, ipaddr.IP, '1:2:3:4:5:6:7:8:') + self.assertRaises(ValueError, ipaddr.IP, '1::3:4:5:6::8') + self.assertRaises(ValueError, ipaddr.IP, 'a:') + self.assertRaises(ValueError, ipaddr.IP, ':') + self.assertRaises(ValueError, ipaddr.IP, ':::') + self.assertRaises(ValueError, ipaddr.IP, '::a:') + self.assertRaises(ValueError, ipaddr.IP, '1ffff::') + self.assertRaises(ValueError, ipaddr.IP, '0xa::') + self.assertRaises(ValueError, ipaddr.IP, '1:2:3:4:5:6:1a.2.3.4') + self.assertRaises(ValueError, ipaddr.IP, '1:2:3:4:5:1.2.3.4:8') + self.assertRaises(ipaddr.IPv4IpValidationError, ipaddr.IPv4, '') + self.assertRaises(ipaddr.IPv4IpValidationError, ipaddr.IPv4, + 'google.com') + self.assertRaises(ipaddr.IPv4IpValidationError, ipaddr.IPv4, + '::1.2.3.4') + self.assertRaises(ipaddr.IPv6IpValidationError, ipaddr.IPv6, '') + self.assertRaises(ipaddr.IPv6IpValidationError, ipaddr.IPv6, + 'google.com') + self.assertRaises(ipaddr.IPv6IpValidationError, ipaddr.IPv6, + '1.2.3.4') + + def testGetNetwork(self): + self.assertEqual(self.ipv4.network, 16909056) + self.assertEqual(self.ipv4.network_ext, '1.2.3.0') + self.assertEqual(self.ipv4_hostmask.network_ext, '10.0.0.0') + + self.assertEqual(self.ipv6.network, + 42540616829182469433403647294022090752) + self.assertEqual(self.ipv6.network_ext, + '2001:658:22a:cafe::') + self.assertEqual(self.ipv6.hostmask_ext, + '::ffff:ffff:ffff:ffff') + + def testIpFromInt(self): + self.assertEqual(self.ipv4.ip, ipaddr.IPv4(16909060).ip) + self.assertRaises(ipaddr.IPv4IpValidationError, + ipaddr.IPv4, 2**32) + self.assertRaises(ipaddr.IPv4IpValidationError, + ipaddr.IPv4, -1) + + self.assertEqual(self.ipv6.ip, + ipaddr.IPv6(42540616829182469433547762482097946625).ip) + self.assertRaises(ipaddr.IPv6IpValidationError, + ipaddr.IPv6, 2**128) + self.assertRaises(ipaddr.IPv6IpValidationError, + ipaddr.IPv6, -1) + + self.assertEqual(ipaddr.IP(self.ipv4.ip).version, 4) + self.assertEqual(ipaddr.IP(self.ipv6.ip).version, 6) + + if ipaddr._compat_has_real_bytes: # on python3+ + def testIpFromPacked(self): + ip = ipaddr.IP + + self.assertEqual(self.ipv4.ip, + ip(_cb('\x01\x02\x03\x04')).ip) + self.assertEqual(ip('255.254.253.252'), + ip(_cb('\xff\xfe\xfd\xfc'))) + self.assertRaises(ValueError, ipaddr.IP, _cb('\x00' * 3)) + self.assertRaises(ValueError, ipaddr.IP, _cb('\x00' * 5)) + self.assertEqual(self.ipv6.ip, + ip(_cb('\x20\x01\x06\x58\x02\x2a\xca\xfe' + '\x02\x00\x00\x00\x00\x00\x00\x01')).ip) + self.assertEqual(ip('ffff:2:3:4:ffff::'), + ip(_cb('\xff\xff\x00\x02\x00\x03\x00\x04' + + '\xff\xff' + '\x00' * 6))) + self.assertEqual(ip('::'), + ip(_cb('\x00' * 16))) + self.assertRaises(ValueError, ip, _cb('\x00' * 15)) + self.assertRaises(ValueError, ip, _cb('\x00' * 17)) + + def testGetIp(self): + self.assertEqual(self.ipv4.ip, 16909060) + self.assertEqual(self.ipv4.ip_ext, '1.2.3.4') + self.assertEqual(self.ipv4.ip_ext_full, '1.2.3.4') + self.assertEqual(self.ipv4_hostmask.ip_ext, '10.0.0.1') + + self.assertEqual(self.ipv6.ip, 42540616829182469433547762482097946625) + self.assertEqual(self.ipv6.ip_ext, + '2001:658:22a:cafe:200::1') + self.assertEqual(self.ipv6.ip_ext_full, + '2001:0658:022a:cafe:0200:0000:0000:0001') + + def testGetNetmask(self): + self.assertEqual(self.ipv4.netmask, 4294967040L) + self.assertEqual(self.ipv4.netmask_ext, '255.255.255.0') + self.assertEqual(self.ipv4_hostmask.netmask_ext, '255.0.0.0') + self.assertEqual(self.ipv6.netmask, + 340282366920938463444927863358058659840) + self.assertEqual(self.ipv6.netmask_ext, 64) + + def testZeroNetmask(self): + ipv4_zero_netmask = ipaddr.IPv4('1.2.3.4/0') + self.assertEqual(ipv4_zero_netmask.netmask, 0) + self.assert_(ipv4_zero_netmask._is_valid_netmask(str(0))) + + ipv6_zero_netmask = ipaddr.IPv6('::1/0') + self.assertEqual(ipv6_zero_netmask.netmask, 0) + self.assert_(ipv6_zero_netmask._is_valid_netmask(str(0))) + + def testGetBroadcast(self): + self.assertEqual(self.ipv4.broadcast, 16909311L) + self.assertEqual(self.ipv4.broadcast_ext, '1.2.3.255') + + self.assertEqual(self.ipv6.broadcast, + 42540616829182469451850391367731642367) + self.assertEqual(self.ipv6.broadcast_ext, + '2001:658:22a:cafe:ffff:ffff:ffff:ffff') + + def testGetPrefixlen(self): + self.assertEqual(self.ipv4.prefixlen, 24) + + self.assertEqual(self.ipv6.prefixlen, 64) + + def testGetSupernet(self): + self.assertEqual(self.ipv4.supernet().prefixlen, 23) + self.assertEqual(self.ipv4.supernet().network_ext, '1.2.2.0') + self.assertEqual(ipaddr.IPv4('0.0.0.0/0').supernet(), + ipaddr.IPv4('0.0.0.0/0')) + + self.assertEqual(self.ipv6.supernet().prefixlen, 63) + self.assertEqual(self.ipv6.supernet().network_ext, + '2001:658:22a:cafe::') + self.assertEqual(ipaddr.IPv6('::0/0').supernet(), ipaddr.IPv6('::0/0')) + + def testGetSupernet3(self): + self.assertEqual(self.ipv4.supernet(3).prefixlen, 21) + self.assertEqual(self.ipv4.supernet(3).network_ext, '1.2.0.0') + + self.assertEqual(self.ipv6.supernet(3).prefixlen, 61) + self.assertEqual(self.ipv6.supernet(3).network_ext, + '2001:658:22a:caf8::') + + def testGetSubnet(self): + self.assertEqual(self.ipv4.subnet()[0].prefixlen, 25) + self.assertEqual(self.ipv4.subnet()[0].network_ext, '1.2.3.0') + self.assertEqual(self.ipv4.subnet()[1].network_ext, '1.2.3.128') + + self.assertEqual(self.ipv6.subnet()[0].prefixlen, 65) + + def testGetSubnetForSingle32(self): + ip = ipaddr.IPv4('1.2.3.4/32') + subnets1 = [str(x) for x in ip.subnet()] + subnets2 = [str(x) for x in ip.subnet(2)] + self.assertEqual(subnets1, ['1.2.3.4/32']) + self.assertEqual(subnets1, subnets2) + + def testGetSubnetForSingle128(self): + ip = ipaddr.IPv6('::1/128') + subnets1 = [str(x) for x in ip.subnet()] + subnets2 = [str(x) for x in ip.subnet(2)] + self.assertEqual(subnets1, ['::1/128']) + self.assertEqual(subnets1, subnets2) + + def testSubnet2(self): + ips = [str(x) for x in self.ipv4.subnet(2)] + self.assertEqual( + ips, + ['1.2.3.0/26', '1.2.3.64/26', '1.2.3.128/26', '1.2.3.192/26']) + + ipsv6 = [str(x) for x in self.ipv6.subnet(2)] + self.assertEqual( + ipsv6, + ['2001:658:22a:cafe::/66', + '2001:658:22a:cafe:4000::/66', + '2001:658:22a:cafe:8000::/66', + '2001:658:22a:cafe:c000::/66']) + + def testSubnetFailsForLargeCidrDiff(self): + self.assertRaises(ipaddr.PrefixlenDiffInvalidError, self.ipv4.subnet, 9) + self.assertRaises(ipaddr.PrefixlenDiffInvalidError, self.ipv6.subnet, + 65) + + def testSupernetFailsForLargeCidrDiff(self): + self.assertRaises(ipaddr.PrefixlenDiffInvalidError, self.ipv4.supernet, + 25) + self.assertRaises(ipaddr.PrefixlenDiffInvalidError, self.ipv6.supernet, + 65) + + def testSubnetFailsForNegativeCidrDiff(self): + self.assertRaises(ipaddr.PrefixlenDiffInvalidError, self.ipv4.subnet, + -1) + self.assertRaises(ipaddr.PrefixlenDiffInvalidError, self.ipv6.subnet, + -1) + + def testGetNumHosts(self): + self.assertEqual(self.ipv4.numhosts, 256) + self.assertEqual(self.ipv4.subnet()[0].numhosts, 128) + self.assertEqual(self.ipv4.supernet().numhosts, 512) + + self.assertEqual(self.ipv6.numhosts, 18446744073709551616) + self.assertEqual(self.ipv6.subnet()[0].numhosts, 9223372036854775808) + self.assertEqual(self.ipv6.supernet().numhosts, 36893488147419103232) + + def testContains(self): + self.assertTrue(ipaddr.IPv4('1.2.3.128/25') in self.ipv4) + self.assertFalse(ipaddr.IPv4('1.2.4.1/24') in self.ipv4) + self.assertFalse(self.ipv4 in self.ipv6) + self.assertFalse(self.ipv6 in self.ipv4) + self.assertTrue(self.ipv4 in self.ipv4) + self.assertTrue(self.ipv6 in self.ipv6) + + def testBadAddress(self): + self.assertRaises(ipaddr.IPv4IpValidationError, ipaddr.IPv4, 'poop') + self.assertRaises(ipaddr.IPv4IpValidationError, + ipaddr.IPv4, '1.2.3.256') + + self.assertRaises(ipaddr.IPv6IpValidationError, ipaddr.IPv6, 'poopv6') + self.assertRaises(ipaddr.IPv4IpValidationError, + ipaddr.IPv4, '1.2.3.4/32/24') + + def testBadNetMask(self): + self.assertRaises(ipaddr.IPv4NetmaskValidationError, + ipaddr.IPv4, '1.2.3.4/') + self.assertRaises(ipaddr.IPv4NetmaskValidationError, + ipaddr.IPv4, '1.2.3.4/33') + self.assertRaises(ipaddr.IPv4NetmaskValidationError, + ipaddr.IPv4, '1.2.3.4/254.254.255.256') + + self.assertRaises(ipaddr.IPv6NetmaskValidationError, + ipaddr.IPv6, '::1/') + self.assertRaises(ipaddr.IPv6NetmaskValidationError, + ipaddr.IPv6, '::1/129') + + def testNth(self): + self.assertEqual(self.ipv4[5], '1.2.3.5') + self.assertRaises(IndexError, self.ipv4.__getitem__, 256) + + self.assertEqual(self.ipv6[5], + '2001:658:22a:cafe::5') + + def testGetitem(self): + # http://code.google.com/p/ipaddr-py/issues/detail?id=15 + addr = ipaddr.IPv4('172.31.255.128/255.255.255.240') + self.assertEqual(28, addr.prefixlen) + addr_list = list(addr) + self.assertEqual('172.31.255.128', addr_list[0]) + self.assertEqual('172.31.255.128', addr[0]) + self.assertEqual('172.31.255.143', addr_list[-1]) + self.assertEqual('172.31.255.143', addr[-1]) + self.assertEqual(addr_list[-1], addr[-1]) + + def testEquals(self): + self.assertTrue(self.ipv4 == ipaddr.IPv4('1.2.3.4/24')) + self.assertFalse(self.ipv4 == ipaddr.IPv4('1.2.3.4/23')) + self.assertFalse(self.ipv4 == ipaddr.IPv4('1.2.3.5/24')) + self.assertFalse(self.ipv4 == ipaddr.IPv6('::1.2.3.4/24')) + self.assertFalse(self.ipv4 == '') + self.assertFalse(self.ipv4 == []) + self.assertFalse(self.ipv4 == 2) + + self.assertTrue(self.ipv6 == + ipaddr.IPv6('2001:658:22a:cafe:200::1/64')) + self.assertFalse(self.ipv6 == + ipaddr.IPv6('2001:658:22a:cafe:200::1/63')) + self.assertFalse(self.ipv6 == + ipaddr.IPv6('2001:658:22a:cafe:200::2/64')) + self.assertFalse(self.ipv6 == ipaddr.IPv4('1.2.3.4/23')) + self.assertFalse(self.ipv6 == '') + self.assertFalse(self.ipv6 == []) + self.assertFalse(self.ipv6 == 2) + + def testNotEquals(self): + self.assertFalse(self.ipv4 != ipaddr.IPv4('1.2.3.4/24')) + self.assertTrue(self.ipv4 != ipaddr.IPv4('1.2.3.4/23')) + self.assertTrue(self.ipv4 != ipaddr.IPv4('1.2.3.5/24')) + self.assertTrue(self.ipv4 != ipaddr.IPv6('::1.2.3.4/24')) + self.assertTrue(self.ipv4 != '') + self.assertTrue(self.ipv4 != []) + self.assertTrue(self.ipv4 != 2) + + self.assertFalse(self.ipv6 != + ipaddr.IPv6('2001:658:22a:cafe:200::1/64')) + self.assertTrue(self.ipv6 != + ipaddr.IPv6('2001:658:22a:cafe:200::1/63')) + self.assertTrue(self.ipv6 != + ipaddr.IPv6('2001:658:22a:cafe:200::2/64')) + self.assertTrue(self.ipv6 != ipaddr.IPv4('1.2.3.4/23')) + self.assertTrue(self.ipv6 != '') + self.assertTrue(self.ipv6 != []) + self.assertTrue(self.ipv6 != 2) + + def testSlash32Constructor(self): + self.assertEquals(str(ipaddr.IPv4('1.2.3.4/255.255.255.255')), + '1.2.3.4/32') + + def testSlash128Constructor(self): + self.assertEquals(str(ipaddr.IPv6('::1/128')), + '::1/128') + + def testSlash0Constructor(self): + self.assertEquals(str(ipaddr.IPv4('1.2.3.4/0.0.0.0')), '1.2.3.4/0') + + def testCollapsing(self): + ip1 = ipaddr.IPv4('1.1.0.0/24') + ip2 = ipaddr.IPv4('1.1.1.0/24') + ip3 = ipaddr.IPv4('1.1.2.0/24') + ip4 = ipaddr.IPv4('1.1.3.0/24') + ip5 = ipaddr.IPv4('1.1.4.0/24') + # stored in no particular order b/c we want CollapseAddr to call [].sort + ip6 = ipaddr.IPv4('1.1.0.0/22') + # check that addreses are subsumed properlly. + collapsed = ipaddr.collapse_address_list([ip1, ip2, ip3, ip4, ip5, ip6]) + self.assertEqual(collapsed, [ipaddr.IPv4('1.1.0.0/22'), + ipaddr.IPv4('1.1.4.0/24')]) + # test that two addresses are supernet'ed properlly + collapsed = ipaddr.collapse_address_list([ip1, ip2]) + self.assertEqual(collapsed, [ipaddr.IPv4('1.1.0.0/23')]) + + ip_same1 = ip_same2 = ipaddr.IPv4('1.1.1.1/32') + self.assertEqual(ipaddr.collapse_address_list([ip_same1, ip_same2]), + [ip_same1]) + ip1 = ipaddr.IPv6('::2001:1/100') + ip2 = ipaddr.IPv6('::2002:1/120') + ip3 = ipaddr.IPv6('::2001:1/96') + # test that ipv6 addresses are subsumed properly. + collapsed = ipaddr.collapse_address_list([ip1, ip2, ip3]) + self.assertEqual(collapsed, [ip3]) + + def testNetworkComparison(self): + # ip1 and ip2 have the same network address + ip1 = ipaddr.IPv4('1.1.1.0/24') + ip2 = ipaddr.IPv4('1.1.1.1/24') + ip3 = ipaddr.IPv4('1.1.2.0/24') + + self.assertTrue(ip1 < ip3) + self.assertTrue(ip3 > ip2) + + self.assertEquals(ip1.compare_networks(ip2), 0) + self.assertTrue(ip1._get_networks_key() == ip2._get_networks_key()) + self.assertEquals(ip1.compare_networks(ip3), -1) + self.assertTrue(ip1._get_networks_key() < ip3._get_networks_key()) + + ip1 = ipaddr.IPv6('2001::2000/96') + ip2 = ipaddr.IPv6('2001::2001/96') + ip3 = ipaddr.IPv6('2001:ffff::2000/96') + + self.assertTrue(ip1 < ip3) + self.assertTrue(ip3 > ip2) + self.assertEquals(ip1.compare_networks(ip2), 0) + self.assertTrue(ip1._get_networks_key() == ip2._get_networks_key()) + self.assertEquals(ip1.compare_networks(ip3), -1) + self.assertTrue(ip1._get_networks_key() < ip3._get_networks_key()) + + # Test comparing different protocols + ipv6 = ipaddr.IPv6('::/0') + ipv4 = ipaddr.IPv4('0.0.0.0/0') + self.assertTrue(ipv6 > ipv4) + self.assertTrue(ipv4 < ipv6) + + # Regression test for issue 19. + ip1 = ipaddr.IP('10.1.2.128/25') + self.assertFalse(ip1 < ip1) + self.assertFalse(ip1 > ip1) + ip2 = ipaddr.IP('10.1.3.0/24') + self.assertTrue(ip1 < ip2) + self.assertFalse(ip2 < ip1) + self.assertFalse(ip1 > ip2) + self.assertTrue(ip2 > ip1) + ip3 = ipaddr.IP('10.1.3.0/25') + self.assertTrue(ip2 < ip3) + self.assertFalse(ip3 < ip2) + self.assertFalse(ip2 > ip3) + self.assertTrue(ip3 > ip2) + + def testEmbeddedIpv4(self): + ipv4_string = '192.168.0.1' + ipv4 = ipaddr.IPv4(ipv4_string) + v4compat_ipv6 = ipaddr.IPv6('::%s' % ipv4_string) + self.assertEquals(v4compat_ipv6.ip, ipv4.ip) + v4mapped_ipv6 = ipaddr.IPv6('::ffff:%s' % ipv4_string) + self.assertNotEquals(v4mapped_ipv6.ip, ipv4.ip) + self.assertRaises(ipaddr.IPv6IpValidationError, ipaddr.IPv6, + '2001:1.1.1.1:1.1.1.1') + + def testIPVersion(self): + self.assertEqual(self.ipv4.version, 4) + self.assertEqual(self.ipv6.version, 6) + + def testPacked(self): + self.assertEqual(self.ipv4.packed, + _cb('\x01\x02\x03\x04')) + self.assertEqual(ipaddr.IPv4('255.254.253.252').packed, + _cb('\xff\xfe\xfd\xfc')) + self.assertEqual(self.ipv6.packed, + _cb('\x20\x01\x06\x58\x02\x2a\xca\xfe' + '\x02\x00\x00\x00\x00\x00\x00\x01')) + self.assertEqual(ipaddr.IPv6('ffff:2:3:4:ffff::').packed, + _cb('\xff\xff\x00\x02\x00\x03\x00\x04\xff\xff' + + '\x00' * 6)) + self.assertEqual(ipaddr.IPv6('::1:0:0:0:0').packed, + _cb('\x00' * 6 + '\x00\x01' + '\x00' * 8)) + + def testIpStrFromPrefixlen(self): + ipv4 = ipaddr.IPv4('1.2.3.4/24') + self.assertEquals(ipv4._ip_string_from_prefix(), '255.255.255.0') + self.assertEquals(ipv4._ip_string_from_prefix(28), '255.255.255.240') + + def testIpType(self): + ipv4 = ipaddr.IP('1.2.3.4') + ipv6 = ipaddr.IP('::1.2.3.4') + self.assertEquals(ipaddr.IPv4, type(ipv4)) + self.assertEquals(ipaddr.IPv6, type(ipv6)) + + def testReservedIpv4(self): + self.assertEquals(True, ipaddr.IP('224.1.1.1/31').is_multicast) + self.assertEquals(False, ipaddr.IP('240.0.0.0').is_multicast) + + self.assertEquals(True, ipaddr.IP('192.168.1.1/17').is_private) + self.assertEquals(False, ipaddr.IP('192.169.0.0').is_private) + self.assertEquals(True, ipaddr.IP('10.255.255.255').is_private) + self.assertEquals(False, ipaddr.IP('11.0.0.0').is_private) + self.assertEquals(True, ipaddr.IP('172.31.255.255').is_private) + self.assertEquals(False, ipaddr.IP('172.32.0.0').is_private) + + self.assertEquals(True, ipaddr.IP('169.254.100.200/24').is_link_local) + self.assertEquals(False, ipaddr.IP('169.255.100.200/24').is_link_local) + + self.assertEquals(True, ipaddr.IP('127.100.200.254/32').is_loopback) + self.assertEquals(True, ipaddr.IP('127.42.0.0/16').is_loopback) + self.assertEquals(False, ipaddr.IP('128.0.0.0').is_loopback) + + def testReservedIpv6(self): + ip = ipaddr.IP + + self.assertEquals(True, ip('ffff::').is_multicast) + self.assertEquals(True, ip(2**128-1).is_multicast) + self.assertEquals(True, ip('ff00::').is_multicast) + self.assertEquals(False, ip('fdff::').is_multicast) + + self.assertEquals(True, ip('fecf::').is_site_local) + self.assertEquals(True, ip('feff:ffff:ffff:ffff::').is_site_local) + self.assertEquals(False, ip('fbf:ffff::').is_site_local) + self.assertEquals(False, ip('ff00::').is_site_local) + + self.assertEquals(True, ip('fc00::').is_private) + self.assertEquals(True, ip('fc00:ffff:ffff:ffff::').is_private) + self.assertEquals(False, ip('fbff:ffff::').is_private) + self.assertEquals(False, ip('fe00::').is_private) + + self.assertEquals(True, ip('fea0::').is_link_local) + self.assertEquals(True, ip('febf:ffff::').is_link_local) + self.assertEquals(False, ip('fe7f:ffff::').is_link_local) + self.assertEquals(False, ip('fec0::').is_link_local) + + self.assertEquals(True, ip('0:0::0:01').is_loopback) + self.assertEquals(False, ip('::1/127').is_loopback) + self.assertEquals(False, ip('::').is_loopback) + self.assertEquals(False, ip('::2').is_loopback) + + self.assertEquals(True, ip('0::0').is_unspecified) + self.assertEquals(False, ip('::1').is_unspecified) + self.assertEquals(False, ip('::/127').is_unspecified) + + def testAddrExclude(self): + addr1 = ipaddr.IP('10.1.1.0/24') + addr2 = ipaddr.IP('10.1.1.0/26') + addr3 = ipaddr.IP('10.2.1.0/24') + self.assertEqual(addr1.address_exclude(addr2), + [ipaddr.IP('10.1.1.64/26'), + ipaddr.IP('10.1.1.128/25')]) + self.assertRaises(ValueError, addr1.address_exclude, addr3) + + def testHash(self): + self.assertEquals(hash(ipaddr.IP('10.1.1.0/24')), + hash(ipaddr.IP('10.1.1.0/24'))) + dummy = {} + dummy[self.ipv4] = None + dummy[self.ipv6] = None + self.assertTrue(self.ipv4 in dummy) + + def testIPv4PrefixFromInt(self): + addr1 = ipaddr.IP('10.1.1.0/24') + addr2 = ipaddr.IPv4(addr1.ip) # clone prefix + addr2.set_prefix(addr1.prefixlen) + addr3 = ipaddr.IP(123456) + + self.assertEqual(123456, addr3.ip) + self.assertRaises(ipaddr.IPv4NetmaskValidationError, + addr2.set_prefix, -1L) + self.assertEqual(addr1, addr2) + self.assertEqual(str(addr1), str(addr2)) + + def testIPv6PrefixFromInt(self): + addr1 = ipaddr.IP('2001:0658:022a:cafe:0200::1/64') + addr2 = ipaddr.IPv6(addr1.ip) # clone prefix + addr2.set_prefix(addr1.prefixlen) + addr3 = ipaddr.IP(123456) + + self.assertEqual(123456, addr3.ip) + self.assertRaises(ipaddr.IPv6NetmaskValidationError, + addr2.set_prefix, -1L) + self.assertEqual(addr1, addr2) + self.assertEqual(str(addr1), str(addr2)) + + def testCopyConstructor(self): + addr1 = ipaddr.IP('10.1.1.0/24') + addr2 = ipaddr.IP(addr1) + addr3 = ipaddr.IP('2001:658:22a:cafe:200::1/64') + addr4 = ipaddr.IP(addr3) + + self.assertEqual(addr1, addr2) + self.assertEqual(addr3, addr4) + + def testCompressIPv6Address(self): + test_addresses = { + '1:2:3:4:5:6:7:8': '1:2:3:4:5:6:7:8/128', + '2001:0:0:4:0:0:0:8': '2001:0:0:4::8/128', + '2001:0:0:4:5:6:7:8': '2001::4:5:6:7:8/128', + '2001:0:3:4:5:6:7:8': '2001:0:3:4:5:6:7:8/128', + '2001:0::3:4:5:6:7:8': '2001:0:3:4:5:6:7:8/128', + '0:0:3:0:0:0:0:ffff': '0:0:3::ffff/128', + '0:0:0:4:0:0:0:ffff': '::4:0:0:0:ffff/128', + '0:0:0:0:5:0:0:ffff': '::5:0:0:ffff/128', + '1:0:0:4:0:0:7:8': '1::4:0:0:7:8/128', + '0:0:0:0:0:0:0:0': '::/128', + '0:0:0:0:0:0:0:0/0': '::/0', + '0:0:0:0:0:0:0:1': '::1/128', + '2001:0658:022a:cafe:0000:0000:0000:0000/66': + '2001:658:22a:cafe::/66', + } + for uncompressed, compressed in test_addresses.items(): + self.assertEquals(compressed, str(ipaddr.IPv6(uncompressed))) + + def testExplodeShortHandIpStr(self): + addr1 = ipaddr.IPv6('2001::1') + self.assertEqual('2001:0000:0000:0000:0000:0000:0000:0001', + addr1._explode_shorthand_ip_string(addr1.ip_ext)) + + def testIntRepresentation(self): + self.assertEqual(16909060, int(self.ipv4)) + self.assertEqual(42540616829182469433547762482097946625, int(self.ipv6)) + + def testHexRepresentation(self): + self.assertEqual(hex(0x1020304), hex(self.ipv4)) + + self.assertEqual(hex(0x20010658022ACAFE0200000000000001), + hex(self.ipv6)) + + # backwards compatibility + def testBackwardsCompability(self): + ip = ipaddr.IP + + self.assertEqual(ipaddr.CollapseAddrList( + [ip('1.1.0.0/24'), ip('1.1.1.0/24')]), + [ip('1.1.0.0/23')]) + + self.assertEqual(ip('::42:0/112').AddressExclude(ip('::42:8000/113')), + [ip('::42:0/113')]) + + self.assertTrue(ip('1::/8').CompareNetworks(ip('2::/9')) < 0) + + self.assertEqual(ip('1::/16').Contains(ip('2::/16')), False) + + i4 = ip('1.2.3.1/12') + i4.set_prefix(0) + self.assertEqual(i4.get_prefix(), 0) + + i6 = ip('::1/2') + i6.set_prefix(0) + self.assertEqual(i6.get_prefix(), 0) + + self.assertEqual(ip('0.0.0.0/0').Subnet(), + [ip('0.0.0.0/1'), ip('128.0.0.0/1')]) + self.assertEqual(ip('::/127').Subnet(), [ip('::/128'), ip('::1/128')]) + + self.assertEqual(ip('1.0.0.0/32').Supernet(), ip('1.0.0.0/31')) + self.assertEqual(ip('::/121').Supernet(), ip('::/120')) + + self.assertEqual(ip('10.0.0.02').IsRFC1918(), True) + self.assertEqual(ip('10.0.0.0').IsMulticast(), False) + self.assertEqual(ip('127.255.255.255').IsLoopback(), True) + self.assertEqual(ip('169.255.255.255').IsLinkLocal(), False) + +if __name__ == '__main__': + unittest.main() diff --git a/tags/1.1.1/setup.py b/tags/1.1.1/setup.py new file mode 100755 index 0000000..6088ced --- /dev/null +++ b/tags/1.1.1/setup.py @@ -0,0 +1,35 @@ +#!/usr/bin/python +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from distutils.core import setup + +import ipaddr + + +setup(name='ipaddr', + maintainer='Google', + maintainer_email='ipaddr-py-dev@googlegroups.com', + version=ipaddr.__version__, + url='http://code.google.com/p/ipaddr-py/', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Topic :: Internet', + 'Topic :: Software Development :: Libraries', + 'Topic :: System :: Networking'], + py_modules=['ipaddr']) diff --git a/tags/1.1.1/test-2to3.sh b/tags/1.1.1/test-2to3.sh new file mode 100755 index 0000000..408d665 --- /dev/null +++ b/tags/1.1.1/test-2to3.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# Converts the python2 ipaddr files to python3 and runs the unit tests +# with both python versions. + +mkdir -p 2to3output && \ +cp -f *.py 2to3output && \ +( cd 2to3output && 2to3 . | patch -p0 ) && \ +py3version=$(python3 --version 2>&1) && \ +echo -e "\nTesting with ${py3version}" && \ +python3 2to3output/ipaddr_test.py && \ +rm -r 2to3output && \ +pyversion=$(python --version 2>&1) && \ +echo -e "\nTesting with ${pyversion}" && \ +./ipaddr_test.py |