View Javadoc
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 }