forked from I2P_Developers/i2p.i2p
Compare commits
77 Commits
i2p_0_3_1
...
i2p_0_3_1_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61c97ab940 | ||
|
|
b0a1b3b5ca | ||
|
|
4c7af01edc | ||
|
|
0d431213cd | ||
|
|
ad9dd9a2e2 | ||
|
|
c7895ed905 | ||
|
|
57d7979d51 | ||
|
|
61f6871cd1 | ||
|
|
406048f7b9 | ||
|
|
6dd5b0fe45 | ||
|
|
fd4bc5e3cf | ||
|
|
3bab2d8957 | ||
|
|
af2f5cd2e1 | ||
|
|
d4bb32da82 | ||
|
|
08aca6ca61 | ||
|
|
697b3c6772 | ||
|
|
878525ced8 | ||
|
|
418531736b | ||
|
|
6c175440c6 | ||
|
|
723a2f2008 | ||
|
|
ea9b9fbf17 | ||
|
|
303e257841 | ||
|
|
07b6a8ba92 | ||
|
|
e216e18368 | ||
|
|
e57c5b4bc2 | ||
|
|
f772d6ddeb | ||
|
|
cd37c301d9 | ||
|
|
f5fa26639e | ||
|
|
45ec73c115 | ||
|
|
13952ebd8b | ||
|
|
2df0007a10 | ||
|
|
89bc5db3e1 | ||
|
|
4021deec7f | ||
|
|
a3977f37f7 | ||
|
|
766c12242e | ||
|
|
a82b951aff | ||
|
|
997a94eecc | ||
|
|
635535aac2 | ||
|
|
25314fd91a | ||
|
|
9a06a5758d | ||
|
|
e5a2a9644f | ||
|
|
e0e7211852 | ||
|
|
cdaeb4d176 | ||
|
|
07aa2e280d | ||
|
|
6c4bc67ff3 | ||
|
|
d9f0cc27ef | ||
|
|
59aec9d289 | ||
|
|
451f4c503d | ||
|
|
cd82089d4d | ||
|
|
3db8b63cde | ||
|
|
6edf5d1e4f | ||
|
|
a23fa6fadd | ||
|
|
51eb77e409 | ||
|
|
691326cea8 | ||
|
|
3cac1238ed | ||
|
|
b04512a4f6 | ||
|
|
3a4d0549aa | ||
|
|
d7467f5dc3 | ||
|
|
141902b86d | ||
|
|
5aa680fc93 | ||
|
|
a790117f5a | ||
|
|
2a5a52c810 | ||
|
|
2156f4c2f3 | ||
|
|
2585460286 | ||
|
|
1b4af66986 | ||
|
|
0324bac044 | ||
|
|
2bfbe1ca27 | ||
|
|
60584228d9 | ||
|
|
44e34f7b11 | ||
|
|
7912050647 | ||
|
|
2231abd407 | ||
|
|
8d17ba4d66 | ||
|
|
8244bdb440 | ||
|
|
bc3b7ffd86 | ||
|
|
e22cb62493 | ||
|
|
e923aa1f72 | ||
|
|
68a21f1fbb |
@@ -4,7 +4,6 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Responsible for actually conducting the tests, coordinating the storing of the
|
||||
|
||||
@@ -10,7 +10,6 @@ import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
|
||||
class HeartbeatMonitorGUI extends JFrame {
|
||||
private HeartbeatMonitor _monitor;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.util.List;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.axis.DateAxis;
|
||||
import org.jfree.chart.axis.NumberAxis;
|
||||
import org.jfree.chart.plot.Plot;
|
||||
import org.jfree.chart.plot.XYPlot;
|
||||
import org.jfree.chart.renderer.XYItemRenderer;
|
||||
import org.jfree.chart.renderer.XYLineAndShapeRenderer;
|
||||
import org.jfree.data.XYSeries;
|
||||
import org.jfree.data.XYSeriesCollection;
|
||||
|
||||
import net.i2p.heartbeat.PeerData;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.jfree.data.XYSeries;
|
||||
import org.jfree.data.XYSeriesCollection;
|
||||
import org.jfree.data.MovingAverage;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.plot.Plot;
|
||||
import org.jfree.chart.plot.XYPlot;
|
||||
import org.jfree.chart.axis.DateAxis;
|
||||
import org.jfree.chart.axis.NumberAxis;
|
||||
import org.jfree.chart.renderer.XYLineAndShapeRenderer;
|
||||
import org.jfree.chart.renderer.XYItemRenderer;
|
||||
import org.jfree.chart.renderer.XYDotRenderer;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import java.awt.Font;
|
||||
import java.awt.Color;
|
||||
|
||||
class JFreeChartAdapter {
|
||||
private final static Log _log = new Log(JFreeChartAdapter.class);
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JLabel;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.heartbeat.PeerDataWriter;
|
||||
import net.i2p.util.Log;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*
|
||||
|
||||
@@ -17,6 +17,8 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
private static final Log _log = new Log(I2PTunnelClient.class);
|
||||
|
||||
protected Destination dest;
|
||||
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
public I2PTunnelClient(int localPort, String destination, Logging l, boolean ownDest, EventDispatcher notifyThis) {
|
||||
super(localPort, ownDest, l, notifyThis, "SynSender");
|
||||
@@ -45,9 +47,13 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
notifyEvent("openClientResult", "ok");
|
||||
}
|
||||
|
||||
public void setReadTimeout(long ms) { readTimeout = ms; }
|
||||
public long getReadTimeout() { return readTimeout; }
|
||||
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
try {
|
||||
I2PSocket i2ps = createI2PSocket(dest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
new I2PTunnelRunner(s, i2ps, sockLock, null);
|
||||
} catch (Exception ex) {
|
||||
_log.info("Error connecting", ex);
|
||||
|
||||
@@ -33,6 +33,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
private static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
|
||||
|
||||
private static volatile long __clientId = 0;
|
||||
private long _clientId;
|
||||
protected Object sockLock = new Object(); // Guards sockMgr and mySockets
|
||||
private I2PSocketManager sockMgr;
|
||||
private List mySockets = new ArrayList();
|
||||
@@ -60,9 +62,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName) {
|
||||
super(localPort + " (uninitialized)", notifyThis);
|
||||
_clientId = ++__clientId;
|
||||
this.localPort = localPort;
|
||||
this.l = l;
|
||||
this.handlerName = handlerName;
|
||||
this.handlerName = handlerName + _clientId;
|
||||
|
||||
synchronized (sockLock) {
|
||||
if (ownDest) {
|
||||
@@ -75,7 +78,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
l.log("I2P session created");
|
||||
|
||||
Thread t = new I2PThread(this);
|
||||
t.setName("Client");
|
||||
t.setName("Client " + _clientId);
|
||||
listenerReady = false;
|
||||
t.start();
|
||||
open = true;
|
||||
@@ -179,8 +182,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
public I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
|
||||
I2PSocket i2ps;
|
||||
|
||||
i2ps = sockMgr.connect(dest, opt);
|
||||
synchronized (sockLock) {
|
||||
i2ps = sockMgr.connect(dest, opt);
|
||||
mySockets.add(i2ps);
|
||||
}
|
||||
|
||||
@@ -272,12 +275,14 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile long __runnerId = 0;
|
||||
|
||||
public class ClientConnectionRunner extends I2PThread {
|
||||
private Socket s;
|
||||
|
||||
public ClientConnectionRunner(Socket s, String name) {
|
||||
this.s = s;
|
||||
setName(name);
|
||||
setName(name + '.' + (++__runnerId));
|
||||
start();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
@@ -20,6 +23,25 @@ import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Act as a mini HTTP proxy, handling various different types of requests,
|
||||
* forwarding them through I2P appropriately, and displaying the reply. Supported
|
||||
* request formats are: <pre>
|
||||
* $method http://$site[$port]/$path $protocolVersion
|
||||
* or
|
||||
* $method $path $protocolVersion\nHost: $site
|
||||
* or
|
||||
* $method http://i2p/$site/$path $protocolVersion
|
||||
* or
|
||||
* $method /$site/$path $protocolVersion
|
||||
* </pre>
|
||||
*
|
||||
* If the $site resolves with the I2P naming service, then it is directed towards
|
||||
* that eepsite, otherwise it is directed towards this client's outproxy (typically
|
||||
* "squid.i2p"). Only HTTP is supported (no HTTPS, ftp, mailto, etc). Both GET
|
||||
* and POST have been tested, though other $methods should work.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable {
|
||||
private static final Log _log = new Log(I2PTunnelHTTPClient.class);
|
||||
|
||||
@@ -43,7 +65,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"That Desitination was not found. Perhaps you pasted in the wrong "+
|
||||
"BASE64 I2P Destination or the link you are following is bad. "+
|
||||
"The host (or the WWW proxy, if you're using one) could also be "+
|
||||
"temporarily offline. "+
|
||||
"temporarily offline. You may want to <b>retry</b>. "+
|
||||
"Could not find the following Destination:<BR><BR>")
|
||||
.getBytes();
|
||||
|
||||
@@ -53,19 +75,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"Cache-control: no-cache\r\n\r\n"+
|
||||
"<html><body><H1>I2P ERROR: TIMEOUT</H1>"+
|
||||
"That Desitination was reachable, but timed out getting a "+
|
||||
"response. This may be a temporary error, so you should simply "+
|
||||
"response. This is likely a temporary error, so you should simply "+
|
||||
"try to refresh, though if the problem persists, the remote "+
|
||||
"destination may have issues. Could not get a response from "+
|
||||
"the following Destination:<BR><BR>")
|
||||
.getBytes();
|
||||
|
||||
// public I2PTunnelHTTPClient(int localPort, Logging l,
|
||||
// boolean ownDest, String wwwProxy) {
|
||||
// this(localPort, l, ownDest, wwwProxy, (EventDispatcher)null);
|
||||
// }
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
public I2PTunnelHTTPClient(int localPort, Logging l, boolean ownDest, String wwwProxy, EventDispatcher notifyThis) {
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPHandler");
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId));
|
||||
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openHTTPClientResult", "error");
|
||||
@@ -92,7 +112,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
String line, method = null, protocol = null, host = null, destination = null;
|
||||
StringBuffer newRequest = new StringBuffer();
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Line=[" + line + "]");
|
||||
|
||||
if (line.startsWith("Connection: ") ||
|
||||
line.startsWith("Keep-Alive: ") ||
|
||||
line.startsWith("Proxy-Connection: "))
|
||||
continue;
|
||||
|
||||
if (method == null) { // first line (GET /base64/realaddr)
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Method is null for [" + line + "]");
|
||||
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) break;
|
||||
method = line.substring(0, pos);
|
||||
@@ -126,6 +157,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
// The request must be forwarded to a WWW proxy
|
||||
destination = wwwProxy;
|
||||
usingWWWProxy = true;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Host doesnt end with .i2p and it contains a period [" + host + "]: wwwProxy!");
|
||||
} else {
|
||||
request = request.substring(pos + 1);
|
||||
pos = request.indexOf("/");
|
||||
@@ -150,14 +183,25 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
_log.debug("HOST :" + host + ":");
|
||||
_log.debug("DEST :" + destination + ":");
|
||||
}
|
||||
|
||||
} else if (line.startsWith("Host: ") && !usingWWWProxy) {
|
||||
line = "Host: " + host;
|
||||
if (_log.shouldLog(Log.INFO)) _log.info("Setting host = " + host);
|
||||
|
||||
} else {
|
||||
if (line.startsWith("Host: ") && !usingWWWProxy) {
|
||||
line = "Host: " + host;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Setting host = " + host);
|
||||
}
|
||||
}
|
||||
|
||||
if (line.length() == 0) {
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
} else {
|
||||
newRequest.append(line).append("\r\n"); // HTTP spec
|
||||
}
|
||||
newRequest.append(line).append("\r\n"); // HTTP spec
|
||||
if (line.length() == 0) break;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("NewRequest header: [" + newRequest.toString() + "]");
|
||||
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i = br.read();
|
||||
if (i != -1) {
|
||||
@@ -176,6 +220,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Destination: " + destination);
|
||||
|
||||
Destination dest = I2PTunnel.destFromName(destination);
|
||||
if (dest == null) {
|
||||
l.log("Could not resolve " + destination + ".");
|
||||
@@ -211,6 +259,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
private static final long INACTIVITY_TIMEOUT = 120 * 1000;
|
||||
private static volatile long __timeoutId = 0;
|
||||
|
||||
private class InactivityTimeoutThread extends I2PThread {
|
||||
|
||||
@@ -230,7 +279,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
_targetRequest = targetRequest;
|
||||
_useWWWProxy = useWWWProxy;
|
||||
_disabled = false;
|
||||
setName("InactivityThread");
|
||||
long timeoutId = ++__timeoutId;
|
||||
setName("InactivityThread " + timeoutId);
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
|
||||
@@ -21,6 +21,8 @@ import net.i2p.util.Log;
|
||||
public class I2PTunnelRunner extends I2PThread {
|
||||
private final static Log _log = new Log(I2PTunnelRunner.class);
|
||||
|
||||
private static volatile long __runnerId;
|
||||
private long _runnerId;
|
||||
/**
|
||||
* max bytes streamed in a packet - smaller ones might be filled
|
||||
* up to this size. Larger ones are not split (at least not on
|
||||
@@ -51,7 +53,8 @@ public class I2PTunnelRunner extends I2PThread {
|
||||
lastActivityOn = -1;
|
||||
startedOn = -1;
|
||||
_log.info("I2PTunnelRunner started");
|
||||
setName("I2PTunnelRunner");
|
||||
_runnerId = ++__runnerId;
|
||||
setName("I2PTunnelRunner " + _runnerId);
|
||||
start();
|
||||
}
|
||||
|
||||
@@ -129,6 +132,8 @@ public class I2PTunnelRunner extends I2PThread {
|
||||
}
|
||||
}
|
||||
|
||||
private volatile long __forwarderId = 0;
|
||||
|
||||
private class StreamForwarder extends I2PThread {
|
||||
|
||||
InputStream in;
|
||||
@@ -137,7 +142,7 @@ public class I2PTunnelRunner extends I2PThread {
|
||||
private StreamForwarder(InputStream in, OutputStream out) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
setName("StreamForwarder");
|
||||
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
|
||||
start();
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
private Logging l;
|
||||
|
||||
private long readTimeout = -1;
|
||||
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
|
||||
/** default timeout to 3 minutes - override if desired */
|
||||
private long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis) {
|
||||
super(host + ":" + port + " <- " + privData, notifyThis);
|
||||
@@ -81,13 +83,16 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
open = true;
|
||||
}
|
||||
|
||||
|
||||
private static volatile long __serverId = 0;
|
||||
|
||||
/**
|
||||
* Start running the I2PTunnelServer.
|
||||
*
|
||||
*/
|
||||
public void startRunning() {
|
||||
Thread t = new I2PThread(this);
|
||||
t.setName("Server");
|
||||
t.setName("Server " + (++__serverId));
|
||||
t.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,71 +4,148 @@ import java.net.ConnectException;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Initial stub implementation for the server socket
|
||||
* Server socket implementation, allowing multiple threads to accept I2PSockets
|
||||
* and pull from a queue populated by various threads (each of whom have their own
|
||||
* timeout)
|
||||
*
|
||||
*/
|
||||
class I2PServerSocketImpl implements I2PServerSocket {
|
||||
private final static Log _log = new Log(I2PServerSocketImpl.class);
|
||||
private I2PSocketManager mgr;
|
||||
private I2PSocket cached = null; // buffer one socket here
|
||||
|
||||
private boolean closing = false; // Are we being closed?
|
||||
|
||||
private Object acceptLock = new Object();
|
||||
|
||||
/** list of sockets waiting for the client to accept them */
|
||||
private List pendingSockets = Collections.synchronizedList(new ArrayList(4));
|
||||
|
||||
/** have we been closed */
|
||||
private volatile boolean closing = false;
|
||||
|
||||
/** lock on this when accepting a pending socket, and wait on it for notification of acceptance */
|
||||
private Object socketAcceptedLock = new Object();
|
||||
/** lock on this when adding a new socket to the pending list, and wait on it accordingly */
|
||||
private Object socketAddedLock = new Object();
|
||||
|
||||
public I2PServerSocketImpl(I2PSocketManager mgr) {
|
||||
this.mgr = mgr;
|
||||
}
|
||||
|
||||
public synchronized I2PSocket accept() throws I2PException, ConnectException {
|
||||
I2PSocket ret;
|
||||
|
||||
synchronized (acceptLock) {
|
||||
while ((cached == null) && !closing) {
|
||||
myWait();
|
||||
}
|
||||
|
||||
if (closing) {
|
||||
throw new ConnectException("I2PServerSocket closed");
|
||||
}
|
||||
|
||||
ret = cached;
|
||||
cached = null;
|
||||
acceptLock.notifyAll();
|
||||
}
|
||||
|
||||
_log.debug("TIMING: handed out accept result " + ret.hashCode());
|
||||
|
||||
/**
|
||||
* Waits for the next socket connecting. If a remote user tried to make a
|
||||
* connection and the local application wasn't .accept()ing new connections,
|
||||
* they should get refused (if .accept() doesnt occur in some small period -
|
||||
* currently 5 seconds)
|
||||
*
|
||||
* @return a connected I2PSocket
|
||||
*
|
||||
* @throws I2PException if there is a problem with reading a new socket
|
||||
* from the data available (aka the I2PSession closed, etc)
|
||||
* @throws ConnectException if the I2PServerSocket is closed
|
||||
*/
|
||||
public I2PSocket accept() throws I2PException, ConnectException {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("accept() called, pending: " + pendingSockets.size());
|
||||
|
||||
I2PSocket ret = null;
|
||||
|
||||
while ( (ret == null) && (!closing) ){
|
||||
while (pendingSockets.size() <= 0) {
|
||||
if (closing) throw new ConnectException("I2PServerSocket closed");
|
||||
try {
|
||||
synchronized(socketAddedLock) {
|
||||
socketAddedLock.wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
synchronized (pendingSockets) {
|
||||
if (pendingSockets.size() > 0) {
|
||||
ret = (I2PSocket)pendingSockets.remove(0);
|
||||
}
|
||||
}
|
||||
if (ret != null) {
|
||||
synchronized (socketAcceptedLock) {
|
||||
socketAcceptedLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: handed out accept result " + ret.hashCode());
|
||||
return ret;
|
||||
}
|
||||
|
||||
public boolean getNewSocket(I2PSocket s) {
|
||||
synchronized (acceptLock) {
|
||||
while (cached != null) {
|
||||
myWait();
|
||||
}
|
||||
cached = s;
|
||||
acceptLock.notifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make the socket available and wait until the client app accepts it, or until
|
||||
* the given timeout elapses. This doesn't have any limits on the queue size -
|
||||
* perhaps it should add some choking (e.g. after 5 waiting for accept, refuse)
|
||||
*
|
||||
* @param timeoutMs how long to wait until accept
|
||||
* @return true if the socket was accepted, false if the timeout expired
|
||||
* or the socket was closed
|
||||
*/
|
||||
public boolean addWaitForAccept(I2PSocket s, long timeoutMs) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("addWaitForAccept [new socket arrived, pending: " + pendingSockets.size());
|
||||
|
||||
if (closing) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Already closing the socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
Clock clock = I2PAppContext.getGlobalContext().clock();
|
||||
long start = clock.now();
|
||||
long end = start + timeoutMs;
|
||||
pendingSockets.add(s);
|
||||
synchronized (socketAddedLock) {
|
||||
socketAddedLock.notifyAll();
|
||||
}
|
||||
|
||||
// keep looping until the socket has been grabbed by the accept()
|
||||
// (or the expiration passes, or the socket is closed)
|
||||
while (pendingSockets.contains(s)) {
|
||||
long now = clock.now();
|
||||
if (now >= end) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Expired while waiting for accept (time elapsed =" + (now - start) + "ms");
|
||||
pendingSockets.remove(s);
|
||||
return false;
|
||||
}
|
||||
if (closing) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Server socket closed while waiting for accept");
|
||||
pendingSockets.remove(s);
|
||||
return false;
|
||||
}
|
||||
long remaining = end - now;
|
||||
try {
|
||||
synchronized (socketAcceptedLock) {
|
||||
socketAcceptedLock.wait(remaining);
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
long now = clock.now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.info("Socket accepted after " + (now-start) + "ms");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void close() throws I2PException {
|
||||
synchronized (acceptLock) {
|
||||
closing = true;
|
||||
acceptLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public I2PSocketManager getManager() {
|
||||
return mgr;
|
||||
}
|
||||
|
||||
private void myWait() {
|
||||
try {
|
||||
acceptLock.wait();
|
||||
} catch (InterruptedException ex) {}
|
||||
|
||||
public void close() {
|
||||
closing = true;
|
||||
// let anyone .accept()ing know to fsck off
|
||||
synchronized (socketAddedLock) {
|
||||
socketAddedLock.notifyAll();
|
||||
}
|
||||
// let anyone addWaitForAccept()ing know to fsck off
|
||||
synchronized (socketAcceptedLock) {
|
||||
socketAcceptedLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public I2PSocketManager getManager() { return mgr; }
|
||||
}
|
||||
|
||||
@@ -31,8 +31,32 @@ class I2PSocketImpl implements I2PSocket {
|
||||
private I2POutputStream out;
|
||||
private boolean outgoing;
|
||||
private Object flagLock = new Object();
|
||||
private boolean closed = false, sendClose = true, closed2 = false;
|
||||
|
||||
/**
|
||||
* Whether the I2P socket has already been closed.
|
||||
*/
|
||||
private boolean closed = false;
|
||||
|
||||
/**
|
||||
* Whether to send out a close packet when the socket is
|
||||
* closed. (If the socket is closed because of an incoming close
|
||||
* packet, we need not send one.)
|
||||
*/
|
||||
private boolean sendClose = true;
|
||||
|
||||
/**
|
||||
* Whether the I2P socket has already been closed and all data
|
||||
* (from I2P to the app, dunno whether to call this incoming or
|
||||
* outgoing) has been processed.
|
||||
*/
|
||||
private boolean closed2 = false;
|
||||
|
||||
/**
|
||||
* @param peer who this socket is (or should be) connected to
|
||||
* @param mgr how we talk to the network
|
||||
* @param outgoing did we initiate the connection (true) or did we receive it (false)?
|
||||
* @param localID what is our half of the socket ID?
|
||||
*/
|
||||
public I2PSocketImpl(Destination peer, I2PSocketManager mgr, boolean outgoing, String localID) {
|
||||
this.outgoing = outgoing;
|
||||
manager = mgr;
|
||||
@@ -45,10 +69,17 @@ class I2PSocketImpl implements I2PSocket {
|
||||
this.localID = localID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our half of the socket's unique ID
|
||||
*
|
||||
*/
|
||||
public String getLocalID() {
|
||||
return localID;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received the other side's half of the socket's unique ID
|
||||
*/
|
||||
public void setRemoteID(String id) {
|
||||
synchronized (remoteIDWaiter) {
|
||||
remoteID = id;
|
||||
@@ -56,10 +87,32 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
}
|
||||
|
||||
public String getRemoteID(boolean wait) throws InterruptedIOException {
|
||||
return getRemoteID(wait, -1);
|
||||
/**
|
||||
* Retrieve the other side's half of the socket's unique ID, or null if it
|
||||
* isn't known yet
|
||||
*
|
||||
* @param wait if true, we should wait until we receive it from the peer, otherwise
|
||||
* return what we know immediately (which may be null)
|
||||
*/
|
||||
public String getRemoteID(boolean wait) {
|
||||
try {
|
||||
return getRemoteID(wait, -1);
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("wtf, we said we didn't want it to time out! you smell", iie);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the other side's half of the socket's unique ID, or null if it isn't
|
||||
* known yet and we were instructed not to wait
|
||||
*
|
||||
* @param wait should we wait for the peer to send us their half of the ID, or
|
||||
* just return immediately?
|
||||
* @param maxWait if we're going to wait, after how long should we timeout and fail?
|
||||
* (if this value is < 0, we wait indefinitely)
|
||||
* @throws InterruptedIOException when the max waiting period has been exceeded
|
||||
*/
|
||||
public String getRemoteID(boolean wait, long maxWait) throws InterruptedIOException {
|
||||
long dieAfter = System.currentTimeMillis() + maxWait;
|
||||
synchronized (remoteIDWaiter) {
|
||||
@@ -75,17 +128,30 @@ class I2PSocketImpl implements I2PSocket {
|
||||
if ((maxWait >= 0) && (System.currentTimeMillis() >= dieAfter))
|
||||
throw new InterruptedIOException("Timed out waiting for remote ID");
|
||||
|
||||
_log.debug("TIMING: RemoteID set to " + I2PSocketManager.getReadableForm(remoteID) + " for "
|
||||
+ this.hashCode());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: RemoteID set to "
|
||||
+ I2PSocketManager.getReadableForm(remoteID) + " for "
|
||||
+ this.hashCode());
|
||||
}
|
||||
return remoteID;
|
||||
}
|
||||
}
|
||||
|
||||
public String getRemoteID() throws InterruptedIOException {
|
||||
/**
|
||||
* Retrieve the other side's half of the socket's unique ID, or null if it
|
||||
* isn't known yet. This does not wait
|
||||
*
|
||||
*/
|
||||
public String getRemoteID() {
|
||||
return getRemoteID(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* The other side has given us some data, so inject it into our socket's
|
||||
* inputStream
|
||||
*
|
||||
* @param data the data to inject into our local inputStream
|
||||
*/
|
||||
public void queueData(byte[] data) {
|
||||
in.queueData(data);
|
||||
}
|
||||
@@ -121,7 +187,8 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the socket if not closed yet
|
||||
* Closes the socket if not closed yet (from the Application
|
||||
* side).
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
synchronized (flagLock) {
|
||||
@@ -132,7 +199,10 @@ class I2PSocketImpl implements I2PSocket {
|
||||
in.notifyClosed();
|
||||
}
|
||||
|
||||
public void internalClose() {
|
||||
/**
|
||||
* Close the socket from the I2P side, e. g. by a close packet.
|
||||
*/
|
||||
protected void internalClose() {
|
||||
synchronized (flagLock) {
|
||||
closed = true;
|
||||
closed2 = true;
|
||||
@@ -143,9 +213,17 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
|
||||
private byte getMask(int add) {
|
||||
return (byte) ((outgoing ? (byte) 0xA0 : (byte) 0x50) + (byte) add);
|
||||
if (outgoing)
|
||||
return (byte)(I2PSocketManager.DATA_IN + (byte)add);
|
||||
else
|
||||
return (byte)(I2PSocketManager.DATA_OUT + (byte)add);
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the longest we'll block on the input stream while waiting
|
||||
* for more data? If this value is exceeded, the read() throws
|
||||
* InterruptedIOException
|
||||
*/
|
||||
public long getReadTimeout() {
|
||||
return in.getReadTimeout();
|
||||
}
|
||||
@@ -155,7 +233,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
public class I2PInputStream extends InputStream {
|
||||
private class I2PInputStream extends InputStream {
|
||||
|
||||
private ByteCollector bc = new ByteCollector();
|
||||
|
||||
@@ -187,7 +265,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
while (read.length == 0) {
|
||||
synchronized (flagLock) {
|
||||
if (closed) {
|
||||
_log.debug("Closed is set, so closing stream: " + this.hashCode());
|
||||
_log.debug("Closed is set, so closing stream: " + hashCode());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -210,12 +288,13 @@ class I2PSocketImpl implements I2PSocket {
|
||||
System.arraycopy(read, 0, b, off, read.length);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Read from I2PInputStream " + this.hashCode() + " returned " + read.length + " bytes");
|
||||
_log.debug("Read from I2PInputStream " + hashCode() + " returned "
|
||||
+ read.length + " bytes");
|
||||
}
|
||||
//if (_log.shouldLog(Log.DEBUG)) {
|
||||
// _log.debug("Read from I2PInputStream " + this.hashCode()
|
||||
// + " returned "+read.length+" bytes:\n"
|
||||
// + HexDump.dump(read));
|
||||
// + " returned "+read.length+" bytes:\n"
|
||||
// + HexDump.dump(read));
|
||||
//}
|
||||
return read.length;
|
||||
}
|
||||
@@ -229,18 +308,24 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
|
||||
public synchronized void queueData(byte[] data, int off, int len) {
|
||||
_log.debug("Insert " + len + " bytes into queue: " + this.hashCode());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Insert " + len + " bytes into queue: " + hashCode());
|
||||
bc.append(data, off, len);
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
public synchronized void notifyClosed() {
|
||||
notifyAll();
|
||||
I2PInputStream.this.notifyAll();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
notifyClosed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class I2POutputStream extends OutputStream {
|
||||
private class I2POutputStream extends OutputStream {
|
||||
|
||||
public I2PInputStream sendTo;
|
||||
|
||||
@@ -261,74 +346,89 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
}
|
||||
|
||||
public class I2PSocketRunner extends I2PThread {
|
||||
private static volatile long __runnerId = 0;
|
||||
private class I2PSocketRunner extends I2PThread {
|
||||
|
||||
public InputStream in;
|
||||
|
||||
public I2PSocketRunner(InputStream in) {
|
||||
_log.debug("Runner's input stream is: " + in.hashCode());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Runner's input stream is: " + in.hashCode());
|
||||
this.in = in;
|
||||
setName("SocketRunner from " + I2PSocketImpl.this.remote.calculateHash().toBase64().substring(0, 4));
|
||||
String peer = I2PSocketImpl.this.remote.calculateHash().toBase64();
|
||||
setName("SocketRunner " + (++__runnerId) + " " + peer.substring(0, 4));
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pump some more data
|
||||
*
|
||||
* @return true if we should keep on handling, false otherwise
|
||||
*/
|
||||
private boolean handleNextPacket(ByteCollector bc, byte buffer[])
|
||||
throws IOException, I2PSessionException {
|
||||
int len = in.read(buffer);
|
||||
int bcsize = bc.getCurrentSize();
|
||||
if (len != -1) {
|
||||
bc.append(buffer, len);
|
||||
} else if (bcsize == 0) {
|
||||
// nothing left in the buffer, and read(..) got EOF (-1).
|
||||
// the bart the
|
||||
return false;
|
||||
}
|
||||
if ((bcsize < MAX_PACKET_SIZE) && (in.available() == 0)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Runner Point d: " + hashCode());
|
||||
|
||||
try {
|
||||
Thread.sleep(PACKET_DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
_log.warn("wtf", e);
|
||||
}
|
||||
}
|
||||
if ((bcsize >= MAX_PACKET_SIZE) || (in.available() == 0)) {
|
||||
byte[] data = bc.startToByteArray(MAX_PACKET_SIZE);
|
||||
if (data.length > 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Message size is: " + data.length);
|
||||
boolean sent = sendBlock(data);
|
||||
if (!sent) {
|
||||
_log.error("Error sending message to peer. Killing socket runner");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
byte[] buffer = new byte[MAX_PACKET_SIZE];
|
||||
ByteCollector bc = new ByteCollector();
|
||||
boolean sent = true;
|
||||
boolean keepHandling = true;
|
||||
int packetsHandled = 0;
|
||||
try {
|
||||
int len, bcsize;
|
||||
// try {
|
||||
while (true) {
|
||||
len = in.read(buffer);
|
||||
bcsize = bc.getCurrentSize();
|
||||
if (len != -1) {
|
||||
bc.append(buffer, len);
|
||||
} else if (bcsize == 0) {
|
||||
break;
|
||||
}
|
||||
if ((bcsize < MAX_PACKET_SIZE) && (in.available() == 0)) {
|
||||
_log.debug("Runner Point d: " + this.hashCode());
|
||||
|
||||
try {
|
||||
Thread.sleep(PACKET_DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if ((bcsize >= MAX_PACKET_SIZE) || (in.available() == 0)) {
|
||||
byte[] data = bc.startToByteArray(MAX_PACKET_SIZE);
|
||||
if (data.length > 0) {
|
||||
_log.debug("Message size is: " + data.length);
|
||||
sent = sendBlock(data);
|
||||
if (!sent) {
|
||||
_log.error("Error sending message to peer. Killing socket runner");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// try {
|
||||
while (keepHandling) {
|
||||
keepHandling = handleNextPacket(bc, buffer);
|
||||
packetsHandled++;
|
||||
}
|
||||
if ((bc.getCurrentSize() > 0) && sent) {
|
||||
_log.error("A SCARY MONSTER HAS EATEN SOME DATA! " + "(input stream: " + in.hashCode() + "; "
|
||||
if ((bc.getCurrentSize() > 0) && (packetsHandled > 1)) {
|
||||
_log.error("A SCARY MONSTER HAS EATEN SOME DATA! " + "(input stream: "
|
||||
+ in.hashCode() + "; "
|
||||
+ "queue size: " + bc.getCurrentSize() + ")");
|
||||
}
|
||||
synchronized (flagLock) {
|
||||
closed2 = true;
|
||||
}
|
||||
// } catch (IOException ex) {
|
||||
// if (_log.shouldLog(Log.INFO))
|
||||
// _log.info("Error reading and writing", ex);
|
||||
// }
|
||||
boolean sc;
|
||||
synchronized (flagLock) {
|
||||
sc = sendClose;
|
||||
} // FIXME: Race here?
|
||||
if (sc) {
|
||||
_log.info("Sending close packet: " + outgoing);
|
||||
byte[] packet = I2PSocketManager.makePacket((byte) (getMask(0x02)), remoteID, new byte[0]);
|
||||
synchronized (manager.getSession()) {
|
||||
sent = manager.getSession().sendMessage(remote, packet);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending close packet: " + outgoing);
|
||||
byte[] packet = I2PSocketManager.makePacket(getMask(0x02), remoteID, new byte[0]);
|
||||
boolean sent = manager.getSession().sendMessage(remote, packet);
|
||||
if (!sent) {
|
||||
_log.error("Error sending close packet to peer");
|
||||
}
|
||||
@@ -348,7 +448,8 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
|
||||
private boolean sendBlock(byte data[]) throws I2PSessionException {
|
||||
_log.debug("TIMING: Block to send for " + I2PSocketImpl.this.hashCode());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: Block to send for " + I2PSocketImpl.this.hashCode());
|
||||
if (remoteID == null) {
|
||||
_log.error("NULL REMOTEID");
|
||||
return false;
|
||||
@@ -358,9 +459,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
synchronized (flagLock) {
|
||||
if (closed2) return false;
|
||||
}
|
||||
synchronized (manager.getSession()) {
|
||||
sent = manager.getSession().sendMessage(remote, packet);
|
||||
}
|
||||
sent = manager.getSession().sendMessage(remote, packet);
|
||||
return sent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -40,11 +41,27 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
private HashMap _outSockets;
|
||||
private HashMap _inSockets;
|
||||
private I2PSocketOptions _defaultOptions;
|
||||
private long _acceptTimeout;
|
||||
|
||||
public static final short ACK = 0x51;
|
||||
public static final short CLOSE_OUT = 0x52;
|
||||
public static final short DATA_OUT = 0x50;
|
||||
public static final short SYN = 0xA1;
|
||||
public static final short CLOSE_IN = 0xA2;
|
||||
public static final short DATA_IN = 0xA0;
|
||||
public static final short CHAFF = 0xFF;
|
||||
|
||||
/**
|
||||
* How long to wait for the client app to accept() before sending back CLOSE?
|
||||
* This includes the time waiting in the queue. Currently set to 5 seconds.
|
||||
*/
|
||||
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
|
||||
|
||||
public I2PSocketManager() {
|
||||
_session = null;
|
||||
_inSockets = new HashMap(16);
|
||||
_outSockets = new HashMap(16);
|
||||
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
|
||||
}
|
||||
|
||||
public I2PSession getSession() {
|
||||
@@ -55,15 +72,25 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
_session = session;
|
||||
if (session != null) session.setSessionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* How long should we wait for the client to .accept() a socket before
|
||||
* sending back a NACK/Close?
|
||||
*
|
||||
* @param ms milliseconds to wait, maximum
|
||||
*/
|
||||
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
|
||||
public long getAcceptTimeout() { return _acceptTimeout; }
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
_log.error("Disconnected from the session");
|
||||
_log.info("Disconnected from the session");
|
||||
destroySocketManager();
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
_log.error("Error occurred: [" + message + "]", error);
|
||||
}
|
||||
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
I2PSocketImpl s;
|
||||
@@ -77,157 +104,276 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
return;
|
||||
}
|
||||
int type = msg[0] & 0xff;
|
||||
String id = new String(new byte[] { msg[1], msg[2], msg[3]}, "ISO-8859-1");
|
||||
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
|
||||
byte[] payload = new byte[msg.length - 4];
|
||||
System.arraycopy(msg, 4, payload, 0, payload.length);
|
||||
_log.debug("Message read: type = [" + Integer.toHexString(type) + "] id = [" + getReadableForm(id)
|
||||
+ "] payload length: " + payload.length + "]");
|
||||
synchronized (lock) {
|
||||
switch (type) {
|
||||
case 0x51:
|
||||
// ACK outgoing
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
if (s == null) {
|
||||
_log.warn("No socket responsible for ACK packet");
|
||||
return;
|
||||
}
|
||||
if (payload.length == 3 && s.getRemoteID(false) == null) {
|
||||
String newID = new String(payload, "ISO-8859-1");
|
||||
s.setRemoteID(newID);
|
||||
return;
|
||||
} else {
|
||||
if (payload.length != 3)
|
||||
_log.warn("Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn("Remote ID already exists? " + s.getRemoteID());
|
||||
return;
|
||||
}
|
||||
case 0x52:
|
||||
// disconnect outgoing
|
||||
_log.debug("*Disconnect outgoing!");
|
||||
try {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
if (s != null) {
|
||||
if (payload.length > 0) {
|
||||
_log.debug("Disconnect packet had "
|
||||
+ payload.length + " bytes");
|
||||
}
|
||||
if (s.getRemoteID(false) == null) {
|
||||
s.setRemoteID(null); // Just to wake up socket
|
||||
return;
|
||||
}
|
||||
s.internalClose();
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
return;
|
||||
} catch (Exception t) {
|
||||
_log.error("Ignoring error on disconnect", t);
|
||||
}
|
||||
case 0x50:
|
||||
// packet send outgoing
|
||||
_log.debug("*Packet send outgoing [" + payload.length + "]");
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.error("Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
case 0xA1:
|
||||
// SYN incoming
|
||||
_log.debug("*Syn!");
|
||||
String newLocalID = makeID(_inSockets);
|
||||
Destination d = new Destination();
|
||||
d.readBytes(new ByteArrayInputStream(payload));
|
||||
|
||||
if (_serverSocket == null) {
|
||||
// The app did not instantiate an I2PServerSocket
|
||||
byte[] packet = makePacket((byte) 0x52, id, newLocalID.getBytes("ISO-8859-1"));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.error("Error sending close to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message", new Exception("Failed creation"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
s = new I2PSocketImpl(d, this, false, newLocalID);
|
||||
s.setRemoteID(id);
|
||||
if (_serverSocket.getNewSocket(s)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket((byte) 0x51, id, newLocalID.getBytes("ISO-8859-1"));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.error("Error sending reply to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message", new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
}
|
||||
} else {
|
||||
byte[] packet = (" " + id).getBytes("ISO-8859-1");
|
||||
packet[0] = 0x52;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.error("Error sending NACK for session creation");
|
||||
}
|
||||
s.internalClose();
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Message read: type = [" + Integer.toHexString(type)
|
||||
+ "] id = [" + getReadableForm(id)
|
||||
+ "] payload length: [" + payload.length + "]");
|
||||
switch (type) {
|
||||
case ACK:
|
||||
ackAvailable(id, payload);
|
||||
return;
|
||||
case 0xA2:
|
||||
// disconnect incoming
|
||||
_log.debug("*Disconnect incoming!");
|
||||
try {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length == 0 && s != null) {
|
||||
s.internalClose();
|
||||
_inSockets.remove(id);
|
||||
return;
|
||||
} else {
|
||||
if (payload.length > 0) _log.warn("Disconnect packet had " + payload.length + " bytes");
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.error("Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
case 0xA0:
|
||||
// packet send incoming
|
||||
_log.debug("*Packet send incoming [" + payload.length + "]");
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.error("Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
case 0xFF:
|
||||
case CLOSE_OUT:
|
||||
disconnectAvailable(id, payload);
|
||||
return;
|
||||
case DATA_OUT:
|
||||
sendOutgoingAvailable(id, payload);
|
||||
return;
|
||||
case SYN:
|
||||
synIncomingAvailable(id, payload, session);
|
||||
return;
|
||||
case CLOSE_IN:
|
||||
disconnectIncoming(id, payload);
|
||||
return;
|
||||
case DATA_IN:
|
||||
sendIncoming(id, payload);
|
||||
case CHAFF:
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
_log.error("\n\n=============== Unknown packet! " + "============" + "\nType: " + (int) type
|
||||
+ "\nID: " + getReadableForm(id) + "\nBase64'ed Data: " + Base64.encode(payload)
|
||||
+ "\n\n\n");
|
||||
if (id != null) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
default:
|
||||
handleUnknown(type, id, payload);
|
||||
return;
|
||||
}
|
||||
} catch (I2PException ise) {
|
||||
_log.error("Error processing", ise);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error processing", ioe);
|
||||
} catch (IllegalStateException ise) {
|
||||
_log.debug("Error processing", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received an ACK packet (hopefully, in response to a SYN that we
|
||||
* recently sent out). Notify the associated I2PSocket that we now have
|
||||
* the remote stream ID (which should get things going, since the handshake
|
||||
* is complete).
|
||||
*
|
||||
*/
|
||||
private void ackAvailable(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
if (s == null) {
|
||||
_log.warn("No socket responsible for ACK packet");
|
||||
return;
|
||||
}
|
||||
|
||||
String remoteId = null;
|
||||
remoteId = s.getRemoteID(false);
|
||||
|
||||
if ( (payload.length == 3) && (remoteId == null) ) {
|
||||
String newID = toString(payload);
|
||||
s.setRemoteID(newID);
|
||||
return;
|
||||
} else {
|
||||
// (payload.length != 3 || getRemoteId != null)
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (payload.length != 3)
|
||||
_log.warn("Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn("Remote ID already exists? " + remoteId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a disconnect packet, telling us to tear down the specified
|
||||
* stream.
|
||||
*/
|
||||
private void disconnectAvailable(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
_log.debug("*Disconnect outgoing!");
|
||||
try {
|
||||
if (s != null) {
|
||||
if (payload.length > 0) {
|
||||
_log.debug("Disconnect packet had "
|
||||
+ payload.length + " bytes");
|
||||
}
|
||||
if (s.getRemoteID(false) == null) {
|
||||
s.setRemoteID(null); // Just to wake up socket
|
||||
return;
|
||||
}
|
||||
s.internalClose();
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (Exception t) {
|
||||
_log.error("Ignoring error on disconnect", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we created - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
// packet send outgoing
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("*Packet send outgoing [" + payload.length + "]");
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.error("Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a SYN packet (a request for a new stream). If the client has
|
||||
* said they want incoming sockets (by retrieving the serverSocket), the stream
|
||||
* will be ACKed, but if they have not, they'll be NACKed)
|
||||
*
|
||||
* @throws DataFormatException if the destination in the SYN was invalid
|
||||
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
|
||||
*/
|
||||
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
|
||||
throws DataFormatException, I2PSessionException {
|
||||
_log.debug("*Syn!");
|
||||
Destination d = new Destination();
|
||||
d.fromByteArray(payload);
|
||||
|
||||
I2PSocketImpl s = null;
|
||||
boolean acceptConnections = (_serverSocket != null);
|
||||
String newLocalID = null;
|
||||
synchronized (lock) {
|
||||
newLocalID = makeID(_inSockets);
|
||||
if (acceptConnections) {
|
||||
s = new I2PSocketImpl(d, this, false, newLocalID);
|
||||
s.setRemoteID(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!acceptConnections) {
|
||||
// The app did not instantiate an I2PServerSocket
|
||||
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.error("Error sending close to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
if (!replySentOk) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error sending reply to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
}
|
||||
} else {
|
||||
// timed out or serverSocket closed
|
||||
byte[] packet = toBytes(" " + id);
|
||||
packet[0] = CLOSE_OUT;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.warn("Error sending NACK for session creation");
|
||||
}
|
||||
s.internalClose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a disconnect for a socket we didn't initiate, so kill
|
||||
* the socket.
|
||||
*
|
||||
*/
|
||||
private void disconnectIncoming(String id, byte payload[]) {
|
||||
_log.debug("*Disconnect incoming!");
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length == 0 && s != null) {
|
||||
_inSockets.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (payload.length == 0 && s != null) {
|
||||
s.internalClose();
|
||||
return;
|
||||
} else {
|
||||
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.error("Disconnect packet had " + payload.length + " bytes");
|
||||
if (s != null)
|
||||
s.internalClose();
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.error("Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we received - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendIncoming(String id, byte payload[]) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("*Packet send incoming [" + payload.length + "]");
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
}
|
||||
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.info("Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown packet. moo.
|
||||
*
|
||||
*/
|
||||
private void handleUnknown(int type, String id, byte payload[]) {
|
||||
_log.error("\n\n=============== Unknown packet! " + "============"
|
||||
+ "\nType: " + (int) type
|
||||
+ "\nID: " + getReadableForm(id)
|
||||
+ "\nBase64'ed Data: " + Base64.encode(payload)
|
||||
+ "\n\n\n");
|
||||
if (id != null) {
|
||||
synchronized (lock) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
_log.error("Abuse reported [" + severity + "]");
|
||||
}
|
||||
@@ -258,25 +404,24 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
|
||||
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options)
|
||||
throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
String localID, lcID;
|
||||
I2PSocketImpl s;
|
||||
synchronized (lock) {
|
||||
localID = makeID(_outSockets);
|
||||
lcID = getReadableForm(localID);
|
||||
s = new I2PSocketImpl(peer, this, true, localID);
|
||||
_outSockets.put(s.getLocalID(), s);
|
||||
_outSockets.put(localID, s);
|
||||
}
|
||||
try {
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
||||
_session.getMyDestination().writeBytes(pubkey);
|
||||
String remoteID;
|
||||
byte[] packet = makePacket((byte) 0xA1, localID, pubkey.toByteArray());
|
||||
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
|
||||
boolean sent = false;
|
||||
synchronized (_session) {
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
}
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
if (!sent) {
|
||||
_log.info("Unable to send & receive ack for SYN packet");
|
||||
synchronized (lock) {
|
||||
@@ -285,32 +430,51 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
throw new I2PException("Error sending through I2P network");
|
||||
}
|
||||
remoteID = s.getRemoteID(true, options.getConnectTimeout());
|
||||
if (remoteID == null) { throw new ConnectException("Connection refused by peer"); }
|
||||
if ("".equals(remoteID)) { throw new NoRouteToHostException("Unable to reach peer"); }
|
||||
_log.debug("TIMING: s given out for remoteID " + getReadableForm(remoteID));
|
||||
|
||||
if (remoteID == null)
|
||||
throw new ConnectException("Connection refused by peer");
|
||||
if ("".equals(remoteID))
|
||||
throw new NoRouteToHostException("Unable to reach peer");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: s given out for remoteID "
|
||||
+ getReadableForm(remoteID));
|
||||
|
||||
return s;
|
||||
} catch (InterruptedIOException ioe) {
|
||||
_log.error("Timeout waiting for ack from syn for id " + getReadableForm(lcID), ioe);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Timeout waiting for ack from syn for id "
|
||||
+ getReadableForm(lcID), ioe);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw new InterruptedIOException("Timeout waiting for ack");
|
||||
} catch (ConnectException ex) {
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (NoRouteToHostException ex) {
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error sending syn on id " + getReadableForm(lcID), ex);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error sending syn on id " + getReadableForm(lcID), ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw new I2PException("Unhandled IOException occurred");
|
||||
} catch (I2PException ex) {
|
||||
_log.info("Error sending syn on id " + getReadableForm(lcID), ex);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Error sending syn on id " + getReadableForm(lcID), ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
s.internalClose();
|
||||
_log.error("Unhandled error connecting", e);
|
||||
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +488,8 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
|
||||
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
return connect(peer, null);
|
||||
}
|
||||
|
||||
@@ -334,14 +499,9 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*
|
||||
*/
|
||||
public void destroySocketManager() {
|
||||
|
||||
try {
|
||||
if (_serverSocket != null) {
|
||||
_serverSocket.close();
|
||||
_serverSocket = null;
|
||||
}
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error closing I2PServerSocket", ex);
|
||||
if (_serverSocket != null) {
|
||||
_serverSocket.close();
|
||||
_serverSocket = null;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
@@ -353,8 +513,9 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_inSockets.get(id);
|
||||
_log.debug("Closing inSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closing inSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
|
||||
@@ -362,8 +523,9 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_outSockets.get(id);
|
||||
_log.debug("Closing outSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closing outSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
}
|
||||
@@ -406,7 +568,7 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*/
|
||||
public boolean ping(Destination peer, long timeoutMs) {
|
||||
try {
|
||||
return _session.sendMessage(peer, new byte[] { (byte) 0xFF});
|
||||
return _session.sendMessage(peer, new byte[] { (byte) CHAFF});
|
||||
} catch (I2PException ex) {
|
||||
_log.error("I2PException:", ex);
|
||||
return false;
|
||||
@@ -415,8 +577,8 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
|
||||
public void removeSocket(I2PSocketImpl sock) {
|
||||
synchronized (lock) {
|
||||
_log.debug("Removing socket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Removing socket \"" + getReadableForm(sock.getLocalID()) + "\"");
|
||||
_inSockets.remove(sock.getLocalID());
|
||||
_outSockets.remove(sock.getLocalID());
|
||||
lock.notify();
|
||||
@@ -424,14 +586,9 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
}
|
||||
|
||||
public static String getReadableForm(String id) {
|
||||
try {
|
||||
if (id == null) return "(null)";
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(id.getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
if (id == null) return "(null)";
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(toBytes(id));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,22 +596,17 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*
|
||||
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
|
||||
*/
|
||||
public static String makeID(HashMap uniqueIn) {
|
||||
private static String makeID(HashMap uniqueIn) {
|
||||
String newID;
|
||||
try {
|
||||
do {
|
||||
int id = (int) (Math.random() * 16777215 + 1);
|
||||
byte[] nid = new byte[3];
|
||||
nid[0] = (byte) (id / 65536);
|
||||
nid[1] = (byte) ((id / 256) % 256);
|
||||
nid[2] = (byte) (id % 256);
|
||||
newID = new String(nid, "ISO-8859-1");
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
do {
|
||||
int id = (int) (Math.random() * 16777215 + 1);
|
||||
byte[] nid = new byte[3];
|
||||
nid[0] = (byte) (id / 65536);
|
||||
nid[1] = (byte) ((id / 256) % 256);
|
||||
nid[2] = (byte) (id % 256);
|
||||
newID = toString(nid);
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -462,17 +614,28 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
* the given payload
|
||||
*/
|
||||
public static byte[] makePacket(byte type, String id, byte[] payload) {
|
||||
byte[] packet = new byte[payload.length + 4];
|
||||
packet[0] = type;
|
||||
byte[] temp = toBytes(id);
|
||||
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
|
||||
System.arraycopy(temp, 0, packet, 1, 3);
|
||||
System.arraycopy(payload, 0, packet, 4, payload.length);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static final String toString(byte data[]) {
|
||||
try {
|
||||
byte[] packet = new byte[payload.length + 4];
|
||||
packet[0] = type;
|
||||
byte[] temp = id.getBytes("ISO-8859-1");
|
||||
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
|
||||
System.arraycopy(temp, 0, packet, 1, 3);
|
||||
System.arraycopy(payload, 0, packet, 4, payload.length);
|
||||
return packet;
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Error building the packet", ex);
|
||||
return new byte[0];
|
||||
return new String(data, "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte[] toBytes(String str) {
|
||||
try {
|
||||
return str.getBytes("ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package net.i2p.client.streaming;
|
||||
|
||||
/**
|
||||
* Define the configuration for streaming and verifying data on the socket.
|
||||
* No options available...
|
||||
*
|
||||
*/
|
||||
public class I2PSocketOptions {
|
||||
|
||||
@@ -33,57 +33,61 @@ class PeerSummaryReader {
|
||||
PeerSummary summary = null;
|
||||
String curDescription = null;
|
||||
List curArgs = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("peer\t")) {
|
||||
String name = line.substring("peer\t".length()).trim();
|
||||
summary = monitor.getSummary(name);
|
||||
if (summary == null)
|
||||
summary = new PeerSummary(name);
|
||||
} else if (line.startsWith("## ")) {
|
||||
curDescription = line.substring("## ".length()).trim();
|
||||
curArgs = new ArrayList(4);
|
||||
} else if (line.startsWith("# param ")) {
|
||||
int start = line.indexOf(':');
|
||||
String arg = line.substring(start+1).trim();
|
||||
curArgs.add(arg);
|
||||
} else {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String name = tok.nextToken();
|
||||
try {
|
||||
long when = getTime(tok.nextToken());
|
||||
boolean isDouble = false;
|
||||
List argVals = new ArrayList(curArgs.size());
|
||||
while (tok.hasMoreTokens()) {
|
||||
String val = (String)tok.nextToken();
|
||||
if (val.indexOf('.') >= 0) {
|
||||
argVals.add(new Double(val));
|
||||
isDouble = true;
|
||||
} else {
|
||||
argVals.add(new Long(val));
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("peer\t")) {
|
||||
String name = line.substring("peer\t".length()).trim();
|
||||
summary = monitor.getSummary(name);
|
||||
if (summary == null)
|
||||
summary = new PeerSummary(name);
|
||||
} else if (line.startsWith("## ")) {
|
||||
curDescription = line.substring("## ".length()).trim();
|
||||
curArgs = new ArrayList(4);
|
||||
} else if (line.startsWith("# param ")) {
|
||||
int start = line.indexOf(':');
|
||||
String arg = line.substring(start+1).trim();
|
||||
curArgs.add(arg);
|
||||
} else {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String name = tok.nextToken();
|
||||
try {
|
||||
long when = getTime(tok.nextToken());
|
||||
boolean isDouble = false;
|
||||
List argVals = new ArrayList(curArgs.size());
|
||||
while (tok.hasMoreTokens()) {
|
||||
String val = (String)tok.nextToken();
|
||||
if (val.indexOf('.') >= 0) {
|
||||
argVals.add(new Double(val));
|
||||
isDouble = true;
|
||||
} else {
|
||||
argVals.add(new Long(val));
|
||||
}
|
||||
}
|
||||
String valDescriptions[] = new String[curArgs.size()];
|
||||
for (int i = 0; i < curArgs.size(); i++)
|
||||
valDescriptions[i] = (String)curArgs.get(i);
|
||||
if (isDouble) {
|
||||
double values[] = new double[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Double)argVals.get(i)).doubleValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
} else {
|
||||
long values[] = new long[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Long)argVals.get(i)).longValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
}
|
||||
} catch (ParseException pe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", pe);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", nfe);
|
||||
}
|
||||
String valDescriptions[] = new String[curArgs.size()];
|
||||
for (int i = 0; i < curArgs.size(); i++)
|
||||
valDescriptions[i] = (String)curArgs.get(i);
|
||||
if (isDouble) {
|
||||
double values[] = new double[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Double)argVals.get(i)).doubleValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
} else {
|
||||
long values[] = new long[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Long)argVals.get(i)).longValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
}
|
||||
} catch (ParseException pe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", pe);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
_log.error("Error handling the data", e);
|
||||
throw new IOException("Error parsing the data");
|
||||
}
|
||||
if (summary == null)
|
||||
return;
|
||||
summary.coallesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
|
||||
58
apps/sam/csharp/src/SAM.NET/SAM.NET.Test/AssemblyInfo.cs
Normal file
58
apps/sam/csharp/src/SAM.NET/SAM.NET.Test/AssemblyInfo.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
//
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
//
|
||||
[assembly: AssemblyTitle("")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
//
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
//
|
||||
// In order to sign your assembly you must specify a key to use. Refer to the
|
||||
// Microsoft .NET Framework documentation for more information on assembly signing.
|
||||
//
|
||||
// Use the attributes below to control which key is used for signing.
|
||||
//
|
||||
// Notes:
|
||||
// (*) If no key is specified, the assembly is not signed.
|
||||
// (*) KeyName refers to a key that has been installed in the Crypto Service
|
||||
// Provider (CSP) on your machine. KeyFile refers to a file which contains
|
||||
// a key.
|
||||
// (*) If the KeyFile and the KeyName values are both specified, the
|
||||
// following processing occurs:
|
||||
// (1) If the KeyName can be found in the CSP, that key is used.
|
||||
// (2) If the KeyName does not exist and the KeyFile does exist, the key
|
||||
// in the KeyFile is installed into the CSP and used.
|
||||
// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
|
||||
// When specifying the KeyFile, the location of the KeyFile should be
|
||||
// relative to the project output directory which is
|
||||
// %Project Directory%\obj\<configuration>. For example, if your KeyFile is
|
||||
// located in the project directory, you would specify the AssemblyKeyFile
|
||||
// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
|
||||
// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
|
||||
// documentation for more information on this.
|
||||
//
|
||||
[assembly: AssemblyDelaySign(false)]
|
||||
[assembly: AssemblyKeyFile("")]
|
||||
[assembly: AssemblyKeyName("")]
|
||||
50
apps/sam/csharp/src/SAM.NET/SAM.NET.Test/SAM.NET.Test.cs
Normal file
50
apps/sam/csharp/src/SAM.NET/SAM.NET.Test/SAM.NET.Test.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
|
||||
namespace SAM.NET
|
||||
{
|
||||
class SAMTester
|
||||
{
|
||||
[STAThread]
|
||||
static void Main(string[] args)
|
||||
{
|
||||
new SAMTester();
|
||||
}
|
||||
public SAMTester ()
|
||||
{
|
||||
SAMConnection connection1 = new SAMConnection(IPAddress.Parse("127.0.0.1"),7656);
|
||||
SAMSession session1 = new SAMSession(connection1,SAM.NET.SamSocketType.Stream,"alice");
|
||||
|
||||
SAMConnection connection2 = new SAMConnection(IPAddress.Parse("127.0.0.1"),7656);
|
||||
SAMSession session2 = new SAMSession(connection2,SAM.NET.SamSocketType.Stream,"bob");
|
||||
|
||||
SAMStream stream1 = new SAMStream(connection1,session1,233);
|
||||
stream1.Connect(session2.getKey());
|
||||
|
||||
//Wait till we are connected to destination
|
||||
while (!stream1.isConnected)
|
||||
Thread.Sleep(1000);
|
||||
|
||||
//Send some bytes
|
||||
stream1.Write(Encoding.ASCII.GetBytes(DateTime.Now.ToLongTimeString() + "Hi!!!!!!"));
|
||||
|
||||
//Wait till a stream magically appears on the other side
|
||||
while (session2.getStreams().Count == 0) Thread.Sleep(1000);
|
||||
|
||||
Thread.Sleep(1000);
|
||||
foreach (SAMStream stream in session2.getStreams().Values)
|
||||
{
|
||||
Console.WriteLine("Text received on " + stream.getID() + " at " + DateTime.Now.ToLongTimeString());
|
||||
Console.WriteLine(Encoding.ASCII.GetString(stream.ReadToEnd()));
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
stream1.Close();
|
||||
connection1.Close();
|
||||
connection2.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
58
apps/sam/csharp/src/SAM.NET/SAM.NET/AssemblyInfo.cs
Normal file
58
apps/sam/csharp/src/SAM.NET/SAM.NET/AssemblyInfo.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
//
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
//
|
||||
[assembly: AssemblyTitle("")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
//
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
//
|
||||
// In order to sign your assembly you must specify a key to use. Refer to the
|
||||
// Microsoft .NET Framework documentation for more information on assembly signing.
|
||||
//
|
||||
// Use the attributes below to control which key is used for signing.
|
||||
//
|
||||
// Notes:
|
||||
// (*) If no key is specified, the assembly is not signed.
|
||||
// (*) KeyName refers to a key that has been installed in the Crypto Service
|
||||
// Provider (CSP) on your machine. KeyFile refers to a file which contains
|
||||
// a key.
|
||||
// (*) If the KeyFile and the KeyName values are both specified, the
|
||||
// following processing occurs:
|
||||
// (1) If the KeyName can be found in the CSP, that key is used.
|
||||
// (2) If the KeyName does not exist and the KeyFile does exist, the key
|
||||
// in the KeyFile is installed into the CSP and used.
|
||||
// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
|
||||
// When specifying the KeyFile, the location of the KeyFile should be
|
||||
// relative to the project output directory which is
|
||||
// %Project Directory%\obj\<configuration>. For example, if your KeyFile is
|
||||
// located in the project directory, you would specify the AssemblyKeyFile
|
||||
// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
|
||||
// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
|
||||
// documentation for more information on this.
|
||||
//
|
||||
[assembly: AssemblyDelaySign(false)]
|
||||
[assembly: AssemblyKeyFile("")]
|
||||
[assembly: AssemblyKeyName("")]
|
||||
271
apps/sam/csharp/src/SAM.NET/SAM.NET/SAM.NET.cs
Normal file
271
apps/sam/csharp/src/SAM.NET/SAM.NET/SAM.NET.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
|
||||
namespace SAM.NET
|
||||
{
|
||||
public enum SamSocketType
|
||||
{
|
||||
Stream,
|
||||
Datagram,
|
||||
Raw
|
||||
}
|
||||
|
||||
public class SAMConnection
|
||||
{
|
||||
private const string propertyMinVersion = "1.0";
|
||||
private const string propertyMaxVersion = "1.0";
|
||||
|
||||
private Socket _sock;
|
||||
private NetworkStream _sockStream;
|
||||
private StreamReader _sockStreamIn;
|
||||
private StreamWriter _sockStreamOut;
|
||||
|
||||
public SAMConnection(IPAddress routerIP, int port)
|
||||
{
|
||||
_sock = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
|
||||
IPEndPoint rEP = new IPEndPoint(routerIP,port);
|
||||
_sock.Connect(rEP);
|
||||
_sockStream = new NetworkStream(_sock);
|
||||
_sockStreamIn = new StreamReader(_sockStream);
|
||||
_sockStreamOut = new StreamWriter(_sockStream);
|
||||
try
|
||||
{
|
||||
sendVersion(propertyMinVersion,propertyMinVersion);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sock.Close();
|
||||
throw (new Exception("No SAM for you :("));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void sendVersion(string min, string max)
|
||||
{
|
||||
_sockStreamOut.WriteLine("HELLO VERSION MIN=" + propertyMinVersion + " MAX=" + propertyMaxVersion);
|
||||
_sockStreamOut.Flush();
|
||||
Hashtable response = SAMUtil.parseKeyValues(_sockStreamIn.ReadLine(),2);
|
||||
if (response["RESULT"].ToString() != "OK") throw (new Exception("Version mismatch"));
|
||||
}
|
||||
|
||||
public StreamWriter getOutputStream()
|
||||
{
|
||||
return _sockStreamOut;
|
||||
}
|
||||
|
||||
public StreamReader getInputStream()
|
||||
{
|
||||
return _sockStreamIn;
|
||||
}
|
||||
|
||||
public NetworkStream getStream()
|
||||
{
|
||||
return _sockStream;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_sock.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Creating a SAMSession object will automatically:
|
||||
* 1) create a sesion on SAM
|
||||
* 1) query for the base64key
|
||||
* 2) start a listening thread to catch all stream commands
|
||||
*/
|
||||
public class SAMSession
|
||||
{
|
||||
private Hashtable _streams;
|
||||
private string _sessionKey;
|
||||
|
||||
public SAMSession (SAMConnection connection, SamSocketType type, string destination)
|
||||
{
|
||||
_streams = new Hashtable();
|
||||
StreamWriter writer = connection.getOutputStream();
|
||||
StreamReader reader = connection.getInputStream();
|
||||
writer.WriteLine("SESSION CREATE STYLE=STREAM DESTINATION=" + destination);
|
||||
writer.Flush();
|
||||
Hashtable response = SAMUtil.parseKeyValues(reader.ReadLine(),2);
|
||||
if (response["RESULT"].ToString() != "OK")
|
||||
{
|
||||
throw (new Exception(response["MESSAGE"].ToString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteLine("NAMING LOOKUP NAME=ME");
|
||||
writer.Flush();
|
||||
response = SAMUtil.parseKeyValues(reader.ReadLine(),2);
|
||||
_sessionKey = response["VALUE"].ToString();
|
||||
SAMSessionListener listener = new SAMSessionListener(connection,this,_streams);
|
||||
new Thread(new ThreadStart(listener.startListening)).Start();
|
||||
}
|
||||
}
|
||||
public void addStream(SAMStream stream)
|
||||
{
|
||||
_streams.Add(stream.getID(),stream);
|
||||
}
|
||||
public string getKey()
|
||||
{
|
||||
return _sessionKey;
|
||||
}
|
||||
public Hashtable getStreams()
|
||||
{
|
||||
return _streams;
|
||||
}
|
||||
}
|
||||
|
||||
public class SAMSessionListener
|
||||
{
|
||||
private Hashtable _streams;
|
||||
private SAMConnection _connection;
|
||||
private SAMSession _session;
|
||||
private bool stayAlive = true;
|
||||
|
||||
public SAMSessionListener(SAMConnection connection,SAMSession session, Hashtable streams)
|
||||
{
|
||||
_streams = streams;
|
||||
_connection = connection;
|
||||
_session = session;
|
||||
}
|
||||
public void startListening()
|
||||
{
|
||||
StreamReader reader = _connection.getInputStream();
|
||||
while (stayAlive)
|
||||
{
|
||||
string response = reader.ReadLine();
|
||||
if (response.StartsWith("STREAM STATUS"))
|
||||
{
|
||||
Hashtable values = SAMUtil.parseKeyValues(response,2);
|
||||
SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())];
|
||||
if (theStream != null) theStream.ReceivedStatus(values);
|
||||
}
|
||||
if (response.StartsWith("STREAM CONNECTED"))
|
||||
{
|
||||
Hashtable values = SAMUtil.parseKeyValues(response,2);
|
||||
SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())];
|
||||
if (theStream != null) theStream.isConnected = true;
|
||||
}
|
||||
if (response.StartsWith("STREAM RECEIVED"))
|
||||
{
|
||||
Hashtable values = SAMUtil.parseKeyValues(response,2);
|
||||
int streamID = int.Parse(values["ID"].ToString());
|
||||
SAMStream theStream = (SAMStream)_streams[streamID];
|
||||
if (theStream == null) new SAMStream(_connection,_session,streamID);
|
||||
theStream = (SAMStream)_streams[streamID];
|
||||
theStream.ReceivedData(int.Parse(values["SIZE"].ToString()));
|
||||
}
|
||||
if (response.StartsWith("STREAM CLOSE"))
|
||||
{
|
||||
Hashtable values = SAMUtil.parseKeyValues(response,2);
|
||||
SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())];
|
||||
if (theStream != null) theStream.isConnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SAMStream
|
||||
{
|
||||
private int _ID;
|
||||
private byte[] _data;
|
||||
private int _position=0;
|
||||
private int _size=0;
|
||||
private SAMSession _session;
|
||||
private SAMConnection _connection;
|
||||
public bool isConnected=false;
|
||||
|
||||
public SAMStream (SAMConnection connection,SAMSession session, int ID)
|
||||
{
|
||||
_data = new byte[100000]; //FIXME: change to non-static structure for storing stream data
|
||||
_ID = ID;
|
||||
_connection = connection;
|
||||
_session = session;
|
||||
_session.addStream(this);
|
||||
}
|
||||
|
||||
public void Connect(string destination)
|
||||
{
|
||||
StreamWriter writer = _connection.getOutputStream();
|
||||
writer.WriteLine("STREAM CONNECT ID=" + _ID.ToString() + " DESTINATION=" + destination);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
public void ReceivedData(int size) //FIXME: WTF is going on when reading the payload here? All zeros and way too many of them.
|
||||
{
|
||||
NetworkStream stream = _connection.getStream();
|
||||
int bytesRead = stream.Read(_data,_size,size);
|
||||
_size = _size + bytes;
|
||||
}
|
||||
|
||||
public void ReceivedStatus(Hashtable response)
|
||||
{
|
||||
if (response["RESULT"].ToString() != "OK")
|
||||
{
|
||||
throw (new Exception(response["RESULT"].ToString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
isConnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public int getID() {return _ID;}
|
||||
|
||||
public bool DataAvailable()
|
||||
{
|
||||
return _position != _size;
|
||||
}
|
||||
|
||||
public void Write(byte[] buf)
|
||||
{
|
||||
NetworkStream stream = _connection.getStream();
|
||||
int sent = 0;
|
||||
while (sent < buf.Length)
|
||||
{
|
||||
int toSend = Math.Min(buf.Length - sent,32768);
|
||||
string header = "STREAM SEND ID=" + _ID.ToString() + " SIZE=" + toSend.ToString() + "\n";
|
||||
byte[] headerbytes = Encoding.ASCII.GetBytes(header);
|
||||
stream.Write(headerbytes,0,headerbytes.Length);
|
||||
stream.Write(buf,sent,toSend);
|
||||
sent = sent + toSend;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] ReadToEnd()
|
||||
{
|
||||
byte[] ret = new byte[_size - _position];
|
||||
Array.Copy(_data,_position,ret,0,_size - _position);
|
||||
_position = _size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
StreamWriter writer = _connection.getOutputStream();
|
||||
writer.WriteLine("STREAM CLOSE " + _ID.ToString());
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public class SAMUtil
|
||||
{
|
||||
public static Hashtable parseKeyValues(string str, int startingWord)
|
||||
{
|
||||
Hashtable hash = new Hashtable();
|
||||
string strTruncated = string.Join(" ",str.Split(' '),startingWord,str.Split(' ').Length - startingWord);
|
||||
string[] sets = strTruncated.Split('=',' ');
|
||||
for (int i=0; i<sets.Length; i=i+2)
|
||||
{
|
||||
hash.Add(sets[i],sets[i+1]);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,28 @@ package net.i2p.sam;
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Properties;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* SAM bridge implementation.
|
||||
@@ -23,15 +38,25 @@ import net.i2p.util.Log;
|
||||
* @author human
|
||||
*/
|
||||
public class SAMBridge implements Runnable {
|
||||
|
||||
private final static Log _log = new Log(SAMBridge.class);
|
||||
private ServerSocket serverSocket;
|
||||
private Properties i2cpProps;
|
||||
/**
|
||||
* filename in which the name to private key mapping should
|
||||
* be stored (and loaded from)
|
||||
*/
|
||||
private String persistFilename;
|
||||
/**
|
||||
* app designated destination name to the base64 of the I2P formatted
|
||||
* destination keys (Destination+PrivateKey+SigningPrivateKey)
|
||||
*/
|
||||
private Map nameToPrivKeys = Collections.synchronizedMap(new HashMap(8));
|
||||
|
||||
private boolean acceptConnections = true;
|
||||
|
||||
private final static int SAM_LISTENPORT = 7656;
|
||||
|
||||
private static final int SAM_LISTENPORT = 7656;
|
||||
public static final String DEFAULT_SAM_KEYFILE = "sam.keys";
|
||||
|
||||
private SAMBridge() {}
|
||||
|
||||
/**
|
||||
@@ -40,8 +65,11 @@ public class SAMBridge implements Runnable {
|
||||
* @param listenHost hostname to listen for SAM connections on ("0.0.0.0" for all)
|
||||
* @param listenPort port number to listen for SAM connections on
|
||||
* @param i2cpProps set of I2CP properties for finding and communicating with the router
|
||||
* @param persistFile location to store/load named keys to/from
|
||||
*/
|
||||
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps) {
|
||||
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) {
|
||||
persistFilename = persistFile;
|
||||
loadKeys();
|
||||
try {
|
||||
if ( (listenHost != null) && !("0.0.0.0".equals(listenHost)) ) {
|
||||
serverSocket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
|
||||
@@ -63,6 +91,97 @@ public class SAMBridge implements Runnable {
|
||||
this.i2cpProps = i2cpProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the destination associated with the given name
|
||||
*
|
||||
* @return null if the name does not exist, or if it is improperly formatted
|
||||
*/
|
||||
public Destination getDestination(String name) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(val);
|
||||
return d;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error retrieving the destination from " + name, dfe);
|
||||
nameToPrivKeys.remove(name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the I2P private keystream for the given name, formatted
|
||||
* as a base64 string (Destination+PrivateKey+SessionPrivateKey, as I2CP
|
||||
* stores it).
|
||||
*
|
||||
* @return null if the name does not exist, else the stream
|
||||
*/
|
||||
public String getKeystream(String name) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the given keystream should be used for the given name
|
||||
*
|
||||
*/
|
||||
public void addKeystream(String name, String stream) {
|
||||
nameToPrivKeys.put(name, stream);
|
||||
storeKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load up the keys from the persistFilename
|
||||
*
|
||||
*/
|
||||
private synchronized void loadKeys() {
|
||||
Map keys = new HashMap(16);
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(persistFilename);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
int eq = line.indexOf('=');
|
||||
String name = line.substring(0, eq);
|
||||
String privKeys = line.substring(eq+1);
|
||||
keys.put(name, privKeys);
|
||||
}
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
_log.warn("Key file does not exist at " + persistFilename);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to read the keys from " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
nameToPrivKeys = Collections.synchronizedMap(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current keys to disk in the location specified on creation
|
||||
*
|
||||
*/
|
||||
private synchronized void storeKeys() {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(persistFilename);
|
||||
for (Iterator iter = nameToPrivKeys.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String privKeys = (String)nameToPrivKeys.get(name);
|
||||
out.write(name.getBytes());
|
||||
out.write('=');
|
||||
out.write(privKeys.getBytes());
|
||||
out.write('\n');
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <pre>SAMBridge [[listenHost ]listenPort[ name=val]*]</pre>
|
||||
@@ -72,15 +191,17 @@ public class SAMBridge implements Runnable {
|
||||
* depth, etc.
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String keyfile = DEFAULT_SAM_KEYFILE;
|
||||
int port = SAM_LISTENPORT;
|
||||
String host = "0.0.0.0";
|
||||
Properties opts = null;
|
||||
if (args.length > 0) {
|
||||
int portIndex = 0;
|
||||
keyfile = args[0];
|
||||
int portIndex = 1;
|
||||
try {
|
||||
port = Integer.parseInt(args[portIndex]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
host = args[0];
|
||||
host = args[1];
|
||||
portIndex++;
|
||||
try {
|
||||
port = Integer.parseInt(args[portIndex]);
|
||||
@@ -91,7 +212,7 @@ public class SAMBridge implements Runnable {
|
||||
}
|
||||
opts = parseOptions(args, portIndex+1);
|
||||
}
|
||||
SAMBridge bridge = new SAMBridge(host, port, opts);
|
||||
SAMBridge bridge = new SAMBridge(host, port, opts, keyfile);
|
||||
I2PThread t = new I2PThread(bridge, "SAMListener");
|
||||
t.start();
|
||||
}
|
||||
@@ -114,7 +235,8 @@ public class SAMBridge implements Runnable {
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.err.println("Usage: SAMBridge [listenHost listenPortNum[ name=val]*]");
|
||||
System.err.println("Usage: SAMBridge [keyfile [listenHost] listenPortNum[ name=val]*]");
|
||||
System.err.println(" keyfile: location to persist private keys (default sam.keys)");
|
||||
System.err.println(" listenHost: interface to listen on (0.0.0.0 for all interfaces)");
|
||||
System.err.println(" listenPort: port to listen for SAM connections on (default 7656)");
|
||||
System.err.println(" name=val: options to pass when connecting via I2CP, such as ");
|
||||
@@ -140,10 +262,18 @@ public class SAMBridge implements Runnable {
|
||||
} catch (IOException e) {}
|
||||
continue;
|
||||
}
|
||||
handler.setBridge(this);
|
||||
handler.startHandling();
|
||||
} catch (SAMException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("SAM error: " + e.getMessage(), e);
|
||||
try {
|
||||
String reply = "HELLO REPLY RESULT=I2P_ERROR MESSAGE=\"" + e.getMessage() + "\"\n";
|
||||
s.getOutputStream().write(reply.getBytes("ISO-8859-1"));
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("SAM Error sending error reply", ioe);
|
||||
}
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import net.i2p.util.Log;
|
||||
public class SAMDatagramSession extends SAMMessageSession {
|
||||
|
||||
private final static Log _log = new Log(SAMDatagramSession.class);
|
||||
public static int DGRAM_SIZE_MAX = 31*1024;
|
||||
|
||||
private SAMDatagramReceiver recv = null;
|
||||
|
||||
@@ -43,7 +44,8 @@ public class SAMDatagramSession extends SAMMessageSession {
|
||||
* @param recv Object that will receive incoming data
|
||||
*/
|
||||
public SAMDatagramSession(String dest, Properties props,
|
||||
SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
|
||||
SAMDatagramReceiver recv) throws IOException,
|
||||
DataFormatException, I2PSessionException {
|
||||
super(dest, props);
|
||||
|
||||
this.recv = recv;
|
||||
@@ -58,7 +60,8 @@ public class SAMDatagramSession extends SAMMessageSession {
|
||||
* @param recv Object that will receive incoming data
|
||||
*/
|
||||
public SAMDatagramSession(InputStream destStream, Properties props,
|
||||
SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
|
||||
SAMDatagramReceiver recv) throws IOException,
|
||||
DataFormatException, I2PSessionException {
|
||||
super(destStream, props);
|
||||
|
||||
this.recv = recv;
|
||||
@@ -73,6 +76,9 @@ public class SAMDatagramSession extends SAMMessageSession {
|
||||
* @return True if the data was sent, false otherwise
|
||||
*/
|
||||
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
|
||||
if (data.length > DGRAM_SIZE_MAX)
|
||||
throw new DataFormatException("Datagram size exceeded (" + data.length + ")");
|
||||
|
||||
byte[] dgram = dgramMaker.makeI2PDatagram(data);
|
||||
|
||||
return sendBytesThroughMessageSession(dest, dgram);
|
||||
|
||||
@@ -30,6 +30,7 @@ public abstract class SAMHandler implements Runnable {
|
||||
private final static Log _log = new Log(SAMHandler.class);
|
||||
|
||||
protected I2PThread thread = null;
|
||||
protected SAMBridge bridge = null;
|
||||
|
||||
private Object socketWLock = new Object(); // Guards writings on socket
|
||||
private Socket socket = null;
|
||||
@@ -71,6 +72,8 @@ public abstract class SAMHandler implements Runnable {
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void setBridge(SAMBridge bridge) { this.bridge = bridge; }
|
||||
|
||||
/**
|
||||
* Actually handle the SAM protocol.
|
||||
*
|
||||
@@ -124,7 +127,9 @@ public abstract class SAMHandler implements Runnable {
|
||||
*
|
||||
*/
|
||||
protected final void closeClientSocket() throws IOException {
|
||||
socket.close();
|
||||
if (socket != null)
|
||||
socket.close();
|
||||
socket = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,8 +32,8 @@ public class SAMHandlerFactory {
|
||||
*
|
||||
* @param s Socket attached to SAM client
|
||||
* @param i2cpProps config options for our i2cp connection
|
||||
*
|
||||
* @return A SAM protocol handler
|
||||
* @throws SAMException if the connection handshake (HELLO message) was malformed
|
||||
* @return A SAM protocol handler, or null if the client closed before the handshake
|
||||
*/
|
||||
public static SAMHandler createSAMHandler(Socket s, Properties i2cpProps) throws SAMException {
|
||||
BufferedReader br;
|
||||
@@ -66,8 +66,8 @@ public class SAMHandlerFactory {
|
||||
{
|
||||
String opcode;
|
||||
if (!(opcode = tok.nextToken()).equals("VERSION")) {
|
||||
throw new SAMException("Unrecognized HELLO message opcode: \""
|
||||
+ opcode + "\"");
|
||||
throw new SAMException("Unrecognized HELLO message opcode: '"
|
||||
+ opcode + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,22 +88,8 @@ public class SAMHandlerFactory {
|
||||
}
|
||||
|
||||
String ver = chooseBestVersion(minVer, maxVer);
|
||||
if (ver == null) {
|
||||
// Let's answer negatively
|
||||
try {
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO REPLY RESULT=NOVERSION\n".getBytes("ISO-8859-1"));
|
||||
return null;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
_log.error("Caught UnsupportedEncodingException ("
|
||||
+ e.getMessage() + ")");
|
||||
throw new SAMException("Character encoding error: "
|
||||
+ e.getMessage());
|
||||
} catch (IOException e) {
|
||||
throw new SAMException("Error reading from socket: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
if (ver == null)
|
||||
throw new SAMException("No version specified");
|
||||
|
||||
// Let's answer positively
|
||||
try {
|
||||
@@ -135,8 +121,8 @@ public class SAMHandlerFactory {
|
||||
throw new SAMException("BUG! (in handler instantiation)");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
_log.error("IOException caught during SAM handler instantiation");
|
||||
return null;
|
||||
_log.error("Error creating the v1 handler", e);
|
||||
throw new SAMException("IOException caught during SAM handler instantiation");
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import net.i2p.util.Log;
|
||||
public class SAMRawSession extends SAMMessageSession {
|
||||
|
||||
private final static Log _log = new Log(SAMRawSession.class);
|
||||
public static final int RAW_SIZE_MAX = 32*1024;
|
||||
|
||||
private SAMRawReceiver recv = null;
|
||||
/**
|
||||
@@ -64,6 +65,8 @@ public class SAMRawSession extends SAMMessageSession {
|
||||
* @return True if the data was sent, false otherwise
|
||||
*/
|
||||
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
|
||||
if (data.length > RAW_SIZE_MAX)
|
||||
throw new DataFormatException("Data size limit exceeded (" + data.length + ")");
|
||||
return sendBytesThroughMessageSession(dest, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
@@ -100,11 +101,23 @@ public class SAMStreamSession {
|
||||
allprops.putAll(System.getProperties());
|
||||
allprops.putAll(props);
|
||||
|
||||
// FIXME: we should setup I2CP host and port, too
|
||||
String i2cpHost = allprops.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
|
||||
int i2cpPort = 7654;
|
||||
String port = allprops.getProperty(I2PClient.PROP_TCP_PORT, "7654");
|
||||
try {
|
||||
i2cpPort = Integer.parseInt(port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new SAMException("Invalid I2CP port specified [" + port + "]");
|
||||
}
|
||||
// streams MUST be mode=guaranteed (though i think the socket manager
|
||||
// enforces this anyway...
|
||||
allprops.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
|
||||
_log.debug("Creating I2PSocketManager...");
|
||||
socketMgr = I2PSocketManagerFactory.createManager(destStream,
|
||||
"127.0.0.1",
|
||||
7654, allprops);
|
||||
i2cpHost,
|
||||
i2cpPort,
|
||||
allprops);
|
||||
if (socketMgr == null) {
|
||||
throw new SAMException("Error creating I2PSocketManager");
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import net.i2p.I2PAppContext;
|
||||
public class SAMUtils {
|
||||
|
||||
private final static Log _log = new Log(SAMUtils.class);
|
||||
private static I2PAppContext _context = new I2PAppContext();
|
||||
|
||||
/**
|
||||
* Generate a random destination key
|
||||
@@ -86,7 +85,7 @@ public class SAMUtils {
|
||||
* @return the Destination for the specified hostname, or null if not found
|
||||
*/
|
||||
public static Destination lookupHost(String name, OutputStream pubKey) {
|
||||
NamingService ns = _context.namingService();
|
||||
NamingService ns = I2PAppContext.getGlobalContext().namingService();
|
||||
Destination dest = ns.lookup(name);
|
||||
|
||||
if ((pubKey != null) && (dest != null)) {
|
||||
@@ -109,9 +108,10 @@ public class SAMUtils {
|
||||
*
|
||||
* @param tok A StringTokenizer pointing to the SAM parameters
|
||||
*
|
||||
* @return Properties with the parsed SAM params, or null if none is found
|
||||
* @throws SAMException if the data was formatted incorrectly
|
||||
* @return Properties with the parsed SAM params
|
||||
*/
|
||||
public static Properties parseParams(StringTokenizer tok) {
|
||||
public static Properties parseParams(StringTokenizer tok) throws SAMException {
|
||||
int pos, nprops = 0, ntoks = tok.countTokens();
|
||||
String token, param, value;
|
||||
Properties props = new Properties();
|
||||
@@ -122,7 +122,7 @@ public class SAMUtils {
|
||||
pos = token.indexOf("=");
|
||||
if (pos == -1) {
|
||||
_log.debug("Error in params format");
|
||||
return null;
|
||||
throw new SAMException("Bad formatting for param [" + token + "]");
|
||||
}
|
||||
param = token.substring(0, pos);
|
||||
value = token.substring(pos + 1);
|
||||
@@ -135,22 +135,18 @@ public class SAMUtils {
|
||||
_log.debug("Parsed properties: " + dumpProperties(props));
|
||||
}
|
||||
|
||||
if (nprops != 0) {
|
||||
return props;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
/* Dump a Properties object in an human-readable form */
|
||||
private static String dumpProperties(Properties props) {
|
||||
Enumeration enum = props.propertyNames();
|
||||
Enumeration names = props.propertyNames();
|
||||
String msg = "";
|
||||
String key, val;
|
||||
boolean firstIter = true;
|
||||
|
||||
while (enum.hasMoreElements()) {
|
||||
key = (String)enum.nextElement();
|
||||
while (names.hasMoreElements()) {
|
||||
key = (String)names.nextElement();
|
||||
val = props.getProperty(key);
|
||||
|
||||
if (!firstIter) {
|
||||
|
||||
@@ -108,7 +108,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
break;
|
||||
}
|
||||
|
||||
msg = buf.toString("ISO-8859-1");
|
||||
msg = buf.toString("ISO-8859-1").trim();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("New message received: " + msg);
|
||||
}
|
||||
@@ -127,8 +127,6 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
+ "\"; opcode: \"" + opcode + "\")");
|
||||
}
|
||||
props = SAMUtils.parseParams(tok);
|
||||
if (i2cpProps != null)
|
||||
props.putAll(i2cpProps); // make sure we've got the i2cp settings
|
||||
|
||||
if (domain.equals("STREAM")) {
|
||||
canContinue = execStreamMessage(opcode, props);
|
||||
@@ -137,6 +135,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
} else if (domain.equals("RAW")) {
|
||||
canContinue = execRawMessage(opcode, props);
|
||||
} else if (domain.equals("SESSION")) {
|
||||
if (i2cpProps != null)
|
||||
props.putAll(i2cpProps); // make sure we've got the i2cp settings
|
||||
canContinue = execSessionMessage(opcode, props);
|
||||
} else if (domain.equals("DEST")) {
|
||||
canContinue = execDestMessage(opcode, props);
|
||||
@@ -154,10 +154,10 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
_log.error("Caught UnsupportedEncodingException ("
|
||||
+ e.getMessage() + ")");
|
||||
+ e.getMessage() + ")", e);
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException ("
|
||||
+ e.getMessage() + ")");
|
||||
+ e.getMessage() + ")", e);
|
||||
} catch (Exception e) {
|
||||
_log.error("Unexpected exception", e);
|
||||
} finally {
|
||||
@@ -189,76 +189,91 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
if ((rawSession != null) || (datagramSession != null)
|
||||
|| (streamSession != null)) {
|
||||
_log.debug("Trying to create a session, but one still exists");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n");
|
||||
}
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in SESSION CREATE message");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n");
|
||||
}
|
||||
|
||||
dest = props.getProperty("DESTINATION");
|
||||
if (dest == null) {
|
||||
_log.debug("SESSION DESTINATION parameter not specified");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n");
|
||||
}
|
||||
props.remove("DESTINATION");
|
||||
|
||||
|
||||
String destKeystream = null;
|
||||
|
||||
if (dest.equals("TRANSIENT")) {
|
||||
_log.debug("TRANSIENT destination requested");
|
||||
ByteArrayOutputStream priv = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream priv = new ByteArrayOutputStream(640);
|
||||
SAMUtils.genRandomKey(priv, null);
|
||||
|
||||
dest = Base64.encode(priv.toByteArray());
|
||||
destKeystream = Base64.encode(priv.toByteArray());
|
||||
} else {
|
||||
destKeystream = bridge.getKeystream(dest);
|
||||
if (destKeystream == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Custom destination specified [" + dest + "] but it isnt know, creating a new one");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(640);
|
||||
SAMUtils.genRandomKey(baos, null);
|
||||
destKeystream = Base64.encode(baos.toByteArray());
|
||||
bridge.addKeystream(dest, destKeystream);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Custom destination specified [" + dest + "] and it is already known");
|
||||
}
|
||||
}
|
||||
|
||||
String style = props.getProperty("STYLE");
|
||||
if (style == null) {
|
||||
_log.debug("SESSION STYLE parameter not specified");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n");
|
||||
}
|
||||
props.remove("STYLE");
|
||||
|
||||
if (style.equals("RAW")) {
|
||||
rawSession = new SAMRawSession(dest, props, this);
|
||||
rawSession = new SAMRawSession(destKeystream, props, this);
|
||||
} else if (style.equals("DATAGRAM")) {
|
||||
datagramSession = new SAMDatagramSession(dest, props,this);
|
||||
datagramSession = new SAMDatagramSession(destKeystream, props,this);
|
||||
} else if (style.equals("STREAM")) {
|
||||
String dir = props.getProperty("DIRECTION");
|
||||
if (dir == null) {
|
||||
_log.debug("No DIRECTION parameter in STREAM session");
|
||||
return false;
|
||||
_log.debug("No DIRECTION parameter in STREAM session, defaulting to BOTH");
|
||||
dir = "BOTH";
|
||||
}
|
||||
if (!dir.equals("CREATE") && !dir.equals("RECEIVE")
|
||||
&& !dir.equals("BOTH")) {
|
||||
_log.debug("Unknow DIRECTION parameter value: " + dir);
|
||||
return false;
|
||||
_log.debug("Unknow DIRECTION parameter value: [" + dir + "]");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n");
|
||||
}
|
||||
props.remove("DIRECTION");
|
||||
|
||||
streamSession = new SAMStreamSession(dest, dir,props,this);
|
||||
streamSession = new SAMStreamSession(destKeystream, dir,props,this);
|
||||
} else {
|
||||
_log.debug("Unrecognized SESSION STYLE: \"" + style +"\"");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n");
|
||||
}
|
||||
return writeString("SESSION STATUS RESULT=OK DESTINATION="
|
||||
+ dest + "\n");
|
||||
} else {
|
||||
_log.debug("Unrecognized SESSION message opcode: \""
|
||||
+ opcode + "\"");
|
||||
return false;
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n");
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
_log.debug("Invalid destination specified");
|
||||
return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
} catch (I2PSessionException e) {
|
||||
_log.debug("I2P error when instantiating session", e);
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
} catch (SAMException e) {
|
||||
_log.error("Unexpected SAM error", e);
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
} catch (IOException e) {
|
||||
_log.error("Unexpected IOException", e);
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +281,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
private boolean execDestMessage(String opcode, Properties props) {
|
||||
|
||||
if (opcode.equals("GENERATE")) {
|
||||
if (props != null) {
|
||||
if (props.size() > 0) {
|
||||
_log.debug("Properties specified in DEST GENERATE message");
|
||||
return false;
|
||||
}
|
||||
@@ -483,159 +498,171 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
if (opcode.equals("SEND")) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM SEND ID specified: " + strid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int size;
|
||||
{
|
||||
String strsize = props.getProperty("SIZE");
|
||||
if (strsize == null) {
|
||||
_log.debug("Size not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
size = Integer.parseInt(strsize);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM SEND size specified: "+strsize);
|
||||
return false;
|
||||
}
|
||||
if (!checkSize(size)) {
|
||||
_log.debug("Specified size (" + size
|
||||
+ ") is out of protocol limits");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(getClientSocketInputStream());
|
||||
byte[] data = new byte[size];
|
||||
|
||||
in.readFully(data);
|
||||
|
||||
if (!streamSession.sendBytes(id, data)) {
|
||||
_log.error("STREAM SEND failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (EOFException e) {
|
||||
_log.debug("Too few bytes with RAW SEND message (expected: "
|
||||
+ size);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException while parsing RAW SEND message",
|
||||
e);
|
||||
return false;
|
||||
}
|
||||
return execStreamSend(props);
|
||||
} else if (opcode.equals("CONNECT")) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CONNECT message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
if (id < 1) {
|
||||
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
props.remove("ID");
|
||||
}
|
||||
|
||||
String dest = props.getProperty("DESTINATION");
|
||||
if (dest == null) {
|
||||
_log.debug("Destination not specified in RAW SEND message");
|
||||
return false;
|
||||
}
|
||||
props.remove("DESTINATION");
|
||||
|
||||
try {
|
||||
if (!streamSession.connect(id, dest, props)) {
|
||||
_log.debug("STREAM connection failed");
|
||||
return false;
|
||||
}
|
||||
return writeString("STREAM STATUS RESULT=OK ID=" + id + "\n");
|
||||
} catch (DataFormatException e) {
|
||||
_log.debug("Invalid destination in STREAM CONNECT message");
|
||||
return writeString("STREAM STATUS RESULT=INVALID_KEY ID="
|
||||
+ id + "\n");
|
||||
} catch (SAMInvalidDirectionException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=INVALID_DIRECTION ID="
|
||||
+ id + "\n");
|
||||
} catch (ConnectException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=CONNECTION_REFUSED ID="
|
||||
+ id + "\n");
|
||||
} catch (NoRouteToHostException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=CANT_REACH_PEER ID="
|
||||
+ id + "\n");
|
||||
} catch (InterruptedIOException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=TIMEOUT ID="
|
||||
+ id + "\n");
|
||||
} catch (I2PException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
|
||||
+ id + "\n");
|
||||
}
|
||||
return execStreamConnect(props);
|
||||
} else if (opcode.equals("CLOSE")) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CLOSE message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM CLOSE message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM CLOSE ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return streamSession.closeConnection(id);
|
||||
return execStreamClose(props);
|
||||
} else {
|
||||
_log.debug("Unrecognized RAW message opcode: \""
|
||||
+ opcode + "\"");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execStreamSend(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM SEND ID specified: " + strid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int size;
|
||||
{
|
||||
String strsize = props.getProperty("SIZE");
|
||||
if (strsize == null) {
|
||||
_log.debug("Size not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
size = Integer.parseInt(strsize);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM SEND size specified: "+strsize);
|
||||
return false;
|
||||
}
|
||||
if (!checkSize(size)) {
|
||||
_log.debug("Specified size (" + size
|
||||
+ ") is out of protocol limits");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(getClientSocketInputStream());
|
||||
byte[] data = new byte[size];
|
||||
|
||||
in.readFully(data);
|
||||
|
||||
if (!streamSession.sendBytes(id, data)) {
|
||||
_log.error("STREAM SEND failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (EOFException e) {
|
||||
_log.debug("Too few bytes with RAW SEND message (expected: "
|
||||
+ size);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException while parsing RAW SEND message",
|
||||
e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execStreamConnect(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CONNECT message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM SEND message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
if (id < 1) {
|
||||
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
props.remove("ID");
|
||||
}
|
||||
|
||||
String dest = props.getProperty("DESTINATION");
|
||||
if (dest == null) {
|
||||
_log.debug("Destination not specified in RAW SEND message");
|
||||
return false;
|
||||
}
|
||||
props.remove("DESTINATION");
|
||||
|
||||
try {
|
||||
if (!streamSession.connect(id, dest, props)) {
|
||||
_log.debug("STREAM connection failed");
|
||||
return false;
|
||||
}
|
||||
return writeString("STREAM STATUS RESULT=OK ID=" + id + "\n");
|
||||
} catch (DataFormatException e) {
|
||||
_log.debug("Invalid destination in STREAM CONNECT message");
|
||||
return writeString("STREAM STATUS RESULT=INVALID_KEY ID="
|
||||
+ id + "\n");
|
||||
} catch (SAMInvalidDirectionException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=INVALID_DIRECTION ID="
|
||||
+ id + "\n");
|
||||
} catch (ConnectException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=CONNECTION_REFUSED ID="
|
||||
+ id + "\n");
|
||||
} catch (NoRouteToHostException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=CANT_REACH_PEER ID="
|
||||
+ id + "\n");
|
||||
} catch (InterruptedIOException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=TIMEOUT ID="
|
||||
+ id + "\n");
|
||||
} catch (I2PException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
|
||||
+ id + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execStreamClose(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CLOSE message");
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
{
|
||||
String strid = props.getProperty("ID");
|
||||
if (strid == null) {
|
||||
_log.debug("ID not specified in STREAM CLOSE message");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
id = Integer.parseInt(strid);
|
||||
} catch (NumberFormatException e) {
|
||||
_log.debug("Invalid STREAM CLOSE ID specified: " +strid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return streamSession.closeConnection(id);
|
||||
}
|
||||
|
||||
/* Check whether a size is inside the limits allowed by this protocol */
|
||||
private boolean checkSize(int size) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestCreateSessionDatagram {
|
||||
private static Log _log = new Log(TestCreateSessionDatagram.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testTransient(samHost, samPort, conOptions);
|
||||
testNewDest(samHost, samPort, conOptions);
|
||||
testOldDest(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testTransient(String host, int port, String conOptions) {
|
||||
testDest(host, port, conOptions, "TRANSIENT");
|
||||
_log.debug("\n\nTest of transient complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
private static void testNewDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
}
|
||||
private static void testOldDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of initial contact for " + destName + " complete, waiting 90 seconds");
|
||||
try { Thread.sleep(90*1000); } catch (InterruptedException ie) {}
|
||||
_log.debug("now testing subsequent contact\n\n\n");
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of subsequent contact complete\n\n");
|
||||
}
|
||||
|
||||
private static void testDest(String host, int port, String conOptions, String destName) {
|
||||
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=DATAGRAM DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0"; // "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 tunnels.inboundDepth=0";
|
||||
if (args.length > 0) {
|
||||
conOptions = "";
|
||||
for (int i = 0; i < args.length; i++)
|
||||
conOptions = conOptions + " " + args[i];
|
||||
}
|
||||
try {
|
||||
TestUtil.startupBridge(6000);
|
||||
runTest("localhost", 6000, conOptions);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
78
apps/sam/java/test/net/i2p/sam/TestCreateSessionRaw.java
Normal file
78
apps/sam/java/test/net/i2p/sam/TestCreateSessionRaw.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestCreateSessionRaw {
|
||||
private static Log _log = new Log(TestCreateSessionRaw.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testTransient(samHost, samPort, conOptions);
|
||||
testNewDest(samHost, samPort, conOptions);
|
||||
testOldDest(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testTransient(String host, int port, String conOptions) {
|
||||
testDest(host, port, conOptions, "TRANSIENT");
|
||||
_log.debug("\n\nTest of transient complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
private static void testNewDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
}
|
||||
private static void testOldDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of initial contact for " + destName + " complete, waiting 90 seconds");
|
||||
try { Thread.sleep(90*1000); } catch (InterruptedException ie) {}
|
||||
_log.debug("now testing subsequent contact\n\n\n");
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of subsequent contact complete\n\n");
|
||||
}
|
||||
|
||||
private static void testDest(String host, int port, String conOptions, String destName) {
|
||||
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=RAW DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 tunnels.inboundDepth=0";
|
||||
if (args.length > 0) {
|
||||
conOptions = "";
|
||||
for (int i = 0; i < args.length; i++)
|
||||
conOptions = conOptions + " " + args[i];
|
||||
}
|
||||
try {
|
||||
TestUtil.startupBridge(6000);
|
||||
runTest("localhost", 6000, conOptions);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
78
apps/sam/java/test/net/i2p/sam/TestCreateSessionStream.java
Normal file
78
apps/sam/java/test/net/i2p/sam/TestCreateSessionStream.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestCreateSessionStream {
|
||||
private static Log _log = new Log(TestCreateSessionStream.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testTransient(samHost, samPort, conOptions);
|
||||
testNewDest(samHost, samPort, conOptions);
|
||||
testOldDest(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testTransient(String host, int port, String conOptions) {
|
||||
testDest(host, port, conOptions, "TRANSIENT");
|
||||
_log.debug("\n\nTest of transient complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
private static void testNewDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
}
|
||||
private static void testOldDest(String host, int port, String conOptions) {
|
||||
String destName = "Alice" + Math.random();
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of initial contact for " + destName + " complete, waiting 90 seconds");
|
||||
try { Thread.sleep(90*1000); } catch (InterruptedException ie) {}
|
||||
_log.debug("now testing subsequent contact\n\n\n");
|
||||
testDest(host, port, conOptions, destName);
|
||||
_log.debug("\n\nTest of subsequent contact complete\n\n");
|
||||
}
|
||||
|
||||
private static void testDest(String host, int port, String conOptions, String destName) {
|
||||
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
if (args.length > 0) {
|
||||
conOptions = "";
|
||||
for (int i = 0; i < args.length; i++)
|
||||
conOptions = conOptions + " " + args[i];
|
||||
}
|
||||
try {
|
||||
TestUtil.startupBridge(6000);
|
||||
runTest("localhost", 6000, conOptions);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
106
apps/sam/java/test/net/i2p/sam/TestDatagramTransfer.java
Normal file
106
apps/sam/java/test/net/i2p/sam/TestDatagramTransfer.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.sam.SAMUtils;
|
||||
|
||||
public class TestDatagramTransfer {
|
||||
private static Log _log = new Log(TestCreateSessionDatagram.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testTransfer(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testTransfer(String host, int port, String conOptions) {
|
||||
String destName = "TRANSIENT";
|
||||
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=DATAGRAM DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "NAMING LOOKUP NAME=ME\n";
|
||||
out.write(lookup.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the lookup for ME: " + line);
|
||||
_log.debug("The above should be a NAMING REPLY");
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
String value = props.getProperty("VALUE");
|
||||
if (value == null) {
|
||||
_log.error("No value for ME found! [" + line + "]");
|
||||
return;
|
||||
} else {
|
||||
_log.info("Alice is located at " + value);
|
||||
}
|
||||
|
||||
String send = "DATAGRAM SEND DESTINATION=" + value + " SIZE=3\nYo!";
|
||||
out.write(send.getBytes());
|
||||
line = reader.readLine();
|
||||
tok = new StringTokenizer(line);
|
||||
maj = tok.nextToken();
|
||||
min = tok.nextToken();
|
||||
props = SAMUtils.parseParams(tok);
|
||||
String size = props.getProperty("SIZE");
|
||||
String from = props.getProperty("DESTINATION");
|
||||
if ( (value == null) || (size == null) ||
|
||||
(!from.equals(value)) || (!size.equals("3")) ) {
|
||||
_log.error("Reply of the datagram is incorrect: [" + line + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[] = new char[3];
|
||||
int read = reader.read(buf);
|
||||
if (read != 3) {
|
||||
_log.error("Unable to read the full datagram");
|
||||
return;
|
||||
}
|
||||
if (new String(buf).equals("Yo!")) {
|
||||
_log.info("Received payload successfully");
|
||||
} else {
|
||||
_log.error("Payload is incorrect! [" + new String(buf) + "]");
|
||||
}
|
||||
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0"; // "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 tunnels.inboundDepth=0";
|
||||
if (args.length > 0) {
|
||||
conOptions = "";
|
||||
for (int i = 0; i < args.length; i++)
|
||||
conOptions = conOptions + " " + args[i];
|
||||
}
|
||||
try {
|
||||
TestUtil.startupBridge(6000);
|
||||
runTest("localhost", 6000, conOptions);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
62
apps/sam/java/test/net/i2p/sam/TestDest.java
Normal file
62
apps/sam/java/test/net/i2p/sam/TestDest.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestDest {
|
||||
private static Log _log = new Log(TestDest.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
test(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void test(String host, int port, String conOptions) {
|
||||
_log.info("\n\nTesting a DEST generate (should come back with 'DEST REPLY PUB=val PRIV=val')\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=testNaming " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.debug("Response to creating the session with destination testNaming: " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "DEST GENERATE\n";
|
||||
out.write(lookup.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the dest generate: " + line);
|
||||
_log.debug("The abouve should be a DEST REPLY");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0"; // "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
if (args.length > 0) {
|
||||
conOptions = "";
|
||||
for (int i = 0; i < args.length; i++)
|
||||
conOptions = conOptions + " " + args[i];
|
||||
}
|
||||
try {
|
||||
TestUtil.startupBridge(6000);
|
||||
runTest("localhost", 6000, conOptions);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
76
apps/sam/java/test/net/i2p/sam/TestHello.java
Normal file
76
apps/sam/java/test/net/i2p/sam/TestHello.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestHello {
|
||||
private static Log _log = new Log(TestHello.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort) {
|
||||
testValidVersion(samHost, samPort);
|
||||
testInvalidVersion(samHost, samPort);
|
||||
testCorruptLine(samHost, samPort);
|
||||
}
|
||||
|
||||
private static void testValidVersion(String host, int port) {
|
||||
_log.info("\n\nTesting valid version (should come back with an OK)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.info("line read for valid version: " + line);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testInvalidVersion(String host, int port) {
|
||||
_log.info("\n\nTesting invalid version (should come back with an error)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=9.0 MAX=8.3\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.info("line read for invalid version: " + line);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void testCorruptLine(String host, int port) {
|
||||
_log.info("\n\nTesting corrupt line (should come back with an error)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO h0 h0 h0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.info("line read for valid version: " + line);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
TestUtil.startupBridge(6000);
|
||||
runTest("localhost", 6000);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
82
apps/sam/java/test/net/i2p/sam/TestNaming.java
Normal file
82
apps/sam/java/test/net/i2p/sam/TestNaming.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestNaming {
|
||||
private static Log _log = new Log(TestNaming.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testMe(samHost, samPort, conOptions);
|
||||
testDuck(samHost, samPort, conOptions);
|
||||
testUnknown(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testMe(String host, int port, String conOptions) {
|
||||
testName(host, port, conOptions, "ME");
|
||||
_log.debug("\n\nTest of ME complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
private static void testDuck(String host, int port, String conOptions) {
|
||||
testName(host, port, conOptions, "duck.i2p");
|
||||
_log.debug("\n\nTest of duck complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
private static void testUnknown(String host, int port, String conOptions) {
|
||||
testName(host, port, conOptions, "www.odci.gov");
|
||||
_log.debug("\n\nTest of unknown host complete\n\n\n");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
private static void testName(String host, int port, String conOptions, String name) {
|
||||
_log.info("\n\nTesting a name lookup (should come back with 'NAMING REPLY RESULT=OK VALUE=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=testNaming " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.debug("Response to creating the session with destination testNaming: " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "NAMING LOOKUP NAME=" + name + "\n";
|
||||
out.write(lookup.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the lookup for [" + name +"]: " + line);
|
||||
_log.debug("The abouve should be a NAMING REPLY");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
if (args.length > 0) {
|
||||
conOptions = "";
|
||||
for (int i = 0; i < args.length; i++)
|
||||
conOptions = conOptions + " " + args[i];
|
||||
}
|
||||
try {
|
||||
TestUtil.startupBridge(6000);
|
||||
runTest("localhost", 6000, conOptions);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
109
apps/sam/java/test/net/i2p/sam/TestRawTransfer.java
Normal file
109
apps/sam/java/test/net/i2p/sam/TestRawTransfer.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.sam.SAMUtils;
|
||||
|
||||
public class TestRawTransfer {
|
||||
private static Log _log = new Log(TestCreateSessionDatagram.class);
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
testTransfer(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void testTransfer(String host, int port, String conOptions) {
|
||||
String destName = "TRANSIENT";
|
||||
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=RAW DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "NAMING LOOKUP NAME=ME\n";
|
||||
out.write(lookup.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the lookup for ME: " + line);
|
||||
_log.debug("The above should be a NAMING REPLY");
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
String value = props.getProperty("VALUE");
|
||||
if (value == null) {
|
||||
_log.error("No value for ME found! [" + line + "]");
|
||||
return;
|
||||
} else {
|
||||
_log.info("Alice is located at " + value);
|
||||
}
|
||||
|
||||
String send = "RAW SEND DESTINATION=" + value + " SIZE=3\nYo!";
|
||||
out.write(send.getBytes());
|
||||
line = reader.readLine();
|
||||
try {
|
||||
tok = new StringTokenizer(line);
|
||||
maj = tok.nextToken();
|
||||
min = tok.nextToken();
|
||||
props = SAMUtils.parseParams(tok);
|
||||
} catch (Exception e) {
|
||||
_log.error("Error parsing response line: [" + line + "]", e);
|
||||
return;
|
||||
}
|
||||
String size = props.getProperty("SIZE");
|
||||
if ( (size == null) || (!size.equals("3")) ) {
|
||||
_log.error("Reply of the datagram is incorrect: [" + line + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[] = new char[3];
|
||||
int read = reader.read(buf);
|
||||
if (read != 3) {
|
||||
_log.error("Unable to read the full datagram");
|
||||
return;
|
||||
}
|
||||
if (new String(buf).equals("Yo!")) {
|
||||
_log.info("Rec8eived payload successfully");
|
||||
} else {
|
||||
_log.error("Payload is incorrect! [" + new String(buf) + "]");
|
||||
}
|
||||
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 tunnels.inboundDepth=0";
|
||||
if (args.length > 0) {
|
||||
conOptions = "";
|
||||
for (int i = 0; i < args.length; i++)
|
||||
conOptions = conOptions + " " + args[i];
|
||||
}
|
||||
try {
|
||||
TestUtil.startupBridge(6000);
|
||||
runTest("localhost", 6000, conOptions);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
223
apps/sam/java/test/net/i2p/sam/TestStreamTransfer.java
Normal file
223
apps/sam/java/test/net/i2p/sam/TestStreamTransfer.java
Normal file
@@ -0,0 +1,223 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* <ol>
|
||||
* <li>start up SAM</li>
|
||||
* <li>Alice connects as 'Alice', gets her destination, stashes it away, and
|
||||
* listens for any streams, echoing back whatever she receives.</li>
|
||||
* <li>Bob connects as 'Bob', establishes a stream to the destination Alice
|
||||
* stashed away, sends a few bundles of data, and closes the stream.</li>
|
||||
* <li>Alice and Bob disconnect from SAM</li>
|
||||
* <li>SAM bridge taken down</li>
|
||||
* </ol>
|
||||
*/
|
||||
public class TestStreamTransfer {
|
||||
private static Log _log = new Log(TestStreamTransfer.class);
|
||||
private static String _alice = null;
|
||||
private static boolean _dead = false;
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
startAlice(samHost, samPort, conOptions);
|
||||
testBob(samHost, samPort, conOptions);
|
||||
}
|
||||
|
||||
private static void startAlice(String host, int port, String conOptions) {
|
||||
_log.info("\n\nStarting up Alice");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=Alice " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination Alice: " + line);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
String value = props.getProperty("VALUE");
|
||||
if (value == null) {
|
||||
_log.error("No value for ME found! [" + line + "]");
|
||||
return;
|
||||
} else {
|
||||
_log.info("Alice is located at " + value);
|
||||
}
|
||||
_alice = value;
|
||||
I2PThread aliceThread = new I2PThread(new AliceRunner(reader, out, s));
|
||||
aliceThread.setName("Alice");
|
||||
aliceThread.start();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class AliceRunner implements Runnable {
|
||||
private BufferedReader _reader;
|
||||
private OutputStream _out;
|
||||
private Socket _s;
|
||||
/** ID (string) to base64 destination */
|
||||
private Map _streams;
|
||||
public AliceRunner(BufferedReader reader, OutputStream out, Socket s) {
|
||||
_reader = reader;
|
||||
_out = out;
|
||||
_s = s;
|
||||
_streams = Collections.synchronizedMap(new HashMap(4));
|
||||
}
|
||||
public void run() {
|
||||
while (!_dead) {
|
||||
try {
|
||||
doRun();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error running alice", e);
|
||||
try { _reader.close(); } catch (IOException ioe) {}
|
||||
try { _out.close(); } catch (IOException ioe) {}
|
||||
try { _s.close(); } catch (IOException ioe) {}
|
||||
_streams.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
private void doRun() throws IOException, SAMException {
|
||||
String line = _reader.readLine();
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
if ( ("STREAM".equals(maj)) && ("CONNECTED".equals(min)) ) {
|
||||
String dest = props.getProperty("DESTINATION");
|
||||
String id = props.getProperty("ID");
|
||||
if ( (dest == null) || (id == null) ) {
|
||||
_log.error("Invalid STREAM CONNECTED line: [" + line + "]");
|
||||
return;
|
||||
}
|
||||
dest = dest.trim();
|
||||
id = id.trim();
|
||||
_streams.put(id, dest);
|
||||
} else if ( ("STREAM".equals(maj)) && ("CLOSED".equals(min)) ) {
|
||||
String id = props.getProperty("ID");
|
||||
if (id == null) {
|
||||
_log.error("Invalid STREAM CLOSED line: [" + line + "]");
|
||||
return;
|
||||
}
|
||||
_streams.remove(id);
|
||||
} else if ( ("STREAM".equals(maj)) && ("RECEIVED".equals(min)) ) {
|
||||
String id = props.getProperty("ID");
|
||||
String size = props.getProperty("SIZE");
|
||||
if ( (id == null) || (size == null) ) {
|
||||
_log.error("Invalid STREAM RECEIVED line: [" + line + "]");
|
||||
return;
|
||||
}
|
||||
id = id.trim();
|
||||
size = size.trim();
|
||||
int payloadSize = -1;
|
||||
try {
|
||||
payloadSize = Integer.parseInt(size);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Invalid SIZE in message [" + size + "]");
|
||||
return;
|
||||
}
|
||||
// i know, its bytes, but this test uses chars
|
||||
char payload[] = new char[payloadSize];
|
||||
int read = _reader.read(payload);
|
||||
if (read != payloadSize) {
|
||||
_log.error("Incorrect size read - expected " + payloadSize + " got " + read);
|
||||
return;
|
||||
}
|
||||
_log.info("Received from the stream " + id + ": [" + new String(payload) + "]");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
// now echo it back
|
||||
String reply = "STREAM SEND ID=" + id +
|
||||
" SIZE=" + payloadSize +
|
||||
"\n" + payload;
|
||||
_out.write(reply.getBytes());
|
||||
_out.flush();
|
||||
} else {
|
||||
_log.error("Received unsupported type [" + maj + "/"+ min + "]");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void testBob(String host, int port, String conOptions) {
|
||||
_log.info("\n\nTesting Bob\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=Bob " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination Bob: " + line);
|
||||
req = "STREAM CONNECT ID=42 DESTINATION=" + _alice + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to the stream connect from Bob to Alice: " + line);
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
String result = props.getProperty("RESULT");
|
||||
if (!("OK".equals(result))) {
|
||||
_log.error("Unable to connect!");
|
||||
_dead = true;
|
||||
return;
|
||||
}
|
||||
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
|
||||
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
|
||||
out.write(req.getBytes());
|
||||
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}
|
||||
req = "STREAM CLOSE ID=42\n";
|
||||
out.write(req.getBytes());
|
||||
try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
|
||||
_dead = true;
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
|
||||
if (args.length > 0) {
|
||||
conOptions = "";
|
||||
for (int i = 0; i < args.length; i++)
|
||||
conOptions = conOptions + " " + args[i];
|
||||
}
|
||||
try {
|
||||
TestUtil.startupBridge(6000);
|
||||
runTest("localhost", 6000, conOptions);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
apps/sam/java/test/net/i2p/sam/TestUtil.java
Normal file
9
apps/sam/java/test/net/i2p/sam/TestUtil.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
|
||||
public class TestUtil {
|
||||
public static void startupBridge(int listenPort) {
|
||||
// Usage: SAMBridge [listenHost listenPortNum[ name=val]*]
|
||||
SAMBridge.main(new String[] { "0.0.0.0", listenPort+"" });
|
||||
}
|
||||
}
|
||||
41
apps/time/java/build.xml
Normal file
41
apps/time/java/build.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="time">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src" debug="true" destdir="./build/obj" includes="**/*.java" classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/timestamper.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.time.Timestamper" />
|
||||
<attribute name="Class-Path" value="i2p.jar timestamper.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
access="package"
|
||||
splitindex="true"
|
||||
windowtitle="I2P timestamper" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
145
apps/time/java/src/net/i2p/time/NtpClient.java
Normal file
145
apps/time/java/src/net/i2p/time/NtpClient.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package net.i2p.time;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
|
||||
/**
|
||||
* NtpClient - an NTP client for Java. This program connects to an NTP server
|
||||
* and prints the response to the console.
|
||||
*
|
||||
* The local clock offset calculation is implemented according to the SNTP
|
||||
* algorithm specified in RFC 2030.
|
||||
*
|
||||
* Note that on windows platforms, the curent time-of-day timestamp is limited
|
||||
* to an resolution of 10ms and adversely affects the accuracy of the results.
|
||||
*
|
||||
*
|
||||
* This code is copyright (c) Adam Buckley 2004
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version. A HTML version of the GNU General Public License can be
|
||||
* seen at http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* @author Adam Buckley
|
||||
* (minor refactoring by jrandom)
|
||||
*/
|
||||
public class NtpClient {
|
||||
/** difference between the unix epoch and jan 1 1900 (NTP uses that) */
|
||||
private final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
|
||||
private final static int NTP_PORT = 123;
|
||||
|
||||
/**
|
||||
* Query the ntp servers, returning the current time from first one we find
|
||||
*
|
||||
* @return milliseconds since january 1, 1970 (UTC)
|
||||
* @throws IllegalArgumentException if none of the servers are reachable
|
||||
*/
|
||||
public static long currentTime(String serverNames[]) {
|
||||
if (serverNames == null)
|
||||
throw new IllegalArgumentException("No NTP servers specified");
|
||||
for (int i = 0; i < serverNames.length; i++) {
|
||||
long now = currentTime(serverNames[i]);
|
||||
if (now > 0)
|
||||
return now;
|
||||
}
|
||||
throw new IllegalArgumentException("No reachable NTP servers specified");
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the given NTP server, returning the current internet time
|
||||
*
|
||||
* @return milliseconds since january 1, 1970 (UTC), or -1 on error
|
||||
*/
|
||||
public static long currentTime(String serverName) {
|
||||
try {
|
||||
// Send request
|
||||
DatagramSocket socket = new DatagramSocket();
|
||||
InetAddress address = InetAddress.getByName(serverName);
|
||||
byte[] buf = new NtpMessage().toByteArray();
|
||||
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, NTP_PORT);
|
||||
|
||||
// Set the transmit timestamp *just* before sending the packet
|
||||
// ToDo: Does this actually improve performance or not?
|
||||
NtpMessage.encodeTimestamp(packet.getData(), 40,
|
||||
(System.currentTimeMillis()/1000.0)
|
||||
+ SECONDS_1900_TO_EPOCH);
|
||||
|
||||
socket.send(packet);
|
||||
|
||||
// Get response
|
||||
packet = new DatagramPacket(buf, buf.length);
|
||||
socket.setSoTimeout(10*1000);
|
||||
try {
|
||||
socket.receive(packet);
|
||||
} catch (InterruptedIOException iie) {
|
||||
socket.close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Immediately record the incoming timestamp
|
||||
double destinationTimestamp = (System.currentTimeMillis()/1000.0) + SECONDS_1900_TO_EPOCH;
|
||||
|
||||
// Process response
|
||||
NtpMessage msg = new NtpMessage(packet.getData());
|
||||
double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
|
||||
(msg.receiveTimestamp-msg.transmitTimestamp);
|
||||
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
|
||||
(msg.transmitTimestamp - destinationTimestamp)) / 2;
|
||||
socket.close();
|
||||
|
||||
//System.out.println("host: " + serverName + " rtt: " + roundTripDelay + " offset: " + localClockOffset + " seconds");
|
||||
return (long)(System.currentTimeMillis() + localClockOffset*1000);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
// Process command-line args
|
||||
if(args.length <= 0) {
|
||||
printUsage();
|
||||
return;
|
||||
// args = new String[] { "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
|
||||
}
|
||||
|
||||
long now = currentTime(args);
|
||||
System.out.println("Current time: " + new java.util.Date(now));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Prints usage
|
||||
*/
|
||||
static void printUsage() {
|
||||
System.out.println(
|
||||
"NtpClient - an NTP client for Java.\n" +
|
||||
"\n" +
|
||||
"This program connects to an NTP server and prints the current time to the console.\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"Usage: java NtpClient server[ server]*\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"This program is copyright (c) Adam Buckley 2004 and distributed under the terms\n" +
|
||||
"of the GNU General Public License. This program is distributed in the hope\n" +
|
||||
"that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n" +
|
||||
"warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" +
|
||||
"General Public License available at http://www.gnu.org/licenses/gpl.html for\n" +
|
||||
"more details.");
|
||||
|
||||
}
|
||||
}
|
||||
451
apps/time/java/src/net/i2p/time/NtpMessage.java
Normal file
451
apps/time/java/src/net/i2p/time/NtpMessage.java
Normal file
@@ -0,0 +1,451 @@
|
||||
package net.i2p.time;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* This class represents a NTP message, as specified in RFC 2030. The message
|
||||
* format is compatible with all versions of NTP and SNTP.
|
||||
*
|
||||
* This class does not support the optional authentication protocol, and
|
||||
* ignores the key ID and message digest fields.
|
||||
*
|
||||
* For convenience, this class exposes message values as native Java types, not
|
||||
* the NTP-specified data formats. For example, timestamps are
|
||||
* stored as doubles (as opposed to the NTP unsigned 64-bit fixed point
|
||||
* format).
|
||||
*
|
||||
* However, the contructor NtpMessage(byte[]) and the method toByteArray()
|
||||
* allow the import and export of the raw NTP message format.
|
||||
*
|
||||
*
|
||||
* Usage example
|
||||
*
|
||||
* // Send message
|
||||
* DatagramSocket socket = new DatagramSocket();
|
||||
* InetAddress address = InetAddress.getByName("ntp.cais.rnp.br");
|
||||
* byte[] buf = new NtpMessage().toByteArray();
|
||||
* DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 123);
|
||||
* socket.send(packet);
|
||||
*
|
||||
* // Get response
|
||||
* socket.receive(packet);
|
||||
* System.out.println(msg.toString());
|
||||
*
|
||||
*
|
||||
* This code is copyright (c) Adam Buckley 2004
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version. A HTML version of the GNU General Public License can be
|
||||
* seen at http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
*
|
||||
* Comments for member variables are taken from RFC2030 by David Mills,
|
||||
* University of Delaware.
|
||||
*
|
||||
* Number format conversion code in NtpMessage(byte[] array) and toByteArray()
|
||||
* inspired by http://www.pps.jussieu.fr/~jch/enseignement/reseaux/
|
||||
* NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
|
||||
*
|
||||
* @author Adam Buckley
|
||||
*/
|
||||
public class NtpMessage {
|
||||
/**
|
||||
* This is a two-bit code warning of an impending leap second to be
|
||||
* inserted/deleted in the last minute of the current day. It's values
|
||||
* may be as follows:
|
||||
*
|
||||
* Value Meaning
|
||||
* ----- -------
|
||||
* 0 no warning
|
||||
* 1 last minute has 61 seconds
|
||||
* 2 last minute has 59 seconds)
|
||||
* 3 alarm condition (clock not synchronized)
|
||||
*/
|
||||
public byte leapIndicator = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the NTP/SNTP version number. The version number
|
||||
* is 3 for Version 3 (IPv4 only) and 4 for Version 4 (IPv4, IPv6 and OSI).
|
||||
* If necessary to distinguish between IPv4, IPv6 and OSI, the
|
||||
* encapsulating context must be inspected.
|
||||
*/
|
||||
public byte version = 3;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the mode, with values defined as follows:
|
||||
*
|
||||
* Mode Meaning
|
||||
* ---- -------
|
||||
* 0 reserved
|
||||
* 1 symmetric active
|
||||
* 2 symmetric passive
|
||||
* 3 client
|
||||
* 4 server
|
||||
* 5 broadcast
|
||||
* 6 reserved for NTP control message
|
||||
* 7 reserved for private use
|
||||
*
|
||||
* In unicast and anycast modes, the client sets this field to 3 (client)
|
||||
* in the request and the server sets it to 4 (server) in the reply. In
|
||||
* multicast mode, the server sets this field to 5 (broadcast).
|
||||
*/
|
||||
public byte mode = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the stratum level of the local clock, with values
|
||||
* defined as follows:
|
||||
*
|
||||
* Stratum Meaning
|
||||
* ----------------------------------------------
|
||||
* 0 unspecified or unavailable
|
||||
* 1 primary reference (e.g., radio clock)
|
||||
* 2-15 secondary reference (via NTP or SNTP)
|
||||
* 16-255 reserved
|
||||
*/
|
||||
public short stratum = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the maximum interval between successive messages,
|
||||
* in seconds to the nearest power of two. The values that can appear in
|
||||
* this field presently range from 4 (16 s) to 14 (16284 s); however, most
|
||||
* applications use only the sub-range 6 (64 s) to 10 (1024 s).
|
||||
*/
|
||||
public byte pollInterval = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the precision of the local clock, in seconds to
|
||||
* the nearest power of two. The values that normally appear in this field
|
||||
* range from -6 for mains-frequency clocks to -20 for microsecond clocks
|
||||
* found in some workstations.
|
||||
*/
|
||||
public byte precision = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the total roundtrip delay to the primary reference
|
||||
* source, in seconds. Note that this variable can take on both positive
|
||||
* and negative values, depending on the relative time and frequency
|
||||
* offsets. The values that normally appear in this field range from
|
||||
* negative values of a few milliseconds to positive values of several
|
||||
* hundred milliseconds.
|
||||
*/
|
||||
public double rootDelay = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This value indicates the nominal error relative to the primary reference
|
||||
* source, in seconds. The values that normally appear in this field
|
||||
* range from 0 to several hundred milliseconds.
|
||||
*/
|
||||
public double rootDispersion = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is a 4-byte array identifying the particular reference source.
|
||||
* In the case of NTP Version 3 or Version 4 stratum-0 (unspecified) or
|
||||
* stratum-1 (primary) servers, this is a four-character ASCII string, left
|
||||
* justified and zero padded to 32 bits. In NTP Version 3 secondary
|
||||
* servers, this is the 32-bit IPv4 address of the reference source. In NTP
|
||||
* Version 4 secondary servers, this is the low order 32 bits of the latest
|
||||
* transmit timestamp of the reference source. NTP primary (stratum 1)
|
||||
* servers should set this field to a code identifying the external
|
||||
* reference source according to the following list. If the external
|
||||
* reference is one of those listed, the associated code should be used.
|
||||
* Codes for sources not listed can be contrived as appropriate.
|
||||
*
|
||||
* Code External Reference Source
|
||||
* ---- -------------------------
|
||||
* LOCL uncalibrated local clock used as a primary reference for
|
||||
* a subnet without external means of synchronization
|
||||
* PPS atomic clock or other pulse-per-second source
|
||||
* individually calibrated to national standards
|
||||
* ACTS NIST dialup modem service
|
||||
* USNO USNO modem service
|
||||
* PTB PTB (Germany) modem service
|
||||
* TDF Allouis (France) Radio 164 kHz
|
||||
* DCF Mainflingen (Germany) Radio 77.5 kHz
|
||||
* MSF Rugby (UK) Radio 60 kHz
|
||||
* WWV Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz
|
||||
* WWVB Boulder (US) Radio 60 kHz
|
||||
* WWVH Kaui Hawaii (US) Radio 2.5, 5, 10, 15 MHz
|
||||
* CHU Ottawa (Canada) Radio 3330, 7335, 14670 kHz
|
||||
* LORC LORAN-C radionavigation system
|
||||
* OMEG OMEGA radionavigation system
|
||||
* GPS Global Positioning Service
|
||||
* GOES Geostationary Orbit Environment Satellite
|
||||
*/
|
||||
public byte[] referenceIdentifier = {0, 0, 0, 0};
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the local clock was last set or corrected, in
|
||||
* seconds since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double referenceTimestamp = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the request departed the client for the
|
||||
* server, in seconds since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double originateTimestamp = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the request arrived at the server, in seconds
|
||||
* since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double receiveTimestamp = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This is the time at which the reply departed the server for the client,
|
||||
* in seconds since 00:00 1-Jan-1900.
|
||||
*/
|
||||
public double transmitTimestamp = 0;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new NtpMessage from an array of bytes.
|
||||
*/
|
||||
public NtpMessage(byte[] array) {
|
||||
// See the packet format diagram in RFC 2030 for details
|
||||
leapIndicator = (byte) ((array[0] >> 6) & 0x3);
|
||||
version = (byte) ((array[0] >> 3) & 0x7);
|
||||
mode = (byte) (array[0] & 0x7);
|
||||
stratum = unsignedByteToShort(array[1]);
|
||||
pollInterval = array[2];
|
||||
precision = array[3];
|
||||
|
||||
rootDelay = (array[4] * 256.0) +
|
||||
unsignedByteToShort(array[5]) +
|
||||
(unsignedByteToShort(array[6]) / 256.0) +
|
||||
(unsignedByteToShort(array[7]) / 65536.0);
|
||||
|
||||
rootDispersion = (unsignedByteToShort(array[8]) * 256.0) +
|
||||
unsignedByteToShort(array[9]) +
|
||||
(unsignedByteToShort(array[10]) / 256.0) +
|
||||
(unsignedByteToShort(array[11]) / 65536.0);
|
||||
|
||||
referenceIdentifier[0] = array[12];
|
||||
referenceIdentifier[1] = array[13];
|
||||
referenceIdentifier[2] = array[14];
|
||||
referenceIdentifier[3] = array[15];
|
||||
|
||||
referenceTimestamp = decodeTimestamp(array, 16);
|
||||
originateTimestamp = decodeTimestamp(array, 24);
|
||||
receiveTimestamp = decodeTimestamp(array, 32);
|
||||
transmitTimestamp = decodeTimestamp(array, 40);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new NtpMessage in client -> server mode, and sets the
|
||||
* transmit timestamp to the current time.
|
||||
*/
|
||||
public NtpMessage() {
|
||||
// Note that all the other member variables are already set with
|
||||
// appropriate default values.
|
||||
this.mode = 3;
|
||||
this.transmitTimestamp = (System.currentTimeMillis()/1000.0) + 2208988800.0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This method constructs the data bytes of a raw NTP packet.
|
||||
*/
|
||||
public byte[] toByteArray() {
|
||||
// All bytes are automatically set to 0
|
||||
byte[] p = new byte[48];
|
||||
|
||||
p[0] = (byte) (leapIndicator << 6 | version << 3 | mode);
|
||||
p[1] = (byte) stratum;
|
||||
p[2] = (byte) pollInterval;
|
||||
p[3] = (byte) precision;
|
||||
|
||||
// root delay is a signed 16.16-bit FP, in Java an int is 32-bits
|
||||
int l = (int) (rootDelay * 65536.0);
|
||||
p[4] = (byte) ((l >> 24) & 0xFF);
|
||||
p[5] = (byte) ((l >> 16) & 0xFF);
|
||||
p[6] = (byte) ((l >> 8) & 0xFF);
|
||||
p[7] = (byte) (l & 0xFF);
|
||||
|
||||
// root dispersion is an unsigned 16.16-bit FP, in Java there are no
|
||||
// unsigned primitive types, so we use a long which is 64-bits
|
||||
long ul = (long) (rootDispersion * 65536.0);
|
||||
p[8] = (byte) ((ul >> 24) & 0xFF);
|
||||
p[9] = (byte) ((ul >> 16) & 0xFF);
|
||||
p[10] = (byte) ((ul >> 8) & 0xFF);
|
||||
p[11] = (byte) (ul & 0xFF);
|
||||
|
||||
p[12] = referenceIdentifier[0];
|
||||
p[13] = referenceIdentifier[1];
|
||||
p[14] = referenceIdentifier[2];
|
||||
p[15] = referenceIdentifier[3];
|
||||
|
||||
encodeTimestamp(p, 16, referenceTimestamp);
|
||||
encodeTimestamp(p, 24, originateTimestamp);
|
||||
encodeTimestamp(p, 32, receiveTimestamp);
|
||||
encodeTimestamp(p, 40, transmitTimestamp);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of a NtpMessage
|
||||
*/
|
||||
public String toString() {
|
||||
String precisionStr = new DecimalFormat("0.#E0").format(Math.pow(2, precision));
|
||||
|
||||
return "Leap indicator: " + leapIndicator + "\n" +
|
||||
"Version: " + version + "\n" +
|
||||
"Mode: " + mode + "\n" +
|
||||
"Stratum: " + stratum + "\n" +
|
||||
"Poll: " + pollInterval + "\n" +
|
||||
"Precision: " + precision + " (" + precisionStr + " seconds)\n" +
|
||||
"Root delay: " + new DecimalFormat("0.00").format(rootDelay*1000) + " ms\n" +
|
||||
"Root dispersion: " + new DecimalFormat("0.00").format(rootDispersion*1000) + " ms\n" +
|
||||
"Reference identifier: " + referenceIdentifierToString(referenceIdentifier, stratum, version) + "\n" +
|
||||
"Reference timestamp: " + timestampToString(referenceTimestamp) + "\n" +
|
||||
"Originate timestamp: " + timestampToString(originateTimestamp) + "\n" +
|
||||
"Receive timestamp: " + timestampToString(receiveTimestamp) + "\n" +
|
||||
"Transmit timestamp: " + timestampToString(transmitTimestamp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Converts an unsigned byte to a short. By default, Java assumes that
|
||||
* a byte is signed.
|
||||
*/
|
||||
public static short unsignedByteToShort(byte b) {
|
||||
if((b & 0x80)==0x80)
|
||||
return (short) (128 + (b & 0x7f));
|
||||
else
|
||||
return (short) b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Will read 8 bytes of a message beginning at <code>pointer</code>
|
||||
* and return it as a double, according to the NTP 64-bit timestamp
|
||||
* format.
|
||||
*/
|
||||
public static double decodeTimestamp(byte[] array, int pointer) {
|
||||
double r = 0.0;
|
||||
|
||||
for(int i=0; i<8; i++) {
|
||||
r += unsignedByteToShort(array[pointer+i]) * Math.pow(2, (3-i)*8);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Encodes a timestamp in the specified position in the message
|
||||
*/
|
||||
public static void encodeTimestamp(byte[] array, int pointer, double timestamp) {
|
||||
// Converts a double into a 64-bit fixed point
|
||||
for(int i=0; i<8; i++) {
|
||||
// 2^24, 2^16, 2^8, .. 2^-32
|
||||
double base = Math.pow(2, (3-i)*8);
|
||||
|
||||
// Capture byte value
|
||||
array[pointer+i] = (byte) (timestamp / base);
|
||||
|
||||
// Subtract captured value from remaining total
|
||||
timestamp = timestamp - (double) (unsignedByteToShort(array[pointer+i]) * base);
|
||||
}
|
||||
|
||||
// From RFC 2030: It is advisable to fill the non-significant
|
||||
// low order bits of the timestamp with a random, unbiased
|
||||
// bitstring, both to avoid systematic roundoff errors and as
|
||||
// a means of loop detection and replay detection.
|
||||
array[7+pointer] = (byte) (Math.random()*255.0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a timestamp (number of seconds since 00:00 1-Jan-1900) as a
|
||||
* formatted date/time string.
|
||||
*/
|
||||
public static String timestampToString(double timestamp) {
|
||||
if(timestamp==0) return "0";
|
||||
|
||||
// timestamp is relative to 1900, utc is used by Java and is relative
|
||||
// to 1970
|
||||
double utc = timestamp - (2208988800.0);
|
||||
|
||||
// milliseconds
|
||||
long ms = (long) (utc * 1000.0);
|
||||
|
||||
// date/time
|
||||
String date = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss").format(new Date(ms));
|
||||
|
||||
// fraction
|
||||
double fraction = timestamp - ((long) timestamp);
|
||||
String fractionSting = new DecimalFormat(".000000").format(fraction);
|
||||
|
||||
return date + fractionSting;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of a reference identifier according
|
||||
* to the rules set out in RFC 2030.
|
||||
*/
|
||||
public static String referenceIdentifierToString(byte[] ref, short stratum, byte version) {
|
||||
// From the RFC 2030:
|
||||
// In the case of NTP Version 3 or Version 4 stratum-0 (unspecified)
|
||||
// or stratum-1 (primary) servers, this is a four-character ASCII
|
||||
// string, left justified and zero padded to 32 bits.
|
||||
if(stratum==0 || stratum==1) {
|
||||
return new String(ref);
|
||||
}
|
||||
|
||||
// In NTP Version 3 secondary servers, this is the 32-bit IPv4
|
||||
// address of the reference source.
|
||||
else if(version==3) {
|
||||
return unsignedByteToShort(ref[0]) + "." +
|
||||
unsignedByteToShort(ref[1]) + "." +
|
||||
unsignedByteToShort(ref[2]) + "." +
|
||||
unsignedByteToShort(ref[3]);
|
||||
}
|
||||
|
||||
// In NTP Version 4 secondary servers, this is the low order 32 bits
|
||||
// of the latest transmit timestamp of the reference source.
|
||||
else if(version==4) {
|
||||
return "" + ((unsignedByteToShort(ref[0]) / 256.0) +
|
||||
(unsignedByteToShort(ref[1]) / 65536.0) +
|
||||
(unsignedByteToShort(ref[2]) / 16777216.0) +
|
||||
(unsignedByteToShort(ref[3]) / 4294967296.0));
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
110
apps/time/java/src/net/i2p/time/Timestamper.java
Normal file
110
apps/time/java/src/net/i2p/time/Timestamper.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package net.i2p.time;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.MalformedURLException;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Periodically query a series of NTP servers and post the offset
|
||||
* to a given URL. It tries the NTP servers in order, contacting them
|
||||
* using UDP port 123, and sends the current date to the URL specified
|
||||
* (specifically, URL+"&now=" + yyyyMMdd_HH:mm:ss.SSS in the UK locale).
|
||||
* It does this every 5 minutes, forever.
|
||||
*
|
||||
* Usage: <pre>
|
||||
* Timestamper URL ntpServer1[ ntpServer2]*
|
||||
* </pre>
|
||||
*/
|
||||
public class Timestamper implements Runnable {
|
||||
private static Log _log = new Log(Timestamper.class);
|
||||
private String _targetURL;
|
||||
private String _serverList[];
|
||||
|
||||
private int DELAY_MS = 5*60*1000;
|
||||
|
||||
public Timestamper(String url, String serverNames[]) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Creating new timestamper pointing at " + url);
|
||||
_targetURL = url;
|
||||
_serverList = serverNames;
|
||||
}
|
||||
|
||||
public void startTimestamper() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting timestamper pointing at " + _targetURL);
|
||||
I2PThread t = new I2PThread(this, "Timestamper");
|
||||
t.setPriority(I2PThread.MIN_PRIORITY);
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting up timestamper");
|
||||
try {
|
||||
while (true) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Querying servers " + _serverList);
|
||||
long now = NtpClient.currentTime(_serverList);
|
||||
if (now < 0) {
|
||||
_log.error("Unable to contact any of the NTP servers - network disconnect?");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Stamp time");
|
||||
stampTime(now);
|
||||
}
|
||||
try { Thread.sleep(DELAY_MS); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Timestamper died!", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an HTTP request to a given URL specifying the current time
|
||||
*/
|
||||
private void stampTime(long now) {
|
||||
try {
|
||||
String toRequest = _targetURL + "&now=" + getNow(now);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Stamping [" + toRequest + "]");
|
||||
URL url = new URL(toRequest);
|
||||
Object o = url.getContent();
|
||||
// ignore the content
|
||||
} catch (MalformedURLException mue) {
|
||||
_log.error("Invalid URL", mue);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error stamping the time", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd_HH:mm:ss.SSS", Locale.UK);
|
||||
private String getNow(long now) {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.format(new Date(now));
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
if ( (args == null) || (args.length < 2) ) {
|
||||
usage();
|
||||
return;
|
||||
//args = new String[] { "http://dev.i2p.net:80/somePath?pass=password", "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
|
||||
}
|
||||
String servers[] = new String[args.length-1];
|
||||
System.arraycopy(args, 1, servers, 0, servers.length);
|
||||
Timestamper ts = new Timestamper(args[0], servers);
|
||||
ts.startTimestamper();
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.err.println("Usage: Timestamper URL ntpServer[ ntpServer]*");
|
||||
_log.error("Usage: Timestamper URL ntpServer[ ntpServer]*");
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
<ant dir="apps/sam/java/" target="jar" />
|
||||
<ant dir="apps/heartbeat/java/" target="jar" />
|
||||
<ant dir="apps/netmonitor/java/" target="jar" />
|
||||
<ant dir="apps/time/java/" target="jar" />
|
||||
<ant dir="installer/java/" target="jar" />
|
||||
</target>
|
||||
<target name="compile" />
|
||||
@@ -36,6 +37,7 @@
|
||||
<copy file="apps/sam/java/build/sam.jar" todir="build/" />
|
||||
<copy file="apps/heartbeat/java/build/heartbeat.jar" todir="build/" />
|
||||
<copy file="apps/netmonitor/java/build/netmonitor.jar" todir="build/" />
|
||||
<copy file="apps/time/java/build/timestamper.jar" todir="build/" />
|
||||
<copy file="installer/java/build/install.jar" todir="build/" />
|
||||
<copy file="installer/java/build/guiinstall.jar" todir="build/" />
|
||||
<copy file="installer/java/build/fetchseeds.jar" todir="build/" />
|
||||
@@ -64,6 +66,7 @@
|
||||
<ant dir="apps/sam/java/" target="distclean" />
|
||||
<ant dir="apps/heartbeat/java/" target="distclean" />
|
||||
<ant dir="apps/netmonitor/java/" target="distclean" />
|
||||
<ant dir="apps/time/java/" target="distclean" />
|
||||
<ant dir="installer/java/" target="distclean" />
|
||||
<delete>
|
||||
<fileset dir="." includes="**/*.class" />
|
||||
|
||||
@@ -14,8 +14,8 @@ package net.i2p;
|
||||
*
|
||||
*/
|
||||
public class CoreVersion {
|
||||
public final static String ID = "$Revision: 1.3 $ $Date: 2004/04/20 04:18:54 $";
|
||||
public final static String VERSION = "0.3.1";
|
||||
public final static String ID = "$Revision: 1.5 $ $Date: 2004/05/07 12:52:49 $";
|
||||
public final static String VERSION = "0.3.1.2";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Core version: " + VERSION);
|
||||
|
||||
@@ -318,6 +318,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
return tags;
|
||||
}
|
||||
|
||||
private static volatile long __notifierId = 0;
|
||||
|
||||
/**
|
||||
* Recieve a payload message and let the app know its available
|
||||
*/
|
||||
@@ -337,9 +339,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
_sessionListener.messageAvailable(I2PSessionImpl.this, id, size);
|
||||
}
|
||||
});
|
||||
notifier.setName("Notifier [" + _sessionId + "/" + id + "]");
|
||||
long nid = ++__notifierId;
|
||||
notifier.setName("Notifier " + nid);
|
||||
notifier.setDaemon(true);
|
||||
notifier.start();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Notifier " + nid + " is for session " + _sessionId + ", message " + id + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,7 +452,6 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
if (_closed) return;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Destroy the session", new Exception("DestroySession()"));
|
||||
_closed = true;
|
||||
if (sendDisconnect) {
|
||||
try {
|
||||
_producer.disconnect(this);
|
||||
@@ -455,6 +459,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
propogateError("Error destroying the session", ipe);
|
||||
}
|
||||
}
|
||||
_closed = true;
|
||||
closeSocket();
|
||||
if (_sessionListener != null) _sessionListener.disconnected(this);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
}
|
||||
|
||||
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
|
||||
|
||||
return sendMessage(dest, payload, new SessionKey(), new HashSet(64));
|
||||
}
|
||||
|
||||
@@ -225,12 +226,12 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
_log.error("State with nonce " + state.getNonce()
|
||||
+ " was not accepted? (no messageId!! found=" + found
|
||||
+ " msgId=" + state.getMessageId() + ")",
|
||||
new Exception("Race on accept/success status messages?"));
|
||||
new Exception("Race on accept/success status messages, or reconnected?"));
|
||||
nackTags(state);
|
||||
//if (_log.shouldLog(Log.CRIT))
|
||||
// _log.log(Log.CRIT, "Disconnecting/reconnecting because we never were accepted!");
|
||||
//disconnect();
|
||||
//return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
|
||||
@@ -30,6 +30,8 @@ public class I2CPMessageReader {
|
||||
private I2CPMessageEventListener _listener;
|
||||
private I2CPMessageReaderRunner _reader;
|
||||
private Thread _readerThread;
|
||||
|
||||
private static volatile long __readerId = 0;
|
||||
|
||||
public I2CPMessageReader(InputStream stream, I2CPMessageEventListener lsnr) {
|
||||
_stream = stream;
|
||||
@@ -37,7 +39,7 @@ public class I2CPMessageReader {
|
||||
_reader = new I2CPMessageReaderRunner();
|
||||
_readerThread = new I2PThread(_reader);
|
||||
_readerThread.setDaemon(true);
|
||||
_readerThread.setName("I2CP Reader");
|
||||
_readerThread.setName("I2CP Reader " + (++__readerId));
|
||||
}
|
||||
|
||||
public void setListener(I2CPMessageEventListener lsnr) {
|
||||
|
||||
@@ -30,7 +30,7 @@ class PersistenceHelper {
|
||||
_log.error("Error formatting " + val + " into a long", nfe);
|
||||
}
|
||||
} else {
|
||||
_log.error("Key " + prefix + name + " does not exist");
|
||||
_log.warn("Key " + prefix + name + " does not exist");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class Clock {
|
||||
/** if the clock is skewed by 3+ days, fuck 'em */
|
||||
public final static long MAX_OFFSET = 3 * 24 * 60 * 60 * 1000;
|
||||
/** if the clock skewed changes by less than 1s, ignore the update (so we don't slide all over the place) */
|
||||
public final static long MIN_OFFSET_CHANGE = 30 * 1000;
|
||||
public final static long MIN_OFFSET_CHANGE = 10 * 1000;
|
||||
|
||||
/**
|
||||
* Specify how far away from the "correct" time the computer is - a positive
|
||||
|
||||
@@ -24,7 +24,8 @@ public class NativeBigInteger extends BigInteger {
|
||||
_log.info("Native BigInteger library jbigi loaded");
|
||||
} catch (UnsatisfiedLinkError ule) {
|
||||
_nativeOk = false;
|
||||
_log.warn("Native BigInteger library jbigi not loaded - using pure java", ule);
|
||||
_log.log(Log.CRIT, "Native BigInteger library jbigi not loaded - using pure java");
|
||||
_log.warn("jbigi not loaded", ule);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
hosts.txt
11
hosts.txt
@@ -1,6 +1,10 @@
|
||||
; TC's hosts.txt guaranteed freshness
|
||||
; $Id: hosts.txt,v 1.28 2004/03/19 21:58:44 jrandom Exp $
|
||||
; $Id: hosts.txt,v 1.4 2004/05/03 19:43:09 jrandom Exp $
|
||||
; changelog:
|
||||
; (1.31) added mush.zeit.i2p (i2p's first eepMUSH)
|
||||
; (1.30) added xilog.i2p
|
||||
; (1.29) added lucky.i2p, removed lp.i2p
|
||||
; (1.28) added sungo.i2p
|
||||
; (1.27) added jar.i2p
|
||||
; (1.26) added tor-www-proxy.i2p (WWW through tor by human - won't be up 24/7!)
|
||||
; (1.25) added ugha.i2p
|
||||
@@ -62,7 +66,6 @@ mesh.firerabbit.i2p=BLt3pciNQcVIU-IGnTfSsrputh~b6drZpc1vH8qeA745XoE~nMCGtw8S7HGY
|
||||
chess.fillament.i2p=8xvXLwcBYu2MxqMAoG6DIahvkNAwBQs43kdZTg9SlouzC3JSQ25RHvspbrxWoGIVAlB~XCxVmBB6EolYEHoSZv2YCziOj4UVxEbVP7nHkh32-7Uc5T7xlcjk8-rsmZzdgz9NhxKVn2Uhp3xtcdVAiyG4mpXisvK-7NgHY-mXPNvj6goaH58roeDUZ5otJN7nCFmcRAUpaUk-nlQs8YfgSCCEsWKOWhsVnAaHwtqtDlpdTo1YKooACMRSa-DcV5W75Il80JEWBD79qpSAeONGAOMMPT~VEMwNNg001VG-UZbvyspZdxHaETw2yd7N9l3-mwI-sYcveTTnNXLWO8IjdgDLdUIP5qrIW6WS9XZIHRKqT2kpwEw7xsEgCA~qSNiZWeai8n6Zs0rLmdyeZeafVEEW9vr6CKcLZ5W7i1SMDqCKnzSbZdd2Hvpb9aWFf5foEjLt33n8w2KSaCUe4zmN~xuQMq2yiB-vQ9~5fgPmphlMxo3ca5MTCfbhshw5137YAAAA
|
||||
kaji.i2p=i-nivH~36AgabgzvS06oKHR~MLoy6NA0oSv8JuNJDLZB8FXEDzIiyzWISmw9XJm8I7QqZ1yFH8~xe84STCHHvMMIQQrfBmOUODLWbKZor~~KhiHdNLtfVZC5BpnXkBCJkklj~fMYSpWa0C~pVRrZl75RoGtBjDVP9P8hioTv5B6RC86g2ypBH5r093rY0wnzxSL8-ZuV3F~H48VYbqro8cRlbMmjx2oEsSHkDpQyjCMVkIYKaCijkSArqZTG~zX6op6Ng9CJwdrkjKSsbzjV6MLnE4aNv-jm2WaGGD5pR24h7e3ImDOGAr17tXRtmNX5ZEQ1udQp8qIhd8UMUumrnm962r8KJWK~9WNzcVeqDrIxaaxC7vcQmXxoPeEW2efbH0yKhVZ7OFu~I9cAapSe~aNWp9UK4URSpuJvOedt0axp3ORaaM-a5U7noW3Ao-HB83qfFEPU-6uUu16HNiPqCFMJiA0qODTOwHiyyx4HKQvbhjujh4mmknSbsuapdgR1AAAA
|
||||
human.i2p=ji02vZzrp51aAsi~NZ8hwMLbr1rzMtdPUSiWAU94H89kO-~9Oc8Vucpf2vc6NOvStXpeTOqcRz-WhF01W8gj-YLP3WFskbjCcUwz0yF8dHonBeC4A5l4CjupAaztBSMbhu4vyN9FJkqZUFN01eZbQ9UqgXgLWMp4DtbUwf78y8VrzdAfmUOrVn6Iu89B~HUfOAKnpIlQXyGsQk1fnLw3PzDo2PVi8Q3C1Ntn0ybovD1xDKPrrHliTK4or2YujTcEOhSBLK4tQGvouN-tWqcVoF9O814yNGtze~uot62ACGJj9nvEU3J7QPgOl~fgBJ5Hvom0Qu-yPAGJuAZa29LSHnvRhih~z~6lWZYHREBYXQ58IzKktk90xJWcTwlwRRhyO-Sz3A5JYR3jM97h4SsoYBVrjK9TWnvGKj~fc8wYRDzt1oFVfubLlT-17LUzNc59H-2Vhxx8yaey8J~dqdWO0YdowqekxxlZf2~IVSGuLvIZYsr7~f--mLAxCgQBCjOjAAAA
|
||||
lp.i2p=i~VkqY6fes7yCR6Yn4Nowuo3h621nKC9yvMVOEGW6nC61kLRKS5xcr7JdsFohF02Z7neR7Nv3GLB1qTvqguU2SekCPpfzNYNSDCZZVPMy1IXegZupmMMtDXnY7dAwcy~d5hdjwtODidfiG37~C-AE2g8cogJSAG5-sgOTOcA288fu0n9qYn9lejK6T8vQYMJIgqW73K5ErLg8F9C8yPfCNwRlqOZ8xSWowpvlyzWy5OFMmhk00S4JA5TAKlw-ILQDT20qFOHNcCWc8biXjNDozMxcw~h3rq-TQtWyW0-J8ERf2tmjPSoRr7zxKTFXlP0ibQz8HqAhlt9xDKlpd2LZyI9cKvMNfmGBIrFZHckO~rHtCTNtA7dN-UheC~k2bX0hDlQ8A6QWKovTnt3yktOJjwxBQU3iL7UGXbS8M~8t9LmiEKdTZgMUnAcBLkrPOfTAKpkCLE-~yAYBDj~U8ZKJKEtD-gLDY9~slVuo2AgNLYF6bipXxSMsoCzevJvcWnqAAAA
|
||||
bt1.eco.i2p=SUHjD4QvbuY5VVTGTm49U8B~DtvTS2bwO1lREMNfJtZU6rxa4tgdqaIpUjRRrbZYLxZcgxjUYx4Bq7gcjriETIRaMy7lQtMx9zFk~XLE5baTmZeXc1~xuQCCTelnYv0yUswWSCZx7ll2a-Y6d88jvTydfxcCTNAYclT~gHnA0kU2kRHD5vgEteidiXFBVer2Ps68tnARUqURDKxTRyRnWtrxwQocmP5MJG29e4dIptcecxX8bKhgqgONzPAmYzAR7F694wEOXJZQO67ro0YQEuQmDXICBI8IwkbAt8qM~BG~JF1H26iWblWWs8mGJonwl34-CpVjMSXWVk~SC~60-xr97CsWSGAeZqrD8pLJyDGFsJ0jNFV0L6Q97ryUViakVwsHaAMlxZ3Hh7KAc6TUcJEGvuygRJ2DKeKA1wyuUDFc08m40HrRVwydn0hwzs4Qfb8hXfGHQ9-yauK6Gk-HvYFUL9qcIrOfrZGgq4xLVdgVh2wB1mIOTkcBMMti4941AAAA
|
||||
gernika.i2p=1D9ee5J04ZI7pvCwQ3hXQMpeMvbNw9cz3V2pioQ9LakwRQzfMEb9CVAeiFt-wE4HyTFWKeof5rz8F5vmIqFMaH~oMJSkCyNtwPfqiRAENeNeALHbY2plMPfqCfEFR4GkTqXnalAkJGqDjo55CokUfalEVTGMvNiv6i6dmNvwS8~0X56sXIaXgLKuGIK~UNOg9hT8A~uEGQWXwTKD3EDmECsJL40iYcT1UR9rffzuyxOvDhL12HbJ8bIlUGarzEscH-jolj5ShvZAbEyw-MnVzR9LiEqy7DaniKpPtC0oXRZuz7PpcQTqzN-zgQaLq8bHTx7NHIfTuA4P~hhz-STO4SjPot7h~Gbdglc193OmGlL5QwbfjXfdOIccBDh6~jtFaa28GxHrTMoi9GafjnllLfWpvynN1y5H7Jh7Uw3E7KDtBGVsDg9-btyvyQLP3kkqPfIAn7Oj6ePHr9u-TN9ZwDbWj~QBmXXutsE~lLu7aT7kv5Jx6PFmLEeWPib82UuGAAAA
|
||||
www.aum.i2p=8x3TYbh9aq6EVo8rSuPWBVSrwD~yS1al6z0RZYvRaQFXL9hFUUJYJNL2n3oR3tBg7Zr00MjqntIuBMd20LUKasnklTp4hDlDCE0KfeftYh~bFGykMRf0yTYEWaMHYpIRBY-IJEvSVlgHe8E4AWLMv-b6VKCDZ0~0AdUrsHQ0Qb4NQ-igBPZfU6c~UU0tUVUl1Efsuz1CdAp5pw5RdPviFtPH4tMvUca2t54Rwa-6v6QCqFZ7S2awyhAa73Zb9YlcqT4hP1JHF0wR0rL-OEoJV0gG47Co4Zr903SNW6cgKDj3lW1tIpzcVMoH3BE22SMEVjYyEHgAORdoYwaj19CUg1slDGmvUCoq4dPsnCIrvV7N0LeoUkZekt2pvr~yCH47ENV3oQYpFVLcMLN82tzI0ZFz5IyBHWGr22vlDlT1C-QVhAYQKN92XubDXSEgrhWv5IHPB0h~EgZi-rDcsJG2zb6ZqjtKFHp4CnNvTUxE1cCJh0aR1MDzM~o0iSMiMqh0AAAA
|
||||
@@ -79,4 +82,8 @@ reefer.i2p=OgiTTgA1xghkPcwgGpnyIJCBG1cZk17UKMAJ7H0YiroAvrAU4mexKPkWoy3JFmsenJuHn
|
||||
ugha.i2p=318iVBG9JmuN5R~ClMb6rkWqkKoCZ3yGvD4dyIJAZum4GXFMHKSLrRiBf~2tFJ2KW1Lg9tKeyDgTC6sMt0aEQTcmzZl48BsFPZlH~WiI0JnPadihezDowBSdBhMi0RXoa3~xbfOKgAsHJv1zjrfRJHYz9fgG9bNQv9~oLeKz6YRdi97yrvKZLuzaUakYWfwdk7t9ZVhaXxsW8USXLSeHfdeQb0NYmsNc6is7Gp15HvsVUMgZXuBGea-AzMY1SSA6KdJwwPlXFvvcTM7neZKJzFVXyFsaOMEDjoUFfEC3tq277H0cqv4rAVmYp7WN78oBC7JgjfvFtXCY1r1l2-Qh1AEMwevVwI7tWdPUp50eMKEiccaHNZ7q2Zt0Uk0vlxKYW84p8ZTvXqfYDWyN8DQH2NKcy87MV1ZapDrbJSrF7cb-LvZy~nHtx~UwdKLS2gziM25JTtGiC1litArYS5KaY8rQXtL9kkSx9J66gc-05S-nbFMN-f-rEO9Fl27RvdQPAAAA
|
||||
tor-www-proxy.i2p=9bZhTZvATJzpBa6UPslEwJCiDcsNhguT1mwbayD-rY0TN4Igj1PqPeVranzoO4Ity87ABVu2XJXatMzp~xHHtNiH6hF2XwJlpzx1Rr7A7SCTZwfBB1qLglwEBqYNEV5WT1faIDaArLc6o-ukhrOkIa8aJdaEpkjDkHjPtoOWt3nIIYlDxIliABrjFxPDeQZcOJyw7ftckqWfv7RKjdC8YpZrjXuojmi-TuhdRPu58tCNoH3j9laG4fUuU1RPK0YEj1HNSRHJVHDpCATtgHHfPiQvYK2HbMb0UBUfCtccmYqu6Cft5xZGHEOKvrMLXeEryV9ye-aczeNfBG2KzaF9pgQ2AN~eKBW7~UaNOQakyIaHDiY7aZ5qOCNW1CjuyJsEkjgRvqHogh6k5d3CkP82VlbEpTl5XaJFuflNJ0pHqZq2Le2T3wMkyECLbR0cX~qifE6Uw79AJnu-XEYQHFvHb0tV3XY2STDulgZu3fqZsjOVw2lZMHPHsszqlhiDhZIZAAAA
|
||||
jar.i2p=xPIYObh2AirO1xoWCj7Wwc5RsGmQ3qulIAOHux9pOm9tzErjAfxv~2EazsZjyXCZ0zi3ylUjxqfj3L1pWEQBM-VM7HshHwg-PTuGWcdcUSRRFpQ7Gcp9u~~I3HSLRdHDj6ZkBNBk0jXM03tSKQEE4V1eum0tJwlOhBCNVqtt~FhyOBvo9~ypv6zW0sb6I3NppTYGq1LL4py4KrHSjb80e3Adfyhl1E2TfSHv6Uwp8qB~a2ac2IGhB2s-FK4gKSolpV-cUsn3roZRyq9jKJ2ciT4Y-PVcIDl6D8kV~LcNUbqD7vHy1yQxv1ByCCyIi3IYDacl5n5udwHO64L0M2mtZ1zWHS7K0~IxZTyk~mpO98qslbRfQqbk0ohZ-JGFhuB~cbVWlH6tSLmMJqmD-rOWnuxRfHLVtnbQstObQ9~KVIaX9KeLusgna2ZOhFjcy424BHtaVnbnLVyh-DEq~LkJGNx9Feyi6Z-aSQvThuhyE-ALiSvSw05x2G0yM9Me6MOWAAAA
|
||||
sungo.i2p=S53R0ZV3gK3yA0woixx--czUOOsltTZABkFw0VyVuq-Dsx7inqAPYZKIWok3TP2vxBNM9I8LEExp6rlvJlPNvunzxYw0uZg3paN8V1vU3uRRhg4KDh~eAHxQo4cMC7Qng1jCt8Nb6acsg-NNm81fFJGvFaSqoi7Vwr-zZ9G1OuuzLI02Ald4RnvTOcsoOlh~gna0Y~IXGXJFI0KA76rKL3jgRAE~TqIIorGK2fTSPOwcjUPE9VXTq2LTpJayqMBdc6U6U8goYyt~cyX5rE-U4t49Vg0dVTkEfDWq-GVTiMSu0tbbyjgUcz3Ls038ugOJL11wO20QRvBkqIDcKo9gIX2ZFlKFhJWpYpNkRmXHAUobMx~zqjrpmbqy6dEA-HzRxlXYHBbIQwrX3qiAFSXPbnWrSMfm2kqMd57PZtxpb9pftKA-I7yhqqRFl0yukzG5Q5nN-ID7zo~6jLTChYsnj1ceEXn6~KMnZ3K3JTC7dR8PsWlGmSwgogHpU31ipyOhAAAA
|
||||
lucky.i2p=Xzf7x5fOHaUDri-ZCNpg0S-CL7aojzaZ8NQ7Ax~zq60zhvz1yIQoPw38QYS7Vn7F5H3tMlrsUxoEoMd2tgV2gVCUesDMgThRNAkQiJSSsIf2dA5AKqD3FtyNtNTYVH1PjlrsXQ67VOmXoxYgiIRY1QVJj5A4Huw0X5FFzvM1QfHV8CSHI6P0lvFtHZwT~TyhH4SeqfZUL4uilvtRKJb1beayuALpXQE~9B7NUlr-Ws3w6D6g9PJ8mgePo1~iVFo7oKzOPmiWLnXBCh7IZXpSIKkQiSWXD6vA-QkCtlQClV6VPqhwIs63qX8MIE2yXsNQgOb-u9Tc~Y8vk0VjMgaGCDd9g6L8BmsxKHerELt4ZDLcBt9yuX3DWwGjwskZ8E~Fydk9ELnI6if3sOKoKv4Ov8-CwyL4WsgUJECyPelCaZxHkv~m-OygnRN~71jvGOpWDGGRvoVQmSB~JwTQkUVAbCZpXp~J1qQsR2QgK2onW8q1OZ7Q~UsVQS86DmfOwQmQAAAA
|
||||
xilog.i2p=yHOthRzTaowYM0dH0H8LfHeBpNzfVnBL9TtVSPF1bAImcm0tI1jyw4dERfijVunXviLGQ0NahyOaSRvbn3pBth179n1w5KE-~m1m9EIevv~XgMjDrfkrMnrQXiubvnCiSV9f7u6Cu0fdjlwP7e4Bw3p~6Uy06zikJKdB4duwdWmqOr-7UFuoY3CZRjp6fXe1Xy~JKRxHdUoTfT1qYmEVQqTT5bfCDTt0eiUcKfFz7zMAJgkLVbV7CifasppgaCDG5L3O~87JjKlQ5TazDhCD5222WBw81cMlxlrVXbGMKoS56DZfvBr0oKebzPI2DXq52mjtVF4u5VBgDfEUx2pWQoRaF7iePx2nYg9WdRcc-r4idLPeoZgQLgoPu74iof~T-wXs5wWe9U6lyYQWmRf9VxIiOjEMBOoNjs657-~vWXDQlk6IS95bqRxYXvXZheFqY1uzmdSA1VgmrJX~yN-lZC2LXHJqi4PiSpMqVWjwyGUZINzlc3flUX-NwouSmp01AAAA
|
||||
mush.zeit.i2p=3mYATMQg83VtUln0eEi9-LZYJ-0wCuhFu5PnDV6mYWFnLwbPf2rG22jyhOBw2h1fXcj3lJg7VnLRx-PGYQIWsIle~z1FBrOyT9ydqGrjbQYJ0bqBYUnWSR-xuHOGWiJ8lH34uqZ~j~owvhH-NAKs332BkSCl36HsjJF46i00ICS4qxJQ9l7YBtGYAvoliMN4rz0FETPsvwroyQ7JptfovHe1yviF5bfjeZ9ITP5EpYpyOVtfR7ELpvjYN3mH087TrgvJWL5Sz4qjAc122luTHDZ~Z01Ti3d0GluOF0Fh8cugcMXZTcSZLLcoo1UX8Iv~azxYoYw4MB-w9o4ftxZGawHnkzNCX9LXvQb9IHtURGu4p~eKQKT3YBDEM1qIZL-AdOeoUJ87wLke-ukLBTErYrszIiBgCr22pAHbH12ygH63UAKgzChg4eYfQ0Ku-I6PukG3QQhn029cYz8KO9LG5cT6QzeMbGIQ0cVgkbxM6028DXQaChA6Ul8lCOR56ZDPAAAA
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<ant dir="../../apps/sam/java/" target="build" />
|
||||
<ant dir="../../apps/netmonitor/java/" target="build" />
|
||||
<ant dir="../../apps/heartbeat/java/" target="build" />
|
||||
<ant dir="../../apps/time/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
@@ -39,6 +40,7 @@
|
||||
<fileset file="../../apps/sam/java/build/sam.jar" />
|
||||
<fileset file="../../apps/heartbeat/java/build/heartbeat.jar" />
|
||||
<fileset file="../../apps/netmonitor/java/build/netmonitor.jar" />
|
||||
<fileset file="../../apps/time/java/build/timestamper.jar" />
|
||||
<fileset file="../doc/COPYING" />
|
||||
<fileset file="../../readme.txt" />
|
||||
<fileset file="../../hosts.txt" />
|
||||
@@ -62,6 +64,7 @@
|
||||
<fileset file="../../apps/sam/java/build/sam.jar" />
|
||||
<fileset file="../../apps/heartbeat/java/build/heartbeat.jar" />
|
||||
<fileset file="../../apps/netmonitor/java/build/netmonitor.jar" />
|
||||
<fileset file="../../apps/time/java/build/timestamper.jar" />
|
||||
<fileset file="../doc/COPYING" />
|
||||
<fileset file="../../readme.txt" />
|
||||
<fileset file="../../hosts.txt" />
|
||||
@@ -83,6 +86,7 @@
|
||||
<ant dir="../../apps/sam/java/" target="cleandep" />
|
||||
<ant dir="../../apps/heartbeat/java" target="cleandep" />
|
||||
<ant dir="../../apps/netmonitor/java" target="cleandep" />
|
||||
<ant dir="../../apps/time/java" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../core/java/" target="distclean" />
|
||||
@@ -92,5 +96,6 @@
|
||||
<ant dir="../../apps/sam/java/" target="distclean" />
|
||||
<ant dir="../../apps/heartbeat/java" target="distclean" />
|
||||
<ant dir="../../apps/netmonitor/java" target="distclean" />
|
||||
<ant dir="../../apps/time/java" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
|
||||
@@ -95,6 +95,12 @@ statGroup.4.detail.6.field=3
|
||||
statGroup.4.detail.7.name=unknown tunnel time remaining (day)
|
||||
statGroup.4.detail.7.option=stat_tunnel.unknownTunnelTimeLeft.24h
|
||||
statGroup.4.detail.7.field=0
|
||||
statGroup.4.detail.8.name=tunnel test time (hour)
|
||||
statGroup.4.detail.8.option=stat_tunnel.testSuccessTime.60m
|
||||
statGroup.4.detail.8.field=0
|
||||
statGroup.4.detail.9.name=tunnel test time (day)
|
||||
statGroup.4.detail.9.option=stat_tunnel.testSuccessTime.24h
|
||||
statGroup.4.detail.9.field=0
|
||||
#
|
||||
statGroup.5.name=transfer
|
||||
statGroup.5.detail.0.name=messages sent (5 minutes)
|
||||
@@ -154,4 +160,7 @@ statGroup.7.detail.2.option=stat_netDb.storeSent.5m
|
||||
statGroup.7.detail.2.field=3
|
||||
statGroup.7.detail.3.name=db store sent (hour)
|
||||
statGroup.7.detail.3.option=stat_netDb.storeSent.60m
|
||||
statGroup.7.detail.3.field=3
|
||||
statGroup.7.detail.4.name=failed db lookups (hour)
|
||||
statGroup.7.detail.3.option=stat_netDb.failedPeers.60m
|
||||
statGroup.7.detail.3.field=3
|
||||
@@ -88,7 +88,7 @@ qs.0045.question=<none>
|
||||
qs.0050.question=End of configuration.
|
||||
|
||||
|
||||
libs.count=13
|
||||
libs.count=14
|
||||
libs.0001.name=i2p.jar
|
||||
libs.0001.islib=true
|
||||
libs.0002.name=i2ptunnel.jar
|
||||
@@ -114,4 +114,6 @@ libs.0011.name=netmonitor.jar
|
||||
libs.0012.name=harvester.config
|
||||
libs.0012.islib=false
|
||||
libs.0013.name=heartbeat.config
|
||||
libs.0013.islib=false
|
||||
libs.0013.islib=false
|
||||
libs.0014.name=timestamper.jar
|
||||
libs.0014.islib=true
|
||||
@@ -5,24 +5,11 @@
|
||||
##_router_hn##
|
||||
##_router_port##
|
||||
##_router_lavalid##
|
||||
# unless you really really know what you're doing, keep listenAddressIsValid=false
|
||||
##_router_tcpdisable##
|
||||
|
||||
# maximum number of TCP connections we will want to
|
||||
# attempt to establish at once (each of which
|
||||
# requires a 2048bit DH exchange)
|
||||
i2np.tcp.concurrentEstablishers=5
|
||||
|
||||
# Polling HTTP configuration, which is used to keep your router's clock in sync
|
||||
# [also for communication when no inbound connections are possible, once its fixed up again]
|
||||
##_router_phttpreg##
|
||||
##_router_phttpsend##
|
||||
|
||||
# The following option specifies whether the router wants to keep the router's internal time in sync
|
||||
# with the PHTTP relay's clock (which should be NTP synced). If however you are sure your local machine
|
||||
# always has the correct time, you can set this to false (but your clock MUST be synced - see
|
||||
# http://wiki.invisiblenet.net/iip-wiki?I2PTiming for more info.
|
||||
i2np.phttp.trustRelayTime=true
|
||||
|
||||
# I2CP client port, for client connections
|
||||
i2cp.port=##_router_i2cp_port##
|
||||
@@ -156,26 +143,42 @@ router.maxWaitingJobs=40
|
||||
# applications it is up and running, all within the router's JVM. Uncomment the
|
||||
# ones you want (revising the numbers and ports accordingly)
|
||||
|
||||
# Network monitor (harvests data from the network database and stores it under
|
||||
# monitorData/, and with the netviewer GUI you can browse through its results)
|
||||
clientApp.0.main=net.i2p.netmonitor.NetMonitor
|
||||
clientApp.0.name=NetMonitor
|
||||
clientApp.0.args=
|
||||
# Keep the router's clock in sync by querying one of the specified NTP servers once
|
||||
# a minute (uses UDP port 123)
|
||||
# Please change the NTP server specified to include ones closer to you - see
|
||||
# http://www.eecis.udel.edu/~mills/ntp/clock2a.html for a list (you can specify as
|
||||
# many as you want on the args= line - they'll be tried in order until one answers).
|
||||
# Some example servers you may want to try:
|
||||
# US: dewey.lib.ci.phoenix.az.us
|
||||
# US: clock.fmt.he.net
|
||||
# BR: ntp1.pucpr.br
|
||||
# BE: ntp2.belbone.be
|
||||
# AU: ntp.saard.net
|
||||
clientApp.0.main=net.i2p.time.Timestamper
|
||||
clientApp.0.name=Timestamper
|
||||
clientApp.0.onBoot=true
|
||||
clientApp.0.args=http://localhost:7655/setTime?k=v clock.fmt.he.net ntp2.belbone.be
|
||||
|
||||
# SAM bridge (a simplified socket based protocol for using I2P - listens on port 7656. see
|
||||
# the specs at http://www.i2p.net/node/view/144 for more info)
|
||||
clientApp.1.main=net.i2p.sam.SAMBridge
|
||||
clientApp.1.name=SAMBridge
|
||||
clientApp.1.args=0.0.0.0 7656 i2cp.tcp.host=localhost i2cp.tcp.port=##_router_i2cp_port##
|
||||
clientApp.1.args=sam.keys 0.0.0.0 7656 i2cp.tcp.host=localhost i2cp.tcp.port=##_router_i2cp_port##
|
||||
|
||||
# EepProxy (HTTP proxy that lets you browse both eepsites and the normal web via squid.i2p)
|
||||
clientApp.2.main=net.i2p.i2ptunnel.I2PTunnel
|
||||
clientApp.2.name=EepProxy
|
||||
clientApp.2.args=-nogui -e "config localhost ##_router_i2cp_port##" -e "httpclient 4444"
|
||||
clientApp.2.args=-nocli -e "config localhost ##_router_i2cp_port##" -e "httpclient 4444"
|
||||
|
||||
# Heartbeat engine (uber-simple ping/pong system, configured in heartbeat.config. By itself
|
||||
# Network monitor (harvests data from the network database and stores it under
|
||||
# monitorData/, and with the netviewer GUI you can browse through its results)
|
||||
#clientApp.3.main=net.i2p.netmonitor.NetMonitor
|
||||
#clientApp.3.name=NetMonitor
|
||||
#clientApp.3.args=
|
||||
|
||||
# Heartbeat engine (ueber-simple ping/pong system, configured in heartbeat.config. By itself
|
||||
# it just writes out stat data where its told to, but there's a seperate HeartbeatMonitor
|
||||
# GUI to let you visualize things)
|
||||
#clientApp.3.main=net.i2p.heartbeat.Heartbeat
|
||||
#clientApp.3.name=Heartbeat
|
||||
#clientApp.3.args=heartbeat.config
|
||||
#clientApp.4.main=net.i2p.heartbeat.Heartbeat
|
||||
#clientApp.4.name=Heartbeat
|
||||
#clientApp.4.args=heartbeat.config
|
||||
|
||||
@@ -3,7 +3,5 @@ title I2P Router
|
||||
cd ##_scripts_installdir##
|
||||
|
||||
REM the -XX args are workarounds for bugs in java 1.4.2's garbage collector
|
||||
REM replace java with javaw if you don't want a window to pop up
|
||||
|
||||
javaw -cp lib\i2p.jar;lib\router.jar;lib\mstreaming.jar;lib\heartbeat.jar;lib\i2ptunnel.jar;lib\netmonitor.jar;lib\sam.jar -Djava.library.path=. -DloggerFilenameOverride=logs\log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router
|
||||
echo Router started up, please see http://localhost:7655/
|
||||
java -cp lib\i2p.jar;lib\router.jar;lib\mstreaming.jar;lib\heartbeat.jar;lib\i2ptunnel.jar;lib\netmonitor.jar;lib\sam.jar;lib\timestamper.jar -Djava.library.path=. -DloggerFilenameOverride=logs\log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
cd ##_scripts_installdir##
|
||||
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
|
||||
# the -XX args are workarounds for bugs in java 1.4.2's garbage collector
|
||||
nohup nice java -cp lib/i2p.jar:lib/router.jar:lib/mstreaming.jar:lib/heartbeat.jar:lib/i2ptunnel.jar:lib/netmonitor.jar:lib/sam.jar -Djava.library.path=. -DloggerFilenameOverride=logs/log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router --quiet > /dev/null &
|
||||
nohup nice java -cp lib/i2p.jar:lib/router.jar:lib/mstreaming.jar:lib/heartbeat.jar:lib/i2ptunnel.jar:lib/netmonitor.jar:lib/sam.jar:lib/timestamper.jar -Djava.library.path=. -DloggerFilenameOverride=logs/log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router --quiet > /dev/null &
|
||||
# Save the pid just in case we ever want to stop the router
|
||||
echo $! > router.pid
|
||||
echo I2P Router started
|
||||
|
||||
@@ -146,6 +146,8 @@ public class TunnelMessage extends I2NPMessageImpl {
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[TunnelMessage: ");
|
||||
buf.append("\n\tMessageId: ").append(getUniqueId());
|
||||
buf.append("\n\tExpiration: ").append(getMessageExpiration());
|
||||
buf.append("\n\tTunnel ID: ").append(getTunnelId());
|
||||
buf.append("\n\tVerification Structure: ").append(getVerificationStructure());
|
||||
buf.append("\n\tEncrypted Instructions: ").append(getEncryptedDeliveryInstructions());
|
||||
|
||||
@@ -13,6 +13,8 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.TreeMap;
|
||||
import java.util.SortedMap;
|
||||
import java.util.Collections;
|
||||
|
||||
import net.i2p.router.message.HandleSourceRouteReplyMessageJob;
|
||||
import net.i2p.router.networkdb.HandleDatabaseLookupMessageJob;
|
||||
@@ -40,10 +42,8 @@ public class JobQueue {
|
||||
private ArrayList _readyJobs;
|
||||
/** list of jobs that are scheduled for running in the future */
|
||||
private ArrayList _timedJobs;
|
||||
/** when true, don't run any new jobs or update any limits, etc */
|
||||
private boolean _paused;
|
||||
/** job name to JobStat for that job */
|
||||
private TreeMap _jobStats;
|
||||
private SortedMap _jobStats;
|
||||
/** how many job queue runners can go concurrently */
|
||||
private int _maxRunners;
|
||||
private QueuePumper _pumper;
|
||||
@@ -115,8 +115,7 @@ public class JobQueue {
|
||||
_readyJobs = new ArrayList();
|
||||
_timedJobs = new ArrayList();
|
||||
_queueRunners = new HashMap();
|
||||
_paused = false;
|
||||
_jobStats = new TreeMap();
|
||||
_jobStats = Collections.synchronizedSortedMap(new TreeMap());
|
||||
_allowParallelOperation = false;
|
||||
_pumper = new QueuePumper();
|
||||
I2PThread pumperThread = new I2PThread(_pumper);
|
||||
@@ -153,9 +152,9 @@ public class JobQueue {
|
||||
|
||||
_context.statManager().addRateData("jobQueue.readyJobs", numReady, 0);
|
||||
if (shouldDrop(job, numReady)) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Dropping job due to overload! # ready jobs: "
|
||||
+ numReady + ": job = " + job);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping job due to overload! # ready jobs: "
|
||||
+ numReady + ": job = " + job);
|
||||
job.dropped();
|
||||
_context.statManager().addRateData("jobQueue.droppedJobs", 1, 1);
|
||||
awaken(1);
|
||||
@@ -239,9 +238,6 @@ public class JobQueue {
|
||||
*/
|
||||
Job getNext() {
|
||||
while (_alive) {
|
||||
while (_paused) {
|
||||
try { Thread.sleep(30); } catch (InterruptedException ie) {}
|
||||
}
|
||||
Job rv = null;
|
||||
int ready = 0;
|
||||
synchronized (_readyJobs) {
|
||||
@@ -252,10 +248,13 @@ public class JobQueue {
|
||||
if (rv != null) {
|
||||
// we found one, but there may be more, so wake up enough
|
||||
// other runners
|
||||
awaken(ready-1);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Using a ready job after waking up " + (ready-1) + " others");
|
||||
_log.debug("Waking up " + (ready-1) + " job runners (and running one)");
|
||||
awaken(ready-1);
|
||||
return rv;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("No jobs pending, waiting a second");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -264,6 +263,8 @@ public class JobQueue {
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No longer alive, returning null");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -339,8 +340,6 @@ public class JobQueue {
|
||||
}
|
||||
}
|
||||
|
||||
//public void pauseQueue() { _paused = true; }
|
||||
//public void unpauseQueue() { _paused = false; }
|
||||
void removeRunner(int id) { _queueRunners.remove(new Integer(id)); }
|
||||
|
||||
|
||||
@@ -356,23 +355,6 @@ public class JobQueue {
|
||||
_runnerLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
int numRunners = 0;
|
||||
synchronized (_queueRunners) {
|
||||
numRunners = _queueRunners.size();
|
||||
}
|
||||
|
||||
if (numRunners > 1) {
|
||||
if (numMadeReady > numRunners) {
|
||||
if (numMadeReady < _maxRunners) {
|
||||
_log.info("Too much job contention (" + numMadeReady + " ready and waiting, " + numRunners + " runners exist), adding " + numMadeReady + " new runners (with max " + _maxRunners + ")");
|
||||
runQueue(numMadeReady);
|
||||
} else {
|
||||
_log.info("Too much job contention (" + numMadeReady + " ready and waiting, " + numRunners + " runners exist), increasing to our max of " + _maxRunners + " runners");
|
||||
runQueue(_maxRunners);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,10 +372,6 @@ public class JobQueue {
|
||||
public void run() {
|
||||
try {
|
||||
while (_alive) {
|
||||
while (_paused) {
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
// periodically update our max runners limit
|
||||
long now = _context.clock().now();
|
||||
if (now > _lastLimitUpdated + MAX_LIMIT_UPDATE_DELAY) {
|
||||
@@ -436,13 +414,15 @@ public class JobQueue {
|
||||
MessageHistory hist = _context.messageHistory();
|
||||
long uptime = _context.router().getUptime();
|
||||
|
||||
synchronized (_jobStats) {
|
||||
if (!_jobStats.containsKey(key))
|
||||
_jobStats.put(key, new JobStats(key));
|
||||
JobStats stats = (JobStats)_jobStats.get(key);
|
||||
|
||||
stats.jobRan(duration, lag);
|
||||
JobStats stats = null;
|
||||
if (!_jobStats.containsKey(key)) {
|
||||
_jobStats.put(key, new JobStats(key));
|
||||
// yes, if two runners finish the same job at the same time, this could
|
||||
// create an extra object. but, who cares, its pushed out of the map
|
||||
// immediately anyway.
|
||||
}
|
||||
stats = (JobStats)_jobStats.get(key);
|
||||
stats.jobRan(duration, lag);
|
||||
|
||||
String dieMsg = null;
|
||||
|
||||
@@ -599,15 +579,20 @@ public class JobQueue {
|
||||
ArrayList readyJobs = null;
|
||||
ArrayList timedJobs = null;
|
||||
ArrayList activeJobs = new ArrayList(4);
|
||||
ArrayList justFinishedJobs = new ArrayList(4);
|
||||
synchronized (_readyJobs) { readyJobs = new ArrayList(_readyJobs); }
|
||||
synchronized (_timedJobs) { timedJobs = new ArrayList(_timedJobs); }
|
||||
synchronized (_queueRunners) {
|
||||
for (Iterator iter = _queueRunners.values().iterator(); iter.hasNext();) {
|
||||
JobQueueRunner runner = (JobQueueRunner)iter.next();
|
||||
Job job = runner.getCurrentJob();
|
||||
if (job != null)
|
||||
if (job != null) {
|
||||
activeJobs.add(job.getName());
|
||||
} else {
|
||||
job = runner.getLastJob();
|
||||
justFinishedJobs.add(job.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
StringBuffer buf = new StringBuffer(20*1024);
|
||||
buf.append("<h2>JobQueue</h2>");
|
||||
@@ -621,6 +606,11 @@ public class JobQueue {
|
||||
buf.append("<li>").append(activeJobs.get(i)).append("</li>\n");
|
||||
}
|
||||
buf.append("</ol>\n");
|
||||
buf.append("# just finished jobs: ").append(justFinishedJobs.size()).append("<ol>\n");
|
||||
for (int i = 0; i < justFinishedJobs.size(); i++) {
|
||||
buf.append("<li>").append(justFinishedJobs.get(i)).append("</li>\n");
|
||||
}
|
||||
buf.append("</ol>\n");
|
||||
buf.append("# ready/waiting jobs: ").append(readyJobs.size()).append(" <i>(lots of these mean there's likely a big problem)</i><ol>\n");
|
||||
for (int i = 0; i < readyJobs.size(); i++) {
|
||||
buf.append("<li>").append(readyJobs.get(i)).append("</li>\n");
|
||||
@@ -662,7 +652,7 @@ public class JobQueue {
|
||||
|
||||
TreeMap tstats = null;
|
||||
synchronized (_jobStats) {
|
||||
tstats = (TreeMap)_jobStats.clone();
|
||||
tstats = new TreeMap(_jobStats);
|
||||
}
|
||||
|
||||
for (Iterator iter = tstats.values().iterator(); iter.hasNext(); ) {
|
||||
|
||||
@@ -12,6 +12,7 @@ class JobQueueRunner implements Runnable {
|
||||
private int _id;
|
||||
private long _numJobs;
|
||||
private Job _currentJob;
|
||||
private Job _lastJob;
|
||||
|
||||
public JobQueueRunner(RouterContext context, int id) {
|
||||
_context = context;
|
||||
@@ -19,6 +20,7 @@ class JobQueueRunner implements Runnable {
|
||||
_keepRunning = true;
|
||||
_numJobs = 0;
|
||||
_currentJob = null;
|
||||
_lastJob = null;
|
||||
_log = _context.logManager().getLog(JobQueueRunner.class);
|
||||
_context.statManager().createRateStat("jobQueue.jobRun", "How long jobs take", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("jobQueue.jobLag", "How long jobs have to wait before running", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
@@ -27,6 +29,7 @@ class JobQueueRunner implements Runnable {
|
||||
}
|
||||
|
||||
public Job getCurrentJob() { return _currentJob; }
|
||||
public Job getLastJob() { return _lastJob; }
|
||||
public int getRunnerId() { return _id; }
|
||||
public void stopRunning() { _keepRunning = false; }
|
||||
public void run() {
|
||||
@@ -34,7 +37,11 @@ class JobQueueRunner implements Runnable {
|
||||
while ( (_keepRunning) && (_context.jobQueue().isAlive()) ) {
|
||||
try {
|
||||
Job job = _context.jobQueue().getNext();
|
||||
if (job == null) continue;
|
||||
if (job == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("getNext returned null - dead?");
|
||||
continue;
|
||||
}
|
||||
long now = _context.clock().now();
|
||||
|
||||
long enqueuedTime = 0;
|
||||
@@ -49,8 +56,8 @@ class JobQueueRunner implements Runnable {
|
||||
}
|
||||
|
||||
long betweenJobs = now - lastActive;
|
||||
_context.statManager().addRateData("jobQueue.jobRunnerInactive", betweenJobs, betweenJobs);
|
||||
_currentJob = job;
|
||||
_lastJob = null;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Runner " + _id + " running job " + job.getJobId() + ": " + job.getName());
|
||||
long origStartAfter = job.getTiming().getStartAfter();
|
||||
@@ -63,6 +70,7 @@ class JobQueueRunner implements Runnable {
|
||||
_context.jobQueue().updateStats(job, doStart, origStartAfter, duration);
|
||||
long diff = _context.clock().now() - beforeUpdate;
|
||||
|
||||
_context.statManager().addRateData("jobQueue.jobRunnerInactive", betweenJobs, betweenJobs);
|
||||
_context.statManager().addRateData("jobQueue.jobRun", duration, duration);
|
||||
_context.statManager().addRateData("jobQueue.jobLag", doStart - origStartAfter, 0);
|
||||
_context.statManager().addRateData("jobQueue.jobWait", enqueuedTime, enqueuedTime);
|
||||
@@ -75,6 +83,7 @@ class JobQueueRunner implements Runnable {
|
||||
_log.debug("Job duration " + duration + "ms for " + job.getName()
|
||||
+ " with lag of " + (doStart-origStartAfter) + "ms");
|
||||
lastActive = _context.clock().now();
|
||||
_lastJob = _currentJob;
|
||||
_currentJob = null;
|
||||
} catch (Throwable t) {
|
||||
if (_log.shouldLog(Log.CRIT))
|
||||
|
||||
@@ -5,71 +5,81 @@ import net.i2p.data.DataHelper;
|
||||
/** glorified struct to contain basic job stats */
|
||||
class JobStats {
|
||||
private String _job;
|
||||
private long _numRuns;
|
||||
private long _totalTime;
|
||||
private long _maxTime;
|
||||
private long _minTime;
|
||||
private long _totalPendingTime;
|
||||
private long _maxPendingTime;
|
||||
private long _minPendingTime;
|
||||
|
||||
private volatile long _numRuns;
|
||||
private volatile long _totalTime;
|
||||
private volatile long _maxTime;
|
||||
private volatile long _minTime;
|
||||
private volatile long _totalPendingTime;
|
||||
private volatile long _maxPendingTime;
|
||||
private volatile long _minPendingTime;
|
||||
|
||||
public JobStats(String name) {
|
||||
_job = name;
|
||||
_numRuns = 0;
|
||||
_totalTime = 0;
|
||||
_maxTime = -1;
|
||||
_minTime = -1;
|
||||
_totalPendingTime = 0;
|
||||
_maxPendingTime = -1;
|
||||
_minPendingTime = -1;
|
||||
_job = name;
|
||||
_numRuns = 0;
|
||||
_totalTime = 0;
|
||||
_maxTime = -1;
|
||||
_minTime = -1;
|
||||
_totalPendingTime = 0;
|
||||
_maxPendingTime = -1;
|
||||
_minPendingTime = -1;
|
||||
}
|
||||
|
||||
|
||||
public void jobRan(long runTime, long lag) {
|
||||
_numRuns++;
|
||||
_totalTime += runTime;
|
||||
if ( (_maxTime < 0) || (runTime > _maxTime) )
|
||||
_maxTime = runTime;
|
||||
if ( (_minTime < 0) || (runTime < _minTime) )
|
||||
_minTime = runTime;
|
||||
_totalPendingTime += lag;
|
||||
if ( (_maxPendingTime < 0) || (lag > _maxPendingTime) )
|
||||
_maxPendingTime = lag;
|
||||
if ( (_minPendingTime < 0) || (lag < _minPendingTime) )
|
||||
_minPendingTime = lag;
|
||||
_numRuns++;
|
||||
_totalTime += runTime;
|
||||
if ( (_maxTime < 0) || (runTime > _maxTime) )
|
||||
_maxTime = runTime;
|
||||
if ( (_minTime < 0) || (runTime < _minTime) )
|
||||
_minTime = runTime;
|
||||
_totalPendingTime += lag;
|
||||
if ( (_maxPendingTime < 0) || (lag > _maxPendingTime) )
|
||||
_maxPendingTime = lag;
|
||||
if ( (_minPendingTime < 0) || (lag < _minPendingTime) )
|
||||
_minPendingTime = lag;
|
||||
}
|
||||
|
||||
|
||||
public String getName() { return _job; }
|
||||
public long getRuns() { return _numRuns; }
|
||||
public long getTotalTime() { return _totalTime; }
|
||||
public long getMaxTime() { return _maxTime; }
|
||||
public long getMinTime() { return _minTime; }
|
||||
public long getAvgTime() { if (_numRuns > 0) return _totalTime / _numRuns; else return 0; }
|
||||
public long getAvgTime() {
|
||||
if (_numRuns > 0)
|
||||
return _totalTime / _numRuns;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
public long getTotalPendingTime() { return _totalPendingTime; }
|
||||
public long getMaxPendingTime() { return _maxPendingTime; }
|
||||
public long getMinPendingTime() { return _minPendingTime; }
|
||||
public long getAvgPendingTime() { if (_numRuns > 0) return _totalPendingTime / _numRuns; else return 0; }
|
||||
|
||||
public long getAvgPendingTime() {
|
||||
if (_numRuns > 0)
|
||||
return _totalPendingTime / _numRuns;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int hashCode() { return _job.hashCode(); }
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj != null) && (obj instanceof JobStats) ) {
|
||||
JobStats stats = (JobStats)obj;
|
||||
return DataHelper.eq(getName(), stats.getName()) &&
|
||||
getRuns() == stats.getRuns() &&
|
||||
getTotalTime() == stats.getTotalTime() &&
|
||||
getMaxTime() == stats.getMaxTime() &&
|
||||
getMinTime() == stats.getMinTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if ( (obj != null) && (obj instanceof JobStats) ) {
|
||||
JobStats stats = (JobStats)obj;
|
||||
return DataHelper.eq(getName(), stats.getName()) &&
|
||||
getRuns() == stats.getRuns() &&
|
||||
getTotalTime() == stats.getTotalTime() &&
|
||||
getMaxTime() == stats.getMaxTime() &&
|
||||
getMinTime() == stats.getMinTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("Over ").append(getRuns()).append(" runs, job <b>").append(getName()).append("</b> took ");
|
||||
buf.append(getTotalTime()).append("ms (").append(getAvgTime()).append("ms/").append(getMaxTime()).append("ms/");
|
||||
buf.append(getMinTime()).append("ms avg/max/min) after a total lag of ");
|
||||
buf.append(getTotalPendingTime()).append("ms (").append(getAvgPendingTime()).append("ms/");
|
||||
buf.append(getMaxPendingTime()).append("ms/").append(getMinPendingTime()).append("ms avg/max/min)");
|
||||
return buf.toString();
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("Over ").append(getRuns()).append(" runs, job <b>").append(getName()).append("</b> took ");
|
||||
buf.append(getTotalTime()).append("ms (").append(getAvgTime()).append("ms/").append(getMaxTime()).append("ms/");
|
||||
buf.append(getMinTime()).append("ms avg/max/min) after a total lag of ");
|
||||
buf.append(getTotalPendingTime()).append("ms (").append(getAvgPendingTime()).append("ms/");
|
||||
buf.append(getMaxPendingTime()).append("ms/").append(getMinPendingTime()).append("ms avg/max/min)");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,13 @@ public interface ProfileManager {
|
||||
*/
|
||||
void tunnelRejected(Hash peer, long responseTimeMs);
|
||||
|
||||
/**
|
||||
* Note that a tunnel that the router is participating in
|
||||
* was successfully tested with the given round trip latency
|
||||
*
|
||||
*/
|
||||
void tunnelTestSucceeded(Hash peer, long responseTimeMs);
|
||||
|
||||
/**
|
||||
* Note that the peer participated in a tunnel that failed. Its failure may not have
|
||||
* been the peer's fault however.
|
||||
|
||||
@@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
|
||||
*
|
||||
*/
|
||||
public class RouterVersion {
|
||||
public final static String ID = "$Revision: 1.2 $ $Date: 2004/04/20 04:18:53 $";
|
||||
public final static String VERSION = "0.3.1";
|
||||
public final static String ID = "$Revision: 1.4 $ $Date: 2004/05/07 12:52:49 $";
|
||||
public final static String VERSION = "0.3.1.2";
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Router version: " + VERSION);
|
||||
System.out.println("Router ID: " + RouterVersion.ID);
|
||||
|
||||
@@ -104,11 +104,14 @@ public class StatisticsManager implements Service {
|
||||
includeRate("jobQueue.droppedJobs", stats, new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
includeRate("inNetPool.dropped", stats, new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
includeRate("tunnel.participatingTunnels", stats, new long[] { 5*60*1000, 60*60*1000 });
|
||||
includeRate("tunnel.testSuccessTime", stats, new long[] { 60*60*1000l, 24*60*60*1000l });
|
||||
includeRate("netDb.lookupsReceived", stats, new long[] { 5*60*1000, 60*60*1000 });
|
||||
includeRate("netDb.lookupsHandled", stats, new long[] { 5*60*1000, 60*60*1000 });
|
||||
includeRate("netDb.lookupsMatched", stats, new long[] { 5*60*1000, 60*60*1000 });
|
||||
includeRate("netDb.storeSent", stats, new long[] { 5*60*1000, 60*60*1000 });
|
||||
includeRate("netDb.successPeers", stats, new long[] { 60*60*1000 });
|
||||
includeRate("netDb.failedPeers", stats, new long[] { 60*60*1000 });
|
||||
includeRate("netDb.searchCount", stats, new long[] { 3*60*60*1000});
|
||||
includeRate("transport.receiveMessageSize", stats, new long[] { 5*60*1000, 60*60*1000 });
|
||||
includeRate("transport.sendMessageSize", stats, new long[] { 5*60*1000, 60*60*1000 });
|
||||
includeRate("client.sendAckTime", stats, new long[] { 60*60*1000, 24*60*60*1000l }, true);
|
||||
|
||||
@@ -48,6 +48,7 @@ public class TunnelInfo extends DataStructureImpl {
|
||||
private Properties _options;
|
||||
private TunnelSettings _settings;
|
||||
private long _created;
|
||||
private long _lastTested;
|
||||
private boolean _ready;
|
||||
private boolean _wasEverReady;
|
||||
|
||||
@@ -67,6 +68,7 @@ public class TunnelInfo extends DataStructureImpl {
|
||||
_ready = false;
|
||||
_wasEverReady = false;
|
||||
_created = _context.clock().now();
|
||||
_lastTested = -1;
|
||||
}
|
||||
|
||||
public TunnelId getTunnelId() { return _id; }
|
||||
@@ -142,6 +144,10 @@ public class TunnelInfo extends DataStructureImpl {
|
||||
|
||||
public long getCreated() { return _created; }
|
||||
|
||||
/** when was the peer last tested (or -1 if never)? */
|
||||
public long getLastTested() { return _lastTested; }
|
||||
public void setLastTested(long when) { _lastTested = when; }
|
||||
|
||||
/**
|
||||
* Number of hops left in the tunnel (including this one)
|
||||
*
|
||||
|
||||
@@ -8,6 +8,9 @@ import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.Locale;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.text.ParseException;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.router.Router;
|
||||
@@ -48,6 +51,9 @@ class AdminRunner implements Runnable {
|
||||
reply(out, _generator.generateStatsPage());
|
||||
} else if (command.indexOf("/profile/") >= 0) {
|
||||
replyText(out, getProfile(command));
|
||||
} else if (command.indexOf("setTime") >= 0) {
|
||||
setTime(command);
|
||||
reply(out, "<html><body>Time updated</body></html>");
|
||||
} else if (true || command.indexOf("routerConsole.html") > 0) {
|
||||
reply(out, _context.router().renderStatusHTML());
|
||||
}
|
||||
@@ -105,4 +111,30 @@ class AdminRunner implements Runnable {
|
||||
|
||||
return "No such peer is being profiled\n";
|
||||
}
|
||||
|
||||
|
||||
private static final String FORMAT_STRING = "yyyyMMdd_HH:mm:ss.SSS";
|
||||
private SimpleDateFormat _fmt = new SimpleDateFormat(FORMAT_STRING, Locale.UK);
|
||||
|
||||
private long getTime(String now) throws ParseException {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.parse(now).getTime();
|
||||
}
|
||||
}
|
||||
private void setTime(String cmd) {
|
||||
int start = cmd.indexOf("now=");
|
||||
String str = cmd.substring(start + 4, start+4+FORMAT_STRING.length());
|
||||
try {
|
||||
long now = getTime(str);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.log(Log.INFO, "Admin time set to " + str);
|
||||
setTime(now);
|
||||
} catch (ParseException pe) {
|
||||
_log.error("Invalid time specified [" + str + "]", pe);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTime(long now) {
|
||||
_context.clock().setNow(now);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,9 +68,9 @@ public class ClientListenerRunner implements Runnable {
|
||||
_log.debug("Connection received");
|
||||
runConnection(socket);
|
||||
} else {
|
||||
socket.close();
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Refused connection from " + socket.getInetAddress());
|
||||
socket.close();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Server error accepting", ioe);
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.data.Certificate;
|
||||
@@ -60,6 +61,7 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
private NextStepJob _nextStep;
|
||||
private LookupLeaseSetFailedJob _lookupLeaseSetFailed;
|
||||
private long _overallExpiration;
|
||||
private boolean _shouldBundle;
|
||||
|
||||
/**
|
||||
* final timeout (in milliseconds) that the outbound message will fail in.
|
||||
@@ -77,6 +79,29 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
/** dont search for the lease more than 6 times */
|
||||
private final static int MAX_LEASE_LOOKUPS = 6;
|
||||
|
||||
/**
|
||||
* If the client's config specifies shouldBundleReplyInfo=true, messages sent from
|
||||
* that client to any peers will probabalistically include the sending destination's
|
||||
* current LeaseSet (allowing the recipient to reply without having to do a full
|
||||
* netDb lookup). This should improve performance during the initial negotiations,
|
||||
* but is not necessary for communication that isn't bidirectional.
|
||||
*
|
||||
*/
|
||||
public static final String BUNDLE_REPLY_LEASESET = "shouldBundleReplyInfo";
|
||||
/**
|
||||
* Allow the override of the frequency of bundling the reply info in with a message.
|
||||
* The client app can specify bundleReplyInfoProbability=80 (for instance) and that
|
||||
* will cause the router to include the sender's leaseSet with 80% of the messages
|
||||
* sent to the peer.
|
||||
*
|
||||
*/
|
||||
public static final String BUNDLE_PROBABILITY = "bundleReplyInfoProbability";
|
||||
/**
|
||||
* How often do messages include the reply leaseSet (out of every 100 tries).
|
||||
* Including it each time is probably overkill, but who knows.
|
||||
*/
|
||||
private static final int BUNDLE_PROBABILITY_DEFAULT = 80;
|
||||
|
||||
/**
|
||||
* Send the sucker
|
||||
*/
|
||||
@@ -109,20 +134,21 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
_status = new OutboundClientMessageStatus(msg);
|
||||
_nextStep = new NextStepJob();
|
||||
_lookupLeaseSetFailed = new LookupLeaseSetFailedJob();
|
||||
_shouldBundle = getShouldBundle();
|
||||
}
|
||||
|
||||
public String getName() { return "Outbound client message"; }
|
||||
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send outbound client message job beginning");
|
||||
_log.debug(getJobId() + ": Send outbound client message job beginning");
|
||||
buildClove();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Clove built");
|
||||
_log.debug(getJobId() + ": Clove built");
|
||||
Hash to = _status.getTo().calculateHash();
|
||||
long timeoutMs = _overallExpiration - _context.clock().now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send outbound client message - sending off leaseSet lookup job");
|
||||
_log.debug(getJobId() + ": Send outbound client message - sending off leaseSet lookup job");
|
||||
_status.incrementLookups();
|
||||
_context.netDb().lookupLeaseSet(to, _nextStep, _lookupLeaseSetFailed, timeoutMs);
|
||||
}
|
||||
@@ -132,24 +158,24 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
*/
|
||||
private void sendNext() {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("sendNext() called with " + _status.getNumSent() + " already sent");
|
||||
_log.debug(getJobId() + ": sendNext() called with " + _status.getNumSent() + " already sent");
|
||||
}
|
||||
|
||||
if (_status.getSuccess()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("sendNext() - already successful!");
|
||||
_log.debug(getJobId() + ": sendNext() - already successful!");
|
||||
return;
|
||||
}
|
||||
if (_status.getFailure()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("sendNext() - already failed!");
|
||||
_log.debug(getJobId() + ": sendNext() - already failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
long now = _context.clock().now();
|
||||
if (now >= _overallExpiration) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("sendNext() - Expired");
|
||||
_log.warn(getJobId() + ": sendNext() - Expired");
|
||||
dieFatal();
|
||||
return;
|
||||
}
|
||||
@@ -157,26 +183,26 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
Lease nextLease = getNextLease();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send outbound client message - next lease found for ["
|
||||
_log.debug(getJobId() + ": Send outbound client message - next lease found for ["
|
||||
+ _status.getTo().calculateHash().toBase64() + "] - "
|
||||
+ nextLease);
|
||||
|
||||
if (nextLease == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No more leases, and we still haven't heard back from the peer"
|
||||
_log.warn(getJobId() + ": No more leases, and we still haven't heard back from the peer"
|
||||
+ ", refetching the leaseSet to try again");
|
||||
_status.setLeaseSet(null);
|
||||
long remainingMs = _overallExpiration - _context.clock().now();
|
||||
if (_status.getNumLookups() < MAX_LEASE_LOOKUPS) {
|
||||
_status.incrementLookups();
|
||||
Hash to = _status.getMessage().getDestination().calculateHash();
|
||||
_status.clearAlreadySent();
|
||||
_context.netDb().fail(to);
|
||||
_status.clearAlreadySent(); // so we can send down old tunnels again
|
||||
_context.netDb().fail(to); // so we don't just fetch what we have
|
||||
_context.netDb().lookupLeaseSet(to, _nextStep, _lookupLeaseSetFailed, remainingMs);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("sendNext() - max # lease lookups exceeded! "
|
||||
_log.warn(getJobId() + ": sendNext() - max # lease lookups exceeded! "
|
||||
+ _status.getNumLookups());
|
||||
dieFatal();
|
||||
return;
|
||||
@@ -199,12 +225,12 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
if (ls == null) {
|
||||
ls = _context.netDb().lookupLeaseSetLocally(_status.getTo().calculateHash());
|
||||
if (ls == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Lookup locally didn't find the leaseSet");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Lookup locally didn't find the leaseSet");
|
||||
return null;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Lookup locally DID find the leaseSet");
|
||||
_log.debug(getJobId() + ": Lookup locally DID find the leaseSet");
|
||||
}
|
||||
_status.setLeaseSet(ls);
|
||||
}
|
||||
@@ -216,7 +242,7 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
Lease lease = ls.getLease(i);
|
||||
if (lease.isExpired(Router.CLOCK_FUDGE_FACTOR)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("getNextLease() - expired lease! - " + lease);
|
||||
_log.warn(getJobId() + ": getNextLease() - expired lease! - " + lease);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -224,12 +250,19 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
leases.add(lease);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("getNextLease() - skipping lease we've already sent it down - "
|
||||
_log.debug(getJobId() + ": getNextLease() - skipping lease we've already sent it down - "
|
||||
+ lease);
|
||||
}
|
||||
}
|
||||
|
||||
// randomize the ordering (so leases with equal # of failures per next sort are randomly ordered)
|
||||
if (leases.size() <= 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": No leases found, since we've tried them all (so fail it and relookup)");
|
||||
return null;
|
||||
}
|
||||
|
||||
// randomize the ordering (so leases with equal # of failures per next
|
||||
// sort are randomly ordered)
|
||||
Collections.shuffle(leases);
|
||||
|
||||
// ordered by lease number of failures
|
||||
@@ -241,15 +274,32 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
id++;
|
||||
orderedLeases.put(new Long(id), lease);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("getNextLease() - ranking lease we havent sent it down as " + id);
|
||||
_log.debug(getJobId() + ": ranking lease we havent sent it down as " + id);
|
||||
}
|
||||
|
||||
if (orderedLeases.size() <= 0) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No leases in the ordered set found! all = " + leases.size());
|
||||
return null;
|
||||
return (Lease)orderedLeases.get(orderedLeases.firstKey());
|
||||
}
|
||||
|
||||
private boolean getShouldBundle() {
|
||||
Properties opts = _status.getMessage().getSenderConfig().getOptions();
|
||||
String wantBundle = opts.getProperty(BUNDLE_REPLY_LEASESET, "true");
|
||||
if ("true".equals(wantBundle)) {
|
||||
int probability = BUNDLE_PROBABILITY_DEFAULT;
|
||||
String str = opts.getProperty(BUNDLE_PROBABILITY);
|
||||
try {
|
||||
if (str != null)
|
||||
probability = Integer.parseInt(str);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getJobId() + ": Bundle leaseSet probability overridden incorrectly ["
|
||||
+ str + "]", nfe);
|
||||
}
|
||||
if (probability >= _context.random().nextInt(100))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else {
|
||||
return (Lease)orderedLeases.get(orderedLeases.firstKey());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,19 +312,23 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
*
|
||||
*/
|
||||
private void send(Lease lease) {
|
||||
// send it as a garlic with a DeliveryStatusMessage clove and a message selector w/ successJob on reply
|
||||
long token = _context.random().nextInt(Integer.MAX_VALUE);
|
||||
PublicKey key = _status.getLeaseSet().getEncryptionKey();
|
||||
SessionKey sessKey = new SessionKey();
|
||||
Set tags = new HashSet();
|
||||
LeaseSet replyLeaseSet = null;
|
||||
if (_shouldBundle) {
|
||||
replyLeaseSet = _context.netDb().lookupLeaseSetLocally(_status.getFrom().calculateHash());
|
||||
}
|
||||
|
||||
GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(_context, token,
|
||||
_overallExpiration, key,
|
||||
_status.getClove(),
|
||||
_status.getTo(), sessKey,
|
||||
tags, true);
|
||||
tags, true, replyLeaseSet);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("send(lease) - token expected " + token);
|
||||
_log.debug(getJobId() + ": send(lease) - token expected " + token);
|
||||
|
||||
_status.sent(lease.getRouterIdentity().getHash(), lease.getTunnelId());
|
||||
|
||||
@@ -283,14 +337,14 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
ReplySelector selector = new ReplySelector(token);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Placing GarlicMessage into the new tunnel message bound for "
|
||||
_log.debug(getJobId() + ": Placing GarlicMessage into the new tunnel message bound for "
|
||||
+ lease.getTunnelId() + " on "
|
||||
+ lease.getRouterIdentity().getHash().toBase64());
|
||||
|
||||
TunnelId outTunnelId = selectOutboundTunnel();
|
||||
if (outTunnelId != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending tunnel message out " + outTunnelId + " to "
|
||||
_log.debug(getJobId() + ": Sending tunnel message out " + outTunnelId + " to "
|
||||
+ lease.getTunnelId() + " on "
|
||||
+ lease.getRouterIdentity().getHash().toBase64());
|
||||
SendTunnelMessageJob j = new SendTunnelMessageJob(_context, msg, outTunnelId,
|
||||
@@ -301,8 +355,8 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
_context.jobQueue().addJob(j);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not find any outbound tunnels to send the payload through... wtf?");
|
||||
_context.jobQueue().addJob(onFail);
|
||||
_log.error(getJobId() + ": Could not find any outbound tunnels to send the payload through... wtf?");
|
||||
dieFatal();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,12 +388,12 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
ClientMessage msg = _status.getMessage();
|
||||
if (alreadyFailed) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("dieFatal() - already failed sending " + msg.getMessageId()
|
||||
_log.debug(getJobId() + ": dieFatal() - already failed sending " + msg.getMessageId()
|
||||
+ ", no need to do it again", new Exception("Duplicate death?"));
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Failed to send the message " + msg.getMessageId() + " after "
|
||||
_log.error(getJobId() + ": Failed to send the message " + msg.getMessageId() + " after "
|
||||
+ _status.getNumSent() + " sends and " + _status.getNumLookups()
|
||||
+ " lookups (and " + sendTime + "ms)",
|
||||
new Exception("Message send failure"));
|
||||
@@ -378,7 +432,7 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
_status.setClove(clove);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Built payload clove with id " + clove.getId());
|
||||
_log.debug(getJobId() + ": Built payload clove with id " + clove.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -587,7 +641,8 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
boolean alreadySuccessful = _status.success();
|
||||
MessageId msgId = _status.getMessage().getMessageId();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("SUCCESS! Message delivered completely for message " + msgId
|
||||
_log.debug(OutboundClientMessageJob.this.getJobId()
|
||||
+ ": SUCCESS! Message delivered completely for message " + msgId
|
||||
+ " after " + sendTime + "ms [for "
|
||||
+ _status.getMessage().getMessageId() + "]");
|
||||
|
||||
@@ -598,7 +653,8 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
|
||||
if (alreadySuccessful) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Success is a duplicate for " + _status.getMessage().getMessageId()
|
||||
_log.debug(OutboundClientMessageJob.this.getJobId()
|
||||
+ ": Success is a duplicate for " + _status.getMessage().getMessageId()
|
||||
+ ", dont notify again...");
|
||||
return;
|
||||
}
|
||||
@@ -631,7 +687,8 @@ public class OutboundClientMessageJob extends JobImpl {
|
||||
public String getName() { return "Send client message timed out through a lease"; }
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Soft timeout through the lease " + _lease);
|
||||
_log.debug(OutboundClientMessageJob.this.getJobId()
|
||||
+ ": Soft timeout through the lease " + _lease);
|
||||
_lease.setNumFailure(_lease.getNumFailure()+1);
|
||||
sendNext();
|
||||
}
|
||||
|
||||
@@ -19,9 +19,11 @@ import net.i2p.data.Payload;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.i2np.DataMessage;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.TunnelInfo;
|
||||
@@ -50,23 +52,32 @@ class OutboundClientMessageJobHelper {
|
||||
*
|
||||
* For now, its just a tunneled DeliveryStatusMessage
|
||||
*
|
||||
* @param bundledReplyLeaseSet if specified, the given LeaseSet will be packaged with the message (allowing
|
||||
* much faster replies, since their netDb search will return almost instantly)
|
||||
*/
|
||||
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK, Payload data, Destination dest, SessionKey wrappedKey, Set wrappedTags, boolean requireAck) {
|
||||
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
|
||||
Payload data, Destination dest, SessionKey wrappedKey, Set wrappedTags,
|
||||
boolean requireAck, LeaseSet bundledReplyLeaseSet) {
|
||||
PayloadGarlicConfig dataClove = buildDataClove(ctx, data, dest, expiration);
|
||||
return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, dest, wrappedKey, wrappedTags, requireAck);
|
||||
return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, dest, wrappedKey,
|
||||
wrappedTags, requireAck, bundledReplyLeaseSet);
|
||||
}
|
||||
/**
|
||||
* Allow the app to specify the data clove directly, which enables OutboundClientMessage to resend the
|
||||
* same payload (including expiration and unique id) in different garlics (down different tunnels)
|
||||
*
|
||||
*/
|
||||
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK, PayloadGarlicConfig dataClove, Destination dest, SessionKey wrappedKey, Set wrappedTags, boolean requireAck) {
|
||||
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, dest, requireAck);
|
||||
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
|
||||
PayloadGarlicConfig dataClove, Destination dest, SessionKey wrappedKey,
|
||||
Set wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet) {
|
||||
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, dest, requireAck, bundledReplyLeaseSet);
|
||||
GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, config, wrappedKey, wrappedTags);
|
||||
return msg;
|
||||
}
|
||||
|
||||
private static GarlicConfig createGarlicConfig(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK, PayloadGarlicConfig dataClove, Destination dest, boolean requireAck) {
|
||||
private static GarlicConfig createGarlicConfig(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
|
||||
PayloadGarlicConfig dataClove, Destination dest, boolean requireAck,
|
||||
LeaseSet bundledReplyLeaseSet) {
|
||||
Log log = ctx.logManager().getLog(OutboundClientMessageJobHelper.class);
|
||||
log.debug("Reply token: " + replyToken);
|
||||
GarlicConfig config = new GarlicConfig();
|
||||
@@ -78,6 +89,11 @@ class OutboundClientMessageJobHelper {
|
||||
config.addClove(ackClove);
|
||||
}
|
||||
|
||||
if (bundledReplyLeaseSet != null) {
|
||||
PayloadGarlicConfig leaseSetClove = buildLeaseSetClove(ctx, expiration, bundledReplyLeaseSet);
|
||||
config.addClove(leaseSetClove);
|
||||
}
|
||||
|
||||
DeliveryInstructions instructions = new DeliveryInstructions();
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL);
|
||||
instructions.setDelayRequested(false);
|
||||
@@ -177,4 +193,32 @@ class OutboundClientMessageJobHelper {
|
||||
|
||||
return clove;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a clove that stores the leaseSet locally
|
||||
*/
|
||||
static PayloadGarlicConfig buildLeaseSetClove(RouterContext ctx, long expiration, LeaseSet replyLeaseSet) {
|
||||
PayloadGarlicConfig clove = new PayloadGarlicConfig();
|
||||
|
||||
DeliveryInstructions instructions = new DeliveryInstructions();
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL);
|
||||
instructions.setDelayRequested(false);
|
||||
instructions.setDelaySeconds(0);
|
||||
instructions.setEncrypted(false);
|
||||
|
||||
clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
clove.setDeliveryInstructions(instructions);
|
||||
clove.setExpiration(expiration);
|
||||
clove.setId(ctx.random().nextInt(Integer.MAX_VALUE));
|
||||
DatabaseStoreMessage msg = new DatabaseStoreMessage(ctx);
|
||||
msg.setLeaseSet(replyLeaseSet);
|
||||
msg.setMessageExpiration(new Date(expiration));
|
||||
msg.setKey(replyLeaseSet.getDestination().calculateHash());
|
||||
clove.setPayload(msg);
|
||||
clove.setRecipientPublicKey(null);
|
||||
clove.setRequestAck(false);
|
||||
|
||||
return clove;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +401,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
|
||||
// Iterate through the old failure / success count, copying over the old
|
||||
// values (if any tunnels overlap between leaseSets). no need to be
|
||||
// uberthreadsafe fascists here, since these values are just heuristics
|
||||
// ueberthreadsafe fascists here, since these values are just heuristics
|
||||
if (rv != null) {
|
||||
for (int i = 0; i < rv.getLeaseCount(); i++) {
|
||||
Lease old = rv.getLease(i);
|
||||
|
||||
@@ -80,7 +80,8 @@ class SearchJob extends JobImpl {
|
||||
_context.statManager().createRateStat("netDb.successTime", "How long a successful search takes", "Network Database", new long[] { 60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.failedTime", "How long a failed search takes", "Network Database", new long[] { 60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.successPeers", "How many peers are contacted in a successful search", "Network Database", new long[] { 60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.failedPeers", "How many peers are contacted in a failed search", "Network Database", new long[] { 60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.failedPeers", "How many peers fail to respond to a lookup?", "Network Database", new long[] { 60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.searchCount", "Overall number of searches sent", "Network Database", new long[] { 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Search (" + getClass().getName() + " for " + key.toBase64(), new Exception("Search enqueued by"));
|
||||
}
|
||||
@@ -88,6 +89,7 @@ class SearchJob extends JobImpl {
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Searching for " + _state.getTarget()); // , getAddedBy());
|
||||
_context.statManager().addRateData("netDb.searchCount", 1, 0);
|
||||
searchNext();
|
||||
}
|
||||
|
||||
@@ -474,6 +476,7 @@ class SearchJob extends JobImpl {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("NOT (!!) Penalizing peer for timeout on search: " + _peer.toBase64());
|
||||
}
|
||||
_context.statManager().addRateData("netDb.failedPeers", 1, 0);
|
||||
searchNext();
|
||||
}
|
||||
public String getName() { return "Kademlia Search Failed"; }
|
||||
@@ -509,7 +512,6 @@ class SearchJob extends JobImpl {
|
||||
if (_keepStats) {
|
||||
long time = _context.clock().now() - _state.getWhenStarted();
|
||||
_context.statManager().addRateData("netDb.failedTime", time, 0);
|
||||
_context.statManager().addRateData("netDb.failedPeers", _state.getAttempted().size(), time);
|
||||
}
|
||||
if (_onFailure != null)
|
||||
_context.jobQueue().addJob(_onFailure);
|
||||
|
||||
@@ -4,8 +4,9 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.router.RouterContext;
|
||||
import java.io.File;
|
||||
|
||||
class PeerProfile {
|
||||
public class PeerProfile {
|
||||
private Log _log;
|
||||
private RouterContext _context;
|
||||
// whoozaat?
|
||||
@@ -22,6 +23,7 @@ class PeerProfile {
|
||||
private RateStat _receiveSize = null;
|
||||
private RateStat _dbResponseTime = null;
|
||||
private RateStat _tunnelCreateResponseTime = null;
|
||||
private RateStat _tunnelTestResponseTime = null;
|
||||
private RateStat _commError = null;
|
||||
private RateStat _dbIntroduction = null;
|
||||
// calculation bonuses
|
||||
@@ -63,7 +65,7 @@ class PeerProfile {
|
||||
public void setPeer(Hash peer) { _peer = peer; }
|
||||
|
||||
/**
|
||||
* are we keeping an expanded profile on the peer, or just the bare minimum?
|
||||
* are we keeping an expanded profile on the peer, or just the bare minimum.
|
||||
* If we aren't keeping the expanded profile, all of the rates as well as the
|
||||
* TunnelHistory and DBHistory will not be available.
|
||||
*
|
||||
@@ -123,7 +125,9 @@ class PeerProfile {
|
||||
public RateStat getDbResponseTime() { return _dbResponseTime; }
|
||||
/** how long it takes to get a tunnel create response from the peer (in milliseconds), calculated over a 1 minute, 1 hour, and 1 day period */
|
||||
public RateStat getTunnelCreateResponseTime() { return _tunnelCreateResponseTime; }
|
||||
/** how long between communication errors with the peer (e.g. disconnection), calculated over a 1 minute, 1 hour, and 1 day period */
|
||||
/** how long it takes to successfully test a tunnel this peer participates in (in milliseconds), calculated over a 10 minute, 1 hour, and 1 day period */
|
||||
public RateStat getTunnelTestResponseTime() { return _tunnelTestResponseTime; }
|
||||
/** how long between communication errors with the peer (disconnection, etc), calculated over a 1 minute, 1 hour, and 1 day period */
|
||||
public RateStat getCommError() { return _commError; }
|
||||
/** how many new peers we get from dbSearchReplyMessages or dbStore messages, calculated over a 1 hour, 1 day, and 1 week period */
|
||||
public RateStat getDbIntroduction() { return _dbIntroduction; }
|
||||
@@ -160,7 +164,7 @@ class PeerProfile {
|
||||
*/
|
||||
public double getSpeedValue() { return _speedValue; }
|
||||
/**
|
||||
* How likely are they to stay up and pass on messages over the next few minutes?
|
||||
* How likely are they to stay up and pass on messages over the next few minutes.
|
||||
* Positive numbers means more likely, negative numbers means its probably not
|
||||
* even worth trying.
|
||||
*
|
||||
@@ -189,6 +193,7 @@ class PeerProfile {
|
||||
_receiveSize = null;
|
||||
_dbResponseTime = null;
|
||||
_tunnelCreateResponseTime = null;
|
||||
_tunnelTestResponseTime = null;
|
||||
_commError = null;
|
||||
_dbIntroduction = null;
|
||||
_tunnelHistory = null;
|
||||
@@ -207,25 +212,27 @@ class PeerProfile {
|
||||
public void expandProfile() {
|
||||
if (_sendSuccessSize == null)
|
||||
_sendSuccessSize = new RateStat("sendSuccessSize", "How large successfully sent messages are", "profile", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
if (_sendFailureSize == null)
|
||||
_sendFailureSize = new RateStat("sendFailureSize", "How large messages that could not be sent were", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_receiveSize == null)
|
||||
_receiveSize = new RateStat("receiveSize", "How large received messages are", "profile", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_dbResponseTime == null)
|
||||
_dbResponseTime = new RateStat("dbResponseTime", "how long it takes to get a db response from the peer (in milliseconds)", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_tunnelCreateResponseTime == null)
|
||||
_tunnelCreateResponseTime = new RateStat("tunnelCreateResponseTime", "how long it takes to get a tunnel create response from the peer (in milliseconds)", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_commError == null)
|
||||
_commError = new RateStat("commErrorRate", "how long between communication errors with the peer (e.g. disconnection)", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_dbIntroduction == null)
|
||||
_dbIntroduction = new RateStat("dbIntroduction", "how many new peers we get from dbSearchReplyMessages or dbStore messages", "profile", new long[] { 60*60*1000l, 24*60*60*1000l, 7*24*60*60*1000l });
|
||||
|
||||
if (_tunnelHistory == null)
|
||||
_tunnelHistory = new TunnelHistory(_context);
|
||||
if (_dbHistory == null)
|
||||
_dbHistory = new DBHistory(_context);
|
||||
|
||||
_expanded = true;
|
||||
if (_sendFailureSize == null)
|
||||
_sendFailureSize = new RateStat("sendFailureSize", "How large messages that could not be sent were", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_receiveSize == null)
|
||||
_receiveSize = new RateStat("receiveSize", "How large received messages are", "profile", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_dbResponseTime == null)
|
||||
_dbResponseTime = new RateStat("dbResponseTime", "how long it takes to get a db response from the peer (in milliseconds)", "profile", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_tunnelCreateResponseTime == null)
|
||||
_tunnelCreateResponseTime = new RateStat("tunnelCreateResponseTime", "how long it takes to get a tunnel create response from the peer (in milliseconds)", "profile", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_tunnelTestResponseTime == null)
|
||||
_tunnelTestResponseTime = new RateStat("tunnelTestResponseTime", "how long it takes to successfully test a tunnel this peer participates in (in milliseconds)", "profile", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_commError == null)
|
||||
_commError = new RateStat("commErrorRate", "how long between communication errors with the peer (e.g. disconnection)", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_dbIntroduction == null)
|
||||
_dbIntroduction = new RateStat("dbIntroduction", "how many new peers we get from dbSearchReplyMessages or dbStore messages", "profile", new long[] { 60*60*1000l, 24*60*60*1000l, 7*24*60*60*1000l });
|
||||
|
||||
if (_tunnelHistory == null)
|
||||
_tunnelHistory = new TunnelHistory(_context);
|
||||
if (_dbHistory == null)
|
||||
_dbHistory = new DBHistory(_context);
|
||||
|
||||
_expanded = true;
|
||||
}
|
||||
|
||||
/** update the stats and rates (this should be called once a minute) */
|
||||
@@ -238,6 +245,7 @@ class PeerProfile {
|
||||
_sendFailureSize.coallesceStats();
|
||||
_sendSuccessSize.coallesceStats();
|
||||
_tunnelCreateResponseTime.coallesceStats();
|
||||
_tunnelTestResponseTime.coallesceStats();
|
||||
_dbHistory.coallesceStats();
|
||||
|
||||
_speedValue = calculateSpeed();
|
||||
@@ -270,7 +278,7 @@ class PeerProfile {
|
||||
* for an expanded profile, and ~212 bytes for a compacted one.
|
||||
*
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
public static void main2(String args[]) {
|
||||
RouterContext ctx = new RouterContext(null);
|
||||
testProfileSize(ctx, 100, 0); // 560KB
|
||||
testProfileSize(ctx, 1000, 0); // 3.9MB
|
||||
@@ -280,6 +288,36 @@ class PeerProfile {
|
||||
testProfileSize(ctx, 0, 300000); // 63MB
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in all of the profiles specified and print out
|
||||
* their calculated values. Usage: <pre>
|
||||
* PeerProfile [filename]*
|
||||
* </pre>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
RouterContext ctx = new RouterContext(new net.i2p.router.Router());
|
||||
ProfilePersistenceHelper helper = new ProfilePersistenceHelper(ctx);
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException e) {}
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
PeerProfile profile = helper.readProfile(new File(args[i]));
|
||||
if (profile == null) {
|
||||
buf.append("Could not load profile ").append(args[i]).append('\n');
|
||||
continue;
|
||||
}
|
||||
//profile.coallesceStats();
|
||||
buf.append("Peer " + profile.getPeer().toBase64()
|
||||
+ ":\t Speed:\t" + profile.calculateSpeed()
|
||||
+ " Reliability:\t" + profile.calculateReliability()
|
||||
+ " Integration:\t" + profile.calculateIntegration()
|
||||
+ " Active?\t" + profile.getIsActive()
|
||||
+ " Failing?\t" + profile.calculateIsFailing()
|
||||
+ '\n');
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException e) {}
|
||||
System.out.println(buf.toString());
|
||||
}
|
||||
|
||||
private static void testProfileSize(RouterContext ctx, int numExpanded, int numCompact) {
|
||||
Runtime.getRuntime().gc();
|
||||
PeerProfile profs[] = new PeerProfile[numExpanded];
|
||||
|
||||
@@ -101,6 +101,17 @@ public class ProfileManagerImpl implements ProfileManager {
|
||||
data.getTunnelHistory().incrementRejected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that a tunnel that the router is participating in
|
||||
* was successfully tested with the given round trip latency
|
||||
*
|
||||
*/
|
||||
public void tunnelTestSucceeded(Hash peer, long responseTimeMs) {
|
||||
PeerProfile data = getProfile(peer);
|
||||
if (data == null) return;
|
||||
data.getTunnelTestResponseTime().addData(responseTimeMs, responseTimeMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that the peer participated in a tunnel that failed. Its failure may not have
|
||||
* been the peer's fault however.
|
||||
|
||||
@@ -57,6 +57,15 @@ public class ProfileOrganizer {
|
||||
/** integration value, seperating well integrated from not well integrated */
|
||||
private double _thresholdIntegrationValue;
|
||||
|
||||
/**
|
||||
* Defines what percentage of the average reliability will be used as the
|
||||
* reliability threshold. For example, .5 means all peers with the reliability
|
||||
* greater than half of the average will be considered "reliable".
|
||||
*
|
||||
*/
|
||||
public static final String PROP_RELIABILITY_THRESHOLD_FACTOR = "profileOrganizer.reliabilityThresholdFactor";
|
||||
public static final double DEFAULT_RELIABILITY_THRESHOLD_FACTOR = .5d;
|
||||
|
||||
/** synchronized against this lock when updating the tier that peers are located in (and when fetching them from a peer) */
|
||||
private Object _reorganizeLock = new Object();
|
||||
|
||||
@@ -395,7 +404,7 @@ public class ProfileOrganizer {
|
||||
if (profile.getReliabilityValue() > 0)
|
||||
totalReliability += profile.getReliabilityValue();
|
||||
}
|
||||
_thresholdReliabilityValue = 0.5d * avg(totalReliability, numActive);
|
||||
_thresholdReliabilityValue = getReliabilityThresholdFactor() * avg(totalReliability, numActive);
|
||||
|
||||
// now derive the integration and speed thresholds based ONLY on the reliable
|
||||
// and active peers
|
||||
@@ -620,6 +629,46 @@ public class ProfileOrganizer {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* How much should we shrink (or grow) the average reliability to determine the
|
||||
* threshold - numbers greater than 1 increase the threshold, less than 1 decrease
|
||||
* it. This can be changed during runtime by updating the router.config
|
||||
*
|
||||
* @return factor to multiply the average reliability with to determine the threshold
|
||||
*/
|
||||
private double getReliabilityThresholdFactor() {
|
||||
if (_context.router() != null) {
|
||||
String val = _context.router().getConfigSetting(PROP_RELIABILITY_THRESHOLD_FACTOR);
|
||||
if (val != null) {
|
||||
try {
|
||||
double rv = Double.parseDouble(val);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("router config said " + PROP_RELIABILITY_THRESHOLD_FACTOR + '=' + val);
|
||||
return rv;
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Reliability threshold factor improperly set in the router config [" + val + "]", nfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
String val = _context.getProperty(PROP_RELIABILITY_THRESHOLD_FACTOR, ""+DEFAULT_RELIABILITY_THRESHOLD_FACTOR);
|
||||
if (val != null) {
|
||||
try {
|
||||
double rv = Double.parseDouble(val);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("router context said " + PROP_RELIABILITY_THRESHOLD_FACTOR+ '=' + val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Reliability threshold factor improperly set in the router environment [" + val + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("no config for " + PROP_RELIABILITY_THRESHOLD_FACTOR + ", using " + DEFAULT_RELIABILITY_THRESHOLD_FACTOR);
|
||||
return DEFAULT_RELIABILITY_THRESHOLD_FACTOR;
|
||||
}
|
||||
|
||||
private final static DecimalFormat _fmt = new DecimalFormat("###,##0.00", new DecimalFormatSymbols(Locale.UK));
|
||||
private final static String num(double num) { synchronized (_fmt) { return _fmt.format(num); } }
|
||||
}
|
||||
|
||||
@@ -123,7 +123,9 @@ class ProfilePersistenceHelper {
|
||||
profile.getDbResponseTime().store(out, "dbResponseTime");
|
||||
profile.getReceiveSize().store(out, "receiveSize");
|
||||
profile.getSendFailureSize().store(out, "sendFailureSize");
|
||||
profile.getSendSuccessSize().store(out, "tunnelCreateResponseTime");
|
||||
profile.getSendSuccessSize().store(out, "sendSuccessSize");
|
||||
profile.getTunnelCreateResponseTime().store(out, "tunnelCreateResponseTime");
|
||||
profile.getTunnelTestResponseTime().store(out, "tunnelTestResponseTime");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,10 +156,13 @@ class ProfilePersistenceHelper {
|
||||
rv.add(files[i]);
|
||||
return rv;
|
||||
}
|
||||
private PeerProfile readProfile(File file) {
|
||||
public PeerProfile readProfile(File file) {
|
||||
Hash peer = getHash(file.getName());
|
||||
try {
|
||||
if (peer == null) return null;
|
||||
if (peer == null) {
|
||||
_log.error("The file " + file.getName() + " is not a valid hash");
|
||||
return null;
|
||||
}
|
||||
PeerProfile profile = new PeerProfile(_context, peer);
|
||||
Properties props = new Properties();
|
||||
|
||||
@@ -181,7 +186,9 @@ class ProfilePersistenceHelper {
|
||||
profile.getDbResponseTime().load(props, "dbResponseTime", true);
|
||||
profile.getReceiveSize().load(props, "receiveSize", true);
|
||||
profile.getSendFailureSize().load(props, "sendFailureSize", true);
|
||||
profile.getSendSuccessSize().load(props, "tunnelCreateResponseTime", true);
|
||||
profile.getSendSuccessSize().load(props, "sendSuccessSize", true);
|
||||
profile.getTunnelCreateResponseTime().load(props, "tunnelCreateResponseTime", true);
|
||||
profile.getTunnelTestResponseTime().load(props, "tunnelTestResponseTime", true);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Loaded the profile for " + peer.toBase64() + " from " + file.getName());
|
||||
@@ -222,7 +229,7 @@ class ProfilePersistenceHelper {
|
||||
props.setProperty(key, val);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error loading properties from " + file.getName(), ioe);
|
||||
_log.warn("Error loading properties from " + file.getName(), ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
@@ -237,6 +244,7 @@ class ProfilePersistenceHelper {
|
||||
h.fromBase64(key);
|
||||
return h;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.warn("Invalid base64 [" + key + "]", dfe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -247,10 +255,16 @@ class ProfilePersistenceHelper {
|
||||
|
||||
private File getProfileDir() {
|
||||
if (_profileDir == null) {
|
||||
String dir = _context.router().getConfigSetting(PROP_PEER_PROFILE_DIR);
|
||||
if (dir == null) {
|
||||
_log.info("No peer profile dir specified [" + PROP_PEER_PROFILE_DIR + "], using [" + DEFAULT_PEER_PROFILE_DIR + "]");
|
||||
dir = DEFAULT_PEER_PROFILE_DIR;
|
||||
String dir = null;
|
||||
if (_context.router() == null) {
|
||||
dir = _context.getProperty(PROP_PEER_PROFILE_DIR, DEFAULT_PEER_PROFILE_DIR);
|
||||
} else {
|
||||
dir = _context.router().getConfigSetting(PROP_PEER_PROFILE_DIR);
|
||||
if (dir == null) {
|
||||
_log.info("No peer profile dir specified [" + PROP_PEER_PROFILE_DIR
|
||||
+ "], using [" + DEFAULT_PEER_PROFILE_DIR + "]");
|
||||
dir = DEFAULT_PEER_PROFILE_DIR;
|
||||
}
|
||||
}
|
||||
_profileDir = new File(dir);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,7 @@ public class ReliabilityCalculator extends Calculator {
|
||||
val += profile.getSendSuccessSize().getRate(60*60*1000).getLastEventCount();
|
||||
val += profile.getSendSuccessSize().getRate(60*60*1000).getCurrentEventCount();
|
||||
|
||||
val += profile.getTunnelCreateResponseTime().getRate(60*1000).getCurrentEventCount() * 10;
|
||||
val += profile.getTunnelCreateResponseTime().getRate(60*1000).getLastEventCount() * 5;
|
||||
val += profile.getTunnelCreateResponseTime().getRate(10*60*1000).getLastEventCount() * 5;
|
||||
val += profile.getTunnelCreateResponseTime().getRate(60*60*1000).getCurrentEventCount();
|
||||
val += profile.getTunnelCreateResponseTime().getRate(60*60*1000).getLastEventCount();
|
||||
|
||||
|
||||
@@ -8,60 +8,318 @@ import net.i2p.router.RouterContext;
|
||||
/**
|
||||
* Quantify how fast the peer is - how fast they respond to our requests, how fast
|
||||
* they pass messages on, etc. This should be affected both by their bandwidth/latency,
|
||||
* as well as their load.
|
||||
* as well as their load. The essence of the current algorithm is to determine
|
||||
* approximately how many 2KB messages the peer can pass round trip within a single
|
||||
* minute - not based just on itself though, but including the delays of other peers
|
||||
* in the tunnels. As such, more events make it more accurate.
|
||||
*
|
||||
*/
|
||||
public class SpeedCalculator extends Calculator {
|
||||
private Log _log;
|
||||
private RouterContext _context;
|
||||
|
||||
/**
|
||||
* minimum number of events to use a particular period's data. If this many
|
||||
* events haven't occurred in the period yet, the next largest period is tried.
|
||||
*/
|
||||
public static final String PROP_EVENT_THRESHOLD = "speedCalculator.eventThreshold";
|
||||
public static final int DEFAULT_EVENT_THRESHOLD = 50;
|
||||
/** should the calculator use instantaneous rates, or period averages? */
|
||||
public static final String PROP_USE_INSTANTANEOUS_RATES = "speedCalculator.useInstantaneousRates";
|
||||
public static final boolean DEFAULT_USE_INSTANTANEOUS_RATES = false;
|
||||
/** should the calculator use tunnel test time only, or include all data? */
|
||||
public static final String PROP_USE_TUNNEL_TEST_ONLY = "speedCalculator.useTunnelTestOnly";
|
||||
public static final boolean DEFAULT_USE_TUNNEL_TEST_ONLY = false;
|
||||
|
||||
public SpeedCalculator(RouterContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(SpeedCalculator.class);
|
||||
}
|
||||
|
||||
public double calc(PeerProfile profile) {
|
||||
double dbResponseTime = profile.getDbResponseTime().getRate(60*1000).getLifetimeAverageValue();
|
||||
double tunnelResponseTime = profile.getTunnelCreateResponseTime().getRate(60*1000).getLifetimeAverageValue();
|
||||
double roundTripRate = Math.max(dbResponseTime, tunnelResponseTime);
|
||||
long threshold = getEventThreshold();
|
||||
boolean tunnelTestOnly = getUseTunnelTestOnly();
|
||||
|
||||
// send and receive rates are the (period rate) * (saturation %)
|
||||
double sendRate = calcSendRate(profile);
|
||||
double receiveRate = calcReceiveRate(profile);
|
||||
long period = 10*60*1000;
|
||||
long events = getEventCount(profile, period, tunnelTestOnly);
|
||||
if (events < threshold) {
|
||||
period = 60*60*1000l;
|
||||
events = getEventCount(profile, period, tunnelTestOnly);
|
||||
if (events < threshold) {
|
||||
period = 24*60*60*1000;
|
||||
events = getEventCount(profile, period, tunnelTestOnly);
|
||||
if (events < threshold) {
|
||||
period = -1;
|
||||
events = getEventCount(profile, period, tunnelTestOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double measuredRoundTripTime = getMeasuredRoundTripTime(profile, period, tunnelTestOnly);
|
||||
double measuredRTPerMinute = 0;
|
||||
if (measuredRoundTripTime > 0)
|
||||
measuredRTPerMinute = (60000.0d / measuredRoundTripTime);
|
||||
|
||||
double val = 60000.0d - 0.1*roundTripRate + sendRate + receiveRate;
|
||||
// if we don't have any data, the rate is 0
|
||||
if ( (roundTripRate == 0.0d) && (sendRate == 0.0d) )
|
||||
val = 0.0;
|
||||
double estimatedRTPerMinute = 0;
|
||||
double estimatedRoundTripTime = 0;
|
||||
if (!tunnelTestOnly) {
|
||||
estimatedRoundTripTime = getEstimatedRoundTripTime(profile, period);
|
||||
if (estimatedRoundTripTime > 0)
|
||||
estimatedRTPerMinute = (60000.0d / estimatedRoundTripTime);
|
||||
}
|
||||
|
||||
double estimateFactor = getEstimateFactor(threshold, events);
|
||||
double rv = (1-estimateFactor)*measuredRTPerMinute + (estimateFactor)*estimatedRTPerMinute;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("roundTripRate: " + roundTripRate + "ms sendRate: " + sendRate + "bytes/second, receiveRate: " + receiveRate + "bytes/second, val: " + val + " for " + profile.getPeer().toBase64());
|
||||
|
||||
val += profile.getSpeedBonus();
|
||||
return val;
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("\n\nrv: " + rv + " events: " + events + " threshold: " + threshold + " period: " + period + " useTunnelTestOnly? " + tunnelTestOnly + "\n"
|
||||
+ "measuredRTT: " + measuredRoundTripTime + " measured events per minute: " + measuredRTPerMinute + "\n"
|
||||
+ "estimateRTT: " + estimatedRoundTripTime + " estimated events per minute: " + estimatedRTPerMinute + "\n"
|
||||
+ "estimateFactor: " + estimateFactor + "\n"
|
||||
+ "for peer: " + profile.getPeer().toBase64());
|
||||
}
|
||||
|
||||
rv += profile.getSpeedBonus();
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much do we want to prefer the measured values more than the estimated
|
||||
* values, as a fraction. The value 1 means ignore the measured values, while
|
||||
* the value 0 means ignore the estimate, and everything inbetween means, well
|
||||
* everything inbetween.
|
||||
*
|
||||
*/
|
||||
private double getEstimateFactor(long eventThreshold, long numEvents) {
|
||||
if (numEvents > eventThreshold)
|
||||
return 0.0d;
|
||||
else
|
||||
return numEvents / eventThreshold;
|
||||
}
|
||||
|
||||
private double calcSendRate(PeerProfile profile) { return calcRate(profile.getSendSuccessSize()); }
|
||||
private double calcReceiveRate(PeerProfile profile) { return calcRate(profile.getReceiveSize()); }
|
||||
|
||||
private double calcRate(RateStat stat) {
|
||||
double rate = 0.0d;
|
||||
Rate hourRate = stat.getRate(60*60*1000);
|
||||
rate = calcRate(hourRate);
|
||||
return rate;
|
||||
/**
|
||||
* How many measured events do we have for the given period? If the period is negative,
|
||||
* return the lifetime events.
|
||||
*
|
||||
*/
|
||||
private long getEventCount(PeerProfile profile, long period, boolean tunnelTestOnly) {
|
||||
if (period < 0) {
|
||||
Rate dbResponseRate = profile.getDbResponseTime().getRate(60*60*1000l);
|
||||
Rate tunnelResponseRate = profile.getTunnelCreateResponseTime().getRate(60*60*1000l);
|
||||
Rate tunnelTestRate = profile.getTunnelTestResponseTime().getRate(60*60*1000l);
|
||||
|
||||
long dbResponses = tunnelTestOnly ? 0 : dbResponseRate.getLifetimeEventCount();
|
||||
long tunnelResponses = tunnelTestOnly ? 0 : tunnelResponseRate.getLifetimeEventCount();
|
||||
long tunnelTests = tunnelTestRate.getLifetimeEventCount();
|
||||
|
||||
return dbResponses + tunnelResponses + tunnelTests;
|
||||
} else {
|
||||
Rate dbResponseRate = profile.getDbResponseTime().getRate(period);
|
||||
Rate tunnelResponseRate = profile.getTunnelCreateResponseTime().getRate(period);
|
||||
Rate tunnelTestRate = profile.getTunnelTestResponseTime().getRate(period);
|
||||
|
||||
long dbResponses = tunnelTestOnly ? 0 : dbResponseRate.getCurrentEventCount() + dbResponseRate.getLastEventCount();
|
||||
long tunnelResponses = tunnelTestOnly ? 0 : tunnelResponseRate.getCurrentEventCount() + tunnelResponseRate.getLastEventCount();
|
||||
long tunnelTests = tunnelTestRate.getCurrentEventCount() + tunnelTestRate.getLastEventCount();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TunnelTests for period " + period + ": " + tunnelTests +
|
||||
" last: " + tunnelTestRate.getLastEventCount() + " lifetime: " +
|
||||
tunnelTestRate.getLifetimeEventCount());
|
||||
|
||||
return dbResponses + tunnelResponses + tunnelTests;
|
||||
}
|
||||
}
|
||||
|
||||
private double calcRate(Rate rate) {
|
||||
long events = rate.getLastEventCount() + rate.getCurrentEventCount();
|
||||
/**
|
||||
* Retrieve the average measured round trip time within the period specified (including
|
||||
* db responses, tunnel create responses, and tunnel tests). If the period is negative,
|
||||
* it uses the lifetime stats. In addition, it weights each of those three measurements
|
||||
* equally according to their event count (e.g. 4 dbResponses @ 10 seconds and 1 tunnel test
|
||||
* at 5 seconds will leave the average at 9 seconds)
|
||||
*
|
||||
*/
|
||||
private double getMeasuredRoundTripTime(PeerProfile profile, long period, boolean tunnelTestOnly) {
|
||||
if (period < 0) {
|
||||
Rate dbResponseRate = profile.getDbResponseTime().getRate(60*60*1000l);
|
||||
Rate tunnelResponseRate = profile.getTunnelCreateResponseTime().getRate(60*60*1000l);
|
||||
Rate tunnelTestRate = profile.getTunnelTestResponseTime().getRate(60*60*1000l);
|
||||
|
||||
long dbResponses = tunnelTestOnly ? 0 : dbResponseRate.getLifetimeEventCount();
|
||||
long tunnelResponses = tunnelTestOnly ? 0 : tunnelResponseRate.getLifetimeEventCount();
|
||||
long tunnelTests = tunnelTestRate.getLifetimeEventCount();
|
||||
|
||||
double dbResponseTime = tunnelTestOnly ? 0 : dbResponseRate.getLifetimeAverageValue();
|
||||
double tunnelResponseTime = tunnelTestOnly ? 0 : tunnelResponseRate.getLifetimeAverageValue();
|
||||
double tunnelTestTime = tunnelTestRate.getLifetimeAverageValue();
|
||||
|
||||
long events = dbResponses + tunnelResponses + tunnelTests;
|
||||
if (events <= 0) return 0;
|
||||
return (dbResponses*dbResponseTime + tunnelResponses*tunnelResponseTime + tunnelTests*tunnelTestTime)
|
||||
/ events;
|
||||
} else {
|
||||
Rate dbResponseRate = profile.getDbResponseTime().getRate(period);
|
||||
Rate tunnelResponseRate = profile.getTunnelCreateResponseTime().getRate(period);
|
||||
Rate tunnelTestRate = profile.getTunnelTestResponseTime().getRate(period);
|
||||
|
||||
long dbResponses = tunnelTestOnly ? 0 : dbResponseRate.getCurrentEventCount() + dbResponseRate.getLastEventCount();
|
||||
long tunnelResponses = tunnelTestOnly ? 0 : tunnelResponseRate.getCurrentEventCount() + tunnelResponseRate.getLastEventCount();
|
||||
long tunnelTests = tunnelTestRate.getCurrentEventCount() + tunnelTestRate.getLastEventCount();
|
||||
|
||||
double dbResponseTime = tunnelTestOnly ? 0 : dbResponseRate.getAverageValue();
|
||||
double tunnelResponseTime = tunnelTestOnly ? 0 : tunnelResponseRate.getAverageValue();
|
||||
double tunnelTestTime = tunnelTestRate.getAverageValue();
|
||||
|
||||
long events = dbResponses + tunnelResponses + tunnelTests;
|
||||
if (events <= 0) return 0;
|
||||
return (dbResponses*dbResponseTime + tunnelResponses*tunnelResponseTime + tunnelTests*tunnelTestTime)
|
||||
/ events;
|
||||
}
|
||||
}
|
||||
|
||||
private double getEstimatedRoundTripTime(PeerProfile profile, long period) {
|
||||
double estSendTime = getEstimatedSendTime(profile, period);
|
||||
double estRecvTime = getEstimatedReceiveTime(profile, period);
|
||||
return estSendTime + estRecvTime;
|
||||
}
|
||||
|
||||
private double getEstimatedSendTime(PeerProfile profile, long period) {
|
||||
double bps = calcRate(profile.getSendSuccessSize(), period);
|
||||
if (bps <= 0)
|
||||
return 0.0d;
|
||||
else
|
||||
return 2048.0d / bps;
|
||||
}
|
||||
private double getEstimatedReceiveTime(PeerProfile profile, long period) {
|
||||
double bps = calcRate(profile.getReceiveSize(), period);
|
||||
if (bps <= 0)
|
||||
return 0.0d;
|
||||
else
|
||||
return 2048.0d / bps;
|
||||
}
|
||||
|
||||
private double calcRate(RateStat stat, long period) {
|
||||
Rate rate = stat.getRate(period);
|
||||
if (rate == null) return 0.0d;
|
||||
return calcRate(rate, period);
|
||||
}
|
||||
|
||||
private double calcRate(Rate rate, long period) {
|
||||
long events = rate.getCurrentEventCount();
|
||||
if (events >= 1) {
|
||||
double ms = rate.getLastTotalEventTime() + rate.getCurrentTotalEventTime();
|
||||
double bytes = rate.getLastTotalValue() + rate.getCurrentTotalValue();
|
||||
double ms = rate.getCurrentTotalEventTime();
|
||||
double bytes = rate.getCurrentTotalValue();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("calculating rate: ms=" + ((int)ms) + " bytes=" + ((int)bytes));
|
||||
if ( (bytes > 0) && (ms > 0) ) {
|
||||
return (bytes * 1000.0d) / ms;
|
||||
if (getUseInstantaneousRates()) {
|
||||
return (bytes * 1000.0d) / ms;
|
||||
} else {
|
||||
// period average
|
||||
return (bytes * 1000.0d) / period;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0.0d;
|
||||
}
|
||||
/**
|
||||
* What is the minimum number of measured events we want in a period before
|
||||
* trusting the values? This first checks the router's configuration, then
|
||||
* the context, and then finally falls back on a static default (100).
|
||||
*
|
||||
*/
|
||||
private long getEventThreshold() {
|
||||
if (_context.router() != null) {
|
||||
String threshold = _context.router().getConfigSetting(PROP_EVENT_THRESHOLD);
|
||||
if (threshold != null) {
|
||||
try {
|
||||
return Long.parseLong(threshold);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Event threshold for speed improperly set in the router config [" + threshold + "]", nfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
String threshold = _context.getProperty(PROP_EVENT_THRESHOLD, ""+DEFAULT_EVENT_THRESHOLD);
|
||||
if (threshold != null) {
|
||||
try {
|
||||
return Long.parseLong(threshold);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Event threshold for speed improperly set in the router environment [" + threshold + "]", nfe);
|
||||
}
|
||||
}
|
||||
return DEFAULT_EVENT_THRESHOLD;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Should we use instantaneous rates for the estimated speed, or the period rates?
|
||||
* This first checks the router's configuration, then the context, and then
|
||||
* finally falls back on a static default (true).
|
||||
*
|
||||
* @return true if we should use instantaneous rates, false if we should use period averages
|
||||
*/
|
||||
private boolean getUseInstantaneousRates() {
|
||||
if (_context.router() != null) {
|
||||
String val = _context.router().getConfigSetting(PROP_USE_INSTANTANEOUS_RATES);
|
||||
if (val != null) {
|
||||
try {
|
||||
return Boolean.getBoolean(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Instantaneous rate for speed improperly set in the router config [" + val + "]", nfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
String val = _context.getProperty(PROP_USE_INSTANTANEOUS_RATES, ""+DEFAULT_USE_INSTANTANEOUS_RATES);
|
||||
if (val != null) {
|
||||
try {
|
||||
return Boolean.getBoolean(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Instantaneous rate for speed improperly set in the router environment [" + val + "]", nfe);
|
||||
}
|
||||
}
|
||||
return DEFAULT_USE_INSTANTANEOUS_RATES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we only use the measured tunnel testing time, or should we include
|
||||
* measurements on the db responses and tunnel create responses. This first
|
||||
* checks the router's configuration, then the context, and then finally falls
|
||||
* back on a static default (true).
|
||||
*
|
||||
* @return true if we should use tunnel test time only, false if we should use all available
|
||||
*/
|
||||
private boolean getUseTunnelTestOnly() {
|
||||
if (_context.router() != null) {
|
||||
String val = _context.router().getConfigSetting(PROP_USE_TUNNEL_TEST_ONLY);
|
||||
if (val != null) {
|
||||
try {
|
||||
boolean rv = Boolean.getBoolean(val);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("router config said " + PROP_USE_TUNNEL_TEST_ONLY + '=' + val);
|
||||
return rv;
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Tunnel test only for speed improperly set in the router config [" + val + "]", nfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
String val = _context.getProperty(PROP_USE_TUNNEL_TEST_ONLY, ""+DEFAULT_USE_TUNNEL_TEST_ONLY);
|
||||
if (val != null) {
|
||||
try {
|
||||
boolean rv = Boolean.getBoolean(val);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("router context said " + PROP_USE_TUNNEL_TEST_ONLY + '=' + val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Tunnel test only for speed improperly set in the router environment [" + val + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("no config for " + PROP_USE_TUNNEL_TEST_ONLY + ", using " + DEFAULT_USE_TUNNEL_TEST_ONLY);
|
||||
return DEFAULT_USE_TUNNEL_TEST_ONLY;
|
||||
}
|
||||
}
|
||||
|
||||
168
router/java/src/net/i2p/router/startup/LoadClientAppsJob.java
Normal file
168
router/java/src/net/i2p/router/startup/LoadClientAppsJob.java
Normal file
@@ -0,0 +1,168 @@
|
||||
package net.i2p.router.startup;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Run any client applications specified in the router.config. If any clientApp
|
||||
* contains the config property ".onBoot=true" it'll be launched immediately, otherwise
|
||||
* it'll get queued up for starting 2 minutes later.
|
||||
*
|
||||
*/
|
||||
class LoadClientAppsJob extends JobImpl {
|
||||
private Log _log;
|
||||
/** wait 2 minutes before starting up client apps */
|
||||
private final static long STARTUP_DELAY = 2*60*1000;
|
||||
public LoadClientAppsJob(RouterContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(LoadClientAppsJob.class);
|
||||
}
|
||||
public void runJob() {
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String className = _context.router().getConfigSetting("clientApp."+i+".main");
|
||||
String clientName = _context.router().getConfigSetting("clientApp."+i+".name");
|
||||
String args = _context.router().getConfigSetting("clientApp."+i+".args");
|
||||
String onBoot = _context.router().getConfigSetting("clientApp." + i + ".onBoot");
|
||||
boolean onStartup = false;
|
||||
if (onBoot != null)
|
||||
onStartup = "true".equals(onBoot) || "yes".equals(onBoot);
|
||||
|
||||
if (className == null)
|
||||
break;
|
||||
|
||||
String argVal[] = parseArgs(args);
|
||||
if (onStartup) {
|
||||
// run this guy now
|
||||
runClient(className, clientName, argVal);
|
||||
} else {
|
||||
// wait 2 minutes
|
||||
_context.jobQueue().addJob(new DelayedRunClient(className, clientName, argVal));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private class DelayedRunClient extends JobImpl {
|
||||
private String _className;
|
||||
private String _clientName;
|
||||
private String _args[];
|
||||
public DelayedRunClient(String className, String clientName, String args[]) {
|
||||
super(LoadClientAppsJob.this._context);
|
||||
_className = className;
|
||||
_clientName = clientName;
|
||||
_args = args;
|
||||
getTiming().setStartAfter(LoadClientAppsJob.this._context.clock().now() + STARTUP_DELAY);
|
||||
}
|
||||
public String getName() { return "Delayed client job"; }
|
||||
public void runJob() {
|
||||
runClient(_className, _clientName, _args);
|
||||
}
|
||||
}
|
||||
|
||||
static String[] parseArgs(String args) {
|
||||
List argList = new ArrayList(4);
|
||||
if (args != null) {
|
||||
char data[] = args.toCharArray();
|
||||
StringBuffer buf = new StringBuffer(32);
|
||||
boolean isQuoted = false;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
switch (data[i]) {
|
||||
case '\'':
|
||||
case '\"':
|
||||
if (isQuoted) {
|
||||
String str = buf.toString().trim();
|
||||
if (str.length() > 0)
|
||||
argList.add(str);
|
||||
buf = new StringBuffer(32);
|
||||
} else {
|
||||
isQuoted = true;
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
case '\t':
|
||||
// whitespace - if we're in a quoted section, keep this as part of the quote,
|
||||
// otherwise use it as a delim
|
||||
if (isQuoted) {
|
||||
buf.append(data[i]);
|
||||
} else {
|
||||
String str = buf.toString().trim();
|
||||
if (str.length() > 0)
|
||||
argList.add(str);
|
||||
buf = new StringBuffer(32);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
buf.append(data[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (buf.length() > 0) {
|
||||
String str = buf.toString().trim();
|
||||
if (str.length() > 0)
|
||||
argList.add(str);
|
||||
}
|
||||
}
|
||||
String rv[] = new String[argList.size()];
|
||||
for (int i = 0; i < argList.size(); i++)
|
||||
rv[i] = (String)argList.get(i);
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void runClient(String className, String clientName, String args[]) {
|
||||
_log.info("Loading up the client application " + clientName + ": " + className + " " + args);
|
||||
I2PThread t = new I2PThread(new RunApp(className, clientName, args));
|
||||
t.setName(clientName);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private final class RunApp implements Runnable {
|
||||
private String _className;
|
||||
private String _appName;
|
||||
private String _args[];
|
||||
public RunApp(String className, String appName, String args[]) {
|
||||
_className = className;
|
||||
_appName = appName;
|
||||
if (args == null)
|
||||
_args = new String[0];
|
||||
else
|
||||
_args = args;
|
||||
}
|
||||
public void run() {
|
||||
try {
|
||||
Class cls = Class.forName(_className);
|
||||
Method method = cls.getMethod("main", new Class[] { String[].class });
|
||||
method.invoke(cls, new Object[] { _args });
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Error starting up the client class " + _className, t);
|
||||
}
|
||||
_log.info("Done running client application " + _appName);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() { return "Load up any client applications"; }
|
||||
|
||||
public static void main(String args[]) {
|
||||
test(null);
|
||||
test("hi how are you?");
|
||||
test("hi how are you? ");
|
||||
test(" hi how are you? ");
|
||||
test(" hi how are \"y\"ou? ");
|
||||
test("-nogui -e \"config localhost 17654\" -e \"httpclient 4544\"");
|
||||
test("-nogui -e 'config localhost 17654' -e 'httpclient 4544'");
|
||||
}
|
||||
private static void test(String args) {
|
||||
String parsed[] = parseArgs(args);
|
||||
System.out.print("Parsed [" + args + "] into " + parsed.length + " elements: ");
|
||||
for (int i = 0; i < parsed.length; i++)
|
||||
System.out.print("[" + parsed[i] + "] ");
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import net.i2p.router.ClientManagerFacade;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.admin.AdminManager;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
@@ -39,132 +38,6 @@ public class StartAcceptingClientsJob extends JobImpl {
|
||||
|
||||
_context.jobQueue().addJob(new ReadConfigJob(_context));
|
||||
_context.jobQueue().addJob(new RebuildRouterInfoJob(_context));
|
||||
new AdminManager(_context).startup();
|
||||
_context.jobQueue().allowParallelOperation();
|
||||
_context.jobQueue().addJob(new LoadClientAppsJob(_context));
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
test(null);
|
||||
test("hi how are you?");
|
||||
test("hi how are you? ");
|
||||
test(" hi how are you? ");
|
||||
test(" hi how are \"y\"ou? ");
|
||||
test("-nogui -e \"config localhost 17654\" -e \"httpclient 4544\"");
|
||||
test("-nogui -e 'config localhost 17654' -e 'httpclient 4544'");
|
||||
}
|
||||
private static void test(String args) {
|
||||
String parsed[] = LoadClientAppsJob.parseArgs(args);
|
||||
System.out.print("Parsed [" + args + "] into " + parsed.length + " elements: ");
|
||||
for (int i = 0; i < parsed.length; i++)
|
||||
System.out.print("[" + parsed[i] + "] ");
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
class LoadClientAppsJob extends JobImpl {
|
||||
private Log _log;
|
||||
/** wait 2 minutes before starting up client apps */
|
||||
private final static long STARTUP_DELAY = 2*60*1000;
|
||||
public LoadClientAppsJob(RouterContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(LoadClientAppsJob.class);
|
||||
getTiming().setStartAfter(STARTUP_DELAY + _context.clock().now());
|
||||
}
|
||||
public void runJob() {
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String className = _context.router().getConfigSetting("clientApp."+i+".main");
|
||||
String clientName = _context.router().getConfigSetting("clientApp."+i+".name");
|
||||
String args = _context.router().getConfigSetting("clientApp."+i+".args");
|
||||
if (className == null) break;
|
||||
|
||||
String argVal[] = parseArgs(args);
|
||||
_log.info("Loading up the client application " + clientName + ": " + className + " " + args);
|
||||
runClient(className, clientName, argVal);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
static String[] parseArgs(String args) {
|
||||
List argList = new ArrayList(4);
|
||||
if (args != null) {
|
||||
char data[] = args.toCharArray();
|
||||
StringBuffer buf = new StringBuffer(32);
|
||||
boolean isQuoted = false;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
switch (data[i]) {
|
||||
case '\'':
|
||||
case '\"':
|
||||
if (isQuoted) {
|
||||
String str = buf.toString().trim();
|
||||
if (str.length() > 0)
|
||||
argList.add(str);
|
||||
buf = new StringBuffer(32);
|
||||
} else {
|
||||
isQuoted = true;
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
case '\t':
|
||||
// whitespace - if we're in a quoted section, keep this as part of the quote,
|
||||
// otherwise use it as a delim
|
||||
if (isQuoted) {
|
||||
buf.append(data[i]);
|
||||
} else {
|
||||
String str = buf.toString().trim();
|
||||
if (str.length() > 0)
|
||||
argList.add(str);
|
||||
buf = new StringBuffer(32);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
buf.append(data[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (buf.length() > 0) {
|
||||
String str = buf.toString().trim();
|
||||
if (str.length() > 0)
|
||||
argList.add(str);
|
||||
}
|
||||
}
|
||||
String rv[] = new String[argList.size()];
|
||||
for (int i = 0; i < argList.size(); i++)
|
||||
rv[i] = (String)argList.get(i);
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void runClient(String className, String clientName, String args[]) {
|
||||
I2PThread t = new I2PThread(new RunApp(className, clientName, args));
|
||||
t.setName(clientName);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private final class RunApp implements Runnable {
|
||||
private String _className;
|
||||
private String _appName;
|
||||
private String _args[];
|
||||
public RunApp(String className, String appName, String args[]) {
|
||||
_className = className;
|
||||
_appName = appName;
|
||||
if (args == null)
|
||||
_args = new String[0];
|
||||
else
|
||||
_args = args;
|
||||
}
|
||||
public void run() {
|
||||
try {
|
||||
Class cls = Class.forName(_className);
|
||||
Method method = cls.getMethod("main", new Class[] { String[].class });
|
||||
method.invoke(cls, new Object[] { _args });
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Error starting up the client class " + _className, t);
|
||||
}
|
||||
_log.info("Done running client application " + _appName);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() { return "Load up any client applications"; }
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ package net.i2p.router.startup;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.StatisticsManager;
|
||||
import net.i2p.router.admin.AdminManager;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -35,8 +36,10 @@ public class StartupJob extends JobImpl {
|
||||
}
|
||||
|
||||
public String getName() { return "Startup Router"; }
|
||||
public void runJob() {
|
||||
public void runJob() {
|
||||
ReadConfigJob.doRead(_context);
|
||||
new AdminManager(_context).startup();
|
||||
_context.jobQueue().addJob(new LoadClientAppsJob(_context));
|
||||
_context.statPublisher().startup();
|
||||
_context.jobQueue().addJob(new LoadRouterInfoJob(_context));
|
||||
}
|
||||
|
||||
@@ -62,7 +62,9 @@ public class TrivialBandwidthLimiter extends BandwidthLimiter {
|
||||
|
||||
_context.jobQueue().addJob(new UpdateBWJob());
|
||||
updateLimits();
|
||||
_log.info("Initializing the limiter with maximum inbound [" + MAX_IN_POOL + "] outbound [" + MAX_OUT_POOL + "]");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Initializing the limiter with maximum inbound [" + MAX_IN_POOL
|
||||
+ "] outbound [" + MAX_OUT_POOL + "]");
|
||||
}
|
||||
|
||||
public long getTotalSendBytes() { return _totalSendBytes; }
|
||||
@@ -80,7 +82,9 @@ public class TrivialBandwidthLimiter extends BandwidthLimiter {
|
||||
// we don't have sufficient bytes.
|
||||
// the delay = (needed/numPerMinute)
|
||||
long val = MINUTE*(numBytes-_availableReceive)/_maxReceiveBytesPerMinute;
|
||||
_log.debug("DelayInbound: " + val + " for " + numBytes + " (avail=" + _availableReceive + ", max=" + _maxReceiveBytesPerMinute + ")");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("DelayInbound: " + val + " for " + numBytes + " (avail="
|
||||
+ _availableReceive + ", max=" + _maxReceiveBytesPerMinute + ")");
|
||||
return val;
|
||||
}
|
||||
}
|
||||
@@ -97,7 +101,9 @@ public class TrivialBandwidthLimiter extends BandwidthLimiter {
|
||||
// we don't have sufficient bytes.
|
||||
// the delay = (needed/numPerMinute)
|
||||
long val = MINUTE*(numBytes-_availableSend)/_maxSendBytesPerMinute;
|
||||
_log.debug("DelayOutbound: " + val + " for " + numBytes + " (avail=" + _availableSend + ", max=" + _maxSendBytesPerMinute + ")");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("DelayOutbound: " + val + " for " + numBytes + " (avail="
|
||||
+ _availableSend + ", max=" + _maxSendBytesPerMinute + ")");
|
||||
return val;
|
||||
}
|
||||
}
|
||||
@@ -129,7 +135,8 @@ public class TrivialBandwidthLimiter extends BandwidthLimiter {
|
||||
long oldReceive = _maxReceiveBytesPerMinute;
|
||||
long oldSend = _maxSendBytesPerMinute;
|
||||
|
||||
_log.debug("Read limits ["+inBwStr+" in, " + outBwStr + " out] vs current [" + oldReceive + " in, " + oldSend + " out]");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read limits ["+inBwStr+" in, " + outBwStr + " out] vs current [" + oldReceive + " in, " + oldSend + " out]");
|
||||
|
||||
if ( (inBwStr != null) && (inBwStr.trim().length() > 0) ) {
|
||||
try {
|
||||
@@ -179,16 +186,20 @@ public class TrivialBandwidthLimiter extends BandwidthLimiter {
|
||||
_availableSend += numMinutes * _maxSendBytesPerMinute;
|
||||
_lastResync = now;
|
||||
|
||||
_log.debug("Adding " + (numMinutes*_maxReceiveBytesPerMinute) + " bytes to availableReceive");
|
||||
_log.debug("Adding " + (numMinutes*_maxSendBytesPerMinute) + " bytes to availableSend");
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Adding " + (numMinutes*_maxReceiveBytesPerMinute) + " bytes to availableReceive");
|
||||
_log.debug("Adding " + (numMinutes*_maxSendBytesPerMinute) + " bytes to availableSend");
|
||||
}
|
||||
|
||||
// if we're huge, trim
|
||||
if (_availableReceive > MAX_IN_POOL) {
|
||||
_log.debug("Trimming available receive to " + MAX_IN_POOL);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Trimming available receive to " + MAX_IN_POOL);
|
||||
_availableReceive = MAX_IN_POOL;
|
||||
}
|
||||
if (_availableSend > MAX_OUT_POOL) {
|
||||
_log.debug("Trimming available send to " + MAX_OUT_POOL);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Trimming available send to " + MAX_OUT_POOL);
|
||||
_availableSend = MAX_OUT_POOL;
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ public class VMCommSystem extends CommSystemFacade {
|
||||
_ctx = us;
|
||||
_from = from;
|
||||
_msg = msg;
|
||||
// bah, uberspeed!
|
||||
// bah, ueberspeed!
|
||||
//getTiming().setStartAfter(us.clock().now() + 50);
|
||||
}
|
||||
public void runJob() {
|
||||
@@ -123,4 +123,4 @@ public class VMCommSystem extends CommSystemFacade {
|
||||
public void startup() {
|
||||
_commSystemFacades.put(_context.routerHash(), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,17 +538,19 @@ public class TCPTransport extends TransportImpl {
|
||||
buf.append("<li>");
|
||||
RouterIdentity ident = (RouterIdentity)iter.next();
|
||||
List curCons = (List)cons.get(ident);
|
||||
buf.append("Connections to ").append(ident.getHash().toBase64()).append(": ").append(curCons.size()).append("<br/><ul>\n");
|
||||
buf.append("Connection to ").append(ident.getHash().toBase64()).append(": ");
|
||||
String lifetime = null;
|
||||
for (int i = 0; i < curCons.size(); i++) {
|
||||
TCPConnection con = (TCPConnection)curCons.get(i);
|
||||
if (con.getLifetime() > 0) {
|
||||
established++;
|
||||
buf.append("<li>Connection ").append(con.getId()).append(": pending # messages to be sent: ").append(con.getPendingMessageCount()).append(" lifetime: ").append(DataHelper.formatDuration(con.getLifetime())).append("</li>\n");
|
||||
} else {
|
||||
buf.append("<li>Connection ").append(con.getId()).append(": [connection in progress]</li>\n");
|
||||
lifetime = DataHelper.formatDuration(con.getLifetime());
|
||||
}
|
||||
}
|
||||
buf.append("</ul>\n");
|
||||
if (lifetime != null)
|
||||
buf.append(lifetime);
|
||||
else
|
||||
buf.append("[pending]");
|
||||
buf.append("</li>\n");
|
||||
}
|
||||
buf.append("</ul>\n");
|
||||
|
||||
@@ -37,26 +37,33 @@ import net.i2p.router.RouterContext;
|
||||
|
||||
class TestTunnelJob extends JobImpl {
|
||||
private Log _log;
|
||||
private TunnelId _id;
|
||||
/** tunnel that we want to test */
|
||||
private TunnelId _primaryId;
|
||||
/** tunnel that is used to help test the primary id */
|
||||
private TunnelId _secondaryId;
|
||||
private TunnelPool _pool;
|
||||
private long _nonce;
|
||||
|
||||
public TestTunnelJob(RouterContext ctx, TunnelId id, TunnelPool pool) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(TestTunnelJob.class);
|
||||
_id = id;
|
||||
_primaryId = id;
|
||||
_pool = pool;
|
||||
_nonce = ctx.random().nextInt(Integer.MAX_VALUE);
|
||||
}
|
||||
public String getName() { return "Test Tunnel"; }
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Testing tunnel " + _id.getTunnelId());
|
||||
TunnelInfo info = _pool.getTunnelInfo(_id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Testing tunnel " + _primaryId.getTunnelId());
|
||||
TunnelInfo info = _pool.getTunnelInfo(_primaryId);
|
||||
if (info == null) {
|
||||
_log.error("wtf, why are we testing a tunnel that we do not know about? [" + _id.getTunnelId() + "]", getAddedBy());
|
||||
_log.error("wtf, why are we testing a tunnel that we do not know about? ["
|
||||
+ _primaryId.getTunnelId() + "]", getAddedBy());
|
||||
return;
|
||||
}
|
||||
|
||||
// mark it as something we're testing
|
||||
info.setLastTested(_context.clock().now());
|
||||
if (isOutbound(info)) {
|
||||
testOutbound(info);
|
||||
} else {
|
||||
@@ -75,7 +82,7 @@ class TestTunnelJob extends JobImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
private final static long TEST_TIMEOUT = 60*1000; // 60 seconds for a test to succeed
|
||||
private final static long TEST_TIMEOUT = 30*1000; // 30 seconds for a test to succeed
|
||||
private final static int TEST_PRIORITY = 100;
|
||||
|
||||
/**
|
||||
@@ -83,22 +90,51 @@ class TestTunnelJob extends JobImpl {
|
||||
* to ourselves and wait for it to arrive.
|
||||
*/
|
||||
private void testOutbound(TunnelInfo info) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Testing outbound tunnel " + info);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Testing outbound tunnel " + info);
|
||||
DeliveryStatusMessage msg = new DeliveryStatusMessage(_context);
|
||||
msg.setArrival(new Date(_context.clock().now()));
|
||||
msg.setMessageId(_nonce);
|
||||
Hash us = _context.routerHash();
|
||||
TunnelId inboundTunnelId = getReplyTunnel();
|
||||
if (inboundTunnelId == null) {
|
||||
_secondaryId = getReplyTunnel();
|
||||
if (_secondaryId == null) {
|
||||
_context.jobQueue().addJob(new TestFailedJob());
|
||||
return;
|
||||
}
|
||||
|
||||
TunnelInfo inboundInfo = _pool.getTunnelInfo(_secondaryId);
|
||||
inboundInfo.setLastTested(_context.clock().now());
|
||||
|
||||
TestFailedJob failureJob = new TestFailedJob();
|
||||
MessageSelector selector = new TestMessageSelector(msg.getMessageId(), info.getTunnelId().getTunnelId());
|
||||
SendTunnelMessageJob testJob = new SendTunnelMessageJob(_context, msg, info.getTunnelId(), us, inboundTunnelId, null, new TestSuccessfulJob(), failureJob, selector, TEST_TIMEOUT, TEST_PRIORITY);
|
||||
SendTunnelMessageJob testJob = new SendTunnelMessageJob(_context, msg, info.getTunnelId(), us, _secondaryId, null, new TestSuccessfulJob(), failureJob, selector, TEST_TIMEOUT, TEST_PRIORITY);
|
||||
_context.jobQueue().addJob(testJob);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the gateway and wait for it to arrive.
|
||||
*/
|
||||
private void testInbound(TunnelInfo info) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Testing inbound tunnel " + info);
|
||||
DeliveryStatusMessage msg = new DeliveryStatusMessage(_context);
|
||||
msg.setArrival(new Date(_context.clock().now()));
|
||||
msg.setMessageId(_nonce);
|
||||
|
||||
_secondaryId = getOutboundTunnel();
|
||||
if (_secondaryId == null) {
|
||||
_context.jobQueue().addJob(new TestFailedJob());
|
||||
return;
|
||||
}
|
||||
|
||||
TunnelInfo outboundInfo = _pool.getTunnelInfo(_secondaryId);
|
||||
outboundInfo.setLastTested(_context.clock().now());
|
||||
|
||||
TestFailedJob failureJob = new TestFailedJob();
|
||||
MessageSelector selector = new TestMessageSelector(msg.getMessageId(), info.getTunnelId().getTunnelId());
|
||||
SendTunnelMessageJob j = new SendTunnelMessageJob(_context, msg, _secondaryId, info.getThisHop(), info.getTunnelId(), null, new TestSuccessfulJob(), failureJob, selector, _context.clock().now()+TEST_TIMEOUT, TEST_PRIORITY);
|
||||
_context.jobQueue().addJob(j);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tunnel for replies to be sent down when testing outbound tunnels
|
||||
@@ -116,7 +152,7 @@ class TestTunnelJob extends JobImpl {
|
||||
|
||||
for (int i = 0; i < tunnelIds.size(); i++) {
|
||||
TunnelId id = (TunnelId)tunnelIds.get(i);
|
||||
if (id.equals(_id)) {
|
||||
if (id.equals(_primaryId)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Not testing a tunnel with itself [duh]");
|
||||
} else {
|
||||
@@ -124,39 +160,36 @@ class TestTunnelJob extends JobImpl {
|
||||
}
|
||||
}
|
||||
|
||||
_log.error("Unable to test tunnel " + _id + ", since there are NO OTHER INBOUND TUNNELS to receive the ack through");
|
||||
_log.error("Unable to test tunnel " + _primaryId + ", since there are NO OTHER INBOUND TUNNELS to receive the ack through");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the gateway and wait for it to arrive.
|
||||
* todo: send the message to the gateway via an outbound tunnel or garlic, NOT DIRECT.
|
||||
* Get the tunnel to send thte message out when testing inbound tunnels
|
||||
*
|
||||
*/
|
||||
private void testInbound(TunnelInfo info) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Testing inbound tunnel " + info);
|
||||
DeliveryStatusMessage msg = new DeliveryStatusMessage(_context);
|
||||
msg.setArrival(new Date(_context.clock().now()));
|
||||
msg.setMessageId(_nonce);
|
||||
TestFailedJob failureJob = new TestFailedJob();
|
||||
MessageSelector selector = new TestMessageSelector(msg.getMessageId(), info.getTunnelId().getTunnelId());
|
||||
TunnelMessage tmsg = new TunnelMessage(_context);
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
msg.writeBytes(baos);
|
||||
tmsg.setData(baos.toByteArray());
|
||||
tmsg.setTunnelId(info.getTunnelId());
|
||||
_context.jobQueue().addJob(new SendMessageDirectJob(_context, tmsg, info.getThisHop(), new TestSuccessfulJob(), failureJob, selector, _context.clock().now() + TEST_TIMEOUT, TEST_PRIORITY));
|
||||
|
||||
String bodyType = msg.getClass().getName();
|
||||
_context.messageHistory().wrap(bodyType, msg.getUniqueId(), TunnelMessage.class.getName(), tmsg.getUniqueId());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the tunnel message to send to the tunnel", ioe);
|
||||
_pool.tunnelFailed(_id);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error writing out the tunnel message to send to the tunnel", dfe);
|
||||
_pool.tunnelFailed(_id);
|
||||
private TunnelId getOutboundTunnel() {
|
||||
TunnelSelectionCriteria crit = new TunnelSelectionCriteria();
|
||||
crit.setMinimumTunnelsRequired(2);
|
||||
crit.setMaximumTunnelsRequired(2);
|
||||
// arbitrary priorities
|
||||
crit.setAnonymityPriority(50);
|
||||
crit.setLatencyPriority(50);
|
||||
crit.setReliabilityPriority(50);
|
||||
List tunnelIds = _context.tunnelManager().selectOutboundTunnelIds(crit);
|
||||
|
||||
for (int i = 0; i < tunnelIds.size(); i++) {
|
||||
TunnelId id = (TunnelId)tunnelIds.get(i);
|
||||
if (id.equals(_primaryId)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Not testing a tunnel with itself [duh]");
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
_log.error("Unable to test tunnel " + _primaryId + ", since there are NO OTHER OUTBOUND TUNNELS to send the ack through");
|
||||
return null;
|
||||
}
|
||||
|
||||
private class TestFailedJob extends JobImpl {
|
||||
@@ -167,8 +200,17 @@ class TestTunnelJob extends JobImpl {
|
||||
public String getName() { return "Tunnel Test Failed"; }
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Test of tunnel " + _id.getTunnelId() + " failed while waiting for nonce " + _nonce, getAddedBy());
|
||||
_pool.tunnelFailed(_id);
|
||||
_log.warn("Test of tunnel " + _primaryId.getTunnelId()
|
||||
+ " failed while waiting for nonce " + _nonce + ": "
|
||||
+ _pool.getTunnelInfo(_primaryId), getAddedBy());
|
||||
_pool.tunnelFailed(_primaryId);
|
||||
if (_secondaryId != null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Secondary test of tunnel " + _secondaryId.getTunnelId()
|
||||
+ " failed while waiting for nonce " + _nonce + ": "
|
||||
+ _pool.getTunnelInfo(_secondaryId), getAddedBy());
|
||||
_pool.tunnelFailed(_secondaryId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,10 +225,30 @@ class TestTunnelJob extends JobImpl {
|
||||
public void runJob() {
|
||||
long time = (_context.clock().now() - _msg.getArrival().getTime());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Test of tunnel " + _id+ " successfull after " + time + "ms waiting for " + _nonce);
|
||||
TunnelInfo info = _pool.getTunnelInfo(_id);
|
||||
if (info != null)
|
||||
_log.info("Test of tunnel " + _primaryId+ " successfull after "
|
||||
+ time + "ms waiting for " + _nonce);
|
||||
TunnelInfo info = _pool.getTunnelInfo(_primaryId);
|
||||
if (info != null) {
|
||||
TestTunnelJob.this._context.messageHistory().tunnelValid(info, time);
|
||||
updateProfiles(info, time);
|
||||
}
|
||||
|
||||
info = _pool.getTunnelInfo(_secondaryId);
|
||||
if (info != null) {
|
||||
TestTunnelJob.this._context.messageHistory().tunnelValid(info, time);
|
||||
updateProfiles(info, time);
|
||||
}
|
||||
_context.statManager().addRateData("tunnel.testSuccessTime", time, time);
|
||||
}
|
||||
|
||||
private void updateProfiles(TunnelInfo info, long time) {
|
||||
TunnelInfo cur = info;
|
||||
while (cur != null) {
|
||||
Hash peer = cur.getThisHop();
|
||||
if ( (peer != null) && (!_context.routerHash().equals(peer)) )
|
||||
_context.profileManager().tunnelTestSucceeded(peer, time);
|
||||
cur = cur.getNextHopInfo();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessage(I2NPMessage message) {
|
||||
@@ -205,15 +267,16 @@ class TestTunnelJob extends JobImpl {
|
||||
_found = false;
|
||||
_expiration = _context.clock().now() + TEST_TIMEOUT;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("the expiration while testing tunnel " + tunnelId + " waiting for nonce " + id + ": " + new Date(_expiration));
|
||||
_log.debug("the expiration while testing tunnel " + tunnelId
|
||||
+ " waiting for nonce " + id + ": " + new Date(_expiration));
|
||||
}
|
||||
public boolean continueMatching() {
|
||||
if (!_found) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Continue matching while looking for nonce for tunnel " + _tunnelId);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Don't continue matching for tunnel " + _tunnelId + " / " + _id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Don't continue matching for tunnel " + _tunnelId + " / " + _id);
|
||||
}
|
||||
return !_found;
|
||||
}
|
||||
@@ -229,12 +292,15 @@ class TestTunnelJob extends JobImpl {
|
||||
DeliveryStatusMessage msg = (DeliveryStatusMessage)message;
|
||||
if (msg.getMessageId() == _id) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Found successful test of tunnel " + _tunnelId + " after " + (_context.clock().now() - msg.getArrival().getTime()) + "ms waiting for " + _id);
|
||||
_log.debug("Found successful test of tunnel " + _tunnelId + " after "
|
||||
+ (_context.clock().now() - msg.getArrival().getTime())
|
||||
+ "ms waiting for " + _id);
|
||||
_found = true;
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Found a delivery status message, but it contains nonce " + msg.getMessageId() + " and not " + _id);
|
||||
_log.debug("Found a delivery status message, but it contains nonce "
|
||||
+ msg.getMessageId() + " and not " + _id);
|
||||
}
|
||||
} else {
|
||||
//_log.debug("Not a match while looking to test tunnel " + _tunnelId + " with nonce " + _id + " (" + message + ")");
|
||||
@@ -244,7 +310,8 @@ class TestTunnelJob extends JobImpl {
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(256);
|
||||
buf.append(super.toString());
|
||||
buf.append(": TestMessageSelector: tunnel ").append(_tunnelId).append(" looking for ").append(_id).append(" expiring on ");
|
||||
buf.append(": TestMessageSelector: tunnel ").append(_tunnelId);
|
||||
buf.append(" looking for ").append(_id).append(" expiring on ");
|
||||
buf.append(new Date(_expiration));
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@@ -32,16 +32,13 @@ class TunnelTestManager {
|
||||
private TunnelPool _pool;
|
||||
private boolean _stopTesting;
|
||||
|
||||
private final static long MINIMUM_RETEST_DELAY = 60*1000; // dont test tunnels more than once every 30 seconds
|
||||
|
||||
/** avg # tests per tunnel lifetime that we want */
|
||||
private final static int TESTS_PER_DURATION = 2;
|
||||
/** how many times we'll be able to try the tests (this should take into consideration user prefs, but fsck it for now) */
|
||||
private final static int CHANCES_PER_DURATION = 8;
|
||||
/** dont test any particular tunnel more than once a minute */
|
||||
private final static long MINIMUM_RETEST_DELAY = 60*1000;
|
||||
|
||||
public TunnelTestManager(RouterContext ctx, TunnelPool pool) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(TunnelTestManager.class);
|
||||
ctx.statManager().createRateStat("tunnel.testSuccessTime", "How long do successful tunnel tests take?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
|
||||
_pool = pool;
|
||||
_stopTesting = false;
|
||||
_context.jobQueue().addJob(new CoordinateTunnelTestingJob());
|
||||
@@ -61,18 +58,28 @@ class TunnelTestManager {
|
||||
// skip not ready tunnels
|
||||
} else if (info.getSettings().getExpiration() < now + MINIMUM_RETEST_DELAY) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tunnel " + id.getTunnelId() + " will be expiring within the current period (" + new Date(info.getSettings().getExpiration()) + "), so skip testing it");
|
||||
_log.debug("Tunnel " + id.getTunnelId()
|
||||
+ " will be expiring within the current period ("
|
||||
+ new Date(info.getSettings().getExpiration())
|
||||
+ "), so skip testing it");
|
||||
} else if (info.getSettings().getCreated() + MINIMUM_RETEST_DELAY < now) {
|
||||
double probability = TESTS_PER_DURATION / (allIds.size() * CHANCES_PER_DURATION);
|
||||
if (_context.random().nextInt(10) <= (probability*10d)) {
|
||||
toTest.add(id);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tunnel " + id.getTunnelId() + " could be tested, but probabilistically isn't going to be");
|
||||
// we're past the initial buffer period
|
||||
if (info.getLastTested() + MINIMUM_RETEST_DELAY < now) {
|
||||
// we haven't tested this tunnel in the minimum delay, so maybe we
|
||||
// should.
|
||||
if (_context.random().nextBoolean()) {
|
||||
toTest.add(id);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We could have tested tunnel " + id.getTunnelId()
|
||||
+ ", but randomly decided not to.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tunnel " + id.getTunnelId() + " was just created (" + new Date(info.getSettings().getCreated()) + "), wait until the next pass to test it");
|
||||
_log.debug("Tunnel " + id.getTunnelId() + " was just created ("
|
||||
+ new Date(info.getSettings().getCreated())
|
||||
+ "), wait until the next pass to test it");
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -112,11 +119,8 @@ class TunnelTestManager {
|
||||
}
|
||||
|
||||
private void reschedule() {
|
||||
long minNext = TunnelTestManager.this._context.clock().now() + MINIMUM_RETEST_DELAY;
|
||||
long nxt = minNext + TunnelTestManager.this._context.random().nextInt(60*1000); // test tunnels once every 30-90 seconds
|
||||
long nxt = TunnelTestManager.this._context.clock().now() + 30*1000;
|
||||
getTiming().setStartAfter(nxt);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Rescheduling tunnel tests for " + new Date(nxt));
|
||||
TunnelTestManager.this._context.jobQueue().addJob(CoordinateTunnelTestingJob.this);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user