From 37a6f5ed5dd56284ec37e14e0554e912f84426a1 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Wed, 12 Nov 2014 12:11:31 +0900 Subject: [PATCH] Handle the interface name in IPv6 address correctly Motivation: NetUtil.isValidIpV6Address() handles the interface name in IPv6 address incorrectly. For example, it returns false for the following addresses: - ::1%lo - ::1%_%_in_name_ Modifications: - Strip the square brackets before validation for simplicity - Strip the part after the percent sign completely before validation for simplicity - Simplify and reformat NetUtilTest Result: - The interface names in IPv6 addresses are handled correctly. - NetUtilTest is cleaner --- .../src/main/java/io/netty/util/NetUtil.java | 100 +-- .../test/java/io/netty/util/NetUtilTest.java | 718 ++++++++---------- 2 files changed, 364 insertions(+), 454 deletions(-) diff --git a/common/src/main/java/io/netty/util/NetUtil.java b/common/src/main/java/io/netty/util/NetUtil.java index b2ecdfb5da..20659c9e7d 100644 --- a/common/src/main/java/io/netty/util/NetUtil.java +++ b/common/src/main/java/io/netty/util/NetUtil.java @@ -443,45 +443,36 @@ public final class NetUtil { boolean doubleColon = false; int numberOfColons = 0; int numberOfPeriods = 0; - int numberOfPercent = 0; StringBuilder word = new StringBuilder(); char c = 0; char prevChar; - int offset = 0; // offset for [] ip addresses + int startOffset = 0; // offset for [] ip addresses + int endOffset = ipAddress.length(); - if (length < 2) { + if (endOffset < 2) { return false; } - for (int i = 0; i < length; i ++) { + // Strip [] + if (ipAddress.charAt(0) == '[') { + if (ipAddress.charAt(endOffset - 1) != ']') { + return false; // must have a close ] + } + + startOffset = 1; + endOffset --; + } + + // Strip the interface name/index after the percent sign. + int percentIdx = ipAddress.indexOf('%', startOffset); + if (percentIdx >= 0) { + endOffset = percentIdx; + } + + for (int i = startOffset; i < endOffset; i ++) { prevChar = c; c = ipAddress.charAt(i); switch (c) { - - // case for an open bracket [x:x:x:...x] - case '[': - if (i != 0) { - return false; // must be first character - } - if (ipAddress.charAt(length - 1) != ']') { - return false; // must have a close ] - } - offset = 1; - if (length < 4) { - return false; - } - break; - - // case for a closed bracket at end of IP [x:x:x:...x] - case ']': - if (i != length - 1) { - return false; // must be last charcter - } - if (ipAddress.charAt(0) != '[') { - return false; // must have a open [ - } - break; - // case for the last 32-bits represented as IPv4 x:x:x:x:x:x:d.d.d.d case '.': numberOfPeriods ++; @@ -496,8 +487,8 @@ 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 == 7 && ipAddress.charAt(offset) != ':' && - ipAddress.charAt(1 + offset) != ':') { + if (numberOfColons == 7 && ipAddress.charAt(startOffset) != ':' && + ipAddress.charAt(1 + startOffset) != ':') { return false; } word.delete(0, word.length()); @@ -507,7 +498,7 @@ 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 == offset && (ipAddress.length() <= i || ipAddress.charAt(i + 1) != ':')) { + if (i == startOffset && (ipAddress.length() <= i || ipAddress.charAt(i + 1) != ':')) { return false; } // END FIX "IP6 mechanism syntax #ip6-bad1" @@ -526,38 +517,13 @@ public final class NetUtil { } word.delete(0, word.length()); break; - case '%': - if (numberOfColons == 0) { - return false; - } - numberOfPercent ++; - - // validate that the stuff after the % is valid - if (i + 1 >= length) { - // in this case the percent is there but no number is - // available - return false; - } - try { - if (Integer.parseInt(ipAddress.substring(i + 1)) < 0) { - return false; - } - } catch (NumberFormatException e) { - // right now we just support an integer after the % so if - // this is not - // what is there then return - return false; - } - break; default: - if (numberOfPercent == 0) { - if (word != null && word.length() > 3) { - return false; - } - if (!isValidHexChar(c)) { - return false; - } + if (word != null && word.length() > 3) { + return false; + } + if (!isValidHexChar(c)) { + return false; } word.append(c); } @@ -579,11 +545,9 @@ public final class NetUtil { // 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 (numberOfPercent == 0) { - if (word.length() == 0 && ipAddress.charAt(length - 1 - offset) == ':' && - ipAddress.charAt(length - 2 - offset) != ':') { - return false; - } + if (word.length() == 0 && ipAddress.charAt(length - 1 - startOffset) == ':' && + ipAddress.charAt(length - 2 - startOffset) != ':') { + return false; } } @@ -696,7 +660,7 @@ public final class NetUtil { int i = 0; int ipv6Seperators = 0; int ipv4Seperators = 0; - int tmp = 0; + int tmp; boolean needsShift = false; for (; i < ipLength; ++i) { final char c = ip.charAt(i); diff --git a/common/src/test/java/io/netty/util/NetUtilTest.java b/common/src/test/java/io/netty/util/NetUtilTest.java index 1d9867bf0d..e3471befb5 100644 --- a/common/src/test/java/io/netty/util/NetUtilTest.java +++ b/common/src/test/java/io/netty/util/NetUtilTest.java @@ -15,12 +15,7 @@ */ package io.netty.util; -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 org.junit.Test; import java.net.InetAddress; import java.net.UnknownHostException; @@ -28,468 +23,387 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import org.junit.Test; +import static org.junit.Assert.*; public class NetUtilTest { - private static final Map validIpV4Hosts = new HashMap() { - private static final long serialVersionUID = 2629792739366724032L; - { - put("192.168.1.0", new byte[]{ - (byte) 0xc0, (byte) 0xa8, 0x01, 0x00} - ); - put("10.255.255.254", new byte[]{ - 0x0a, (byte) 0xff, (byte) 0xff, (byte) 0xfe - }); - put("172.18.5.4", new byte[]{ - (byte) 0xac, 0x12, 0x05, 0x04 - }); - put("0.0.0.0", new byte[]{ - 0x00, 0x00, 0x00, 0x00 - }); - put("127.0.0.1", new byte[]{ - 0x7f, 0x00, 0x00, 0x01 - }); + + private static final class TestMap extends HashMap { + private static final long serialVersionUID = -298642816998608473L; + + TestMap(String... values) { + for (int i = 0; i < values.length; i += 2) { + String key = values[i]; + String value = values[i + 1]; + put(key, value); + } } - }; - private static final Map invalidIpV4Hosts = new HashMap() { - 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 validIpV6Hosts = new HashMap() { - private static final long serialVersionUID = 3999763170377573184L; - { - put("::ffff:5.6.7.8", new byte[]{ - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, (byte) 0xff, (byte) 0xff, - 0x05, 0x06, 0x07, 0x08} - ); - put("fdf8:f53b:82e4::53", new byte[]{ - (byte) 0xfd, (byte) 0xf8, (byte) 0xf5, 0x3b, - (byte) 0x82, (byte) 0xe4, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x53} - ); - put("fe80::200:5aee:feaa:20a2", new byte[]{ - (byte) 0xfe, (byte) 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x5a, (byte) 0xee, - (byte) 0xfe, (byte) 0xaa, 0x20, (byte) 0xa2} - ); - put("2001::1", new byte[]{ - 0x20, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01} - ); - put("2001:0000:4136:e378:8000:63bf:3fff:fdd2", new byte[]{ - 0x20, 0x01, 0x00, 0x00, - 0x41, 0x36, (byte) 0xe3, 0x78, - (byte) 0x80, 0x00, 0x63, (byte) 0xbf, - 0x3f, (byte) 0xff, (byte) 0xfd, (byte) 0xd2} - ); - put("2001:0002:6c::430", new byte[]{ - 0x20, 0x01, 0x00, 0x02, - 0x00, 0x6c, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x04, 0x30} - ); - put("2001:10:240:ab::a", new byte[]{ - 0x20, 0x01, 0x00, 0x10, - 0x02, 0x40, 0x00, (byte) 0xab, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0a}); - put("2002:cb0a:3cdd:1::1", new byte[]{ - 0x20, 0x02, (byte) 0xcb, 0x0a, - 0x3c, (byte) 0xdd, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01} - ); - put("2001:db8:8:4::2", new byte[]{ - 0x20, 0x01, 0x0d, (byte) 0xb8, - 0x00, 0x08, 0x00, 0x04, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02} - ); - put("ff01:0:0:0:0:0:0:2", new byte[]{ - (byte) 0xff, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02} - ); - put("[fdf8:f53b:82e4::53]", new byte[]{ - (byte) 0xfd, (byte) 0xf8, (byte) 0xf5, 0x3b, - (byte) 0x82, (byte) 0xe4, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x53} - ); - put("[fe80::200:5aee:feaa:20a2]", new byte[]{ - (byte) 0xfe, (byte) 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x5a, (byte) 0xee, - (byte) 0xfe, (byte) 0xaa, 0x20, (byte) 0xa2} - ); - put("[2001::1]", new byte[]{ - 0x20, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01} - ); - put("[2001:0000:4136:e378:8000:63bf:3fff:fdd2]", new byte[]{ - 0x20, 0x01, 0x00, 0x00, - 0x41, 0x36, (byte) 0xe3, 0x78, - (byte) 0x80, 0x00, 0x63, (byte) 0xbf, - 0x3f, (byte) 0xff, (byte) 0xfd, (byte) 0xd2} - ); - put("0:1:2:3:4:5:6:789a", new byte[]{ - 0x00, 0x00, 0x00, 0x01, - 0x00, 0x02, 0x00, 0x03, - 0x00, 0x04, 0x00, 0x05, - 0x00, 0x06, 0x78, (byte) 0x9a} - ); - put("0:1:2:3::f", new byte[]{ - 0x00, 0x00, 0x00, 0x01, - 0x00, 0x02, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0f} - ); - put("0:0:0:0:0:0:10.0.0.1", new byte[]{ - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x01} - ); - put("::ffff:192.168.0.1", new byte[]{ - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, (byte) 0xff, (byte) 0xff, - (byte) 0xc0, (byte) 0xa8, 0x00, 0x01} - ); - } - }; - private static final Map invalidIpV6Hosts = new HashMap() { - private static final long serialVersionUID = -5870810805409009696L; - { + } + + private static final Map validIpV4Hosts = new TestMap( + "192.168.1.0", "c0a80100", + "10.255.255.254", "0afffffe", + "172.18.5.4", "ac120504", + "0.0.0.0", "00000000", + "127.0.0.1", "7f000001"); + + private static final Map invalidIpV4Hosts = new TestMap( + "1.256.3.4", null, + "256.0.0.1", null, + "1.1.1.1.1", null, + "x.255.255.255", null, + "0.1:0.0", null, + "0.1.0.0:", null, + "127.0.0.", null, + "1.2..4", null, + "192.0.1", null, + "192.0.1.1.1", null, + "192.0.1.a", null, + "19a.0.1.1", null, + "a.0.1.1", null, + ".0.1.1", null, + "...", null); + + private static final Map validIpV6Hosts = new TestMap( + "::ffff:5.6.7.8", "00000000000000000000ffff05060708", + "fdf8:f53b:82e4::53", "fdf8f53b82e400000000000000000053", + "fe80::200:5aee:feaa:20a2", "fe8000000000000002005aeefeaa20a2", + "2001::1", "20010000000000000000000000000001", + "2001:0000:4136:e378:8000:63bf:3fff:fdd2", "200100004136e378800063bf3ffffdd2", + "2001:0002:6c::430", "20010002006c00000000000000000430", + "2001:10:240:ab::a", "20010010024000ab000000000000000a", + "2002:cb0a:3cdd:1::1", "2002cb0a3cdd00010000000000000001", + "2001:db8:8:4::2", "20010db8000800040000000000000002", + "ff01:0:0:0:0:0:0:2", "ff010000000000000000000000000002", + "[fdf8:f53b:82e4::53]", "fdf8f53b82e400000000000000000053", + "[fe80::200:5aee:feaa:20a2]", "fe8000000000000002005aeefeaa20a2", + "[2001::1]", "20010000000000000000000000000001", + "[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", + "::ffff:192.168.0.1", "00000000000000000000ffffc0a80001", + // Test if various interface names after the percent sign are recognized. + "[::1%1]", "00000000000000000000000000000001", + "[::1%eth0]", "00000000000000000000000000000001", + "[::1%%]", "00000000000000000000000000000001", + "::1%1", "00000000000000000000000000000001", + "::1%eth0", "00000000000000000000000000000001", + "::1%%", "00000000000000000000000000000001"); + + private static final Map invalidIpV6Hosts = new TestMap( // Test method with garbage. - put("Obvious Garbage", null); + "Obvious Garbage", null, // Test method with preferred style, too many : - put("0:1:2:3:4:5:6:7:8", null); + "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); + "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); + "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); + "0:1:2:3:4:5:6::7", null, // Too many : separators trailing - put("0:1:2:3:4:5:6:7::", null); + "0:1:2:3:4:5:6:7::", null, // Too many : separators leading - put("::0:1:2:3:4:5:6:7", null); + "::0:1:2:3:4:5:6:7", null, // Too many : separators trailing - put("1:2:3:4:5:6:7:", null); + "1:2:3:4:5:6:7:", null, // Too many : separators leading - put(":1:2:3:4:5:6:7", null); + ":1:2:3:4:5:6:7", null, // Too many : separators leading 0 - put("0::1:2:3:4:5:6:7", null); + "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); + "0:1:2:3:4:5:6:789abcdef", null, // Test method with compressed style, bad digits. - put("0:1:2:3::x", null); + "0:1:2:3::x", null, // Test method with compressed style, too many adjacent : - put("0:1:2:::3", null); + "0:1:2:::3", null, // Test method with compressed style, too many digits. - put("0:1:2:3::abcde", null); + "0:1:2:3::abcde", null, // Test method with preferred style, too many : - put("0:1:2:3:4:5:6:7:8", null); + "0:1:2:3:4:5:6:7:8", null, // Test method with compressed style, not enough : - put("0:1", null); + "0:1", null, // Test method with ipv4 style, bad ipv6 digits. - put("0:0:0:0:0:x:10.0.0.1", null); + "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); + "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); + "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); + "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); + "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); + "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); + "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); + "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); + "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); + "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); + "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); + "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); + "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); + "::fffx:192.168.0.1", null, // Test method with compressed ipv4 style, bad ipv4 digits. - put("::ffff:192.168.0.x", null); + "::ffff:192.168.0.x", null, // Test method with compressed ipv4 style, too many adjacent : - put(":::ffff:192.168.0.1", null); + ":::ffff:192.168.0.1", null, // Test method with compressed ipv4 style, too many ipv6 digits. - put("::fffff:192.168.0.1", null); + "::fffff:192.168.0.1", null, // Test method with compressed ipv4 style, too many ipv4 digits. - put("::ffff:1923.168.0.1", null); + "::ffff:1923.168.0.1", null, // Test method with compressed ipv4 style, not enough : - put(":ffff:192.168.0.1", null); + ":ffff:192.168.0.1", null, // Test method with compressed ipv4 style, too many . - put("::ffff:192.168.0.1.2", null); + "::ffff:192.168.0.1.2", null, // Test method with compressed ipv4 style, not enough . - put("::ffff:192.168.0", null); + "::ffff:192.168.0", null, // Test method with compressed ipv4 style, adjacent . - put("::ffff:192.168..0.1", null); + "::ffff:192.168..0.1", null, // Test method, garbage. - put("absolute, and utter garbage", null); + "absolute, and utter garbage", null, // Test method, bad ipv6 digits. - put("x:0:0:0:0:0:10.0.0.1", null); + "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); + "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); + "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); + "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); + "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); + "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); + "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); + ":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); + "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); + ":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); + "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); + "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); + "0:0:0:0:0:0:10.0.0..1", null, // Double compression symbol - put("::0::", null); + "::0::", null, // Empty contents - put("", null); + "", null, // Trailing : (max number of : = 8) - put("2001:0:4136:e378:8000:63bf:3fff:fdd2:", null); + "2001:0:4136:e378:8000:63bf:3fff:fdd2:", null, // Leading : (max number of : = 8) - put(":aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", null); + ":aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", null, // Invalid character - put("1234:2345:3456:4567:5678:6789::X890", null); + "1234:2345:3456:4567:5678:6789::X890", null, // Trailing . in IPv4 - put("::ffff:255.255.255.255.", null); + "::ffff:255.255.255.255.", null, // To many characters in IPv4 - put("::ffff:0.0.1111.0", null); + "::ffff:0.0.1111.0", null, // Test method, adjacent . - put("::ffff:0.0..0", null); + "::ffff:0.0..0", null, // Not enough IPv4 entries trailing . - put("::ffff:127.0.0.", null); + "::ffff:127.0.0.", null, // Not enough IPv4 entries no trailing . - put("::ffff:1.2.4", null); + "::ffff:1.2.4", null, // Extra IPv4 entry - put("::ffff:192.168.0.1.255", null); + "::ffff:192.168.0.1.255", null, // Not enough IPv6 content - put(":ffff:192.168.0.1.255", null); + ":ffff:192.168.0.1.255", null, // Intermixed IPv4 and IPv6 symbols - put("::ffff:255.255:255.255.", null); - } - }; + "::ffff:255.255:255.255.", null); + private static final Map ipv6ToAddressStrings = new HashMap() { 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"); + 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}, - "::"); + 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 ipv4MappedToIPv6AddressStrings = new HashMap() { - private static final long serialVersionUID = 1999763170377573184L; - { + private static final Map ipv4MappedToIPv6AddressStrings = new TestMap( // 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"); + "255.255.255.255", "::ffff:255.255.255.255", + "0.0.0.0", "::ffff:0.0.0.0", + "127.0.0.1", "::ffff:127.0.0.1", + "1.2.3.4", "::ffff:1.2.3.4", + "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"); + "2001:0:4136:e378:8000:63bf:3fff:fdd2", "2001:0:4136:e378:8000:63bf:3fff:fdd2", + "aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", "aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", + "0:0:0:0:0:0:0:0", "::", + "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"); + "::1:0:0:0:1", "::1:0:0:0:1", + "::1:ffff:ffff", "::1:ffff:ffff", + "::", "::", + "::1", "::1", + "::ffff", "::ffff", + "::ffff:0", "::ffff:0", + "::ffff:ffff", "::ffff:ffff", + "::0987:9876:8765", "::987:9876:8765", + "::0987:9876:8765:7654", "::987:9876:8765:7654", + "::0987:9876:8765:7654:6543", "::987:9876:8765:7654:6543", + "::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"); + "::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::", "::"); + "2001:db8:abcd:bcde:cdef:def1:ef12::", "2001:db8:abcd:bcde:cdef:def1:ef12:0", + "2001:db8:abcd:bcde:cdef:def1::", "2001:db8:abcd:bcde:cdef:def1::", + "2001:db8:abcd:bcde:cdef::", "2001:db8:abcd:bcde:cdef::", + "2001:db8:abcd:bcde::", "2001:db8:abcd:bcde::", + "2001:db8:abcd::", "2001:db8:abcd::", + "2001:1234::", "2001:1234::", + "2001::", "2001::", + "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"); + "1234:2345::7890", "1234:2345::7890", + "1234::2345:7890", "1234::2345:7890", + "1234:2345:3456::7890", "1234:2345:3456::7890", + "1234:2345::3456:7890", "1234:2345::3456:7890", + "1234::2345:3456:7890", "1234::2345:3456:7890", + "1234:2345:3456:4567::7890", "1234:2345:3456:4567::7890", + "1234:2345:3456::4567:7890", "1234:2345:3456::4567:7890", + "1234:2345::3456:4567:7890", "1234:2345::3456:4567:7890", + "1234::2345:3456:4567:7890", "1234::2345:3456:4567:7890", + "1234:2345:3456:4567:5678::7890", "1234:2345:3456:4567:5678::7890", + "1234:2345:3456:4567::5678:7890", "1234:2345:3456:4567::5678:7890", + "1234:2345:3456::4567:5678:7890", "1234:2345:3456::4567:5678:7890", + "1234:2345::3456:4567:5678:7890", "1234:2345::3456:4567:5678:7890", + "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"); + "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"); + "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"); + "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"); + "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"); + "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"); + "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"); - } - }; + "::ffff:255.255.255.255", "::ffff:255.255.255.255", + "::ffff:0.0.0.0", "::ffff:0.0.0.0", + "::ffff:127.0.0.1", "::ffff:127.0.0.1", + "::ffff:1.2.3.4", "::ffff:1.2.3.4", + "::ffff:192.168.0.1", "::ffff:192.168.0.1"); @Test public void testLocalhost() { @@ -523,41 +437,40 @@ public class NetUtilTest { @Test public void testCreateByteArrayFromIpAddressString() { - for (Entry stringEntry : validIpV4Hosts.entrySet()) { - assertArrayEquals(stringEntry.getValue(), NetUtil.createByteArrayFromIpAddressString(stringEntry.getKey())); + for (Entry e : validIpV4Hosts.entrySet()) { + assertHexDumpEquals(e.getValue(), NetUtil.createByteArrayFromIpAddressString(e.getKey())); } - for (Entry stringEntry : invalidIpV4Hosts.entrySet()) { - assertArrayEquals(stringEntry.getValue(), NetUtil.createByteArrayFromIpAddressString(stringEntry.getKey())); + for (Entry e : invalidIpV4Hosts.entrySet()) { + assertHexDumpEquals(e.getValue(), NetUtil.createByteArrayFromIpAddressString(e.getKey())); } - for (Entry stringEntry : validIpV6Hosts.entrySet()) { - assertArrayEquals(stringEntry.getValue(), NetUtil.createByteArrayFromIpAddressString(stringEntry.getKey())); + for (Entry e : validIpV6Hosts.entrySet()) { + assertHexDumpEquals(e.getValue(), NetUtil.createByteArrayFromIpAddressString(e.getKey())); } - for (Entry stringEntry : invalidIpV6Hosts.entrySet()) { - assertArrayEquals(stringEntry.getValue(), NetUtil.createByteArrayFromIpAddressString(stringEntry.getKey())); + for (Entry e : invalidIpV6Hosts.entrySet()) { + assertHexDumpEquals(e.getValue(), NetUtil.createByteArrayFromIpAddressString(e.getKey())); } } @Test public void testIp6AddressToString() throws UnknownHostException { for (Entry testEntry : ipv6ToAddressStrings.entrySet()) { - assertEquals(testEntry.getValue(), - NetUtil.toAddressString(InetAddress.getByAddress(testEntry.getKey()))); + assertEquals(testEntry.getValue(), NetUtil.toAddressString(InetAddress.getByAddress(testEntry.getKey()))); } } @Test public void testIp4AddressToString() throws UnknownHostException { - for (Entry stringEntry : validIpV4Hosts.entrySet()) { - assertEquals(stringEntry.getKey(), - NetUtil.toAddressString(InetAddress.getByAddress(stringEntry.getValue()))); + for (Entry e : validIpV4Hosts.entrySet()) { + assertEquals(e.getKey(), NetUtil.toAddressString(InetAddress.getByAddress(unhex(e.getValue())))); } } @Test public void testIpv4MappedIp6GetByName() { for (Entry testEntry : ipv4MappedToIPv6AddressStrings.entrySet()) { - assertEquals(testEntry.getValue(), - NetUtil.toAddressString(NetUtil.getByName(testEntry.getKey(), true), true)); + assertEquals( + testEntry.getValue(), + NetUtil.toAddressString(NetUtil.getByName(testEntry.getKey(), true), true)); } } @@ -571,4 +484,37 @@ public class NetUtilTest { assertNull(NetUtil.getByName(testEntry, true)); } } + + private static void assertHexDumpEquals(String expected, byte[] actual) { + assertEquals(expected, hex(actual)); + } + + private static String hex(byte[] value) { + if (value == null) { + return null; + } + + StringBuilder buf = new StringBuilder(value.length << 1); + for (byte b: value) { + String hex = Integer.toHexString(b & 0xFF); + if (hex.length() == 1) { + buf.append('0'); + } + buf.append(hex); + } + return buf.toString(); + } + + private static byte[] unhex(String value) { + if (value == null) { + return null; + } + + byte[] buf = new byte[value.length() >>> 1]; + for (int i = 0; i < buf.length; i ++) { + buf[i] = (byte) Integer.parseInt(value.substring(i << 1, i + 1 << 1), 16); + } + + return buf; + } }