diff --git a/common/src/main/java/io/netty/util/NetUtil.java b/common/src/main/java/io/netty/util/NetUtil.java
index 307c5a3196..3a1f1057e0 100644
--- a/common/src/main/java/io/netty/util/NetUtil.java
+++ b/common/src/main/java/io/netty/util/NetUtil.java
@@ -27,6 +27,7 @@ import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
@@ -68,6 +69,51 @@ public final class NetUtil {
*/
public static final int SOMAXCONN;
+ /**
+ * This defines how many words (represented as ints) are needed to represent an IPv6 address
+ */
+ private static final int IPV6_WORD_COUNT = 8;
+
+ /**
+ * The maximum number of characters for an IPV6 string with no scope
+ */
+ private static final int IPV6_MAX_CHAR_COUNT = 39;
+
+ /**
+ * Number of bytes needed to represent and IPV6 value
+ */
+ private static final int IPV6_BYTE_COUNT = 16;
+
+ /**
+ * Maximum amount of value adding characters in between IPV6 separators
+ */
+ private static final int IPV6_MAX_CHAR_BETWEEN_SEPARATOR = 4;
+
+ /**
+ * Minimum number of separators that must be present in an IPv6 string
+ */
+ private static final int IPV6_MIN_SEPARATORS = 2;
+
+ /**
+ * Maximum number of separators that must be present in an IPv6 string
+ */
+ private static final int IPV6_MAX_SEPARATORS = 8;
+
+ /**
+ * Number of bytes needed to represent and IPV4 value
+ */
+ private static final int IPV4_BYTE_COUNT = 4;
+
+ /**
+ * Maximum amount of value adding characters in between IPV4 separators
+ */
+ private static final int IPV4_MAX_CHAR_BETWEEN_SEPARATOR = 3;
+
+ /**
+ * Number of separators that must be present in an IPv4 string
+ */
+ private static final int IPV4_SEPARATORS = 3;
+
/**
* The logger being used by this class
*/
@@ -223,8 +269,8 @@ public final class NetUtil {
StringTokenizer tokenizer = new StringTokenizer(ipAddressString, ".");
String token;
int tempInt;
- byte[] byteAddress = new byte[4];
- for (int i = 0; i < 4; i ++) {
+ byte[] byteAddress = new byte[IPV4_BYTE_COUNT];
+ for (int i = 0; i < IPV4_BYTE_COUNT; i ++) {
token = tokenizer.nextToken();
tempInt = Integer.parseInt(token);
byteAddress[i] = (byte) tempInt;
@@ -294,11 +340,11 @@ public final class NetUtil {
}
}
- byte[] ipByteArray = new byte[16];
+ byte[] ipByteArray = new byte[IPV6_BYTE_COUNT];
// Finally convert these strings to bytes...
for (int i = 0; i < hexStrings.size(); i ++) {
- convertToBytes(hexStrings.get(i), ipByteArray, i * 2);
+ convertToBytes(hexStrings.get(i), ipByteArray, i << 1);
}
// Now if there are any decimal values, we know where they go...
@@ -553,10 +599,14 @@ public final class NetUtil {
return Integer.parseInt(word) <= 255;
}
- static boolean isValidHexChar(char c) {
+ private static boolean isValidHexChar(char c) {
return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f';
}
+ private static boolean isValidNumericChar(char c) {
+ return c >= '0' && c <= '9';
+ }
+
/**
* Takes a string and parses it to see if it is a valid IPV4 address.
*
@@ -605,6 +655,368 @@ public final class NetUtil {
return periods == 3;
}
+ /**
+ * Returns the {@link Inet6Address} representation of a {@link CharSequence} IP address.
+ *
+ * This method will treat all IPv4 type addresses as "IPv4 mapped" (see {@link #getByName(CharSequence, boolean)})
+ * @param ip {@link CharSequence} IP address to be converted to a {@link Inet6Address}
+ * @return {@link Inet6Address} representation of the {@code ip} or {@code null} if not a valid IP address.
+ */
+ public static Inet6Address getByName(CharSequence ip) {
+ return getByName(ip, true);
+ }
+
+ /**
+ * Returns the {@link Inet6Address} representation of a {@link CharSequence} IP address.
+ *
+ * The {@code ipv4Mapped} parameter specifies how IPv4 addresses should be treated.
+ * "IPv4 mapped" format as
+ * defined in rfc 4291 section 2 is supported.
+ * @param ip {@link CharSequence} IP address to be converted to a {@link Inet6Address}
+ * @param ipv4Mapped
+ *
+ * - {@code true} To allow IPv4 mapped inputs to be translated into {@link Inet6Address}
+ * - {@code false} Don't turn IPv4 addressed to mapped addresses
+ *
+ * @return {@link Inet6Address} representation of the {@code ip} or {@code null} if not a valid IP address.
+ */
+ public static Inet6Address getByName(CharSequence ip, boolean ipv4Mapped) {
+ final byte[] bytes = new byte[IPV6_BYTE_COUNT];
+ final int ipLength = ip.length();
+ int compressBegin = 0;
+ int compressLength = 0;
+ int currentIndex = 0;
+ int value = 0;
+ int begin = -1;
+ int i = 0;
+ int ipv6Seperators = 0;
+ int ipv4Seperators = 0;
+ int tmp = 0;
+ boolean needsShift = false;
+ for (; i < ipLength; ++i) {
+ final char c = ip.charAt(i);
+ switch (c) {
+ case ':':
+ ++ipv6Seperators;
+ if (i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR ||
+ ipv4Seperators > 0 || ipv6Seperators > IPV6_MAX_SEPARATORS ||
+ currentIndex + 1 >= bytes.length) {
+ return null;
+ }
+ value <<= (IPV6_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
+
+ if (compressLength > 0) {
+ compressLength -= 2;
+ }
+
+ // The value integer holds at most 4 bytes from right (most significant) to left (least significant).
+ // The following bit shifting is used to extract and re-order the individual bytes to achieve a
+ // left (most significant) to right (least significant) ordering.
+ bytes[currentIndex++] = (byte) (((value & 0xf) << 4) | ((value >> 4) & 0xf));
+ bytes[currentIndex++] = (byte) ((((value >> 8) & 0xf) << 4) | ((value >> 12) & 0xf));
+ tmp = i + 1;
+ if (tmp < ipLength && ip.charAt(tmp) == ':') {
+ ++tmp;
+ if (compressBegin != 0 || (tmp < ipLength && ip.charAt(tmp) == ':')) {
+ return null;
+ }
+ ++ipv6Seperators;
+ needsShift = ipv6Seperators == 2 && value == 0;
+ compressBegin = currentIndex;
+ compressLength = bytes.length - compressBegin - 2;
+ ++i;
+ }
+ value = 0;
+ begin = -1;
+ break;
+ case '.':
+ ++ipv4Seperators;
+ if (i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR
+ || ipv4Seperators > IPV4_SEPARATORS
+ || (ipv6Seperators > 0 && (currentIndex + compressLength < 12))
+ || i + 1 >= ipLength
+ || currentIndex >= bytes.length
+ || begin < 0
+ || (begin == 0 && (i == 3 && (!isValidNumericChar(ip.charAt(2)) ||
+ !isValidNumericChar(ip.charAt(1)) ||
+ !isValidNumericChar(ip.charAt(0))) ||
+ i == 2 && (!isValidNumericChar(ip.charAt(1)) ||
+ !isValidNumericChar(ip.charAt(0))) ||
+ i == 1 && !isValidNumericChar(ip.charAt(0))))) {
+ return null;
+ }
+ value <<= (IPV4_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
+
+ // The value integer holds at most 3 bytes from right (most significant) to left (least significant).
+ // The following bit shifting is to restructure the bytes to be left (most significant) to
+ // right (least significant) while also accounting for each IPv4 digit is base 10.
+ begin = (value & 0xf) * 100 + ((value >> 4) & 0xf) * 10 + ((value >> 8) & 0xf);
+ if (begin < 0 || begin > 255) {
+ return null;
+ }
+ bytes[currentIndex++] = (byte) begin;
+ value = 0;
+ begin = -1;
+ break;
+ default:
+ if (!isValidHexChar(c) || (ipv4Seperators > 0 && !isValidNumericChar(c))) {
+ return null;
+ }
+ if (begin < 0) {
+ begin = i;
+ } else if (i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR) {
+ return null;
+ }
+ // The value is treated as a sort of array of numbers because we are dealing with
+ // at most 4 consecutive bytes we can use bit shifting to accomplish this.
+ // The most significant byte will be encountered first, and reside in the right most
+ // position of the following integer
+ value += getIntValue(c) << ((i - begin) << 2);
+ break;
+ }
+ }
+
+ final boolean isCompressed = compressBegin > 0;
+ // Finish up last set of data that was accumulated in the loop (or before the loop)
+ if (ipv4Seperators > 0) {
+ if (begin > 0 && i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR ||
+ ipv4Seperators != IPV4_SEPARATORS ||
+ currentIndex >= bytes.length) {
+ return null;
+ }
+ if (ipv6Seperators == 0) {
+ compressLength = 12;
+ } else if (ipv6Seperators >= IPV6_MIN_SEPARATORS &&
+ ip.charAt(ipLength - 1) != ':' &&
+ (!isCompressed && (ipv6Seperators == 6 && ip.charAt(0) != ':') ||
+ isCompressed && (ipv6Seperators + 1 < IPV6_MAX_SEPARATORS &&
+ (ip.charAt(0) != ':' || compressBegin <= 2)))) {
+ compressLength -= 2;
+ } else {
+ return null;
+ }
+ value <<= (IPV4_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
+
+ // The value integer holds at most 3 bytes from right (most significant) to left (least significant).
+ // The following bit shifting is to restructure the bytes to be left (most significant) to
+ // right (least significant) while also accounting for each IPv4 digit is base 10.
+ begin = (value & 0xf) * 100 + ((value >> 4) & 0xf) * 10 + ((value >> 8) & 0xf);
+ if (begin < 0 || begin > 255) {
+ return null;
+ }
+ bytes[currentIndex++] = (byte) begin;
+ } else {
+ tmp = ipLength - 1;
+ if (begin > 0 && i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR ||
+ ipv6Seperators < IPV6_MIN_SEPARATORS ||
+ !isCompressed && (ipv6Seperators + 1 != IPV6_MAX_SEPARATORS ||
+ ip.charAt(0) == ':' || ip.charAt(tmp) == ':') ||
+ isCompressed && (ipv6Seperators > IPV6_MAX_SEPARATORS ||
+ (ipv6Seperators == IPV6_MAX_SEPARATORS &&
+ (compressBegin <= 2 && ip.charAt(0) != ':' ||
+ compressBegin >= 14 && ip.charAt(tmp) != ':'))) ||
+ currentIndex + 1 >= bytes.length) {
+ return null;
+ }
+ if (begin >= 0 && i - begin <= IPV6_MAX_CHAR_BETWEEN_SEPARATOR) {
+ value <<= (IPV6_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
+ }
+ // The value integer holds at most 4 bytes from right (most significant) to left (least significant).
+ // The following bit shifting is used to extract and re-order the individual bytes to achieve a
+ // left (most significant) to right (least significant) ordering.
+ bytes[currentIndex++] = (byte) (((value & 0xf) << 4) | ((value >> 4) & 0xf));
+ bytes[currentIndex++] = (byte) ((((value >> 8) & 0xf) << 4) | ((value >> 12) & 0xf));
+ }
+
+ i = currentIndex + compressLength;
+ if (needsShift || i >= bytes.length) {
+ // Right shift array
+ if (i >= bytes.length) {
+ ++compressBegin;
+ }
+ for (i = currentIndex; i < bytes.length; ++i) {
+ for (begin = bytes.length - 1; begin >= compressBegin; --begin) {
+ bytes[begin] = bytes[begin - 1];
+ }
+ bytes[begin] = 0;
+ ++compressBegin;
+ }
+ } else {
+ // Selectively move elements
+ for (i = 0; i < compressLength; ++i) {
+ begin = i + compressBegin;
+ currentIndex = begin + compressLength;
+ if (currentIndex < bytes.length) {
+ bytes[currentIndex] = bytes[begin];
+ bytes[begin] = 0;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (ipv4Mapped && ipv4Seperators > 0 &&
+ bytes[0] == 0 && bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 0 && bytes[4] == 0 &&
+ bytes[5] == 0 && bytes[6] == 0 && bytes[7] == 0 && bytes[8] == 0 && bytes[9] == 0) {
+ bytes[10] = bytes[11] = (byte) 0xff;
+ }
+
+ try {
+ return Inet6Address.getByAddress(null, bytes, -1);
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e); // Should never happen
+ }
+ }
+
+ /**
+ * Returns the {@link String} representation of an {@link InetAddress}.
+ *
+ * - Inet4Address results are identical to {@link InetAddress#getHostAddress()}
+ * - Inet6Address results adhere to
+ * rfc 5952 section 4
+ *
+ *
+ * The output does not include Scope ID.
+ * @param ip {@link InetAddress} to be converted to an address string
+ * @return {@code String} containing the text-formatted IP address
+ */
+ public static String toAddressString(InetAddress ip) {
+ return toAddressString(ip, false);
+ }
+
+ /**
+ * Returns the {@link String} representation of an {@link InetAddress}.
+ *
+ * - Inet4Address results are identical to {@link InetAddress#getHostAddress()}
+ * - Inet6Address results adhere to
+ * rfc 5952 section 4 if
+ * {@code ipv4Mapped} is false. If {@code ipv4Mapped} is true then "IPv4 mapped" format
+ * from rfc 4291 section 2 will be supported.
+ * The compressed result will always obey the compression rules defined in
+ * rfc 5952 section 4
+ *
+ *
+ * The output does not include Scope ID.
+ * @param ip {@link InetAddress} to be converted to an address string
+ * @param ipv4Mapped
+ *
+ * - {@code true} to stray from strict rfc 5952 and support the "IPv4 mapped" format
+ * defined in rfc 4291 section 2 while still
+ * following the updated guidelines in
+ * rfc 5952 section 4
+ * - {@code false} to strictly follow rfc 5952
+ *
+ * @return {@code String} containing the text-formatted IP address
+ */
+ public static String toAddressString(InetAddress ip, boolean ipv4Mapped) {
+ if (ip instanceof Inet4Address) {
+ return ip.getHostAddress();
+ }
+ if (!(ip instanceof Inet6Address)) {
+ throw new IllegalArgumentException("Unhandled type: " + ip.getClass());
+ }
+
+ final byte[] bytes = ip.getAddress();
+ final int[] words = new int[IPV6_WORD_COUNT];
+ int i;
+ for (i = 0; i < words.length; ++i) {
+ words[i] = ((bytes[i << 1] & 0xff) << 8) | (bytes[(i << 1) + 1] & 0xff);
+ }
+
+ // Find longest run of 0s, tie goes to first found instance
+ int currentStart = -1;
+ int currentLength = 0;
+ int shortestStart = -1;
+ int shortestLength = 0;
+ for (i = 0; i < words.length; ++i) {
+ if (words[i] == 0) {
+ if (currentStart < 0) {
+ currentStart = i;
+ }
+ } else if (currentStart >= 0) {
+ currentLength = i - currentStart;
+ if (currentLength > shortestLength) {
+ shortestStart = currentStart;
+ shortestLength = currentLength;
+ }
+ currentStart = -1;
+ }
+ }
+ // If the array ends on a streak of zeros, make sure we account for it
+ if (currentStart >= 0) {
+ currentLength = i - currentStart;
+ if (currentLength > shortestLength) {
+ shortestStart = currentStart;
+ shortestLength = currentLength;
+ }
+ }
+ // Ignore the longest streak if it is only 1 long
+ if (shortestLength == 1) {
+ shortestLength = 0;
+ shortestStart = -1;
+ }
+
+ // Translate to string taking into account longest consecutive 0s
+ final int shortestEnd = shortestStart + shortestLength;
+ final StringBuilder b = new StringBuilder(IPV6_MAX_CHAR_COUNT);
+ if (shortestEnd < 0) { // Optimization when there is no compressing needed
+ b.append(Integer.toHexString(words[0]));
+ for (i = 1; i < words.length; ++i) {
+ b.append(':');
+ b.append(Integer.toHexString(words[i]));
+ }
+ } else { // General case that can handle compressing (and not compressing)
+ // Loop unroll the first index (so we don't constantly check i==0 cases in loop)
+ final boolean isIpv4Mapped;
+ if (inRangeEndExclusive(0, shortestStart, shortestEnd)) {
+ b.append("::");
+ isIpv4Mapped = ipv4Mapped && (shortestEnd == 5 && words[5] == 0xffff);
+ } else {
+ b.append(Integer.toHexString(words[0]));
+ isIpv4Mapped = false;
+ }
+ for (i = 1; i < words.length; ++i) {
+ if (!inRangeEndExclusive(i, shortestStart, shortestEnd)) {
+ if (!inRangeEndExclusive(i - 1, shortestStart, shortestEnd)) {
+ // If the last index was not part of the shortened sequence
+ if (!isIpv4Mapped || i == 6) {
+ b.append(':');
+ } else {
+ b.append('.');
+ }
+ }
+ if (isIpv4Mapped && i > 5) {
+ b.append(words[i] >> 8);
+ b.append('.');
+ b.append(words[i] & 0xff);
+ } else {
+ b.append(Integer.toHexString(words[i]));
+ }
+ } else if (!inRangeEndExclusive(i - 1, shortestStart, shortestEnd)) {
+ // If we are in the shortened sequence and the last index was not
+ b.append("::");
+ }
+ }
+ }
+
+ return b.toString();
+ }
+
+ /**
+ * Does a range check on {@code value} if is within {@code start} (inclusive) and {@code end} (exclusive).
+ * @param value The value to checked if is within {@code start} (inclusive) and {@code end} (exclusive)
+ * @param start The start of the range (inclusive)
+ * @param end The end of the range (exclusive)
+ * @return
+ *
+ * - {@code true} if {@code value} if is within {@code start} (inclusive) and {@code end} (exclusive)
+ * - {@code false} otherwise
+ *
+ */
+ private static boolean inRangeEndExclusive(int value, int start, int end) {
+ return value >= start && value < end;
+ }
+
/**
* A constructor to stop this class being constructed.
*/
diff --git a/common/src/test/java/io/netty/util/NetUtilTest.java b/common/src/test/java/io/netty/util/NetUtilTest.java
index fa36514a8c..1d9867bf0d 100644
--- a/common/src/test/java/io/netty/util/NetUtilTest.java
+++ b/common/src/test/java/io/netty/util/NetUtilTest.java
@@ -15,13 +15,20 @@
*/
package io.netty.util;
-import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
-import static org.junit.Assert.*;
+import org.junit.Test;
public class NetUtilTest {
private static final Map validIpV4Hosts = new HashMap() {
@@ -50,6 +57,18 @@ public class NetUtilTest {
put("1.256.3.4", null);
put("256.0.0.1", null);
put("1.1.1.1.1", null);
+ put("x.255.255.255", null);
+ put("0.1:0.0", null);
+ put("0.1.0.0:", null);
+ put("127.0.0.", null);
+ put("1.2..4", null);
+ put("192.0.1", null);
+ put("192.0.1.1.1", null);
+ put("192.0.1.a", null);
+ put("19a.0.1.1", null);
+ put("a.0.1.1", null);
+ put(".0.1.1", null);
+ put("...", null);
}
};
private static final Map validIpV6Hosts = new HashMap() {
@@ -177,6 +196,16 @@ public class NetUtilTest {
put("0:1:2:3:4:5:6:x", null);
// Test method with preferred style, adjacent :
put("0:1:2:3:4:5:6::7", null);
+ // Too many : separators trailing
+ put("0:1:2:3:4:5:6:7::", null);
+ // Too many : separators leading
+ put("::0:1:2:3:4:5:6:7", null);
+ // Too many : separators trailing
+ put("1:2:3:4:5:6:7:", null);
+ // Too many : separators leading
+ put(":1:2:3:4:5:6:7", null);
+ // Too many : separators leading 0
+ put("0::1:2:3:4:5:6:7", null);
// Test method with preferred style, too many digits.
put("0:1:2:3:4:5:6:789abcdef", null);
// Test method with compressed style, bad digits.
@@ -207,6 +236,14 @@ public class NetUtilTest {
put("0:0:0:0:0:0:10.0.1", null);
// Test method with ipv4 style, adjacent .
put("0:0:0:0:0:0:10..0.0.1", null);
+ // Test method with ipv4 style, leading .
+ put("0:0:0:0:0:0:.0.0.1", null);
+ // Test method with ipv4 style, leading .
+ put("0:0:0:0:0:0:.10.0.0.1", null);
+ // Test method with ipv4 style, trailing .
+ put("0:0:0:0:0:0:10.0.0.", null);
+ // Test method with ipv4 style, trailing .
+ put("0:0:0:0:0:0:10.0.0.1.", null);
// Test method with compressed ipv4 style, bad ipv6 digits.
put("::fffx:192.168.0.1", null);
// Test method with compressed ipv4 style, bad ipv4 digits.
@@ -239,12 +276,218 @@ public class NetUtilTest {
put("0:0:0:0:0:0:0:10.0.0.1", null);
// Test method, not enough :
put("0:0:0:0:0:10.0.0.1", null);
+ // Test method, out of order trailing :
+ put("0:0:0:0:0:10.0.0.1:", null);
+ // Test method, out of order leading :
+ put(":0:0:0:0:0:10.0.0.1", null);
+ // Test method, out of order leading :
+ put("0:0:0:0::10.0.0.1:", null);
+ // Test method, out of order trailing :
+ put(":0:0:0:0::10.0.0.1", null);
// Test method, too many .
put("0:0:0:0:0:0:10.0.0.0.1", null);
// Test method, not enough .
put("0:0:0:0:0:0:10.0.1", null);
// Test method, adjacent .
put("0:0:0:0:0:0:10.0.0..1", null);
+ // Double compression symbol
+ put("::0::", null);
+ // Empty contents
+ put("", null);
+ // Trailing : (max number of : = 8)
+ put("2001:0:4136:e378:8000:63bf:3fff:fdd2:", null);
+ // Leading : (max number of : = 8)
+ put(":aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", null);
+ // Invalid character
+ put("1234:2345:3456:4567:5678:6789::X890", null);
+ // Trailing . in IPv4
+ put("::ffff:255.255.255.255.", null);
+ // To many characters in IPv4
+ put("::ffff:0.0.1111.0", null);
+ // Test method, adjacent .
+ put("::ffff:0.0..0", null);
+ // Not enough IPv4 entries trailing .
+ put("::ffff:127.0.0.", null);
+ // Not enough IPv4 entries no trailing .
+ put("::ffff:1.2.4", null);
+ // Extra IPv4 entry
+ put("::ffff:192.168.0.1.255", null);
+ // Not enough IPv6 content
+ put(":ffff:192.168.0.1.255", null);
+ // Intermixed IPv4 and IPv6 symbols
+ put("::ffff:255.255:255.255.", null);
+ }
+ };
+ private static final Map ipv6ToAddressStrings = new HashMap() {
+ private static final long serialVersionUID = 2999763170377573184L;
+ {
+ // From the RFC 5952 http://tools.ietf.org/html/rfc5952#section-4
+ put(new byte[]{
+ 32, 1, 13, -72,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 1},
+ "2001:db8::1");
+ put(new byte[]{
+ 32, 1, 13, -72,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 2, 0, 1},
+ "2001:db8::2:1");
+ put(new byte[]{
+ 32, 1, 13, -72,
+ 0, 0, 0, 1,
+ 0, 1, 0, 1,
+ 0, 1, 0, 1},
+ "2001:db8:0:1:1:1:1:1");
+
+ // Other examples
+ put(new byte[]{
+ 32, 1, 13, -72,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 2, 0, 1},
+ "2001:db8::2:1");
+ put(new byte[]{
+ 32, 1, 0, 0,
+ 0, 0, 0, 1,
+ 0, 0, 0, 0,
+ 0, 0, 0, 1},
+ "2001:0:0:1::1");
+ put(new byte[]{
+ 32, 1, 13, -72,
+ 0, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 1},
+ "2001:db8::1:0:0:1");
+ put(new byte[]{
+ 32, 1, 13, -72,
+ 0, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 0},
+ "2001:db8:0:0:1::");
+ put(new byte[]{
+ 32, 1, 13, -72,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 2, 0, 0},
+ "2001:db8::2:0");
+ put(new byte[]{
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 1},
+ "::1");
+ put(new byte[]{
+ 0, 0, 0, 0,
+ 0, 0, 0, 1,
+ 0, 0, 0, 0,
+ 0, 0, 0, 1},
+ "::1:0:0:0:1");
+ put(new byte[]{
+ 0, 0, 0, 0,
+ 1, 0, 0, 1,
+ 0, 0, 0, 0,
+ 1, 0, 0, 0},
+ "::100:1:0:0:100:0");
+ put(new byte[]{
+ 32, 1, 0, 0,
+ 65, 54, -29, 120,
+ -128, 0, 99, -65,
+ 63, -1, -3, -46},
+ "2001:0:4136:e378:8000:63bf:3fff:fdd2");
+ put(new byte[]{
+ -86, -86, -69, -69,
+ -52, -52, -35, -35,
+ -18, -18, -1, -1,
+ 17, 17, 34, 34},
+ "aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222");
+ put(new byte[]{
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0},
+ "::");
+ }
+ };
+
+ private static final Map ipv4MappedToIPv6AddressStrings = new HashMap() {
+ private static final long serialVersionUID = 1999763170377573184L;
+ {
+ // IPv4 addresses
+ put("255.255.255.255", "::ffff:255.255.255.255");
+ put("0.0.0.0", "::ffff:0.0.0.0");
+ put("127.0.0.1", "::ffff:127.0.0.1");
+ put("1.2.3.4", "::ffff:1.2.3.4");
+ put("192.168.0.1", "::ffff:192.168.0.1");
+
+ // IPv6 addresses
+ // Fully specified
+ put("2001:0:4136:e378:8000:63bf:3fff:fdd2", "2001:0:4136:e378:8000:63bf:3fff:fdd2");
+ put("aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", "aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222");
+ put("0:0:0:0:0:0:0:0", "::");
+ put("0:0:0:0:0:0:0:1", "::1");
+
+ // Compressing at the beginning
+ put("::1:0:0:0:1", "::1:0:0:0:1");
+ put("::1:ffff:ffff", "::1:ffff:ffff");
+ put("::", "::");
+ put("::1", "::1");
+ put("::ffff", "::ffff");
+ put("::ffff:0", "::ffff:0");
+ put("::ffff:ffff", "::ffff:ffff");
+ put("::0987:9876:8765", "::987:9876:8765");
+ put("::0987:9876:8765:7654", "::987:9876:8765:7654");
+ put("::0987:9876:8765:7654:6543", "::987:9876:8765:7654:6543");
+ put("::0987:9876:8765:7654:6543:5432", "::987:9876:8765:7654:6543:5432");
+ // Note the compression is removed (rfc 5952 section 4.2.2)
+ put("::0987:9876:8765:7654:6543:5432:3210", "0:987:9876:8765:7654:6543:5432:3210");
+
+ // Compressing at the end
+ // Note the compression is removed (rfc 5952 section 4.2.2)
+ put("2001:db8:abcd:bcde:cdef:def1:ef12::", "2001:db8:abcd:bcde:cdef:def1:ef12:0");
+ put("2001:db8:abcd:bcde:cdef:def1::", "2001:db8:abcd:bcde:cdef:def1::");
+ put("2001:db8:abcd:bcde:cdef::", "2001:db8:abcd:bcde:cdef::");
+ put("2001:db8:abcd:bcde::", "2001:db8:abcd:bcde::");
+ put("2001:db8:abcd::", "2001:db8:abcd::");
+ put("2001:1234::", "2001:1234::");
+ put("2001::", "2001::");
+ put("0::", "::");
+
+ // Compressing in the middle
+ put("1234:2345::7890", "1234:2345::7890");
+ put("1234::2345:7890", "1234::2345:7890");
+ put("1234:2345:3456::7890", "1234:2345:3456::7890");
+ put("1234:2345::3456:7890", "1234:2345::3456:7890");
+ put("1234::2345:3456:7890", "1234::2345:3456:7890");
+ put("1234:2345:3456:4567::7890", "1234:2345:3456:4567::7890");
+ put("1234:2345:3456::4567:7890", "1234:2345:3456::4567:7890");
+ put("1234:2345::3456:4567:7890", "1234:2345::3456:4567:7890");
+ put("1234::2345:3456:4567:7890", "1234::2345:3456:4567:7890");
+ put("1234:2345:3456:4567:5678::7890", "1234:2345:3456:4567:5678::7890");
+ put("1234:2345:3456:4567::5678:7890", "1234:2345:3456:4567::5678:7890");
+ put("1234:2345:3456::4567:5678:7890", "1234:2345:3456::4567:5678:7890");
+ put("1234:2345::3456:4567:5678:7890", "1234:2345::3456:4567:5678:7890");
+ put("1234::2345:3456:4567:5678:7890", "1234::2345:3456:4567:5678:7890");
+ // Note the compression is removed (rfc 5952 section 4.2.2)
+ put("1234:2345:3456:4567:5678:6789::7890", "1234:2345:3456:4567:5678:6789:0:7890");
+ // Note the compression is removed (rfc 5952 section 4.2.2)
+ put("1234:2345:3456:4567:5678::6789:7890", "1234:2345:3456:4567:5678:0:6789:7890");
+ // Note the compression is removed (rfc 5952 section 4.2.2)
+ put("1234:2345:3456:4567::5678:6789:7890", "1234:2345:3456:4567:0:5678:6789:7890");
+ // Note the compression is removed (rfc 5952 section 4.2.2)
+ put("1234:2345:3456::4567:5678:6789:7890", "1234:2345:3456:0:4567:5678:6789:7890");
+ // Note the compression is removed (rfc 5952 section 4.2.2)
+ put("1234:2345::3456:4567:5678:6789:7890", "1234:2345:0:3456:4567:5678:6789:7890");
+ // Note the compression is removed (rfc 5952 section 4.2.2)
+ put("1234::2345:3456:4567:5678:6789:7890", "1234:0:2345:3456:4567:5678:6789:7890");
+
+ // IPv4 mapped addresses
+ put("::ffff:255.255.255.255", "::ffff:255.255.255.255");
+ put("::ffff:0.0.0.0", "::ffff:0.0.0.0");
+ put("::ffff:127.0.0.1", "::ffff:127.0.0.1");
+ put("::ffff:1.2.3.4", "::ffff:1.2.3.4");
+ put("::ffff:192.168.0.1", "::ffff:192.168.0.1");
}
};
@@ -293,4 +536,39 @@ public class NetUtilTest {
assertArrayEquals(stringEntry.getValue(), NetUtil.createByteArrayFromIpAddressString(stringEntry.getKey()));
}
}
+
+ @Test
+ public void testIp6AddressToString() throws UnknownHostException {
+ for (Entry testEntry : ipv6ToAddressStrings.entrySet()) {
+ assertEquals(testEntry.getValue(),
+ NetUtil.toAddressString(InetAddress.getByAddress(testEntry.getKey())));
+ }
+ }
+
+ @Test
+ public void testIp4AddressToString() throws UnknownHostException {
+ for (Entry stringEntry : validIpV4Hosts.entrySet()) {
+ assertEquals(stringEntry.getKey(),
+ NetUtil.toAddressString(InetAddress.getByAddress(stringEntry.getValue())));
+ }
+ }
+
+ @Test
+ public void testIpv4MappedIp6GetByName() {
+ for (Entry testEntry : ipv4MappedToIPv6AddressStrings.entrySet()) {
+ assertEquals(testEntry.getValue(),
+ NetUtil.toAddressString(NetUtil.getByName(testEntry.getKey(), true), true));
+ }
+ }
+
+ @Test
+ public void testinvalidIpv4MappedIp6GetByName() {
+ for (String testEntry : invalidIpV4Hosts.keySet()) {
+ assertNull(NetUtil.getByName(testEntry, true));
+ }
+
+ for (String testEntry : invalidIpV6Hosts.keySet()) {
+ assertNull(NetUtil.getByName(testEntry, true));
+ }
+ }
}
diff --git a/microbench/src/test/java/io/netty/microbenchmark/common/NetUtilBenchmark.java b/microbench/src/test/java/io/netty/microbenchmark/common/NetUtilBenchmark.java
new file mode 100644
index 0000000000..0512fe33d8
--- /dev/null
+++ b/microbench/src/test/java/io/netty/microbenchmark/common/NetUtilBenchmark.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.microbenchmark.common;
+
+import io.netty.microbench.util.AbstractMicrobenchmark;
+import io.netty.util.NetUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+
+@Threads(4)
+@Warmup(iterations = 10)
+@Measurement(iterations = 10)
+public class NetUtilBenchmark extends AbstractMicrobenchmark {
+
+ @Benchmark
+ public void useGetByNameIpv4() {
+ for (String testEntry : invalidIpV4Hosts.keySet()) {
+ if (NetUtil.getByName(testEntry, true) != null) {
+ throw new RuntimeException("error");
+ }
+ }
+ }
+
+ @Benchmark
+ public void useGetByNameIpv6() {
+ for (String testEntry : invalidIpV6Hosts.keySet()) {
+ if (NetUtil.getByName(testEntry, true) != null) {
+ throw new RuntimeException("error");
+ }
+ }
+ }
+
+ @Benchmark
+ public void useIsValidIpv6() {
+ for (String host : invalidIpV6Hosts.keySet()) {
+ if (NetUtil.isValidIpV6Address(host)) {
+ throw new RuntimeException("error");
+ }
+ }
+ }
+
+ @Benchmark
+ public void useIsValidIpv4() {
+ for (String host : invalidIpV4Hosts.keySet()) {
+ if (NetUtil.isValidIpV4Address(host)) {
+ throw new RuntimeException("error");
+ }
+ }
+ }
+
+ private static final Map invalidIpV4Hosts = new HashMap() {
+ private static final long serialVersionUID = 1299215199895717282L;
+ {
+ put("1.256.3.4", null);
+ put("256.0.0.1", null);
+ put("1.1.1.1.1", null);
+ put("x.255.255.255", null);
+ put("0.1:0.0", null);
+ put("0.1.0.0:", null);
+ put("127.0.0.", null);
+ put("1.2..4", null);
+ put("192.0.1", null);
+ put("192.0.1.1.1", null);
+ put("192.0.1.a", null);
+ put("19a.0.1.1", null);
+ put("a.0.1.1", null);
+ put(".0.1.1", null);
+ put("...", null);
+ }
+ };
+
+ private static final Map invalidIpV6Hosts = new HashMap() {
+ private static final long serialVersionUID = -5870810805409009696L;
+ {
+ // Test method with garbage.
+ put("Obvious Garbage", null);
+ // Test method with preferred style, too many :
+ put("0:1:2:3:4:5:6:7:8", null);
+ // Test method with preferred style, not enough :
+ put("0:1:2:3:4:5:6", null);
+ // Test method with preferred style, bad digits.
+ put("0:1:2:3:4:5:6:x", null);
+ // Test method with preferred style, adjacent :
+ put("0:1:2:3:4:5:6::7", null);
+ // Too many : separators trailing
+ put("0:1:2:3:4:5:6:7::", null);
+ // Too many : separators leading
+ put("::0:1:2:3:4:5:6:7", null);
+ // Too many : separators trailing
+ put("1:2:3:4:5:6:7:", null);
+ // Too many : separators leading
+ put(":1:2:3:4:5:6:7", null);
+ // Too many : separators leading 0
+ put("0::1:2:3:4:5:6:7", null);
+ // Test method with preferred style, too many digits.
+ put("0:1:2:3:4:5:6:789abcdef", null);
+ // Test method with compressed style, bad digits.
+ put("0:1:2:3::x", null);
+ // Test method with compressed style, too many adjacent :
+ put("0:1:2:::3", null);
+ // Test method with compressed style, too many digits.
+ put("0:1:2:3::abcde", null);
+ // Test method with preferred style, too many :
+ put("0:1:2:3:4:5:6:7:8", null);
+ // Test method with compressed style, not enough :
+ put("0:1", null);
+ // Test method with ipv4 style, bad ipv6 digits.
+ put("0:0:0:0:0:x:10.0.0.1", null);
+ // Test method with ipv4 style, bad ipv4 digits.
+ put("0:0:0:0:0:0:10.0.0.x", null);
+ // Test method with ipv4 style, adjacent :
+ put("0:0:0:0:0::0:10.0.0.1", null);
+ // Test method with ipv4 style, too many ipv6 digits.
+ put("0:0:0:0:0:00000:10.0.0.1", null);
+ // Test method with ipv4 style, too many :
+ put("0:0:0:0:0:0:0:10.0.0.1", null);
+ // Test method with ipv4 style, not enough :
+ put("0:0:0:0:0:10.0.0.1", null);
+ // Test method with ipv4 style, too many .
+ put("0:0:0:0:0:0:10.0.0.0.1", null);
+ // Test method with ipv4 style, not enough .
+ put("0:0:0:0:0:0:10.0.1", null);
+ // Test method with ipv4 style, adjacent .
+ put("0:0:0:0:0:0:10..0.0.1", null);
+ // Test method with ipv4 style, leading .
+ put("0:0:0:0:0:0:.0.0.1", null);
+ // Test method with ipv4 style, leading .
+ put("0:0:0:0:0:0:.10.0.0.1", null);
+ // Test method with ipv4 style, trailing .
+ put("0:0:0:0:0:0:10.0.0.", null);
+ // Test method with ipv4 style, trailing .
+ put("0:0:0:0:0:0:10.0.0.1.", null);
+ // Test method with compressed ipv4 style, bad ipv6 digits.
+ put("::fffx:192.168.0.1", null);
+ // Test method with compressed ipv4 style, bad ipv4 digits.
+ put("::ffff:192.168.0.x", null);
+ // Test method with compressed ipv4 style, too many adjacent :
+ put(":::ffff:192.168.0.1", null);
+ // Test method with compressed ipv4 style, too many ipv6 digits.
+ put("::fffff:192.168.0.1", null);
+ // Test method with compressed ipv4 style, too many ipv4 digits.
+ put("::ffff:1923.168.0.1", null);
+ // Test method with compressed ipv4 style, not enough :
+ put(":ffff:192.168.0.1", null);
+ // Test method with compressed ipv4 style, too many .
+ put("::ffff:192.168.0.1.2", null);
+ // Test method with compressed ipv4 style, not enough .
+ put("::ffff:192.168.0", null);
+ // Test method with compressed ipv4 style, adjacent .
+ put("::ffff:192.168..0.1", null);
+ // Test method, garbage.
+ put("absolute, and utter garbage", null);
+ // Test method, bad ipv6 digits.
+ put("x:0:0:0:0:0:10.0.0.1", null);
+ // Test method, bad ipv4 digits.
+ put("0:0:0:0:0:0:x.0.0.1", null);
+ // Test method, too many ipv6 digits.
+ put("00000:0:0:0:0:0:10.0.0.1", null);
+ // Test method, too many ipv4 digits.
+ put("0:0:0:0:0:0:10.0.0.1000", null);
+ // Test method, too many :
+ put("0:0:0:0:0:0:0:10.0.0.1", null);
+ // Test method, not enough :
+ put("0:0:0:0:0:10.0.0.1", null);
+ // Test method, out of order trailing :
+ put("0:0:0:0:0:10.0.0.1:", null);
+ // Test method, out of order leading :
+ put(":0:0:0:0:0:10.0.0.1", null);
+ // Test method, out of order leading :
+ put("0:0:0:0::10.0.0.1:", null);
+ // Test method, out of order trailing :
+ put(":0:0:0:0::10.0.0.1", null);
+ // Test method, too many .
+ put("0:0:0:0:0:0:10.0.0.0.1", null);
+ // Test method, not enough .
+ put("0:0:0:0:0:0:10.0.1", null);
+ // Test method, adjacent .
+ put("0:0:0:0:0:0:10.0.0..1", null);
+ // Double compression symbol
+ put("::0::", null);
+ // Empty contents
+ put("", null);
+ // Trailing : (max number of : = 8)
+ put("2001:0:4136:e378:8000:63bf:3fff:fdd2:", null);
+ // Leading : (max number of : = 8)
+ put(":aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", null);
+ // Invalid character
+ put("1234:2345:3456:4567:5678:6789::X890", null);
+ // Trailing . in IPv4
+ put("::ffff:255.255.255.255.", null);
+ // To many characters in IPv4
+ put("::ffff:0.0.1111.0", null);
+ // Test method, adjacent .
+ put("::ffff:0.0..0", null);
+ // Not enough IPv4 entries trailing .
+ put("::ffff:127.0.0.", null);
+ // Not enough IPv4 entries no trailing .
+ put("::ffff:1.2.4", null);
+ // Extra IPv4 entry
+ put("::ffff:192.168.0.1.255", null);
+ // Not enough IPv6 content
+ put(":ffff:192.168.0.1.255", null);
+ // Intermixed IPv4 and IPv6 symbols
+ put("::ffff:255.255:255.255.", null);
+ }
+ };
+}