/* GASH 2 IPv4Range.java Created: 4 April 2001 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Web site: http://www.arlut.utexas.edu/gash2 Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 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. 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, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.gasharl; import java.util.Enumeration; import java.util.NoSuchElementException; import java.util.Vector; import arlut.csd.ganymede.common.IPAddress; import arlut.csd.Util.StringUtils; /*------------------------------------------------------------------------------ class IPv4Range ------------------------------------------------------------------------------*/ /** * <p>This class is used to store and manipulate a definition for a * sequence of IPv4 addresses, with support for conversion to/from a * textual format, for generating an enumeration of addresses in the * IPv4Range, and more.</p> * * <p>This class supports continuous and discontinuous IP range * specification using a string that comprises multiple lines, each of * which has four dot-separated octets which may either be simple * numeric values, or a range of values, with the start and stop point * for the range separated by a dash and surrounded by brackets. The * iteration across the range implemented by the getElements() * Enumeration will vary across the least significant byte of ranged * stanzas first.</p> * * <p>In other words, the following examples would be legal:</p> * * <pre>10.2.[12-21].[1-253] * 10.5.11.[1-100]</pre> * * <p>which would start at 10.2.12.1, then 10.2.12.2, up to 10.2.12.253, then * to 10.2.13.1, on up to 10.2.21.253, and then to 10.5.11.1 and finishing * up with 10.5.11.100.</p> * * <p>and</p> * * <pre>129.116.[224-226].[253-1]</pre> * * <p>which will start at 129.116.224.253, then 129.116.224.252, and ending * up at 129.116.226.1</p> */ public class IPv4Range { /** * <p>An array of arrays of byte arrays.</p> * * <p>The individual signed Java bytes are encoded using the * IPAddress u2s() method.</p> * * <p>Note that u2s() encoding bytes can be compared for greater * than or less than, and subtracting them from each other will * result in the proper distance between them.</p> */ private byte[][][] range = null; /* -- */ public IPv4Range() { } public IPv4Range(byte[][][] initRange) { range = new byte[initRange.length][4][2]; for (int i = 0; i < initRange.length; i++) { for (int j = 0; j < 4; j++) { for (int k = 0; k < 2; k++) { range[i][j][k] = initRange[i][j][k]; } } } } public IPv4Range(byte[][] initRange) { range = new byte[1][4][2]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 2; j++) { range[0][i][j] = initRange[i][j]; } } } public IPv4Range(IPAddress networkNumber) { if (!networkNumber.isIPv4()) { throw new IllegalArgumentException("Bad number of bytes"); } byte[] netBytes = networkNumber.getBytes(); range = new byte[1][4][2]; boolean hostPortion = true; for (int i = 3; i >= 0; i--) { range[0][i][0] = netBytes[i]; range[0][i][1] = netBytes[i]; if (hostPortion) { if (range[0][i][0] == IPAddress.u2s(0)) { range[0][i][0] = IPAddress.u2s(1); range[0][i][1] = IPAddress.u2s(254); } else { hostPortion = false; } } } } public IPv4Range(String initValue) { this.setRange(initValue); } public synchronized byte[][][] getByteArray() { if (range == null) { return null; } byte[][][] _range = new byte[range.length][4][2]; for (int i = 0; i < range.length; i++) { for (int j = 0; j < 4; j++) { for (int k = 0; k < 2; k++) { _range[i][j][k] = range[i][j][k]; } } } return _range; } /** * <p>This method is used to load an IP address allocation specifier * into this IPv4Range.</p> * * <p>See the Javadocs for this class for the details on the * allowed formats for the initialization string.</p> */ public synchronized void setRange(String value) { String lines[] = StringUtils.split(value, "\n"); byte[][][] _range = new byte[lines.length][4][2]; for (int i = 0; i < lines.length; i++) { String octets[] = StringUtils.split(lines[i], "."); if (octets.length != 4) { throw new IllegalArgumentException("Error, line '" + lines[i] + "' does not contain four dot-separated byte specifiers"); } for (int j = 0; j < octets.length; j++) { if (octets[j].length() == 0) { throw new IllegalArgumentException("Error, line '" + lines[i] + "' has an empty string in byte " + j); } if (!StringUtils.containsOnly(octets[j], "0123456789[-]")) { throw new IllegalArgumentException("Error, line '" + lines[i] + "' contains invalid characters in byte " + j); } if (StringUtils.containsOnly(octets[j], "0123456789")) { // simple fixed value int val = Integer.parseInt(octets[j]); if (val > 255) { throw new IllegalArgumentException("Error, line '" + lines[i] + "' byte " + j + " is out of range."); } _range[i][j][0] = IPAddress.u2s(val); _range[i][j][1] = _range[i][j][0]; continue; } if (StringUtils.containsAll(octets[j], "[-]")) { if (!(octets[j].charAt(0) == '[' && octets[j].charAt(octets[j].length()-1) == ']')) { throw new IllegalArgumentException("Error, line '" + lines[i] + "' contains a formatting error in byte " + j); } String strippedString = octets[j].substring(1, octets[j].length()-1); String endpoints[] = StringUtils.split(strippedString, "-"); if (endpoints.length != 2) { throw new IllegalArgumentException("Error, line '" + lines[i] + "' contains a formatting error in byte " + j); } for (int k = 0; k < endpoints.length; k++) { int val = Integer.parseInt(endpoints[k]); if (val > 255) { throw new IllegalArgumentException("Error, line '" + lines[i] + "' contains a range error in byte " + j); } _range[i][j][k] = IPAddress.u2s(val); } continue; } throw new IllegalArgumentException("Error, line '" + lines[i] + "' contains an invalid element in byte " + j); } } if (!sanityCheck(_range)) { throw new IllegalArgumentException("Error, overlapping entries"); } range = _range; } /** * <p>This method returns the total number of IPv4 addresses that * this IPv4Range contains.</p> */ public synchronized int getSize() { if (range == null) { return 0; } int count = 0; for (int i = 0; i < range.length; i++) { int stanzaCount = 1; for (int j = 0; j < 4; j++) { if (range[i][j][0] < range[i][j][1]) { stanzaCount *= ((range[i][j][1] - range[i][j][0]) + 1); } else if (range[i][j][0] > range[i][j][1]) { stanzaCount *= ((range[i][j][0] - range[i][j][1]) + 1); } } count += stanzaCount; } return count; } public synchronized String toString() { StringBuilder result = new StringBuilder(); /* -- */ for (int i = 0; i < range.length; i++) { if (i > 0) { result.append("\n"); } for (int j = 0; j < 4; j++) { if (j > 0) { result.append("."); } if (range[i][j][0] == range[i][j][1]) { result.append(IPAddress.s2u(range[i][j][0])); } else { result.append("["); result.append(IPAddress.s2u(range[i][j][0])); result.append("-"); result.append(IPAddress.s2u(range[i][j][1])); result.append("]"); } } } return result.toString(); } /** * <p>This method returns an enumeration that will iterate over all * IPv4 addresses specified by this IPv4Range, in order.</p> * * <p>The returned Enumeration uses a snapshot of the state of this * IPv4Range, so changes made to this IPv4Range's state after * obtaining the Enumeration will have no effect on the addresses * generated by the Enumeration.</p> * * <p>The nextElement() method in the returned Enumeration return a * IPAddress.</p> */ public Enumeration getElements() { return new IPv4RangeEnumerator(this); } /** * <p>This method returns an enumeration that will use defined value * for the starting and stopping points in ranging across the fourth * octet of the IPv4 addresses specified by this IPv4Range. Any * addresses that would ordinarily be present in this IPv4Range will * be skipped if they do not fall between the start and stop values, * and the order in which the fourth byte will be ranged will depend * on whether start or stop is greater. The enumeration returned * will not generate any values that would not be generated by the * no parameter getElements() call with no external start and stop * values specified.</p> * * <p>The returned Enumeration uses a snapshot of the state of this * IPv4Range, so changes made to this IPv4Range's state after * obtaining the Enumeration will have no effect on the addresses * generated by the Enumeration.</p> * * <p>The nextElement() method in the returned Enumeration return an * IPAddress.</p> * * <p>If start and stop are both -1, a non-constrained Enumeration * will be return. If one but not both of start and stop are * -1, an error will result.</p> */ public Enumeration getElements(int start, int stop) { if (start == -1 && stop == -1) { return new IPv4RangeEnumerator(this); } return new IPv4RangeEnumerator(this, start, stop); } /** * <p>This method returns an enumeration that will iterate over all * discretely defined subranges of this IPv4Range, in order.</p> * * <p>In other words, if the String used to initialize this * IPv4Range consisted of multiple lines, the enumeration returned * by this method will return a sequences of IPv4Range objects, * one for each line of the original String.</p> * * <p>The returned Enumeration uses a snapshot of the state of this * IPv4Range, so changes made to this IPv4Range's state after * obtaining the Enumeration will have no effect on the IPv4Range's * generated by the returned Enumeration.</p> * * <p>The nextElement() method in the returned Enumeration returns IPv4Range * objects which each contain the equivalent of a single line's worth of * range information, relative to the string specification form used to * construct IPv4Range's.</p> */ public Enumeration getSubRanges() { return new IPv4SubRangeEnumerator(this); } /** * <p>This method returns an enumeration that will iterate over all * Class C sized (three octet prefix) network numbers available * from this IPv4Range, in order.</p> * * <p>The returned Enumeration uses a snapshot of the state of this * IPv4Range, so changes made to this IPv4Range's state after * obtaining the Enumeration will have no effect on the addresses * generated by the Enumeration.</p> * * <p>The nextElement() method in the returned Enumeration returns * an IPAddress.</p> * * <p>The fourth and final octet of each Byte array returned by nextElement() * will contain a value of -128, representing 0 for the last octet.</p> */ public Enumeration getClassCEnum() { return new IPv4ClassCEnumerator(this); } /** * <p>This method returns true if this IPv4Range and the * IPv4Range _range contain any overlaps in specified address * space.</p> */ public synchronized boolean overlapsRange(IPv4Range _range) { synchronized (_range) { for (int i = 0; i < range.length; i++) { for (int j = 0; j < _range.range.length; j++) { if (byteOverlap(range[i][0], _range.range[j][0]) && byteOverlap(range[i][1], _range.range[j][1]) && byteOverlap(range[i][2], _range.range[j][2]) && byteOverlap(range[i][3], _range.range[j][3])) { return true; } } } } return false; } /** * <p>This method returns true if address is a member of * the collection of IPv4 addresses represented by this * IPv4Range object, false otherwise.</p> */ public boolean matches(String address) { return matches(IPAddress.genIPV4bytes(address)); } /** * <p>This method returns true if address is a member of * the collection of IPv4 addresses represented by this * IPv4Range object, false otherwise.</p> * * <p>If start and stop are not equal to -1, matches will * only return true if the last octet of the address in * question is between start and stop. This is an additional * restriction on top of that specified in this IPv4Range * object.</p> */ public boolean matches(String address, int start, int stop) { return matches(IPAddress.genIPV4bytes(address), start, stop); } /** * <p>This method returns true if address is a member of * the collection of IPv4 addresses represented by this * IPv4Range object, false otherwise.</p> */ public boolean matches(IPAddress address) { return matches(address.getBytes(), -1, -1); } /** * <p>This method returns true if address is a member of * the collection of IPv4 addresses represented by this * IPv4Range object, false otherwise.</p> */ public boolean matches(byte[] address) { return matches(address, -1, -1); } /** * <p>This method returns true if address is a member of * the collection of IPv4 addresses represented by this * IPv4Range object, false otherwise.</p> * * <p>If start and stop are not equal to -1, matches will * only return true if the last octet of the address in * question is between start and stop. This is an additional * restriction on top of that specified in this IPv4Range * object.</p> */ public boolean matches(IPAddress address, int start, int stop) { return matches(address.getBytes(), start, stop); } /** * <p>This method returns true if address is a member of * the collection of IPv4 addresses represented by this * IPv4Range object, false otherwise.</p> * * <p>If start and stop are not equal to -1, matches will * only return true if the last octet of the address in * question is between start and stop. This is an additional * restriction on top of that specified in this IPv4Range * object.</p> */ public synchronized boolean matches(byte[] address, int start, int stop) { if (address.length != 4) { throw new IllegalArgumentException("bad number of bytes in address"); } boolean found = false; for (int i = 0; !found && i < range.length; i++) { if (stanzaMatch(range[i], address, start, stop)) { found = true; } } return found; } /** * <p>This method compares a given 4 signed byte address against * a specific stanza and returns true if the address could be generated * by the stanza array passed in as _range.</p> * * <p>If start and stop are not each equal to -1, stanzaMatch will * only return true if the last octet of the address in * question is between start and stop. This is an additional * restriction on top of that specified in the _range parameter.</p> */ private boolean stanzaMatch(byte _range[][], byte address[], int start, int stop) { for (int i = 0; i < 4; i++) { if ((address[i] < _range[i][0] && address[i] < _range[i][1]) || (address[i] > _range[i][0] && address[i] > _range[i][1])) { return false; } } if (start != -1 || stop != -1) { if (start > stop) { if (address[4] < stop || address[4] > start) { return false; } } else { if (address[4] < start || address[4] > stop) { return false; } } } return true; } /** * <p>This method compares a set of stanzas of a multi-line * IPv4Range speficiation, and returns true if any of the * ranges overlap at all. We want to avoid stanza overlap * so that we don't generate a single address at more than one * point in the getElements() enumeration.</p> */ private boolean sanityCheck(byte _range[][][]) { for (int i = 0; i < _range.length; i++) { for (int j = i+1; j < _range.length; j++) { if (byteOverlap(_range[i][0], _range[j][0]) && byteOverlap(_range[i][1], _range[j][1]) && byteOverlap(_range[i][2], _range[j][2]) && byteOverlap(_range[i][3], _range[j][3])) { return false; } } } return true; } /** * <p>This method returns true if there is any overlap between * the range of values specified in pair1 and the range of * values specified in pair2.</p> */ private boolean byteOverlap(byte pair1[], byte pair2[]) { if (pair1.length != 2) { throw new IllegalArgumentException("pair1 length is wrong: " + pair1.length); } if (pair2.length != 2) { throw new IllegalArgumentException("pair2 length is wrong: " + pair2.length); } if (pair1[0] == pair1[1]) { if ((pair2[0] <= pair1[0] && pair2[1] >= pair1[0]) || (pair2[0] >= pair1[0] && pair2[1] <= pair1[0])) { return true; } return false; } int firstLow = 0; int firstHigh = 0; if (pair1[0] < pair1[1]) { firstLow = pair1[0]; firstHigh = pair1[1]; } else { firstLow = pair1[1]; firstHigh = pair1[0]; } int secondLow = 0; int secondHigh = 0; if (pair2[0] < pair2[1]) { secondLow = pair2[0]; secondHigh = pair2[1]; } else { secondLow = pair2[1]; secondHigh = pair2[0]; } if (secondLow <= firstLow && secondHigh >= firstLow) { return true; } if (secondLow <= firstHigh && secondHigh >= firstHigh) { return true; } if (secondLow >= firstLow && secondHigh <= firstHigh) { return true; } return false; } } /*------------------------------------------------------------------------------ class IPv4RangeEnumerator ------------------------------------------------------------------------------*/ /** * <p>This class handles enumerating across a range of IP addresses * specified in an {@arlut.csd.ganymede.gasharl.IPv4Range IPv4Range} * object. An IPv4RangeEnumerator is created as a snapshot of the * state of an IPv4Range object, and may be iterated over even if the * source IPv4Range object is modified after it has been created.</p> */ final class IPv4RangeEnumerator implements Enumeration { /** * <p>An array of arrays of byte arrays.</p> * * <p>The individual signed Java bytes are encoded using the * IPAddress u2s() method.</p> * * <p>Note that u2s() encoding bytes can be compared for greater * than or less than, and subtracting them from each other will * result in the proper distance between them.</p> */ private byte[][][] range = null; private int line = 0; /** * <p>An array of directions, each int will be either 1 for an * ascending range, 0 for no change, or -1 for a descending * range.</p> */ private int index[] = new int[4]; /* -- */ IPv4RangeEnumerator(IPv4Range v4Range, int start, int stop) { if (start < 0 || start > 255) { throw new IllegalArgumentException("start out of range:" + start); } if (stop < 0 || stop > 255) { throw new IllegalArgumentException("stop out of range:" + stop); } byte[][][] tmpRange = v4Range.getByteArray(); int rowCount = 0; for (int i = 0; i < tmpRange.length; i++) { int firstIndex = IPAddress.s2u(tmpRange[i][3][0]); int lastIndex = IPAddress.s2u(tmpRange[i][3][1]); if (firstIndex < lastIndex) { if ((start < firstIndex && stop < firstIndex) || (start > lastIndex && stop > lastIndex)) { tmpRange[i] = null; continue; } if (start > firstIndex && start < lastIndex) { tmpRange[i][3][0] = IPAddress.u2s(start); } if (stop < lastIndex && stop > firstIndex) { tmpRange[i][3][1] = IPAddress.u2s(stop); } rowCount++; } else if (firstIndex > lastIndex) { if ((start > firstIndex && stop > firstIndex) || (start < lastIndex && stop < lastIndex)) { tmpRange[i] = null; continue; } if (start < firstIndex && start > lastIndex) { tmpRange[i][3][0] = IPAddress.u2s(start); } if (stop > lastIndex && stop < firstIndex) { tmpRange[i][3][1] = IPAddress.u2s(stop); } rowCount++; } else if (firstIndex == lastIndex) { if ((start < firstIndex && stop < firstIndex) || (start > firstIndex && stop > firstIndex)) { tmpRange[i] = null; continue; } rowCount++; } } range = new byte[rowCount][4][2]; for (int i = 0, sourceIndex = 0; i < rowCount; i++, sourceIndex++) { while (tmpRange[sourceIndex] == null) { sourceIndex++; } for (int j = 0; j < 4; j++) { range[i][j][0] = tmpRange[sourceIndex][j][0]; range[i][j][1] = tmpRange[sourceIndex][j][1]; } } } IPv4RangeEnumerator(IPv4Range v4Range) { range = v4Range.getByteArray(); for (int i = 0; i < 4; i++) { index[i] = 0; } } public boolean hasMoreElements() { return line < range.length; } public synchronized Object nextElement() { if (line >= range.length) { throw new NoSuchElementException("IPv4RangeEnumerator"); } // create the next address to return byte[] bytes = new byte[4]; for (int i = 0; i < 4; i++) { bytes[i] = (byte) (range[line][i][0] + index[i]); } IPAddress address = new IPAddress(bytes); // now update our range index variables to find the set of // offsets for the next address in our sequence boolean incremented = false; for (int i = 3; !incremented && i >= 0; i--) { if (range[line][i][1] > range[line][i][0]) { index[i]++; if (index[i] > (range[line][i][1] - range[line][i][0])) { for (int j = i; j < 4; j++) { index[j] = 0; } } else { incremented = true; } } else if (range[line][i][1] < range[line][i][0]) { index[i]--; if (index[i] < (range[line][i][1] - range[line][i][0])) { for (int j = i; j < 4; j++) { index[j] = 0; } } else { incremented = true; } } } if (!incremented) { line++; } return address; } } /*------------------------------------------------------------------------------ class IPv4SubRangeEnumerator ------------------------------------------------------------------------------*/ /** * <p>This class handles enumerating across a set of IPv4Range subsets * specified in an {@arlut.csd.ganymede.gasharl.IPv4Range IPv4Range} * object. An IPv4SubRangeEnumerator is created as a snapshot of the * state of an IPv4Range object, and may be iterated over even if the * source IPv4Range object is modified after it has been created.</p> * * <p>The nextElement() method in IPv4SubRangeEnumerator returns * IPv4Range objects which each contain the equivalent of a single * line's worth of range information, relative to the string * specification form used to construct IPv4Range's.</p> */ final class IPv4SubRangeEnumerator implements Enumeration { /** * <p>An array of arrays of byte arrays.</p> * * <p>The individual signed Java bytes are encoded using the * IPAddress u2s() method.</p> * * <p>Note that u2s() encoding bytes can be compared for greater * than or less than, and subtracting them from each other will * result in the proper distance between them.</p> */ private byte[][][] range = null; private int line = 0; /* -- */ IPv4SubRangeEnumerator(IPv4Range v4Range) { range = v4Range.getByteArray(); } public boolean hasMoreElements() { return line < range.length; } public synchronized Object nextElement() { if (line >= range.length) { throw new NoSuchElementException("IPv4SubRangeEnumerator"); } return new IPv4Range(range[line++]); } } /*------------------------------------------------------------------------------ class IPv4ClassCEnumerator ------------------------------------------------------------------------------*/ /** * <p>This class handles enumerating across a set of Class C sized * (three octet prefix) subsets specified in an * {@arlut.csd.ganymede.gasharl.IPv4Range IPv4Range} object. An * IPv4ClassCEnumerator is created as a snapshot of the state of an * IPv4Range object, and may be iterated over even if the source * IPv4Range object is modified after it has been created.</p> * * <p>The nextElement() method in IPv4ClassCEnumerator returns Byte[] * arrays which contain four elements, one for each of the four octets * of an IPv4 address. The elements hold values in the range -128 to * 127, which are equivalent to the 0 to 255 range for IPv4 octets. * These numbers can be converted to the appropriate number for IPv4 * by adding 128 to each element.</p> * * <p>The fourth and final octet of each Byte array returned by * nextElement() will contain a value of -128, representing 0 for the * last octet.</p> */ final class IPv4ClassCEnumerator implements Enumeration { /** * <p>An array of arrays of byte arrays.</p> * * <p>The individual signed Java bytes are encoded using the * IPAddress u2s() method.</p> * * <p>Note that u2s() encoding bytes can be compared for greater * than or less than, and subtracting them from each other will * result in the proper distance between them.</p> */ private byte[][][] range = null; private int line = 0; /** * <p>An array of directions, each int will be either 1 for an * ascending range, 0 for no change, or -1 for a descending * range.</p> */ private int index[] = new int[3]; /* -- */ IPv4ClassCEnumerator(IPv4Range v4Range) { range = v4Range.getByteArray(); for (int i = 0; i < 3; i++) { index[i] = 0; } } public boolean hasMoreElements() { return line < range.length; } public synchronized Object nextElement() { if (line >= range.length) { throw new NoSuchElementException("IPv4ClassCEnumerator"); } // create the next Class C-sized subnet to return byte[] bytes = new byte[4]; for (int i = 0; i < 3; i++) { bytes[i] = (byte) (range[line][i][0] + index[i]); } bytes[3] = IPAddress.u2s(0); IPAddress address = new IPAddress(bytes); // now update our range index variables to find the set of // offsets for the next address in our sequence boolean incremented = false; for (int i = 2; !incremented && i >= 0; i--) { if (range[line][i][1] > range[line][i][0]) { index[i]++; if (index[i] > (range[line][i][1] - range[line][i][0])) { for (int j = i; j < 3; j++) { index[j] = 0; } } else { incremented = true; } } else if (range[line][i][1] < range[line][i][0]) { index[i]--; if (index[i] < (range[line][i][1] - range[line][i][0])) { for (int j = i; j < 3; j++) { index[j] = 0; } } else { incremented = true; } } } if (!incremented) { line++; } return address; } }