aboutsummaryrefslogtreecommitdiffstats
path: root/guava/src/com/google/common/net/InetAddresses.java
diff options
context:
space:
mode:
Diffstat (limited to 'guava/src/com/google/common/net/InetAddresses.java')
-rw-r--r--guava/src/com/google/common/net/InetAddresses.java298
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;
+ }
}