001    
002    /*
003     * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004     *
005     * $Id: IPv4Util.java 2 2011-02-05 21:51:43Z archie.cobbs $
006     */
007    
008    package org.dellroad.stuff.net;
009    
010    import java.net.Inet4Address;
011    import java.net.InetAddress;
012    import java.net.UnknownHostException;
013    import java.util.regex.Matcher;
014    import java.util.regex.Pattern;
015    
016    /**
017     * Utility methods for working with IPv4 addresses and netmasks.
018     */
019    public final class IPv4Util {
020    
021        public static final int MIN_VALID_NETMASK_WIDTH = 8;
022        public static final int MAX_VALID_NETMASK_WIDTH = 30;
023    
024        private IPv4Util() {
025        }
026    
027        /**
028         * Convert from 32-bit integer representation.
029         */
030        public static Inet4Address toAddress(int address) {
031            return IPv4Util.toAddress(new byte[] {
032                (byte)((address >> 24) & 0xff),
033                (byte)((address >> 16) & 0xff),
034                (byte)((address >> 8) & 0xff),
035                (byte)(address & 0xff),
036            });
037        }
038    
039        /**
040         * Convert to 32-bit integer representation.
041         */
042        public static int toInt32(Inet4Address address) {
043            byte[] bytes = address.getAddress();
044            return ((bytes[0] & 0xff) << 24)
045                 | ((bytes[1] & 0xff) << 16)
046                 | ((bytes[2] & 0xff) << 8)
047                 | (bytes[3] & 0xff);
048        }
049    
050        /**
051         * Convert to an unsigned 32-bit integer representation within a 64-bit long.
052         */
053        public static long toUInt32(Inet4Address address) {
054            return toInt32(address) & 0x00000000ffffffffL;
055        }
056    
057        /**
058         * Convert raw bytes to an {@link Inet4Address}.
059         *
060         * @throws IllegalArgumentException if array does not have length four
061         */
062        public static Inet4Address toAddress(byte[] bytes) {
063            if (bytes.length != 4)
064                throw new IllegalArgumentException("array has length " + bytes.length + " != 4");
065            try {
066                return (Inet4Address)InetAddress.getByAddress(toString(bytes), bytes);
067            } catch (UnknownHostException e) {
068                throw new RuntimeException("unexpected exception", e);
069            }
070        }
071    
072        /**
073         * Convert an IPv4 address to string representation.
074         *
075         * @throws IllegalArgumentException if array does not have length four
076         */
077        public static String toString(Inet4Address address) {
078            return IPv4Util.toString(address.getAddress());
079        }
080    
081        /**
082         * Convert a network address (IPv4 base address and netmask) to string representation.
083         *
084         * @throws IllegalArgumentException if array does not have length four
085         */
086        public static String toString(Inet4Address address, Inet4Address netmask) {
087            return IPv4Util.toString(address) + "/" + IPv4Util.getWidth(netmask);
088        }
089    
090        private static String toString(byte[] bytes) {
091            assert bytes.length == 4;
092            return (bytes[0] & 0xff) + "." + (bytes[1] & 0xff) + "." + (bytes[2] & 0xff) + "." + (bytes[3] & 0xff);
093        }
094    
095        /**
096         * Convert from string representation.
097         *
098         * @throws IllegalArgumentException if string is not an IP address
099         */
100        public static Inet4Address fromString(String string) {
101            String bytepat = "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
102            Pattern pattern = Pattern.compile(bytepat + "\\." + bytepat + "\\." + bytepat + "\\." + bytepat);
103            Matcher matcher = pattern.matcher(string);
104            if (!matcher.matches())
105                throw new IllegalArgumentException("invalid IP address: " + string);
106            return toAddress(new byte[] {
107              (byte)Integer.parseInt(matcher.group(1)),
108              (byte)Integer.parseInt(matcher.group(2)),
109              (byte)Integer.parseInt(matcher.group(3)),
110              (byte)Integer.parseInt(matcher.group(4))
111            });
112        }
113    
114        /**
115         * Get netmask width.
116         *
117         * @return a value between zero and 32 (inclusive)
118         * @throws IllegalArgumentException if the value is not a valid CIDR netmask
119         */
120        public static int getWidth(Inet4Address netmask) {
121            int mask = toInt32(netmask);
122            if (mask == 0)
123                return 0;
124            for (int shift = 0; shift < 32; shift++) {
125                if (mask == (~0 << shift))
126                    return 32 - shift;
127            }
128            throw new IllegalArgumentException("invalid netmask " + netmask);
129        }
130    
131        /**
132         * Determine if the given address is a valid CIDR netmask.
133         * The netmask must have a width between 8 and 30 (inclusive).
134         */
135        public static boolean isValidNetmask(Inet4Address netmask) {
136            try {
137                int width = getWidth(netmask);
138                return width >= MIN_VALID_NETMASK_WIDTH && width <= MAX_VALID_NETMASK_WIDTH;
139            } catch (IllegalArgumentException e) {
140                return false;
141            }
142        }
143    
144        /**
145         * Get netmask with the given width.
146         *
147         * @throws IllegalArgumentException if width is less than zero or greater than 32
148         */
149        public static Inet4Address getNetmaskForWidth(int width) {
150            if (width < 0 || width > 32)
151                throw new IllegalArgumentException("invalid netmask width " + width);
152            return toAddress(~0 << (32 - width));
153        }
154    
155        /**
156         * Get the base address (all host bits zero) of the given IP network.
157         *
158         * @param address any IP address on the network
159         * @param netmask the netmask of the network
160         * @throws IllegalArgumentException if {@code netmask} is not a valid netmask
161         */
162        public static Inet4Address getBaseAddress(Inet4Address address, Inet4Address netmask) {
163            int addr = toInt32(address);
164            int width = getWidth(netmask);
165            addr &= ~0 << (32 - width);
166            return toAddress(addr);
167        }
168    
169        /**
170         * Get the broadcast address (all host bits one) of the given IP network.
171         *
172         * @param address any IP address on the network
173         * @param netmask the netmask of the network
174         * @throws IllegalArgumentException if {@code netmask} is not a valid netmask
175         */
176        public static Inet4Address getBroadcastAddress(Inet4Address address, Inet4Address netmask) {
177            int addr = toInt32(address);
178            int width = getWidth(netmask);
179            addr |= ~(~0 << (32 - width));
180            return toAddress(addr);
181        }
182    
183        /**
184         * Determine if the given address lives on the given network.
185         *
186         * @param address IP address in question
187         * @param network network address
188         * @param netmask network netmask
189         * @throws IllegalArgumentException if {@code netmask} is not a valid netmask
190         */
191        public static boolean isOnNetwork(Inet4Address address, Inet4Address network, Inet4Address netmask) {
192            int addr1 = toInt32(address);
193            int addr2 = toInt32(network);
194            int width = getWidth(netmask);
195            int mask = ~0 << (32 - width);
196            return (addr1 & mask) == (addr2 & mask);
197        }
198    
199        /**
200         * Determine if the given address is a valid host address on the given network.
201         * The address must lie on the given network and not equal either the base or broadcast addresses.
202         *
203         * @param address IP address in question
204         * @param network network address
205         * @param netmask network netmask
206         * @throws IllegalArgumentException if {@code netmask} is not a valid netmask
207         */
208        public static boolean isValidHostOnNetwork(Inet4Address address, Inet4Address network, Inet4Address netmask) {
209            return isOnNetwork(address, network, netmask)
210             && !address.equals(getBaseAddress(network, netmask))
211             && !address.equals(getBroadcastAddress(network, netmask));
212        }
213    }
214