SubnetUtils.java

  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.  *      http://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. import java.util.regex.Matcher;
  19. import java.util.regex.Pattern;

  20. /**
  21.  * Performs subnet calculations given a network address and a subnet mask.
  22.  *
  23.  * @see "http://www.faqs.org/rfcs/rfc1519.html"
  24.  * @since 2.0
  25.  */
  26. public class SubnetUtils {

  27.     /**
  28.      * Convenience container for subnet summary information.
  29.      */
  30.     public final class SubnetInfo {

  31.         /** Mask to convert unsigned int to a long (i.e. keep 32 bits). */
  32.         private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL;

  33.         private SubnetInfo() {
  34.         }

  35.         public int asInteger(final String address) {
  36.             return toInteger(address);
  37.         }

  38.         private long broadcastLong() {
  39.             return broadcast & UNSIGNED_INT_MASK;
  40.         }

  41.         /**
  42.          * Converts a 4-element array into dotted decimal format.
  43.          */
  44.         private String format(final int[] octets) {
  45.             final int last = octets.length - 1;
  46.             final StringBuilder builder = new StringBuilder();
  47.             for (int i = 0;; i++) {
  48.                 builder.append(octets[i]);
  49.                 if (i == last) {
  50.                     return builder.toString();
  51.                 }
  52.                 builder.append('.');
  53.             }
  54.         }

  55.         public String getAddress() {
  56.             return format(toArray(address));
  57.         }

  58.         /**
  59.          * Gets the count of available addresses. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
  60.          *
  61.          * @return the count of addresses, may be zero.
  62.          * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE}
  63.          * @deprecated (3.4) use {@link #getAddressCountLong()} instead
  64.          */
  65.         @Deprecated
  66.         public int getAddressCount() {
  67.             final long countLong = getAddressCountLong();
  68.             if (countLong > Integer.MAX_VALUE) {
  69.                 throw new IllegalStateException("Count is larger than an integer: " + countLong);
  70.             }
  71.             // Cannot be negative here
  72.             return (int) countLong;
  73.         }

  74.         /**
  75.          * Gets the count of available addresses. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
  76.          *
  77.          * @return the count of addresses, may be zero.
  78.          * @since 3.4
  79.          */
  80.         public long getAddressCountLong() {
  81.             final long b = broadcastLong();
  82.             final long n = networkLong();
  83.             final long count = b - n + (isInclusiveHostCount() ? 1 : -1);
  84.             return count < 0 ? 0 : count;
  85.         }

  86.         public String[] getAllAddresses() {
  87.             final int ct = getAddressCount();
  88.             final String[] addresses = new String[ct];
  89.             if (ct == 0) {
  90.                 return addresses;
  91.             }
  92.             for (int add = low(), j = 0; add <= high(); ++add, ++j) {
  93.                 addresses[j] = format(toArray(add));
  94.             }
  95.             return addresses;
  96.         }

  97.         public String getBroadcastAddress() {
  98.             return format(toArray(broadcast));
  99.         }

  100.         public String getCidrSignature() {
  101.             return format(toArray(address)) + "/" + Integer.bitCount(netmask);
  102.         }

  103.         /**
  104.          * Gets the high address as a dotted IP address. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
  105.          *
  106.          * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
  107.          */
  108.         public String getHighAddress() {
  109.             return format(toArray(high()));
  110.         }

  111.         /**
  112.          * Gets the low address as a dotted IP address. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
  113.          *
  114.          * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
  115.          */
  116.         public String getLowAddress() {
  117.             return format(toArray(low()));
  118.         }

  119.         public String getNetmask() {
  120.             return format(toArray(netmask));
  121.         }

  122.         public String getNetworkAddress() {
  123.             return format(toArray(network));
  124.         }

  125.         public String getNextAddress() {
  126.             return format(toArray(address + 1));
  127.         }

  128.         public String getPreviousAddress() {
  129.             return format(toArray(address - 1));
  130.         }

  131.         private int high() {
  132.             return isInclusiveHostCount() ? broadcast : broadcastLong() - networkLong() > 1 ? broadcast - 1 : 0;
  133.         }

  134.         /**
  135.          * Tests if the parameter <code>address</code> is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast
  136.          * addresses by default. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this.
  137.          *
  138.          * @param address the address to check
  139.          * @return true if it is in range
  140.          * @since 3.4 (made public)
  141.          */
  142.         public boolean isInRange(final int address) {
  143.             if (address == 0) { // cannot ever be in range; rejecting now avoids problems with CIDR/31,32
  144.                 return false;
  145.             }
  146.             final long addLong = address & UNSIGNED_INT_MASK;
  147.             final long lowLong = low() & UNSIGNED_INT_MASK;
  148.             final long highLong = high() & UNSIGNED_INT_MASK;
  149.             return addLong >= lowLong && addLong <= highLong;
  150.         }

  151.         /**
  152.          * Tests if the parameter <code>address</code> is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast
  153.          * addresses. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this.
  154.          *
  155.          * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1"
  156.          * @return True if in range, false otherwise
  157.          */
  158.         public boolean isInRange(final String address) {
  159.             return isInRange(toInteger(address));
  160.         }

  161.         private int low() {
  162.             return isInclusiveHostCount() ? network : broadcastLong() - networkLong() > 1 ? network + 1 : 0;
  163.         }

  164.         /** Long versions of the values (as unsigned int) which are more suitable for range checking. */
  165.         private long networkLong() {
  166.             return network & UNSIGNED_INT_MASK;
  167.         }

  168.         /**
  169.          * Converts a packed integer address into a 4-element array
  170.          */
  171.         private int[] toArray(final int val) {
  172.             final int[] ret = new int[4];
  173.             for (int j = 3; j >= 0; --j) {
  174.                 ret[j] |= val >>> 8 * (3 - j) & 0xff;
  175.             }
  176.             return ret;
  177.         }

  178.         /**
  179.          * {@inheritDoc}
  180.          *
  181.          * @since 2.2
  182.          */
  183.         @Override
  184.         public String toString() {
  185.             final StringBuilder buf = new StringBuilder();
  186.             // @formatter:off
  187.             buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]\n")
  188.                 .append("  Netmask: [").append(getNetmask()).append("]\n")
  189.                 .append("  Network: [").append(getNetworkAddress()).append("]\n")
  190.                 .append("  Broadcast: [").append(getBroadcastAddress()).append("]\n")
  191.                 .append("  First address: [").append(getLowAddress()).append("]\n")
  192.                 .append("  Last address: [").append(getHighAddress()).append("]\n")
  193.                 .append("  Address Count: [").append(getAddressCountLong()).append("]\n");
  194.             // @formatter:on
  195.             return buf.toString();
  196.         }
  197.     }

  198.     private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
  199.     private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,2})"; // 0 -> 32
  200.     private static final Pattern ADDRESS_PATTERN = Pattern.compile(IP_ADDRESS);
  201.     private static final Pattern CIDR_PATTERN = Pattern.compile(SLASH_FORMAT);

  202.     private static final int NBITS = 32;

  203.     private static final String PARSE_FAIL = "Could not parse [%s]";

  204.     /*
  205.      * Extracts the components of a dotted decimal address and pack into an integer using a regex match
  206.      */
  207.     private static int matchAddress(final Matcher matcher) {
  208.         int addr = 0;
  209.         for (int i = 1; i <= 4; ++i) {
  210.             final int n = rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255);
  211.             addr |= (n & 0xff) << 8 * (4 - i);
  212.         }
  213.         return addr;
  214.     }

  215.     /*
  216.      * 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.
  217.      */
  218.     private static int rangeCheck(final int value, final int begin, final int end) {
  219.         if (value >= begin && value <= end) { // (begin,end]
  220.             return value;
  221.         }
  222.         throw new IllegalArgumentException("Value [" + value + "] not in range [" + begin + "," + end + "]");
  223.     }

  224.     /*
  225.      * Converts a dotted decimal format address to a packed integer format
  226.      */
  227.     private static int toInteger(final String address) {
  228.         final Matcher matcher = ADDRESS_PATTERN.matcher(address);
  229.         if (matcher.matches()) {
  230.             return matchAddress(matcher);
  231.         }
  232.         throw new IllegalArgumentException(String.format(PARSE_FAIL, address));
  233.     }

  234.     private final int netmask;

  235.     private final int address;

  236.     private final int network;

  237.     private final int broadcast;

  238.     /** Whether the broadcast/network address are included in host count */
  239.     private boolean inclusiveHostCount;

  240.     /**
  241.      * Constructs an instance from a CIDR-notation string, e.g. "192.168.0.1/16"
  242.      *
  243.      * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16"
  244.      * @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
  245.      *                                  0-32
  246.      */
  247.     public SubnetUtils(final String cidrNotation) {
  248.         final Matcher matcher = CIDR_PATTERN.matcher(cidrNotation);

  249.         if (!matcher.matches()) {
  250.             throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation));
  251.         }
  252.         this.address = matchAddress(matcher);

  253.         // Create a binary netmask from the number of bits specification /x

  254.         final int trailingZeroes = NBITS - rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS);

  255.         //
  256.         // An IPv4 netmask consists of 32 bits, a contiguous sequence
  257.         // of the specified number of ones followed by all zeros.
  258.         // So, it can be obtained by shifting an unsigned integer (32 bits) to the left by
  259.         // the number of trailing zeros which is (32 - the # bits specification).
  260.         // Note that there is no unsigned left shift operator, so we have to use
  261.         // a long to ensure that the left-most bit is shifted out correctly.
  262.         //
  263.         this.netmask = (int) (0x0FFFFFFFFL << trailingZeroes);

  264.         // Calculate base network address
  265.         this.network = address & netmask;

  266.         // Calculate broadcast address
  267.         this.broadcast = network | ~netmask;
  268.     }

  269.     /**
  270.      * Constructs an instance from a dotted decimal address and a dotted decimal mask.
  271.      *
  272.      * @param address An IP address, e.g. "192.168.0.1"
  273.      * @param mask    A dotted decimal netmask e.g. "255.255.0.0"
  274.      * @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
  275.      */
  276.     public SubnetUtils(final String address, final String mask) {
  277.         this.address = toInteger(address);
  278.         this.netmask = toInteger(mask);

  279.         if ((this.netmask & -this.netmask) - 1 != ~this.netmask) {
  280.             throw new IllegalArgumentException(String.format(PARSE_FAIL, mask));
  281.         }

  282.         // Calculate base network address
  283.         this.network = this.address & this.netmask;

  284.         // Calculate broadcast address
  285.         this.broadcast = this.network | ~this.netmask;
  286.     }

  287.     /**
  288.      * Gets a {@link SubnetInfo} instance that contains subnet-specific statistics
  289.      *
  290.      * @return new instance
  291.      */
  292.     public final SubnetInfo getInfo() {
  293.         return new SubnetInfo();
  294.     }

  295.     public SubnetUtils getNext() {
  296.         return new SubnetUtils(getInfo().getNextAddress(), getInfo().getNetmask());
  297.     }

  298.     public SubnetUtils getPrevious() {
  299.         return new SubnetUtils(getInfo().getPreviousAddress(), getInfo().getNetmask());
  300.     }

  301.     /**
  302.      * Tests if the return value of {@link SubnetInfo#getAddressCount()} includes the network and broadcast addresses.
  303.      *
  304.      * @since 2.2
  305.      * @return true if the host count includes the network and broadcast addresses
  306.      */
  307.     public boolean isInclusiveHostCount() {
  308.         return inclusiveHostCount;
  309.     }

  310.     /**
  311.      * Sets to {@code true} if you want the return value of {@link SubnetInfo#getAddressCount()} to include the network and broadcast addresses. This also
  312.      * applies to {@link SubnetInfo#isInRange(int)}
  313.      *
  314.      * @param inclusiveHostCount true if network and broadcast addresses are to be included
  315.      * @since 2.2
  316.      */
  317.     public void setInclusiveHostCount(final boolean inclusiveHostCount) {
  318.         this.inclusiveHostCount = inclusiveHostCount;
  319.     }

  320.     /**
  321.      * Converts this instance to a debug String.
  322.      *
  323.      * @return this instance to a debug String.
  324.      * @since 3.11.0
  325.      */
  326.     @Override
  327.     public String toString() {
  328.         return getInfo().toString();
  329.     }
  330. }