1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.net.util;
18
19 import java.util.Iterator;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22 import java.util.stream.Stream;
23 import java.util.stream.StreamSupport;
24
25 /**
26 * Performs subnet calculations given a network address and a subnet mask.
27 *
28 * @see "http://www.faqs.org/rfcs/rfc1519.html"
29 * @since 2.0
30 */
31 public class SubnetUtils {
32
33 /**
34 * Allows an object to be the target of the "for-each loop" statement for a SubnetInfo.
35 */
36 private static final class SubnetAddressStringIterable implements Iterable<String> {
37
38 private final SubnetInfo subnetInfo;
39
40 /**
41 * Constructs a new instance.
42 *
43 * @param subnetInfo the SubnetInfo to iterate.
44 */
45 private SubnetAddressStringIterable(final SubnetInfo subnetInfo) {
46 this.subnetInfo = subnetInfo;
47 }
48
49 @Override
50 public Iterator<String> iterator() {
51 return new SubnetAddressStringIterator(subnetInfo);
52 }
53 }
54
55 /**
56 * Iterates over a SubnetInfo.
57 */
58 private static final class SubnetAddressStringIterator implements Iterator<String> {
59
60 private int currentAddress;
61
62 private final SubnetInfo subnetInfo;
63
64 /**
65 * Constructs a new instance.
66 *
67 * @param subnetInfo the SubnetInfo to iterate.
68 */
69 private SubnetAddressStringIterator(final SubnetInfo subnetInfo) {
70 this.subnetInfo = subnetInfo;
71 currentAddress = subnetInfo.low();
72 }
73
74 @Override
75 public boolean hasNext() {
76 return subnetInfo.getAddressCountLong() > 0 && currentAddress <= subnetInfo.high();
77 }
78
79 @Override
80 public String next() {
81 return format(toArray4(currentAddress++));
82 }
83 }
84
85 /**
86 * Contains subnet summary information.
87 */
88 public final class SubnetInfo {
89
90 /** Mask to convert unsigned int to a long (i.e. keep 32 bits). */
91 private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL;
92
93 private SubnetInfo() {
94 }
95
96 /**
97 * Converts a dotted decimal format address to a packed integer format.
98 *
99 * @param address a dotted decimal format address.
100 * @return packed integer formatted int.
101 */
102 public int asInteger(final String address) {
103 return toInteger(address);
104 }
105
106 private long broadcastLong() {
107 return broadcast & UNSIGNED_INT_MASK;
108 }
109
110 /**
111 * Gets this instance's address into a dotted decimal String.
112 *
113 * @return a dotted decimal String.
114 */
115 public String getAddress() {
116 return format(toArray4(address));
117 }
118
119 /**
120 * Gets the count of available addresses. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
121 *
122 * @return the count of addresses, may be zero.
123 * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE}
124 * @deprecated (3.4) use {@link #getAddressCountLong()} instead
125 */
126 @Deprecated
127 public int getAddressCount() {
128 final long countLong = getAddressCountLong();
129 if (countLong > Integer.MAX_VALUE) {
130 throw new IllegalStateException("Count is larger than an integer: " + countLong);
131 }
132 // Cannot be negative here
133 return (int) countLong;
134 }
135
136 /**
137 * Gets the count of available addresses. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
138 *
139 * @return the count of addresses, may be zero.
140 * @since 3.4
141 */
142 public long getAddressCountLong() {
143 final long b = broadcastLong();
144 final long n = networkLong();
145 final long count = b - n + (isInclusiveHostCount() ? 1 : -1);
146 return count < 0 ? 0 : count;
147 }
148
149 /**
150 * Gets all addresses in this subnet, the return array could be huge.
151 * <p>
152 * For large ranges, you can iterate or stream over the addresses instead using {@link #iterableAddressStrings()} or {@link #streamAddressStrings()}.
153 * </p>
154 *
155 * @return all addresses in this subnet.
156 * @see #iterableAddressStrings()
157 * @see #streamAddressStrings()
158 */
159 public String[] getAllAddresses() {
160 final int ct = getAddressCount();
161 final String[] addresses = new String[ct];
162 if (ct == 0) {
163 return addresses;
164 }
165 final int high = high();
166 for (int add = low(), j = 0; add <= high; ++add, ++j) {
167 addresses[j] = format(toArray4(add));
168 }
169 return addresses;
170 }
171
172 /**
173 * Gets the broadcast address for this subnet.
174 *
175 * @return the broadcast address for this subnet.
176 */
177 public String getBroadcastAddress() {
178 return format(toArray4(broadcast));
179 }
180
181 /**
182 * Gets the CIDR signature for this subnet.
183 *
184 * @return the CIDR signature for this subnet.
185 */
186 public String getCidrSignature() {
187 return format(toArray4(address)) + "/" + Integer.bitCount(netmask);
188 }
189
190 /**
191 * Gets the high address as a dotted IP address. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
192 *
193 * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
194 */
195 public String getHighAddress() {
196 return format(toArray4(high()));
197 }
198
199 /**
200 * Gets the low address as a dotted IP address. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
201 *
202 * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
203 */
204 public String getLowAddress() {
205 return format(toArray4(low()));
206 }
207
208 /**
209 * Gets the network mask for this subnet.
210 *
211 * @return the network mask for this subnet.
212 */
213 public String getNetmask() {
214 return format(toArray4(netmask));
215 }
216
217 /**
218 * Gets the network address for this subnet.
219 *
220 * @return the network address for this subnet.
221 */
222 public String getNetworkAddress() {
223 return format(toArray4(network));
224 }
225
226 /**
227 * Gets the next address for this subnet.
228 *
229 * @return the next address for this subnet.
230 */
231 public String getNextAddress() {
232 return format(toArray4(address + 1));
233 }
234
235 /**
236 * Gets the previous address for this subnet.
237 *
238 * @return the previous address for this subnet.
239 */
240 public String getPreviousAddress() {
241 return format(toArray4(address - 1));
242 }
243
244 private int high() {
245 return isInclusiveHostCount() ? broadcast : broadcastLong() - networkLong() > 1 ? broadcast - 1 : 0;
246 }
247
248 /**
249 * Tests if the parameter {@code address} is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast
250 * addresses by default. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this.
251 *
252 * @param address the address to check
253 * @return true if it is in range
254 * @since 3.4 (made public)
255 */
256 public boolean isInRange(final int address) {
257 if (address == 0) { // cannot ever be in range; rejecting now avoids problems with CIDR/31,32
258 return false;
259 }
260 final long addLong = address & UNSIGNED_INT_MASK;
261 final long lowLong = low() & UNSIGNED_INT_MASK;
262 final long highLong = high() & UNSIGNED_INT_MASK;
263 return addLong >= lowLong && addLong <= highLong;
264 }
265
266 /**
267 * Tests if the parameter {@code address} is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast
268 * addresses. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this.
269 *
270 * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1"
271 * @return True if in range, false otherwise
272 */
273 public boolean isInRange(final String address) {
274 return isInRange(toInteger(address));
275 }
276
277 /**
278 * Creates a new Iterable of address Strings.
279 *
280 * @return a new Iterable of address Strings
281 * @see #getAllAddresses()
282 * @see #streamAddressStrings()
283 * @since 3.12.0
284 */
285 public Iterable<String> iterableAddressStrings() {
286 return new SubnetAddressStringIterable(this);
287 }
288
289 private int low() {
290 return isInclusiveHostCount() ? network : broadcastLong() - networkLong() > 1 ? network + 1 : 0;
291 }
292
293 /** Long versions of the values (as unsigned int) which are more suitable for range checking. */
294 private long networkLong() {
295 return network & UNSIGNED_INT_MASK;
296 }
297
298 /**
299 * Creates a new Stream of address Strings.
300 *
301 * @return a new Stream of address Strings.
302 * @see #getAllAddresses()
303 * @see #iterableAddressStrings()
304 * @since 3.12.0
305 */
306 public Stream<String> streamAddressStrings() {
307 return StreamSupport.stream(iterableAddressStrings().spliterator(), false);
308 }
309
310 /**
311 * {@inheritDoc}
312 *
313 * @since 2.2
314 */
315 @Override
316 public String toString() {
317 final StringBuilder buf = new StringBuilder();
318 // @formatter:off
319 buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]\n")
320 .append(" Netmask: [").append(getNetmask()).append("]\n")
321 .append(" Network: [").append(getNetworkAddress()).append("]\n")
322 .append(" Broadcast: [").append(getBroadcastAddress()).append("]\n")
323 .append(" First address: [").append(getLowAddress()).append("]\n")
324 .append(" Last address: [").append(getHighAddress()).append("]\n")
325 .append(" Address Count: [").append(getAddressCountLong()).append("]\n");
326 // @formatter:on
327 return buf.toString();
328 }
329 }
330
331 private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
332
333 private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,2})"; // 0 -> 32
334
335 private static final Pattern ADDRESS_PATTERN = Pattern.compile(IP_ADDRESS);
336 private static final Pattern CIDR_PATTERN = Pattern.compile(SLASH_FORMAT);
337 private static final int NBITS = 32;
338 private static final String PARSE_FAIL = "Could not parse [%s]";
339
340 /**
341 * Converts a 4-element array into dotted decimal format.
342 */
343 private static String format(final int[] octets) {
344 final int last = octets.length - 1;
345 final StringBuilder builder = new StringBuilder();
346 for (int i = 0;; i++) {
347 builder.append(octets[i]);
348 if (i == last) {
349 return builder.toString();
350 }
351 builder.append('.');
352 }
353 }
354 /**
355 * Extracts the components of a dotted decimal address and pack into an integer using a regex match
356 */
357 private static int matchAddress(final Matcher matcher) {
358 int addr = 0;
359 for (int i = 1; i <= 4; ++i) {
360 final int n = rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255);
361 addr |= (n & 0xff) << 8 * (4 - i);
362 }
363 return addr;
364 }
365
366 /**
367 * 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.
368 */
369 private static int rangeCheck(final int value, final int begin, final int end) {
370 if (value >= begin && value <= end) { // (begin,end]
371 return value;
372 }
373 throw new IllegalArgumentException("Value [" + value + "] not in range [" + begin + "," + end + "]");
374 }
375
376 /**
377 * Converts a packed integer address into a 4-element array
378 */
379 private static int[] toArray4(final int val) {
380 final int[] ret = new int[4];
381 for (int j = 3; j >= 0; --j) {
382 ret[j] |= val >>> 8 * (3 - j) & 0xff;
383 }
384 return ret;
385 }
386
387 /**
388 * Converts a dotted decimal format address to a packed integer format.
389 */
390 private static int toInteger(final String address) {
391 final Matcher matcher = ADDRESS_PATTERN.matcher(address);
392 if (matcher.matches()) {
393 return matchAddress(matcher);
394 }
395 throw new IllegalArgumentException(String.format(PARSE_FAIL, address));
396 }
397
398 private final int address;
399
400 private final int broadcast;
401
402 /** Whether the broadcast/network address are included in host count */
403 private boolean inclusiveHostCount;
404
405 private final int netmask;
406
407 private final int network;
408
409 /**
410 * Constructs an instance from a CIDR-notation string, e.g. "192.168.0.1/16"
411 *
412 * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16"
413 * @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
414 * 0-32
415 */
416 public SubnetUtils(final String cidrNotation) {
417 final Matcher matcher = CIDR_PATTERN.matcher(cidrNotation);
418
419 if (!matcher.matches()) {
420 throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation));
421 }
422 this.address = matchAddress(matcher);
423
424 // Create a binary netmask from the number of bits specification /x
425
426 final int trailingZeroes = NBITS - rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS);
427
428 //
429 // An IPv4 netmask consists of 32 bits, a contiguous sequence
430 // of the specified number of ones followed by all zeros.
431 // So, it can be obtained by shifting an unsigned integer (32 bits) to the left by
432 // the number of trailing zeros which is (32 - the # bits specification).
433 // Note that there is no unsigned left shift operator, so we have to use
434 // a long to ensure that the left-most bit is shifted out correctly.
435 //
436 this.netmask = (int) (0x0FFFFFFFFL << trailingZeroes);
437
438 // Calculate base network address
439 this.network = address & netmask;
440
441 // Calculate broadcast address
442 this.broadcast = network | ~netmask;
443 }
444
445 /**
446 * Constructs an instance from a dotted decimal address and a dotted decimal mask.
447 *
448 * @param address An IP address, e.g. "192.168.0.1"
449 * @param mask A dotted decimal netmask e.g. "255.255.0.0"
450 * @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
451 */
452 public SubnetUtils(final String address, final String mask) {
453 this.address = toInteger(address);
454 this.netmask = toInteger(mask);
455
456 if ((this.netmask & -this.netmask) - 1 != ~this.netmask) {
457 throw new IllegalArgumentException(String.format(PARSE_FAIL, mask));
458 }
459
460 // Calculate base network address
461 this.network = this.address & this.netmask;
462
463 // Calculate broadcast address
464 this.broadcast = this.network | ~this.netmask;
465 }
466
467 /**
468 * Gets a {@link SubnetInfo} instance that contains subnet-specific statistics
469 *
470 * @return new instance
471 */
472 public final SubnetInfo getInfo() {
473 return new SubnetInfo();
474 }
475
476 /**
477 * Gets the next subnet for this instance.
478 *
479 * @return the next subnet for this instance.
480 */
481 public SubnetUtils getNext() {
482 return new SubnetUtils(getInfo().getNextAddress(), getInfo().getNetmask());
483 }
484
485 /**
486 * Gets the previous subnet for this instance.
487 *
488 * @return the next previous for this instance.
489 */
490 public SubnetUtils getPrevious() {
491 return new SubnetUtils(getInfo().getPreviousAddress(), getInfo().getNetmask());
492 }
493
494 /**
495 * Tests if the return value of {@link SubnetInfo#getAddressCount()} includes the network and broadcast addresses.
496 *
497 * @return true if the host count includes the network and broadcast addresses
498 * @since 2.2
499 */
500 public boolean isInclusiveHostCount() {
501 return inclusiveHostCount;
502 }
503
504 /**
505 * Sets to {@code true} if you want the return value of {@link SubnetInfo#getAddressCount()} to include the network and broadcast addresses. This also
506 * applies to {@link SubnetInfo#isInRange(int)}
507 *
508 * @param inclusiveHostCount true if network and broadcast addresses are to be included
509 * @since 2.2
510 */
511 public void setInclusiveHostCount(final boolean inclusiveHostCount) {
512 this.inclusiveHostCount = inclusiveHostCount;
513 }
514
515 /**
516 * Converts this instance to a debug String.
517 *
518 * @return this instance to a debug String.
519 * @since 3.11.0
520 */
521 @Override
522 public String toString() {
523 return getInfo().toString();
524 }
525 }