001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.net.util; 018 019import java.util.regex.Matcher; 020import java.util.regex.Pattern; 021 022/** 023 * A class that performs some subnet calculations given a network address and a subnet mask. 024 * @see "http://www.faqs.org/rfcs/rfc1519.html" 025 * @since 2.0 026 */ 027public class SubnetUtils { 028 029 private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})"; 030 private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,3})"; 031 private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS); 032 private static final Pattern cidrPattern = Pattern.compile(SLASH_FORMAT); 033 private static final int NBITS = 32; 034 035 private int netmask = 0; 036 private int address = 0; 037 private int network = 0; 038 private int broadcast = 0; 039 040 /** Whether the broadcast/network address are included in host count */ 041 private boolean inclusiveHostCount = false; 042 043 044 /** 045 * Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16" 046 * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16" 047 * @throws IllegalArgumentException if the parameter is invalid, 048 * i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-3 decimal digits in range 1-32 049 */ 050 public SubnetUtils(String cidrNotation) { 051 calculate(cidrNotation); 052 } 053 054 /** 055 * Constructor that takes a dotted decimal address and a dotted decimal mask. 056 * @param address An IP address, e.g. "192.168.0.1" 057 * @param mask A dotted decimal netmask e.g. "255.255.0.0" 058 * @throws IllegalArgumentException if the address or mask is invalid, 059 * i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros 060 */ 061 public SubnetUtils(String address, String mask) { 062 calculate(toCidrNotation(address, mask)); 063 } 064 065 066 /** 067 * Returns <code>true</code> if the return value of {@link SubnetInfo#getAddressCount()} 068 * includes the network and broadcast addresses. 069 * @since 2.2 070 * @return true if the hostcount includes the network and broadcast addresses 071 */ 072 public boolean isInclusiveHostCount() { 073 return inclusiveHostCount; 074 } 075 076 /** 077 * Set to <code>true</code> if you want the return value of {@link SubnetInfo#getAddressCount()} 078 * to include the network and broadcast addresses. 079 * @param inclusiveHostCount true if network and broadcast addresses are to be included 080 * @since 2.2 081 */ 082 public void setInclusiveHostCount(boolean inclusiveHostCount) { 083 this.inclusiveHostCount = inclusiveHostCount; 084 } 085 086 087 088 /** 089 * Convenience container for subnet summary information. 090 * 091 */ 092 public final class SubnetInfo { 093 /* Mask to convert unsigned int to a long (i.e. keep 32 bits) */ 094 private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL; 095 096 private SubnetInfo() {} 097 098 private int netmask() { return netmask; } 099 private int network() { return network; } 100 private int address() { return address; } 101 private int broadcast() { return broadcast; } 102 103 // long versions of the values (as unsigned int) which are more suitable for range checking 104 private long networkLong() { return network & UNSIGNED_INT_MASK; } 105 private long broadcastLong(){ return broadcast & UNSIGNED_INT_MASK; } 106 107 private int low() { 108 return (isInclusiveHostCount() ? network() : 109 broadcastLong() - networkLong() > 1 ? network() + 1 : 0); 110 } 111 112 private int high() { 113 return (isInclusiveHostCount() ? broadcast() : 114 broadcastLong() - networkLong() > 1 ? broadcast() -1 : 0); 115 } 116 117 /** 118 * Returns true if the parameter <code>address</code> is in the 119 * range of usable endpoint addresses for this subnet. This excludes the 120 * network and broadcast adresses. 121 * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1" 122 * @return True if in range, false otherwise 123 */ 124 public boolean isInRange(String address) { 125 return isInRange(toInteger(address)); 126 } 127 128 /** 129 * 130 * @param address the address to check 131 * @return true if it is in range 132 * @since 3.4 (made public) 133 */ 134 public boolean isInRange(int address) { 135 long addLong = address & UNSIGNED_INT_MASK; 136 long lowLong = low() & UNSIGNED_INT_MASK; 137 long highLong = high() & UNSIGNED_INT_MASK; 138 return addLong >= lowLong && addLong <= highLong; 139 } 140 141 public String getBroadcastAddress() { 142 return format(toArray(broadcast())); 143 } 144 145 public String getNetworkAddress() { 146 return format(toArray(network())); 147 } 148 149 public String getNetmask() { 150 return format(toArray(netmask())); 151 } 152 153 public String getAddress() { 154 return format(toArray(address())); 155 } 156 157 /** 158 * Return the low address as a dotted IP address. 159 * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. 160 * 161 * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address 162 */ 163 public String getLowAddress() { 164 return format(toArray(low())); 165 } 166 167 /** 168 * Return the high address as a dotted IP address. 169 * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. 170 * 171 * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address 172 */ 173 public String getHighAddress() { 174 return format(toArray(high())); 175 } 176 177 /** 178 * Get the count of available addresses. 179 * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. 180 * @return the count of addresses, may be zero. 181 * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE} 182 * @deprecated (3.4) use {@link #getAddressCountLong()} instead 183 */ 184 @Deprecated 185 public int getAddressCount() { 186 long countLong = getAddressCountLong(); 187 if (countLong > Integer.MAX_VALUE) { 188 throw new RuntimeException("Count is larger than an integer: " + countLong); 189 } 190 // N.B. cannot be negative 191 return (int)countLong; 192 } 193 194 /** 195 * Get the count of available addresses. 196 * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. 197 * @return the count of addresses, may be zero. 198 * @since 3.4 199 */ 200 public long getAddressCountLong() { 201 long b = broadcastLong(); 202 long n = networkLong(); 203 long count = b - n + (isInclusiveHostCount() ? 1 : -1); 204 return count < 0 ? 0 : count; 205 } 206 207 public int asInteger(String address) { 208 return toInteger(address); 209 } 210 211 public String getCidrSignature() { 212 return toCidrNotation( 213 format(toArray(address())), 214 format(toArray(netmask())) 215 ); 216 } 217 218 public String[] getAllAddresses() { 219 int ct = getAddressCount(); 220 String[] addresses = new String[ct]; 221 if (ct == 0) { 222 return addresses; 223 } 224 for (int add = low(), j=0; add <= high(); ++add, ++j) { 225 addresses[j] = format(toArray(add)); 226 } 227 return addresses; 228 } 229 230 /** 231 * {@inheritDoc} 232 * @since 2.2 233 */ 234 @Override 235 public String toString() { 236 final StringBuilder buf = new StringBuilder(); 237 buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]") 238 .append(" Netmask: [").append(getNetmask()).append("]\n") 239 .append("Network:\t[").append(getNetworkAddress()).append("]\n") 240 .append("Broadcast:\t[").append(getBroadcastAddress()).append("]\n") 241 .append("First Address:\t[").append(getLowAddress()).append("]\n") 242 .append("Last Address:\t[").append(getHighAddress()).append("]\n") 243 .append("# Addresses:\t[").append(getAddressCount()).append("]\n"); 244 return buf.toString(); 245 } 246 } 247 248 /** 249 * Return a {@link SubnetInfo} instance that contains subnet-specific statistics 250 * @return new instance 251 */ 252 public final SubnetInfo getInfo() { return new SubnetInfo(); } 253 254 /* 255 * Initialize the internal fields from the supplied CIDR mask 256 */ 257 private void calculate(String mask) { 258 Matcher matcher = cidrPattern.matcher(mask); 259 260 if (matcher.matches()) { 261 address = matchAddress(matcher); 262 263 /* Create a binary netmask from the number of bits specification /x */ 264 int cidrPart = rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS); 265 for (int j = 0; j < cidrPart; ++j) { 266 netmask |= (1 << 31 - j); 267 } 268 269 /* Calculate base network address */ 270 network = (address & netmask); 271 272 /* Calculate broadcast address */ 273 broadcast = network | ~(netmask); 274 } else { 275 throw new IllegalArgumentException("Could not parse [" + mask + "]"); 276 } 277 } 278 279 /* 280 * Convert a dotted decimal format address to a packed integer format 281 */ 282 private int toInteger(String address) { 283 Matcher matcher = addressPattern.matcher(address); 284 if (matcher.matches()) { 285 return matchAddress(matcher); 286 } else { 287 throw new IllegalArgumentException("Could not parse [" + address + "]"); 288 } 289 } 290 291 /* 292 * Convenience method to extract the components of a dotted decimal address and 293 * pack into an integer using a regex match 294 */ 295 private int matchAddress(Matcher matcher) { 296 int addr = 0; 297 for (int i = 1; i <= 4; ++i) { 298 int n = (rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255)); 299 addr |= ((n & 0xff) << 8*(4-i)); 300 } 301 return addr; 302 } 303 304 /* 305 * Convert a packed integer address into a 4-element array 306 */ 307 private int[] toArray(int val) { 308 int ret[] = new int[4]; 309 for (int j = 3; j >= 0; --j) { 310 ret[j] |= ((val >>> 8*(3-j)) & (0xff)); 311 } 312 return ret; 313 } 314 315 /* 316 * Convert a 4-element array into dotted decimal format 317 */ 318 private String format(int[] octets) { 319 StringBuilder str = new StringBuilder(); 320 for (int i =0; i < octets.length; ++i){ 321 str.append(octets[i]); 322 if (i != octets.length - 1) { 323 str.append("."); 324 } 325 } 326 return str.toString(); 327 } 328 329 /* 330 * Convenience function to check integer boundaries. 331 * Checks if a value x is in the range [begin,end]. 332 * Returns x if it is in range, throws an exception otherwise. 333 */ 334 private int rangeCheck(int value, int begin, int end) { 335 if (value >= begin && value <= end) { // (begin,end] 336 return value; 337 } 338 339 throw new IllegalArgumentException("Value [" + value + "] not in range ["+begin+","+end+"]"); 340 } 341 342 /* 343 * Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy 344 * see Hacker's Delight section 5.1 345 */ 346 int pop(int x) { 347 x = x - ((x >>> 1) & 0x55555555); 348 x = (x & 0x33333333) + ((x >>> 2) & 0x33333333); 349 x = (x + (x >>> 4)) & 0x0F0F0F0F; 350 x = x + (x >>> 8); 351 x = x + (x >>> 16); 352 return x & 0x0000003F; 353 } 354 355 /* Convert two dotted decimal addresses to a single xxx.xxx.xxx.xxx/yy format 356 * by counting the 1-bit population in the mask address. (It may be better to count 357 * NBITS-#trailing zeroes for this case) 358 */ 359 private String toCidrNotation(String addr, String mask) { 360 return addr + "/" + pop(toInteger(mask)); 361 } 362}