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 * Performs subnet calculations given a network address and a subnet mask. 024 * 025 * @see "http://www.faqs.org/rfcs/rfc1519.html" 026 * @since 2.0 027 */ 028public class SubnetUtils { 029 030 /** 031 * Convenience container for subnet summary information. 032 */ 033 public final class SubnetInfo { 034 035 /** Mask to convert unsigned int to a long (i.e. keep 32 bits). */ 036 private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL; 037 038 private SubnetInfo() { 039 } 040 041 public int asInteger(final String address) { 042 return toInteger(address); 043 } 044 045 private long broadcastLong() { 046 return broadcast & UNSIGNED_INT_MASK; 047 } 048 049 /** 050 * Converts a 4-element array into dotted decimal format. 051 */ 052 private String format(final int[] octets) { 053 final int last = octets.length - 1; 054 final StringBuilder builder = new StringBuilder(); 055 for (int i = 0;; i++) { 056 builder.append(octets[i]); 057 if (i == last) { 058 return builder.toString(); 059 } 060 builder.append('.'); 061 } 062 } 063 064 public String getAddress() { 065 return format(toArray(address)); 066 } 067 068 /** 069 * Gets the count of available addresses. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. 070 * 071 * @return the count of addresses, may be zero. 072 * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE} 073 * @deprecated (3.4) use {@link #getAddressCountLong()} instead 074 */ 075 @Deprecated 076 public int getAddressCount() { 077 final long countLong = getAddressCountLong(); 078 if (countLong > Integer.MAX_VALUE) { 079 throw new IllegalStateException("Count is larger than an integer: " + countLong); 080 } 081 // Cannot be negative here 082 return (int) countLong; 083 } 084 085 /** 086 * Gets the count of available addresses. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. 087 * 088 * @return the count of addresses, may be zero. 089 * @since 3.4 090 */ 091 public long getAddressCountLong() { 092 final long b = broadcastLong(); 093 final long n = networkLong(); 094 final long count = b - n + (isInclusiveHostCount() ? 1 : -1); 095 return count < 0 ? 0 : count; 096 } 097 098 public String[] getAllAddresses() { 099 final int ct = getAddressCount(); 100 final String[] addresses = new String[ct]; 101 if (ct == 0) { 102 return addresses; 103 } 104 for (int add = low(), j = 0; add <= high(); ++add, ++j) { 105 addresses[j] = format(toArray(add)); 106 } 107 return addresses; 108 } 109 110 public String getBroadcastAddress() { 111 return format(toArray(broadcast)); 112 } 113 114 public String getCidrSignature() { 115 return format(toArray(address)) + "/" + Integer.bitCount(netmask); 116 } 117 118 /** 119 * Gets the high address as a dotted IP address. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. 120 * 121 * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address 122 */ 123 public String getHighAddress() { 124 return format(toArray(high())); 125 } 126 127 /** 128 * Gets the low address as a dotted IP address. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. 129 * 130 * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address 131 */ 132 public String getLowAddress() { 133 return format(toArray(low())); 134 } 135 136 public String getNetmask() { 137 return format(toArray(netmask)); 138 } 139 140 public String getNetworkAddress() { 141 return format(toArray(network)); 142 } 143 144 public String getNextAddress() { 145 return format(toArray(address + 1)); 146 } 147 148 public String getPreviousAddress() { 149 return format(toArray(address - 1)); 150 } 151 152 private int high() { 153 return isInclusiveHostCount() ? broadcast : broadcastLong() - networkLong() > 1 ? broadcast - 1 : 0; 154 } 155 156 /** 157 * Tests if the parameter <code>address</code> is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast 158 * addresses by default. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this. 159 * 160 * @param address the address to check 161 * @return true if it is in range 162 * @since 3.4 (made public) 163 */ 164 public boolean isInRange(final int address) { 165 if (address == 0) { // cannot ever be in range; rejecting now avoids problems with CIDR/31,32 166 return false; 167 } 168 final long addLong = address & UNSIGNED_INT_MASK; 169 final long lowLong = low() & UNSIGNED_INT_MASK; 170 final long highLong = high() & UNSIGNED_INT_MASK; 171 return addLong >= lowLong && addLong <= highLong; 172 } 173 174 /** 175 * Tests if the parameter <code>address</code> is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast 176 * addresses. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this. 177 * 178 * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1" 179 * @return True if in range, false otherwise 180 */ 181 public boolean isInRange(final String address) { 182 return isInRange(toInteger(address)); 183 } 184 185 private int low() { 186 return isInclusiveHostCount() ? network : broadcastLong() - networkLong() > 1 ? network + 1 : 0; 187 } 188 189 /** Long versions of the values (as unsigned int) which are more suitable for range checking. */ 190 private long networkLong() { 191 return network & UNSIGNED_INT_MASK; 192 } 193 194 /** 195 * Converts a packed integer address into a 4-element array 196 */ 197 private int[] toArray(final int val) { 198 final int[] ret = new int[4]; 199 for (int j = 3; j >= 0; --j) { 200 ret[j] |= val >>> 8 * (3 - j) & 0xff; 201 } 202 return ret; 203 } 204 205 /** 206 * {@inheritDoc} 207 * 208 * @since 2.2 209 */ 210 @Override 211 public String toString() { 212 final StringBuilder buf = new StringBuilder(); 213 // @formatter:off 214 buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]\n") 215 .append(" Netmask: [").append(getNetmask()).append("]\n") 216 .append(" Network: [").append(getNetworkAddress()).append("]\n") 217 .append(" Broadcast: [").append(getBroadcastAddress()).append("]\n") 218 .append(" First address: [").append(getLowAddress()).append("]\n") 219 .append(" Last address: [").append(getHighAddress()).append("]\n") 220 .append(" Address Count: [").append(getAddressCountLong()).append("]\n"); 221 // @formatter:on 222 return buf.toString(); 223 } 224 } 225 226 private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})"; 227 private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,2})"; // 0 -> 32 228 private static final Pattern ADDRESS_PATTERN = Pattern.compile(IP_ADDRESS); 229 private static final Pattern CIDR_PATTERN = Pattern.compile(SLASH_FORMAT); 230 231 private static final int NBITS = 32; 232 233 private static final String PARSE_FAIL = "Could not parse [%s]"; 234 235 /* 236 * Extracts the components of a dotted decimal address and pack into an integer using a regex match 237 */ 238 private static int matchAddress(final Matcher matcher) { 239 int addr = 0; 240 for (int i = 1; i <= 4; ++i) { 241 final int n = rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255); 242 addr |= (n & 0xff) << 8 * (4 - i); 243 } 244 return addr; 245 } 246 247 /* 248 * Checks integer boundaries. Checks if a value x is in the range [begin,end]. Returns x if it is in range, throws an exception otherwise. 249 */ 250 private static int rangeCheck(final int value, final int begin, final int end) { 251 if (value >= begin && value <= end) { // (begin,end] 252 return value; 253 } 254 throw new IllegalArgumentException("Value [" + value + "] not in range [" + begin + "," + end + "]"); 255 } 256 257 /* 258 * Converts a dotted decimal format address to a packed integer format 259 */ 260 private static int toInteger(final String address) { 261 final Matcher matcher = ADDRESS_PATTERN.matcher(address); 262 if (matcher.matches()) { 263 return matchAddress(matcher); 264 } 265 throw new IllegalArgumentException(String.format(PARSE_FAIL, address)); 266 } 267 268 private final int netmask; 269 270 private final int address; 271 272 private final int network; 273 274 private final int broadcast; 275 276 /** Whether the broadcast/network address are included in host count */ 277 private boolean inclusiveHostCount; 278 279 /** 280 * Constructs an instance from a CIDR-notation string, e.g. "192.168.0.1/16" 281 * 282 * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16" 283 * @throws IllegalArgumentException if the parameter is invalid, i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-2 decimal digits in range 284 * 0-32 285 */ 286 public SubnetUtils(final String cidrNotation) { 287 final Matcher matcher = CIDR_PATTERN.matcher(cidrNotation); 288 289 if (!matcher.matches()) { 290 throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation)); 291 } 292 this.address = matchAddress(matcher); 293 294 // Create a binary netmask from the number of bits specification /x 295 296 final int trailingZeroes = NBITS - rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS); 297 298 // 299 // An IPv4 netmask consists of 32 bits, a contiguous sequence 300 // of the specified number of ones followed by all zeros. 301 // So, it can be obtained by shifting an unsigned integer (32 bits) to the left by 302 // the number of trailing zeros which is (32 - the # bits specification). 303 // Note that there is no unsigned left shift operator, so we have to use 304 // a long to ensure that the left-most bit is shifted out correctly. 305 // 306 this.netmask = (int) (0x0FFFFFFFFL << trailingZeroes); 307 308 // Calculate base network address 309 this.network = address & netmask; 310 311 // Calculate broadcast address 312 this.broadcast = network | ~netmask; 313 } 314 315 /** 316 * Constructs an instance from a dotted decimal address and a dotted decimal mask. 317 * 318 * @param address An IP address, e.g. "192.168.0.1" 319 * @param mask A dotted decimal netmask e.g. "255.255.0.0" 320 * @throws IllegalArgumentException if the address or mask is invalid, i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros 321 */ 322 public SubnetUtils(final String address, final String mask) { 323 this.address = toInteger(address); 324 this.netmask = toInteger(mask); 325 326 if ((this.netmask & -this.netmask) - 1 != ~this.netmask) { 327 throw new IllegalArgumentException(String.format(PARSE_FAIL, mask)); 328 } 329 330 // Calculate base network address 331 this.network = this.address & this.netmask; 332 333 // Calculate broadcast address 334 this.broadcast = this.network | ~this.netmask; 335 } 336 337 /** 338 * Gets a {@link SubnetInfo} instance that contains subnet-specific statistics 339 * 340 * @return new instance 341 */ 342 public final SubnetInfo getInfo() { 343 return new SubnetInfo(); 344 } 345 346 public SubnetUtils getNext() { 347 return new SubnetUtils(getInfo().getNextAddress(), getInfo().getNetmask()); 348 } 349 350 public SubnetUtils getPrevious() { 351 return new SubnetUtils(getInfo().getPreviousAddress(), getInfo().getNetmask()); 352 } 353 354 /** 355 * Tests if the return value of {@link SubnetInfo#getAddressCount()} includes the network and broadcast addresses. 356 * 357 * @since 2.2 358 * @return true if the host count includes the network and broadcast addresses 359 */ 360 public boolean isInclusiveHostCount() { 361 return inclusiveHostCount; 362 } 363 364 /** 365 * Sets to {@code true} if you want the return value of {@link SubnetInfo#getAddressCount()} to include the network and broadcast addresses. This also 366 * applies to {@link SubnetInfo#isInRange(int)} 367 * 368 * @param inclusiveHostCount true if network and broadcast addresses are to be included 369 * @since 2.2 370 */ 371 public void setInclusiveHostCount(final boolean inclusiveHostCount) { 372 this.inclusiveHostCount = inclusiveHostCount; 373 } 374 375 /** 376 * Converts this instance to a debug String. 377 * 378 * @return this instance to a debug String. 379 * @since 3.11.0 380 */ 381 @Override 382 public String toString() { 383 return getInfo().toString(); 384 } 385}