diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java index 560475e42f65999877b418ea3939220820aaf4fa..3b279a679aa008448a4170d8ce4f6b422840477c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java @@ -662,7 +662,7 @@ public class I2PTunnel implements Logging, EventDispatcher { * "openSOCKSTunnelResult" = "ok" or "error" after the client tunnel has * started. * - * @param args {portNumber} + * @param args {portNumber [, sharedClient]} * @param l logger to receive events and output */ public void runSOCKSTunnel(String args[], Logging l) { @@ -677,6 +677,11 @@ public class I2PTunnel implements Logging, EventDispatcher { return; } + boolean isShared = false; + if (args.length > 1) + isShared = "true".equalsIgnoreCase(args[1].trim()); + + ownDest = !isShared; I2PTunnelTask task; task = new I2PSOCKSTunnel(port, l, ownDest, (EventDispatcher) this, this); addtask(task); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 955d3abd1e82afb47427608c7176579a7a608ea5..f8592fcd39586e2e61a9947f05c030e7cdd26c92 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -135,8 +135,10 @@ public class TunnelController implements Logging { } if ("httpclient".equals(type)) { startHttpClient(); - }else if("ircclient".equals(type)) { + } else if("ircclient".equals(type)) { startIrcClient(); + } else if("sockstunnel".equals(type)) { + startSocksClient(); } else if ("client".equals(type)) { startClient(); } else if ("server".equals(type)) { @@ -176,6 +178,17 @@ public class TunnelController implements Logging { _running = true; } + private void startSocksClient() { + setI2CPOptions(); + setSessionOptions(); + setListenOn(); + String listenPort = getListenPort(); + String sharedClient = getSharedClient(); + _tunnel.runSOCKSTunnel(new String[] { listenPort, sharedClient }, this); + acquire(); + _running = true; + } + /** * Note the fact that we are using some sessions, so that they dont get * closed by some other tunnels diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java index 9b216e13ae6d5371f4451c46d540583613704e63..be398f770eb62d0af1962f4b745cb97615a5f689 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java @@ -7,6 +7,12 @@ package net.i2p.i2ptunnel.socks; import java.net.Socket; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; import net.i2p.client.streaming.I2PSocket; import net.i2p.data.Destination; @@ -20,7 +26,7 @@ import net.i2p.util.Log; public class I2PSOCKSTunnel extends I2PTunnelClientBase { private static final Log _log = new Log(I2PSOCKSTunnel.class); - + private HashMap<String, List<String>> proxies = null; // port# + "" or "default" -> hostname list protected Destination outProxyDest = null; //public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest) { @@ -36,7 +42,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase { } setName(getLocalPort() + " -> SOCKSTunnel"); - + parseOptions(); startRunning(); notifyEvent("openSOCKSTunnelResult", "ok"); @@ -46,11 +52,49 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase { try { SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s); Socket clientSock = serv.getClientSocket(); - I2PSocket destSock = serv.getDestinationI2PSocket(); + I2PSocket destSock = serv.getDestinationI2PSocket(this); new I2PTunnelRunner(clientSock, destSock, sockLock, null, mySockets); } catch (SOCKSException e) { _log.error("Error from SOCKS connection: " + e.getMessage()); closeSocket(s); } } -} \ No newline at end of file + + private static final String PROP_PROXY = "i2ptunnel.socks.proxy."; + private void parseOptions() { + Properties opts = getTunnel().getClientOptions(); + proxies = new HashMap(0); + for (Map.Entry e : opts.entrySet()) { + String prop = (String)e.getKey(); + if ((!prop.startsWith(PROP_PROXY)) || prop.length() <= PROP_PROXY.length()) + continue; + String port = prop.substring(PROP_PROXY.length()); + List proxyList = new ArrayList(1); + StringTokenizer tok = new StringTokenizer((String)e.getValue(), ", \t"); + while (tok.hasMoreTokens()) { + String proxy = tok.nextToken().trim(); + if (proxy.endsWith(".i2p")) + proxyList.add(proxy); + else + _log.error("Non-i2p SOCKS outproxy: " + proxy); + } + proxies.put(port, proxyList); + } + } + + public HashMap<String, List<String>> getProxyMap() { + return proxies; + } + + public List<String> getProxies(int port) { + List<String> rv = proxies.get(port + ""); + if (rv == null) + rv = getDefaultProxies(); + return rv; + } + + public List<String> getDefaultProxies() { + return proxies.get("default"); + } + +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java index 5efc51d9fb54cab9adc8910b98f4756867a9b5de..252d4e1aa8e3b55ff6228e473829a9fdc15dfb7c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java @@ -12,7 +12,14 @@ import java.io.DataOutputStream; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; - +import java.net.SocketException; +import java.util.List; + +import net.i2p.I2PAppContext; +import net.i2p.I2PException; +import net.i2p.client.streaming.I2PSocket; +import net.i2p.data.DataFormatException; +import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.util.HexDump; import net.i2p.util.Log; @@ -28,7 +35,6 @@ public class SOCKS5Server extends SOCKSServer { private static final int SOCKS_VERSION_5 = 0x05; private Socket clientSock = null; - private boolean setupCompleted = false; /** @@ -126,6 +132,7 @@ public class SOCKS5Server extends SOCKSServer { throw new SOCKSException("UDP ASSOCIATE command not supported"); default: _log.debug("unknown command in request (" + Integer.toHexString(command) + ")"); + sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); throw new SOCKSException("Invalid command in request"); } @@ -166,12 +173,14 @@ public class SOCKS5Server extends SOCKSServer { throw new SOCKSException("IPV6 addresses not supported"); default: _log.debug("unknown address type in request (" + Integer.toHexString(command) + ")"); + sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); throw new SOCKSException("Invalid addresses type in request"); } connPort = in.readUnsignedShort(); if (connPort == 0) { _log.debug("trying to connect to TCP port 0? Dropping!"); + sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); throw new SOCKSException("Invalid port number in request"); } } @@ -248,6 +257,107 @@ public class SOCKS5Server extends SOCKSServer { out.write(reply); } + /** + * Get an I2PSocket that can be used to send/receive 8-bit clean data + * to/from the destination of the SOCKS connection. + * + * @return an I2PSocket connected with the destination + */ + public I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException { + setupServer(); + + if (connHostName == null) { + _log.error("BUG: destination host name has not been initialized!"); + throw new SOCKSException("BUG! See the logs!"); + } + if (connPort == 0) { + _log.error("BUG: destination port has not been initialized!"); + throw new SOCKSException("BUG! See the logs!"); + } + + DataOutputStream out; // for errors + try { + out = new DataOutputStream(clientSock.getOutputStream()); + } catch (IOException e) { + throw new SOCKSException("Connection error (" + e.getMessage() + ")"); + } + + // FIXME: here we should read our config file, select an + // outproxy, and instantiate the proper socket class that + // handles the outproxy itself (SOCKS4a, SOCKS5, HTTP CONNECT...). + I2PSocket destSock; + + try { + if (connHostName.toLowerCase().endsWith(".i2p")) { + _log.debug("connecting to " + connHostName + "..."); + // Let's not due a new Dest for every request, huh? + //I2PSocketManager sm = I2PSocketManagerFactory.createManager(); + //destSock = sm.connect(I2PTunnel.destFromName(connHostName), null); + destSock = t.createI2PSocket(I2PTunnel.destFromName(connHostName)); + } else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) { + String err = "No localhost accesses allowed through the Socks Proxy"; + _log.error(err); + try { + sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); + } catch (IOException ioe) {} + throw new SOCKSException(err); + } else if (connPort == 80) { + // rewrite GET line to include hostname??? or add Host: line??? + // or forward to local eepProxy (but that's a Socket not an I2PSocket) + // use eepProxy configured outproxies? + String err = "No handler for HTTP outproxy implemented"; + _log.error(err); + try { + sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); + } catch (IOException ioe) {} + throw new SOCKSException(err); + } else { + List<String> proxies = t.getProxies(connPort); + if (proxies == null || proxies.size() <= 0) { + String err = "No outproxy configured for port " + connPort + " and no default configured either"; + _log.error(err); + try { + sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); + } catch (IOException ioe) {} + throw new SOCKSException(err); + } + int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size()); + String proxy = proxies.get(p); + _log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "..."); + // this isn't going to work, these need to be socks outproxies so we need + // to do a socks session to them? + destSock = t.createI2PSocket(I2PTunnel.destFromName(proxy)); + } + confirmConnection(); + _log.debug("connection confirmed - exchanging data..."); + } catch (DataFormatException e) { + try { + sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); + } catch (IOException ioe) {} + throw new SOCKSException("Error in destination format"); + } catch (SocketException e) { + try { + sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); + } catch (IOException ioe) {} + throw new SOCKSException("Error connecting (" + + e.getMessage() + ")"); + } catch (IOException e) { + try { + sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); + } catch (IOException ioe) {} + throw new SOCKSException("Error connecting (" + + e.getMessage() + ")"); + } catch (I2PException e) { + try { + sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); + } catch (IOException ioe) {} + throw new SOCKSException("Error connecting (" + + e.getMessage() + ")"); + } + + return destSock; + } + /* * Some namespaces to enclose SOCKS protocol codes */ @@ -279,4 +389,4 @@ public class SOCKS5Server extends SOCKSServer { private static final int COMMAND_NOT_SUPPORTED = 0x07; private static final int ADDRESS_TYPE_NOT_SUPPORTED = 0x08; } -} \ No newline at end of file +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java index caf4d1ce3f2478751998fdfcfce21cf833d6f22d..06c3fab552af235f8bf34896d072b8d406a6d594 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java @@ -6,15 +6,9 @@ */ package net.i2p.i2ptunnel.socks; -import java.io.IOException; import java.net.Socket; -import java.net.SocketException; -import net.i2p.I2PException; import net.i2p.client.streaming.I2PSocket; -import net.i2p.client.streaming.I2PSocketManager; -import net.i2p.client.streaming.I2PSocketManagerFactory; -import net.i2p.data.DataFormatException; import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.util.Log; @@ -30,10 +24,6 @@ public abstract class SOCKSServer { protected String connHostName = null; protected int connPort = 0; - I2PSocket destSocket = null; - - Object FIXME = new Object(); - /** * Perform server initialization (expecially regarding protected * variables). @@ -59,47 +49,6 @@ public abstract class SOCKSServer { * * @return an I2PSocket connected with the destination */ - public I2PSocket getDestinationI2PSocket() throws SOCKSException { - setupServer(); - - if (connHostName == null) { - _log.error("BUG: destination host name has not been initialized!"); - throw new SOCKSException("BUG! See the logs!"); - } - if (connPort == 0) { - _log.error("BUG: destination port has not been initialized!"); - throw new SOCKSException("BUG! See the logs!"); - } - - // FIXME: here we should read our config file, select an - // outproxy, and instantiate the proper socket class that - // handles the outproxy itself (SOCKS4a, SOCKS5, HTTP CONNECT...). - I2PSocket destSock; - - try { - if (connHostName.toLowerCase().endsWith(".i2p")) { - _log.debug("connecting to " + connHostName + "..."); - I2PSocketManager sm = I2PSocketManagerFactory.createManager(); - destSock = sm.connect(I2PTunnel.destFromName(connHostName), null); - confirmConnection(); - _log.debug("connection confirmed - exchanging data..."); - } else { - _log.error("We don't support outproxies (yet)"); - throw new SOCKSException("Ouproxies not supported (yet)"); - } - } catch (DataFormatException e) { - throw new SOCKSException("Error in destination format"); - } catch (SocketException e) { - throw new SOCKSException("Error connecting (" - + e.getMessage() + ")"); - } catch (IOException e) { - throw new SOCKSException("Error connecting (" - + e.getMessage() + ")"); - } catch (I2PException e) { - throw new SOCKSException("Error connecting (" - + e.getMessage() + ")"); - } + public abstract I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException; - return destSock; - } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java index 357149652e54fc3b0936727347d5ba0fec5133a4..67a52d6889e9dd6f3aa5a8cdcc99b6e5302cccd9 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java @@ -7,6 +7,7 @@ package net.i2p.i2ptunnel.socks; import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; @@ -18,6 +19,15 @@ import net.i2p.util.Log; public class SOCKSServerFactory { private final static Log _log = new Log(SOCKSServerFactory.class); + private final static String ERR_REQUEST_DENIED = + "HTTP/1.1 403 Access Denied\r\n" + + "Content-Type: text/html; charset=iso-8859-1\r\n" + + "Cache-control: no-cache\r\n" + + "\r\n" + + "<html><body><H1>I2P SOCKS PROXY ERROR: REQUEST DENIED</H1>" + + "Your browser is misconfigured. This is a SOCKS proxy, not a HTTP proxy" + + "</body></html>"; + /** * Create a new SOCKS server, using the provided socket (that must * be connected to a client) to select the proper SOCKS protocol @@ -38,9 +48,15 @@ public class SOCKSServerFactory { // SOCKS version 5 serv = new SOCKS5Server(s); break; + case 'C': + case 'G': + case 'H': + case 'P': + DataOutputStream out = new DataOutputStream(s.getOutputStream()); + out.write(ERR_REQUEST_DENIED.getBytes()); + throw new SOCKSException("HTTP request to socks"); default: - _log.debug("SOCKS protocol version not supported (" + Integer.toHexString(socksVer) + ")"); - return null; + throw new SOCKSException("SOCKS protocol version not supported (" + Integer.toHexString(socksVer) + ")"); } } catch (IOException e) { _log.debug("error reading SOCKS protocol version"); @@ -49,4 +65,4 @@ public class SOCKSServerFactory { return serv; } -} \ No newline at end of file +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index 90e5f7e201086c5f0c612f480561b8dfd612c559..627024b67167e98ce1151ac41c89b9258aa02b40 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -8,9 +8,12 @@ package net.i2p.i2ptunnel.web; * */ +import java.util.Arrays; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Properties; +import java.util.Set; import java.util.StringTokenizer; import net.i2p.i2ptunnel.TunnelController; @@ -28,9 +31,7 @@ public class EditBean extends IndexBean { if (controllers.size() > tunnel) { TunnelController cur = (TunnelController)controllers.get(tunnel); if (cur == null) return false; - return ( ("client".equals(cur.getType())) || - ("httpclient".equals(cur.getType()))|| - ("ircclient".equals(cur.getType()))); + return isClient(cur.getType()); } else { return false; } @@ -38,7 +39,7 @@ public class EditBean extends IndexBean { public String getTargetHost(int tunnel) { TunnelController tun = getController(tunnel); - if (tun != null) + if (tun != null && tun.getTargetHost() != null) return tun.getTargetHost(); else return "127.0.0.1"; @@ -52,7 +53,7 @@ public class EditBean extends IndexBean { } public String getSpoofedHost(int tunnel) { TunnelController tun = getController(tunnel); - if (tun != null) + if (tun != null && tun.getSpoofedHost() != null) return tun.getSpoofedHost(); else return ""; @@ -82,119 +83,100 @@ public class EditBean extends IndexBean { } public boolean shouldDelay(int tunnel) { - TunnelController tun = getController(tunnel); - if (tun != null) { - Properties opts = getOptions(tun); - if (opts != null) { - String delay = opts.getProperty("i2p.streaming.connectDelay"); - if ( (delay == null) || ("0".equals(delay)) ) - return false; - else - return true; - } else { - return false; - } - } else { - return false; - } + return getProperty(tunnel, "i2p.streaming.connectDelay", 0) > 0; } public boolean isInteractive(int tunnel) { - TunnelController tun = getController(tunnel); - if (tun != null) { - Properties opts = getOptions(tun); - if (opts != null) { - String wsiz = opts.getProperty("i2p.streaming.maxWindowSize"); - if ( (wsiz == null) || (!"1".equals(wsiz)) ) - return false; - else - return true; - } else { - return false; - } - } else { - return false; - } + return getProperty(tunnel, "i2p.streaming.maxWindowSize", 128) == 12; } public int getTunnelDepth(int tunnel, int defaultLength) { - TunnelController tun = getController(tunnel); - if (tun != null) { - Properties opts = getOptions(tun); - if (opts != null) { - String len = opts.getProperty("inbound.length"); - if (len == null) return defaultLength; - try { - return Integer.parseInt(len); - } catch (NumberFormatException nfe) { - return defaultLength; - } - } else { - return defaultLength; - } - } else { - return defaultLength; - } + return getProperty(tunnel, "inbound.length", defaultLength); } public int getTunnelQuantity(int tunnel, int defaultQuantity) { - TunnelController tun = getController(tunnel); - if (tun != null) { - Properties opts = getOptions(tun); - if (opts != null) { - String len = opts.getProperty("inbound.quantity"); - if (len == null) return defaultQuantity; - try { - return Integer.parseInt(len); - } catch (NumberFormatException nfe) { - return defaultQuantity; - } - } else { - return defaultQuantity; - } - } else { - return defaultQuantity; - } + return getProperty(tunnel, "inbound.quantity", defaultQuantity); } public int getTunnelBackupQuantity(int tunnel, int defaultBackupQuantity) { + return getProperty(tunnel, "inbound.backupQuantity", defaultBackupQuantity); + } + + public int getTunnelVariance(int tunnel, int defaultVariance) { + return getProperty(tunnel, "inbound.lengthVariance", defaultVariance); + } + + public boolean getReduce(int tunnel) { + return false; + } + + public int getReduceCount(int tunnel) { + return getProperty(tunnel, "inbound.reduceQuantity", 1); + } + + public int getReduceTime(int tunnel) { + return getProperty(tunnel, "reduceIdleTime", 20); + } + + public int getCert(int tunnel) { + return 0; + } + + public int getEffort(int tunnel) { + return 23; + } + + public String getSigner(int tunnel) { + return ""; + } + + public boolean getEncrypt(int tunnel) { + return false; + } + + public String getEncryptKey(int tunnel) { + return getProperty(tunnel, "encryptKey", ""); + } + + public boolean getAccess(int tunnel) { + return false; + } + + public String getAccessList(int tunnel) { + return getProperty(tunnel, "accessList", ""); + } + + public boolean getClose(int tunnel) { + return false; + } + + public boolean getNewDest(int tunnel) { + return false; + } + + private int getProperty(int tunnel, String prop, int def) { TunnelController tun = getController(tunnel); if (tun != null) { Properties opts = getOptions(tun); if (opts != null) { - String len = opts.getProperty("inbound.backupQuantity"); - if (len == null) return defaultBackupQuantity; + String s = opts.getProperty(prop); + if (s == null) return def; try { - return Integer.parseInt(len); - } catch (NumberFormatException nfe) { - return defaultBackupQuantity; - } - } else { - return defaultBackupQuantity; + return Integer.parseInt(s); + } catch (NumberFormatException nfe) {} } - } else { - return defaultBackupQuantity; } + return def; } - - public int getTunnelVariance(int tunnel, int defaultVariance) { + + private String getProperty(int tunnel, String prop, String def) { TunnelController tun = getController(tunnel); if (tun != null) { Properties opts = getOptions(tun); - if (opts != null) { - String len = opts.getProperty("inbound.lengthVariance"); - if (len == null) return defaultVariance; - try { - return Integer.parseInt(len); - } catch (NumberFormatException nfe) { - return defaultVariance; - } - } else { - return defaultVariance; - } - } else { - return defaultVariance; + if (opts != null) + return opts.getProperty(prop, def); } + return def; } public String getI2CPHost(int tunnel) { @@ -213,6 +195,14 @@ public class EditBean extends IndexBean { return "7654"; } + private static final String noShowProps[] = { + "inbound.length", "outbound.length", "inbound.lengthVariance", "outbound.lengthVariance", + "inbound.backupQuantity", "outbound.backupQuantity", "inbound.quantity", "outbound.quantity", + "inbound.nickname", "outbound.nickname", "i2p.streaming.connectDelay", "i2p.streaming.maxWindowSize" + }; + private static final Set noShowSet = new HashSet(noShowProps.length); + static { noShowSet.addAll(Arrays.asList(noShowProps)); } + public String getCustomOptions(int tunnel) { TunnelController tun = getController(tunnel); if (tun != null) { @@ -222,19 +212,9 @@ public class EditBean extends IndexBean { int i = 0; for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) { String key = (String)iter.next(); + if (noShowSet.contains(key)) + continue; String val = opts.getProperty(key); - if ("inbound.length".equals(key)) continue; - if ("outbound.length".equals(key)) continue; - if ("inbound.lengthVariance".equals(key)) continue; - if ("outbound.lengthVariance".equals(key)) continue; - if ("inbound.backupQuantity".equals(key)) continue; - if ("outbound.backupQuantity".equals(key)) continue; - if ("inbound.quantity".equals(key)) continue; - if ("outbound.quantity".equals(key)) continue; - if ("inbound.nickname".equals(key)) continue; - if ("outbound.nickname".equals(key)) continue; - if ("i2p.streaming.connectDelay".equals(key)) continue; - if ("i2p.streaming.maxWindowSize".equals(key)) continue; if (i != 0) buf.append(' '); buf.append(key).append('=').append(val); i++; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index 8c361195add42d262599b83dd731901b95c13538..1500150e3e6e4b6882f714f2b50b05072e71dc78 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -209,10 +209,7 @@ public class IndexBean { } // Only modify other shared tunnels // if the current tunnel is shared, and of supported type - if ("true".equalsIgnoreCase(cur.getSharedClient()) && - ("ircclient".equals(cur.getType()) || - "httpclient".equals(cur.getType()) || - "client".equals(cur.getType()))) { + if ("true".equalsIgnoreCase(cur.getSharedClient()) && isClient(cur.getType())) { // all clients use the same I2CP session, and as such, use the same I2CP options List controllers = _group.getControllers(); @@ -224,11 +221,7 @@ public class IndexBean { // Only modify this non-current tunnel // if it belongs to a shared destination, and is of supported type - if ("true".equalsIgnoreCase(c.getSharedClient()) && - ("httpclient".equals(c.getType()) || - "ircclient".equals(c.getType()) || - "client".equals(c.getType()))) { - + if ("true".equalsIgnoreCase(c.getSharedClient()) && isClient(c.getType())) { Properties cOpt = c.getConfig(""); if (_tunnelQuantity != null) { cOpt.setProperty("option.inbound.quantity", _tunnelQuantity); @@ -326,9 +319,14 @@ public class IndexBean { public boolean isClient(int tunnelNum) { TunnelController cur = getController(tunnelNum); if (cur == null) return false; - return ( ("client".equals(cur.getType())) || - ("httpclient".equals(cur.getType())) || - ("ircclient".equals(cur.getType()))); + return isClient(cur.getType()); + } + + public static boolean isClient(String type) { + return ( ("client".equals(type)) || + ("httpclient".equals(type)) || + ("sockstunnel".equals(type)) || + ("ircclient".equals(type))); } public String getTunnelName(int tunnel) { @@ -361,6 +359,7 @@ public class IndexBean { else if ("ircclient".equals(internalType)) return "IRC client"; else if ("server".equals(internalType)) return "Standard server"; else if ("httpserver".equals(internalType)) return "HTTP server"; + else if ("sockstunnel".equals(internalType)) return "SOCKS proxy"; else return internalType; } @@ -579,77 +578,40 @@ public class IndexBean { Properties config = new Properties(); updateConfigGeneric(config); - if ("httpclient".equals(_type)) { + if (isClient(_type)) { + // generic client stuff if (_port != null) config.setProperty("listenPort", _port); if (_reachableByOther != null) config.setProperty("interface", _reachableByOther); else config.setProperty("interface", _reachableBy); - if (_proxyList != null) - config.setProperty("proxyList", _proxyList); - - config.setProperty("option.inbound.nickname", CLIENT_NICKNAME); - config.setProperty("option.outbound.nickname", CLIENT_NICKNAME); + config.setProperty("option.inbound.nickname", CLIENT_NICKNAME); + config.setProperty("option.outbound.nickname", CLIENT_NICKNAME); if (_name != null && !_sharedClient) { config.setProperty("option.inbound.nickname", _name); config.setProperty("option.outbound.nickname", _name); } - config.setProperty("sharedClient", _sharedClient + ""); - }else if ("ircclient".equals(_type)) { - if (_port != null) - config.setProperty("listenPort", _port); - if (_reachableByOther != null) - config.setProperty("interface", _reachableByOther); - else - config.setProperty("interface", _reachableBy); - if (_targetDestination != null) - config.setProperty("targetDestination", _targetDestination); - - config.setProperty("option.inbound.nickname", CLIENT_NICKNAME); - config.setProperty("option.outbound.nickname", CLIENT_NICKNAME); - if (_name != null && !_sharedClient) { - config.setProperty("option.inbound.nickname", _name); - config.setProperty("option.outbound.nickname", _name); - } - - config.setProperty("sharedClient", _sharedClient + ""); - } else if ("client".equals(_type)) { - if (_port != null) - config.setProperty("listenPort", _port); - if (_reachableByOther != null) - config.setProperty("interface", _reachableByOther); - else - config.setProperty("interface", _reachableBy); - if (_targetDestination != null) - config.setProperty("targetDestination", _targetDestination); - - config.setProperty("option.inbound.nickname", CLIENT_NICKNAME); - config.setProperty("option.outbound.nickname", CLIENT_NICKNAME); - if (_name != null && !_sharedClient) { - config.setProperty("option.inbound.nickname", _name); - config.setProperty("option.outbound.nickname", _name); - } - config.setProperty("sharedClient", _sharedClient + ""); - } else if ("server".equals(_type)) { + } else { + // generic server stuff if (_targetHost != null) config.setProperty("targetHost", _targetHost); if (_targetPort != null) config.setProperty("targetPort", _targetPort); if (_privKeyFile != null) config.setProperty("privKeyFile", _privKeyFile); + } + + if ("httpclient".equals(_type)) { + if (_proxyList != null) + config.setProperty("proxyList", _proxyList); + } else if ("ircclient".equals(_type) || "client".equals(_type)) { + if (_targetDestination != null) + config.setProperty("targetDestination", _targetDestination); } else if ("httpserver".equals(_type)) { - if (_targetHost != null) - config.setProperty("targetHost", _targetHost); - if (_targetPort != null) - config.setProperty("targetPort", _targetPort); - if (_privKeyFile != null) - config.setProperty("privKeyFile", _privKeyFile); if (_spoofedHost != null) config.setProperty("spoofedHost", _spoofedHost); - } else { - return null; } return config; diff --git a/apps/i2ptunnel/jsp/edit.jsp b/apps/i2ptunnel/jsp/edit.jsp index 931629fb1f325479ccc820c17987de59757cbe3c..67fdf016c37daf170f4b92db3851cc5df898dd8d 100644 --- a/apps/i2ptunnel/jsp/edit.jsp +++ b/apps/i2ptunnel/jsp/edit.jsp @@ -14,7 +14,7 @@ String tun = request.getParameter("tunnel"); } else { String type = request.getParameter("type"); int curTunnel = -1; - if ("client".equals(type) || "httpclient".equals(type) || "ircclient".equals(type)) { + if (EditBean.isClient(type)) { %><jsp:include page="editClient.jsp" /><% } else if ("server".equals(type) || "httpserver".equals(type)) { %><jsp:include page="editServer.jsp" /><% @@ -22,4 +22,4 @@ String tun = request.getParameter("tunnel"); %>Invalid tunnel type<% } } -%> \ No newline at end of file +%> diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index 97f8adbe5ec9d3e2a24802e5cba4cee7fb135876..f7ee2294c73726ad36296251b225c7c1061fb89e 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -116,14 +116,14 @@ <hr /> </div> - <% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { + <% if ("httpclient".equals(tunnelType)) { %><div id="destinationField" class="rowItem"> <label for="proxyList" accesskey="x"> Outpro<span class="accessKey">x</span>ies: </label> <input type="text" size="30" id="proxyList" name="proxyList" title="List of Outproxy I2P destinations" value="<%=editBean.getClientDestination(curTunnel)%>" class="freetext" /> </div> - <% } else { + <% } else if ("client".equals(tunnelType) || "ircclient".equals(tunnelType)) { %><div id="destinationField" class="rowItem"> <label for="targetDestination" accesskey="T"> <span class="accessKey">T</span>unnel Destination: @@ -205,12 +205,12 @@ <span class="accessKey">V</span>ariance: </label> <select id="tunnelVariance" name="tunnelVariance" title="Level of Randomization for Tunnel Depth" class="selectbox"> - <% int tunnelVariance = editBean.getTunnelVariance(curTunnel, -1); + <% int tunnelVariance = editBean.getTunnelVariance(curTunnel, 0); %><option value="0"<%=(tunnelVariance == 0 ? " selected=\"selected\"" : "") %>>0 hop variance (no randomisation, consistant performance)</option> - <option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option> - <option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (high randomisation, variable performance)</option> <option value="1"<%=(tunnelVariance == 1 ? " selected=\"selected\"" : "") %>>+ 0-1 hop variance (medium additive randomisation, subtractive performance)</option> <option value="2"<%=(tunnelVariance == 2 ? " selected=\"selected\"" : "") %>>+ 0-2 hop variance (high additive randomisation, subtractive performance)</option> + <option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option> + <option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (not recommended)</option> <% if (tunnelVariance > 2 || tunnelVariance < -2) { %> <option value="<%=tunnelVariance%>" selected="selected"><%= (tunnelVariance > 2 ? "+ " : "+/- ") %>0-<%=tunnelVariance%> hop variance</option> <% } @@ -265,6 +265,62 @@ </label> <input type="text" id="clientPort" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" /> </div> + + <div class="subdivider"> + <hr /> + </div> + + <div id="optionsField" class="rowItem"> + <label for="reduce" accesskey="c"> + <span class="accessKey">C</span>lose tunnels when idle: + </label> + </div> + <div id="portField" class="rowItem"> + <label for="access" accesskey="c"> + Enable: + </label> + <input value="1" type="checkbox" id="startOnLoad" name="close" title="Close Tunnels"<%=(editBean.getClose(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> + </div> + <div id="portField" class="rowItem"> + <label for="access" accesskey="c"> + Generate New Destination Keys On Reopen: + </label> + <input value="1" type="checkbox" id="startOnLoad" name="newDest" title="New Destination"<%=(editBean.getNewDest(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> + </div> + <div id="portField" class="rowItem"> + <label for="reduceTime" accesskey="c"> + Reduce when idle (minutes): + </label> + <input type="text" id="port" name="reduceTime" size="4" maxlength="4" title="Reduced Tunnel Idle Time" value="<%=editBean.getReduceTime(curTunnel)%>" class="freetext" /> + </div> + + <div class="subdivider"> + <hr /> + </div> + + <div id="optionsField" class="rowItem"> + <label for="reduce" accesskey="d"> + Re<span class="accessKey">d</span>uce tunnel quantity when idle: + </label> + </div> + <div id="portField" class="rowItem"> + <label for="access" accesskey="d"> + Enable: + </label> + <input value="1" type="checkbox" id="startOnLoad" name="reduce" title="Reduce Tunnels"<%=(editBean.getReduce(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> + </div> + <div id="portField" class="rowItem"> + <label for="reduceCount" accesskey="d"> + Reduced tunnel count: + </label> + <input type="text" id="port" name="reduceCount" size="1" maxlength="1" title="Reduced Tunnel Count" value="<%=editBean.getReduceCount(curTunnel)%>" class="freetext" /> + </div> + <div id="portField" class="rowItem"> + <label for="reduceTime" accesskey="d"> + Reduce when idle (minutes): + </label> + <input type="text" id="port" name="reduceTime" size="4" maxlength="4" title="Reduced Tunnel Idle Time" value="<%=editBean.getReduceTime(curTunnel)%>" class="freetext" /> + </div> <div class="subdivider"> <hr /> @@ -284,8 +340,10 @@ <div class="header"></div> <div class="footer"> <div class="toolbox"> + <span class="comment">NOTE: If tunnel is currently running, most changes will not take effect until tunnel is stopped and restarted</span> <input type="hidden" value="true" name="removeConfirm" /> - <button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button><button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button> + <button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button> + <button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button> </div> </div> </div> diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index 0cdf5e0e9ec57bd2d8756fe41305f0b7aecd5858..e6cc0497d7d3219331a443260e4c12d301cf29b1 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -110,7 +110,8 @@ <label for="spoofedHost" accesskey="W"> <span class="accessKey">W</span>ebsite name: </label> - <input type="text" size="20" id="spoofedHost" name="spoofedHost" title="Website Host Name" value="<%=editBean.getSpoofedHost(curTunnel)%>" class="freetext" /> + <input type="text" size="20" id="targetHost" name="spoofedHost" title="Website Host Name" value="<%=editBean.getSpoofedHost(curTunnel)%>" class="freetext" /> + <span class="comment">(leave blank for outproxies)</span> </div> <% } %><div id="privKeyField" class="rowItem"> @@ -177,12 +178,12 @@ <span class="accessKey">V</span>ariance: </label> <select id="tunnelVariance" name="tunnelVariance" title="Level of Randomization for Tunnel Depth" class="selectbox"> - <% int tunnelVariance = editBean.getTunnelVariance(curTunnel, -1); + <% int tunnelVariance = editBean.getTunnelVariance(curTunnel, 0); %><option value="0"<%=(tunnelVariance == 0 ? " selected=\"selected\"" : "") %>>0 hop variance (no randomisation, consistant performance)</option> - <option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option> - <option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (high randomisation, variable performance)</option> <option value="1"<%=(tunnelVariance == 1 ? " selected=\"selected\"" : "") %>>+ 0-1 hop variance (medium additive randomisation, subtractive performance)</option> <option value="2"<%=(tunnelVariance == 2 ? " selected=\"selected\"" : "") %>>+ 0-2 hop variance (high additive randomisation, subtractive performance)</option> + <option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option> + <option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (not recommended)</option> <% if (tunnelVariance > 2 || tunnelVariance < -2) { %> <option value="<%=tunnelVariance%>" selected="selected"><%= (tunnelVariance > 2 ? "+ " : "+/- ") %>0-<%=tunnelVariance%> hop variance</option> <% } @@ -242,6 +243,130 @@ <hr /> </div> + <div id="optionsField" class="rowItem"> + <label for="encrypt" accesskey="e"> + <span class="accessKey">E</span>ncrypt Leaseset: + </label> + </div> + <div id="portField" class="rowItem"> + <label for="encrypt" accesskey="e"> + Enable: + </label> + <input value="1" type="checkbox" id="startOnLoad" name="encrypt" title="Encrypt LeaseSet"<%=(editBean.getEncrypt(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> + </div> + <div id="hostField" class="rowItem"> + <label for="encrypt" accesskey="e"> + Leaseset Encryption Key: + </label> + <input type="text" id="hostField" name="encryptKey" size="60" title="Encrypt Key" value="<%=editBean.getEncryptKey(curTunnel)%>" class="freetext" /> + <span class="comment">(Users will require this key)</span> + </div> + + <div class="subdivider"> + <hr /> + </div> + + <div id="optionsField" class="rowItem"> + <label for="access" accesskey="s"> + Restricted Acce<span class="accessKey">s</span>s List: + </label> + </div> + <div id="portField" class="rowItem"> + <label for="access" accesskey="s"> + Enable: + </label> + <input value="1" type="checkbox" id="startOnLoad" name="access" title="Enable Access List"<%=(editBean.getAccess(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> + </div> + <div id="hostField" class="rowItem"> + <label for="accessList" accesskey="s"> + Access List: + </label> + <textarea rows="2" cols="60" id="hostField" title="Access List" wrap="off"><%=editBean.getAccessList(curTunnel)%></textarea> + <span class="comment">(Restrict to these clients only)</span> + </div> + + <div class="subdivider"> + <hr /> + </div> + + <div id="optionsField" class="rowItem"> + <label for="reduce" accesskey="d"> + Re<span class="accessKey">d</span>uce tunnel quantity when idle: + </label> + </div> + <div id="portField" class="rowItem"> + <label for="access" accesskey="d"> + Enable: + </label> + <input value="1" type="checkbox" id="startOnLoad" name="reduce" title="Reduce Tunnels"<%=(editBean.getReduce(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" /> + </div> + <div id="portField" class="rowItem"> + <label for="reduceCount" accesskey="d"> + Reduced tunnel count: + </label> + <input type="text" id="port" name="reduceCount" size="1" maxlength="1" title="Reduced Tunnel Count" value="<%=editBean.getReduceCount(curTunnel)%>" class="freetext" /> + </div> + <div id="portField" class="rowItem"> + <label for="reduceTime" accesskey="d"> + Reduce when idle (minutes): + </label> + <input type="text" id="port" name="reduceTime" size="4" maxlength="4" title="Reduced Tunnel Idle Time" value="<%=editBean.getReduceTime(curTunnel)%>" class="freetext" /> + </div> + + <div class="subdivider"> + <hr /> + </div> + + <div id="tunnelOptionsField" class="rowItem"> + <label for="cert" accesskey="c"> + <span class="accessKey">C</span>ertificate type: + </label> + </div> + <div id="hostField" class="rowItem"> + <div id="portField" class="rowItem"> + <label>None</label> + <input value="0" type="radio" id="startOnLoad" name="cert" title="No Certificate"<%=(editBean.getCert(curTunnel)==0 ? " checked=\"checked\"" : "")%> class="tickbox" /> + <span class="comment"></span> + </div> + <div id="portField" class="rowItem"> + <label>Hashcash (effort)</label> + <input value="1" type="radio" id="startOnLoad" name="cert" title="Hashcash Certificate"<%=(editBean.getCert(curTunnel)==1 ? " checked=\"checked\"" : "")%> class="tickbox" /> + <input type="text" id="port" name="effort" size="2" title="Hashcash Effort" value="<%=editBean.getEffort(curTunnel)%>" class="freetext" /> + </div> + </div> + <div id="portField" class="rowItem"> + <label for="force" accesskey="c"> + Estimate Hashcash Calc Time: + </label> + <button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Estimate Calculation Time" title="Estimate Calculation Time">Estimate</button> + </div> + <div id="hostField" class="rowItem"> + <div id="portField" class="rowItem"> + <label>Hidden</label> + <input value="2" type="radio" id="startOnLoad" name="cert" title="Hidden Certificate"<%=(editBean.getCert(curTunnel)==2 ? " checked=\"checked\"" : "")%> class="tickbox" /> + <span class="comment"></span> + </div> + <div id="portField" class="rowItem"> + <label for="signer" accesskey="c"> + Signed (signed by): + </label> + <input value="3" type="radio" id="startOnLoad" name="cert" title="Signed Certificate"<%=(editBean.getCert(curTunnel)==3 ? " checked=\"checked\"" : "")%> class="tickbox" /> + <input type="text" id="port" name="signer" size="50" title="Cert Signer" value="<%=editBean.getSigner(curTunnel)%>" class="freetext" /> + <span class="comment"></span> + </div> + </div> + <div id="portField" class="rowItem"> + <label for="force" accesskey="c"> + Modify Certificate: + </label> + <button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Modify Cert Now" title="Force New Cert Now">Modify</button> + <span class="comment">(Tunnel must be stopped first)</span> + </div> + + <div class="subdivider"> + <hr /> + </div> + <div id="customOptionsField" class="rowItem"> <label for="customOptions" accesskey="u"> C<span class="accessKey">u</span>stom options: @@ -256,8 +381,10 @@ <div class="header"></div> <div class="footer"> <div class="toolbox"> + <span class="comment">NOTE: If tunnel is currently running, most changes will not take effect until tunnel is stopped and restarted</span> <input type="hidden" value="true" name="removeConfirm" /> - <button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button><button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button> + <button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button> + <button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button> </div> </div> </div> diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp index 9fc2d2a9677a116be38433e2f4292751b3c4a398..0825493073c731d37d6b568fc6609e9258f21986 100644 --- a/apps/i2ptunnel/jsp/index.jsp +++ b/apps/i2ptunnel/jsp/index.jsp @@ -112,10 +112,12 @@ } %></div> + <% if (!"sockstunnel".equals(indexBean.getInternalType(curClient))) { %> <div class="destinationField rowItem"> <label>Destination:</label> <input class="freetext" size="40" readonly="readonly" value="<%=indexBean.getClientDestination(curClient)%>" /> </div> + <% } %> <div class="descriptionField rowItem"> <label>Description:</label> @@ -140,6 +142,7 @@ <option value="client">Standard</option> <option value="httpclient">HTTP</option> <option value="ircclient">IRC</option> + <option value="sockstunnel">SOCKS</option> </select> <input class="control" type="submit" value="Create" /> </div> diff --git a/apps/jetty/build.xml b/apps/jetty/build.xml index aaa97835493bbb8039a16bb8de9d6450ea4ee949..643dd79fdcb77b1c908ef84be030b3b654224e60 100644 --- a/apps/jetty/build.xml +++ b/apps/jetty/build.xml @@ -78,7 +78,6 @@ <include name="jasper-runtime.jar" /> <include name="javax.servlet.jar" /> <include name="org.mortbay.jetty.jar" /> - <include name="xercesImpl.jar" /> </fileset> </copy> <delete dir="jetty-5.1.12" /> diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..09f0905bf3122812de9733e5bb42dfbb705a76ed --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHandler.java @@ -0,0 +1,55 @@ +package net.i2p.router.web; + +import net.i2p.I2PAppContext; +import net.i2p.data.DataFormatException; +import net.i2p.data.Destination; +import net.i2p.data.Hash; +import net.i2p.data.SessionKey; + +/** + * Support additions via B64 Destkey, B64 Desthash, or blahblah.i2p + */ +public class ConfigKeyringHandler extends FormHandler { + private String _peer; + private String _key; + + protected void processForm() { + if ("Add key".equals(_action)) { + if (_peer == null || _key == null) { + addFormError("You must enter a destination and a key"); + return; + } + Hash h = new Hash(); + try { + h.fromBase64(_peer); + } catch (DataFormatException dfe) {} + if (h.getData() == null) { + try { + Destination d = new Destination(); + d.fromBase64(_peer); + h = d.calculateHash(); + } catch (DataFormatException dfe) {} + } + if (h.getData() == null) { + Destination d = _context.namingService().lookup(_peer); + if (d != null) + h = d.calculateHash(); + } + SessionKey sk = new SessionKey(); + try { + sk.fromBase64(_key); + } catch (DataFormatException dfe) {} + if (h.getData() != null && sk.getData() != null) { + _context.keyRing().put(h, sk); + addFormNotice("Key for " + h.toBase64() + " added to keyring"); + } else { + addFormError("Invalid destination or key"); + } + } else { + addFormError("Unsupported"); + } + } + + public void setPeer(String peer) { _peer = peer; } + public void setKey(String peer) { _key = peer; } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..48bc15068e9767b0af4029d33d2a25d7b719bdec --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigKeyringHelper.java @@ -0,0 +1,36 @@ +package net.i2p.router.web; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; + +import net.i2p.router.RouterContext; + +public class ConfigKeyringHelper { + private RouterContext _context; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public ConfigKeyringHelper() {} + + public String getSummary() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024); + try { + _context.keyRing().renderStatusHTML(new OutputStreamWriter(baos)); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + return new String(baos.toByteArray()); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java index 41f0ad90c578a73f11976116e10dabc1f0d52493..114579a27c854690343ae8ba66bdb42e0552bc81 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java @@ -11,6 +11,7 @@ public class NetDbHelper { private RouterContext _context; private Writer _out; private String _routerPrefix; + private boolean _full = false; /** * Configure this bean to query a particular router context @@ -30,6 +31,7 @@ public class NetDbHelper { public void setWriter(Writer writer) { _out = writer; } public void setRouter(String r) { _routerPrefix = r; } + public void setFull(String f) { _full = "1".equals(f); }; public String getNetDbSummary() { try { @@ -37,14 +39,14 @@ public class NetDbHelper { if (_routerPrefix != null) _context.netDb().renderRouterInfoHTML(_out, _routerPrefix); else - _context.netDb().renderStatusHTML(_out); + _context.netDb().renderStatusHTML(_out, _full); return ""; } else { ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024); if (_routerPrefix != null) _context.netDb().renderRouterInfoHTML(new OutputStreamWriter(baos), _routerPrefix); else - _context.netDb().renderStatusHTML(new OutputStreamWriter(baos)); + _context.netDb().renderStatusHTML(new OutputStreamWriter(baos), _full); return new String(baos.toByteArray()); } } catch (IOException ioe) { diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java index 2b445a0042361205218e0ae6042b5cca1de272d6..ad8e7135d5496dcd93fa768ea6457cae71f96baa 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java @@ -244,7 +244,7 @@ public class SummaryHelper { */ public String getInboundSecondKBps() { if (_context == null) - return "0.0"; + return "0"; double kbps = _context.bandwidthLimiter().getReceiveBps()/1024d; DecimalFormat fmt = new DecimalFormat("##0.00"); return fmt.format(kbps); @@ -256,7 +256,7 @@ public class SummaryHelper { */ public String getOutboundSecondKBps() { if (_context == null) - return "0.0"; + return "0"; double kbps = _context.bandwidthLimiter().getSendBps()/1024d; DecimalFormat fmt = new DecimalFormat("##0.00"); return fmt.format(kbps); @@ -269,10 +269,10 @@ public class SummaryHelper { */ public String getInboundFiveMinuteKBps() { if (_context == null) - return "0.0"; + return "0"; RateStat receiveRate = _context.statManager().getRate("bw.recvRate"); - if (receiveRate == null) return "0.0"; + if (receiveRate == null) return "0"; Rate rate = receiveRate.getRate(5*60*1000); double kbps = rate.getAverageValue()/1024; DecimalFormat fmt = new DecimalFormat("##0.00"); @@ -286,10 +286,10 @@ public class SummaryHelper { */ public String getOutboundFiveMinuteKBps() { if (_context == null) - return "0.0"; + return "0"; RateStat receiveRate = _context.statManager().getRate("bw.sendRate"); - if (receiveRate == null) return "0.0"; + if (receiveRate == null) return "0"; Rate rate = receiveRate.getRate(5*60*1000); double kbps = rate.getAverageValue()/1024; DecimalFormat fmt = new DecimalFormat("##0.00"); @@ -303,10 +303,10 @@ public class SummaryHelper { */ public String getInboundLifetimeKBps() { if (_context == null) - return "0.0"; + return "0"; RateStat receiveRate = _context.statManager().getRate("bw.recvRate"); - if (receiveRate == null) return "0.0"; + if (receiveRate == null) return "0"; double kbps = receiveRate.getLifetimeAverageValue()/1024; DecimalFormat fmt = new DecimalFormat("##0.00"); return fmt.format(kbps); @@ -319,10 +319,10 @@ public class SummaryHelper { */ public String getOutboundLifetimeKBps() { if (_context == null) - return "0.0"; + return "0"; RateStat sendRate = _context.statManager().getRate("bw.sendRate"); - if (sendRate == null) return "0.0"; + if (sendRate == null) return "0"; double kbps = sendRate.getLifetimeAverageValue()/1024; DecimalFormat fmt = new DecimalFormat("##0.00"); return fmt.format(kbps); @@ -335,11 +335,11 @@ public class SummaryHelper { */ public String getInboundTransferred() { if (_context == null) - return "0.0"; + return "0"; long received = _context.bandwidthLimiter().getTotalAllocatedInboundBytes(); - return getTransferred(received); + return DataHelper.formatSize(received) + 'B'; } /** @@ -349,40 +349,10 @@ public class SummaryHelper { */ public String getOutboundTransferred() { if (_context == null) - return "0.0"; + return "0"; long sent = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes(); - return getTransferred(sent); - } - - private static String getTransferred(long bytes) { - double val = bytes; - int scale = 0; - if (bytes > 1024*1024*1024) { - // gigs transferred - scale = 3; - val /= (double)(1024*1024*1024); - } else if (bytes > 1024*1024) { - // megs transferred - scale = 2; - val /= (double)(1024*1024); - } else if (bytes > 1024) { - // kbytes transferred - scale = 1; - val /= (double)1024; - } else { - scale = 0; - } - - DecimalFormat fmt = new DecimalFormat("##0.00"); - - String str = fmt.format(val); - switch (scale) { - case 1: return str + "KB"; - case 2: return str + "MB"; - case 3: return str + "GB"; - default: return bytes + "bytes"; - } + return DataHelper.formatSize(sent) + 'B'; } /** diff --git a/apps/routerconsole/jsp/configkeyring.jsp b/apps/routerconsole/jsp/configkeyring.jsp new file mode 100644 index 0000000000000000000000000000000000000000..7dd8bf1784bef7252ad2cad1bfd5c46374cd2c36 --- /dev/null +++ b/apps/routerconsole/jsp/configkeyring.jsp @@ -0,0 +1,58 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> + +<html><head> +<title>I2P Router Console - config keyring</title> +<link rel="stylesheet" href="default.css" type="text/css" /> +</head><body> + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> + +<div class="main" id="main"> + <%@include file="confignav.jsp" %> + + <jsp:useBean class="net.i2p.router.web.ConfigKeyringHandler" id="formhandler" scope="request" /> + <jsp:setProperty name="formhandler" property="*" /> + <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> + <font color="red"><jsp:getProperty name="formhandler" property="errors" /></font> + <i><jsp:getProperty name="formhandler" property="notices" /></i> + + + + <jsp:useBean class="net.i2p.router.web.ConfigKeyringHelper" id="keyringhelper" scope="request" /> + <jsp:setProperty name="keyringhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> + + <p> + <h2>Keyring</h2> + The router keyring is used to decrypt encrypted leaseSets. + The keyring may contain keys for local or remote encrypted destinations. + <p><jsp:getProperty name="keyringhelper" property="summary" /> + </p> + + <hr /> + + <form action="configkeyring.jsp" method="POST"> + <% String prev = System.getProperty("net.i2p.router.web.ConfigKeyringHandler.nonce"); + if (prev != null) System.setProperty("net.i2p.router.web.ConfigKeyringHandler.noncePrev", prev); + System.setProperty("net.i2p.router.web.ConfigKeyringHandler.nonce", new java.util.Random().nextLong()+""); %> + <input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigKeyringHandler.nonce")%>" /> + <h2>Manual Keyring Addition</h2> + Enter keys for encrypted remote destinations here. + Keys for local destinations must be entered on the <a href="i2ptunnel/index.jsp">I2PTunnel page</a>. + <p> + <table> + <tr><td>Dest. name, hash, or full key: + <td><textarea name="peer" cols="44" rows="1" wrap="off"></textarea> + <tr><td align="right">Session Key: + <td><input type="text" size="55" name="key" /> + <tr><td><td><input type="submit" name="action" value="Add key" /> + </table> + </form> + + +</div> + +</body> +</html> diff --git a/apps/routerconsole/jsp/confignav.jsp b/apps/routerconsole/jsp/confignav.jsp index b6a5ce6df3b5b317799bfa69dc7ef542a0f97bd9..851ab79b5038edf07ad0151458ced47c8b673ffd 100644 --- a/apps/routerconsole/jsp/confignav.jsp +++ b/apps/routerconsole/jsp/confignav.jsp @@ -10,6 +10,8 @@ %>Clients | <% } else { %><a href="configclients.jsp">Clients</a> | <% } if (request.getRequestURI().indexOf("configpeer.jsp") != -1) { %>Peers | <% } else { %><a href="configpeer.jsp">Peers</a> | <% } + if (request.getRequestURI().indexOf("configkeyring.jsp") != -1) { + %>Keyring | <% } else { %><a href="configkeyring.jsp">Keyring</a> | <% } if (request.getRequestURI().indexOf("configlogging.jsp") != -1) { %>Logging | <% } else { %><a href="configlogging.jsp">Logging</a> | <% } if (request.getRequestURI().indexOf("configstats.jsp") != -1) { diff --git a/apps/routerconsole/jsp/help.jsp b/apps/routerconsole/jsp/help.jsp index 8580f6e653907177c5bd0ddd1bb269951994b395..d53f93a885bffc821dd7bf1dbc3a30011a9f0a49 100644 --- a/apps/routerconsole/jsp/help.jsp +++ b/apps/routerconsole/jsp/help.jsp @@ -34,9 +34,8 @@ licenses and dependencies. This webpage is being served as part of the I2P rout client application, which is built off a trimmed down <a href="http://jetty.mortbay.com/jetty/index.html">Jetty</a> instance (trimmed down, as in, we do not include the demo apps or other add-ons, and we simplify configuration), allowing you to deploy standard JSP/Servlet web applications into your router. Jetty in turn makes use of -Apache's javax.servlet (javax.servlet.jar) implementation, as well as their xerces-j XML parser (xerces.jar). -Their XML parser requires the Sun XML APIs (JAXP) which is included in binary form (xml-apis.jar) as required -by their binary code license. This product includes software developed by the Apache Software Foundation +Apache's javax.servlet (javax.servlet.jar) implementation. +This product includes software developed by the Apache Software Foundation (http://www.apache.org/). </p> <p>Another application you can see on this webpage is <a href="http://www.i2p2.i2p/i2ptunnel">I2PTunnel</a> diff --git a/apps/routerconsole/jsp/netdb.jsp b/apps/routerconsole/jsp/netdb.jsp index 89c2bdec20d5a954e4511bdd07fb69af0f42e7dd..08a1377d37ff35a188da0076466dc0cc95e9b519 100644 --- a/apps/routerconsole/jsp/netdb.jsp +++ b/apps/routerconsole/jsp/netdb.jsp @@ -14,6 +14,7 @@ <jsp:useBean class="net.i2p.router.web.NetDbHelper" id="netdbHelper" scope="request" /> <jsp:setProperty name="netdbHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:setProperty name="netdbHelper" property="writer" value="<%=out%>" /> + <jsp:setProperty name="netdbHelper" property="full" value="<%=request.getParameter("f")%>" /> <jsp:setProperty name="netdbHelper" property="router" value="<%=request.getParameter("r")%>" /> <jsp:getProperty name="netdbHelper" property="netDbSummary" /> </div> diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java index 184e4a5457e9e7ac6c0185cba8d2c07727ea781b..431540d46c51086a2f988152dcc6aa871fd7bea6 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java @@ -45,6 +45,7 @@ public class Connection { private long _congestionWindowEnd; private long _highestAckedThrough; private boolean _isInbound; + private boolean _updatedShareOpts; /** Packet ID (Long) to PacketLocal for sent but unacked packets */ private Map _outboundPackets; private PacketQueue _outboundQueue; @@ -120,6 +121,7 @@ public class Connection { _activeResends = 0; _resetSentOn = -1; _isInbound = false; + _updatedShareOpts = false; _connectionEvent = new ConEvent(); _hardDisconnected = false; _context.statManager().createRateStat("stream.con.windowSizeAtCongestion", "How large was our send window when we send a dup?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); @@ -586,6 +588,8 @@ public class Connection { if (_remotePeerSet) throw new RuntimeException("Remote peer already set [" + _remotePeer + ", " + peer + "]"); _remotePeerSet = true; _remotePeer = peer; + // now that we know who the other end is, get the rtt etc. from the cache + _connectionManager.updateOptsFromShare(this); } private boolean _sendStreamIdSet = false; @@ -709,7 +713,13 @@ public class Connection { } public long getCloseReceivedOn() { return _closeReceivedOn; } public void setCloseReceivedOn(long when) { _closeReceivedOn = when; } - + + public void updateShareOpts() { + if (_closeSentOn > 0 && !_updatedShareOpts) { + _connectionManager.updateShareOpts(this); + _updatedShareOpts = true; + } + } public void incrementUnackedPacketsReceived() { _unackedPacketsReceived++; } public int getUnackedPacketsReceived() { return _unackedPacketsReceived; } /** how many packets have we sent but not yet received an ACK for? @@ -998,7 +1008,7 @@ public class Connection { /** * Coordinate the resends of a given packet */ - private class ResendPacketEvent implements SimpleTimer.TimedEvent { + public class ResendPacketEvent implements SimpleTimer.TimedEvent { private PacketLocal _packet; private long _nextSendTime; public ResendPacketEvent(PacketLocal packet, long sendTime) { @@ -1104,7 +1114,22 @@ public class Connection { _context.sessionKeyManager().failTags(_remotePeer.getPublicKey()); } - if (numSends - 1 <= _options.getMaxResends()) { + if (numSends - 1 > _options.getMaxResends()) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Too many resends"); + _packet.cancelled(); + disconnect(false); + } else { + //long timeout = _options.getResendDelay() << numSends; + long rto = _options.getRTO(); + if (rto < MIN_RESEND_DELAY) + rto = MIN_RESEND_DELAY; + long timeout = rto << (numSends-1); + if ( (timeout > MAX_RESEND_DELAY) || (timeout <= 0) ) + timeout = MAX_RESEND_DELAY; + // set this before enqueue() as it passes it on to the router + _nextSendTime = timeout + _context.clock().now(); + if (_log.shouldLog(Log.INFO)) _log.info("Resend packet " + _packet + " time " + numSends + " activeResends: " + _activeResends + @@ -1113,6 +1138,10 @@ public class Connection { + (_context.clock().now() - _packet.getCreatedOn()) + "ms)"); _outboundQueue.enqueue(_packet); _lastSendTime = _context.clock().now(); + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Scheduling resend in " + timeout + "ms for " + _packet); + RetransmissionTimer.getInstance().addEvent(ResendPacketEvent.this, timeout); } // acked during resending (... or somethin') @@ -1123,25 +1152,7 @@ public class Connection { } return true; } - - if (numSends - 1 > _options.getMaxResends()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Too many resends"); - _packet.cancelled(); - disconnect(false); - } else { - //long timeout = _options.getResendDelay() << numSends; - long rto = _options.getRTO(); - if (rto < MIN_RESEND_DELAY) - rto = MIN_RESEND_DELAY; - long timeout = rto << (numSends-1); - if ( (timeout > MAX_RESEND_DELAY) || (timeout <= 0) ) - timeout = MAX_RESEND_DELAY; - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Scheduling resend in " + timeout + "ms for " + _packet); - RetransmissionTimer.getInstance().addEvent(ResendPacketEvent.this, timeout); - _nextSendTime = timeout + _context.clock().now(); - } + return true; } else { //if (_log.shouldLog(Log.DEBUG)) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java index da2b1ab1279ef04016477ce75115edf3a8d17793..7826ba2a81c0d86d48ce235ddedaa96c07d51994 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java @@ -30,6 +30,7 @@ public class ConnectionManager { private PacketQueue _outboundQueue; private SchedulerChooser _schedulerChooser; private ConnectionPacketHandler _conPacketHandler; + private TCBShare _tcbShare; /** Inbound stream ID (Long) to Connection map */ private Map _connectionByInboundId; /** Ping ID (Long) to PingRequest */ @@ -52,6 +53,7 @@ public class ConnectionManager { _connectionHandler = new ConnectionHandler(context, this); _schedulerChooser = new SchedulerChooser(context); _conPacketHandler = new ConnectionPacketHandler(context); + _tcbShare = new TCBShare(context); _session = session; session.setSessionListener(_messageHandler); _outboundQueue = new PacketQueue(context, session, this); @@ -127,6 +129,7 @@ public class ConnectionManager { */ public Connection receiveConnection(Packet synPacket) { Connection con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler, new ConnectionOptions(_defaultOptions)); + _tcbShare.updateOptsFromShare(con); con.setInbound(); long receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1; boolean reject = false; @@ -277,6 +280,8 @@ public class ConnectionManager { public ConnectionHandler getConnectionHandler() { return _connectionHandler; } public I2PSession getSession() { return _session; } public PacketQueue getPacketQueue() { return _outboundQueue; } + public void updateOptsFromShare(Connection con) { _tcbShare.updateOptsFromShare(con); } + public void updateShareOpts(Connection con) { _tcbShare.updateShareOpts(con); } /** * Something b0rked hard, so kill all of our connections without mercy. @@ -292,6 +297,7 @@ public class ConnectionManager { _connectionByInboundId.clear(); _connectionLock.notifyAll(); } + _tcbShare.stop(); } /** diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java index 6a062d4a6c73f9e0daa04c002c690942c32806fd..7c445f0380140cb12199061d579da758cf260aa3 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java @@ -213,6 +213,10 @@ public class ConnectionPacketHandler { packet.releasePayload(); } + // update the TCB Cache now that we've processed the acks and updated our rtt etc. + if (isNew && packet.isFlagSet(Packet.FLAG_CLOSE) && packet.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED)) + con.updateShareOpts(); + //if (choke) // con.fastRetransmit(); } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java index 2d22226d377f96dafab664a2f7e7eddc81388c2b..a56e7753dd83060398bc4cff9faf192c084e857d 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java @@ -82,7 +82,16 @@ class PacketQueue { // this should not block! begin = _context.clock().now(); - sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent); + long expires = 0; + Connection.ResendPacketEvent rpe = (Connection.ResendPacketEvent) packet.getResendEvent(); + if (rpe != null) + // we want the router to expire it a little before we do, + // so if we retransmit it will use a new tunnel/lease combo + expires = rpe.getNextSendTime() - 500; + if (expires > 0) + sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires); + else + sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent); end = _context.clock().now(); if ( (end-begin > 1000) && (_log.shouldLog(Log.WARN)) ) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/TCBShare.java b/apps/streaming/java/src/net/i2p/client/streaming/TCBShare.java new file mode 100644 index 0000000000000000000000000000000000000000..1562f948e5d4f1fe2b68777ee12dbf20c9b9fba3 --- /dev/null +++ b/apps/streaming/java/src/net/i2p/client/streaming/TCBShare.java @@ -0,0 +1,137 @@ +package net.i2p.client.streaming; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import net.i2p.I2PAppContext; +import net.i2p.data.Destination; +import net.i2p.util.Log; +import net.i2p.util.SimpleTimer; + +/** + * Share important TCP Control Block parameters across Connections + * to the same remote peer. + * This is intended for "temporal" sharing at connection open/close time, + * not "ensemble" sharing during a connection. Ref. RFC 2140. + * + * There is a TCB share per ConnectionManager (i.e. per local Destination) + * so that there is no information leakage to other Destinations on the + * same router. + * + */ +public class TCBShare { + private I2PAppContext _context; + private Log _log; + private Map<Destination, Entry> _cache; + private CleanEvent _cleaner; + + private static final long EXPIRE_TIME = 30*60*1000; + private static final long CLEAN_TIME = 10*60*1000; + private static final double RTT_DAMPENING = 0.75; + private static final double WDW_DAMPENING = 0.75; + private static final int MAX_RTT = ((int) Connection.MAX_RESEND_DELAY) / 2; + private static final int MAX_WINDOW_SIZE = Connection.MAX_WINDOW_SIZE / 4; + + public TCBShare(I2PAppContext ctx) { + _context = ctx; + _log = ctx.logManager().getLog(TCBShare.class); + _cache = new ConcurrentHashMap(4); + _cleaner = new CleanEvent(); + SimpleTimer.getInstance().addEvent(_cleaner, CLEAN_TIME); + } + + public void stop() { + SimpleTimer.getInstance().removeEvent(_cleaner); + } + + public void updateOptsFromShare(Connection con) { + Destination dest = con.getRemotePeer(); + if (dest == null) + return; + ConnectionOptions opts = con.getOptions(); + if (opts == null) + return; + Entry e = _cache.get(dest); + if (e == null || e.isExpired()) + return; + if (_log.shouldLog(Log.DEBUG)) + _log.debug("From cache: " + + con.getSession().getMyDestination().calculateHash().toBase64().substring(0, 4) + + '-' + + dest.calculateHash().toBase64().substring(0, 4) + + " RTT: " + e.getRTT() + " wdw: " + e.getWindowSize()); + opts.setRTT(e.getRTT()); + opts.setWindowSize(e.getWindowSize()); + } + + public void updateShareOpts(Connection con) { + Destination dest = con.getRemotePeer(); + if (dest == null) + return; + if (con.getAckedPackets() <= 0) + return; + ConnectionOptions opts = con.getOptions(); + if (opts == null) + return; + int old = -1; + int oldw = -1; + Entry e = _cache.get(dest); + if (e == null || e.isExpired()) { + e = new Entry(opts.getRTT(), opts.getWindowSize()); + _cache.put(dest, e); + } else { + old = e.getRTT(); + oldw = e.getWindowSize(); + e.setRTT(opts.getRTT()); + e.setWindowSize(opts.getWindowSize()); + } + if (_log.shouldLog(Log.DEBUG)) + _log.debug("To cache: " + + con.getSession().getMyDestination().calculateHash().toBase64().substring(0, 4) + + '-' + + dest.calculateHash().toBase64().substring(0, 4) + + " old: " + old + " con: " + opts.getRTT() + " new: " + e.getRTT() + + " oldw: " + oldw + " conw: " + opts.getWindowSize() + " neww: " + e.getWindowSize()); + } + + private class Entry { + int _rtt; + int _wdw; + long _updated; + + public Entry(int ms, int wdw) { + _rtt = ms; + _wdw = wdw; + _updated = _context.clock().now(); + } + public int getRTT() { return _rtt; } + public void setRTT(int ms) { + _rtt = (int)(RTT_DAMPENING*_rtt + (1-RTT_DAMPENING)*ms); + if (_rtt > MAX_RTT) + _rtt = MAX_RTT; + _updated = _context.clock().now(); + } + public int getWindowSize() { return _wdw; } + public void setWindowSize(int wdw) { + _wdw = (int)(0.5 + WDW_DAMPENING*_wdw + (1-WDW_DAMPENING)*wdw); + if (_wdw > MAX_WINDOW_SIZE) + _wdw = MAX_WINDOW_SIZE; + _updated = _context.clock().now(); + } + public boolean isExpired() { + return _updated < _context.clock().now() - EXPIRE_TIME; + } + } + + private class CleanEvent implements SimpleTimer.TimedEvent { + public CleanEvent() {} + public void timeReached() { + for (Iterator iter = _cache.keySet().iterator(); iter.hasNext(); ) { + if (_cache.get(iter.next()).isExpired()) + iter.remove(); + } + SimpleTimer.getInstance().addEvent(CleanEvent.this, CLEAN_TIME); + } + } +} diff --git a/build.xml b/build.xml index f9a015a6ef89bfdacdc008c6b15c191783197d34..9b69a13f35c6740a5df2fa9a22d928b39f01b075 100644 --- a/build.xml +++ b/build.xml @@ -60,7 +60,6 @@ <copy file="apps/jetty/jettylib/jasper-runtime.jar" todir="build/" /> <copy file="apps/jetty/jettylib/commons-logging.jar" todir="build/" /> <copy file="apps/jetty/jettylib/commons-el.jar" todir="build/" /> - <copy file="apps/jetty/jettylib/xercesImpl.jar" todir="build/" /> <copy file="apps/jetty/jettylib/javax.servlet.jar" todir="build/" /> </target> <target name="buildexe"> @@ -87,7 +86,7 @@ <jar destfile="./build/launchi2p.jar"> <manifest> <attribute name="Main-Class" value="net.i2p.router.RouterLaunch" /> - <attribute name="Class-Path" value="lib/i2p.jar lib/router.jar lib/jbigi.jar lib/BOB.jar lib/sam.jar lib/mstreaming.jar lib/streaming.jar lib/routerconsole.jar lib/i2ptunnel.jar lib/org.mortbay.jetty.jar lib/javax.servlet.jar lib/jasper-compiler.jar lib/jasper-runtime.jar lib/commons-logging.jar lib/commons-el.jar lib/ant.jar lib/xercesImpl.jar lib/wrapper.jar lib/systray.jar lib/systray4j.jar" /> + <attribute name="Class-Path" value="lib/i2p.jar lib/router.jar lib/jbigi.jar lib/BOB.jar lib/sam.jar lib/mstreaming.jar lib/streaming.jar lib/routerconsole.jar lib/i2ptunnel.jar lib/org.mortbay.jetty.jar lib/javax.servlet.jar lib/jasper-compiler.jar lib/jasper-runtime.jar lib/commons-logging.jar lib/commons-el.jar lib/ant.jar lib/wrapper.jar lib/systray.jar lib/systray4j.jar" /> </manifest> </jar> <!-- now the standalone launcher exe --> @@ -219,7 +218,6 @@ <copy file="apps/systray/java/lib/systray4j.dll" todir="pkg-temp/lib" /> <copy file="apps/systray/java/resources/iggy.ico" todir="pkg-temp/icons" /> <copy file="apps/systray/java/resources/iggy.xpm" todir="pkg-temp/icons" /> - <copy file="build/xercesImpl.jar" todir="pkg-temp/lib/" /> <copy file="build/i2ptunnel.war" todir="pkg-temp/webapps/" /> <copy file="build/routerconsole.war" todir="pkg-temp/webapps/" /> <copy file="build/addressbook.war" todir="pkg-temp/webapps/" /> @@ -371,17 +369,16 @@ <copy file="build/commons-el.jar" todir="pkg-temp/lib/" /> <copy file="build/javax.servlet.jar" todir="pkg-temp/lib/" /> <copy file="build/org.mortbay.jetty.jar" todir="pkg-temp/lib/" /> - <copy file="build/xercesImpl.jar" todir="pkg-temp/lib/" /> </target> <target name="installer" depends="preppkg"> <taskdef name="izpack" classpath="${basedir}/installer/lib/izpack/standalone-compiler.jar" classname="com.izforge.izpack.ant.IzPackTask" /> - <jar destfile="./pkg-temp/lib/copy.jar" basedir="./core/java/build/obj" includes="net/i2p/util/*.class"> + <jar destfile="./pkg-temp/lib/copy.jar" basedir="./core/java/build/obj" includes="net/i2p/util/Copy.class net/i2p/util/FileUtil.class"> <manifest><attribute name="Main-Class" value="net.i2p.util.Copy" /></manifest> </jar> - <jar destfile="./pkg-temp/lib/delete.jar" basedir="./core/java/build/obj" includes="net/i2p/util/*.class"> + <jar destfile="./pkg-temp/lib/delete.jar" basedir="./core/java/build/obj" includes="net/i2p/util/Delete.class net/i2p/util/FileUtil.class"> <manifest><attribute name="Main-Class" value="net.i2p.util.Delete" /></manifest> </jar> - <jar destfile="./pkg-temp/lib/exec.jar" basedir="./core/java/build/obj" includes="net/i2p/util/*.class"> + <jar destfile="./pkg-temp/lib/exec.jar" basedir="./core/java/build/obj" includes="net/i2p/util/Exec.class"> <manifest><attribute name="Main-Class" value="net.i2p.util.Exec" /></manifest> </jar> <izpack input="${basedir}/installer/install.xml" output="${basedir}/install.jar" installerType="standard" basedir="${basedir}" /> @@ -452,7 +449,7 @@ <arg value="-output"/> <arg value="findbugs.xml"/> <arg value="-auxclasspath"/> - <arg value="build/ant.jar:build/commons-el.jar:build/commons-logging.jar:build/jasper-compiler.jar:build/jasper-runtime.jar:build/javax.servlet.jar:build/org.mortbay.jetty.jar:apps/jrobin/jrobin-1.4.0.jar:apps/systray/java/lib/systray4j.jar:installer/lib/wrapper/linux/wrapper.jar:build/xercesImpl.jar"/> + <arg value="build/ant.jar:build/commons-el.jar:build/commons-logging.jar:build/jasper-compiler.jar:build/jasper-runtime.jar:build/javax.servlet.jar:build/org.mortbay.jetty.jar:apps/jrobin/jrobin-1.4.0.jar:apps/systray/java/lib/systray4j.jar:installer/lib/wrapper/linux/wrapper.jar"/> <arg value="-sourcepath"/> <arg value="apps/BOB/src/:apps/addressbook/java/src/:apps/i2psnark/java/src/:apps/i2ptunnel/java/src/:apps/ministreaming/java/src/:apps/routerconsole/java/src/:apps/sam/java/src/:apps/streaming/java/src/:apps/susidns/src/java/src/:apps/susimail/src/src/:apps/systray/java/src/:core/java/src/:router/java/src/"/> <!-- start of the files to be analyzed --> diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java index 3e514ea18b3e9db7fdfa3fb06c944a73b9e9ccd0..6b3b0fd5bf3fcb6eee1de0be897febe0ebf250c9 100644 --- a/core/java/src/net/i2p/I2PAppContext.java +++ b/core/java/src/net/i2p/I2PAppContext.java @@ -24,6 +24,7 @@ import net.i2p.data.RoutingKeyGenerator; import net.i2p.stat.StatManager; import net.i2p.util.Clock; import net.i2p.util.FortunaRandomSource; +import net.i2p.util.KeyRing; import net.i2p.util.LogManager; import net.i2p.util.PooledRandomSource; import net.i2p.util.RandomSource; @@ -75,6 +76,7 @@ public class I2PAppContext { private RoutingKeyGenerator _routingKeyGenerator; private RandomSource _random; private KeyGenerator _keyGenerator; + protected KeyRing _keyRing; // overridden in RouterContext private volatile boolean _statManagerInitialized; private volatile boolean _sessionKeyManagerInitialized; private volatile boolean _namingServiceInitialized; @@ -91,6 +93,7 @@ public class I2PAppContext { private volatile boolean _routingKeyGeneratorInitialized; private volatile boolean _randomInitialized; private volatile boolean _keyGeneratorInitialized; + protected volatile boolean _keyRingInitialized; // used in RouterContext /** @@ -141,12 +144,14 @@ public class I2PAppContext { _elGamalEngine = null; _elGamalAESEngine = null; _logManager = null; + _keyRing = null; _statManagerInitialized = false; _sessionKeyManagerInitialized = false; _namingServiceInitialized = false; _elGamalEngineInitialized = false; _elGamalAESEngineInitialized = false; _logManagerInitialized = false; + _keyRingInitialized = false; } /** @@ -512,6 +517,23 @@ public class I2PAppContext { } } + /** + * Basic hash map + */ + public KeyRing keyRing() { + if (!_keyRingInitialized) + initializeKeyRing(); + return _keyRing; + } + + protected void initializeKeyRing() { + synchronized (this) { + if (_keyRing == null) + _keyRing = new KeyRing(); + _keyRingInitialized = true; + } + } + /** * [insert snarky comment here] * diff --git a/core/java/src/net/i2p/client/I2CPMessageProducer.java b/core/java/src/net/i2p/client/I2CPMessageProducer.java index 9af1fbd19ccb3ea2d40bbdeb739752ac8f5d2691..5b45ee7a362cda72097d00fac8e514f13096b37d 100644 --- a/core/java/src/net/i2p/client/I2CPMessageProducer.java +++ b/core/java/src/net/i2p/client/I2CPMessageProducer.java @@ -9,6 +9,7 @@ package net.i2p.client; * */ +import java.util.Date; import java.util.Set; import net.i2p.I2PAppContext; @@ -28,6 +29,7 @@ import net.i2p.data.i2cp.DestroySessionMessage; import net.i2p.data.i2cp.MessageId; import net.i2p.data.i2cp.ReportAbuseMessage; import net.i2p.data.i2cp.SendMessageMessage; +import net.i2p.data.i2cp.SendMessageExpiresMessage; import net.i2p.data.i2cp.SessionConfig; import net.i2p.util.Log; @@ -91,8 +93,13 @@ class I2CPMessageProducer { * */ public void sendMessage(I2PSessionImpl session, Destination dest, long nonce, byte[] payload, SessionTag tag, - SessionKey key, Set tags, SessionKey newKey) throws I2PSessionException { - SendMessageMessage msg = new SendMessageMessage(); + SessionKey key, Set tags, SessionKey newKey, long expires) throws I2PSessionException { + SendMessageMessage msg; + if (expires > 0) { + msg = new SendMessageExpiresMessage(); + ((SendMessageExpiresMessage)msg).setExpiration(new Date(expires)); + } else + msg = new SendMessageMessage(); msg.setDestination(dest); msg.setSessionId(session.getSessionId()); msg.setNonce(nonce); diff --git a/core/java/src/net/i2p/client/I2PSession.java b/core/java/src/net/i2p/client/I2PSession.java index 627d1775a5f6e7943428e43346c9be5a0ca640ee..d8c64f2222406a39710e32fee3bb91077e9467d1 100644 --- a/core/java/src/net/i2p/client/I2PSession.java +++ b/core/java/src/net/i2p/client/I2PSession.java @@ -70,6 +70,7 @@ public interface I2PSession { */ public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException; public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent) throws I2PSessionException; + public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire) throws I2PSessionException; /** Receive a message that the router has notified the client about, returning * the payload. diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java index 78f4ba763c4682b38678d9f4016cc60d516718eb..a57957107a8d29380cee2ce7be4c7428ab331dd0 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl.java @@ -550,10 +550,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa * Pass off the error to the listener */ void propogateError(String msg, Throwable error) { - if (_log.shouldLog(Log.WARN)) - _log.warn(getPrefix() + "Error occurred: " + msg + " - " + error.getMessage()); - if (_log.shouldLog(Log.WARN)) - _log.warn(getPrefix() + " cause", error); + if (_log.shouldLog(Log.ERROR)) + _log.error(getPrefix() + "Error occurred: " + msg + " - " + error.getMessage()); + if (_log.shouldLog(Log.ERROR)) + _log.error(getPrefix() + " cause", error); if (_sessionListener != null) _sessionListener.errorOccurred(this, msg, error); } diff --git a/core/java/src/net/i2p/client/I2PSessionImpl2.java b/core/java/src/net/i2p/client/I2PSessionImpl2.java index 81c6ef22f066d670fe7e00530e124d9131e071bd..6a90952a52f2bcf1ec5312347f36c768e08da083 100644 --- a/core/java/src/net/i2p/client/I2PSessionImpl2.java +++ b/core/java/src/net/i2p/client/I2PSessionImpl2.java @@ -107,15 +107,19 @@ class I2PSessionImpl2 extends I2PSessionImpl { return sendMessage(dest, payload, 0, payload.length); } public boolean sendMessage(Destination dest, byte[] payload, int offset, int size) throws I2PSessionException { - return sendMessage(dest, payload, offset, size, new SessionKey(), new HashSet(64)); + return sendMessage(dest, payload, offset, size, new SessionKey(), new HashSet(64), 0); } @Override public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException { - return sendMessage(dest, payload, 0, payload.length, keyUsed, tagsSent); + return sendMessage(dest, payload, 0, payload.length, keyUsed, tagsSent, 0); } public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent) throws I2PSessionException { + return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0); + } + public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expires) + throws I2PSessionException { if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message"); if (isClosed()) throw new I2PSessionException("Already closed"); @@ -142,7 +146,7 @@ class I2PSessionImpl2 extends I2PSessionImpl { } _context.statManager().addRateData("i2cp.tx.msgCompressed", compressed, 0); _context.statManager().addRateData("i2cp.tx.msgExpanded", size, 0); - return sendBestEffort(dest, payload, keyUsed, tagsSent); + return sendBestEffort(dest, payload, keyUsed, tagsSent, expires); } /** @@ -168,7 +172,7 @@ class I2PSessionImpl2 extends I2PSessionImpl { private static final int NUM_TAGS = 50; - private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent) + private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent, long expires) throws I2PSessionException { SessionKey key = null; SessionKey newKey = null; @@ -176,6 +180,7 @@ class I2PSessionImpl2 extends I2PSessionImpl { Set sentTags = null; int oldTags = 0; long begin = _context.clock().now(); + /*********** if (I2CPMessageProducer.END_TO_END_CRYPTO) { if (_log.shouldLog(Log.DEBUG)) _log.debug("begin sendBestEffort"); key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey()); @@ -220,6 +225,7 @@ class I2PSessionImpl2 extends I2PSessionImpl { } else { // not using end to end crypto, so don't ever bundle any tags } + **********/ if (_log.shouldLog(Log.DEBUG)) _log.debug("before creating nonce"); @@ -233,14 +239,14 @@ class I2PSessionImpl2 extends I2PSessionImpl { if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Setting key = " + key); if (keyUsed != null) { - if (I2CPMessageProducer.END_TO_END_CRYPTO) { - if (newKey != null) - keyUsed.setData(newKey.getData()); - else - keyUsed.setData(key.getData()); - } else { + //if (I2CPMessageProducer.END_TO_END_CRYPTO) { + // if (newKey != null) + // keyUsed.setData(newKey.getData()); + // else + // keyUsed.setData(key.getData()); + //} else { keyUsed.setData(SessionKey.INVALID_KEY.getData()); - } + //} } if (tagsSent != null) { if (sentTags != null) { @@ -261,7 +267,7 @@ class I2PSessionImpl2 extends I2PSessionImpl { + state.getNonce() + " for best effort " + " sync took " + (inSendingSync-beforeSendingSync) + " add took " + (afterSendingSync-inSendingSync)); - _producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey); + _producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey, expires); // since this is 'best effort', all we're waiting for is a status update // saying that the router received it - in theory, that should come back diff --git a/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java index 7d6d816c1900d5c9227daf7fdcd3bd89c2be0f3b..6163771e366c90d6cd68b45fe838f15707485202 100644 --- a/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/RequestLeaseSetMessageHandler.java @@ -21,6 +21,7 @@ import net.i2p.data.Lease; import net.i2p.data.LeaseSet; import net.i2p.data.PrivateKey; import net.i2p.data.PublicKey; +import net.i2p.data.SessionKey; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.data.i2cp.I2CPMessage; @@ -78,6 +79,17 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { leaseSet.setEncryptionKey(li.getPublicKey()); leaseSet.setSigningKey(li.getSigningPublicKey()); + String sk = session.getOptions().getProperty("i2cp.sessionKey"); + if (sk != null) { + SessionKey key = new SessionKey(); + try { + key.fromBase64(sk); + leaseSet.encrypt(key); + _context.keyRing().put(session.getMyDestination().calculateHash(), key); + } catch (DataFormatException dfe) { + _log.error("Bad session key: " + sk); + } + } try { leaseSet.sign(session.getPrivateKey()); session.getProducer().createLeaseSet(session, leaseSet, li.getSigningPrivateKey(), li.getPrivateKey()); @@ -137,4 +149,4 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { && DataHelper.eq(_signingPrivKey, li.getSigningPrivateKey()); } } -} \ No newline at end of file +} diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 835e6a0dd74bb84ae47b72cddbd1928dbbd4c464..4a074f17cf9bdaefe2b59a8b90f16a48651a20a5 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -25,6 +25,7 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.math.BigInteger; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -235,7 +236,7 @@ public class DataHelper { int split = line.indexOf('='); if (split <= 0) continue; String key = line.substring(0, split); - String val = line.substring(split+1); + String val = line.substring(split+1); //.trim() ?????????????? // Unescape line breaks after loading. // Remember: "\" needs escaping both for regex and string. val = val.replaceAll("\\\\r","\r"); @@ -842,6 +843,29 @@ public class DataHelper { } } + /** + * Caller should append 'B' or 'b' as appropriate + */ + public static String formatSize(long bytes) { + double val = bytes; + int scale = 0; + while (val >= 1024) { + scale++; + val /= 1024; + } + + DecimalFormat fmt = new DecimalFormat("##0.00"); + + String str = fmt.format(val); + switch (scale) { + case 1: return str + "K"; + case 2: return str + "M"; + case 3: return str + "G"; + case 4: return str + "T"; + default: return bytes + ""; + } + } + /** * Strip out any HTML (simply removing any less than / greater than symbols) */ diff --git a/core/java/src/net/i2p/data/LeaseSet.java b/core/java/src/net/i2p/data/LeaseSet.java index 7dd74a9d7cbd0f1954d8f3b33112bc9d74d1cd37..8a05dd9569bb744599f7d0e8dc1e51d8e6a786f4 100644 --- a/core/java/src/net/i2p/data/LeaseSet.java +++ b/core/java/src/net/i2p/data/LeaseSet.java @@ -9,6 +9,7 @@ package net.i2p.data; * */ +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -17,13 +18,34 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import net.i2p.I2PAppContext; import net.i2p.crypto.DSAEngine; import net.i2p.util.Clock; import net.i2p.util.Log; +import net.i2p.util.RandomSource; /** * Defines the set of leases a destination currently has. * + * Support encryption and decryption with a supplied key. + * Only the gateways and tunnel IDs in the individual + * leases are encrypted. + * + * Encrypted leases are not indicated as such. + * The only way to tell a lease is encrypted is to + * determine that the listed gateways do not exist. + * Routers wishing to decrypt a leaseset must have the + * desthash and key in their keyring. + * This is required for the local router as well, since + * the encryption is done on the client side of I2CP, the + * router must decrypt it back again for local usage + * (but not for transmission to the floodfills) + * + * Decrypted leases are only available through the getLease() + * method, so that storage and network transmission via + * writeBytes() will output the original encrypted + * leases and the original leaseset signature. + * * @author jrandom */ public class LeaseSet extends DataStructureImpl { @@ -40,6 +62,9 @@ public class LeaseSet extends DataStructureImpl { // Store these since isCurrent() and getEarliestLeaseDate() are called frequently private long _firstExpiration; private long _lastExpiration; + private List _decryptedLeases; + private boolean _decrypted; + private boolean _checked; /** This seems like plenty */ private final static int MAX_LEASES = 6; @@ -55,6 +80,8 @@ public class LeaseSet extends DataStructureImpl { _receivedAsPublished = false; _firstExpiration = Long.MAX_VALUE; _lastExpiration = 0; + _decrypted = false; + _checked = false; } public Destination getDestination() { @@ -104,11 +131,17 @@ public class LeaseSet extends DataStructureImpl { } public int getLeaseCount() { - return _leases.size(); + if (isEncrypted()) + return _leases.size() - 1; + else + return _leases.size(); } public Lease getLease(int index) { - return (Lease) _leases.get(index); + if (isEncrypted()) + return (Lease) _decryptedLeases.get(index); + else + return (Lease) _leases.get(index); } public Signature getSignature() { @@ -335,4 +368,139 @@ public class LeaseSet extends DataStructureImpl { buf.append("]"); return buf.toString(); } + + private static final int DATA_LEN = Hash.HASH_LENGTH + 4; + private static final int IV_LEN = 16; + + /** + * Encrypt the gateway and tunnel ID of each lease, leaving the expire dates unchanged. + * This adds an extra dummy lease, because AES data must be padded to 16 bytes. + * The fact that it is encrypted is not stored anywhere. + * Must be called after all the leases are in place, but before sign(). + */ + public void encrypt(SessionKey key) { + if (_log.shouldLog(Log.WARN)) + _log.warn("encrypting lease: " + _destination.calculateHash()); + try { + encryp(key); + } catch (DataFormatException dfe) { + _log.error("Error encrypting lease: " + _destination.calculateHash()); + } catch (IOException ioe) { + _log.error("Error encrypting lease: " + _destination.calculateHash()); + } + } + + /** + * - Put the {Gateway Hash, TunnelID} pairs for all the leases in a buffer + * - Pad with random data to a multiple of 16 bytes + * - Use the first part of the dest's public key as an IV + * - Encrypt + * - Pad with random data to a multiple of 36 bytes + * - Add an extra lease + * - Replace the Hash and TunnelID in each Lease + */ + private void encryp(SessionKey key) throws DataFormatException, IOException { + int size = _leases.size(); + if (size < 1 || size > MAX_LEASES-1) + throw new IllegalArgumentException("Bad number of leases for encryption"); + int datalen = ((DATA_LEN * size / 16) + 1) * 16; + ByteArrayOutputStream baos = new ByteArrayOutputStream(datalen); + for (int i = 0; i < size; i++) { + ((Lease)_leases.get(i)).getGateway().writeBytes(baos); + ((Lease)_leases.get(i)).getTunnelId().writeBytes(baos); + } + // pad out to multiple of 16 with random data before encryption + int padlen = datalen - (DATA_LEN * size); + byte[] pad = new byte[padlen]; + RandomSource.getInstance().nextBytes(pad); + baos.write(pad); + byte[] iv = new byte[IV_LEN]; + System.arraycopy(_destination.getPublicKey().getData(), 0, iv, 0, IV_LEN); + byte[] enc = new byte[DATA_LEN * (size + 1)]; + I2PAppContext.getGlobalContext().aes().encrypt(baos.toByteArray(), 0, enc, 0, key, iv, datalen); + // pad out to multiple of 36 with random data after encryption + // (even for 4 leases, where 36*4 is a multiple of 16, we add another, just to be consistent) + padlen = enc.length - datalen; + pad = new byte[padlen]; + RandomSource.getInstance().nextBytes(pad); + System.arraycopy(pad, 0, enc, datalen, padlen); + // add the padded lease... + Lease padLease = new Lease(); + padLease.setEndDate(((Lease)_leases.get(0)).getEndDate()); + _leases.add(padLease); + // ...and replace all the gateways and tunnel ids + ByteArrayInputStream bais = new ByteArrayInputStream(enc); + for (int i = 0; i < size+1; i++) { + Hash h = new Hash(); + h.readBytes(bais); + ((Lease)_leases.get(i)).setGateway(h); + TunnelId t = new TunnelId(); + t.readBytes(bais); + ((Lease)_leases.get(i)).setTunnelId(t); + } + } + + /** + * Decrypt the leases, except for the last one which is partially padding. + * Store the new decrypted leases in a backing store, + * and keep the original leases so that verify() still works and the + * encrypted leaseset can be sent on to others (via writeBytes()) + */ + private void decrypt(SessionKey key) throws DataFormatException, IOException { + if (_log.shouldLog(Log.WARN)) + _log.warn("decrypting lease: " + _destination.calculateHash()); + int size = _leases.size(); + if (size < 2) + throw new DataFormatException("Bad number of leases for decryption"); + int datalen = DATA_LEN * size; + ByteArrayOutputStream baos = new ByteArrayOutputStream(datalen); + for (int i = 0; i < size; i++) { + ((Lease)_leases.get(i)).getGateway().writeBytes(baos); + ((Lease)_leases.get(i)).getTunnelId().writeBytes(baos); + } + byte[] iv = new byte[IV_LEN]; + System.arraycopy(_destination.getPublicKey().getData(), 0, iv, 0, IV_LEN); + int enclen = ((DATA_LEN * (size - 1) / 16) + 1) * 16; + byte[] enc = new byte[enclen]; + System.arraycopy(baos.toByteArray(), 0, enc, 0, enclen); + byte[] dec = new byte[enclen]; + I2PAppContext.getGlobalContext().aes().decrypt(enc, 0, dec, 0, key, iv, enclen); + ByteArrayInputStream bais = new ByteArrayInputStream(dec); + _decryptedLeases = new ArrayList(size - 1); + for (int i = 0; i < size-1; i++) { + Lease l = new Lease(); + Hash h = new Hash(); + h.readBytes(bais); + l.setGateway(h); + TunnelId t = new TunnelId(); + t.readBytes(bais); + l.setTunnelId(t); + l.setEndDate(((Lease)_leases.get(i)).getEndDate()); + _decryptedLeases.add(l); + } + } + + /** + * @return true if it was encrypted, and we decrypted it successfully. + * Decrypts on first call. + */ + private synchronized boolean isEncrypted() { + if (_decrypted) + return true; + if (_checked || _destination == null) + return false; + SessionKey key = I2PAppContext.getGlobalContext().keyRing().get(_destination.calculateHash()); + if (key != null) { + try { + decrypt(key); + _decrypted = true; + } catch (DataFormatException dfe) { + _log.error("Error decrypting lease: " + _destination.calculateHash() + dfe); + } catch (IOException ioe) { + _log.error("Error decrypting lease: " + _destination.calculateHash() + ioe); + } + } + _checked = true; + return _decrypted; + } } diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java index 128c312dce4cc1150c4f7f8e54c9c4a50e066fb6..15045028a88eb9b8ba4f0424ab35fdacbbaa4c7d 100644 --- a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java +++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java @@ -18,7 +18,7 @@ import net.i2p.data.DataHelper; import net.i2p.util.Log; /** - * Handle messages from the server for the client + * Handle messages from the server for the client or vice versa * */ public class I2CPMessageHandler { @@ -75,6 +75,8 @@ public class I2CPMessageHandler { return new RequestLeaseSetMessage(); case SendMessageMessage.MESSAGE_TYPE: return new SendMessageMessage(); + case SendMessageExpiresMessage.MESSAGE_TYPE: + return new SendMessageExpiresMessage(); case SessionStatusMessage.MESSAGE_TYPE: return new SessionStatusMessage(); case GetDateMessage.MESSAGE_TYPE: diff --git a/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java b/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..7165f6d3271c9d88b085d694bd0249f4a0d4ef59 --- /dev/null +++ b/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java @@ -0,0 +1,103 @@ +package net.i2p.data.i2cp; + +/* + * free (adj.): unencumbered; not under the control of others + * Written by jrandom in 2003 and released into the public domain + * with no warranty of any kind, either expressed or implied. + * It probably won't make your computer catch on fire, or eat + * your children, but it might. Use at your own risk. + * + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.util.Log; + +/** + * Defines the message a client sends to a router when + * updating the config on an existing session. + * + * @author zzz + */ +public class ReconfigureSessionMessage extends I2CPMessageImpl { + private final static Log _log = new Log(ReconfigureSessionMessage.class); + public final static int MESSAGE_TYPE = 2; + private SessionId _sessionId; + private SessionConfig _sessionConfig; + + public ReconfigureSessionMessage() { + _sessionId = null; + _sessionConfig = null; + } + + public SessionId getSessionId() { + return _sessionId; + } + + public void setSessionId(SessionId id) { + _sessionId = id; + } + + public SessionConfig getSessionConfig() { + return _sessionConfig; + } + + public void setSessionConfig(SessionConfig config) { + _sessionConfig = config; + } + + @Override + protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { + try { + _sessionId = new SessionId(); + _sessionId.readBytes(in); + _sessionConfig = new SessionConfig(); + _sessionConfig.readBytes(in); + } catch (DataFormatException dfe) { + throw new I2CPMessageException("Unable to load the message data", dfe); + } + } + + @Override + protected byte[] doWriteMessage() throws I2CPMessageException, IOException { + if (_sessionId == null || _sessionConfig == null) + throw new I2CPMessageException("Unable to write out the message as there is not enough data"); + ByteArrayOutputStream os = new ByteArrayOutputStream(64); + try { + _sessionId.writeBytes(os); + _sessionConfig.writeBytes(os); + } catch (DataFormatException dfe) { + throw new I2CPMessageException("Error writing out the message data", dfe); + } + return os.toByteArray(); + } + + public int getType() { + return MESSAGE_TYPE; + } + + @Override + public boolean equals(Object object) { + if ((object != null) && (object instanceof ReconfigureSessionMessage)) { + ReconfigureSessionMessage msg = (ReconfigureSessionMessage) object; + return DataHelper.eq(getSessionId(), msg.getSessionId()) + && DataHelper.eq(getSessionConfig(), msg.getSessionConfig()); + } + + return false; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("[ReconfigureSessionMessage: "); + buf.append("\n\tSessionId: ").append(getSessionId()); + buf.append("\n\tSessionConfig: ").append(getSessionConfig()); + buf.append("]"); + return buf.toString(); + } +} diff --git a/core/java/src/net/i2p/data/i2cp/SendMessageExpiresMessage.java b/core/java/src/net/i2p/data/i2cp/SendMessageExpiresMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..d15c1979c4427f1673856cfe2ecff3cbcf731ccf --- /dev/null +++ b/core/java/src/net/i2p/data/i2cp/SendMessageExpiresMessage.java @@ -0,0 +1,117 @@ +package net.i2p.data.i2cp; + +/* + * free (adj.): unencumbered; not under the control of others + * Written by jrandom in 2003 and released into the public domain + * with no warranty of any kind, either expressed or implied. + * It probably won't make your computer catch on fire, or eat + * your children, but it might. Use at your own risk. + * + */ + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; + +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.data.Destination; +import net.i2p.data.Payload; +import net.i2p.util.Log; + +/** + * Same as SendMessageMessage, but with an expiration to be passed to the router + * + * @author zzz + */ +public class SendMessageExpiresMessage extends SendMessageMessage { + private final static Log _log = new Log(SendMessageExpiresMessage.class); + public final static int MESSAGE_TYPE = 36; + private SessionId _sessionId; + private Destination _destination; + private Payload _payload; + private Date _expiration; + + public SendMessageExpiresMessage() { + super(); + setExpiration(null); + } + + public Date getExpiration() { + return _expiration; + } + + public void setExpiration(Date d) { + _expiration = d; + } + + /** + * Read the body into the data structures + * + * @throws IOException + */ + @Override + public void readMessage(InputStream in, int length, int type) throws I2CPMessageException, IOException { + super.readMessage(in, length, type); + + try { + _expiration = DataHelper.readDate(in); + } catch (DataFormatException dfe) { + throw new I2CPMessageException("Unable to load the message data", dfe); + } + } + + /** + * Write out the full message to the stream, including the 4 byte size and 1 + * byte type header. Override the parent so we can be more mem efficient + * + * @throws IOException + */ + @Override + public void writeMessage(OutputStream out) throws I2CPMessageException, IOException { + if ((getSessionId() == null) || (getDestination() == null) || (getPayload() == null) || (getNonce() <= 0) || (_expiration == null)) + throw new I2CPMessageException("Unable to write out the message as there is not enough data"); + int len = 2 + getDestination().size() + getPayload().getSize() + 4 + 4 + DataHelper.DATE_LENGTH; + + try { + DataHelper.writeLong(out, 4, len); + DataHelper.writeLong(out, 1, getType()); + getSessionId().writeBytes(out); + getDestination().writeBytes(out); + getPayload().writeBytes(out); + DataHelper.writeLong(out, 4, getNonce()); + DataHelper.writeDate(out, _expiration); + } catch (DataFormatException dfe) { + throw new I2CPMessageException("Error writing the msg", dfe); + } + } + + public int getType() { + return MESSAGE_TYPE; + } + + @Override + public boolean equals(Object object) { + if ((object != null) && (object instanceof SendMessageExpiresMessage)) { + SendMessageExpiresMessage msg = (SendMessageExpiresMessage) object; + return super.equals(object) + && DataHelper.eq(getExpiration(), msg.getExpiration()); + } + + return false; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("[SendMessageMessage: "); + buf.append("\n\tSessionId: ").append(getSessionId()); + buf.append("\n\tNonce: ").append(getNonce()); + buf.append("\n\tDestination: ").append(getDestination()); + buf.append("\n\tExpiration: ").append(getExpiration()); + buf.append("\n\tPayload: ").append(getPayload()); + buf.append("]"); + return buf.toString(); + } +} diff --git a/core/java/src/net/i2p/util/KeyRing.java b/core/java/src/net/i2p/util/KeyRing.java new file mode 100644 index 0000000000000000000000000000000000000000..6bbfb38dee411b7c0ca29f03961b8b906e7dc817 --- /dev/null +++ b/core/java/src/net/i2p/util/KeyRing.java @@ -0,0 +1,20 @@ +package net.i2p.util; + +import java.io.IOException; +import java.io.Writer; + +import java.util.concurrent.ConcurrentHashMap; + +import net.i2p.data.Hash; +import net.i2p.data.SessionKey; + +/** + * simple + */ +public class KeyRing extends ConcurrentHashMap<Hash, SessionKey> { + public KeyRing() { + super(0); + } + + public void renderStatusHTML(Writer out) throws IOException {} +} diff --git a/installer/resources/wrapper.config b/installer/resources/wrapper.config index 2550f4e3dc428a27b77f33fc198fdf6ade953e73..177bc1c0bf062427d7148d8c38668d3c039137c4 100644 --- a/installer/resources/wrapper.config +++ b/installer/resources/wrapper.config @@ -47,14 +47,13 @@ wrapper.java.classpath.12=lib/jasper-runtime.jar wrapper.java.classpath.13=lib/commons-logging.jar wrapper.java.classpath.14=lib/commons-el.jar wrapper.java.classpath.15=lib/ant.jar -wrapper.java.classpath.16=lib/xercesImpl.jar # java service wrapper, BSD -wrapper.java.classpath.17=lib/wrapper.jar +wrapper.java.classpath.16=lib/wrapper.jar # systray, LGPL -wrapper.java.classpath.18=lib/systray.jar -wrapper.java.classpath.19=lib/systray4j.jar +wrapper.java.classpath.17=lib/systray.jar +wrapper.java.classpath.18=lib/systray4j.jar # BOB -wrapper.java.classpath.20=lib/BOB.jar +wrapper.java.classpath.19=lib/BOB.jar # Java Library Path (location of Wrapper.DLL or libwrapper.so) wrapper.java.library.path.1=. diff --git a/router/java/src/net/i2p/router/ClientMessage.java b/router/java/src/net/i2p/router/ClientMessage.java index 005f69a2d2580b4806012aa38a7ba7b4242f2e2a..ec7820d696a47e8ff065f216e025b85d208d5c91 100644 --- a/router/java/src/net/i2p/router/ClientMessage.java +++ b/router/java/src/net/i2p/router/ClientMessage.java @@ -27,6 +27,7 @@ public class ClientMessage { private SessionConfig _senderConfig; private Hash _destinationHash; private MessageId _messageId; + private long _expiration; public ClientMessage() { setPayload(null); @@ -36,6 +37,7 @@ public class ClientMessage { setSenderConfig(null); setDestinationHash(null); setMessageId(null); + setExpiration(0); } /** @@ -91,4 +93,12 @@ public class ClientMessage { */ public SessionConfig getSenderConfig() { return _senderConfig; } public void setSenderConfig(SessionConfig config) { _senderConfig = config; } + + /** + * Expiration requested by the client that sent the message. This will only be available + * for locally originated messages. + * + */ + public long getExpiration() { return _expiration; } + public void setExpiration(long e) { _expiration = e; } } diff --git a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java index ed4fe155502df3ef50f7025ade8e5bc58e099fef..e4a5ce08b663a7da1c9fc9752104dc33875bae0a 100644 --- a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java @@ -62,4 +62,5 @@ public abstract class NetworkDatabaseFacade implements Service { public int getKnownRouters() { return 0; } public int getKnownLeaseSets() { return 0; } public void renderRouterInfoHTML(Writer out, String s) throws IOException {} + public void renderStatusHTML(Writer out, boolean b) throws IOException {} } diff --git a/router/java/src/net/i2p/router/PersistentKeyRing.java b/router/java/src/net/i2p/router/PersistentKeyRing.java new file mode 100644 index 0000000000000000000000000000000000000000..d02275ea20c72bbb045269d74e52ac1d494ff1df --- /dev/null +++ b/router/java/src/net/i2p/router/PersistentKeyRing.java @@ -0,0 +1,103 @@ +package net.i2p.router; + +import java.io.IOException; +import java.io.Writer; + +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +import net.i2p.data.Base64; +import net.i2p.data.DataFormatException; +import net.i2p.data.Destination; +import net.i2p.data.Hash; +import net.i2p.data.LeaseSet; +import net.i2p.data.SessionKey; +import net.i2p.router.TunnelPoolSettings; +import net.i2p.util.KeyRing; + +/** + * ConcurrentHashMap with backing in the router.config file. + * router.keyring.key.{base64 hash, with = replaced with $}={base64 session key} + * Caution - not all HashMap methods are overridden. + */ +public class PersistentKeyRing extends KeyRing { + private RouterContext _ctx; + private static final String PROP_PFX = "router.keyring.key."; + + public PersistentKeyRing(RouterContext ctx) { + super(); + _ctx = ctx; + addFromProperties(); + } + + public SessionKey put(Hash h, SessionKey sk) { + SessionKey old = super.put(h, sk); + if (!sk.equals(old)) { + _ctx.router().setConfigSetting(PROP_PFX + h.toBase64().replace("=", "$"), + sk.toBase64()); + _ctx.router().saveConfig(); + } + return old; + } + + public SessionKey remove(Hash h) { + _ctx.router().removeConfigSetting(PROP_PFX + h.toBase64().replace("=", "$")); + _ctx.router().saveConfig(); + return super.remove(h); + } + + private void addFromProperties() { + for (Iterator iter = _ctx.getPropertyNames().iterator(); iter.hasNext(); ) { + String prop = (String) iter.next(); + if (!prop.startsWith(PROP_PFX)) + continue; + String key = _ctx.getProperty(prop); + if (key == null || key.length() != 44) + continue; + String hb = prop.substring(PROP_PFX.length()); + hb.replace("$", "="); + Hash dest = new Hash(); + SessionKey sk = new SessionKey(); + try { + dest.fromBase64(hb); + sk.fromBase64(key); + super.put(dest, sk); + } catch (DataFormatException dfe) { continue; } + } + } + + public void renderStatusHTML(Writer out) throws IOException { + StringBuffer buf = new StringBuffer(1024); + buf.append("\n<table border=\"1\"><tr><th align=\"left\">Destination Hash<th align=\"left\">Name or Dest.<th align=\"left\">Session Key</tr>"); + for (Entry<Hash, SessionKey> e : entrySet()) { + buf.append("\n<tr><td>"); + Hash h = e.getKey(); + buf.append(h.toBase64().substring(0, 6)).append("..."); + buf.append("<td>"); + LeaseSet ls = _ctx.netDb().lookupLeaseSetLocally(h); + if (ls != null) { + Destination dest = ls.getDestination(); + if (_ctx.clientManager().isLocal(dest)) { + TunnelPoolSettings in = _ctx.tunnelManager().getInboundSettings(h); + if (in != null && in.getDestinationNickname() != null) + buf.append(in.getDestinationNickname()); + else + buf.append(dest.toBase64().substring(0, 6)).append("..."); + } else { + String host = _ctx.namingService().reverseLookup(dest); + if (host != null) + buf.append(host); + else + buf.append(dest.toBase64().substring(0, 6)).append("..."); + } + } + buf.append("<td>"); + SessionKey sk = e.getValue(); + buf.append(sk.toBase64()); + } + buf.append("\n</table>\n"); + out.write(buf.toString()); + out.flush(); + } +} diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java index 087cde91847cb313680452266920a3bc5b090f37..517a5ba35883f68de8d4d765956d90ec7884a4e7 100644 --- a/router/java/src/net/i2p/router/RouterContext.java +++ b/router/java/src/net/i2p/router/RouterContext.java @@ -26,6 +26,7 @@ import net.i2p.router.transport.VMCommSystem; import net.i2p.router.tunnel.TunnelDispatcher; import net.i2p.router.tunnel.pool.TunnelPoolManager; import net.i2p.util.Clock; +import net.i2p.util.KeyRing; /** * Build off the core I2P context to provide a root for a router instance to @@ -366,4 +367,21 @@ public class RouterContext extends I2PAppContext { } } + /** override to support storage in router.config */ + @Override + public KeyRing keyRing() { + if (!_keyRingInitialized) + initializeKeyRing(); + return _keyRing; + } + + @Override + protected void initializeKeyRing() { + synchronized (this) { + if (_keyRing == null) + _keyRing = new PersistentKeyRing(this); + _keyRingInitialized = true; + } + } + } diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java index 544badcad42169139012fdf5f61993f359aa41c6..133ad142c8b040c415676c5fe9caff8b0f3a1896 100644 --- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java @@ -29,6 +29,7 @@ import net.i2p.data.i2cp.I2CPMessageReader; import net.i2p.data.i2cp.MessageId; import net.i2p.data.i2cp.MessageStatusMessage; import net.i2p.data.i2cp.SendMessageMessage; +import net.i2p.data.i2cp.SendMessageExpiresMessage; import net.i2p.data.i2cp.SessionConfig; import net.i2p.data.i2cp.SessionId; import net.i2p.router.Job; @@ -270,6 +271,9 @@ public class ClientConnectionRunner { Destination dest = message.getDestination(); MessageId id = new MessageId(); id.setMessageId(getNextMessageId()); + long expiration = 0; + if (message instanceof SendMessageExpiresMessage) + expiration = ((SendMessageExpiresMessage) message).getExpiration().getTime(); long beforeLock = _context.clock().now(); long inLock = 0; synchronized (_acceptedPending) { @@ -291,7 +295,7 @@ public class ClientConnectionRunner { // the following blocks as described above SessionConfig cfg = _config; if (cfg != null) - _manager.distributeMessage(cfg.getDestination(), dest, payload, id); + _manager.distributeMessage(cfg.getDestination(), dest, payload, id, expiration); long timeToDistribute = _context.clock().now() - beforeDistribute; if (_log.shouldLog(Log.DEBUG)) _log.warn("Time to distribute in the manager to " diff --git a/router/java/src/net/i2p/router/client/ClientManager.java b/router/java/src/net/i2p/router/client/ClientManager.java index d9838ef7b5a3de471ec6f6a5bdb03b184d852814..18c9c77423d790836665096b9cc8d09926ab684e 100644 --- a/router/java/src/net/i2p/router/client/ClientManager.java +++ b/router/java/src/net/i2p/router/client/ClientManager.java @@ -140,7 +140,7 @@ public class ClientManager { } } - void distributeMessage(Destination fromDest, Destination toDest, Payload payload, MessageId msgId) { + void distributeMessage(Destination fromDest, Destination toDest, Payload payload, MessageId msgId, long expiration) { // check if there is a runner for it ClientConnectionRunner runner = getRunner(toDest); if (runner != null) { @@ -168,6 +168,7 @@ public class ClientManager { msg.setSenderConfig(runner.getConfig()); msg.setFromDestination(runner.getConfig().getDestination()); msg.setMessageId(msgId); + msg.setExpiration(expiration); _ctx.clientMessagePool().add(msg, true); } } diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java index 033e28f2f00b6f8f59acaccddd2332ee5e4d4c20..d36d26401a496ca09fc3eb96098da7ad7c448253 100644 --- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java +++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java @@ -21,7 +21,9 @@ import net.i2p.data.i2cp.MessageId; import net.i2p.data.i2cp.MessagePayloadMessage; import net.i2p.data.i2cp.ReceiveMessageBeginMessage; import net.i2p.data.i2cp.ReceiveMessageEndMessage; +import net.i2p.data.i2cp.ReconfigureSessionMessage; import net.i2p.data.i2cp.SendMessageMessage; +import net.i2p.data.i2cp.SendMessageExpiresMessage; import net.i2p.data.i2cp.SessionId; import net.i2p.data.i2cp.SessionStatusMessage; import net.i2p.data.i2cp.SetDateMessage; @@ -67,6 +69,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi case SendMessageMessage.MESSAGE_TYPE: handleSendMessage(reader, (SendMessageMessage)message); break; + case SendMessageExpiresMessage.MESSAGE_TYPE: + handleSendMessage(reader, (SendMessageExpiresMessage)message); + break; case ReceiveMessageBeginMessage.MESSAGE_TYPE: handleReceiveBegin(reader, (ReceiveMessageBeginMessage)message); break; @@ -237,6 +242,17 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi _context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getHash())); } + /** + * Message's Session ID ignored. This doesn't support removing previously set options. + * Nor do we bother with message.getSessionConfig().verifySignature() ... should we? + * + */ + private void handleReconfigureSession(I2CPMessageReader reader, ReconfigureSessionMessage message) { + if (_log.shouldLog(Log.INFO)) + _log.info("Updating options - session " + _runner.getSessionId()); + _runner.getConfig().getOptions().putAll(message.getSessionConfig().getOptions()); + } + // this *should* be mod 65536, but UnsignedInteger is still b0rked. FIXME private final static int MAX_SESSION_ID = 32767; diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java index ccef8192a58ac143b9159c5680cd9b6959af9eee..0515d5c34459e84384b6fdd17885880e0201fd43 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java @@ -61,6 +61,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { private long _leaseSetLookupBegin; private TunnelInfo _outTunnel; private TunnelInfo _inTunnel; + private boolean _wantACK; /** * final timeout (in milliseconds) that the outbound message will fail in. @@ -69,6 +70,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { */ public final static String OVERALL_TIMEOUT_MS_PARAM = "clientMessageTimeout"; private final static long OVERALL_TIMEOUT_MS_DEFAULT = 60*1000; + private final static long OVERALL_TIMEOUT_MS_MIN = 5*1000; /** priority of messages, that might get honored some day... */ private final static int SEND_PRIORITY = 500; @@ -125,23 +127,34 @@ public class OutboundClientMessageOneShotJob extends JobImpl { _to = msg.getDestination(); _toString = _to.calculateHash().toBase64().substring(0,4); _leaseSetLookupBegin = -1; + _start = getContext().clock().now(); - String param = msg.getSenderConfig().getOptions().getProperty(OVERALL_TIMEOUT_MS_PARAM); - if (param == null) - param = ctx.router().getConfigSetting(OVERALL_TIMEOUT_MS_PARAM); - if (param != null) { - try { - timeoutMs = Long.parseLong(param); - } catch (NumberFormatException nfe) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Invalid client message timeout specified [" + param - + "], defaulting to " + OVERALL_TIMEOUT_MS_DEFAULT, nfe); - timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT; + // use expiration requested by client if available, otherwise session config, + // otherwise router config, otherwise default + _overallExpiration = msg.getExpiration(); + if (_overallExpiration > 0) { + _overallExpiration = Math.max(_overallExpiration, _start + OVERALL_TIMEOUT_MS_MIN); + _overallExpiration = Math.min(_overallExpiration, _start + OVERALL_TIMEOUT_MS_DEFAULT); + if (_log.shouldLog(Log.WARN)) + _log.warn("Message Expiration (ms): " + (_overallExpiration - _start)); + } else { + String param = msg.getSenderConfig().getOptions().getProperty(OVERALL_TIMEOUT_MS_PARAM); + if (param == null) + param = ctx.router().getConfigSetting(OVERALL_TIMEOUT_MS_PARAM); + if (param != null) { + try { + timeoutMs = Long.parseLong(param); + } catch (NumberFormatException nfe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Invalid client message timeout specified [" + param + + "], defaulting to " + OVERALL_TIMEOUT_MS_DEFAULT, nfe); + timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT; + } } + _overallExpiration = timeoutMs + _start; + if (_log.shouldLog(Log.WARN)) + _log.warn("Default Expiration (ms): " + timeoutMs); } - - _start = getContext().clock().now(); - _overallExpiration = timeoutMs + _start; _finished = false; } @@ -267,6 +280,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { long lookupTime = getContext().clock().now() - _leaseSetLookupBegin; getContext().statManager().addRateData("client.leaseSetFoundRemoteTime", lookupTime, lookupTime); } + _wantACK = false; boolean ok = getNextLease(); if (ok) { send(); @@ -400,6 +414,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { } if (_log.shouldLog(Log.INFO)) _log.info("Added to cache - lease for " + _toString); + _wantACK = true; return true; } @@ -443,10 +458,14 @@ public class OutboundClientMessageOneShotJob extends JobImpl { dieFatal(); return; } - boolean wantACK = true; + int existingTags = GarlicMessageBuilder.estimateAvailableTags(getContext(), _leaseSet.getEncryptionKey()); - if ( (existingTags > 30) && (getContext().random().nextInt(100) >= 5) ) - wantACK = false; + _outTunnel = selectOutboundTunnel(_to); + // what's the point of 5% random? possible improvements or replacements: + // - wantACK if we changed their inbound lease (getNextLease() sets _wantACK) + // - wantACK if we changed our outbound tunnel (selectOutboundTunnel() sets _wantACK) + // - wantACK if we haven't in last 1m (requires a new static cache probably) + boolean wantACK = _wantACK || existingTags <= 30 || getContext().random().nextInt(100) < 5; PublicKey key = _leaseSet.getEncryptionKey(); SessionKey sessKey = new SessionKey(); @@ -503,7 +522,6 @@ public class OutboundClientMessageOneShotJob extends JobImpl { + _lease.getTunnelId() + " on " + _lease.getGateway().toBase64()); - _outTunnel = selectOutboundTunnel(_to); if (_outTunnel != null) { if (_log.shouldLog(Log.DEBUG)) _log.debug(getJobId() + ": Sending tunnel message out " + _outTunnel.getSendTunnelId(0) + " to " @@ -718,6 +736,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { _log.warn("Switching back to tunnel " + tunnel + " for " + _toString); _backloggedTunnelCache.remove(hashPair()); _tunnelCache.put(hashPair(), tunnel); + _wantACK = true; return tunnel; } // else still backlogged } else // no longer valid @@ -740,6 +759,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { tunnel = selectOutboundTunnel(); if (tunnel != null) _tunnelCache.put(hashPair(), tunnel); + _wantACK = true; } return tunnel; } diff --git a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java index 96dbadd6cf2472a6b95dd24c4a157ced36e88a58..8fa729d63763dae195aa1321c2895fa8595478ce 100644 --- a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java +++ b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java @@ -20,12 +20,12 @@ import net.i2p.router.RouterContext; import net.i2p.util.Log; /** - * Publish the local router's RouterInfo every 5 to 10 minutes + * Publish the local router's RouterInfo periodically * */ public class PublishLocalRouterInfoJob extends JobImpl { private Log _log; - final static long PUBLISH_DELAY = 5*60*1000; // every 5 to 10 minutes (since we randomize) + final static long PUBLISH_DELAY = 20*60*1000; public PublishLocalRouterInfoJob(RouterContext ctx) { super(ctx); @@ -67,6 +67,6 @@ public class PublishLocalRouterInfoJob extends JobImpl { } catch (DataFormatException dfe) { _log.error("Error signing the updated local router info!", dfe); } - requeue(PUBLISH_DELAY + getContext().random().nextInt((int)PUBLISH_DELAY)); + requeue((PUBLISH_DELAY/2) + getContext().random().nextInt((int)PUBLISH_DELAY)); } } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index c74499f957e62a3031c992d8eac374047342484d..23ef78d86956961261ff24e9013978ee01a43bf4 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -125,6 +125,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { private final static long ROUTER_INFO_EXPIRATION_SHORT = 90*60*1000l; private final static long EXPLORE_JOB_DELAY = 10*60*1000l; + private final static long PUBLISH_JOB_DELAY = 5*60*1000l; public KademliaNetworkDatabaseFacade(RouterContext context) { _context = context; @@ -326,7 +327,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { } // periodically update and resign the router's 'published date', which basically // serves as a version - _context.jobQueue().addJob(new PublishLocalRouterInfoJob(_context)); + Job plrij = new PublishLocalRouterInfoJob(_context); + plrij.getTiming().setStartAfter(_context.clock().now() + PUBLISH_JOB_DELAY); + _context.jobQueue().addJob(plrij); try { publish(ri); } catch (IllegalArgumentException iae) { @@ -970,7 +973,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { StringBuffer buf = new StringBuffer(4*1024); buf.append("<h2>Network Database RouterInfo Lookup</h2>\n"); if (".".equals(routerPrefix)) { - renderRouterInfo(buf, _context.router().getRouterInfo(), true); + renderRouterInfo(buf, _context.router().getRouterInfo(), true, true); } else { boolean notFound = true; Set routers = getRouters(); @@ -978,7 +981,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { RouterInfo ri = (RouterInfo)iter.next(); Hash key = ri.getIdentity().getHash(); if (key.toBase64().startsWith(routerPrefix)) { - renderRouterInfo(buf, ri, false); + renderRouterInfo(buf, ri, false, true); notFound = false; } } @@ -990,7 +993,14 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { } public void renderStatusHTML(Writer out) throws IOException { - StringBuffer buf = new StringBuffer(getKnownRouters() * 2048); + renderStatusHTML(out, true); + } + + public void renderStatusHTML(Writer out, boolean full) throws IOException { + int size = getKnownRouters() * 512; + if (full) + size *= 4; + StringBuffer buf = new StringBuffer(size); buf.append("<h2>Network Database Contents</h2>\n"); if (!_initialized) { buf.append("<i>Not initialized</i>\n"); @@ -1044,10 +1054,15 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { } Hash us = _context.routerHash(); - out.write("<h3>Routers</h3>\n"); + out.write("<a name=\"routers\" /><h3>Routers (<a href=\"netdb.jsp"); + if (full) + out.write("#routers\" >view without"); + else + out.write("?f=1#routers\" >view with"); + out.write(" stats</a>)</h3>\n"); RouterInfo ourInfo = _context.router().getRouterInfo(); - renderRouterInfo(buf, ourInfo, true); + renderRouterInfo(buf, ourInfo, true, true); out.write(buf.toString()); buf.setLength(0); @@ -1061,7 +1076,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { Hash key = ri.getIdentity().getHash(); boolean isUs = key.equals(us); if (!isUs) { - renderRouterInfo(buf, ri, false); + renderRouterInfo(buf, ri, false, full); out.write(buf.toString()); buf.setLength(0); String coreVersion = ri.getOption("coreVersion"); @@ -1102,7 +1117,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { out.flush(); } - private void renderRouterInfo(StringBuffer buf, RouterInfo info, boolean isUs) { + private void renderRouterInfo(StringBuffer buf, RouterInfo info, boolean isUs, boolean full) { String hash = info.getIdentity().getHash().toBase64(); buf.append("<a name=\"").append(hash.substring(0, 6)).append("\" />"); if (isUs) { @@ -1129,13 +1144,18 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { } } buf.append("</i><br />\n"); - buf.append("Stats: <br /><i><code>\n"); - for (Iterator iter = info.getOptions().keySet().iterator(); iter.hasNext(); ) { - String key = (String)iter.next(); - String val = info.getOption(key); - buf.append(DataHelper.stripHTML(key)).append(" = ").append(DataHelper.stripHTML(val)).append("<br />\n"); + if (full) { + buf.append("Stats: <br /><i><code>\n"); + for (Iterator iter = info.getOptions().keySet().iterator(); iter.hasNext(); ) { + String key = (String)iter.next(); + String val = info.getOption(key); + buf.append(DataHelper.stripHTML(key)).append(" = ").append(DataHelper.stripHTML(val)).append("<br />\n"); + } + buf.append("</code></i>\n"); + } else { + buf.append("<a href=\"netdb.jsp?r=").append(hash.substring(0, 6)).append("\" >Full entry</a>\n"); } - buf.append("</code></i><hr />\n"); + buf.append("<hr />\n"); } } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java index 77d2427cd79e41bde1bd28cfb524994cc3da1ba3..5d0d219dbe6fe125bbb698d0bcb49724f783b114 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java @@ -65,10 +65,6 @@ class PersistentDataStore extends TransientDataStore { return super.remove(key); } - public DataStructure removeLease(Hash key) { - return super.removeLease(key); - } - public void put(Hash key, DataStructure data) { if ( (data == null) || (key == null) ) return; super.put(key, data); @@ -77,26 +73,6 @@ class PersistentDataStore extends TransientDataStore { _writer.queue(key, data); } -/* - * We don't store leasesets here anymore, use the TransientDataStore count - * - public int countLeaseSets() { - File dbDir = null; - try { - dbDir = getDbDir(); - } catch (IOException ioe) { - return 0; - } - if (dbDir == null) - return 0; - File leaseSetFiles[] = dbDir.listFiles(LeaseSetFilter.getInstance()); - if (leaseSetFiles == null) - return 0; - else - return leaseSetFiles.length; - } -*/ - private void accept(LeaseSet ls) { super.put(ls.getDestination().calculateHash(), ls); } @@ -249,18 +225,6 @@ class PersistentDataStore extends TransientDataStore { int routerCount = 0; try { File dbDir = getDbDir(); -/**** - if (getContext().router().getUptime() < 10*60*1000) { - File leaseSetFiles[] = dbDir.listFiles(LeaseSetFilter.getInstance()); - if (leaseSetFiles != null) { - for (int i = 0; i < leaseSetFiles.length; i++) { - Hash key = getLeaseSetHash(leaseSetFiles[i].getName()); - if ( (key != null) && (!isKnown(key)) ) - PersistentDataStore.this._context.jobQueue().addJob(new ReadLeaseJob(leaseSetFiles[i], key)); - } - } - } -****/ File routerInfoFiles[] = dbDir.listFiles(RouterInfoFilter.getInstance()); if (routerInfoFiles != null) { routerCount += routerInfoFiles.length; @@ -283,63 +247,6 @@ class PersistentDataStore extends TransientDataStore { } } -/**** - private class ReadLeaseJob extends JobImpl { - private File _leaseFile; - private Hash _key; - public ReadLeaseJob(File leaseFile, Hash key) { - super(PersistentDataStore.this._context); - _leaseFile = leaseFile; - _key = key; - } - public String getName() { return "Read LeaseSet"; } - private boolean shouldRead() { - DataStructure data = get(_key); - if (data == null) return true; - if (data instanceof LeaseSet) { - long knownDate = ((LeaseSet)data).getEarliestLeaseDate(); - long fileDate = _leaseFile.lastModified(); - if (fileDate > knownDate) - return true; - else - return false; - } else { - // wtf - return true; - } - } - public void runJob() { - if (!shouldRead()) return; - try { - FileInputStream fis = null; - boolean corrupt = false; - try { - fis = new FileInputStream(_leaseFile); - LeaseSet ls = new LeaseSet(); - ls.readBytes(fis); - try { - _facade.store(ls.getDestination().calculateHash(), ls); - } catch (IllegalArgumentException iae) { - _log.info("Refused locally loaded leaseSet - deleting"); - corrupt = true; - } - } catch (DataFormatException dfe) { - _log.warn("Error reading the leaseSet from " + _leaseFile.getAbsolutePath(), dfe); - corrupt = true; - } catch (FileNotFoundException fnfe) { - _log.debug("Deleted prior to read.. a race during expiration / load"); - corrupt = false; - } finally { - if (fis != null) try { fis.close(); } catch (IOException ioe) {} - } - if (corrupt) _leaseFile.delete(); - } catch (IOException ioe) { - _log.warn("Error reading the leaseSet from " + _leaseFile.getAbsolutePath(), ioe); - } - } - } -****/ - private class ReadRouterJob extends JobImpl { private File _routerFile; private Hash _key; @@ -464,31 +371,8 @@ class PersistentDataStore extends TransientDataStore { _log.info("Removed router info at " + f.getAbsolutePath()); return; } -/*** - String lsName = getLeaseSetName(key); - File f = new File(dir, lsName); - if (f.exists()) { - boolean removed = f.delete(); - if (!removed) - _log.warn("Unable to remove lease set at " + f.getAbsolutePath()); - else - _log.info("Removed lease set at " + f.getAbsolutePath()); - return; - } -***/ } -/*** - private final static class LeaseSetFilter implements FilenameFilter { - private static final FilenameFilter _instance = new LeaseSetFilter(); - public static final FilenameFilter getInstance() { return _instance; } - public boolean accept(File dir, String name) { - if (name == null) return false; - name = name.toUpperCase(); - return (name.startsWith(LEASESET_PREFIX.toUpperCase()) && name.endsWith(LEASESET_SUFFIX.toUpperCase())); - } - } -***/ private final static class RouterInfoFilter implements FilenameFilter { private static final FilenameFilter _instance = new RouterInfoFilter(); public static final FilenameFilter getInstance() { return _instance; } diff --git a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java index a43fc631195dce3c82986f26a6e469959238247b..967bc7a797bc7e175261b4f6539e5af88e722d87 100644 --- a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java @@ -28,6 +28,13 @@ import net.i2p.router.RouterContext; import net.i2p.util.Log; /** + * This used be called from StartAcceptingClientsJob but is now disabled. + * It is still called once from LoadRouterInfoJob (but not run as a Job). + * + * The following comments appear to be incorrect... + * it rebuilds if the router.info file does not exist. + * There is no check for a router.info.rebuild file. + * * If the file router.info.rebuild exists, rebuild the router info and republish. * This is useful for dhcp or other situations where the router addresses change - * simply create the router.info.rebuild file after modifying router.config and within diff --git a/router/java/src/net/i2p/router/startup/StartAcceptingClientsJob.java b/router/java/src/net/i2p/router/startup/StartAcceptingClientsJob.java index 7ad54299fb9f00847b6db258e601fdad02edb5f2..727d06ac6c1cd3a4c05815bcdc553ba7ca4f20e2 100644 --- a/router/java/src/net/i2p/router/startup/StartAcceptingClientsJob.java +++ b/router/java/src/net/i2p/router/startup/StartAcceptingClientsJob.java @@ -28,7 +28,8 @@ public class StartAcceptingClientsJob extends JobImpl { getContext().clientManager().startup(); getContext().jobQueue().addJob(new ReadConfigJob(getContext())); - getContext().jobQueue().addJob(new RebuildRouterInfoJob(getContext())); + // pointless + //getContext().jobQueue().addJob(new RebuildRouterInfoJob(getContext())); getContext().jobQueue().allowParallelOperation(); } } diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java index 1a3e0d1b630ba36680ea5206eafe8af9a57a8b0b..43120d0b08039616e0485c3a856e17c100ef166b 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java @@ -507,7 +507,7 @@ public class TunnelPoolManager implements TunnelManagerFacade { } out.write("</table>\n"); out.write("Inactive participating tunnels: " + inactive + "<br />\n"); - out.write("Lifetime bandwidth usage: " + processed + "KB<br />\n"); + out.write("Lifetime bandwidth usage: " + DataHelper.formatSize(processed*1024) + "B<br />\n"); } class TunnelComparator implements Comparator { @@ -577,7 +577,8 @@ public class TunnelPoolManager implements TunnelManagerFacade { } if (live <= 0) out.write("<b>No tunnels, waiting for the grace period to end</b><br />\n"); - out.write("Lifetime bandwidth usage: " + processedIn + "KB in, " + processedOut + "KB out<br />"); + out.write("Lifetime bandwidth usage: " + DataHelper.formatSize(processedIn*1024) + "B in, " + + DataHelper.formatSize(processedOut*1024) + "B out<br />"); } private String getCapacity(Hash peer) {