/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2007-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.core.utils; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import org.xbill.DNS.AAAARecord; import org.xbill.DNS.ARecord; import org.xbill.DNS.Lookup; import org.xbill.DNS.Record; import org.xbill.DNS.TextParseException; import org.xbill.DNS.Type; /** * <p>Abstract InetAddressUtils class.</p> * * @author <a href="mailto:brozow@opennms.org">Mathew Brozowski</a> * @version $Id: $ */ abstract public class InetAddressUtils { private static final ByteArrayComparator s_BYTE_ARRAY_COMPARATOR = new ByteArrayComparator(); public static final InetAddress UNPINGABLE_ADDRESS; public static final InetAddress UNPINGABLE_ADDRESS_IPV6; static { try { // This address (192.0.2.123) is within a range of test IPs that // that is guaranteed to be non-routed. // UNPINGABLE_ADDRESS = InetAddress.getByAddress(new byte[] {(byte)192, (byte)0, (byte)2, (byte)123}); } catch (final UnknownHostException e) { throw new IllegalStateException(e); } try { // This address is within a subnet of "Unique Unicast" IPv6 addresses // that are defined by RFC4193. This is the IPv6 equivalent of the // 192.168.0.0/16 subnet and because the IPv6 address space is so large, // you can just randomly generate the first portion of the address. :) // I used an online address generator to get this particular address. // // http://www.rfc-editor.org/rfc/rfc4193.txt // UNPINGABLE_ADDRESS_IPV6 = InetAddress.getByName("fd25:28a0:ba2f:6b78:0000:0000:0000:0001"); } catch (final UnknownHostException e) { throw new IllegalStateException(e); } } public static enum AddressType { IPv4, IPv6 } public static InetAddress getLocalHostAddress() { try { return InetAddress.getLocalHost(); } catch (final UnknownHostException e) { LogUtils.warnf(InetAddressUtils.class, e, "getLocalHostAddress: Could not lookup the host address for the local host machine, address set to '127.0.0.1'."); return addr("127.0.0.1"); } } public static String getLocalHostAddressAsString() { final String localhost = str(getLocalHostAddress()); return localhost == null? "127.0.0.1" : localhost; } public static String getLocalHostName() { final InetAddress localHostAddress = getLocalHostAddress(); if (localHostAddress == null) { LogUtils.warnf(InetAddressUtils.class, "getLocalHostName: Could not lookup the host name for the local host machine, name set to 'localhost'."); return "localhost"; } return localHostAddress.getHostName(); } public static String incr(final String address) throws UnknownHostException { return InetAddressUtils.toIpAddrString(incr(InetAddressUtils.toIpAddrBytes(address))); } public static byte[] incr(final byte[] address) throws UnknownHostException { final BigInteger addr = new BigInteger(1, address).add(BigInteger.ONE); return convertBigIntegerIntoInetAddress(addr).getAddress(); } public static String decr(final String address) throws UnknownHostException { return InetAddressUtils.toIpAddrString(decr(InetAddressUtils.toIpAddrBytes(address))); } public static byte[] decr(final byte[] address) throws UnknownHostException { final BigInteger addr = new BigInteger(1, address).subtract(BigInteger.ONE); return convertBigIntegerIntoInetAddress(addr).getAddress(); } /** * <p>getInetAddress</p> * * @param ipAddrOctets an array of byte. * @return a {@link java.net.InetAddress} object. */ public static InetAddress getInetAddress(final int[] octets, final int offset, final int length) { final byte[] addressBytes = new byte[length]; for (int i = 0; i < addressBytes.length; i++) { addressBytes[i] = Integer.valueOf(octets[i + offset]).byteValue(); } return getInetAddress(addressBytes); } /** * <p>getInetAddress</p> * * @param ipAddrOctets an array of byte. * @return a {@link java.net.InetAddress} object. */ public static InetAddress getInetAddress(final byte[] ipAddrOctets) { try { return InetAddress.getByAddress(ipAddrOctets); } catch (final UnknownHostException e) { throw new IllegalArgumentException("Invalid IPAddress " + ipAddrOctets + " with length " + ipAddrOctets.length); } } /** * <p>getInetAddress</p> * * @param dottedNotation a {@link java.lang.String} object. * @return a {@link java.net.InetAddress} object. */ public static InetAddress getInetAddress(final String dottedNotation) { try { return InetAddress.getByName(dottedNotation); } catch (final UnknownHostException e) { throw new IllegalArgumentException("Invalid IPAddress " + dottedNotation); } } public static InetAddress resolveHostname(final String hostname, final boolean preferInet6Address) throws UnknownHostException { return resolveHostname(hostname, preferInet6Address, true); } /** * This function is used inside XSLT documents, do a string search before refactoring. */ public static InetAddress resolveHostname(final String hostname, final boolean preferInet6Address, final boolean throwException) throws UnknownHostException { InetAddress retval = null; //System.out.println(String.format("%s (%s)", hostname, preferInet6Address ? "6" : "4")); // Do a special case for localhost since the DNS server will generally not // return valid A and AAAA records for "localhost". if ("localhost".equals(hostname)) { return preferInet6Address ? InetAddress.getByName("::1") : InetAddress.getByName("127.0.0.1"); } try { // 2011-05-22 - Matt is seeing some platform-specific inconsistencies when using // InetAddress.getAllByName(). It seems to miss some addresses occasionally on Mac. // We need to use dnsjava here instead since it should be 100% reliable. // // InetAddress[] addresses = InetAddress.getAllByName(hostname); // List<InetAddress> v4Addresses = new ArrayList<InetAddress>(); try { Record[] aRecs = new Lookup(hostname, Type.A).run(); if (aRecs != null) { for (Record aRec : aRecs) { if (aRec instanceof ARecord) { InetAddress addr = ((ARecord)aRec).getAddress(); if (addr instanceof Inet4Address) { v4Addresses.add(addr); } else { // Should never happen throw new UnknownHostException("Non-IPv4 address found via A record DNS lookup of host: " + hostname + ": " + addr.toString()); } } } } else { //throw new UnknownHostException("No IPv4 addresses found via A record DNS lookup of host: " + hostname); } } catch (final TextParseException e) { final UnknownHostException ex = new UnknownHostException("Could not perform A record lookup for host: " + hostname); ex.initCause(e); throw ex; } final List<InetAddress> v6Addresses = new ArrayList<InetAddress>(); try { final Record[] quadARecs = new Lookup(hostname, Type.AAAA).run(); if (quadARecs != null) { for (final Record quadARec : quadARecs) { final InetAddress addr = ((AAAARecord)quadARec).getAddress(); if (addr instanceof Inet6Address) { v6Addresses.add(addr); } else { // Should never happen throw new UnknownHostException("Non-IPv6 address found via AAAA record DNS lookup of host: " + hostname + ": " + addr.toString()); } } } else { // throw new UnknownHostException("No IPv6 addresses found via AAAA record DNS lookup of host: " + hostname); } } catch (final TextParseException e) { final UnknownHostException ex = new UnknownHostException("Could not perform AAAA record lookup for host: " + hostname); ex.initCause(e); throw ex; } final List<InetAddress> addresses = new ArrayList<InetAddress>(); if (preferInet6Address) { addresses.addAll(v6Addresses); addresses.addAll(v4Addresses); } else { addresses.addAll(v4Addresses); addresses.addAll(v6Addresses); } for (final InetAddress address : addresses) { retval = address; if (!preferInet6Address && retval instanceof Inet4Address) break; if (preferInet6Address && retval instanceof Inet6Address) break; } if (preferInet6Address && !(retval instanceof Inet6Address)) { throw new UnknownHostException("No IPv6 address could be found for the hostname: " + hostname); } } catch (final UnknownHostException e) { if (throwException) { throw e; } else { //System.out.println(String.format("UnknownHostException for : %s (%s)", hostname, preferInet6Address ? "6" : "4")); //e.printStackTrace(); return null; } } return retval; } /** * <p>toIpAddrBytes</p> * * @param dottedNotation a {@link java.lang.String} object. * @return an array of byte. */ public static byte[] toIpAddrBytes(final String dottedNotation) { return getInetAddress(dottedNotation).getAddress(); } /** * <p>toIpAddrString</p> * * @param addr IP address * @return a {@link java.lang.String} object. */ public static String toIpAddrString(final InetAddress addr) { if (addr == null) { throw new IllegalArgumentException("Cannot convert null InetAddress to a string"); } else { final byte[] address = addr.getAddress(); if (address == null) { // This case can occur when Jersey uses Spring bean classes which use // CGLIB bytecode manipulation to generate InetAddress classes. This will // occur during REST calls. {@see org.opennms.web.rest.NodeRestServiceTest} // throw new IllegalArgumentException("InetAddress instance violates contract by returning a null address from getAddress()"); } else if (addr instanceof Inet4Address) { return toIpAddrString(address); } else if (addr instanceof Inet6Address) { final Inet6Address addr6 = (Inet6Address)addr; final StringBuilder sb = new StringBuilder(toIpAddrString(address)); if (addr6.getScopeId() != 0) { sb.append("%").append(addr6.getScopeId()); } return sb.toString(); } else { throw new IllegalArgumentException("Unknown type of InetAddress: " + addr.getClass().getName()); } } } /** * <p>toIpAddrString</p> * * @param addr an array of byte. * @return a {@link java.lang.String} object. */ public static String toIpAddrString(final byte[] addr) { if (addr.length == 4) { return getInetAddress(addr).getHostAddress(); } else if (addr.length == 16) { return String.format("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15] ).intern(); } else { throw new IllegalArgumentException("IP address has an illegal number of bytes: " + addr.length); } } /** * Given a list of IP addresses, return the lowest as determined by the * numeric representation and not the alphanumeric string. * * @param addresses a {@link java.util.List} object. * @return a {@link java.net.InetAddress} object. */ public static InetAddress getLowestInetAddress(final List<InetAddress> addresses) { if (addresses == null) { throw new IllegalArgumentException("Cannot take null parameters."); } InetAddress lowest = null; // Start with the highest conceivable IP address value final byte[] originalBytes = toIpAddrBytes("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); byte[] lowestBytes = originalBytes; for (final InetAddress temp : addresses) { byte[] tempBytes = temp.getAddress(); if (s_BYTE_ARRAY_COMPARATOR.compare(tempBytes, lowestBytes) < 0) { lowestBytes = tempBytes; lowest = temp; } } return s_BYTE_ARRAY_COMPARATOR.compare(originalBytes, lowestBytes) == 0 ? null : lowest; } public static BigInteger difference(final String addr1, final String addr2) { return difference(getInetAddress(addr1), getInetAddress(addr2)); } public static BigInteger difference(final InetAddress addr1, final InetAddress addr2) { return new BigInteger(1, addr1.getAddress()).subtract(new BigInteger(1, addr2.getAddress())); } public static boolean isInetAddressInRange(final byte[] laddr, final String beginString, final String endString) { final byte[] begin = InetAddressUtils.toIpAddrBytes(beginString); final byte[] end = InetAddressUtils.toIpAddrBytes(endString); return isInetAddressInRange(laddr, begin, end); } public static boolean isInetAddressInRange(final String addrString, final String beginString, final String endString) { final byte[] addr = InetAddressUtils.toIpAddrBytes(addrString); final byte[] begin = InetAddressUtils.toIpAddrBytes(beginString); if (s_BYTE_ARRAY_COMPARATOR.compare(addr, begin) > 0) { final byte[] end = InetAddressUtils.toIpAddrBytes(endString); if (s_BYTE_ARRAY_COMPARATOR.compare(addr, end) <= 0) { return true; } else { return false; } } else if (s_BYTE_ARRAY_COMPARATOR.compare(addr, begin) == 0) { return true; } else { return false; } } public static boolean inSameScope(final InetAddress addr1, final InetAddress addr2) { if (addr1 instanceof Inet4Address) { if (addr2 instanceof Inet4Address) { return true; } else { return false; } } else { if (addr2 instanceof Inet4Address) { return false; } else { // Compare the IPv6 scope IDs return new Integer(((Inet6Address)addr1).getScopeId()).compareTo(((Inet6Address)addr2).getScopeId()) == 0; } } } public static boolean isInetAddressInRange(final byte[] addr, final byte[] begin, final byte[] end) { if (s_BYTE_ARRAY_COMPARATOR.compare(addr, begin) > 0) { if (s_BYTE_ARRAY_COMPARATOR.compare(addr, end) <= 0) { return true; } else { return false; } } else if (s_BYTE_ARRAY_COMPARATOR.compare(addr, begin) == 0) { return true; } else { return false; } } public static boolean isInetAddressInRange(final String ipAddr, final byte[] begin, final byte[] end) { return isInetAddressInRange(InetAddressUtils.toIpAddrBytes(ipAddr), begin, end); } public static InetAddress convertCidrToInetAddressV4(int cidr) { if (cidr < 0 || cidr > 32) { throw new IllegalArgumentException("Illegal IPv4 CIDR mask length: " + cidr); } StringBuffer binaryString = new StringBuffer(); int i = 0; for (; i < cidr; i++) { binaryString.append('1'); } for (; i < 32; i++) { binaryString.append('0'); } try { return convertBigIntegerIntoInetAddress(new BigInteger(binaryString.toString(), 2)); } catch (UnknownHostException e) { throw new IllegalArgumentException("Could not convert CIDR mask to InetAddress: " + e.getMessage()); } } public static InetAddress convertCidrToInetAddressV6(int cidr) { if (cidr < 0 || cidr > 128) { throw new IllegalArgumentException("Illegal IPv6 CIDR mask length: " + cidr); } StringBuffer binaryString = new StringBuffer(); int i = 0; for (; i < cidr; i++) { binaryString.append('1'); } for (; i < 128; i++) { binaryString.append('0'); } try { return convertBigIntegerIntoInetAddress(new BigInteger(binaryString.toString(), 2)); } catch (UnknownHostException e) { throw new IllegalArgumentException("Could not convert CIDR mask to InetAddress: " + e.getMessage()); } } public static InetAddress convertBigIntegerIntoInetAddress(final BigInteger i) throws UnknownHostException { if (i.compareTo(BigInteger.ZERO) < 0) { throw new IllegalArgumentException("BigInteger is negative, cannot convert into an IP address: " + i.toString()); } else { // Note: This function will return the two's complement byte array so there will always // be a bit of value '0' (indicating positive sign) at the first position of the array // and it will be padded to the byte boundry. For example: // // 255.255.255.255 => 00 FF FF FF FF (5 bytes) // 127.0.0.1 => 0F 00 00 01 (4 bytes) // final byte[] bytes = i.toByteArray(); if (bytes.length == 0) { return InetAddress.getByAddress(new byte[] {0, 0, 0, 0}); } else if (bytes.length <= 4) { // This case covers an IPv4 address with the most significant bit of zero (the MSB // will be used as the two's complement sign bit) final byte[] addressBytes = new byte[4]; int k = 3; for (int j = bytes.length - 1; j >= 0; j--, k--) { addressBytes[k] = bytes[j]; } return InetAddress.getByAddress(addressBytes); } else if (bytes.length <= 5 && bytes[0] == 0) { // This case covers an IPv4 address (4 bytes + two's complement sign bit of zero) final byte[] addressBytes = new byte[4]; int k = 3; for (int j = bytes.length - 1; j >= 1; j--, k--) { addressBytes[k] = bytes[j]; } return InetAddress.getByAddress(addressBytes); } else if (bytes.length <= 16) { // This case covers an IPv6 address with the most significant bit of zero (the MSB // will be used as the two's complement sign bit) final byte[] addressBytes = new byte[16]; int k = 15; for (int j = bytes.length - 1; j >= 0; j--, k--) { addressBytes[k] = bytes[j]; } return InetAddress.getByAddress(addressBytes); } else if (bytes.length <= 17 && bytes[0] == 0) { // This case covers an IPv6 address (16 bytes + two's complement sign bit of zero) final byte[] addressBytes = new byte[16]; int k = 15; for (int j = bytes.length - 1; j >= 1; j--, k--) { addressBytes[k] = bytes[j]; } return InetAddress.getByAddress(addressBytes); } else { throw new IllegalArgumentException("BigInteger is too large to convert into an IP address: " + i.toString()); } } } public static InetAddress addr(final String ipAddrString) { return ipAddrString == null ? null : getInetAddress(ipAddrString.trim()); } /** * This function is used to ensure that an IP address string is in fully-qualified * format without any "::" segments for an IPv6 address. * * FIXME: do we lose */ public static String normalize(final String ipAddrString) { return ipAddrString == null? null : toIpAddrString(addr(ipAddrString.trim())); } public static String str(final InetAddress addr) { return addr == null ? null : toIpAddrString(addr); } public static BigInteger toInteger(final InetAddress ipAddress) { return new BigInteger(1, ipAddress.getAddress()); } public static String toOid(final InetAddress addr) { if (addr == null) return null; if (addr instanceof Inet4Address) { return str(addr); } else if (addr instanceof Inet6Address) { // This is horribly inefficient, I'm sure, but good enough for now. final byte[] buf = addr.getAddress(); final StringBuilder sb = new StringBuilder(); for (int i = 0; i < buf.length; i++) { sb.append(buf[i] & 0xff); if (i != (buf.length - 1)) { sb.append("."); } } return sb.toString(); } else { LogUtils.debugf(InetAddressUtils.class, "don't know how to handle %s", addr); return null; } } public static byte[] macAddressStringToBytes(String macAddress) { if (macAddress == null) { throw new IllegalArgumentException("Cannot decode null MAC address"); } byte[] contents = new byte[6]; String[] digits = macAddress.split(":"); if (digits.length != 6) { // If the MAC address is 12 hex digits long if (macAddress.length() == 12) { digits = new String[] { macAddress.substring(0, 2), macAddress.substring(2, 4), macAddress.substring(4, 6), macAddress.substring(6, 8), macAddress.substring(8, 10), macAddress.substring(10) }; } else { throw new IllegalArgumentException("Cannot decode MAC address: " + macAddress); } } // Decode each MAC address digit into a hexadecimal byte value for (int i = 0; i < 6; i++) { // Prefix the value with "0x" so that Integer.decode() knows which base to use contents[i] = Integer.decode("0x" + digits[i]).byteValue(); } return contents; } public static String macAddressBytesToString(byte[] macAddress) { if (macAddress.length != 6) { throw new IllegalArgumentException("Cannot decode MAC address: " + macAddress); } return String.format( //"%02X:%02X:%02X:%02X:%02X:%02X", "%02x%02x%02x%02x%02x%02x", macAddress[0], macAddress[1], macAddress[2], macAddress[3], macAddress[4], macAddress[5] ); } public static String normalizeMacAddress(String macAddress) { return macAddressBytesToString(macAddressStringToBytes(macAddress)); } }