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.
This commit is contained in:
Scott Mitchell 2017-04-06 10:47:24 -07:00
parent a45f9d7939
commit 9cb858fcf6
6 changed files with 269 additions and 167 deletions

View File

@ -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<String> hexStrings = new ArrayList<String>();
ArrayList<String> decStrings = new ArrayList<String>();
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
* <ul>
* <li>{@code true} To allow IPv4 mapped inputs to be translated into {@link Inet6Address}</li>
* <li>{@code false} Don't turn IPv4 addressed to mapped addresses</li>
* <li>{@code false} Consider IPv4 mapped addresses as invalid.</li>
* </ul>
* @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.
* <p>
* The {@code ipv4Mapped} parameter specifies how IPv4 addresses should be treated.
* "IPv4 mapped" format as
* defined in <a href="http://tools.ietf.org/html/rfc4291#section-2.5.5">rfc 4291 section 2</a> is supported.
* @param ip {@link CharSequence} IP address to be converted to a {@link Inet6Address}
* @param ipv4Mapped
* <ul>
* <li>{@code true} To allow IPv4 mapped inputs to be translated into {@link Inet6Address}</li>
* <li>{@code false} Consider IPv4 mapped addresses as invalid.</li>
* </ul>
* @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;
}
/**

View File

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

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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<byte[], String> ipv6ToAddressStrings = new HashMap<byte[], String>() {
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<String, String> 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));
}
}

View File

@ -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() {