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 *      https://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.Iterator;
020import java.util.regex.Matcher;
021import java.util.regex.Pattern;
022import java.util.stream.Stream;
023import java.util.stream.StreamSupport;
024
025/**
026 * Performs subnet calculations given a network address and a subnet mask.
027 *
028 * @see <a href="https://datatracker.ietf.org/doc/html/rfc1519">Classless Inter-Domain Routing (CIDR): an Address Assignment and Aggregation Strategy</a>
029 * @see SubnetUtils6
030 * @since 2.0
031 */
032public class SubnetUtils {
033
034    /**
035     * Allows an object to be the target of the "for-each loop" statement for a SubnetInfo.
036     */
037    private static final class SubnetAddressStringIterable implements Iterable<String> {
038
039        private final SubnetInfo subnetInfo;
040
041        /**
042         * Constructs a new instance.
043         *
044         * @param subnetInfo the SubnetInfo to iterate.
045         */
046        private SubnetAddressStringIterable(final SubnetInfo subnetInfo) {
047            this.subnetInfo = subnetInfo;
048        }
049
050        @Override
051        public Iterator<String> iterator() {
052            return new SubnetAddressStringIterator(subnetInfo);
053        }
054    }
055
056    /**
057     * Iterates over a SubnetInfo.
058     */
059    private static final class SubnetAddressStringIterator implements Iterator<String> {
060
061        private int currentAddress;
062
063        private final SubnetInfo subnetInfo;
064
065        /**
066         * Constructs a new instance.
067         *
068         * @param subnetInfo the SubnetInfo to iterate.
069         */
070        private SubnetAddressStringIterator(final SubnetInfo subnetInfo) {
071            this.subnetInfo = subnetInfo;
072            currentAddress = subnetInfo.low();
073        }
074
075        @Override
076        public boolean hasNext() {
077            return subnetInfo.getAddressCountLong() > 0 && currentAddress <= subnetInfo.high();
078        }
079
080        @Override
081        public String next() {
082            return format(toArray4(currentAddress++));
083        }
084    }
085
086    /**
087     * Contains subnet summary information.
088     */
089    public final class SubnetInfo {
090
091        /** Mask to convert unsigned int to a long (i.e. keep 32 bits). */
092        private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL;
093
094        private SubnetInfo() {
095        }
096
097        /**
098         * Converts a dotted decimal format address to a packed integer format.
099         *
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}