From 7e65c09373029a63a57e8ad2eae530f48c76724f Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Thu, 4 Sep 2014 06:58:28 -0400 Subject: [PATCH] IPv6 address to string rfc5952 Motivation: The java implementations for Inet6Address.getHostName() do not follow the RFC 5952 (http://tools.ietf.org/html/rfc5952#section-4) for recommended string representation. This introduces inconsistencies when integrating with other technologies that do follow the RFC. Modifications: -NetUtil.java to have another public static method to convert InetAddress to string. Inet4Address will use the java InetAddress.getHostAddress() implementation and there will be new code to implement the RFC 5952 IPV6 string conversion. -New unit tests to test the new method Result: Netty provides a RFC 5952 compliant string conversion method for IPV6 addresses --- .../src/main/java/io/netty/util/NetUtil.java | 422 +++++++++++++++++- .../test/java/io/netty/util/NetUtilTest.java | 282 +++++++++++- .../common/NetUtilBenchmark.java | 226 ++++++++++ 3 files changed, 923 insertions(+), 7 deletions(-) create mode 100644 microbench/src/test/java/io/netty/microbenchmark/common/NetUtilBenchmark.java diff --git a/common/src/main/java/io/netty/util/NetUtil.java b/common/src/main/java/io/netty/util/NetUtil.java index 295aa0d2d7..b2ecdfb5da 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; @@ -299,11 +345,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... @@ -558,10 +604,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. * @@ -610,6 +660,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 + *

+ * @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}. + * + *

+ * 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}. + *

+ *

+ * The output does not include Scope ID. + * @param ip {@link InetAddress} to be converted to an address string + * @param ipv4Mapped + *

+ * @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 + * + */ + 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); + } + }; +}