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     *      http://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     */
017    package org.apache.commons.net.util;
018    
019    import java.util.regex.Matcher;
020    import java.util.regex.Pattern;
021    
022    /**
023     * A class that performs some subnet calculations given a network address and a subnet mask.
024     * @see "http://www.faqs.org/rfcs/rfc1519.html"
025     * @author <rwinston@apache.org>
026     * @since 2.0
027     */
028    public class SubnetUtils {
029    
030        private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
031        private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,3})";
032        private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS);
033        private static final Pattern cidrPattern = Pattern.compile(SLASH_FORMAT);
034        private static final int NBITS = 32;
035    
036        private int netmask = 0;
037        private int address = 0;
038        private int network = 0;
039        private int broadcast = 0;
040    
041        /** Whether the broadcast/network address are included in host count */
042        private boolean inclusiveHostCount = false;
043    
044    
045        /**
046         * Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16"
047         * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16"
048         * @throws IllegalArgumentException if the parameter is invalid, 
049         * 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
050         */
051        public SubnetUtils(String cidrNotation) {
052            calculate(cidrNotation);
053        }
054    
055        /**
056         * Constructor that takes a dotted decimal address and a dotted decimal mask.
057         * @param address An IP address, e.g. "192.168.0.1"
058         * @param mask A dotted decimal netmask e.g. "255.255.0.0"
059         * @throws IllegalArgumentException if the address or mask is invalid, 
060         * i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros
061         */
062        public SubnetUtils(String address, String mask) {
063            calculate(toCidrNotation(address, mask));
064        }
065    
066    
067        /**
068         * Returns <code>true</code> if the return value of {@link SubnetInfo#getAddressCount()}
069         * includes the network address and broadcast addresses.
070         * @since 2.2
071         */
072        public boolean isInclusiveHostCount() {
073            return inclusiveHostCount;
074        }
075    
076        /**
077         * Set to <code>true</code> if you want the return value of {@link SubnetInfo#getAddressCount()}
078         * to include the network and broadcast addresses.
079         * @param inclusiveHostCount
080         * @since 2.2
081         */
082        public void setInclusiveHostCount(boolean inclusiveHostCount) {
083            this.inclusiveHostCount = inclusiveHostCount;
084        }
085    
086    
087    
088        /**
089         * Convenience container for subnet summary information.
090         *
091         */
092        public final class SubnetInfo {
093            private SubnetInfo() {}
094    
095            private int netmask()       { return netmask; }
096            private int network()       { return network; }
097            private int address()       { return address; }
098            private int broadcast()     { return broadcast; }
099            private int low()           { return network() + (isInclusiveHostCount() ? 0 : 1); }
100            private int high()          { return broadcast() - (isInclusiveHostCount() ? 0 : 1); }
101    
102            /**
103             * Returns true if the parameter <code>address</code> is in the
104             * range of usable endpoint addresses for this subnet. This excludes the
105             * network and broadcast adresses.
106             * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1"
107             * @return True if in range, false otherwise
108             */
109            public boolean isInRange(String address)    { return isInRange(toInteger(address)); }
110    
111            private boolean isInRange(int address)      {
112                int diff = address-low();
113                return (diff >= 0 && (diff <= (high()-low())));
114            }
115    
116            public String getBroadcastAddress()         { return format(toArray(broadcast())); }
117            public String getNetworkAddress()           { return format(toArray(network())); }
118            public String getNetmask()                  { return format(toArray(netmask())); }
119            public String getAddress()                  { return format(toArray(address())); }
120            public String getLowAddress()               { return format(toArray(low())); }
121            public String getHighAddress()              { return format(toArray(high())); }
122            public int getAddressCount()                { return (broadcast() - low() + (isInclusiveHostCount() ? 1 : 0)); }
123    
124            public int asInteger(String address)        { return toInteger(address); }
125    
126            public String getCidrSignature() {
127                return toCidrNotation(
128                        format(toArray(address())),
129                        format(toArray(netmask()))
130                );
131            }
132    
133            public String[] getAllAddresses() {
134                String[] addresses = new String[getAddressCount()];
135                for (int add = low(), j=0; add <= high(); ++add, ++j) {
136                    addresses[j] = format(toArray(add));
137                }
138                return addresses;
139            }
140    
141            /**
142             * {@inheritDoc}
143             * @since 2.2
144             */
145            @Override
146            public String toString() {
147                final StringBuilder buf = new StringBuilder();
148                buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]")
149                    .append(" Netmask: [").append(getNetmask()).append("]\n")
150                    .append("Network:\t[").append(getNetworkAddress()).append("]\n")
151                    .append("Broadcast:\t[").append(getBroadcastAddress()).append("]\n")
152                     .append("First Address:\t[").append(getLowAddress()).append("]\n")
153                     .append("Last Address:\t[").append(getHighAddress()).append("]\n")
154                     .append("# Addresses:\t[").append(getAddressCount()).append("]\n");
155                return buf.toString();
156            }
157        }
158    
159        /**
160         * Return a {@link SubnetInfo} instance that contains subnet-specific statistics
161         * @return new instance
162         */
163        public final SubnetInfo getInfo() { return new SubnetInfo(); }
164    
165        /*
166         * Initialize the internal fields from the supplied CIDR mask
167         */
168        private void calculate(String mask) {
169            Matcher matcher = cidrPattern.matcher(mask);
170    
171            if (matcher.matches()) {
172                address = matchAddress(matcher);
173    
174                /* Create a binary netmask from the number of bits specification /x */
175                int cidrPart = rangeCheck(Integer.parseInt(matcher.group(5)), -1, NBITS);
176                for (int j = 0; j < cidrPart; ++j) {
177                    netmask |= (1 << 31-j);
178                }
179    
180                rangeCheck(pop(netmask),0, NBITS);
181    
182                /* Calculate base network address */
183                network = (address & netmask);
184    
185                /* Calculate broadcast address */
186                broadcast = network | ~(netmask);
187            }
188            else
189                throw new IllegalArgumentException("Could not parse [" + mask + "]");
190        }
191    
192        /*
193         * Convert a dotted decimal format address to a packed integer format
194         */
195        private int toInteger(String address) {
196            Matcher matcher = addressPattern.matcher(address);
197            if (matcher.matches()) {
198                return matchAddress(matcher);
199            }
200            else
201                throw new IllegalArgumentException("Could not parse [" + address + "]");
202        }
203    
204        /*
205         * Convenience method to extract the components of a dotted decimal address and
206         * pack into an integer using a regex match
207         */
208        private int matchAddress(Matcher matcher) {
209            int addr = 0;
210            for (int i = 1; i <= 4; ++i) {
211                int n = (rangeCheck(Integer.parseInt(matcher.group(i)), -1, 255));
212                addr |= ((n & 0xff) << 8*(4-i));
213            }
214            return addr;
215        }
216    
217        /*
218         * Convert a packed integer address into a 4-element array
219         */
220        private int[] toArray(int val) {
221            int ret[] = new int[4];
222            for (int j = 3; j >= 0; --j)
223                ret[j] |= ((val >>> 8*(3-j)) & (0xff));
224            return ret;
225        }
226    
227        /*
228         * Convert a 4-element array into dotted decimal format
229         */
230        private String format(int[] octets) {
231            StringBuilder str = new StringBuilder();
232            for (int i =0; i < octets.length; ++i){
233                str.append(octets[i]);
234                if (i != octets.length - 1) {
235                    str.append(".");
236                }
237            }
238            return str.toString();
239        }
240    
241        /*
242         * Convenience function to check integer boundaries.
243         * Checks if a value x is in the range (begin,end].
244         * Returns x if it is in range, throws an exception otherwise.
245         */
246        private int rangeCheck(int value, int begin, int end) {
247            if (value > begin && value <= end) // (begin,end]
248                return value;
249    
250            throw new IllegalArgumentException("Value [" + value + "] not in range ("+begin+","+end+"]");
251        }
252    
253        /*
254         * Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy
255         * see Hacker's Delight section 5.1
256         */
257        int pop(int x) {
258            x = x - ((x >>> 1) & 0x55555555);
259            x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
260            x = (x + (x >>> 4)) & 0x0F0F0F0F;
261            x = x + (x >>> 8);
262            x = x + (x >>> 16);
263            return x & 0x0000003F;
264        }
265    
266        /* Convert two dotted decimal addresses to a single xxx.xxx.xxx.xxx/yy format
267         * by counting the 1-bit population in the mask address. (It may be better to count
268         * NBITS-#trailing zeroes for this case)
269         */
270        private String toCidrNotation(String addr, String mask) {
271            return addr + "/" + pop(toInteger(mask));
272        }
273    }