/***************************************************************************** * * Copyright (C) Zenoss, Inc. 2011, all rights reserved. * * This content is made available according to terms specified in * License.zenoss under the directory where your Zenoss product is installed. * ****************************************************************************/ package org.zenoss.zep.utils; import com.google.common.net.InetAddresses; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * IP address utility methods. */ public final class IpUtils { private IpUtils() { // Utility class - don't instantiate } private static final Pattern IPV4_PATTERN = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"); /** * Parses the string into an InetAddress ensuring that no DNS lookup is performed * on the address (it must be specified as a literal IPv4 or IPv6 address). * * @param value String IP address. * @return InetAddress for the specified address. * @throws IllegalArgumentException If the address is invalid. */ public static InetAddress parseAddress(String value) throws IllegalArgumentException { // Looks for an IPv6 or IPv4 address. Discards anything that looks like a hostname // to prevent long running hostname lookups. final InetAddress addr; if (value.indexOf(':') != -1) { if (value.startsWith("[") && value.endsWith("]")) { // We expect an IPv6 address value = value.substring(1, value.length()-1); } // Use Guava IPv6 parsing - previous parsing performed DNS lookups addr = InetAddresses.forString(value); } else { Matcher matcher = IPV4_PATTERN.matcher(value); if (matcher.matches()) { final byte[] bytes = new byte[4]; for (int i = 1; i <= matcher.groupCount(); i++) { int octet = Integer.parseInt(matcher.group(i)); if (octet > 255) { throw new IllegalArgumentException("Invalid IP address: " + value); } bytes[i-1] = (byte)octet; } try { addr = InetAddress.getByAddress(bytes); } catch (UnknownHostException e) { throw new IllegalArgumentException(e.getLocalizedMessage(), e); } } else { throw new IllegalArgumentException("Invalid IP address: " + value); } } return addr; } /** * Converts the IP address to a canonical string which allows correct comparison * behavior with IP address (used for sorting and range queries). IPv4 and IPv6 * addresses are converted to have leading zeros in addresses (i.e. 192.168.1.2 becomes * 192.168.001.002 and ::1 becomes 0000:0000:0000:0000:0000:0000:0000:0001). * * @param addr IP address. * @return The canonical format used for sorting and range queries. */ public static String canonicalIpAddress(InetAddress addr) { StringBuilder sb = new StringBuilder(36); byte[] addrbytes = addr.getAddress(); if (addrbytes.length == 4) { for (byte b : addrbytes) { int i = (b & 0xff); if (sb.length() > 0) { sb.append('.'); } sb.append(String.format("%03d", i)); } } else if (addrbytes.length == 16) { for (int i = 0; i < addrbytes.length; i++) { int octet = (addrbytes[i] & 0xff); if (sb.length() > 0 && (i % 2) == 0) { sb.append(':'); } sb.append(String.format("%02x", octet)); } } else { throw new IllegalStateException("Unexpected InetAddress storage"); } return sb.toString(); } /** * Converts a netmask to the number of prefix bits. * * @param netmask The netmask. * @return The number of prefix bits. * @throws IllegalArgumentException If the netmask is invalid. */ public static int netmaskToPrefixBits(InetAddress netmask) throws IllegalArgumentException { int prefixBits = 0; boolean foundZero = false; byte[] netmaskBytes = netmask.getAddress(); for (byte b : netmaskBytes) { for (int i = 7; i >= 0; i--) { if ((b & (1<<i)) != 0) { if (foundZero) { throw new IllegalArgumentException("Invalid netmask"); } ++prefixBits; } else { foundZero = true; } } } return prefixBits; } /** * Converts the specified number of prefix bits to a InetAddress representing * the netmask. * * @param prefixBits The number of prefix bits in the netmask. * @param isIPv6 True if the returned netmask should be IPv6, otherwise uses * IPv4. * @return An InetAddress representing the netmask. * @throws IllegalArgumentException If the prefix bits are out of range. */ public static InetAddress prefixBitsToNetmask(int prefixBits, boolean isIPv6) throws IllegalArgumentException { if (prefixBits < 0) { throw new IllegalArgumentException("Invalid prefix bits"); } final byte[] bytes; if (isIPv6) { if (prefixBits > 128) { throw new IllegalArgumentException("Invalid prefix bits"); } bytes = new byte[16]; } else { if (prefixBits > 32) { throw new IllegalArgumentException("Invalid prefix bits"); } bytes = new byte[4]; } int byteOffset = 0; int bitOffset = 7; for (int i = 0; i < prefixBits; i++) { bytes[byteOffset] |= (1<<bitOffset); --bitOffset; if (bitOffset < 0) { ++byteOffset; bitOffset = 7; } } try { return InetAddress.getByAddress(bytes); } catch (UnknownHostException e) { throw new IllegalArgumentException(e.getLocalizedMessage(), e); } } /** * Given an IP address and a netmask, returns the first address in the range * specified by the netmask (e.g. 192.168.1.2, 255.255.255.0 -> 192.168.1.0). * * @param addr IP address. * @param mask Netmask. * @return The last address in the range specified by the address/netmask. */ private static InetAddress firstAddress(InetAddress addr, InetAddress mask) { final byte[] addrBytes = addr.getAddress(); final byte[] maskBytes = mask.getAddress(); for (int i = 0; i < addrBytes.length; i++) { addrBytes[i] &= maskBytes[i]; } try { return InetAddress.getByAddress(addrBytes); } catch (UnknownHostException e) { throw new IllegalArgumentException(e.getLocalizedMessage(), e); } } /** * Given an IP address and a netmask, returns the last address in the range * specified by the netmask (e.g. 192.168.1.2, 255.255.255.0 -> 192.168.1.255). * * @param addr IP address. * @param mask Netmask. * @return The last address in the range specified by the address/netmask. */ private static InetAddress lastAddress(InetAddress addr, InetAddress mask) { final byte[] addrBytes = addr.getAddress(); final byte[] maskBytes = mask.getAddress(); for (int i = 0; i < addrBytes.length; i++) { addrBytes[i] |= (~maskBytes[i]); } try { return InetAddress.getByAddress(addrBytes); } catch (UnknownHostException e) { throw new IllegalArgumentException(e.getLocalizedMessage(), e); } } /** * <p>Parses an IP range in the format to an IP range.</p> * <ul> * <li>ADDR/MASK</li> * <li>ADDR/PREFIXBITS</li> * <li>STARTADDR-ENDADDR</li> * <li>ADDR (Converts to a range that includes just the address).</li> * </ul> * * @param ipRange IP range string. * @return An IP range representing the start and end of the IP range. * @throws IllegalArgumentException If the specified IP range is invalid. */ public static IpRange parseRange(String ipRange) throws IllegalArgumentException { final int slashIndex, dashIndex; final IpRange range; if ((slashIndex = ipRange.indexOf('/')) > 0) { final InetAddress from = parseAddress(ipRange.substring(0, slashIndex)); InetAddress mask; try { mask = parseAddress(ipRange.substring(slashIndex+1)); } catch (IllegalArgumentException e) { try { int prefixBits = Integer.parseInt(ipRange.substring(slashIndex+1)); mask = prefixBitsToNetmask(prefixBits, from instanceof Inet6Address); } catch (NumberFormatException nfe) { throw new IllegalArgumentException(nfe.getLocalizedMessage(), nfe); } } range = new IpRange(firstAddress(from, mask), lastAddress(from, mask)); } else if ((dashIndex = ipRange.indexOf('-')) > 0) { final InetAddress from = parseAddress(ipRange.substring(0, dashIndex)); InetAddress to = null; try { to = parseAddress(ipRange.substring(dashIndex+1)); } catch (IllegalArgumentException e) { final byte[] fromAddr = from.getAddress(); if (fromAddr.length == 4) { try { final int lastByte = Integer.parseInt(ipRange.substring(dashIndex+1)); if (lastByte < 0 || lastByte > 255) { throw new IllegalArgumentException("Invalid IP range: " + ipRange); } fromAddr[3] = (byte) lastByte; to = InetAddress.getByAddress(fromAddr); } catch (RuntimeException ex) { throw new IllegalArgumentException(e.getLocalizedMessage(), e); } catch (UnknownHostException ex) { throw new IllegalArgumentException(e.getLocalizedMessage(), e); } } else if (fromAddr.length == 16) { try { final int lastTwoBytes = Integer.parseInt(ipRange.substring(dashIndex+1), 16); if (lastTwoBytes < 0 || lastTwoBytes > 0xffff) { throw new IllegalArgumentException("Invalid IP range: " + ipRange); } ByteBuffer.wrap(fromAddr).putShort(14, (short) lastTwoBytes); to = InetAddress.getByAddress(fromAddr); } catch (RuntimeException ex) { throw new IllegalArgumentException(e.getLocalizedMessage(), e); } catch (UnknownHostException ex) { throw new IllegalArgumentException(e.getLocalizedMessage(), e); } } } range = new IpRange(from, to); } else { final InetAddress addr = parseAddress(ipRange); range = new IpRange(addr, addr); } return range; } }