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
This commit is contained in:
zzz
2016-01-06 17:50:06 +00:00
parent 46af643ca8
commit 144f54eb8c
3 changed files with 122 additions and 6 deletions

View File

@ -41,6 +41,7 @@ import net.i2p.util.EventDispatcher;
import net.i2p.util.InternalSocket; import net.i2p.util.InternalSocket;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.PasswordManager; import net.i2p.util.PasswordManager;
import net.i2p.util.PortMapper;
import net.i2p.util.Translate; import net.i2p.util.Translate;
import net.i2p.util.TranslateReader; import net.i2p.util.TranslateReader;
@ -535,7 +536,24 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
while((len = reader.read(buf)) > 0) { while((len = reader.read(buf)) > 0) {
out.append(buf, 0, len); 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 { } finally {
try { try {
if(reader != null) if(reader != null)

View File

@ -1,14 +1,17 @@
package net.i2p.router.web; package net.i2p.router.web;
import java.util.ArrayList;
import java.awt.GraphicsEnvironment; import java.awt.GraphicsEnvironment;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -442,6 +445,7 @@ public class RouterConsoleRunner implements RouterApp {
System.err.println("Bad routerconsole port " + _listenPort); System.err.println("Bad routerconsole port " + _listenPort);
} }
if (lport > 0) { if (lport > 0) {
List<String> hosts = new ArrayList<String>(2);
StringTokenizer tok = new StringTokenizer(_listenHost, " ,"); StringTokenizer tok = new StringTokenizer(_listenHost, " ,");
while (tok.hasMoreTokens()) { while (tok.hasMoreTokens()) {
String host = tok.nextToken().trim(); String host = tok.nextToken().trim();
@ -490,13 +494,20 @@ public class RouterConsoleRunner implements RouterApp {
//_server.addConnector(lsnr); //_server.addConnector(lsnr);
connectors.add(lsnr); connectors.add(lsnr);
boundAddresses++; boundAddresses++;
hosts.add(host);
} catch (Exception ioe) { } catch (Exception ioe) {
System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + ": " + 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); 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?) if (hosts.isEmpty()) {
_context.portMapper().register(PortMapper.SVC_CONSOLE,lport); _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 // add SSL listeners
@ -520,6 +531,7 @@ public class RouterConsoleRunner implements RouterApp {
new String[I2PSSLSocketFactory.EXCLUDE_PROTOCOLS.size()])); new String[I2PSSLSocketFactory.EXCLUDE_PROTOCOLS.size()]));
sslFactory.addExcludeCipherSuites(I2PSSLSocketFactory.EXCLUDE_CIPHERS.toArray( sslFactory.addExcludeCipherSuites(I2PSSLSocketFactory.EXCLUDE_CIPHERS.toArray(
new String[I2PSSLSocketFactory.EXCLUDE_CIPHERS.size()])); new String[I2PSSLSocketFactory.EXCLUDE_CIPHERS.size()]));
List<String> hosts = new ArrayList<String>(2);
StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,"); StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,");
while (tok.hasMoreTokens()) { while (tok.hasMoreTokens()) {
String host = tok.nextToken().trim(); String host = tok.nextToken().trim();
@ -561,6 +573,7 @@ public class RouterConsoleRunner implements RouterApp {
//_server.addConnector(ssll); //_server.addConnector(ssll);
connectors.add(ssll); connectors.add(ssll);
boundAddresses++; boundAddresses++;
hosts.add(host);
} catch (Exception e) { } catch (Exception e) {
System.err.println("Unable to bind routerconsole to " + host + " port " + sslPort + " for SSL: " + e); System.err.println("Unable to bind routerconsole to " + host + " port " + sslPort + " for SSL: " + e);
if (SystemVersion.isGNU()) 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); 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 { } else {
System.err.println("Unable to create or access keystore for SSL: " + keyStore.getAbsolutePath()); 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<String>, 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 * Just to set the name and set Daemon

View File

@ -6,6 +6,7 @@ import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
@ -110,6 +111,68 @@ public class PortMapper {
return ia.getHostName(); 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<String> 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<String> 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 * For debugging only
* @since 0.9.20 * @since 0.9.20
@ -122,7 +185,7 @@ public class PortMapper {
InetSocketAddress ia = _dir.get(s); InetSocketAddress ia = _dir.get(s);
if (ia == null) if (ia == null)
continue; continue;
out.write("<tr><td>" + s + "<td>" + ia.getHostName() + "<td>" + ia.getPort() + '\n'); out.write("<tr><td>" + s + "<td>" + convertWildcard(ia.getHostName(), "127.0.0.1") + "<td>" + ia.getPort() + '\n');
} }
out.write("</table>\n"); out.write("</table>\n");
} }