IPv6 validation fixes
Motivation: `NetUtil`'s methods `isValidIpV6Address` and `getIPv6ByName` incorrectly validate some IPv6 addresses. Modifications: - `getIPv6ByName`: add checks for single colon at the start or end. - `isValidIpV6Address`: fix checks for the count of colons and use `endOffset` instead of `ipAddress.length()` for the cases with the brackets or '%'. Result: More correct implementation of `NetUtil#isValidIpV6Address` and `NetUtil#getIPv6ByName`.
This commit is contained in:
parent
c9b5415c91
commit
af2f6fba31
@ -366,7 +366,6 @@ public final class NetUtil {
|
||||
}
|
||||
|
||||
public static boolean isValidIpV6Address(String ipAddress) {
|
||||
int length = ipAddress.length();
|
||||
boolean doubleColon = false;
|
||||
int numberOfColons = 0;
|
||||
int numberOfPeriods = 0;
|
||||
@ -417,9 +416,9 @@ public final class NetUtil {
|
||||
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))) {
|
||||
!isValidIPv4MappedChar(ipAddress.charAt(j - 1)) ||
|
||||
!isValidIPv4MappedChar(ipAddress.charAt(j - 2)) ||
|
||||
!isValidIPv4MappedChar(ipAddress.charAt(j - 3))) {
|
||||
return false;
|
||||
}
|
||||
j -= 5;
|
||||
@ -431,9 +430,9 @@ public final class NetUtil {
|
||||
|
||||
// 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 != 6 && !doubleColon) ||
|
||||
(numberOfColons == 7 && (ipAddress.charAt(startOffset) != ':' ||
|
||||
ipAddress.charAt(1 + startOffset) != ':'))) {
|
||||
if ((numberOfColons != 6 && !doubleColon) || numberOfColons > 7 ||
|
||||
(numberOfColons == 7 && (ipAddress.charAt(startOffset) != ':' ||
|
||||
ipAddress.charAt(1 + startOffset) != ':'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -450,38 +449,39 @@ public final class NetUtil {
|
||||
}
|
||||
word.delete(0, word.length());
|
||||
break;
|
||||
case ':':
|
||||
// FIX "IP6 mechanism syntax #ip6-bad1"
|
||||
// An IPV6 address cannot start with a single ":".
|
||||
// Either it can starti with "::" or with a number.
|
||||
if (i == startOffset && (ipAddress.length() <= i || ipAddress.charAt(i + 1) != ':')) {
|
||||
return false;
|
||||
}
|
||||
// END FIX "IP6 mechanism syntax #ip6-bad1"
|
||||
numberOfColons ++;
|
||||
if (numberOfColons > 7) {
|
||||
return false;
|
||||
}
|
||||
if (numberOfPeriods > 0) {
|
||||
return false;
|
||||
}
|
||||
if (prevChar == ':') {
|
||||
if (doubleColon) {
|
||||
|
||||
case ':':
|
||||
// FIX "IP6 mechanism syntax #ip6-bad1"
|
||||
// An IPV6 address cannot start with a single ":".
|
||||
// Either it can starti with "::" or with a number.
|
||||
if (i == startOffset && (endOffset <= i || ipAddress.charAt(i + 1) != ':')) {
|
||||
return false;
|
||||
}
|
||||
doubleColon = true;
|
||||
}
|
||||
word.delete(0, word.length());
|
||||
break;
|
||||
// END FIX "IP6 mechanism syntax #ip6-bad1"
|
||||
numberOfColons ++;
|
||||
if (numberOfColons > 8) {
|
||||
return false;
|
||||
}
|
||||
if (numberOfPeriods > 0) {
|
||||
return false;
|
||||
}
|
||||
if (prevChar == ':') {
|
||||
if (doubleColon) {
|
||||
return false;
|
||||
}
|
||||
doubleColon = true;
|
||||
}
|
||||
word.delete(0, word.length());
|
||||
break;
|
||||
|
||||
default:
|
||||
if (word != null && word.length() > 3) {
|
||||
return false;
|
||||
}
|
||||
if (!isValidHexChar(c)) {
|
||||
return false;
|
||||
}
|
||||
word.append(c);
|
||||
default:
|
||||
if (word != null && word.length() > 3) {
|
||||
return false;
|
||||
}
|
||||
if (!isValidHexChar(c)) {
|
||||
return false;
|
||||
}
|
||||
word.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -498,11 +498,15 @@ public final class NetUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have an empty word at the end, it means we ended in either
|
||||
// a : or a .
|
||||
// If we did not end in :: then this is invalid
|
||||
if (word.length() == 0 && ipAddress.charAt(length - 1 - startOffset) == ':' &&
|
||||
ipAddress.charAt(length - 2 - startOffset) != ':') {
|
||||
if (word.length() == 0) {
|
||||
// If we have an empty word at the end, it means we ended in either
|
||||
// a : or a .
|
||||
// If we did not end in :: then this is invalid
|
||||
if (ipAddress.charAt(endOffset - 1) == ':' &&
|
||||
ipAddress.charAt(endOffset - 2) != ':') {
|
||||
return false;
|
||||
}
|
||||
} else if (numberOfColons == 8 && ipAddress.charAt(startOffset) != ':') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -792,7 +796,9 @@ public final class NetUtil {
|
||||
(ipv6Separators == IPV6_MAX_SEPARATORS &&
|
||||
(compressBegin <= 2 && ip.charAt(0) != ':' ||
|
||||
compressBegin >= 14 && ip.charAt(tmp) != ':'))) ||
|
||||
currentIndex + 1 >= bytes.length) {
|
||||
currentIndex + 1 >= bytes.length ||
|
||||
begin < 0 && ip.charAt(tmp - 1) != ':' ||
|
||||
compressBegin > 2 && ip.charAt(0) == ':') {
|
||||
return null;
|
||||
}
|
||||
if (begin >= 0 && i - begin <= IPV6_MAX_CHAR_BETWEEN_SEPARATOR) {
|
||||
|
@ -28,6 +28,8 @@ import java.util.Map.Entry;
|
||||
import static io.netty.util.NetUtil.getByName;
|
||||
import static io.netty.util.NetUtil.toAddressString;
|
||||
import static io.netty.util.NetUtil.toSocketAddressString;
|
||||
|
||||
import static io.netty.util.NetUtil.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@ -91,6 +93,7 @@ public class NetUtilTest {
|
||||
"0:1:2:3::f", "0000000100020003000000000000000f",
|
||||
"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::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:10.0.0.1", "00000000000000000000ffff0a000001",
|
||||
@ -102,9 +105,31 @@ public class NetUtilTest {
|
||||
"[::1%1]", "00000000000000000000000000000001",
|
||||
"[::1%eth0]", "00000000000000000000000000000001",
|
||||
"[::1%%]", "00000000000000000000000000000001",
|
||||
"0:0:0:0:0:ffff:10.0.0.1%", "00000000000000000000ffff0a000001",
|
||||
"0:0:0:0:0:ffff:10.0.0.1%1", "00000000000000000000ffff0a000001",
|
||||
"[0:0:0:0:0:ffff:10.0.0.1%1]", "00000000000000000000ffff0a000001",
|
||||
"[0:0:0:0:0::10.0.0.1%1]", "00000000000000000000ffff0a000001",
|
||||
"[::0:0:0:0:ffff:10.0.0.1%1]", "00000000000000000000ffff0a000001",
|
||||
"::0:0:0:0:ffff:10.0.0.1%1", "00000000000000000000ffff0a000001",
|
||||
"::1%1", "00000000000000000000000000000001",
|
||||
"::1%eth0", "00000000000000000000000000000001",
|
||||
"::1%%", "00000000000000000000000000000001");
|
||||
"::1%%", "00000000000000000000000000000001",
|
||||
// Tests with leading or trailing compression
|
||||
"0:0:0:0:0:0:0::", "00000000000000000000000000000000",
|
||||
"0:0:0:0:0:0::", "00000000000000000000000000000000",
|
||||
"0:0:0:0:0::", "00000000000000000000000000000000",
|
||||
"0:0:0:0::", "00000000000000000000000000000000",
|
||||
"0:0:0::", "00000000000000000000000000000000",
|
||||
"0:0::", "00000000000000000000000000000000",
|
||||
"0::", "00000000000000000000000000000000",
|
||||
"::", "00000000000000000000000000000000",
|
||||
"::0", "00000000000000000000000000000000",
|
||||
"::0:0", "00000000000000000000000000000000",
|
||||
"::0:0:0", "00000000000000000000000000000000",
|
||||
"::0:0:0:0", "00000000000000000000000000000000",
|
||||
"::0:0:0:0:0", "00000000000000000000000000000000",
|
||||
"::0:0:0:0:0:0", "00000000000000000000000000000000",
|
||||
"::0:0:0:0:0:0:0", "00000000000000000000000000000000");
|
||||
|
||||
private static final Map<String, String> invalidIpV6Hosts = new TestMap(
|
||||
// Test method with garbage.
|
||||
@ -125,6 +150,58 @@ public class NetUtilTest {
|
||||
"1:2:3:4:5:6:7:", null,
|
||||
// Too many : separators leading
|
||||
":1:2:3:4:5:6:7", null,
|
||||
// Compression with : separators trailing
|
||||
"0:1:2:3:4:5::7:", null,
|
||||
"0:1:2:3:4::7:", null,
|
||||
"0:1:2:3::7:", null,
|
||||
"0:1:2::7:", null,
|
||||
"0:1::7:", null,
|
||||
"0::7:", null,
|
||||
// Compression at start with : separators trailing
|
||||
"::0:1:2:3:4:5:7:", null,
|
||||
"::0:1:2:3:4:7:", null,
|
||||
"::0:1:2:3:7:", null,
|
||||
"::0:1:2:7:", null,
|
||||
"::0:1:7:", null,
|
||||
"::7:", null,
|
||||
// The : separators leading and trailing
|
||||
":1:2:3:4:5:6:7:", null,
|
||||
":1:2:3:4:5:6:", null,
|
||||
":1:2:3:4:5:", null,
|
||||
":1:2:3:4:", null,
|
||||
":1:2:3:", null,
|
||||
":1:2:", null,
|
||||
":1:", null,
|
||||
// Compression with : separators leading
|
||||
":1::2:3:4:5:6:7", null,
|
||||
":1::3:4:5:6:7", null,
|
||||
":1::4:5:6:7", null,
|
||||
":1::5:6:7", null,
|
||||
":1::6:7", null,
|
||||
":1::7", null,
|
||||
":1:2:3:4:5:6::7", null,
|
||||
":1:3:4:5:6::7", null,
|
||||
":1:4:5:6::7", null,
|
||||
":1:5:6::7", null,
|
||||
":1:6::7", null,
|
||||
":1::", null,
|
||||
// Compression trailing with : separators leading
|
||||
":1:2:3:4:5:6:7::", null,
|
||||
":1:3:4:5:6:7::", null,
|
||||
":1:4:5:6:7::", null,
|
||||
":1:5:6:7::", null,
|
||||
":1:6:7::", null,
|
||||
":1:7::", null,
|
||||
// Double compression
|
||||
"1::2:3:4:5:6::", null,
|
||||
"::1:2:3:4:5::6", null,
|
||||
"::1:2:3:4:5:6::", null,
|
||||
"::1:2:3:4:5::", null,
|
||||
"::1:2:3:4::", null,
|
||||
"::1:2:3::", null,
|
||||
"::1:2::", null,
|
||||
"::0::", null,
|
||||
"12::0::12", null,
|
||||
// Too many : separators leading 0
|
||||
"0::1:2:3:4:5:6:7", null,
|
||||
// Test method with preferred style, too many digits.
|
||||
@ -135,8 +212,6 @@ public class NetUtilTest {
|
||||
"0:1:2:::3", null,
|
||||
// Test method with compressed style, too many digits.
|
||||
"0:1:2:3::abcde", null,
|
||||
// Test method with preferred style, too many :
|
||||
"0:1:2:3:4:5:6:7:8", null,
|
||||
// Test method with compressed style, not enough :
|
||||
"0:1", null,
|
||||
// Test method with ipv4 style, bad ipv6 digits.
|
||||
@ -181,8 +256,6 @@ public class NetUtilTest {
|
||||
"::ffff:192.168.0", null,
|
||||
// Test method with compressed ipv4 style, adjacent .
|
||||
"::ffff:192.168..0.1", null,
|
||||
// Test method, garbage.
|
||||
"absolute, and utter garbage", null,
|
||||
// Test method, bad ipv6 digits.
|
||||
"x:0:0:0:0:0:10.0.0.1", null,
|
||||
// Test method, bad ipv4 digits.
|
||||
@ -209,12 +282,11 @@ public class NetUtilTest {
|
||||
"0:0:0:0:0:0:10.0.1", null,
|
||||
// Test method, adjacent .
|
||||
"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,
|
||||
// Invalid single compression
|
||||
":", null,
|
||||
":::", null,
|
||||
// Trailing : (max number of : = 8)
|
||||
"2001:0:4136:e378:8000:63bf:3fff:fdd2:", null,
|
||||
// Leading : (max number of : = 8)
|
||||
@ -265,6 +337,8 @@ public class NetUtilTest {
|
||||
"0:0:0:0:0:00000: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 - too few bytes (not enough 0's)
|
||||
"ffff:192.168.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
|
||||
@ -286,7 +360,11 @@ public class NetUtilTest {
|
||||
// Too many digits
|
||||
"0:0:0:0:0:0:ffff:10.0.0.1", null,
|
||||
// Invalid IPv4 format
|
||||
":1.2.3.4", null);
|
||||
":1.2.3.4", null,
|
||||
// Invalid IPv4 format
|
||||
"::.2.3.4", null,
|
||||
// Invalid IPv4 format
|
||||
"::ffff:0.1.2.", null);
|
||||
|
||||
private static final Map<byte[], String> ipv6ToAddressStrings = new HashMap<byte[], String>() {
|
||||
private static final long serialVersionUID = 2999763170377573184L;
|
||||
@ -508,47 +586,103 @@ public class NetUtilTest {
|
||||
|
||||
@Test
|
||||
public void testLocalhost() {
|
||||
assertNotNull(NetUtil.LOCALHOST);
|
||||
assertNotNull(LOCALHOST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoopback() {
|
||||
assertNotNull(NetUtil.LOOPBACK_IF);
|
||||
assertNotNull(LOOPBACK_IF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValidIpV4Address() {
|
||||
for (String host : validIpV4Hosts.keySet()) {
|
||||
assertTrue(NetUtil.isValidIpV4Address(host));
|
||||
assertTrue(host, isValidIpV4Address(host));
|
||||
}
|
||||
for (String host : invalidIpV4Hosts.keySet()) {
|
||||
assertFalse(NetUtil.isValidIpV4Address(host));
|
||||
assertFalse(host, isValidIpV4Address(host));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValidIpV6Address() {
|
||||
for (String host : validIpV6Hosts.keySet()) {
|
||||
assertTrue(host, NetUtil.isValidIpV6Address(host));
|
||||
assertTrue(host, isValidIpV6Address(host));
|
||||
if (host.charAt(0) != '[' && !host.contains("%")) {
|
||||
assertNotNull(host, getByName(host, true));
|
||||
|
||||
String hostMod = '[' + host + ']';
|
||||
assertTrue(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = host + '%';
|
||||
assertTrue(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = host + "%eth1";
|
||||
assertTrue(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = '[' + host + "%]";
|
||||
assertTrue(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = '[' + host + "%1]";
|
||||
assertTrue(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = '[' + host + "]%";
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = '[' + host + "]%1";
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
}
|
||||
}
|
||||
for (String host : invalidIpV6Hosts.keySet()) {
|
||||
assertFalse(host, NetUtil.isValidIpV6Address(host));
|
||||
assertFalse(host, isValidIpV6Address(host));
|
||||
assertNull(host, getByName(host));
|
||||
|
||||
String hostMod = '[' + host + ']';
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = host + '%';
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = host + "%eth1";
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = '[' + host + "%]";
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = '[' + host + "%1]";
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = '[' + host + "]%";
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = '[' + host + "]%1";
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = host + ']';
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
|
||||
hostMod = '[' + host;
|
||||
assertFalse(hostMod, isValidIpV6Address(hostMod));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateByteArrayFromIpAddressString() {
|
||||
for (Entry<String, String> e : validIpV4Hosts.entrySet()) {
|
||||
assertHexDumpEquals(e.getValue(), NetUtil.createByteArrayFromIpAddressString(e.getKey()));
|
||||
String ip = e.getKey();
|
||||
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(ip), ip);
|
||||
}
|
||||
for (Entry<String, String> e : invalidIpV4Hosts.entrySet()) {
|
||||
assertHexDumpEquals(e.getValue(), NetUtil.createByteArrayFromIpAddressString(e.getKey()));
|
||||
String ip = e.getKey();
|
||||
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(ip), ip);
|
||||
}
|
||||
for (Entry<String, String> e : validIpV6Hosts.entrySet()) {
|
||||
assertHexDumpEquals(e.getValue(), NetUtil.createByteArrayFromIpAddressString(e.getKey()));
|
||||
String ip = e.getKey();
|
||||
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(ip), ip);
|
||||
}
|
||||
for (Entry<String, String> e : invalidIpV6Hosts.entrySet()) {
|
||||
assertHexDumpEquals(e.getValue(), NetUtil.createByteArrayFromIpAddressString(e.getKey()));
|
||||
String ip = e.getKey();
|
||||
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(ip), ip);
|
||||
}
|
||||
}
|
||||
|
||||
@ -569,20 +703,22 @@ public class NetUtilTest {
|
||||
@Test
|
||||
public void testIpv4MappedIp6GetByName() {
|
||||
for (Entry<String, String> testEntry : ipv4MappedToIPv6AddressStrings.entrySet()) {
|
||||
Inet6Address inet6Address = getByName(testEntry.getKey(), true);
|
||||
assertNotNull(testEntry.getKey() + ", " + testEntry.getValue(), inet6Address);
|
||||
assertEquals(testEntry.getKey(), testEntry.getValue(), toAddressString(inet6Address, true));
|
||||
String srcIp = testEntry.getKey();
|
||||
String dstIp = testEntry.getValue();
|
||||
Inet6Address inet6Address = getByName(srcIp, true);
|
||||
assertNotNull(srcIp + ", " + dstIp, inet6Address);
|
||||
assertEquals(srcIp, dstIp, toAddressString(inet6Address, true));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testinvalidIpv4MappedIp6GetByName() {
|
||||
for (String testEntry : invalidIpV4Hosts.keySet()) {
|
||||
assertNull(testEntry, getByName(testEntry, true));
|
||||
public void testInvalidIpv4MappedIp6GetByName() {
|
||||
for (String host : invalidIpV4Hosts.keySet()) {
|
||||
assertNull(host, getByName(host, true));
|
||||
}
|
||||
|
||||
for (String testEntry : invalidIpV6Hosts.keySet()) {
|
||||
assertNull(testEntry, getByName(testEntry, true));
|
||||
for (String host : invalidIpV6Hosts.keySet()) {
|
||||
assertNull(host, getByName(host, true));
|
||||
}
|
||||
}
|
||||
|
||||
@ -602,8 +738,8 @@ public class NetUtilTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertHexDumpEquals(String expected, byte[] actual) {
|
||||
assertEquals(expected, hex(actual));
|
||||
private static void assertHexDumpEquals(String expected, byte[] actual, String message) {
|
||||
assertEquals(message, expected, hex(actual));
|
||||
}
|
||||
|
||||
private static String hex(byte[] value) {
|
||||
|
Loading…
Reference in New Issue
Block a user