diff options
Diffstat (limited to 'guava/src/com/google/common/net/InetAddresses.java')
-rw-r--r-- | guava/src/com/google/common/net/InetAddresses.java | 298 |
1 files changed, 205 insertions, 93 deletions
diff --git a/guava/src/com/google/common/net/InetAddresses.java b/guava/src/com/google/common/net/InetAddresses.java index 8eddd0d..2cd9472 100644 --- a/guava/src/com/google/common/net/InetAddresses.java +++ b/guava/src/com/google/common/net/InetAddresses.java @@ -17,9 +17,8 @@ package com.google.common.net; import com.google.common.annotations.Beta; -import com.google.common.base.Objects; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import com.google.common.hash.Hashing; import com.google.common.io.ByteStreams; import com.google.common.primitives.Ints; @@ -42,6 +41,12 @@ import javax.annotation.Nullable; * IP address string literals -- there is no blocking DNS penalty for a * malformed string. * + * <p>This class hooks into the {@code sun.net.util.IPAddressUtil} class + * to make use of the {@code textToNumericFormatV4} and + * {@code textToNumericFormatV6} methods directly as a means to avoid + * accidentally traversing all nameservices (it can be vitally important + * to avoid, say, blocking on DNS at times). + * * <p>When dealing with {@link Inet4Address} and {@link Inet6Address} * objects as byte arrays (vis. {@code InetAddress.getAddress()}) they * are 4 and 16 bytes in length, respectively, and represent the address @@ -114,36 +119,68 @@ import javax.annotation.Nullable; public final class InetAddresses { private static final int IPV4_PART_COUNT = 4; private static final int IPV6_PART_COUNT = 8; - private static final Inet4Address LOOPBACK4 = (Inet4Address) forString("127.0.0.1"); - private static final Inet4Address ANY4 = (Inet4Address) forString("0.0.0.0"); + private static final Inet4Address LOOPBACK4 = + (Inet4Address) forString("127.0.0.1"); + private static final Inet4Address ANY4 = + (Inet4Address) forString("0.0.0.0"); private InetAddresses() {} /** - * Returns an {@link Inet4Address}, given a byte array representation of the IPv4 address. + * Returns an {@link Inet4Address}, given a byte array representation + * of the IPv4 address. * - * @param bytes byte array representing an IPv4 address (should be of length 4) - * @return {@link Inet4Address} corresponding to the supplied byte array - * @throws IllegalArgumentException if a valid {@link Inet4Address} can not be created + * @param bytes byte array representing an IPv4 address (should be + * of length 4). + * @return {@link Inet4Address} corresponding to the supplied byte + * array. + * @throws IllegalArgumentException if a valid {@link Inet4Address} + * can not be created. */ private static Inet4Address getInet4Address(byte[] bytes) { Preconditions.checkArgument(bytes.length == 4, "Byte array has invalid length for an IPv4 address: %s != 4.", bytes.length); - // Given a 4-byte array, this cast should always succeed. - return (Inet4Address) bytesToInetAddress(bytes); + try { + InetAddress ipv4 = InetAddress.getByAddress(bytes); + if (!(ipv4 instanceof Inet4Address)) { + throw new UnknownHostException( + String.format("'%s' is not an IPv4 address.", + ipv4.getHostAddress())); + } + + return (Inet4Address) ipv4; + } catch (UnknownHostException e) { + + /* + * This really shouldn't happen in practice since all our byte + * sequences should be valid IP addresses. + * + * However {@link InetAddress#getByAddress} is documented as + * potentially throwing this "if IP address is of illegal length". + * + * This is mapped to IllegalArgumentException since, presumably, + * the argument triggered some bizarre processing bug. + */ + throw new IllegalArgumentException( + String.format("Host address '%s' is not a valid IPv4 address.", + Arrays.toString(bytes)), + e); + } } /** - * Returns the {@link InetAddress} having the given string representation. + * Returns the {@link InetAddress} having the given string + * representation. * * <p>This deliberately avoids all nameservice lookups (e.g. no DNS). * - * @param ipString {@code String} containing an IPv4 or IPv6 string literal, e.g. - * {@code "192.168.0.1"} or {@code "2001:db8::1"} + * @param ipString {@code String} containing an IPv4 or IPv6 string literal, + * e.g. {@code "192.168.0.1"} or {@code "2001:db8::1"} * @return {@link InetAddress} representing the argument - * @throws IllegalArgumentException if the argument is not a valid IP string literal + * @throws IllegalArgumentException if the argument is not a valid + * IP string literal */ public static InetAddress forString(String ipString) { byte[] addr = ipStringToBytes(ipString); @@ -154,7 +191,25 @@ public final class InetAddresses { String.format("'%s' is not an IP string literal.", ipString)); } - return bytesToInetAddress(addr); + try { + return InetAddress.getByAddress(addr); + } catch (UnknownHostException e) { + + /* + * This really shouldn't happen in practice since all our byte + * sequences should be valid IP addresses. + * + * However {@link InetAddress#getByAddress} is documented as + * potentially throwing this "if IP address is of illegal length". + * + * This is mapped to IllegalArgumentException since, presumably, + * the argument triggered some processing bug in either + * {@link IPAddressUtil#textToNumericFormatV4} or + * {@link IPAddressUtil#textToNumericFormatV6}. + */ + throw new IllegalArgumentException( + String.format("'%s' is extremely broken.", ipString), e); + } } /** @@ -316,25 +371,6 @@ public final class InetAddresses { } /** - * Convert a byte array into an InetAddress. - * - * {@link InetAddress#getByAddress} is documented as throwing a checked - * exception "if IP address if of illegal length." We replace it with - * an unchecked exception, for use by callers who already know that addr - * is an array of length 4 or 16. - * - * @param addr the raw 4-byte or 16-byte IP address in big-endian order - * @return an InetAddress object created from the raw IP address - */ - private static InetAddress bytesToInetAddress(byte[] addr) { - try { - return InetAddress.getByAddress(addr); - } catch (UnknownHostException e) { - throw new AssertionError(e); - } - } - - /** * Returns the string representation of an {@link InetAddress}. * * <p>For IPv4 addresses, this is identical to @@ -375,7 +411,7 @@ public final class InetAddresses { * leftmost run wins. If a qualifying run is found, its hextets are replaced * by the sentinel value -1. * - * @param hextets {@code int[]} mutable array of eight 16-bit hextets + * @param hextets {@code int[]} mutable array of eight 16-bit hextets. */ private static void compressLongestRunOfZeroes(int[] hextets) { int bestRunStart = -1; @@ -400,13 +436,13 @@ public final class InetAddresses { } } - /** + /** * Convert a list of hextets into a human-readable IPv6 address. * * <p>In order for "::" compression to work, the input should contain negative * sentinel values in place of the elided zeroes. * - * @param hextets {@code int[]} array of eight 16-bit hextets, or -1s + * @param hextets {@code int[]} array of eight 16-bit hextets, or -1s. */ private static String hextetsToIPv6String(int[] hextets) { /* @@ -483,26 +519,30 @@ public final class InetAddresses { */ public static InetAddress forUriString(String hostAddr) { Preconditions.checkNotNull(hostAddr); + Preconditions.checkArgument(hostAddr.length() > 0, "host string is empty"); + InetAddress retval = null; - // Decide if this should be an IPv6 or IPv4 address. - String ipString; - int expectBytes; - if (hostAddr.startsWith("[") && hostAddr.endsWith("]")) { - ipString = hostAddr.substring(1, hostAddr.length() - 1); - expectBytes = 16; - } else { - ipString = hostAddr; - expectBytes = 4; + // IPv4 address? + try { + retval = forString(hostAddr); + if (retval instanceof Inet4Address) { + return retval; + } + } catch (IllegalArgumentException e) { + // Not a valid IP address, fall through. } - // Parse the address, and make sure the length/version is correct. - byte[] addr = ipStringToBytes(ipString); - if (addr == null || addr.length != expectBytes) { - throw new IllegalArgumentException( - String.format("Not a valid URI IP literal: '%s'", hostAddr)); + // IPv6 address + if (!(hostAddr.startsWith("[") && hostAddr.endsWith("]"))) { + throw new IllegalArgumentException("Not a valid address: \"" + hostAddr + '"'); + } + + retval = forString(hostAddr.substring(1, hostAddr.length() - 1)); + if (retval instanceof Inet6Address) { + return retval; } - return bytesToInetAddress(addr); + throw new IllegalArgumentException("Not a valid address: \"" + hostAddr + '"'); } /** @@ -542,7 +582,8 @@ public final class InetAddresses { * proper IPv6 addresses (which they are), NOT IPv4 compatible * addresses (which they are generally NOT considered to be). * - * @param ip {@link Inet6Address} to be examined for embedded IPv4 compatible address format + * @param ip {@link Inet6Address} to be examined for embedded IPv4 + * compatible address format * @return {@code true} if the argument is a valid "compat" address */ public static boolean isCompatIPv4Address(Inet6Address ip) { @@ -552,7 +593,7 @@ public final class InetAddresses { byte[] bytes = ip.getAddress(); if ((bytes[12] == 0) && (bytes[13] == 0) && (bytes[14] == 0) - && ((bytes[15] == 0) || (bytes[15] == 1))) { + && ((bytes[15] == 0) || (bytes[15] == 1))) { return false; } @@ -562,15 +603,17 @@ public final class InetAddresses { /** * Returns the IPv4 address embedded in an IPv4 compatible address. * - * @param ip {@link Inet6Address} to be examined for an embedded IPv4 address + * @param ip {@link Inet6Address} to be examined for an embedded + * IPv4 address * @return {@link Inet4Address} of the embedded IPv4 address - * @throws IllegalArgumentException if the argument is not a valid IPv4 compatible address + * @throws IllegalArgumentException if the argument is not a valid + * IPv4 compatible address */ public static Inet4Address getCompatIPv4Address(Inet6Address ip) { Preconditions.checkArgument(isCompatIPv4Address(ip), "Address '%s' is not IPv4-compatible.", toAddrString(ip)); - return getInet4Address(Arrays.copyOfRange(ip.getAddress(), 12, 16)); + return getInet4Address(copyOfRange(ip.getAddress(), 12, 16)); } /** @@ -584,7 +627,8 @@ public final class InetAddresses { * <a target="_parent" href="http://tools.ietf.org/html/rfc3056#section-2" * >http://tools.ietf.org/html/rfc3056</a> * - * @param ip {@link Inet6Address} to be examined for 6to4 address format + * @param ip {@link Inet6Address} to be examined for 6to4 address + * format * @return {@code true} if the argument is a 6to4 address */ public static boolean is6to4Address(Inet6Address ip) { @@ -595,19 +639,21 @@ public final class InetAddresses { /** * Returns the IPv4 address embedded in a 6to4 address. * - * @param ip {@link Inet6Address} to be examined for embedded IPv4 in 6to4 address - * @return {@link Inet4Address} of embedded IPv4 in 6to4 address - * @throws IllegalArgumentException if the argument is not a valid IPv6 6to4 address + * @param ip {@link Inet6Address} to be examined for embedded IPv4 + * in 6to4 address. + * @return {@link Inet4Address} of embedded IPv4 in 6to4 address. + * @throws IllegalArgumentException if the argument is not a valid + * IPv6 6to4 address. */ public static Inet4Address get6to4IPv4Address(Inet6Address ip) { Preconditions.checkArgument(is6to4Address(ip), "Address '%s' is not a 6to4 address.", toAddrString(ip)); - return getInet4Address(Arrays.copyOfRange(ip.getAddress(), 2, 6)); + return getInet4Address(copyOfRange(ip.getAddress(), 2, 6)); } /** - * A simple immutable data class to encapsulate the information to be found in a + * A simple data class to encapsulate the information to be found in a * Teredo address. * * <p>All of the fields in this class are encoded in various portions @@ -635,19 +681,31 @@ public final class InetAddresses { * <p>Both server and client can be {@code null}, in which case the * value {@code "0.0.0.0"} will be assumed. * - * @throws IllegalArgumentException if either of the {@code port} or the {@code flags} - * arguments are out of range of an unsigned short + * @throws IllegalArgumentException if either of the {@code port} + * or the {@code flags} arguments are out of range of an + * unsigned short */ // TODO: why is this public? - public TeredoInfo( - @Nullable Inet4Address server, @Nullable Inet4Address client, int port, int flags) { + public TeredoInfo(@Nullable Inet4Address server, + @Nullable Inet4Address client, + int port, int flags) { Preconditions.checkArgument((port >= 0) && (port <= 0xffff), "port '%s' is out of range (0 <= port <= 0xffff)", port); Preconditions.checkArgument((flags >= 0) && (flags <= 0xffff), "flags '%s' is out of range (0 <= flags <= 0xffff)", flags); - - this.server = Objects.firstNonNull(server, ANY4); - this.client = Objects.firstNonNull(client, ANY4); + + if (server != null) { + this.server = server; + } else { + this.server = ANY4; + } + + if (client != null) { + this.client = client; + } else { + this.client = ANY4; + } + this.port = port; this.flags = flags; } @@ -674,7 +732,8 @@ public final class InetAddresses { * * <p>Teredo addresses begin with the {@code "2001::/32"} prefix. * - * @param ip {@link Inet6Address} to be examined for Teredo address format + * @param ip {@link Inet6Address} to be examined for Teredo address + * format. * @return {@code true} if the argument is a Teredo address */ public static boolean isTeredoAddress(Inet6Address ip) { @@ -686,23 +745,25 @@ public final class InetAddresses { /** * Returns the Teredo information embedded in a Teredo address. * - * @param ip {@link Inet6Address} to be examined for embedded Teredo information + * @param ip {@link Inet6Address} to be examined for embedded Teredo + * information * @return extracted {@code TeredoInfo} - * @throws IllegalArgumentException if the argument is not a valid IPv6 Teredo address + * @throws IllegalArgumentException if the argument is not a valid + * IPv6 Teredo address */ public static TeredoInfo getTeredoInfo(Inet6Address ip) { Preconditions.checkArgument(isTeredoAddress(ip), "Address '%s' is not a Teredo address.", toAddrString(ip)); byte[] bytes = ip.getAddress(); - Inet4Address server = getInet4Address(Arrays.copyOfRange(bytes, 4, 8)); + Inet4Address server = getInet4Address(copyOfRange(bytes, 4, 8)); int flags = ByteStreams.newDataInput(bytes, 8).readShort() & 0xffff; // Teredo obfuscates the mapped client port, per section 4 of the RFC. int port = ~ByteStreams.newDataInput(bytes, 10).readShort() & 0xffff; - byte[] clientBytes = Arrays.copyOfRange(bytes, 12, 16); + byte[] clientBytes = copyOfRange(bytes, 12, 16); for (int i = 0; i < clientBytes.length; i++) { // Teredo obfuscates the mapped client IP, per section 4 of the RFC. clientBytes[i] = (byte) ~clientBytes[i]; @@ -724,7 +785,8 @@ public final class InetAddresses { * <a target="_parent" href="http://tools.ietf.org/html/rfc5214#section-6.1" * >http://tools.ietf.org/html/rfc5214</a> * - * @param ip {@link Inet6Address} to be examined for ISATAP address format + * @param ip {@link Inet6Address} to be examined for ISATAP address + * format. * @return {@code true} if the argument is an ISATAP address */ public static boolean isIsatapAddress(Inet6Address ip) { @@ -751,15 +813,17 @@ public final class InetAddresses { /** * Returns the IPv4 address embedded in an ISATAP address. * - * @param ip {@link Inet6Address} to be examined for embedded IPv4 in ISATAP address + * @param ip {@link Inet6Address} to be examined for embedded IPv4 + * in ISATAP address * @return {@link Inet4Address} of embedded IPv4 in an ISATAP address - * @throws IllegalArgumentException if the argument is not a valid IPv6 ISATAP address + * @throws IllegalArgumentException if the argument is not a valid + * IPv6 ISATAP address */ public static Inet4Address getIsatapIPv4Address(Inet6Address ip) { Preconditions.checkArgument(isIsatapAddress(ip), "Address '%s' is not an ISATAP address.", toAddrString(ip)); - return getInet4Address(Arrays.copyOfRange(ip.getAddress(), 12, 16)); + return getInet4Address(copyOfRange(ip.getAddress(), 12, 16)); } /** @@ -770,12 +834,14 @@ public final class InetAddresses { * due to their trivial spoofability. With other transition addresses * spoofing involves (at least) infection of one's BGP routing table. * - * @param ip {@link Inet6Address} to be examined for embedded IPv4 client address - * @return {@code true} if there is an embedded IPv4 client address + * @param ip {@link Inet6Address} to be examined for embedded IPv4 + * client address. + * @return {@code true} if there is an embedded IPv4 client address. * @since 7.0 */ public static boolean hasEmbeddedIPv4ClientAddress(Inet6Address ip) { - return isCompatIPv4Address(ip) || is6to4Address(ip) || isTeredoAddress(ip); + return isCompatIPv4Address(ip) || is6to4Address(ip) || + isTeredoAddress(ip); } /** @@ -787,9 +853,11 @@ public final class InetAddresses { * due to their trivial spoofability. With other transition addresses * spoofing involves (at least) infection of one's BGP routing table. * - * @param ip {@link Inet6Address} to be examined for embedded IPv4 client address - * @return {@link Inet4Address} of embedded IPv4 client address - * @throws IllegalArgumentException if the argument does not have a valid embedded IPv4 address + * @param ip {@link Inet6Address} to be examined for embedded IPv4 + * client address. + * @return {@link Inet4Address} of embedded IPv4 client address. + * @throws IllegalArgumentException if the argument does not have a valid + * embedded IPv4 address. */ public static Inet4Address getEmbeddedIPv4ClientAddress(Inet6Address ip) { if (isCompatIPv4Address(ip)) { @@ -805,7 +873,8 @@ public final class InetAddresses { } throw new IllegalArgumentException( - String.format("'%s' has no embedded IPv4 address.", toAddrString(ip))); + String.format("'%s' has no embedded IPv4 address.", + toAddrString(ip))); } /** @@ -826,7 +895,8 @@ public final class InetAddresses { * {@link Inet6Address} methods, but it would be unwise to depend on such * a poorly-documented feature.) * - * @param ipString {@code String} to be examined for embedded IPv4-mapped IPv6 address format + * @param ipString {@code String} to be examined for embedded IPv4-mapped + * IPv6 address format * @return {@code true} if the argument is a valid "mapped" address * @since 10.0 */ @@ -899,7 +969,7 @@ public final class InetAddresses { } // Many strategies for hashing are possible. This might suffice for now. - int coercedHash = Hashing.murmur3_32().hashLong(addressAsLong).asInt(); + int coercedHash = hash64To32(addressAsLong); // Squash into 224/4 Multicast and 240/4 Reserved space (i.e. 224/3). coercedHash |= 0xe0000000; @@ -914,6 +984,27 @@ public final class InetAddresses { } /** + * Returns an {@code int} hash of a 64-bit long. + * + * This comes from http://www.concentric.net/~ttwang/tech/inthash.htm + * + * This hash gives no guarantees on the cryptographic suitability nor the + * quality of randomness produced, and the mapping may change in the future. + * + * @param key A 64-bit number to hash + * @return {@code int} the input hashed into 32 bits + */ + @VisibleForTesting static int hash64To32(long key) { + key = (~key) + (key << 18); + key = key ^ (key >>> 31); + key = key * 21; + key = key ^ (key >>> 11); + key = key + (key << 6); + key = key ^ (key >>> 22); + return (int) key; + } + + /** * Returns an integer representing an IPv4 address regardless of * whether the supplied argument is an IPv4 address or not. * @@ -960,7 +1051,8 @@ public final class InetAddresses { * @return an InetAddress object created from the raw IP address * @throws UnknownHostException if IP address is of illegal length */ - public static InetAddress fromLittleEndianByteArray(byte[] addr) throws UnknownHostException { + public static InetAddress fromLittleEndianByteArray(byte[] addr) + throws UnknownHostException { byte[] reversed = new byte[addr.length]; for (int i = 0; i < addr.length; i++) { reversed[i] = addr[addr.length - i - 1]; @@ -973,8 +1065,9 @@ public final class InetAddresses { * This method works for both IPv4 and IPv6 addresses. * * @param address the InetAddress to increment - * @return a new InetAddress that is one more than the passed in address - * @throws IllegalArgumentException if InetAddress is at the end of its range + * @return a new InetAddress that is one more than the passed in address. + * @throws IllegalArgumentException if InetAddress is at the end of its + * range. * @since 10.0 */ public static InetAddress increment(InetAddress address) { @@ -988,7 +1081,11 @@ public final class InetAddresses { Preconditions.checkArgument(i >= 0, "Incrementing %s would wrap.", address); addr[i]++; - return bytesToInetAddress(addr); + try { + return InetAddress.getByAddress(addr); + } catch (UnknownHostException e) { + throw new AssertionError(e); + } } /** @@ -996,7 +1093,7 @@ public final class InetAddresses { * ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff for IPv6. * * @return true if the InetAddress is either 255.255.255.255 for IPv4 or - * ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff for IPv6 + * ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff for IPv6. * @since 10.0 */ public static boolean isMaximum(InetAddress address) { @@ -1008,4 +1105,19 @@ public final class InetAddresses { } return true; } + + /** + * This method emulates the Java 6 method + * {@code Arrays.copyOfRange(byte, int, int)}, which is not available in + * Java 5, and thus cannot be used in Guava code. + */ + private static byte[] copyOfRange(byte[] original, int from, int to) { + Preconditions.checkNotNull(original); + + int end = Math.min(to, original.length); + byte[] result = new byte[to - from]; + + System.arraycopy(original, from, result, 0, end - from); + return result; + } } |