propagate from branch 'i2p.i2p.zzz.test' (head f4edeaaf6cd647f4a69847a09272b54cb51ef758)
to branch 'i2p.i2p' (head 0d7e18b693718b5924035d7a6f638ff0689af589)
This commit is contained in:
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
public abstract I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException;
|
||||
|
||||
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() + ")");
|
||||
}
|
||||
|
||||
return destSock;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
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;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultBackupQuantity;
|
||||
}
|
||||
} else {
|
||||
return defaultBackupQuantity;
|
||||
}
|
||||
} else {
|
||||
return 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.lengthVariance");
|
||||
if (len == null) return defaultVariance;
|
||||
String s = opts.getProperty(prop);
|
||||
if (s == null) return def;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultVariance;
|
||||
}
|
||||
} else {
|
||||
return defaultVariance;
|
||||
return Integer.parseInt(s);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
} else {
|
||||
return defaultVariance;
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
private String getProperty(int tunnel, String prop, String def) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
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++;
|
||||
|
@ -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);
|
||||
} 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);
|
||||
}
|
||||
|
||||
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 ("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);
|
||||
|
||||
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)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
} 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;
|
||||
|
@ -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<%
|
||||
}
|
||||
}
|
||||
%>
|
||||
%>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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" />
|
||||
|
@ -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; }
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
58
apps/routerconsole/jsp/configkeyring.jsp
Normal file
58
apps/routerconsole/jsp/configkeyring.jsp
Normal file
@ -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>
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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,26 +1114,6 @@ public class Connection {
|
||||
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
|
||||
}
|
||||
|
||||
if (numSends - 1 <= _options.getMaxResends()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Resend packet " + _packet + " time " + numSends +
|
||||
" activeResends: " + _activeResends +
|
||||
" (wsize "
|
||||
+ newWindowSize + " lifetime "
|
||||
+ (_context.clock().now() - _packet.getCreatedOn()) + "ms)");
|
||||
_outboundQueue.enqueue(_packet);
|
||||
_lastSendTime = _context.clock().now();
|
||||
}
|
||||
|
||||
// acked during resending (... or somethin')
|
||||
if ( (_packet.getAckTime() > 0) && (_packet.getNumSends() > 1) ) {
|
||||
_activeResends--;
|
||||
synchronized (_outboundPackets) {
|
||||
_outboundPackets.notifyAll();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (numSends - 1 > _options.getMaxResends()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Too many resends");
|
||||
@ -1137,11 +1127,32 @@ public class Connection {
|
||||
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 +
|
||||
" (wsize "
|
||||
+ newWindowSize + " lifetime "
|
||||
+ (_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);
|
||||
_nextSendTime = timeout + _context.clock().now();
|
||||
}
|
||||
|
||||
// acked during resending (... or somethin')
|
||||
if ( (_packet.getAckTime() > 0) && (_packet.getNumSends() > 1) ) {
|
||||
_activeResends--;
|
||||
synchronized (_outboundPackets) {
|
||||
_outboundPackets.notifyAll();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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)) )
|
||||
|
137
apps/streaming/java/src/net/i2p/client/streaming/TCBShare.java
Normal file
137
apps/streaming/java/src/net/i2p/client/streaming/TCBShare.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
13
build.xml
13
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 -->
|
||||
|
@ -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]
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
103
core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java
Normal file
103
core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
117
core/java/src/net/i2p/data/i2cp/SendMessageExpiresMessage.java
Normal file
117
core/java/src/net/i2p/data/i2cp/SendMessageExpiresMessage.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
20
core/java/src/net/i2p/util/KeyRing.java
Normal file
20
core/java/src/net/i2p/util/KeyRing.java
Normal file
@ -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 {}
|
||||
}
|
@ -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=.
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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 {}
|
||||
}
|
||||
|
103
router/java/src/net/i2p/router/PersistentKeyRing.java
Normal file
103
router/java/src/net/i2p/router/PersistentKeyRing.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 "
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
_start = getContext().clock().now();
|
||||
_overallExpiration = timeoutMs + _start;
|
||||
|
||||
// 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);
|
||||
}
|
||||
_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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user