From f2940146c41d3de286aba8ae331644c8ed981c54 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 30 Dec 2022 06:21:40 -0500 Subject: [PATCH] Util: Add methods to convert IPv6 addresses to canonical RFC 5952 strings Moved from NetDbRenderer Use in CLI only for now CLI and conversion micro-optimizations --- .../i2p/router/web/helpers/NetDbRenderer.java | 3 +- core/java/src/net/i2p/util/Addresses.java | 96 +++++++++++++++++-- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java index f5ca57658..872b84c5e 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java @@ -1263,8 +1263,7 @@ class NetDbRenderer { return Addresses.toString(bip); } else if (ip.contains(":0:")) { // convert to canonical - // https://stackoverflow.com/questions/7043983/ipv6-address-into-compressed-form-in-java - return ip.replaceAll("((?:(?:^|:)0+\\b){2,}):?(?!\\S*\\b\\1:0+\\b)(\\S*)", "::$2").replaceFirst("^0::","::"); + return Addresses.toCanonicalString(ip); } return null; } diff --git a/core/java/src/net/i2p/util/Addresses.java b/core/java/src/net/i2p/util/Addresses.java index 1fcdaafce..75e24d042 100644 --- a/core/java/src/net/i2p/util/Addresses.java +++ b/core/java/src/net/i2p/util/Addresses.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import java.util.regex.Pattern; import net.i2p.apache.http.conn.util.InetAddressUtils; @@ -55,6 +56,11 @@ public abstract class Addresses { private static final Set _macCache = new HashSet(); private static final boolean TEST_IPV6_ONLY = false; + // Convert IPv6 to canonical RFC 5952 + // https://stackoverflow.com/questions/7043983/ipv6-address-into-compressed-form-in-java + private static final String RFC5952_MATCH = "((?:(?:^|:)0+\\b){2,}):?(?!\\S*\\b\\1:0+\\b)(\\S*)"; + private static final Pattern RFC5952_PATTERN = Pattern.compile(RFC5952_MATCH); + /** * Do we have any address of this type? * Warning, very slow on Windows, appx. 200ms + 50ms/interface @@ -433,6 +439,65 @@ public abstract class Addresses { } } + /** + * Same as toString() but returns IPv6 in compressed form, ref. RFC 5952 + * @since 0.9.57 + */ + public static String toCanonicalString(byte[] addr) { + if (addr == null) + return "null"; + try { + String rv = InetAddress.getByAddress(addr).getHostAddress(); + if (addr.length == 16) + rv = x_toCanonicalString(rv); + return rv; + } catch (UnknownHostException uhe) { + return "bad IP length " + addr.length; + } + } + + /** + * Same as toString() but returns IPv6 in compressed form, ref. RFC 5952 + * @since 0.9.57 + */ + public static String toCanonicalString(byte[] addr, int port) { + if (addr == null) + return "null:" + port; + try { + String ip = InetAddress.getByAddress(addr).getHostAddress(); + if (addr.length != 16) + return ip + ':' + port; + ip = x_toCanonicalString(ip); + return '[' + ip + "]:" + port; + } catch (UnknownHostException uhe) { + return "(bad IP length " + addr.length + "):" + port; + } + } + + /** + * Converts IPv6 to compressed form, ref. RFC 5952. IPv4 returned unchanged. + * @since 0.9.57 + */ + public static String toCanonicalString(String host) { + if (host == null) + return "null"; + if (host.indexOf(':') < 0) + return host; + return x_toCanonicalString(host); + } + + /** + * Internal + * @param host non-null + * @since 0.9.57 + */ + private static String x_toCanonicalString(String host) { + String rv = RFC5952_PATTERN.matcher(host).replaceAll("::$2"); + if (rv.startsWith("0::")) + rv = rv.substring(1); + return rv; + } + /** * Convenience method to convert and validate a port String * without throwing an exception. @@ -560,11 +625,11 @@ public abstract class Addresses { if (rv == null) { if (isIPAddress(host)) { try { - if (host.contains(".")) { + if (host.indexOf('.') > 0) { rv = getIPv4(host); if (rv == null) return null; - } else if (host.contains(":") && !host.contains("::")) { + } else if (host.indexOf(':') >= 0 && !host.contains("::")) { rv = getIPv6(host); if (rv == null) return null; @@ -1015,26 +1080,26 @@ public abstract class Addresses { print(a); System.out.println("\nAll External Addresses (except deprecated IPv6):"); a = getAddresses(false, false, true); - print(a); + printCanonical(a); byte[] ygg = getYggdrasilAddress(); if (ygg != null) { System.out.println("\nYggdrasil Address:"); - System.out.println(toString(ygg)); + System.out.println(toCanonicalString(ygg)); } System.out.println("\nAll External and Local Addresses (may include deprecated IPv6):"); a = getAddresses(true, false, true); - print(a); + printCanonical(a); System.out.println("\nAll addresses:"); long time = System.currentTimeMillis(); a = getAddresses(true, true, true); time = System.currentTimeMillis() - time; - print(a); + printCanonical(a); System.out.println("\nIPv6 address flags:"); + StringBuilder buf = new StringBuilder(64); for (String s : a) { if (!s.contains(":")) continue; - StringBuilder buf = new StringBuilder(64); - buf.append(s); + buf.append(toCanonicalString(s)); Inet6Address addr; try { addr = (Inet6Address) InetAddress.getByName(buf.toString()); @@ -1057,13 +1122,12 @@ public abstract class Addresses { } } catch (UnknownHostException uhe) {} System.out.println(buf.toString()); + buf.setLength(0); } System.out.println("\nMac addresses:"); Set macs = new TreeSet(); - StringBuilder buf = new StringBuilder(17); for (String m : _macCache) { - buf.setLength(0); int i = 0; while(true) { buf.append(m.substring(i, i+2)); @@ -1073,6 +1137,7 @@ public abstract class Addresses { buf.append(':'); } macs.add(buf.toString()); + buf.setLength(0); } print(macs); System.out.println("\nHas IPv4? " + isConnected() + @@ -1174,6 +1239,17 @@ public abstract class Addresses { } } + /** @since 0.9.57 */ + private static void printCanonical(Set a) { + if (a.isEmpty()) { + System.out.println("none"); + } else { + for (String s : a) { + System.out.println(toCanonicalString(s)); + } + } + } + /** * @return "true", "false", or "unknown" * @since 0.9.50