You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by rw...@apache.org on 2008/03/02 17:51:34 UTC

svn commit: r632783 - in /commons/proper/net/branches/NET_2_0/src: main/java/org/apache/commons/net/util/SubnetUtils.java test/java/org/apache/commons/net/TestSubnetUtils.java

Author: rwinston
Date: Sun Mar  2 08:51:33 2008
New Revision: 632783

URL: http://svn.apache.org/viewvc?rev=632783&view=rev
Log:
Add SubnetUtils class (NET-189)

Added:
    commons/proper/net/branches/NET_2_0/src/main/java/org/apache/commons/net/util/SubnetUtils.java
    commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/TestSubnetUtils.java

Added: commons/proper/net/branches/NET_2_0/src/main/java/org/apache/commons/net/util/SubnetUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/net/branches/NET_2_0/src/main/java/org/apache/commons/net/util/SubnetUtils.java?rev=632783&view=auto
==============================================================================
--- commons/proper/net/branches/NET_2_0/src/main/java/org/apache/commons/net/util/SubnetUtils.java (added)
+++ commons/proper/net/branches/NET_2_0/src/main/java/org/apache/commons/net/util/SubnetUtils.java Sun Mar  2 08:51:33 2008
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.net.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A class that performs some subnet calculations given a network address and a subnet mask. 
+ * @see http://www.faqs.org/rfcs/rfc1519.html
+ * @author <rw...@apache.org>
+ */
+public class SubnetUtils {
+	
+	private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
+	private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,3})";
+	private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS);
+	private static final Pattern cidrPattern = Pattern.compile(SLASH_FORMAT);
+	private static final int NBITS = 32;
+	
+	private int netmask = 0;
+	private int address = 0;
+	private int network = 0;
+	private int broadcast = 0;
+	
+	/**
+	 * Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16"
+	 * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16"
+	 */
+	public SubnetUtils(String cidrNotation) {
+		calculate(cidrNotation);
+	}
+	
+	/**
+	 * Constructor that takes two dotted decimal addresses. 
+	 * @param address An IP address, e.g. "192.168.0.1"
+	 * @param mask A dotted decimal netmask e.g. "255.255.0.0"
+	 */
+	public SubnetUtils(String address, String mask) {
+		calculate(toCidrNotation(address, mask));
+	}
+	
+	/**
+	 * Convenience container for subnet summary information.
+	 *
+	 */
+	public final class SubnetInfo {
+		private int netmask() 		{ return netmask; }
+		private int network() 		{ return network; }
+		private int address() 		{ return address; }
+		private int broadcast() 	{ return broadcast; }
+		private int low()			{ return network() + 1; }
+		private int high()			{ return broadcast() - 1; }
+
+		public boolean isInRange(String address) { return isInRange(toInteger(address)); }
+		private boolean isInRange(int address)   { return ((address-low()) <= (high()-low())); }
+		
+		public String getBroadcastAddress() { return format(toArray(broadcast())); }
+		
+		public String getNetworkAddress() { return format(toArray(network())); }
+		public String getLowAddress() { return format(toArray(low())); }
+		public String getHighAddress() { return format(toArray(high())); }
+		public int getAddressCount() { return (broadcast() - low()); }
+		
+		public String getCidrSignature() { 
+			return toCidrNotation(
+				format(toArray(address())), 
+				format(toArray(netmask()))
+				);
+		}
+	}
+	
+	/**
+	 * Return a {@link SubnetInfo} instance that contains subnet-specific statistics
+	 * @return
+	 */
+	public final SubnetInfo getInfo() { return new SubnetInfo(); }
+
+	/*
+	 * Initialize the internal fields from the supplied CIDR mask
+	 */
+	private void calculate(String mask) {
+		Matcher matcher = cidrPattern.matcher(mask);
+		
+		if (matcher.matches()) {
+			address = matchAddress(matcher);
+			
+			/* Create a binary netmask from the number of bits specification /x */
+			int cidrPart = rangeCheck(Integer.valueOf(matcher.group(5)), 0, NBITS-1);
+			for (int j = 0; j < cidrPart; ++j) {
+				netmask |= (1 << 31-j);
+			}
+			
+			/* Calculate base network address */
+			network = (address & netmask);
+			
+			/* Calculate broadcast address */
+			broadcast = network | ~(netmask);
+		}
+		else 
+			throw new IllegalArgumentException("Could not parse [" + mask + "]");
+	}
+	
+	/*
+	 * Convert a dotted decimal format address to a packed integer format
+	 */
+	private int toInteger(String address) {
+		Matcher matcher = addressPattern.matcher(address);
+		if (matcher.matches()) {
+			return matchAddress(matcher);
+		}
+		else
+			throw new IllegalArgumentException("Could not parse [" + address + "]");
+	}
+
+	/*
+	 * Convenience method to extract the components of a dotted decimal address and 
+	 * pack into an integer using a regex match
+	 */
+	private int matchAddress(Matcher matcher) {
+		int addr = 0;
+		for (int i = 1; i <= 4; ++i) { 
+			int n = (rangeCheck(Integer.valueOf(matcher.group(i)), 0, 255));
+			addr |= ((n & 0xff) << 8*(4-i));
+		}
+		return addr;
+	}
+		
+	/*
+	 * Convert a packed integer address into a 4-element array
+	 */
+	private int[] toArray(int val) {
+		int ret[] = new int[4];
+		for (int j = 3; j >= 0; --j)
+			ret[j] |= ((val >>> 8*(3-j)) & (0xff));
+		return ret;
+	}
+
+	/*
+	 * Convert a 4-element array into dotted decimal format
+	 */
+	private String format(int[] octets) {
+		StringBuilder str = new StringBuilder();
+		for (int i =0; i < octets.length; ++i)
+			str.append((i == octets.length - 1) 
+					? octets[i] : octets[i] + ".");
+		return str.toString();
+	}
+
+	/*
+	 * Convenience function to check integer boundaries
+	 */
+	private int rangeCheck(Integer value, int begin, int end) {
+		if (value >= begin && value <= end)
+			return value;
+		
+		throw new IllegalArgumentException("Value out of range: [" + value + "]");
+	}
+	
+	/*
+	 * Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy
+	 * see Hacker's Delight section 5.1 
+	 */
+	int pop(int x) {
+		   x = x - ((x >>> 1) & 0x55555555); 
+		   x = (x & 0x33333333) + ((x >>> 2) & 0x33333333); 
+		   x = (x + (x >>> 4)) & 0x0F0F0F0F; 
+		   x = x + (x >>> 8); 
+		   x = x + (x >>> 16); 
+		   return x & 0x0000003F; 
+	} 
+
+	/* Convert two dotted decimal addresses to a single xxx.xxx.xxx.xxx/yy format
+	 * by counting the 1-bit population in the mask address. (It may be better to count 
+	 * NBITS-#trailing zeroes for this case)
+	 */
+	private String toCidrNotation(String addr, String mask) {	
+		return addr + "/" + pop(toInteger(mask));
+	}
+
+}

Added: commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/TestSubnetUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/TestSubnetUtils.java?rev=632783&view=auto
==============================================================================
--- commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/TestSubnetUtils.java (added)
+++ commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/TestSubnetUtils.java Sun Mar  2 08:51:33 2008
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+
+import org.apache.commons.net.util.SubnetUtils;
+import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
+
+import junit.framework.TestCase;
+
+public class TestSubnetUtils extends TestCase {
+	
+	public void testParseSimpleNetmask() {
+		final String address = "192.168.0.1";
+		final String masks[] = new String[] { "255.0.0.0", "255.255.0.0", "255.255.255.0", "255.255.255.248"};
+		final String bcastAddresses[] = new String[] { "192.255.255.255", "192.168.255.255", "192.168.0.255", "192.168.0.7"};
+		final String lowAddresses[] = new String[] { "192.0.0.1", "192.168.0.1", "192.168.0.1", "192.168.0.1" };
+		final String highAddresses[] = new String[] { "192.255.255.254", "192.168.255.254", "192.168.0.254", "192.168.0.6" };
+		final String networkAddresses[] = new String[] { "192.0.0.0", "192.168.0.0", "192.168.0.0", "192.168.0.0" };
+		final String cidrSignatures[] = new String[] { "192.168.0.1/8", "192.168.0.1/16", "192.168.0.1/24", "192.168.0.1/29" };
+		final int usableAddresses[] = new int[] { 16777214, 65534, 254, 6 };
+		
+		for (int i = 0; i < masks.length; ++i) {
+			SubnetUtils utils = new SubnetUtils(address, masks[i]);
+			SubnetInfo info = utils.getInfo();
+			assertEquals(bcastAddresses[i], info.getBroadcastAddress());
+			assertEquals(cidrSignatures[i], info.getCidrSignature());
+			assertEquals(lowAddresses[i], info.getLowAddress());
+			assertEquals(highAddresses[i], info.getHighAddress());
+			assertEquals(networkAddresses[i], info.getNetworkAddress());
+			assertEquals(usableAddresses[i], info.getAddressCount());
+		}
+	}
+	
+	public void testAddresses() {
+		SubnetUtils utils = new SubnetUtils("192.168.0.1/29");
+		SubnetInfo info = utils.getInfo();
+		assertTrue(info.isInRange("192.168.0.1"));
+		// We dont count the broadcast address as usable
+		assertFalse(info.isInRange("192.168.0.7"));
+		assertFalse(info.isInRange("192.168.0.8"));
+		assertFalse(info.isInRange("10.10.2.1"));
+		assertFalse(info.isInRange("192.168.1.1"));
+		assertFalse(info.isInRange("192.168.0.255"));
+	}
+}