From: Ian Jackson Date: Sun, 14 Sep 2014 21:40:52 +0000 (+0100) Subject: Python IP addresses: Provide ipaddrset.py library X-Git-Tag: base.polypath.v4~7 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=secnet.git;a=commitdiff_plain;h=6437945aefa308c06ab14da291c5d5489c25b393 Python IP addresses: Provide ipaddrset.py library This library module provides a class for a set of IP addresses, stored as a list of netmasks. This is in terms of the modern `ipaddr' module by Scott Kitterman. In this commit we introduce the ipaddrset.py module and its test module. We also patch the Makefile to install it, and test in `make check' that it produces the expected output. However, due to the presence of the old Cendio ipaddr.py alongside, the provided ipaddrset-test.py needs some hideous hacking of sys.path if it is to work when run in the ordinary way inside the secnet source tree. This will be removed in a later patch. Signed-off-by: Ian Jackson --- diff --git a/.gitignore b/.gitignore index a445369..b5e9399 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ conffile.yy.[ch] /secnet /eax-*-test /eax-*-test.confirm +/ipaddrset-test.new /config.log /config.h diff --git a/Makefile.in b/Makefile.in index 3c4ae92..522a4d0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -130,7 +130,7 @@ secnet: $(wildcard .git/packed-refs) endif check: eax-aes-test.confirm eax-serpent-test.confirm \ - eax-serpentbe-test.confirm + eax-serpentbe-test.confirm check-ipaddrset version.c: Makefile echo "#include \"secnet.h\"" >$@.new @@ -150,6 +150,10 @@ eax-%-test.confirm: eax-%-test eax-%-test.vectors ./$< <$(srcdir)/eax-$*-test.vectors >$@.new mv -f $@.new $@ +check-ipaddrset: ipaddrset-test.py ipaddrset.py ipaddrset-test.expected + $(srcdir)/ipaddrset-test.py >ipaddrset-test.new + diff -u ipaddrset-test.expected ipaddrset-test.new + .PRECIOUS: eax-%-test installdirs: @@ -160,6 +164,7 @@ install: installdirs $(INSTALL_PROGRAM) secnet $(sbindir)/`echo secnet|sed '$(transform)'` $(INSTALL_PROGRAM) ${srcdir}/make-secnet-sites $(sbindir)/`echo make-secnet-sites|sed '$(transform)'` $(INSTALL) ${srcdir}/ipaddr.py $(prefix)/share/secnet/ipaddr.py + $(INSTALL) ${srcdir}/ipaddrset.py $(prefix)/share/secnet/ipaddrset.py $(INSTALL) secnet.8 $(mandir)/man8/secnet.8 clean: diff --git a/ipaddrset-test.expected b/ipaddrset-test.expected new file mode 100644 index 0000000..12d49e8 --- /dev/null +++ b/ipaddrset-test.expected @@ -0,0 +1,23 @@ +s = - +2001:23:24::/40,172.18.45.0/24 +t = 172.18.45.192/28,172.31.80.8/32 +False +False +172.18.44.0/23 +False +False +172.18.45.6/32 +True +False +172.18.45.0/24 +True +False +a = ::/0,0.0.0.0/0 +True +True +^ +172.18.45.192/28 +172.18.45.192/28 +u +2001:23:24::/40,172.18.45.0/24,172.31.80.8/32 +2001:23:24::/40,172.18.45.0/24,172.31.80.8/32 diff --git a/ipaddrset-test.py b/ipaddrset-test.py new file mode 100755 index 0000000..290144b --- /dev/null +++ b/ipaddrset-test.py @@ -0,0 +1,51 @@ +#!/usr/bin/python + +import sys +import copy +import os +saved_path=copy.copy(sys.path) +for p in ['','.',os.getcwd()]: + try: sys.path.remove(p) + except ValueError: pass + +import ipaddr +from ipaddr import IPNetwork, IPAddress + +sys.path=saved_path + +import ipaddrset +from ipaddrset import IPAddressSet + +v4a=IPAddress('172.18.45.6') + +s=IPAddressSet() +print 's =', s +s.append([IPNetwork('172.18.45.0/24')]) +s.append([IPNetwork('2001:23:24::/40')]) +print s + +t=IPAddressSet(map(IPNetwork,['172.31.80.8/32','172.18.45.192/28'])) +print 't =', t +print t <= s +print t == s + +for n1s in ['172.18.44.0/23','172.18.45.6/32','172.18.45.0/24']: + n1=IPNetwork(n1s) + print n1 + print s.contains(n1) + print t.contains(n1) + +n=s.networks()[0] + +a=ipaddrset.complete_set() +print 'a =', a +print a >= s +print a >= t + +print '^' +print s.intersection(t) +print t.intersection(s) + +print 'u' +print s.union(t) +print t.union(s) diff --git a/ipaddrset.py b/ipaddrset.py new file mode 100644 index 0000000..8ccc0ea --- /dev/null +++ b/ipaddrset.py @@ -0,0 +1,148 @@ +"""IP address set manipulation, built on top of ipaddr.py""" + +# Copyright 2014 Ian Jackson +# +# This file is Free Software. It was originally written for secnet. +# +# You may redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import ipaddr + +_vsns = [6,4] + +class IPAddressSet: + "A set of IP addresses" + + # constructors + def __init__(self,l=[]): + "New set contains each ipaddr.IPNetwork in the sequence l" + self._v = {} + for v in _vsns: + self._v[v] = [ ] + self.append(l) + + # housekeeping and representation + def _compact(self): + for v in _vsns: + self._v[v] = ipaddr.collapse_address_list(self._v[v]) + def __repr__(self): + return "IPAddressSet(%s)" % self.networks() + def str(self,comma=",",none="-"): + "Human-readable string with controllable delimiters" + if self: + return comma.join(map(str, self.networks())) + else: + return none + def __str__(self): + return self.str() + + # mutators + def append(self,l): + "Appends each ipaddr.IPNetwork in the sequence l to self" + self._append(l) + self._compact() + + def _append(self,l): + "Appends each ipaddr.IPNetwork in the sequence l to self" + for a in l: + self._v[a.version].append(a) + + # enquirers including standard comparisons + def __nonzero__(self): + for v in _vsns: + if self._v[v]: + return True + return False + + def __eq__(self,other): + for v in _vsns: + if self._v[v] != other._v[v]: + return False + return True + def __ne__(self,other): return not self.__eq__(other) + def __ge__(self,other): + """True iff self completely contains IPAddressSet other""" + for o in other: + if not self._contains_net(o): + return False + return True + def __le__(self,other): return other.__ge__(self) + def __gt__(self,other): return self!=other and other.__ge__(self) + def __lt__(self,other): return other.__gt__(self) + + def __cmp__(self,other): + if self==other: return 0 + if self>=other: return +1 + if self<=other: return -1 + return NotImplemented + + def __iter__(self): + "Iterates over minimal list of distinct IPNetworks in this set" + for v in _vsns: + for i in self._v[v]: + yield i + + def networks(self): + "Returns miminal list of distinct IPNetworks in this set" + return [i for i in self] + + # set operations + def intersection(self,other): + "Returns the intersection; does not modify self" + r = IPAddressSet() + for v in _vsns: + for i in self._v[v]: + for j in other._v[v]: + if i.overlaps(j): + if i.prefixlen > j.prefixlen: + r._append([i]) + else: + r._append([j]) + return r + def union(self,other): + "Returns the union; does not modify self" + r = IPAddressSet() + r._append(self.networks()) + r._append(other.networks()) + r._compact() + return r + + def _contains_net(self,n): + """True iff self completely contains IPNetwork n""" + for i in self: + if i.overlaps(n) and n.prefixlen >= i.prefixlen: + return True + return False + + def contains(self,thing): + """Returns True iff self completely contains thing. + thing may be an IPNetwork or an IPAddressSet""" + try: + v = [thing.version] + except KeyError: + v = None + if v: + return self._contains_net(ipaddr.IPNetwork(thing)) + else: + return self.__ge__(thing) + +def complete_set(): + "Returns a set containing all addresses" + s=IPAddressSet() + for v in _vsns: + a=ipaddr.IPAddress(0,v) + n=ipaddr.IPNetwork("%s/0" % a) + s.append([n]) + return s