forked from I2P_Developers/i2p.i2p
Compare commits
58 Commits
i2p_0_3_1
...
i2p_0_3_1_
Author | SHA1 | Date | |
---|---|---|---|
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 |
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
@ -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,32 +189,47 @@ 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");
|
||||
|
||||
@ -225,40 +240,40 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
} 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) {
|
||||
|
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);
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
}
|
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>
|
138
apps/time/java/src/net/i2p/time/NtpClient.java
Normal file
138
apps/time/java/src/net/i2p/time/NtpClient.java
Normal file
@ -0,0 +1,138 @@
|
||||
package net.i2p.time;
|
||||
|
||||
import java.io.IOException;
|
||||
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.receive(packet);
|
||||
|
||||
// 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] = (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 "";
|
||||
}
|
||||
}
|
103
apps/time/java/src/net/i2p/time/Timestamper.java
Normal file
103
apps/time/java/src/net/i2p/time/Timestamper.java
Normal file
@ -0,0 +1,103 @@
|
||||
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");
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an HTTP request to a given URL specifying the current time
|
||||
*/
|
||||
private void stampTime(long now) {
|
||||
try {
|
||||
URL url = new URL(_targetURL + "&now=" + getNow(now));
|
||||
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.4 $ $Date: 2004/04/30 18:04:13 $";
|
||||
public final static String VERSION = "0.3.1.1";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
; 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.3 2004/05/03 02:04:12 jrandom Exp $
|
||||
; changelog:
|
||||
; (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 +65,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 +81,7 @@ 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
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -5,5 +5,5 @@ 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
|
||||
javaw -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
|
||||
echo Router started up, please see http://localhost:7655/
|
||||
|
@ -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;
|
||||
@ -43,7 +45,7 @@ public class JobQueue {
|
||||
/** 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;
|
||||
@ -116,7 +118,7 @@ public class JobQueue {
|
||||
_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 +155,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);
|
||||
@ -436,13 +438,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 +603,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 +630,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 +676,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() {
|
||||
@ -51,6 +54,7 @@ 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();
|
||||
@ -75,6 +79,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();
|
||||
}
|
||||
}
|
||||
|
@ -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.3 $ $Date: 2004/04/30 18:04:13 $";
|
||||
public final static String VERSION = "0.3.1.1";
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Router version: " + VERSION);
|
||||
System.out.println("Router ID: " + RouterVersion.ID);
|
||||
|
@ -109,6 +109,8 @@ public class StatisticsManager implements Service {
|
||||
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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -207,25 +207,25 @@ 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[] { 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;
|
||||
}
|
||||
|
||||
/** update the stats and rates (this should be called once a minute) */
|
||||
|
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");
|
||||
|
Reference in New Issue
Block a user