From 80a356f964895e571584f619bda433e412fc5f43 Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Fri, 4 Mar 2016 12:06:58 -0700 Subject: [PATCH 01/15] Add wheels support Closes #17 --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index dc76948..d1c3771 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,3 +13,6 @@ show-pep8=1 show-source=1 statistics=1 exclude=build,dist,doc,*.egg,*.egg-info + +[wheel] +universal = 1 From f86734a6897fb09330b6c9b566e13e47b6d8635a Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Fri, 4 Mar 2016 12:20:08 -0700 Subject: [PATCH 02/15] Fix flake8 warnings * Ignore E402 (not happy about my py2/3 compat layer) * Fix comments --- iptools/__init__.py | 46 +++++++++++++++++------------------ iptools/ipv4.py | 28 ++++++++++----------- iptools/ipv6.py | 16 ++++++------ setup.cfg | 4 +-- tests/iptools/iptools_test.py | 8 +++--- 5 files changed, 51 insertions(+), 51 deletions(-) diff --git a/iptools/__init__.py b/iptools/__init__.py index 42cc0c3..52074ba 100644 --- a/iptools/__init__.py +++ b/iptools/__init__.py @@ -64,7 +64,7 @@ def _address2long(address): if parsed is None: parsed = ipv6.ip2long(address) return parsed -#end _addess2long +# end _addess2long class IpRange (Sequence): @@ -164,7 +164,7 @@ def __init__(self, start, end=None): self._ipver = ipv4 if self.endIp > ipv4.MAX_IP: self._ipver = ipv6 - #end __init__ + # end __init__ def __repr__(self): """ @@ -178,7 +178,7 @@ def __repr__(self): return "IpRange(%r, %r)" % ( self._ipver.long2ip(self.startIp), self._ipver.long2ip(self.endIp)) - #end __repr__ + # end __repr__ def __str__(self): """ @@ -192,7 +192,7 @@ def __str__(self): return ( self._ipver.long2ip(self.startIp), self._ipver.long2ip(self.endIp)).__repr__() - #end __str__ + # end __str__ def __eq__(self, other): """ @@ -206,7 +206,7 @@ def __eq__(self, other): return isinstance(other, IpRange) and \ self.startIp == other.startIp and \ self.endIp == other.endIp - #end __eq__ + # end __eq__ def __len__(self): """ @@ -223,7 +223,7 @@ def __len__(self): True """ return self._len - #end __len__ + # end __len__ def __hash__(self): """ @@ -238,7 +238,7 @@ def __hash__(self): False """ return hash((self.startIp, self.endIp)) - #end __hash__ + # end __hash__ def _cast(self, item): if isinstance(item, basestring): @@ -252,10 +252,10 @@ def _cast(self, item): # downcast to ipv4 iff address is in the IPv4 mapped block if item in IpRange(ipv6.IPV4_MAPPED): item = item & ipv4.MAX_IP - #end if + # end if return item - #end _cast + # end _cast def index(self, item): """ @@ -282,11 +282,11 @@ def index(self, item): if offset >= 0 and offset < self._len: return offset raise ValueError('%s is not in range' % self._ipver.long2ip(item)) - #end index + # end index def count(self, item): return int(item in self) - #end count + # end count def __contains__(self, item): """ @@ -313,7 +313,7 @@ def __contains__(self, item): """ item = self._cast(item) return self.startIp <= item <= self.endIp - #end __contains__ + # end __contains__ def __getitem__(self, index): """ @@ -348,7 +348,7 @@ def __getitem__(self, index): """ if isinstance(index, slice): if index.step not in (None, 1): - #TODO: return an IpRangeList + # TODO: return an IpRangeList raise ValueError('slice step not supported') start = index.start or 0 if start < 0: @@ -371,7 +371,7 @@ def __getitem__(self, index): if index < 0 or index >= self._len: raise IndexError('index out of range') return self._ipver.long2ip(self.startIp + index) - #end __getitem__ + # end __getitem__ def __iter__(self): """ @@ -392,8 +392,8 @@ def __iter__(self): while i <= self.endIp: yield self._ipver.long2ip(i) i += 1 - #end __iter__ -#end class IpRange + # end __iter__ +# end class IpRange class IpRangeList (object): @@ -410,7 +410,7 @@ class IpRangeList (object): """ def __init__(self, *args): self.ips = tuple(map(IpRange, args)) - #end __init__ + # end __init__ def __repr__(self): """ @@ -429,7 +429,7 @@ def __repr__(self): IpRange('192.168.0.0', '192.168.255.255'))" """ return "IpRangeList%r" % (self.ips,) - #end __repr__ + # end __repr__ def __str__(self): """ @@ -440,7 +440,7 @@ def __str__(self): ('192.168.0.0', '192.168.255.255'))" """ return "(%s)" % ", ".join(str(i) for i in self.ips) - #end __str__ + # end __str__ def __contains__(self, item): """ @@ -469,7 +469,7 @@ def __contains__(self, item): if item in r: return True return False - #end __contains__ + # end __contains__ def __iter__(self): """ @@ -497,7 +497,7 @@ def __iter__(self): for r in self.ips: for ip in r: yield ip - #end __iter__ + # end __iter__ def __len__(self): """ @@ -516,7 +516,7 @@ def __len__(self): True """ return sum(r.__len__() for r in self.ips) - #end __len__ -#end class IpRangeList + # end __len__ +# end class IpRangeList # vim: set sw=4 ts=4 sts=4 et : diff --git a/iptools/ipv4.py b/iptools/ipv4.py index f1160a0..c3d0127 100644 --- a/iptools/ipv4.py +++ b/iptools/ipv4.py @@ -93,7 +93,7 @@ def bin(x): except NameError: out.reverse() return '0b' + ''.join(out) - #end bin + # end bin # end compatibility "fixes' @@ -223,7 +223,7 @@ def validate_ip(s): return False return True return False -#end validate_ip +# end validate_ip def validate_cidr(s): @@ -266,7 +266,7 @@ def validate_cidr(s): return False return True return False -#end validate_cidr +# end validate_cidr def validate_netmask(s): @@ -305,7 +305,7 @@ def validate_netmask(s): return True else: return False -#end validate_netmask +# end validate_netmask def validate_subnet(s): @@ -348,7 +348,7 @@ def validate_subnet(s): else: return False raise TypeError("expected string or unicode") -#end validate_subnet +# end validate_subnet def ip2long(ip): @@ -385,7 +385,7 @@ def ip2long(ip): for q in quads: lngip = (lngip << 8) | int(q) return lngip -#end ip2long +# end ip2long def ip2network(ip): @@ -406,7 +406,7 @@ def ip2network(ip): for i in range(4): netw = (netw << 8) | int(len(quads) > i and quads[i] or 0) return netw -#end ip2network +# end ip2network def long2ip(l): @@ -448,7 +448,7 @@ def long2ip(l): "expected int between %d and %d inclusive" % (MIN_IP, MAX_IP)) return '%d.%d.%d.%d' % ( l >> 24 & 255, l >> 16 & 255, l >> 8 & 255, l & 255) -#end long2ip +# end long2ip def ip2hex(addr): @@ -478,7 +478,7 @@ def ip2hex(addr): if netip is None: return None return "%08x" % netip -#end ip2hex +# end ip2hex def hex2ip(hex_str): @@ -506,7 +506,7 @@ def hex2ip(hex_str): except ValueError: return None return long2ip(netip) -#end hex2ip +# end hex2ip def cidr2block(cidr): @@ -543,7 +543,7 @@ def cidr2block(cidr): network = ip2network(ip) return _block_from_ip_and_prefix(network, prefix) -#end cidr2block +# end cidr2block def netmask2prefix(mask): @@ -571,7 +571,7 @@ def netmask2prefix(mask): if validate_netmask(mask): return bin(ip2network(mask)).count('1') return 0 -#end netmask2prefix +# end netmask2prefix def subnet2block(subnet): @@ -609,7 +609,7 @@ def subnet2block(subnet): network = ip2network(ip) return _block_from_ip_and_prefix(network, prefix) -#end subnet2block +# end subnet2block def _block_from_ip_and_prefix(ip, prefix): @@ -630,6 +630,6 @@ def _block_from_ip_and_prefix(ip, prefix): mask = (1 << shift) - 1 block_end = block_start | mask return (long2ip(block_start), long2ip(block_end)) -#end _block_from_ip_and_prefix +# end _block_from_ip_and_prefix # vim: set sw=4 ts=4 sts=4 et : diff --git a/iptools/ipv6.py b/iptools/ipv6.py index 319c4b5..9fab832 100644 --- a/iptools/ipv6.py +++ b/iptools/ipv6.py @@ -190,7 +190,7 @@ def validate_ip(s): return False return True return False -#end validate_ip +# end validate_ip def ip2long(ip): @@ -247,7 +247,7 @@ def ip2long(ip): hextets.append('0') for h in h2: hextets.append(h) - #end if + # end if lngip = 0 for h in hextets: @@ -255,7 +255,7 @@ def ip2long(ip): h = '0' lngip = (lngip << 16) | int(h, 16) return lngip -#end ip2long +# end ip2long def long2ip(l): @@ -311,7 +311,7 @@ def long2ip(l): dc_len, dc_start = (run_len, run_start) else: run_len, run_start = (0, -1) - #end for + # end for if dc_len > 1: dc_end = dc_start + dc_len if dc_end == len(hextets): @@ -319,10 +319,10 @@ def long2ip(l): hextets[dc_start:dc_end] = [''] if dc_start == 0: hextets = [''] + hextets - #end if + # end if return ':'.join(hextets) -#end long2ip +# end long2ip def validate_cidr(s): @@ -365,7 +365,7 @@ def validate_cidr(s): return False return True return False -#end validate_cidr +# end validate_cidr def cidr2block(cidr): @@ -399,6 +399,6 @@ def cidr2block(cidr): mask = (1 << shift) - 1 block_end = block_start | mask return (long2ip(block_start), long2ip(block_end)) -#end cidr2block +# end cidr2block # vim: set sw=4 ts=4 sts=4 et : diff --git a/setup.cfg b/setup.cfg index d1c3771..ec2d4e2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,12 +7,12 @@ cover-html=1 cover-html-dir=docs/_build/cover [flake8] -ignore=F821 +ignore=F821,E402 count=1 show-pep8=1 show-source=1 statistics=1 -exclude=build,dist,doc,*.egg,*.egg-info +exclude=build,dist,docs,*.egg,*.egg-info [wheel] universal = 1 diff --git a/tests/iptools/iptools_test.py b/tests/iptools/iptools_test.py index ff23586..a1c8c88 100644 --- a/tests/iptools/iptools_test.py +++ b/tests/iptools/iptools_test.py @@ -35,7 +35,7 @@ def testMixedRange(self): self.assertFalse('209.19.170.129' in INTERNAL_IPS) # end testMixedRange -#end class IpRangeListTests +# end class IpRangeListTests class IpRangeTests(unittest.TestCase): @@ -44,7 +44,7 @@ def testIPv6Range(self): fixture = iptools.IpRange('::ffff:0:0/96') self.assertTrue('::ffff:172.16.11.12' in fixture) self.assertFalse('209.19.170.129' in fixture) - #end testIPv6Range + # end testIPv6Range def testV4MappedAddressInIPv6Range(self): """ @@ -60,7 +60,7 @@ def testV4MappedAddressInIPv6Range(self): self.assertTrue('::ffff:192.168.0.12' in fixture) self.assertFalse('::ffff:192.168.1.12' in fixture) - #end test6to4AddressInIPv6Range -#end class IpRangeTests + # end test6to4AddressInIPv6Range +# end class IpRangeTests # vim:se sw=4 ts=4 sts=4 et: From f047b4e1de2061d1de6df3ea12345a19a7a5af88 Mon Sep 17 00:00:00 2001 From: Piotr Skamruk Date: Wed, 17 Feb 2016 20:34:03 +0100 Subject: [PATCH 03/15] Provide methods for comparison of IpRangeList objects --- iptools/__init__.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/iptools/__init__.py b/iptools/__init__.py index 52074ba..608ea4b 100644 --- a/iptools/__init__.py +++ b/iptools/__init__.py @@ -517,6 +517,38 @@ def __len__(self): """ return sum(r.__len__() for r in self.ips) # end __len__ + + def __hash__(self): + """ + Return correct hash for IpRangeList object + + >>> a = IpRange('127.0.0.0/8') + >>> b = IpRange('127.0.0.0', '127.255.255.255') + >>> IpRangeList(a, b).__hash__() == IpRangeList(a, b).__hash__() + True + >>> IpRangeList(a, b).__hash__() == IpRangeList(b, a).__hash__() + True + >>> c = IpRange('10.0.0.0/8') + >>> IpRangeList(a, c).__hash__() == IpRangeList(c, a).__hash__() + False + """ + return hash(self.ips) + # end __hash__ + + def __eq__(self, other): + """ + >>> a = IpRange('127.0.0.0/8') + >>> b = IpRange('127.0.0.0', '127.255.255.255') + >>> IpRangeList(a, b) == IpRangeList(a, b) + True + >>> IpRangeList(a, b) == IpRangeList(b, a) + True + >>> c = IpRange('10.0.0.0/8') + >>> IpRangeList(a, c) == IpRangeList(c, a) + False + """ + return hash(self) == hash(other) + # end __eq__ # end class IpRangeList # vim: set sw=4 ts=4 sts=4 et : From 33954e7d605cb709c020433565b105a591245249 Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Sat, 20 Aug 2016 23:33:13 -0600 Subject: [PATCH 04/15] travis: remove --use-mirrors --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 79a101a..eada3f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ python: - "3.3" - "pypy" install: - - pip install . --use-mirrors - - pip install -r tests/requirements.txt --use-mirrors + - pip install . + - pip install -r tests/requirements.txt script: - flake8 - nosetests From 285fe5c96685615cf41e4c835df477ec8a39d445 Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Sun, 21 Aug 2016 12:21:57 -0600 Subject: [PATCH 05/15] validate_netmask: ensure 32 bit expansion Ensure that the bit string representation of a netmask is the full 32 bits before validating the left most bits are 1s. See #18 --- iptools/ipv4.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/iptools/ipv4.py b/iptools/ipv4.py index c3d0127..08f199d 100644 --- a/iptools/ipv4.py +++ b/iptools/ipv4.py @@ -285,6 +285,10 @@ def validate_netmask(s): True >>> validate_netmask('128.0.0.1') False + >>> validate_netmask('1.255.255.0') + False + >>> validate_netmask('0.255.255.0') + False :param s: String to validate as a dotted-quad notation netmask. @@ -293,7 +297,8 @@ def validate_netmask(s): :raises: TypeError """ if validate_ip(s): - mask = bin(ip2network(s))[2:] + # Convert to binary string, strip '0b' prefix, 0 pad to 32 bits + mask = bin(ip2network(s))[2:].zfill(32) # all left most bits must be 1, all right most must be 0 seen0 = False for c in mask: From 51b9e84c72e2dd077d8ff4995e9389b1b4614185 Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Sun, 21 Aug 2016 12:25:55 -0600 Subject: [PATCH 06/15] Move imports before exports --- iptools/__init__.py | 15 +++++----- iptools/ipv4.py | 71 ++++++++++++++++++++++----------------------- iptools/ipv6.py | 8 ++--- 3 files changed, 44 insertions(+), 50 deletions(-) diff --git a/iptools/__init__.py b/iptools/__init__.py index 608ea4b..181f845 100644 --- a/iptools/__init__.py +++ b/iptools/__init__.py @@ -22,13 +22,6 @@ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -__version__ = '0.7.0-dev' - -__all__ = ( - 'IpRange', - 'IpRangeList', -) - # sniff for python2.x / python3k compatibility "fixes' try: @@ -51,10 +44,16 @@ def next(iterable): Sequence = object # end compatibility "fixes' - from . import ipv4 from . import ipv6 +__version__ = '0.7.0-dev' + +__all__ = ( + 'IpRange', + 'IpRangeList', +) + def _address2long(address): """ diff --git a/iptools/ipv4.py b/iptools/ipv4.py index 08f199d..3243482 100644 --- a/iptools/ipv4.py +++ b/iptools/ipv4.py @@ -23,6 +23,40 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +import re + +# sniff for python2.x / python3k compatibility "fixes' +try: + basestring = basestring +except NameError: + # 'basestring' is undefined, must be python3k + basestring = str + +try: + bin = bin +except NameError: + # builtin bin function doesn't exist + def bin(x): + """ + From http://code.activestate.com/recipes/219300/#c7 + """ + if x < 0: + return '-' + bin(-x) + out = [] + if x == 0: + out.append('0') + while x > 0: + out.append('01'[x & 1]) + x >>= 1 + pass + try: + return '0b' + ''.join(reversed(out)) + except NameError: + out.reverse() + return '0b' + ''.join(out) + # end bin +# end compatibility "fixes' + __all__ = ( 'cidr2block', 'hex2ip', @@ -60,43 +94,6 @@ 'TEST_NET_3', ) - -import re - - -# sniff for python2.x / python3k compatibility "fixes' -try: - basestring = basestring -except NameError: - # 'basestring' is undefined, must be python3k - basestring = str - -try: - bin = bin -except NameError: - # builtin bin function doesn't exist - def bin(x): - """ - From http://code.activestate.com/recipes/219300/#c7 - """ - if x < 0: - return '-' + bin(-x) - out = [] - if x == 0: - out.append('0') - while x > 0: - out.append('01'[x & 1]) - x >>= 1 - pass - try: - return '0b' + ''.join(reversed(out)) - except NameError: - out.reverse() - return '0b' + ''.join(out) - # end bin -# end compatibility "fixes' - - #: Regex for validating an IPv4 address _DOTTED_QUAD_RE = re.compile(r'^(\d{1,3}\.){0,3}\d{1,3}$') diff --git a/iptools/ipv6.py b/iptools/ipv6.py index 9fab832..97d00ee 100644 --- a/iptools/ipv6.py +++ b/iptools/ipv6.py @@ -23,6 +23,9 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +import re +from . import ipv4 + __all__ = ( 'cidr2block', 'ip2long', @@ -52,11 +55,6 @@ 'UNSPECIFIED_ADDRESS', ) - -import re -from . import ipv4 - - #: Regex for validating an IPv6 in hex notation _HEX_RE = re.compile(r'^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}$') From 041628d6d5860d39825302c4b2a6f68aec7014b2 Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Sun, 21 Aug 2016 14:08:12 -0600 Subject: [PATCH 07/15] Travis: use tox for testing * Update the Travis CI testing configuration to use tox. * Drop testing of Python 2.6, 3.2, 3.3 * Add Python 3.4, 3.5, pypy3 --- .gitignore | 13 +++++++------ .travis.yml | 20 +++++++++++++------- setup.cfg | 5 +++-- tox.ini | 8 ++++++++ 4 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 71b79d3..b11bdd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ +*.egg-info *.pyc -.coverage -.venv -build -dist -extras -iptools.egg-info +/.coverage +/.tox/ +/.venv/ +/build/ +/dist/ +/extras/ setuptools-*.egg diff --git a/.travis.yml b/.travis.yml index eada3f2..70c1d3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,22 @@ language: python python: - - "2.6" - "2.7" - - "3.2" - - "3.3" + - "3.4" + - "3.5" - "pypy" + - "pypy3" +sudo: false +matrix: + fast_finish: true + install: - - pip install . - - pip install -r tests/requirements.txt + - pip install wheel tox-travis + - python setup.py install bdist_wheel + - pip install ./dist/iptools-*.whl script: - - flake8 - - nosetests + - tox + - tox --installpkg ./dist/iptools-*.whl + notifications: email: - travis-ci+python-iptools@bd808.com diff --git a/setup.cfg b/setup.cfg index ec2d4e2..85d3e9c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,18 +1,19 @@ [nosetests] +verbosity=2 detailed-errors=1 with-coverage=1 with-doctest=1 cover-package=iptools cover-html=1 cover-html-dir=docs/_build/cover +cover-branches=1 [flake8] -ignore=F821,E402 count=1 show-pep8=1 show-source=1 statistics=1 -exclude=build,dist,docs,*.egg,*.egg-info +exclude=.tox,.venv,build,dist,docs,*.egg,*.egg-info [wheel] universal = 1 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..a99d698 --- /dev/null +++ b/tox.ini @@ -0,0 +1,8 @@ +[tox] +envlist = py27, py34, py35, pypy, pypy3 + +[testenv] +deps = -r{toxinidir}/tests/requirements.txt +commands = + flake8 + nosetests From d698b0008316760f74290862344d9626398fb4af Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Sun, 21 Aug 2016 14:34:01 -0600 Subject: [PATCH 08/15] Cast input to IpRangeList (#15) Reduce the overhead of testing for membership in a long IpRangeList by ensuring that the provided input has been cast to an integer before checking each contained range. Closes #14 --- iptools/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/iptools/__init__.py b/iptools/__init__.py index 181f845..8d21ce9 100644 --- a/iptools/__init__.py +++ b/iptools/__init__.py @@ -249,7 +249,7 @@ def _cast(self, item): if ipv4 == self._ipver and item > ipv4.MAX_IP: # casting an ipv6 in an ipv4 range # downcast to ipv4 iff address is in the IPv4 mapped block - if item in IpRange(ipv6.IPV4_MAPPED): + if item in _IPV6_MAPPED_IPV4: item = item & ipv4.MAX_IP # end if @@ -395,6 +395,9 @@ def __iter__(self): # end class IpRange +_IPV6_MAPPED_IPV4 = IpRange(ipv6.IPV4_MAPPED) + + class IpRangeList (object): """ List of IpRange objects. @@ -464,6 +467,11 @@ def __contains__(self, item): :type item: str :returns: ``True`` if address is in list, ``False`` otherwise. """ + if isinstance(item, basestring): + item = _address2long(item) + if type(item) not in (type(1), type(ipv4.MAX_IP), type(ipv6.MAX_IP)): + raise TypeError( + "expected ip address, 32-bit integer or 128-bit integer") for r in self.ips: if item in r: return True From fcdc8f1a28861961045c3e484e03f0d6596d555c Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Sun, 21 Aug 2016 15:56:47 -0600 Subject: [PATCH 09/15] Implement RFC 1924 IPv6 encoding/decoding Just for fun really. RFC 1924 encodings are not allowed by most existing methods. New long2rfc1924 and rfc19242long allow encoding/decoding to the 20 character ASCII encoding specified in RFC 1924. Closes #9 --- iptools/ipv6.py | 101 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/iptools/ipv6.py b/iptools/ipv6.py index 97d00ee..9e4c696 100644 --- a/iptools/ipv6.py +++ b/iptools/ipv6.py @@ -30,6 +30,8 @@ 'cidr2block', 'ip2long', 'long2ip', + 'long2rfc1924', + 'rfc19242long', 'validate_cidr', 'validate_ip', 'DOCUMENTATION_NETWORK', @@ -56,7 +58,7 @@ ) #: Regex for validating an IPv6 in hex notation -_HEX_RE = re.compile(r'^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}$') +_HEX_RE = re.compile(r'^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$') #: Regex for validating an IPv6 in dotted-quad notation _DOTTED_QUAD_RE = re.compile(r'^([0-9a-f]{0,4}:){2,6}(\d{1,3}\.){0,3}\d{1,3}$') @@ -136,6 +138,21 @@ #: All DHCP servers and relay agents on the local site MULTICAST_SITE_DHCP = "ff05::1:3" +#: RFC 1924 alphabet +_RFC1924_ALPHABET = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '!', '#', '$', '%', '&', '(', ')', '*', '+', '-', ';', '<', '=', + '>', '?', '@', '^', '_', '`', '{', '|', '}', '~', +] +#: RFC 1924 reverse lookup +_RFC1924_REV = None +#: Regex for validating an IPv6 in hex notation +_RFC1924_RE = re.compile(r'^[0-9A-Za-z!#$%&()*+-;<=>?@^_`{|}~]{20}$') + def validate_ip(s): """Validate a hexidecimal IPv6 ip address. @@ -167,6 +184,8 @@ def validate_ip(s): Traceback (most recent call last): ... TypeError: expected string or buffer + >>> validate_ip('1080:0:0:0:8:800:200c:417a') + True :param s: String to validate as a hexidecimal IPv6 ip address. @@ -218,6 +237,9 @@ def ip2long(ip): True >>> ip2long('ff::ff::ff') == None True + >>> expect = 21932261930451111902915077091070067066 + >>> ip2long('1080:0:0:0:8:800:200C:417A') == expect + True :param ip: Hexidecimal IPv6 address @@ -256,7 +278,7 @@ def ip2long(ip): # end ip2long -def long2ip(l): +def long2ip(l, rfc1924=False): """Convert a network byte order 128-bit integer to a canonical IPv6 address. @@ -281,10 +303,16 @@ def long2ip(l): Traceback (most recent call last): ... TypeError: expected int between 0 and inclusive + >>> long2ip(ip2long('1080::8:800:200C:417A'), rfc1924=True) + '4)+k&C#VzJ4br>0wv%Yp' + >>> long2ip(ip2long('::'), rfc1924=True) + '00000000000000000000' :param l: Network byte order 128-bit integer. :type l: int + :param rfc1924: Encode in RFC 1924 notation (base 85) + :type rfc1924: bool :returns: Canonical IPv6 address (eg. '::1'). :raises: TypeError """ @@ -292,6 +320,9 @@ def long2ip(l): raise TypeError( "expected int between %d and %d inclusive" % (MIN_IP, MAX_IP)) + if rfc1924: + return long2rfc1924(l) + # format as one big hex value hex_str = '%032x' % l # split into double octet chunks without padding zeros @@ -323,6 +354,72 @@ def long2ip(l): # end long2ip +def long2rfc1924(l): + """Convert a network byte order 128-bit integer to an rfc1924 IPv6 + address. + + + >>> long2rfc1924(ip2long('1080::8:800:200C:417A')) + '4)+k&C#VzJ4br>0wv%Yp' + >>> long2rfc1924(ip2long('::')) + '00000000000000000000' + >>> long2rfc1924(MAX_IP) + '=r54lj&NUUO~Hi%c2ym0' + + + :param l: Network byte order 128-bit integer. + :type l: int + :returns: RFC 1924 IPv6 address + :raises: TypeError + """ + if MAX_IP < l or l < MIN_IP: + raise TypeError( + "expected int between %d and %d inclusive" % (MIN_IP, MAX_IP)) + o = [] + r = l + while r > 85: + o.append(_RFC1924_ALPHABET[r % 85]) + r = r // 85 + o.append(_RFC1924_ALPHABET[r]) + return ''.join(reversed(o)).zfill(20) + + +def rfc19242long(s): + """Convert an RFC 1924 IPv6 address to a network byte order 128-bit + integer. + + + >>> expect = 0 + >>> rfc19242long('00000000000000000000') == expect + True + >>> expect = 21932261930451111902915077091070067066 + >>> rfc19242long('4)+k&C#VzJ4br>0wv%Yp') == expect + True + >>> rfc19242long('pizza') == None + True + >>> rfc19242long('~~~~~~~~~~~~~~~~~~~~') == None + True + >>> rfc19242long('=r54lj&NUUO~Hi%c2ym0') == MAX_IP + True + + + :param ip: RFC 1924 IPv6 address + :type ip: str + :returns: Network byte order 128-bit integer or ``None`` if ip is invalid. + """ + global _RFC1924_REV + if not _RFC1924_RE.match(s): + return None + if _RFC1924_REV is None: + _RFC1924_REV = {v: k for k, v in enumerate(_RFC1924_ALPHABET)} + l = 0 + for c in s: + l = l * 85 + _RFC1924_REV[c] + if l > MAX_IP: + return None + return l + + def validate_cidr(s): """Validate a CIDR notation ip address. From 96df2e8f9df052bb680ab7b043bfa883fdae28ba Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Sun, 21 Aug 2016 16:22:48 -0600 Subject: [PATCH 10/15] Update README description of tested versions --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 932796a..8ce94c8 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,8 @@ Local documentation can be built using [Sphinx][]: Python Version Compatibility ---------------------------- -[Travis CI][ci-home] automatically runs tests against python 2.6, 2.7, 3.2, 3.3 and pypy. +[Travis CI][ci-home] automatically runs tests against python 2.7, 3.4, 3.5, +pypy, and pypy3, Installation ------------ From 8d8c140a1d7863388c04180c893fce8c8d082297 Mon Sep 17 00:00:00 2001 From: cclauss Date: Thu, 6 Dec 2018 05:06:00 +0100 Subject: [PATCH 11/15] Travis CI: Add Python 3.6 and 3.7 to the testing --- .travis.yml | 4 ++++ tox.ini | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 70c1d3c..23adb1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,15 @@ python: - "2.7" - "3.4" - "3.5" + - "3.6" - "pypy" - "pypy3" sudo: false matrix: fast_finish: true + include: + - python: "3.7" + dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069) install: - pip install wheel tox-travis diff --git a/tox.ini b/tox.ini index a99d698..f11b876 100644 --- a/tox.ini +++ b/tox.ini @@ -4,5 +4,5 @@ envlist = py27, py34, py35, pypy, pypy3 [testenv] deps = -r{toxinidir}/tests/requirements.txt commands = - flake8 + flake8 --ignore=E741,W605 nosetests From 5a0ebd9afd659cc1c8f06d241fcd80cd138164bc Mon Sep 17 00:00:00 2001 From: cclauss Date: Thu, 6 Dec 2018 08:48:14 +0100 Subject: [PATCH 12/15] Placate flake8 --- iptools/__init__.py | 2 +- iptools/ipv6.py | 8 ++++---- tox.ini | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/iptools/__init__.py b/iptools/__init__.py index 8d21ce9..dd0fd67 100644 --- a/iptools/__init__.py +++ b/iptools/__init__.py @@ -399,7 +399,7 @@ def __iter__(self): class IpRangeList (object): - """ + r""" List of IpRange objects. Converts a list of ip address and/or CIDR addresses into a list of IpRange diff --git a/iptools/ipv6.py b/iptools/ipv6.py index 9e4c696..073a427 100644 --- a/iptools/ipv6.py +++ b/iptools/ipv6.py @@ -412,12 +412,12 @@ def rfc19242long(s): return None if _RFC1924_REV is None: _RFC1924_REV = {v: k for k, v in enumerate(_RFC1924_ALPHABET)} - l = 0 + x = 0 for c in s: - l = l * 85 + _RFC1924_REV[c] - if l > MAX_IP: + x = x * 85 + _RFC1924_REV[c] + if x > MAX_IP: return None - return l + return x def validate_cidr(s): diff --git a/tox.ini b/tox.ini index f11b876..a99d698 100644 --- a/tox.ini +++ b/tox.ini @@ -4,5 +4,5 @@ envlist = py27, py34, py35, pypy, pypy3 [testenv] deps = -r{toxinidir}/tests/requirements.txt commands = - flake8 --ignore=E741,W605 + flake8 nosetests From b4398244355d46dae161a791102ed4a0f48e0d83 Mon Sep 17 00:00:00 2001 From: cclauss Date: Tue, 11 Dec 2018 04:09:20 +0100 Subject: [PATCH 13/15] tox.ini: Add py36, py37 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a99d698..4189f95 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, py34, py35, pypy, pypy3 +envlist = py27, py34, py35, py36, py37, pypy, pypy3 [testenv] deps = -r{toxinidir}/tests/requirements.txt From 5d3fae0056297540355bb7c6c112703cfaa4b6ce Mon Sep 17 00:00:00 2001 From: Bryan Davis Date: Sun, 6 Jan 2019 21:50:08 -0700 Subject: [PATCH 14/15] Prep for 0.7.0 release --- .gitignore | 1 + iptools/__init__.py | 2 +- setup.py | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index b11bdd6..33baecb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.egg-info *.pyc /.coverage +/.python-version /.tox/ /.venv/ /build/ diff --git a/iptools/__init__.py b/iptools/__init__.py index dd0fd67..3a966f7 100644 --- a/iptools/__init__.py +++ b/iptools/__init__.py @@ -47,7 +47,7 @@ def next(iterable): from . import ipv4 from . import ipv6 -__version__ = '0.7.0-dev' +__version__ = '0.7.0' __all__ = ( 'IpRange', diff --git a/setup.py b/setup.py index 996ba0c..a09de1c 100644 --- a/setup.py +++ b/setup.py @@ -32,11 +32,11 @@ 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Utilities', 'Topic :: Internet', ], From e1cf5b12bc2e305b0525757baed3e2be6f6cbb92 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 23 Jan 2020 00:37:14 +0100 Subject: [PATCH 15/15] Travis CI: Python 2.7, 3.5, 3.6, 3.7, 3.8 (#24) * Travis CI: Python 2.7, 3.5, 3.6, 3.7, 3.8 * tox.ini: Drop py34 and Add py38 * Setup.py: Drop Py34 and Add Py38 * README.md: Python 2.7, 3.5, 3.6, 3.7, 3.8, pypy, and pypy3 Implementation by @cclauss --- .travis.yml | 9 ++------- README.md | 4 ++-- setup.py | 2 +- tox.ini | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 23adb1b..6a3e3ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,12 @@ language: python python: - "2.7" - - "3.4" - "3.5" - "3.6" + - "3.7" + - "3.8" - "pypy" - "pypy3" -sudo: false -matrix: - fast_finish: true - include: - - python: "3.7" - dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069) install: - pip install wheel tox-travis diff --git a/README.md b/README.md index 8ce94c8..3b3b4c4 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ Local documentation can be built using [Sphinx][]: Python Version Compatibility ---------------------------- -[Travis CI][ci-home] automatically runs tests against python 2.7, 3.4, 3.5, -pypy, and pypy3, +[Travis CI][ci-home] automatically runs tests against Python 2.7, 3.5, 3.6, +3.7, 3.8, pypy, and pypy3. Installation ------------ diff --git a/setup.py b/setup.py index a09de1c..551a951 100644 --- a/setup.py +++ b/setup.py @@ -33,10 +33,10 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Utilities', 'Topic :: Internet', ], diff --git a/tox.ini b/tox.ini index 4189f95..446681b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, py34, py35, py36, py37, pypy, pypy3 +envlist = py27, py35, py36, py37, py38, pypy, pypy3 [testenv] deps = -r{toxinidir}/tests/requirements.txt