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