From d852a02717c78a52d7f7a9ce835e2a435edb300c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 21 Jun 2017 14:35:06 +0200 Subject: [PATCH] android: Add class to manage a set of IP address ranges/subnets --- .../strongswan/android/utils/IPRangeSet.java | 147 ++++++++++++++ .../android/test/IPRangeSetTest.java | 192 ++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRangeSet.java create mode 100644 src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRangeSet.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRangeSet.java new file mode 100644 index 000000000..60e468bb3 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRangeSet.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2012-2017 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can 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 of the License, or (at your + * option) any later version. See . + * + * 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. + */ + +package org.strongswan.android.utils; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; + +/** + * Class that represents a set of IP address ranges (not necessarily proper subnets) and allows + * modifying the set and enumerating the resulting subnets. + */ +public class IPRangeSet +{ + private TreeSet mRanges = new TreeSet<>(); + + /** + * Parse the given string (space separated subnets in CIDR notation) and return the resulting + * set or {@code null} if the string was invalid. And empty set is returned if the given string + * is {@code null}. + */ + public static IPRangeSet fromString(String ranges) + { + IPRangeSet set = new IPRangeSet(); + if (ranges != null) + { + for (String range : ranges.split("\\s+")) + { + try + { + set.add(new IPRange(range)); + } + catch (Exception unused) + { /* besides due to invalid strings exceptions might get thrown if the string + * contains a hostname (NetworkOnMainThreadException) */ + return null; + } + } + } + return set; + } + + /** + * Add a range to this set. Automatically gets merged with existing ranges. + */ + public void add(IPRange range) + { + if (mRanges.contains(range)) + { + return; + } + reinsert: + while (true) + { + Iterator iterator = mRanges.iterator(); + while (iterator.hasNext()) + { + IPRange existing = iterator.next(); + IPRange replacement = existing.merge(range); + if (replacement != null) + { + iterator.remove(); + range = replacement; + continue reinsert; + } + } + mRanges.add(range); + break; + } + } + + /** + * Remove the given range from this set. Existing ranges are automatically adjusted. + */ + public void remove(IPRange range) + { + ArrayList additions = new ArrayList<>(); + Iterator iterator = mRanges.iterator(); + while (iterator.hasNext()) + { + IPRange existing = iterator.next(); + List result = existing.remove(range); + if (result.size() == 0) + { + iterator.remove(); + } + else if (!result.get(0).equals(existing)) + { + iterator.remove(); + additions.addAll(result); + } + } + mRanges.addAll(additions); + } + + /** + * Returns the subnets derived from all the ranges in this set. + */ + public Enumeration getSubnets() + { + return new Enumeration() + { + private Iterator mIterator = mRanges.iterator(); + private List mSubnets; + + @Override + public boolean hasMoreElements() + { + return (mSubnets != null && mSubnets.size() > 0) || mIterator.hasNext(); + } + + @Override + public IPRange nextElement() + { + if (mSubnets == null || mSubnets.size() == 0) + { + IPRange range = mIterator.next(); + mSubnets = range.toSubnets(); + } + return mSubnets.remove(0); + } + }; + } + + /** + * Returns the number of ranges, not subnets. + */ + public int size() + { + return mRanges.size(); + } +} diff --git a/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java b/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java new file mode 100644 index 000000000..d53d12714 --- /dev/null +++ b/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2017 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can 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 of the License, or (at your + * option) any later version. See . + * + * 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. + */ + +package org.strongswan.android.test; + +import org.junit.Test; +import org.strongswan.android.utils.IPRange; +import org.strongswan.android.utils.IPRangeSet; + +import java.net.UnknownHostException; +import java.util.Enumeration; + +import static org.junit.Assert.assertEquals; + +public class IPRangeSetTest +{ + private void assertSubnets(Enumeration subnets, IPRange...exp) + { + if (exp.length == 0) + { + assertEquals("no subnets", false, subnets.hasMoreElements()); + return; + } + for (int i = 0; i < exp.length; i++) + { + assertEquals("has subnet", true, subnets.hasMoreElements()); + assertEquals("range", exp[i], subnets.nextElement()); + } + assertEquals("done", false, subnets.hasMoreElements()); + } + + @Test + public void testEmpty() + { + IPRangeSet set = new IPRangeSet(); + assertEquals("size", 0, set.size()); + assertSubnets(set.getSubnets()); + } + + @Test + public void testAddDistinct() throws UnknownHostException + { + IPRangeSet set = new IPRangeSet(); + set.add(new IPRange("192.168.1.0/24")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24")); + set.add(new IPRange("10.0.1.0/24")); + assertEquals("size", 2, set.size()); + assertSubnets(set.getSubnets(), new IPRange("10.0.1.0/24"), new IPRange("192.168.1.0/24")); + } + + @Test + public void testAddAdjacent() throws UnknownHostException + { + IPRangeSet set = new IPRangeSet(); + set.add(new IPRange("192.168.1.0/24")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24")); + set.add(new IPRange("192.168.2.0/24")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"), new IPRange("192.168.2.0/24")); + } + + @Test + public void testAddAdjacentJoin() throws UnknownHostException + { + IPRangeSet set = new IPRangeSet(); + set.add(new IPRange("192.168.1.0/24")); + set.add(new IPRange("192.168.3.0/24")); + assertEquals("size", 2, set.size()); + set.add(new IPRange("192.168.2.0/24")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24"), new IPRange("192.168.2.0/23")); + } + + @Test + public void testAddSame() throws UnknownHostException + { + IPRangeSet set = new IPRangeSet(); + set.add(new IPRange("192.168.1.0/24")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24")); + set.add(new IPRange("192.168.1.0/24")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24")); + } + + @Test + public void testAddLarger() throws UnknownHostException + { + IPRangeSet set = new IPRangeSet(); + set.add(new IPRange("192.168.1.0/24")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24")); + set.add(new IPRange("192.168.0.0/16")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.0.0/16")); + set.add(new IPRange("0.0.0.0/0")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("0.0.0.0/0")); + } + + @Test + public void testAddLargerMulti() throws UnknownHostException + { + IPRangeSet set = new IPRangeSet(); + set.add(new IPRange("192.168.1.0/24")); + set.add(new IPRange("10.0.1.0/24")); + set.add(new IPRange("255.255.255.255/32")); + assertEquals("size", 3, set.size()); + set.add(new IPRange("0.0.0.0/0")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("0.0.0.0/0")); + } + + @Test + public void testRemoveNothing() throws UnknownHostException + { + IPRangeSet set = new IPRangeSet(); + set.add(new IPRange("192.168.1.0/24")); + set.remove(new IPRange("192.168.2.0/24")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24")); + set.remove(new IPRange("10.0.1.0/24")); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24")); + } + + @Test + public void testRemoveAll() throws UnknownHostException + { + IPRangeSet set = new IPRangeSet(); + set.add(new IPRange("192.168.1.0/24")); + set.remove(new IPRange("192.168.0.0/16")); + assertEquals("size", 0, set.size()); + assertSubnets(set.getSubnets()); + set.add(new IPRange("192.168.1.0/24")); + set.add(new IPRange("10.0.1.0/24")); + set.add(new IPRange("255.255.255.255/32")); + assertEquals("size", 3, set.size()); + set.remove(new IPRange("0.0.0.0/0")); + assertEquals("size", 0, set.size()); + assertSubnets(set.getSubnets()); + } + + @Test + public void testRemoveOverlap() throws UnknownHostException + { + IPRangeSet set = new IPRangeSet(); + set.add(new IPRange("192.168.1.0/24")); + set.add(new IPRange("192.168.2.0/24")); + set.remove(new IPRange("192.168.1.128", "192.168.2.127")); + assertEquals("size", 2, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/25"), new IPRange("192.168.2.128/25")); + } + + @Test + public void testFromStringSingle() throws UnknownHostException + { + IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24"); + assertEquals("size", 1, set.size()); + assertSubnets(set.getSubnets(), new IPRange("192.168.1.0/24")); + } + + @Test + public void testFromString() throws UnknownHostException + { + IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24 fec1::/64 10.0.1.0/24 255.255.255.255"); + assertEquals("size", 4, set.size()); + assertSubnets(set.getSubnets(), new IPRange("10.0.1.0/24"), new IPRange("192.168.1.0/24"), + new IPRange("255.255.255.255/32"), new IPRange("fec1::/64")); + } + + @Test + public void testFromStringInvalidRange() throws UnknownHostException + { + IPRangeSet set = IPRangeSet.fromString("192.168.1.0/65"); + assertEquals("failed", null, set); + } +}