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    *      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  
19  import java.util.regex.Matcher;
20  import java.util.regex.Pattern;
21  
22  /**
23   * A class that performs some subnet calculations given a network address and a subnet mask.
24   * @see "http://www.faqs.org/rfcs/rfc1519.html"
25   * @author <rwinston@apache.org>
26   * @since 2.0
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 String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,3})";
32      private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS);
33      private static final Pattern cidrPattern = Pattern.compile(SLASH_FORMAT);
34      private static final int NBITS = 32;
35  
36      private int netmask = 0;
37      private int address = 0;
38      private int network = 0;
39      private int broadcast = 0;
40  
41      /** Whether the broadcast/network address are included in host count */
42      private boolean inclusiveHostCount = false;
43  
44  
45      /**
46       * Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16"
47       * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16"
48       * @throws IllegalArgumentException if the parameter is invalid,
49       * i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-3 decimal digits in range 1-32
50       */
51      public SubnetUtils(String cidrNotation) {
52          calculate(cidrNotation);
53      }
54  
55      /**
56       * Constructor that takes a dotted decimal address and a dotted decimal mask.
57       * @param address An IP address, e.g. "192.168.0.1"
58       * @param mask A dotted decimal netmask e.g. "255.255.0.0"
59       * @throws IllegalArgumentException if the address or mask is invalid,
60       * i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros
61       */
62      public SubnetUtils(String address, String mask) {
63          calculate(toCidrNotation(address, mask));
64      }
65  
66  
67      /**
68       * Returns <code>true</code> if the return value of {@link SubnetInfo#getAddressCount()}
69       * includes the network address and broadcast addresses.
70       * @since 2.2
71       */
72      public boolean isInclusiveHostCount() {
73          return inclusiveHostCount;
74      }
75  
76      /**
77       * Set to <code>true</code> if you want the return value of {@link SubnetInfo#getAddressCount()}
78       * to include the network and broadcast addresses.
79       * @param inclusiveHostCount
80       * @since 2.2
81       */
82      public void setInclusiveHostCount(boolean inclusiveHostCount) {
83          this.inclusiveHostCount = inclusiveHostCount;
84      }
85  
86  
87  
88      /**
89       * Convenience container for subnet summary information.
90       *
91       */
92      public final class SubnetInfo {
93          private SubnetInfo() {}
94  
95          private int netmask()       { return netmask; }
96          private int network()       { return network; }
97          private int address()       { return address; }
98          private int broadcast()     { return broadcast; }
99  
100         private int low() {
101             return (isInclusiveHostCount() ? network() :
102                 broadcast() - network() > 1 ? network() + 1 : 0); 
103         }
104         private int high() { 
105             return (isInclusiveHostCount() ? broadcast() :
106                 broadcast() - network() > 1 ? broadcast() -1  : 0); 
107         }
108 
109         /**
110          * Returns true if the parameter <code>address</code> is in the
111          * range of usable endpoint addresses for this subnet. This excludes the
112          * network and broadcast adresses.
113          * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1"
114          * @return True if in range, false otherwise
115          */
116         public boolean isInRange(String address)    { return isInRange(toInteger(address)); }
117 
118         private boolean isInRange(int address)      {
119             int diff = address-low();
120             return (diff >= 0 && (diff <= (high()-low())));
121         }
122 
123         public String getBroadcastAddress()         { return format(toArray(broadcast())); }
124         public String getNetworkAddress()           { return format(toArray(network())); }
125         public String getNetmask()                  { return format(toArray(netmask())); }
126         public String getAddress()                  { return format(toArray(address())); }
127 
128         /**
129          * Return the low address as a dotted IP address.
130          * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
131          * 
132          * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
133          */
134         public String getLowAddress()               { return format(toArray(low())); }
135 
136         /**
137          * Return the high address as a dotted IP address.
138          * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
139          * 
140          * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
141          */
142         public String getHighAddress()              { return format(toArray(high())); }
143         
144         /**
145          * Get the count of available addresses.
146          * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
147          * @return the count of addresses, may be zero.
148          */
149         public int getAddressCount()                { 
150             int count = broadcast() - network() + (isInclusiveHostCount() ? 1 : -1);
151             return count < 0 ? 0 : count;
152         }
153 
154         public int asInteger(String address)        { return toInteger(address); }
155 
156         public String getCidrSignature() {
157             return toCidrNotation(
158                     format(toArray(address())),
159                     format(toArray(netmask()))
160             );
161         }
162 
163         public String[] getAllAddresses() {
164             int ct = getAddressCount();
165             String[] addresses = new String[ct];
166             if (ct == 0) {
167                 return addresses;
168             }
169             for (int add = low(), j=0; add <= high(); ++add, ++j) {
170                 addresses[j] = format(toArray(add));
171             }
172             return addresses;
173         }
174 
175         /**
176          * {@inheritDoc}
177          * @since 2.2
178          */
179         @Override
180         public String toString() {
181             final StringBuilder buf = new StringBuilder();
182             buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]")
183                 .append(" Netmask: [").append(getNetmask()).append("]\n")
184                 .append("Network:\t[").append(getNetworkAddress()).append("]\n")
185                 .append("Broadcast:\t[").append(getBroadcastAddress()).append("]\n")
186                  .append("First Address:\t[").append(getLowAddress()).append("]\n")
187                  .append("Last Address:\t[").append(getHighAddress()).append("]\n")
188                  .append("# Addresses:\t[").append(getAddressCount()).append("]\n");
189             return buf.toString();
190         }
191     }
192 
193     /**
194      * Return a {@link SubnetInfo} instance that contains subnet-specific statistics
195      * @return new instance
196      */
197     public final SubnetInfo getInfo() { return new SubnetInfo(); }
198 
199     /*
200      * Initialize the internal fields from the supplied CIDR mask
201      */
202     private void calculate(String mask) {
203         Matcher matcher = cidrPattern.matcher(mask);
204 
205         if (matcher.matches()) {
206             address = matchAddress(matcher);
207 
208             /* Create a binary netmask from the number of bits specification /x */
209             int cidrPart = rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS);
210             for (int j = 0; j < cidrPart; ++j) {
211                 netmask |= (1 << 31-j);
212             }
213 
214             /* Calculate base network address */
215             network = (address & netmask);
216 
217             /* Calculate broadcast address */
218             broadcast = network | ~(netmask);
219         } else {
220             throw new IllegalArgumentException("Could not parse [" + mask + "]");
221         }
222     }
223 
224     /*
225      * Convert a dotted decimal format address to a packed integer format
226      */
227     private int toInteger(String address) {
228         Matcher matcher = addressPattern.matcher(address);
229         if (matcher.matches()) {
230             return matchAddress(matcher);
231         } else {
232             throw new IllegalArgumentException("Could not parse [" + address + "]");
233         }
234     }
235 
236     /*
237      * Convenience method to extract the components of a dotted decimal address and
238      * pack into an integer using a regex match
239      */
240     private int matchAddress(Matcher matcher) {
241         int addr = 0;
242         for (int i = 1; i <= 4; ++i) {
243             int n = (rangeCheck(Integer.parseInt(matcher.group(i)), -1, 255));
244             addr |= ((n & 0xff) << 8*(4-i));
245         }
246         return addr;
247     }
248 
249     /*
250      * Convert a packed integer address into a 4-element array
251      */
252     private int[] toArray(int val) {
253         int ret[] = new int[4];
254         for (int j = 3; j >= 0; --j) {
255             ret[j] |= ((val >>> 8*(3-j)) & (0xff));
256         }
257         return ret;
258     }
259 
260     /*
261      * Convert a 4-element array into dotted decimal format
262      */
263     private String format(int[] octets) {
264         StringBuilder str = new StringBuilder();
265         for (int i =0; i < octets.length; ++i){
266             str.append(octets[i]);
267             if (i != octets.length - 1) {
268                 str.append(".");
269             }
270         }
271         return str.toString();
272     }
273 
274     /*
275      * Convenience function to check integer boundaries.
276      * Checks if a value x is in the range (begin,end].
277      * Returns x if it is in range, throws an exception otherwise.
278      */
279     private int rangeCheck(int value, int begin, int end) {
280         if (value > begin && value <= end) { // (begin,end]
281             return value;
282         }
283 
284         throw new IllegalArgumentException("Value [" + value + "] not in range ("+begin+","+end+"]");
285     }
286 
287     /*
288      * Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy
289      * see Hacker's Delight section 5.1
290      */
291     int pop(int x) {
292         x = x - ((x >>> 1) & 0x55555555);
293         x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
294         x = (x + (x >>> 4)) & 0x0F0F0F0F;
295         x = x + (x >>> 8);
296         x = x + (x >>> 16);
297         return x & 0x0000003F;
298     }
299 
300     /* Convert two dotted decimal addresses to a single xxx.xxx.xxx.xxx/yy format
301      * by counting the 1-bit population in the mask address. (It may be better to count
302      * NBITS-#trailing zeroes for this case)
303      */
304     private String toCidrNotation(String addr, String mask) {
305         return addr + "/" + pop(toInteger(mask));
306     }
307 }