[PATCH 27/31] Python IP addresses: Provide ipaddrset.py library

Ian Jackson ijackson at chiark.greenend.org.uk
Sat Sep 20 01:32:22 BST 2014


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 <ijackson at chiark.greenend.org.uk>
---
 .gitignore              |    1 +
 Makefile.in             |    7 ++-
 ipaddrset-test.expected |   12 ++++
 ipaddrset-test.py       |   43 ++++++++++++++
 ipaddrset.py            |  148 +++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 210 insertions(+), 1 deletion(-)
 create mode 100644 ipaddrset-test.expected
 create mode 100755 ipaddrset-test.py
 create mode 100644 ipaddrset.py

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 c0a6222..59e1112 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -134,7 +134,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
@@ -154,6 +154,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 ipaddrset-test.expected ipaddrset-test.new
+
 .PRECIOUS: eax-%-test
 
 installdirs:
@@ -164,6 +168,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..afab11f
--- /dev/null
+++ b/ipaddrset-test.expected
@@ -0,0 +1,12 @@
+-
+2001:23:24::/40,172.18.45.0/24
+172.18.45.192/28,172.31.80.8/32
+False
+False
+::/0,0.0.0.0/0
+True
+True
+172.18.45.192/28
+172.18.45.192/28
+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..12663bc
--- /dev/null
+++ b/ipaddrset-test.py
@@ -0,0 +1,43 @@
+#!/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.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
+print t <= s
+print t == s
+
+n=s.networks()[0]
+
+a=ipaddrset.complete_set()
+print a
+print a >= s
+print a >= t
+
+print s.intersection(t)
+print t.intersection(s)
+
+print s.union(t)
+print t.union(s)
diff --git a/ipaddrset.py b/ipaddrset.py
new file mode 100644
index 0000000..16b8fb4
--- /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):
+				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
-- 
1.7.10.4




More information about the sgo-software-discuss mailing list