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      /**
42       * Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16"
43       * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16"
44       */
45      public SubnetUtils(String cidrNotation) {
46          calculate(cidrNotation);
47      }
48  
49      /**
50       * Constructor that takes two dotted decimal addresses. 
51       * @param address An IP address, e.g. "192.168.0.1"
52       * @param mask A dotted decimal netmask e.g. "255.255.0.0"
53       */
54      public SubnetUtils(String address, String mask) {
55          calculate(toCidrNotation(address, mask));
56      }
57  
58      /**
59       * Convenience container for subnet summary information.
60       *
61       */
62      public final class SubnetInfo {
63          private SubnetInfo() {}
64  
65          private int netmask()       { return netmask; }
66          private int network()       { return network; }
67          private int address()       { return address; }
68          private int broadcast()     { return broadcast; }
69          private int low()           { return network() + 1; }
70          private int high()          { return broadcast() - 1; }
71  
72          public boolean isInRange(String address)    { return isInRange(toInteger(address)); }
73          private boolean isInRange(int address)      { return ((address-low()) <= (high()-low())); }
74  
75          public String getBroadcastAddress()         { return format(toArray(broadcast())); }
76          public String getNetworkAddress()           { return format(toArray(network())); }
77          public String getNetmask()                  { return format(toArray(netmask())); }
78          public String getAddress()                  { return format(toArray(address())); }
79          public String getLowAddress()               { return format(toArray(low())); }
80          public String getHighAddress()              { return format(toArray(high())); }
81          public int getAddressCount()                { return (broadcast() - low()); }
82  
83          public int asInteger(String address)        { return toInteger(address); }
84          
85          public String getCidrSignature() { 
86              return toCidrNotation(
87                      format(toArray(address())), 
88                      format(toArray(netmask()))
89              );
90          }
91          
92          public String[] getAllAddresses() { 
93              String[] addresses = new String[getAddressCount()];
94              for (int add = low(), j=0; add <= high(); ++add, ++j) {
95                  addresses[j] = format(toArray(add));
96              }
97              return addresses;
98          }
99      }
100 
101     /**
102      * Return a {@link SubnetInfo} instance that contains subnet-specific statistics
103      * @return
104      */
105     public final SubnetInfo getInfo() { return new SubnetInfo(); }
106 
107     /*
108      * Initialize the internal fields from the supplied CIDR mask
109      */
110     private void calculate(String mask) {
111         Matcher matcher = cidrPattern.matcher(mask);
112 
113         if (matcher.matches()) {
114             address = matchAddress(matcher);
115 
116             /* Create a binary netmask from the number of bits specification /x */
117             int cidrPart = rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS-1);
118             for (int j = 0; j < cidrPart; ++j) {
119                 netmask |= (1 << 31-j);
120             }
121 
122             /* Calculate base network address */
123             network = (address & netmask);
124 
125             /* Calculate broadcast address */
126             broadcast = network | ~(netmask);
127         }
128         else 
129             throw new IllegalArgumentException("Could not parse [" + mask + "]");
130     }
131 
132     /*
133      * Convert a dotted decimal format address to a packed integer format
134      */
135     private int toInteger(String address) {
136         Matcher matcher = addressPattern.matcher(address);
137         if (matcher.matches()) {
138             return matchAddress(matcher);
139         }
140         else
141             throw new IllegalArgumentException("Could not parse [" + address + "]");
142     }
143 
144     /*
145      * Convenience method to extract the components of a dotted decimal address and 
146      * pack into an integer using a regex match
147      */
148     private int matchAddress(Matcher matcher) {
149         int addr = 0;
150         for (int i = 1; i <= 4; ++i) { 
151             int n = (rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255));
152             addr |= ((n & 0xff) << 8*(4-i));
153         }
154         return addr;
155     }
156 
157     /*
158      * Convert a packed integer address into a 4-element array
159      */
160     private int[] toArray(int val) {
161         int ret[] = new int[4];
162         for (int j = 3; j >= 0; --j)
163             ret[j] |= ((val >>> 8*(3-j)) & (0xff));
164         return ret;
165     }
166 
167     /*
168      * Convert a 4-element array into dotted decimal format
169      */
170     private String format(int[] octets) {
171         StringBuilder str = new StringBuilder();
172         for (int i =0; i < octets.length; ++i){
173             str.append(octets[i]);
174             if (i != octets.length - 1) {
175                 str.append("."); 
176             }
177         }
178         return str.toString();
179     }
180 
181     /*
182      * Convenience function to check integer boundaries
183      */
184     private int rangeCheck(int value, int begin, int end) {
185         if (value >= begin && value <= end)
186             return value;
187 
188         throw new IllegalArgumentException("Value out of range: [" + value + "]");
189     }
190 
191     /*
192      * Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy
193      * see Hacker's Delight section 5.1 
194      */
195     int pop(int x) {
196         x = x - ((x >>> 1) & 0x55555555); 
197         x = (x & 0x33333333) + ((x >>> 2) & 0x33333333); 
198         x = (x + (x >>> 4)) & 0x0F0F0F0F; 
199         x = x + (x >>> 8); 
200         x = x + (x >>> 16); 
201         return x & 0x0000003F; 
202     } 
203 
204     /* Convert two dotted decimal addresses to a single xxx.xxx.xxx.xxx/yy format
205      * by counting the 1-bit population in the mask address. (It may be better to count 
206      * NBITS-#trailing zeroes for this case)
207      */
208     private String toCidrNotation(String addr, String mask) {
209         return addr + "/" + pop(toInteger(mask));
210     }
211 }