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
This commit is contained in:
Scott Mitchell 2014-09-04 06:58:28 -04:00
parent 06ea226a28
commit 7e65c09373
3 changed files with 923 additions and 7 deletions

View File

@ -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.
* <p>
* 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.
* <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} Don't turn IPv4 addressed to mapped addresses</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) {
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}.
* <ul>
* <li>Inet4Address results are identical to {@link InetAddress#getHostAddress()}</li>
* <li>Inet6Address results adhere to
* <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a></li>
* </ul>
* <p>
* 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}.
* <ul>
* <li>Inet4Address results are identical to {@link InetAddress#getHostAddress()}</li>
* <li>Inet6Address results adhere to
* <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a> if
* {@code ipv4Mapped} is false. If {@code ipv4Mapped} is true then "IPv4 mapped" format
* from <a href="http://tools.ietf.org/html/rfc4291#section-2.5.5">rfc 4291 section 2</a> will be supported.
* The compressed result will always obey the compression rules defined in
* <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a></li>
* </ul>
* <p>
* The output does not include Scope ID.
* @param ip {@link InetAddress} to be converted to an address string
* @param ipv4Mapped
* <ul>
* <li>{@code true} to stray from strict rfc 5952 and support the "IPv4 mapped" format
* defined in <a href="http://tools.ietf.org/html/rfc4291#section-2.5.5">rfc 4291 section 2</a> while still
* following the updated guidelines in
* <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a></li>
* <li>{@code false} to strictly follow rfc 5952</li>
* </ul>
* @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
* <ul>
* <li>{@code true} if {@code value} if is within {@code start} (inclusive) and {@code end} (exclusive)</li>
* <li>{@code false} otherwise</li>
* </ul>
*/
private static boolean inRangeEndExclusive(int value, int start, int end) {
return value >= start && value < end;
}
/**
* A constructor to stop this class being constructed.
*/

View File

@ -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<String, byte[]> validIpV4Hosts = new HashMap<String, byte[]>() {
@ -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<String, byte[]> validIpV6Hosts = new HashMap<String, byte[]>() {
@ -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<byte[], String> ipv6ToAddressStrings = new HashMap<byte[], String>() {
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<String, String> ipv4MappedToIPv6AddressStrings = new HashMap<String, String>() {
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<byte[], String> testEntry : ipv6ToAddressStrings.entrySet()) {
assertEquals(testEntry.getValue(),
NetUtil.toAddressString(InetAddress.getByAddress(testEntry.getKey())));
}
}
@Test
public void testIp4AddressToString() throws UnknownHostException {
for (Entry<String, byte[]> stringEntry : validIpV4Hosts.entrySet()) {
assertEquals(stringEntry.getKey(),
NetUtil.toAddressString(InetAddress.getByAddress(stringEntry.getValue())));
}
}
@Test
public void testIpv4MappedIp6GetByName() {
for (Entry<String, String> 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));
}
}
}

View File

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