    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}