From 144f54eb8c3e1586569ef162844d288eaf57033f Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 6 Jan 2016 17:50:06 +0000 Subject: [PATCH] Console: Properly register listen hosts with PortMapper I2PTunnel: Fixup console links in error pages if console is on a non-standard host or port, or on https PortMapper: Add method to convert wildcard host to actual host --- .../i2ptunnel/I2PTunnelHTTPClientBase.java | 20 +++++- .../i2p/router/web/RouterConsoleRunner.java | 43 ++++++++++-- core/java/src/net/i2p/util/PortMapper.java | 65 ++++++++++++++++++- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java index e68e38d6b..3776d767b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java @@ -41,6 +41,7 @@ import net.i2p.util.EventDispatcher; import net.i2p.util.InternalSocket; import net.i2p.util.Log; import net.i2p.util.PasswordManager; +import net.i2p.util.PortMapper; import net.i2p.util.Translate; import net.i2p.util.TranslateReader; @@ -535,7 +536,24 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem while((len = reader.read(buf)) > 0) { out.append(buf, 0, len); } - return out.toString(); + String rv = out.toString(); + // Do we need to replace http://127.0.0.1:7657 console links in the error page? + // Get the registered host and port from the PortMapper. + final String unset = "*unset*"; + final String httpHost = ctx.portMapper().getActualHost(PortMapper.SVC_CONSOLE, unset); + final String httpsHost = ctx.portMapper().getActualHost(PortMapper.SVC_HTTPS_CONSOLE, unset); + final int httpPort = ctx.portMapper().getPort(PortMapper.SVC_CONSOLE, 7657); + final int httpsPort = ctx.portMapper().getPort(PortMapper.SVC_HTTPS_CONSOLE, -1); + final boolean httpsOnly = httpsPort > 0 && httpHost.equals(unset) && !httpsHost.equals(unset); + final int port = httpsOnly ? httpsPort : httpPort; + String host = httpsOnly ? httpsHost : httpHost; + if (host.equals(unset)) + host = "127.0.0.1"; + if (httpsOnly || port != 7657 || !host.equals("127.0.0.1")) { + String url = (httpsOnly ? "https://" : "http://") + host + ':' + port; + rv = rv.replace("http://127.0.0.1:7657", url); + } + return rv; } finally { try { if(reader != null) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 8841073f5..8315cdd6b 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -1,14 +1,17 @@ package net.i2p.router.web; -import java.util.ArrayList; import java.awt.GraphicsEnvironment; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.io.Serializable; import java.net.InetAddress; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.ServerSocket; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -442,6 +445,7 @@ public class RouterConsoleRunner implements RouterApp { System.err.println("Bad routerconsole port " + _listenPort); } if (lport > 0) { + List hosts = new ArrayList(2); StringTokenizer tok = new StringTokenizer(_listenHost, " ,"); while (tok.hasMoreTokens()) { String host = tok.nextToken().trim(); @@ -490,13 +494,20 @@ public class RouterConsoleRunner implements RouterApp { //_server.addConnector(lsnr); connectors.add(lsnr); boundAddresses++; + hosts.add(host); } catch (Exception ioe) { System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + ": " + ioe); System.err.println("You may ignore this warning if the console is still available at http://localhost:" + _listenPort); } } - // XXX: what if listenhosts do not include 127.0.0.1? (Should that ever even happen?) - _context.portMapper().register(PortMapper.SVC_CONSOLE,lport); + if (hosts.isEmpty()) { + _context.portMapper().register(PortMapper.SVC_CONSOLE, lport); + } else { + // put IPv4 first + Collections.sort(hosts, new HostComparator()); + _context.portMapper().register(PortMapper.SVC_CONSOLE, hosts.get(0), lport); + // note that we could still fail in connector.start() below + } } // add SSL listeners @@ -520,6 +531,7 @@ public class RouterConsoleRunner implements RouterApp { new String[I2PSSLSocketFactory.EXCLUDE_PROTOCOLS.size()])); sslFactory.addExcludeCipherSuites(I2PSSLSocketFactory.EXCLUDE_CIPHERS.toArray( new String[I2PSSLSocketFactory.EXCLUDE_CIPHERS.size()])); + List hosts = new ArrayList(2); StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,"); while (tok.hasMoreTokens()) { String host = tok.nextToken().trim(); @@ -561,6 +573,7 @@ public class RouterConsoleRunner implements RouterApp { //_server.addConnector(ssll); connectors.add(ssll); boundAddresses++; + hosts.add(host); } catch (Exception e) { System.err.println("Unable to bind routerconsole to " + host + " port " + sslPort + " for SSL: " + e); if (SystemVersion.isGNU()) @@ -568,7 +581,14 @@ public class RouterConsoleRunner implements RouterApp { System.err.println("You may ignore this warning if the console is still available at https://localhost:" + sslPort); } } - _context.portMapper().register(PortMapper.SVC_HTTPS_CONSOLE,sslPort); + if (hosts.isEmpty()) { + _context.portMapper().register(PortMapper.SVC_HTTPS_CONSOLE, sslPort); + } else { + // put IPv4 first + Collections.sort(hosts, new HostComparator()); + _context.portMapper().register(PortMapper.SVC_HTTPS_CONSOLE, hosts.get(0), sslPort); + // note that we could still fail in connector.start() below + } } else { System.err.println("Unable to create or access keystore for SSL: " + keyStore.getAbsolutePath()); } @@ -898,6 +918,21 @@ public class RouterConsoleRunner implements RouterApp { } } + /** + * Put IPv4 first + * @since 0.9.24 + */ + private static class HostComparator implements Comparator, Serializable { + public int compare(String l, String r) { + boolean l4 = l.contains("."); + boolean r4 = r.contains("."); + if (l4 && !r4) + return -1; + if (r4 && !l4) + return 1; + return l.compareTo(r); + } + } /** * Just to set the name and set Daemon diff --git a/core/java/src/net/i2p/util/PortMapper.java b/core/java/src/net/i2p/util/PortMapper.java index 28127dd0e..1a622828b 100644 --- a/core/java/src/net/i2p/util/PortMapper.java +++ b/core/java/src/net/i2p/util/PortMapper.java @@ -6,6 +6,7 @@ import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; @@ -110,6 +111,68 @@ public class PortMapper { return ia.getHostName(); } + /** + * Get the actual host for a service. + * Will return "127.0.0.1" if the service was registered without a host. + * If the service was registered with the host "0.0.0.0", "::", or "0:0:0:0:0:0:0:0", + * it will return a public IP if we have one, + * else a local IP if we have one, else def. + * If it was not registered with a wildcard address, it will return the registered host. + * + * @param def default + * @return def if not registered + * @since 0.9.24 + */ + public String getActualHost(String service, String def) { + InetSocketAddress ia = _dir.get(service); + if (ia == null) + return def; + return convertWildcard(ia.getHostName(), def); + } + + /* + * See above + * @param def default + * @return def if no ips + * @since 0.9.24 + */ + private static String convertWildcard(String ip, String def) { + String rv = ip; + if (rv.equals("0.0.0.0")) { + // public + rv = Addresses.getAnyAddress(); + if (rv == null) { + rv = def; + // local + Set addrs = Addresses.getAddresses(true, false); + for (String addr : addrs) { + if (!addr.startsWith("127.") && !addr.equals("0.0.0.0")) { + rv = addr; + break; + } + } + } + } else if (rv.equals("::") || rv.equals("0:0:0:0:0:0:0:0")) { + rv = def; + // public + Set addrs = Addresses.getAddresses(false, true); + for (String addr : addrs) { + if (!addr.contains(".")) { + return rv; + } + } + // local + addrs = Addresses.getAddresses(true, true); + for (String addr : addrs) { + if (!addr.contains(".") && !addr.equals("::") && !addr.equals("0:0:0:0:0:0:0:0")) { + rv = addr; + break; + } + } + } + return rv; + } + /** * For debugging only * @since 0.9.20 @@ -122,7 +185,7 @@ public class PortMapper { InetSocketAddress ia = _dir.get(s); if (ia == null) continue; - out.write("" + s + "" + ia.getHostName() + "" + ia.getPort() + '\n'); + out.write("" + s + "" + convertWildcard(ia.getHostName(), "127.0.0.1") + "" + ia.getPort() + '\n'); } out.write("\n"); }