View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.util;
16  
17  import java.util.regex.Matcher;
18  import java.util.regex.Pattern;
19  
20  /**
21   * Performs subnet calculations given a network address and a subnet mask.
22   * Inspired by org.apache.commons.net.util.SubnetUtils.
23   *
24   * @see "http://www.faqs.org/rfcs/rfc1519.html"
25   *
26   * @author Ronald Brill
27   */
28  public class SubnetUtils {
29  
30      private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
31      private static final Pattern ADDRESS_PATTERN = Pattern.compile(IP_ADDRESS);
32      private static final String PARSE_FAIL = "Could not parse [%s]";
33      private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL;
34  
35      private final int netmask_;
36      private final int address_;
37      private final int network_;
38      private final int broadcast_;
39  
40      /**
41       * Constructs an instance from a dotted decimal address and a dotted decimal mask.
42       *
43       * @param address An IP address, e.g. "192.168.0.1"
44       * @param mask    A dotted decimal netmask e.g. "255.255.0.0"
45       * @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
46       */
47      public SubnetUtils(final String address, final String mask) {
48          address_ = toInteger(address);
49          netmask_ = toInteger(mask);
50  
51          if ((netmask_ & -netmask_) - 1 != ~netmask_) {
52              throw new IllegalArgumentException(String.format(PARSE_FAIL, mask));
53          }
54  
55          network_ = address_ & netmask_;
56          broadcast_ = network_ | ~netmask_;
57      }
58  
59      /*
60       * Extracts the components of a dotted decimal address and pack into an integer using a regex match
61       */
62      private static int matchAddress(final Matcher matcher) {
63          int addr = 0;
64          for (int i = 1; i <= 4; ++i) {
65              final int n = rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255);
66              addr |= (n & 0xff) << 8 * (4 - i);
67          }
68          return addr;
69      }
70  
71      /*
72       * 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.
73       */
74      private static int rangeCheck(final int value, final int begin, final int end) {
75          // (begin,end]
76          if (value >= begin && value <= end) {
77              return value;
78          }
79          throw new IllegalArgumentException("Value [" + value + "] not in range [" + begin + "," + end + "]");
80      }
81  
82      /*
83       * Converts a dotted decimal format address to a packed integer format
84       */
85      private static int toInteger(final String address) {
86          final Matcher matcher = ADDRESS_PATTERN.matcher(address);
87          if (matcher.matches()) {
88              return matchAddress(matcher);
89          }
90          throw new IllegalArgumentException(String.format(PARSE_FAIL, address));
91      }
92  
93      private long broadcastLong() {
94          return broadcast_ & UNSIGNED_INT_MASK;
95      }
96  
97      private int high() {
98          return broadcastLong() - networkLong() > 1 ? broadcast_ - 1 : 0;
99      }
100 
101     private int low() {
102         return broadcastLong() - networkLong() > 1 ? network_ + 1 : 0;
103     }
104 
105     /** Long versions of the values (as unsigned int) which are more suitable for range checking. */
106     private long networkLong() {
107         return network_ & UNSIGNED_INT_MASK;
108     }
109 
110     /**
111      * Tests if the parameter <code>address</code> is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast
112      * addresses by default. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this.
113      *
114      * @param address the address to check
115      * @return true if it is in range
116      */
117     private boolean isInRange(final int address) {
118         if (address == 0) {
119             return false;
120         }
121         final long addLong = address & UNSIGNED_INT_MASK;
122         final long lowLong = low() & UNSIGNED_INT_MASK;
123         final long highLong = high() & UNSIGNED_INT_MASK;
124         return addLong >= lowLong && addLong <= highLong;
125     }
126 
127     /**
128      * Tests if the parameter <code>address</code> is in the range of usable endpoint addresses for this subnet.
129      * This excludes the network and broadcast addresses.
130      *
131      * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1"
132      * @return true if in range, false otherwise
133      */
134     public boolean isInRange(final String address) {
135         return isInRange(toInteger(address));
136     }
137 }