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:
Nikolay Fedorovskikh 2017-05-09 17:32:36 +05:00 committed by Scott Mitchell
parent 9e62c79574
commit 5643cc6a10
2 changed files with 180 additions and 45 deletions

View File

@ -490,7 +490,6 @@ public final class NetUtil {
}
public static boolean isValidIpV6Address(String ipAddress) {
int length = ipAddress.length();
boolean doubleColon = false;
int numberOfColons = 0;
int numberOfPeriods = 0;
@ -555,7 +554,7 @@ 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) ||
if ((numberOfColons != 6 && !doubleColon) || numberOfColons > 7 ||
(numberOfColons == 7 && (ipAddress.charAt(startOffset) != ':' ||
ipAddress.charAt(1 + startOffset) != ':'))) {
return false;
@ -579,12 +578,12 @@ public final class NetUtil {
// 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) != ':')) {
if (i == startOffset && (endOffset <= i || ipAddress.charAt(i + 1) != ':')) {
return false;
}
// END FIX "IP6 mechanism syntax #ip6-bad1"
numberOfColons ++;
if (numberOfColons > 7) {
if (numberOfColons > 8) {
return false;
}
if (numberOfPeriods > 0) {
@ -623,11 +622,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;
}
}
@ -913,7 +916,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) {

View File

@ -25,11 +25,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import static io.netty.util.NetUtil.bytesToIpAddress;
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 io.netty.util.NetUtil.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@ -94,6 +90,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",
@ -105,9 +102,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.
@ -128,6 +147,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.
@ -138,8 +209,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.
@ -184,8 +253,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.
@ -212,12 +279,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)
@ -268,6 +334,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
@ -289,7 +357,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;
@ -511,47 +583,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(), createByteArrayFromIpAddressString(e.getKey()));
String ip = e.getKey();
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(ip), ip);
}
for (Entry<String, String> e : invalidIpV4Hosts.entrySet()) {
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(e.getKey()));
String ip = e.getKey();
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(ip), ip);
}
for (Entry<String, String> e : validIpV6Hosts.entrySet()) {
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(e.getKey()));
String ip = e.getKey();
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(ip), ip);
}
for (Entry<String, String> e : invalidIpV6Hosts.entrySet()) {
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(e.getKey()));
String ip = e.getKey();
assertHexDumpEquals(e.getValue(), createByteArrayFromIpAddressString(ip), ip);
}
}
@ -582,20 +710,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));
}
}
@ -615,8 +745,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) {