From 9cb858fcf6ad55ec0770f81595f23ae04687f52e Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Thu, 6 Apr 2017 10:47:24 -0700 Subject: [PATCH] NetUtil IPv6 bugs related to IPv4 and compression Motivation: NetUtil#getByName and NetUtil#isValidIpV6Address do not strictly enforce the format of IPv4 addresses that are allowed to be embedded in IPv6 addresses as specified in https://tools.ietf.org/html/rfc4291#section-2.5.5. This may lead to invalid addresses being parsed, or invalid addresses being considered valid. Compression of a single IPv6 word was also not handled correctly if there are 7 : characters. Modifications: - NetUtil#isValidIpV6Address should enforce the IPv4-Compatible and IPv4-Mapped are the only valid formats for including IPv4 addresses as specified in https://tools.ietf.org/html/rfc4291#section-2.5.5 - NetUtil#getByName should more stritcly parse IPv6 addresses which contain IPv4 addresses as specified in https://tools.ietf.org/html/rfc4291#section-2.5.5 - NetUtil should allow compression even if the number of : characters is 7. - NetUtil#createByteArrayFromIpAddressString should use the same IP string to byte[] translation which is used in NetUtil#getByName Result: NetUtil#getByName and NetUtil#isValidIpV6Address respect the IPv6 RFC which defines the valid formats for embedding IPv4 addresses. --- .../src/main/java/io/netty/util/NetUtil.java | 265 ++++++++---------- .../util/internal/ConstantTimeUtils.java | 4 +- .../util/internal/PlatformDependent.java | 27 +- .../util/internal/PlatformDependent0.java | 28 +- .../test/java/io/netty/util/NetUtilTest.java | 95 ++++++- .../util/internal/PlatformDependentTest.java | 17 ++ 6 files changed, 269 insertions(+), 167 deletions(-) diff --git a/common/src/main/java/io/netty/util/NetUtil.java b/common/src/main/java/io/netty/util/NetUtil.java index 0ad14f7b65..8b3ec7c01e 100644 --- a/common/src/main/java/io/netty/util/NetUtil.java +++ b/common/src/main/java/io/netty/util/NetUtil.java @@ -314,8 +314,7 @@ public final class NetUtil { } /** - * Creates an byte[] based on an ipAddressString. No error handling is - * performed here. + * Creates an byte[] based on an ipAddressString. No error handling is performed here. */ public static byte[] createByteArrayFromIpAddressString(String ipAddressString) { @@ -343,112 +342,11 @@ public final class NetUtil { ipAddressString = ipAddressString.substring(0, percentPos); } - StringTokenizer tokenizer = new StringTokenizer(ipAddressString, ":.", true); - ArrayList hexStrings = new ArrayList(); - ArrayList decStrings = new ArrayList(); - String token = ""; - String prevToken = ""; - int doubleColonIndex = -1; // If a double colon exists, we need to - // insert 0s. - - // Go through the tokens, including the seperators ':' and '.' - // When we hit a : or . the previous token will be added to either - // the hex list or decimal list. In the case where we hit a :: - // we will save the index of the hexStrings so we can add zeros - // in to fill out the string - while (tokenizer.hasMoreTokens()) { - prevToken = token; - token = tokenizer.nextToken(); - - if (":".equals(token)) { - if (":".equals(prevToken)) { - doubleColonIndex = hexStrings.size(); - } else if (!prevToken.isEmpty()) { - hexStrings.add(prevToken); - } - } else if (".".equals(token)) { - decStrings.add(prevToken); - } - } - - if (":".equals(prevToken)) { - if (":".equals(token)) { - doubleColonIndex = hexStrings.size(); - } else { - hexStrings.add(token); - } - } else if (".".equals(prevToken)) { - decStrings.add(token); - } - - // figure out how many hexStrings we should have - // also check if it is a IPv4 address - int hexStringsLength = 8; - - // If we have an IPv4 address tagged on at the end, subtract - // 4 bytes, or 2 hex words from the total - if (!decStrings.isEmpty()) { - hexStringsLength -= 2; - } - - // if we hit a double Colon add the appropriate hex strings - if (doubleColonIndex != -1) { - int numberToInsert = hexStringsLength - hexStrings.size(); - for (int i = 0; i < numberToInsert; i ++) { - hexStrings.add(doubleColonIndex, "0"); - } - } - - 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 << 1); - } - - // Now if there are any decimal values, we know where they go... - for (int i = 0; i < decStrings.size(); i ++) { - ipByteArray[i + 12] = (byte) (Integer.parseInt(decStrings.get(i)) & 255); - } - return ipByteArray; + return getIPv6ByName(ipAddressString, true); } return null; } - /** - * Converts a 4 character hex word into a 2 byte word equivalent - */ - private static void convertToBytes(String hexWord, byte[] ipByteArray, int byteIndex) { - - int hexWordLength = hexWord.length(); - int hexWordIndex = 0; - ipByteArray[byteIndex] = 0; - ipByteArray[byteIndex + 1] = 0; - int charValue; - - // high order 4 bits of first byte - if (hexWordLength > 3) { - charValue = getIntValue(hexWord.charAt(hexWordIndex ++)); - ipByteArray[byteIndex] |= charValue << 4; - } - - // low order 4 bits of the first byte - if (hexWordLength > 2) { - charValue = getIntValue(hexWord.charAt(hexWordIndex ++)); - ipByteArray[byteIndex] |= charValue; - } - - // high order 4 bits of second byte - if (hexWordLength > 1) { - charValue = getIntValue(hexWord.charAt(hexWordIndex ++)); - ipByteArray[byteIndex + 1] |= charValue << 4; - } - - // low order 4 bits of the first byte - charValue = getIntValue(hexWord.charAt(hexWordIndex)); - ipByteArray[byteIndex + 1] |= charValue & 15; - } - private static int getIntValue(char c) { switch (c) { case '0': @@ -581,6 +479,35 @@ public final class NetUtil { numberOfPeriods ++; if (numberOfPeriods > 3) { return false; + } else if (numberOfPeriods == 1) { + // Verify this address is of the correct structure to contain an IPv4 address. + // It must be IPv4-Mapped or IPv4-Compatible + // (see https://tools.ietf.org/html/rfc4291#section-2.5.5). + int j = i - word.length() - 2; // index of character before the previous ':'. + final int beginColonIndex = ipAddress.lastIndexOf(':', j); + if (beginColonIndex == -1) { + return false; + } + char tmpChar = ipAddress.charAt(j); + if (isValidIPv4MappedChar(tmpChar)) { + if (j - beginColonIndex != 4 || + !isValidIPv4MappedChar(ipAddress.charAt(j - 1)) || + !isValidIPv4MappedChar(ipAddress.charAt(j - 2)) || + !isValidIPv4MappedChar(ipAddress.charAt(j - 3))) { + return false; + } + j -= 5; + } else if (tmpChar == '0' || tmpChar == ':') { + --j; + } else { + return false; + } + for (; j >= startOffset; --j) { + tmpChar = ipAddress.charAt(j); + if (tmpChar != '0' && tmpChar != ':') { + return false; + } + } } if (!isValidIp4Word(word.toString())) { return false; @@ -588,12 +515,6 @@ public final class NetUtil { if (numberOfColons != 6 && !doubleColon) { return false; } - // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons with an - // IPv4 ending, otherwise 7 :'s is bad - if (numberOfColons == 7 && ipAddress.charAt(startOffset) != ':' && - ipAddress.charAt(1 + startOffset) != ':') { - return false; - } word.delete(0, word.length()); break; @@ -635,7 +556,7 @@ public final class NetUtil { // Check if we have an IPv4 ending if (numberOfPeriods > 0) { // There is a test case with 7 colons and valid ipv4 this should resolve it - if (numberOfPeriods != 3 || !(isValidIp4Word(word.toString()) && numberOfColons < 7)) { + if (numberOfPeriods != 3 || !(isValidIp4Word(word.toString()) && (numberOfColons < 7 || doubleColon))) { return false; } } else { @@ -679,6 +600,23 @@ public final class NetUtil { return c >= '0' && c <= '9'; } + private static boolean isValidIPv4MappedChar(char c) { + return c == 'f' || c == 'F'; + } + + private static boolean isValidIPv4MappedSeparators(byte b0, byte b1, boolean mustBeZero) { + // We allow IPv4 Mapped (https://tools.ietf.org/html/rfc4291#section-2.5.5.1) + // and IPv4 compatible (https://tools.ietf.org/html/rfc4291#section-2.5.5.1). + // The IPv4 compatible is deprecated, but it allows parsing of plain IPv4 addressed into IPv6-Mapped addresses. + return b0 == b1 && (b0 == 0 || !mustBeZero && b1 == -1); + } + + private static boolean isValidIPv4Mapped(byte[] bytes, int currentIndex, int compressBegin, int compressLength) { + return currentIndex <= 12 && currentIndex >= 2 && + isValidIPv4MappedSeparators(bytes[currentIndex - 1], bytes[currentIndex - 2], + (compressBegin + compressLength) >= 14) && PlatformDependent.isZero(bytes, 0, currentIndex - 3); + } + /** * Takes a string and parses it to see if it is a valid IPV4 address. * @@ -748,11 +686,37 @@ public final class NetUtil { * @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
  • + *
  • {@code false} Consider IPv4 mapped addresses as invalid.
  • *
* @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) { + byte[] bytes = getIPv6ByName(ip, ipv4Mapped); + if (bytes == null) { + return null; + } + try { + return Inet6Address.getByAddress(null, bytes, -1); + } catch (UnknownHostException e) { + throw new RuntimeException(e); // Should never happen + } + } + + /** + * Returns the byte array 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} Consider IPv4 mapped addresses as invalid.
  • + *
+ * @return byte array representation of the {@code ip} or {@code null} if not a valid IP address. + */ + private static byte[] getIPv6ByName(CharSequence ip, boolean ipv4Mapped) { final byte[] bytes = new byte[IPV6_BYTE_COUNT]; final int ipLength = ip.length(); int compressBegin = 0; @@ -761,17 +725,17 @@ public final class NetUtil { int value = 0; int begin = -1; int i = 0; - int ipv6Seperators = 0; - int ipv4Seperators = 0; + int ipv6Separators = 0; + int ipv4Separators = 0; int tmp; boolean needsShift = false; for (; i < ipLength; ++i) { final char c = ip.charAt(i); switch (c) { case ':': - ++ipv6Seperators; + ++ipv6Separators; if (i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR || - ipv4Seperators > 0 || ipv6Seperators > IPV6_MAX_SEPARATORS || + ipv4Separators > 0 || ipv6Separators > IPV6_MAX_SEPARATORS || currentIndex + 1 >= bytes.length) { return null; } @@ -792,8 +756,8 @@ public final class NetUtil { if (compressBegin != 0 || (tmp < ipLength && ip.charAt(tmp) == ':')) { return null; } - ++ipv6Seperators; - needsShift = ipv6Seperators == 2 && value == 0; + ++ipv6Separators; + needsShift = ipv6Separators == 2 && value == 0; compressBegin = currentIndex; compressLength = bytes.length - compressBegin - 2; ++i; @@ -802,22 +766,27 @@ public final class NetUtil { begin = -1; break; case '.': - ++ipv4Seperators; - if (i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR - || ipv4Seperators > IPV4_SEPARATORS - || (ipv6Seperators > 0 && (currentIndex + compressLength < 12)) + ++ipv4Separators; + tmp = i - begin; // tmp is the length of the current segment. + if (tmp > IPV4_MAX_CHAR_BETWEEN_SEPARATOR + || begin < 0 + || ipv4Separators > IPV4_SEPARATORS + || (ipv6Separators > 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))))) { + || ipv4Separators == 1 && + // We also parse pure IPv4 addresses as IPv4-Mapped for ease of use. + ((!ipv4Mapped || currentIndex != 0 && !isValidIPv4Mapped(bytes, currentIndex, + compressBegin, compressLength)) || + (tmp == 3 && (!isValidNumericChar(ip.charAt(i - 1)) || + !isValidNumericChar(ip.charAt(i - 2)) || + !isValidNumericChar(ip.charAt(i - 3))) || + tmp == 2 && (!isValidNumericChar(ip.charAt(i - 1)) || + !isValidNumericChar(ip.charAt(i - 2))) || + tmp == 1 && !isValidNumericChar(ip.charAt(i - 1))))) { return null; } - value <<= (IPV4_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2; + value <<= (IPV4_MAX_CHAR_BETWEEN_SEPARATOR - tmp) << 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 @@ -831,7 +800,7 @@ public final class NetUtil { begin = -1; break; default: - if (!isValidHexChar(c) || (ipv4Seperators > 0 && !isValidNumericChar(c))) { + if (!isValidHexChar(c) || (ipv4Separators > 0 && !isValidNumericChar(c))) { return null; } if (begin < 0) { @@ -850,18 +819,17 @@ public final class NetUtil { 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 (ipv4Separators > 0) { if (begin > 0 && i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR || - ipv4Seperators != IPV4_SEPARATORS || + ipv4Separators != IPV4_SEPARATORS || currentIndex >= bytes.length) { return null; } - if (ipv6Seperators == 0) { + if (ipv6Separators == 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 && + } else if (ipv6Separators >= IPV6_MIN_SEPARATORS && + (!isCompressed && (ipv6Separators == 6 && ip.charAt(0) != ':') || + isCompressed && (ipv6Separators < IPV6_MAX_SEPARATORS && (ip.charAt(0) != ':' || compressBegin <= 2)))) { compressLength -= 2; } else { @@ -880,11 +848,11 @@ public final class NetUtil { } else { tmp = ipLength - 1; if (begin > 0 && i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR || - ipv6Seperators < IPV6_MIN_SEPARATORS || - !isCompressed && (ipv6Seperators + 1 != IPV6_MAX_SEPARATORS || + ipv6Separators < IPV6_MIN_SEPARATORS || + !isCompressed && (ipv6Separators + 1 != IPV6_MAX_SEPARATORS || ip.charAt(0) == ':' || ip.charAt(tmp) == ':') || - isCompressed && (ipv6Seperators > IPV6_MAX_SEPARATORS || - (ipv6Seperators == IPV6_MAX_SEPARATORS && + isCompressed && (ipv6Separators > IPV6_MAX_SEPARATORS || + (ipv6Separators == IPV6_MAX_SEPARATORS && (compressBegin <= 2 && ip.charAt(0) != ':' || compressBegin >= 14 && ip.charAt(tmp) != ':'))) || currentIndex + 1 >= bytes.length) { @@ -927,17 +895,14 @@ public final class NetUtil { } } - 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) { + if (ipv4Separators > 0) { + // We only support IPv4-Mapped addresses [1] because IPv4-Compatible addresses are deprecated [2]. + // [1] https://tools.ietf.org/html/rfc4291#section-2.5.5.2 + // [2] https://tools.ietf.org/html/rfc4291#section-2.5.5.1 bytes[10] = bytes[11] = (byte) 0xff; } - try { - return Inet6Address.getByAddress(null, bytes, -1); - } catch (UnknownHostException e) { - throw new RuntimeException(e); // Should never happen - } + return bytes; } /** diff --git a/common/src/main/java/io/netty/util/internal/ConstantTimeUtils.java b/common/src/main/java/io/netty/util/internal/ConstantTimeUtils.java index 8bddb629e4..9411a71169 100644 --- a/common/src/main/java/io/netty/util/internal/ConstantTimeUtils.java +++ b/common/src/main/java/io/netty/util/internal/ConstantTimeUtils.java @@ -95,8 +95,8 @@ public final class ConstantTimeUtils { // Benchmarking demonstrates that using an int to accumulate is faster than other data types. int b = 0; final int end = startPos1 + length; - for (int i = startPos1, j = startPos2; i < end; ++i, ++j) { - b |= bytes1[i] ^ bytes2[j]; + for (; startPos1 < end; ++startPos1, ++startPos2) { + b |= bytes1[startPos1] ^ bytes2[startPos2]; } return equalsConstantTime(b, 0); } diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent.java b/common/src/main/java/io/netty/util/internal/PlatformDependent.java index 9a9e6bde2f..ef07d8fbad 100644 --- a/common/src/main/java/io/netty/util/internal/PlatformDependent.java +++ b/common/src/main/java/io/netty/util/internal/PlatformDependent.java @@ -645,6 +645,19 @@ public final class PlatformDependent { PlatformDependent0.equals(bytes1, startPos1, bytes2, startPos2, length); } + /** + * Determine if a subsection of an array is zero. + * @param bytes The byte array. + * @param startPos The starting index (inclusive) in {@code bytes}. + * @param length The amount of bytes to check for zero. + * @return {@code false} if {@code bytes[startPos:startsPos+length)} contains a value other than zero. + */ + public static boolean isZero(byte[] bytes, int startPos, int length) { + return !hasUnsafe() || !unalignedAccess() ? + isZeroSafe(bytes, startPos, length) : + PlatformDependent0.isZero(bytes, startPos, length); + } + /** * Compare two {@code byte} arrays for equality without leaking timing information. * For performance reasons no bounds checking on the parameters is performed. @@ -1179,8 +1192,18 @@ public final class PlatformDependent { private static boolean equalsSafe(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) { final int end = startPos1 + length; - for (int i = startPos1, j = startPos2; i < end; ++i, ++j) { - if (bytes1[i] != bytes2[j]) { + for (; startPos1 < end; ++startPos1, ++startPos2) { + if (bytes1[startPos1] != bytes2[startPos2]) { + return false; + } + } + return true; + } + + private static boolean isZeroSafe(byte[] bytes, int startPos, int length) { + final int end = startPos + length; + for (; startPos < end; ++startPos) { + if (bytes[startPos] != 0) { return false; } } diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java index 97065c52e2..b755e7392b 100644 --- a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java +++ b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java @@ -552,7 +552,7 @@ final class PlatformDependent0 { } static boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) { - if (length == 0) { + if (length <= 0) { return true; } final long baseOffset1 = BYTE_ARRAY_BASE_OFFSET + startPos1; @@ -620,6 +620,32 @@ final class PlatformDependent0 { } } + static boolean isZero(byte[] bytes, int startPos, int length) { + if (length <= 0) { + return true; + } + final long baseOffset = BYTE_ARRAY_BASE_OFFSET + startPos; + int remainingBytes = length & 7; + final long end = baseOffset + remainingBytes; + for (long i = baseOffset - 8 + length; i >= end; i -= 8) { + if (UNSAFE.getLong(bytes, i) != 0) { + return false; + } + } + + if (remainingBytes >= 4) { + remainingBytes -= 4; + if (UNSAFE.getInt(bytes, baseOffset + remainingBytes) != 0) { + return false; + } + } + if (remainingBytes >= 2) { + return UNSAFE.getChar(bytes, baseOffset) == 0 && + (remainingBytes == 2 || bytes[startPos + 2] == 0); + } + return bytes[startPos] == 0; + } + static int hashCodeAscii(byte[] bytes, int startPos, int length) { int hash = HASH_CODE_ASCII_SEED; final long baseOffset = BYTE_ARRAY_BASE_OFFSET + startPos; diff --git a/common/src/test/java/io/netty/util/NetUtilTest.java b/common/src/test/java/io/netty/util/NetUtilTest.java index 09afacca35..8c12a641d4 100644 --- a/common/src/test/java/io/netty/util/NetUtilTest.java +++ b/common/src/test/java/io/netty/util/NetUtilTest.java @@ -17,6 +17,7 @@ package io.netty.util; import org.junit.Test; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; @@ -29,7 +30,11 @@ import static io.netty.util.NetUtil.createByteArrayFromIpAddressString; import static io.netty.util.NetUtil.getByName; import static io.netty.util.NetUtil.toAddressString; import static io.netty.util.NetUtil.toSocketAddressString; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class NetUtilTest { @@ -87,7 +92,11 @@ public class NetUtilTest { "[2001:0000:4136:e378:8000:63bf:3fff:fdd2]", "200100004136e378800063bf3ffffdd2", "0:1:2:3:4:5:6:789a", "0000000100020003000400050006789a", "0:1:2:3::f", "0000000100020003000000000000000f", - "0:0:0:0:0:0:10.0.0.1", "0000000000000000000000000a000001", + "0:0:0:0:0:0:10.0.0.1", "00000000000000000000ffff0a000001", + "0:0:0:0:0::10.0.0.1", "00000000000000000000ffff0a000001", + "::0:0:0:0:0:10.0.0.1", "00000000000000000000ffff0a000001", + "0:0:0:0:0:0::10.0.0.1", "00000000000000000000ffff0a000001", + "0:0:0:0:0:ffff:10.0.0.1", "00000000000000000000ffff0a000001", "::ffff:192.168.0.1", "00000000000000000000ffffc0a80001", // Test if various interface names after the percent sign are recognized. "[::1%1]", "00000000000000000000000000000001", @@ -134,8 +143,6 @@ public class NetUtilTest { "0:0:0:0:0:x:10.0.0.1", null, // Test method with ipv4 style, bad ipv4 digits. "0:0:0:0:0:0:10.0.0.x", null, - // Test method with ipv4 style, adjacent : - "0:0:0:0:0::0:10.0.0.1", null, // Test method with ipv4 style, too many ipv6 digits. "0:0:0:0:0:00000:10.0.0.1", null, // Test method with ipv4 style, too many : @@ -204,6 +211,8 @@ public class NetUtilTest { "0:0:0:0:0:0:10.0.0..1", null, // Double compression symbol "::0::", null, + // Double compression symbol + "12::0::12", null, // Empty contents "", null, // Trailing : (max number of : = 8) @@ -220,6 +229,14 @@ public class NetUtilTest { "::ffff:0.0..0", null, // Not enough IPv4 entries trailing . "::ffff:127.0.0.", null, + // Invalid trailing IPv4 character + "::ffff:127.0.0.a", null, + // Invalid leading IPv4 character + "::ffff:a.0.0.1", null, + // Invalid middle IPv4 character + "::ffff:127.a.0.1", null, + // Invalid middle IPv4 character + "::ffff:127.0.a.1", null, // Not enough IPv4 entries no trailing . "::ffff:1.2.4", null, // Extra IPv4 entry @@ -227,7 +244,43 @@ public class NetUtilTest { // Not enough IPv6 content ":ffff:192.168.0.1.255", null, // Intermixed IPv4 and IPv6 symbols - "::ffff:255.255:255.255.", null); + "::ffff:255.255:255.255.", null, + // Invalid IPv4 mapped address - invalid ipv4 separator + "0:0:0::0:0:00f.0.0.1", null, + // Invalid IPv4 mapped address - not enough f's + "0:0:0:0:0:fff:1.0.0.1", null, + // Invalid IPv4 mapped address - not IPv4 mapped, not IPv4 compatible + "0:0:0:0:0:ff00:1.0.0.1", null, + // Invalid IPv4 mapped address - not IPv4 mapped, not IPv4 compatible + "0:0:0:0:0:ff:1.0.0.1", null, + // Invalid IPv4 mapped address - too many f's + "0:0:0:0:0:fffff:1.0.0.1", null, + // Invalid IPv4 mapped address - too many bytes (too many 0's) + "0:0:0:0:0:0:ffff:1.0.0.1", null, + // Invalid IPv4 mapped address - too few bytes (not enough 0's) + "0:0:0:0:ffff:1.0.0.1", null, + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "0:0:0:0:0:ffff::10.0.0.1", null, + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "0:0:0:0:ffff::10.0.0.1", null, + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "0:0:0:ffff::10.0.0.1", null, + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "0:0:ffff::10.0.0.1", null, + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "0:ffff::10.0.0.1", null, + // Invalid IPv4 mapped address - 0's after the mapped ffff indicator + "ffff::10.0.0.1", null, + // Invalid IPv4 mapped address - not all 0's before the mapped separator + "1:0:0:0:0:ffff:10.0.0.1", null, + // Address that is similar to IPv4 mapped, but is invalid + "0:0:0:0:ffff:ffff:1.0.0.1", null, + // Valid number of separators, but invalid IPv4 format + "::1:2:3:4:5:6.7.8.9", null, + // Too many digits + "0:0:0:0:0:0:ffff:10.0.0.1", null, + // Invalid IPv4 format + ":1.2.3.4", null); private static final Map ipv6ToAddressStrings = new HashMap() { private static final long serialVersionUID = 2999763170377573184L; @@ -344,6 +397,24 @@ public class NetUtilTest { "1.2.3.4", "::ffff:1.2.3.4", "192.168.0.1", "::ffff:192.168.0.1", + // IPv4 compatible addresses are deprecated [1], so we don't support outputting them, but we do support + // parsing them into IPv4 mapped addresses. These values are treated the same as a plain IPv4 address above. + // [1] https://tools.ietf.org/html/rfc4291#section-2.5.5.1 + "0:0:0:0:0:0:255.254.253.252", "::ffff:255.254.253.252", + "0:0:0:0:0::1.2.3.4", "::ffff:1.2.3.4", + "0:0:0:0::1.2.3.4", "::ffff:1.2.3.4", + "0:0:0:0:0:0::1.2.3.4", "::ffff:1.2.3.4", + "::0:0:0:0:0:1.2.3.4", "::ffff:1.2.3.4", + "::0:0:0:0:1.2.3.4", "::ffff:1.2.3.4", + "::0:0:0:0:1.2.3.4", "::ffff:1.2.3.4", + "::0:0:0:1.2.3.4", "::ffff:1.2.3.4", + "::0:0:1.2.3.4", "::ffff:1.2.3.4", + "::0:1.2.3.4", "::ffff:1.2.3.4", + "::1.2.3.4", "::ffff:1.2.3.4", + + // IPv4 mapped (fully specified) + "0:0:0:0:0:ffff:1.2.3.4", "::ffff:1.2.3.4", + // IPv6 addresses // Fully specified "2001:0:4136:e378:8000:63bf:3fff:fdd2", "2001:0:4136:e378:8000:63bf:3fff:fdd2", @@ -435,10 +506,10 @@ public class NetUtilTest { @Test public void testIsValidIpV6Address() { for (String host : validIpV6Hosts.keySet()) { - assertTrue(NetUtil.isValidIpV6Address(host)); + assertTrue(host, NetUtil.isValidIpV6Address(host)); } for (String host : invalidIpV6Hosts.keySet()) { - assertFalse(NetUtil.isValidIpV6Address(host)); + assertFalse(host, NetUtil.isValidIpV6Address(host)); } } @@ -485,20 +556,20 @@ public class NetUtilTest { @Test public void testIpv4MappedIp6GetByName() { for (Entry testEntry : ipv4MappedToIPv6AddressStrings.entrySet()) { - assertEquals( - testEntry.getValue(), - toAddressString(getByName(testEntry.getKey(), true), true)); + Inet6Address inet6Address = getByName(testEntry.getKey(), true); + assertNotNull(testEntry.getKey() + ", " + testEntry.getValue(), inet6Address); + assertEquals(testEntry.getKey(), testEntry.getValue(), toAddressString(inet6Address, true)); } } @Test public void testinvalidIpv4MappedIp6GetByName() { for (String testEntry : invalidIpV4Hosts.keySet()) { - assertNull(getByName(testEntry, true)); + assertNull(testEntry, getByName(testEntry, true)); } for (String testEntry : invalidIpV6Hosts.keySet()) { - assertNull(getByName(testEntry, true)); + assertNull(testEntry, getByName(testEntry, true)); } } diff --git a/common/src/test/java/io/netty/util/internal/PlatformDependentTest.java b/common/src/test/java/io/netty/util/internal/PlatformDependentTest.java index 2999b66b04..3169dadf28 100644 --- a/common/src/test/java/io/netty/util/internal/PlatformDependentTest.java +++ b/common/src/test/java/io/netty/util/internal/PlatformDependentTest.java @@ -49,6 +49,20 @@ public class PlatformDependentTest { }); } + @Test + public void testIsZero() { + byte[] bytes = new byte[100]; + assertTrue(PlatformDependent.isZero(bytes, 0, 0)); + assertTrue(PlatformDependent.isZero(bytes, 0, -1)); + assertTrue(PlatformDependent.isZero(bytes, 0, 100)); + assertTrue(PlatformDependent.isZero(bytes, 10, 90)); + bytes[10] = 1; + assertTrue(PlatformDependent.isZero(bytes, 0, 10)); + assertFalse(PlatformDependent.isZero(bytes, 0, 11)); + assertFalse(PlatformDependent.isZero(bytes, 10, 1)); + assertTrue(PlatformDependent.isZero(bytes, 11, 89)); + } + private interface EqualityChecker { boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length); } @@ -105,6 +119,9 @@ public class PlatformDependentTest { bytes2 = bytes1.clone(); assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, bytes1.length)); } + + assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, 0)); + assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, -1)); } private static char randomCharInByteRange() {