forked from I2P_Developers/i2p.i2p
Compare commits
59 Commits
i2p_0_4_2_
...
i2p_0_4_2_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
484b528d4f | ||
|
|
758293dc02 | ||
|
|
6cb316b33e | ||
|
|
1d31831e7d | ||
|
|
ee32b07995 | ||
|
|
81f04ca692 | ||
|
|
1756997608 | ||
|
|
ec11ea4ca7 | ||
|
|
a1ebf85e1b | ||
|
|
4b2a734cda | ||
|
|
97ae8f78a0 | ||
|
|
834665c3ba | ||
|
|
d969dd2d8d | ||
|
|
3cb727561c | ||
|
|
cbc89376d3 | ||
|
|
66aa29e3d4 | ||
|
|
5c72aca5ee | ||
|
|
8824815d6d | ||
|
|
ad72e5cbdf | ||
|
|
b2f183fc17 | ||
|
|
9e16bc203a | ||
|
|
83c6eac017 | ||
|
|
d5b277a536 | ||
|
|
77ce6c33e3 | ||
|
|
60f8d349cf | ||
|
|
f539c3df70 | ||
|
|
fe1cf1758c | ||
|
|
8c71c26487 | ||
|
|
2ce39d1fd4 | ||
|
|
88a994b712 | ||
|
|
24c8cc1a0c | ||
|
|
caf684394c | ||
|
|
4b74510450 | ||
|
|
0ddcfc423e | ||
|
|
b4ac56e204 | ||
|
|
3b19ac3942 | ||
|
|
af52cad4ea | ||
|
|
d88396c1e2 | ||
|
|
4c5f7b9451 | ||
|
|
e601cedbb8 | ||
|
|
fa12dc867f | ||
|
|
acfb6c4578 | ||
|
|
e52d637092 | ||
|
|
2fba055696 | ||
|
|
88bb176f3b | ||
|
|
499eeb275b | ||
|
|
61a8d679bb | ||
|
|
2bbde91625 | ||
|
|
9ce098ee06 | ||
|
|
927ae57d24 | ||
|
|
d65c2d3539 | ||
|
|
2d9d8f32dc | ||
|
|
1a30cd5f4a | ||
|
|
f54687f398 | ||
|
|
9f4b4c5de1 | ||
|
|
33bfa94229 | ||
|
|
61e5f190a6 | ||
|
|
a4946272d0 | ||
|
|
8abd99d134 |
@@ -229,6 +229,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
runClientOptions(args, l);
|
||||
} else if ("server".equals(cmdname)) {
|
||||
runServer(args, l);
|
||||
} else if ("httpserver".equals(cmdname)) {
|
||||
runHttpServer(args, l);
|
||||
} else if ("textserver".equals(cmdname)) {
|
||||
runTextServer(args, l);
|
||||
} else if ("client".equals(cmdname)) {
|
||||
@@ -281,6 +283,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("owndest yes|no");
|
||||
l.log("ping <args>");
|
||||
l.log("server <host> <port> <privkeyfile>");
|
||||
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
|
||||
l.log("textserver <host> <port> <privkey>");
|
||||
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
|
||||
l.log("gentextkeys");
|
||||
@@ -370,6 +373,65 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the HTTP server pointing at the host and port specified using the private i2p
|
||||
* destination loaded from the specified file, replacing the HTTP headers
|
||||
* so that the Host: specified is the one spoofed. <p />
|
||||
*
|
||||
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
|
||||
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
|
||||
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
|
||||
*
|
||||
* @param args {hostname, portNumber, spoofedHost, privKeyFilename}
|
||||
* @param l logger to receive events and output
|
||||
*/
|
||||
public void runHttpServer(String args[], Logging l) {
|
||||
if (args.length == 4) {
|
||||
InetAddress serverHost = null;
|
||||
int portNum = -1;
|
||||
File privKeyFile = null;
|
||||
try {
|
||||
serverHost = InetAddress.getByName(args[0]);
|
||||
} catch (UnknownHostException uhe) {
|
||||
l.log("unknown host");
|
||||
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
portNum = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
String spoofedHost = args[2];
|
||||
|
||||
privKeyFile = new File(args[3]);
|
||||
if (!privKeyFile.canRead()) {
|
||||
l.log("private key file does not exist");
|
||||
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
return;
|
||||
}
|
||||
I2PTunnelHTTPServer serv = new I2PTunnelHTTPServer(serverHost, portNum, privKeyFile, args[3], spoofedHost, l, (EventDispatcher) this, this);
|
||||
serv.setReadTimeout(readTimeout);
|
||||
serv.startRunning();
|
||||
addtask(serv);
|
||||
notifyEvent("serverTaskId", new Integer(serv.getId()));
|
||||
return;
|
||||
} else {
|
||||
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
|
||||
l.log(" creates an HTTP server that sends all incoming data\n"
|
||||
+ " of its destination to host:port., filtering the HTTP\n"
|
||||
+ " headers so it looks like the request is to the spoofed host.");
|
||||
notifyEvent("serverTaskId", new Integer(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the server pointing at the host and port specified using the private i2p
|
||||
* destination loaded from the given base64 stream. <p />
|
||||
|
||||
@@ -407,7 +407,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
|
||||
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions(opts));
|
||||
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
|
||||
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data, mySockets);
|
||||
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
} catch (SocketException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
@@ -437,6 +438,30 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
private class OnTimeout implements Runnable {
|
||||
private Socket _socket;
|
||||
private OutputStream _out;
|
||||
private String _target;
|
||||
private boolean _usingProxy;
|
||||
private String _wwwProxy;
|
||||
private long _requestId;
|
||||
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
|
||||
_socket = s;
|
||||
_out = out;
|
||||
_target = target;
|
||||
_usingProxy = usingProxy;
|
||||
_wwwProxy = wwwProxy;
|
||||
_requestId = id;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Timeout occured requesting " + _target);
|
||||
handleHTTPClientException(new RuntimeException("Timeout"), _out,
|
||||
_target, _usingProxy, _wwwProxy, _requestId);
|
||||
closeSocket(_socket);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy) throws IOException {
|
||||
if (out != null) {
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple extension to the I2PTunnelServer that filters the HTTP
|
||||
* headers sent from the client to the server, replacing the Host
|
||||
* header with whatever this instance has been configured with.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
private final static Log _log = new Log(I2PTunnelHTTPServer.class);
|
||||
/** what Host: should we seem to be to the webserver? */
|
||||
private String _spoofHost;
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, privkeyname, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
I2PServerSocket i2pss = sockMgr.getServerSocket();
|
||||
while (true) {
|
||||
I2PSocket i2ps = i2pss.accept();
|
||||
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
|
||||
I2PThread t = new I2PThread(new Handler(i2ps));
|
||||
t.start();
|
||||
}
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Async handler to keep .accept() from blocking too long.
|
||||
* todo: replace with a thread pool so we dont get overrun by threads if/when
|
||||
* receiving a lot of connection requests concurrently.
|
||||
*
|
||||
*/
|
||||
private class Handler implements Runnable {
|
||||
private I2PSocket _handleSocket;
|
||||
public Handler(I2PSocket socket) {
|
||||
_handleSocket = socket;
|
||||
}
|
||||
public void run() {
|
||||
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
//threads.
|
||||
try {
|
||||
_handleSocket.setReadTimeout(readTimeout);
|
||||
String modifiedHeader = getModifiedHeader();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Modified header: [" + modifiedHeader + "]");
|
||||
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
afterSocket = I2PAppContext.getGlobalContext().clock().now();
|
||||
new I2PTunnelRunner(s, _handleSocket, slock, null, modifiedHeader.getBytes(), null);
|
||||
} catch (SocketException ex) {
|
||||
try {
|
||||
_handleSocket.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error while closing the received i2p con", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while waiting for I2PConnections", ex);
|
||||
}
|
||||
|
||||
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
|
||||
long timeToHandle = afterHandle - afterAccept;
|
||||
if (timeToHandle > 1000)
|
||||
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: "
|
||||
+ (afterSocket-afterAccept) + "]");
|
||||
}
|
||||
private String getModifiedHeader() throws IOException {
|
||||
InputStream in = _handleSocket.getInputStream();
|
||||
|
||||
StringBuffer command = new StringBuffer(128);
|
||||
Properties headers = readHeaders(in, command);
|
||||
headers.setProperty("Host", _spoofHost);
|
||||
headers.setProperty("Connection", "close");
|
||||
return formatHeaders(headers, command);
|
||||
}
|
||||
}
|
||||
|
||||
private String formatHeaders(Properties headers, StringBuffer command) {
|
||||
StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64);
|
||||
buf.append(command.toString()).append('\n');
|
||||
for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String val = headers.getProperty(name);
|
||||
buf.append(name).append(": ").append(val).append('\n');
|
||||
}
|
||||
buf.append('\n');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private Properties readHeaders(InputStream in, StringBuffer command) throws IOException {
|
||||
Properties headers = new Properties();
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
|
||||
boolean ok = DataHelper.readLine(in, command);
|
||||
if (!ok) throw new IOException("EOF reached while reading the HTTP command [" + command.toString() + "]");
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the http command [" + command.toString() + "]");
|
||||
|
||||
while (true) {
|
||||
buf.setLength(0);
|
||||
ok = DataHelper.readLine(in, buf);
|
||||
if (!ok) throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
||||
if ( (buf.length() <= 1) && ( (buf.charAt(0) == '\n') || (buf.charAt(0) == '\r') ) ) {
|
||||
// end of headers reached
|
||||
return headers;
|
||||
} else {
|
||||
int split = buf.indexOf(": ");
|
||||
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
|
||||
String name = buf.substring(0, split);
|
||||
String value = buf.substring(split+2); // ": "
|
||||
headers.setProperty(name, value);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the header [" + name + "] = [" + value + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,21 +40,37 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
Object slock, finishLock = new Object();
|
||||
boolean finished = false;
|
||||
HashMap ostreams, sockets;
|
||||
byte[] initialData;
|
||||
byte[] initialI2PData;
|
||||
byte[] initialSocketData;
|
||||
/** when the last data was sent/received (or -1 if never) */
|
||||
private long lastActivityOn;
|
||||
/** when the runner started up */
|
||||
private long startedOn;
|
||||
private List sockList;
|
||||
/** if we die before receiving any data, run this job */
|
||||
private Runnable onTimeout;
|
||||
private long totalSent;
|
||||
private long totalReceived;
|
||||
|
||||
private volatile long __forwarderId;
|
||||
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData, List sockList) {
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList) {
|
||||
this(s, i2ps, slock, initialI2PData, null, sockList, null);
|
||||
}
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList) {
|
||||
this(s, i2ps, slock, initialI2PData, initialSocketData, sockList, null);
|
||||
}
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
|
||||
this(s, i2ps, slock, initialI2PData, null, sockList, onTimeout);
|
||||
}
|
||||
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList, Runnable onTimeout) {
|
||||
this.sockList = sockList;
|
||||
this.s = s;
|
||||
this.i2ps = i2ps;
|
||||
this.slock = slock;
|
||||
this.initialData = initialData;
|
||||
this.initialI2PData = initialI2PData;
|
||||
this.initialSocketData = initialSocketData;
|
||||
this.onTimeout = onTimeout;
|
||||
lastActivityOn = -1;
|
||||
startedOn = Clock.getInstance().now();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@@ -97,24 +113,27 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
}
|
||||
|
||||
public void run() {
|
||||
boolean closedCleanly = false;
|
||||
try {
|
||||
InputStream in = s.getInputStream();
|
||||
OutputStream out = s.getOutputStream(); // = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
|
||||
i2ps.setSocketErrorListener(this);
|
||||
InputStream i2pin = i2ps.getInputStream();
|
||||
OutputStream i2pout = i2ps.getOutputStream(); //new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
|
||||
if (initialData != null) {
|
||||
if (initialI2PData != null) {
|
||||
synchronized (slock) {
|
||||
i2pout.write(initialData);
|
||||
i2pout.write(initialI2PData);
|
||||
//i2pout.flush();
|
||||
}
|
||||
}
|
||||
if (initialSocketData != null) {
|
||||
out.write(initialSocketData);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Initial data " + (initialData != null ? initialData.length : 0)
|
||||
+ " written, starting forwarders");
|
||||
Thread t1 = new StreamForwarder(in, i2pout, "toI2P");
|
||||
Thread t2 = new StreamForwarder(i2pin, out, "fromI2P");
|
||||
_log.debug("Initial data " + (initialI2PData != null ? initialI2PData.length : 0)
|
||||
+ " written to I2P, " + (initialSocketData != null ? initialSocketData.length : 0)
|
||||
+ " written to the socket, starting forwarders");
|
||||
Thread t1 = new StreamForwarder(in, i2pout, true);
|
||||
Thread t2 = new StreamForwarder(i2pin, out, false);
|
||||
synchronized (finishLock) {
|
||||
while (!finished) {
|
||||
finishLock.wait();
|
||||
@@ -122,12 +141,21 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("At least one forwarder completed, closing and joining");
|
||||
|
||||
// this task is useful for the httpclient
|
||||
if (onTimeout != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("runner has a timeout job, totalReceived = " + totalReceived
|
||||
+ " totalSent = " + totalSent + " job = " + onTimeout);
|
||||
if ( (totalSent <= 0) && (totalReceived <= 0) )
|
||||
onTimeout.run();
|
||||
}
|
||||
|
||||
// now one connection is dead - kill the other as well.
|
||||
s.close();
|
||||
i2ps.close();
|
||||
t1.join(30*1000);
|
||||
t2.join(30*1000);
|
||||
closedCleanly = true;
|
||||
} catch (InterruptedException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Interrupted", ex);
|
||||
@@ -140,21 +168,20 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
} finally {
|
||||
removeRef();
|
||||
try {
|
||||
if ( (s != null) && (!closedCleanly) )
|
||||
if (s != null)
|
||||
s.close();
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not close java socket", ex);
|
||||
}
|
||||
try {
|
||||
if (i2ps != null) {
|
||||
if (!closedCleanly)
|
||||
i2ps.close();
|
||||
i2ps.setSocketErrorListener(null);
|
||||
if (i2ps != null) {
|
||||
try {
|
||||
i2ps.close();
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not close I2PSocket", ex);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not close I2PSocket", ex);
|
||||
i2ps.setSocketErrorListener(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,12 +208,14 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
String direction;
|
||||
private boolean _toI2P;
|
||||
private ByteCache _cache;
|
||||
|
||||
private StreamForwarder(InputStream in, OutputStream out, String dir) {
|
||||
private StreamForwarder(InputStream in, OutputStream out, boolean toI2P) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
direction = dir;
|
||||
_toI2P = toI2P;
|
||||
direction = (toI2P ? "toI2P" : "fromI2P");
|
||||
_cache = ByteCache.getInstance(256, NETWORK_BUFFER_SIZE);
|
||||
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
|
||||
start();
|
||||
@@ -207,6 +236,10 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
if (_toI2P)
|
||||
totalSent += len;
|
||||
else
|
||||
totalReceived += len;
|
||||
|
||||
if (len > 0) updateActivity();
|
||||
|
||||
@@ -242,8 +275,8 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
+ ex.getMessage() + "\")");
|
||||
} catch (IOException ex) {
|
||||
if (!finished) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(direction + ": Error forwarding", ex);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(direction + ": Error forwarding", ex);
|
||||
}
|
||||
//else
|
||||
// _log.warn("You may ignore this", ex);
|
||||
@@ -259,10 +292,10 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
_log.warn(direction + ": Error closing input stream", ex);
|
||||
}
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ex) {
|
||||
out.flush();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(direction + ": Error closing output stream", ex);
|
||||
_log.warn(direction + ": Error flushing to close", ioe);
|
||||
}
|
||||
synchronized (finishLock) {
|
||||
finished = true;
|
||||
|
||||
@@ -31,19 +31,20 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
private final static Log _log = new Log(I2PTunnelServer.class);
|
||||
|
||||
private I2PSocketManager sockMgr;
|
||||
private I2PServerSocket i2pss;
|
||||
protected I2PSocketManager sockMgr;
|
||||
protected I2PServerSocket i2pss;
|
||||
|
||||
private Object lock = new Object(), slock = new Object();
|
||||
private Object lock = new Object();
|
||||
protected Object slock = new Object();
|
||||
|
||||
private InetAddress remoteHost;
|
||||
private int remotePort;
|
||||
protected InetAddress remoteHost;
|
||||
protected int remotePort;
|
||||
|
||||
private Logging l;
|
||||
|
||||
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;
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privData, notifyThis, tunnel);
|
||||
|
||||
@@ -57,7 +57,7 @@ public class TunnelController implements Logging {
|
||||
setConfig(config, prefix);
|
||||
_messages = new ArrayList(4);
|
||||
_running = false;
|
||||
if (createKey && ("server".equals(getType())) )
|
||||
if (createKey && ("server".equals(getType()) || "httpserver".equals(getType())) )
|
||||
createPrivateKey();
|
||||
_starting = getStartOnLoad();
|
||||
}
|
||||
@@ -132,6 +132,8 @@ public class TunnelController implements Logging {
|
||||
startClient();
|
||||
} else if ("server".equals(type)) {
|
||||
startServer();
|
||||
} else if ("httpserver".equals(type)) {
|
||||
startHttpServer();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Cannot start tunnel - unknown type [" + type + "]");
|
||||
@@ -206,6 +208,18 @@ public class TunnelController implements Logging {
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void startHttpServer() {
|
||||
setI2CPOptions();
|
||||
setSessionOptions();
|
||||
String targetHost = getTargetHost();
|
||||
String targetPort = getTargetPort();
|
||||
String spoofedHost = getSpoofedHost();
|
||||
String privKeyFile = getPrivKeyFile();
|
||||
_tunnel.runHttpServer(new String[] { targetHost, targetPort, spoofedHost, privKeyFile }, this);
|
||||
acquire();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private void setListenOn() {
|
||||
String listenOn = getListenOnInterface();
|
||||
if ( (listenOn != null) && (listenOn.length() > 0) ) {
|
||||
@@ -233,6 +247,9 @@ public class TunnelController implements Logging {
|
||||
String host = getI2CPHost();
|
||||
if ( (host != null) && (host.length() > 0) )
|
||||
_tunnel.host = host;
|
||||
// woohah, special casing for people with ipv6/etc
|
||||
if ("localhost".equals(_tunnel.host))
|
||||
_tunnel.host = "127.0.0.1";
|
||||
String port = getI2CPPort();
|
||||
if ( (port != null) && (port.length() > 0) )
|
||||
_tunnel.port = port;
|
||||
@@ -294,6 +311,7 @@ public class TunnelController implements Logging {
|
||||
public String getListenOnInterface() { return _config.getProperty("interface"); }
|
||||
public String getTargetHost() { return _config.getProperty("targetHost"); }
|
||||
public String getTargetPort() { return _config.getProperty("targetPort"); }
|
||||
public String getSpoofedHost() { return _config.getProperty("spoofedHost"); }
|
||||
public String getPrivKeyFile() { return _config.getProperty("privKeyFile"); }
|
||||
public String getListenPort() { return _config.getProperty("listenPort"); }
|
||||
public String getTargetDestination() { return _config.getProperty("targetDestination"); }
|
||||
@@ -311,6 +329,8 @@ public class TunnelController implements Logging {
|
||||
getClientSummary(buf);
|
||||
else if ("server".equals(type))
|
||||
getServerSummary(buf);
|
||||
else if ("httpserver".equals(type))
|
||||
getHttpServerSummary(buf);
|
||||
else
|
||||
buf.append("Unknown type ").append(type);
|
||||
}
|
||||
@@ -364,6 +384,18 @@ public class TunnelController implements Logging {
|
||||
getOptionSummary(buf);
|
||||
}
|
||||
|
||||
private void getHttpServerSummary(StringBuffer buf) {
|
||||
String description = getDescription();
|
||||
if ( (description != null) && (description.trim().length() > 0) )
|
||||
buf.append("<i>").append(description).append("</i><br />\n");
|
||||
buf.append("Server tunnel pointing at port ").append(getTargetPort());
|
||||
buf.append(" on ").append(getTargetHost());
|
||||
buf.append(" for the site ").append(getSpoofedHost());
|
||||
buf.append("<br />\n");
|
||||
buf.append("Private destination loaded from ").append(getPrivKeyFile()).append("<br />\n");
|
||||
getOptionSummary(buf);
|
||||
}
|
||||
|
||||
private void getOptionSummary(StringBuffer buf) {
|
||||
String opts = getClientOptions();
|
||||
if ( (opts != null) && (opts.length() > 0) )
|
||||
@@ -375,7 +407,7 @@ public class TunnelController implements Logging {
|
||||
Destination dest = session.getMyDestination();
|
||||
if (dest != null) {
|
||||
buf.append("Destination hash: ").append(dest.calculateHash().toBase64()).append("<br />\n");
|
||||
if ("server".equals(getType())) {
|
||||
if ( ("server".equals(getType())) || ("httpserver".equals(getType())) ) {
|
||||
buf.append("Full destination: ");
|
||||
buf.append("<input type=\"text\" size=\"10\" onclick=\"this.select();\" ");
|
||||
buf.append("value=\"").append(dest.toBase64()).append("\" />\n");
|
||||
|
||||
@@ -18,6 +18,7 @@ class WebEditPageFormGenerator {
|
||||
"<option value=\"httpclient\">HTTP proxy</option>" +
|
||||
"<option value=\"client\">Client tunnel</option>" +
|
||||
"<option value=\"server\">Server tunnel</option>" +
|
||||
"<option value=\"httpserver\">HTTP server tunnel</option>" +
|
||||
"</select> <input type=\"submit\" value=\"GO\" />" +
|
||||
"</form>\n";
|
||||
|
||||
@@ -42,6 +43,8 @@ class WebEditPageFormGenerator {
|
||||
return getEditClientForm(controller, id);
|
||||
else if ("server".equals(type))
|
||||
return getEditServerForm(controller, id);
|
||||
else if ("httpserver".equals(type))
|
||||
return getEditHttpServerForm(controller, id);
|
||||
else
|
||||
return "WTF, unknown type [" + type + "]";
|
||||
}
|
||||
@@ -103,7 +106,7 @@ class WebEditPageFormGenerator {
|
||||
if ( (controller != null) && (controller.getTargetHost() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"localhost\" ");
|
||||
buf.append("value=\"127.0.0.1\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
|
||||
@@ -129,6 +132,48 @@ class WebEditPageFormGenerator {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String getEditHttpServerForm(TunnelController controller, String id) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
addGeneral(buf, controller, id);
|
||||
buf.append("<b>Type:</b> <i>HTTP server tunnel</i><input type=\"hidden\" name=\"type\" value=\"httpserver\" /><br />\n");
|
||||
|
||||
buf.append("<b>Target host:</b> <input type=\"text\" size=\"40\" name=\"targetHost\" ");
|
||||
if ( (controller != null) && (controller.getTargetHost() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"127.0.0.1\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
|
||||
if ( (controller != null) && (controller.getTargetPort() != null) )
|
||||
buf.append("value=\"").append(controller.getTargetPort()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"80\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Website hostname:</b> <input type=\"text\" size=\"16\" name=\"spoofedHost\" ");
|
||||
if ( (controller != null) && (controller.getSpoofedHost() != null) )
|
||||
buf.append("value=\"").append(controller.getSpoofedHost()).append("\" ");
|
||||
else
|
||||
buf.append("value=\"mysite.i2p\" ");
|
||||
buf.append(" /><br />\n");
|
||||
|
||||
buf.append("<b>Private key file:</b> <input type=\"text\" name=\"privKeyFile\" value=\"");
|
||||
if ( (controller != null) && (controller.getPrivKeyFile() != null) ) {
|
||||
buf.append(controller.getPrivKeyFile()).append("\" /><br />");
|
||||
} else {
|
||||
buf.append("myServer.privKey\" /><br />");
|
||||
buf.append("<input type=\"hidden\" name=\"privKeyGenerate\" value=\"true\" />");
|
||||
}
|
||||
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start off the form and add some common fields (name, num, description)
|
||||
*
|
||||
@@ -285,7 +330,7 @@ class WebEditPageFormGenerator {
|
||||
if ( (controller != null) && (controller.getI2CPHost() != null) )
|
||||
buf.append(controller.getI2CPHost());
|
||||
else
|
||||
buf.append("localhost");
|
||||
buf.append("127.0.0.1");
|
||||
buf.append("\" /><br />\n");
|
||||
buf.append("<b>I2CP port:</b> ");
|
||||
buf.append("<input type=\"text\" name=\"clientPort\" size=\"20\" value=\"");
|
||||
|
||||
@@ -38,6 +38,7 @@ public class WebEditPageHelper {
|
||||
private String _targetDestination;
|
||||
private String _targetHost;
|
||||
private String _targetPort;
|
||||
private String _spoofedHost;
|
||||
private String _privKeyFile;
|
||||
private boolean _startOnLoad;
|
||||
private boolean _privKeyGenerate;
|
||||
@@ -139,6 +140,10 @@ public class WebEditPageHelper {
|
||||
public void setTargetPort(String port) {
|
||||
_targetPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** What host does this http server tunnel spoof */
|
||||
public void setSpoofedHost(String host) {
|
||||
_spoofedHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** What filename is this server tunnel's private keys stored in */
|
||||
public void setPrivKeyFile(String file) {
|
||||
_privKeyFile = (file != null ? file.trim() : null);
|
||||
@@ -320,6 +325,15 @@ public class WebEditPageHelper {
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
} else if ("httpserver".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
if (_targetPort != null)
|
||||
config.setProperty("targetPort", _targetPort);
|
||||
if (_privKeyFile != null)
|
||||
config.setProperty("privKeyFile", _privKeyFile);
|
||||
if (_spoofedHost != null)
|
||||
config.setProperty("spoofedHost", _spoofedHost);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<option value="httpclient">HTTP proxy</option>
|
||||
<option value="client">Client tunnel</option>
|
||||
<option value="server">Server tunnel</option>
|
||||
<option value="httpserver">HTTP server tunnel</option>
|
||||
</select> <input type="submit" value="GO" />
|
||||
</form>
|
||||
|
||||
|
||||
@@ -100,8 +100,10 @@ public class I2PSocketManagerFactory {
|
||||
//p.setProperty("tunnels.depthInbound", "0");
|
||||
}
|
||||
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
|
||||
if (i2cpHost != null)
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
|
||||
if (i2cpPort > 0)
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
|
||||
|
||||
try {
|
||||
I2PSession session = client.createSession(myPrivateKeyStream, opts);
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
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.I2PAppContext;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Sit around on a destination, receiving lots of data and sending lots of
|
||||
* data to whomever talks to us.
|
||||
*
|
||||
* Usage: TestSwarm myKeyFile [peerDestFile ]*
|
||||
*
|
||||
*/
|
||||
public class TestSwarm {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private String _destFile;
|
||||
private String _peerDestFiles[];
|
||||
private String _conOptions;
|
||||
private I2PSocketManager _manager;
|
||||
private boolean _dead;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: TestSwarm myDestFile [peerDestFile ]*");
|
||||
return;
|
||||
}
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
String files[] = new String[args.length - 1];
|
||||
System.arraycopy(args, 1, files, 0, files.length);
|
||||
TestSwarm swarm = new TestSwarm(ctx, args[0], files);
|
||||
swarm.startup();
|
||||
}
|
||||
|
||||
public TestSwarm(I2PAppContext ctx, String destFile, String peerDestFiles[]) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(TestSwarm.class);
|
||||
_dead = false;
|
||||
_destFile = destFile;
|
||||
_peerDestFiles = peerDestFiles;
|
||||
_conOptions = "";
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
_log.debug("Starting up");
|
||||
File keys = new File(_destFile);
|
||||
if (!keys.exists()) {
|
||||
try {
|
||||
I2PClientFactory.createClient().createDestination(new FileOutputStream(keys));
|
||||
} catch (Exception e) {
|
||||
_log.error("Error creating a new destination on " + keys, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
_manager = I2PSocketManagerFactory.createManager(new FileInputStream(_destFile), null, -1, null);
|
||||
} catch (Exception e) {
|
||||
_log.error("Error creatign the manager", e);
|
||||
return;
|
||||
}
|
||||
|
||||
I2PThread listener = new I2PThread(new Listener(), "Listener");
|
||||
listener.start();
|
||||
|
||||
connectWithPeers();
|
||||
}
|
||||
|
||||
|
||||
private void connectWithPeers() {
|
||||
if (_peerDestFiles != null) {
|
||||
for (int i = 0; i < _peerDestFiles.length; i++) {
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(_peerDestFiles[i]);
|
||||
Destination dest = new Destination();
|
||||
dest.readBytes(fin);
|
||||
|
||||
I2PThread flooder = new I2PThread(new Flooder(dest), "Flooder+" + dest.calculateHash().toBase64().substring(0,4));
|
||||
flooder.start();
|
||||
} catch (Exception e) {
|
||||
_log.error("Unable to read the peer from " + _peerDestFiles[i], e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Listener implements Runnable {
|
||||
public void run() {
|
||||
try {
|
||||
I2PServerSocket ss = _manager.getServerSocket();
|
||||
I2PSocket s = null;
|
||||
while ( (s = ss.accept()) != null) {
|
||||
I2PThread flooder = new I2PThread(new Flooder(s), "Flooder-" + s.getPeerDestination().calculateHash().toBase64().substring(0,4));
|
||||
flooder.start();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error listening", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile long __conId = 0;
|
||||
private class Flooder implements Runnable {
|
||||
private Destination _remoteDestination;
|
||||
private I2PSocket _socket;
|
||||
private boolean _closed;
|
||||
private long _started;
|
||||
private long _totalSent;
|
||||
private long _totalReceived;
|
||||
private long _lastReceived;
|
||||
private long _lastReceivedOn;
|
||||
private long _connectionId;
|
||||
|
||||
public Flooder(Destination dest) {
|
||||
_socket = null;
|
||||
_remoteDestination = dest;
|
||||
_connectionId = ++__conId;
|
||||
_closed = false;
|
||||
_lastReceived = -1;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
}
|
||||
|
||||
public Flooder(I2PSocket socket) {
|
||||
_socket = socket;
|
||||
_remoteDestination = socket.getPeerDestination();
|
||||
_connectionId = ++__conId;
|
||||
_closed = false;
|
||||
_lastReceived = -1;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
}
|
||||
|
||||
public long getConnectionId() { return _connectionId; }
|
||||
public Destination getDestination() { return _remoteDestination; }
|
||||
|
||||
public void run() {
|
||||
_started = _context.clock().now();
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".started", 1, 0);
|
||||
byte data[] = new byte[32*1024];
|
||||
long value = 0;
|
||||
long lastSend = _context.clock().now();
|
||||
if (_socket == null) {
|
||||
try {
|
||||
_socket = _manager.connect(_remoteDestination);
|
||||
} catch (Exception e) {
|
||||
_log.error("Error connecting to " + _remoteDestination.calculateHash().toBase64().substring(0,4));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
I2PThread floodListener = new I2PThread(new FloodListener(), "FloodListener" + _connectionId);
|
||||
floodListener.start();
|
||||
|
||||
try {
|
||||
OutputStream out = _socket.getOutputStream();
|
||||
while (!_closed) {
|
||||
out.write(data);
|
||||
// out.flush();
|
||||
_totalSent += data.length;
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
|
||||
//try { Thread.sleep(100); } catch (InterruptedException ie) {}
|
||||
long now = _context.clock().now();
|
||||
_log.debug("Sending " + _connectionId + " after " + (now-lastSend));
|
||||
lastSend = now;
|
||||
try { Thread.sleep(20); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error sending", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class FloodListener implements Runnable {
|
||||
public void run() {
|
||||
long lastRead = System.currentTimeMillis();
|
||||
long now = lastRead;
|
||||
try {
|
||||
InputStream in = _socket.getInputStream();
|
||||
byte buf[] = new byte[32*1024];
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1) {
|
||||
now = System.currentTimeMillis();
|
||||
_totalReceived += read;
|
||||
_context.statManager().addRateData("swarm." + getConnectionId() + ".totalReceived", _totalReceived, 0);
|
||||
_log.debug("Receiving " + _connectionId + " with " + read + " after " + (now-lastRead));
|
||||
lastRead = now;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error listening to the flood", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
/**
|
||||
* uuuugly. dump the peer profile data if given a peer.
|
||||
*
|
||||
*/
|
||||
public class StatHelper {
|
||||
private String _peer;
|
||||
private Writer _writer;
|
||||
|
||||
public void setPeer(String peer) { _peer = peer; }
|
||||
public void setWriter(Writer writer) { _writer = writer; }
|
||||
|
||||
public String getProfile() {
|
||||
RouterContext ctx = (RouterContext)net.i2p.router.RouterContext.listContexts().get(0);
|
||||
Set peers = ctx.profileOrganizer().selectAllPeers();
|
||||
for (Iterator iter = peers.iterator(); iter.hasNext(); ) {
|
||||
Hash peer = (Hash)iter.next();
|
||||
if (peer.toBase64().startsWith(_peer)) {
|
||||
try {
|
||||
WriterOutputStream wos = new WriterOutputStream(_writer);
|
||||
ctx.profileOrganizer().exportProfile(peer, wos);
|
||||
wos.flush();
|
||||
_writer.flush();
|
||||
return "";
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* Treat a writer as an output stream. Quick 'n dirty, none
|
||||
* of that "intarnasheeonaleyzayshun" stuff. So we can treat
|
||||
* the jsp's PrintWriter as an OutputStream
|
||||
*/
|
||||
public class WriterOutputStream extends OutputStream {
|
||||
private Writer _writer;
|
||||
|
||||
public WriterOutputStream(Writer writer) { _writer = writer; }
|
||||
public void write(int b) throws IOException { _writer.write(b); }
|
||||
}
|
||||
@@ -66,9 +66,9 @@
|
||||
I2P will attempt to guess your IP address by having whomever it talks to tell it what
|
||||
address they think you are. If and only if you have no working TCP connections and you
|
||||
have not overridden the IP address, your router will believe them. If that doesn't sound
|
||||
ok to you, thats fine - go to the <a href="/configadvanced.jsp">advanced config</a> page
|
||||
ok to you, thats fine - go to the <a href="configadvanced.jsp">advanced config</a> page
|
||||
and add "i2np.tcp.hostname=yourHostname", then go to the
|
||||
<a href="/configservice.jsp">service</a> page and do a graceful restart. We used to make
|
||||
<a href="configservice.jsp">service</a> page and do a graceful restart. We used to make
|
||||
people enter a hostname/IP address on this page, but too many people got it wrong ;)</p>
|
||||
|
||||
<p>The other advanced network option has to do with reseeding - you should never need to
|
||||
|
||||
5
apps/routerconsole/jsp/dumpprofile.jsp
Normal file
5
apps/routerconsole/jsp/dumpprofile.jsp
Normal file
@@ -0,0 +1,5 @@
|
||||
<%@page contentType="text/plain"
|
||||
%><jsp:useBean id="helper" class="net.i2p.router.web.StatHelper"
|
||||
/><jsp:setProperty name="helper" property="peer" value="<%=request.getParameter("peer")%>"
|
||||
/><jsp:setProperty name="helper" property="writer" value="<%=out%>"
|
||||
/><jsp:getProperty name="helper" property="profile" />
|
||||
@@ -33,7 +33,7 @@ by their binary code license. This product includes software developed by the A
|
||||
(http://www.apache.org/). </p>
|
||||
|
||||
<p>Another application you can see on this webpage is <a href="http://www.i2p.net/i2ptunnel">I2PTunnel</a>
|
||||
(your <a href="/i2ptunnel/" target="_blank">web interface</a>) - a GPL'ed application written by mihi that
|
||||
(your <a href="i2ptunnel/" target="_blank">web interface</a>) - a GPL'ed application written by mihi that
|
||||
lets you tunnel normal TCP/IP traffic over I2P (such as the eepproxy and the irc proxy).</p>
|
||||
|
||||
<p>The router by default also includes human's public domain <a href="http://www.i2p.net/sam">SAM</a> bridge,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
if (helper.getActivePeers() <= 0) {
|
||||
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
|
||||
}
|
||||
if (helper.getActiveProfiles() <= 4) { // 4 is the min fallback
|
||||
if (helper.getActiveProfiles() <= 10) { // 10 is the min fallback
|
||||
if ("true".equals(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false"))) {
|
||||
out.print(" <i>reseeding</i>");
|
||||
} else {
|
||||
|
||||
@@ -21,9 +21,9 @@ SRCDIR = src
|
||||
# Programs
|
||||
#
|
||||
|
||||
AR = C:\Dev-Cpp\bin\ar
|
||||
CC = C:\Dev-Cpp\bin\gcc
|
||||
RM = C:\Dev-Cpp\bin\rm
|
||||
AR = C:\MinGW\bin\ar
|
||||
CC = C:\MinGW\bin\gcc
|
||||
RM = C:\MinGW\bin\rm
|
||||
|
||||
#
|
||||
# Flags
|
||||
|
||||
@@ -1 +1 @@
|
||||
See the `docs' directory for documentation and license.
|
||||
See the `docs' directory for the documentation and license.
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
If you would like to make a donation to the author of this library, you can use
|
||||
the following methods:
|
||||
|
||||
* E-Gold account number 1043280
|
||||
* Paypal email mpc@innographx.com
|
||||
|
||||
If you want to use some other method, just ask.
|
||||
32
apps/sam/c/doc/install.txt
Normal file
32
apps/sam/c/doc/install.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
=====================
|
||||
How to Install LibSAM
|
||||
=====================
|
||||
|
||||
1) Be sure you have GNU Make installed.
|
||||
|
||||
2) Find the Makefile for your operating system in the LibSAM root. For example,
|
||||
if you are on FreeBSD, you'd use Makefile.freebsd.
|
||||
|
||||
3) Run gmake with the -f option to build LibSAM. For example, on FreeBSD, you'd
|
||||
run "gmake -f Makefile.freebsd". On Linux, GNU Make is just called 'make', so
|
||||
you'd run "make -f Makefile.linux".
|
||||
|
||||
4) If that worked, you can now try to compile some of the example programs.
|
||||
They are compiled in the same way as LibSAM, but the Makefile names are
|
||||
different.
|
||||
|
||||
I2P-Ping should compile on any Unix system using the default Makefile. It won't
|
||||
work on Win32, however, because Win32 doesn't have the getopt() function.
|
||||
|
||||
The Warhammer example should run on any OS. Use the Makefile.posix for Unix
|
||||
sytems or the Makefile.mingw for Win32.
|
||||
|
||||
*** If you have trouble ***
|
||||
|
||||
If you have trouble compiling LibSAM, try to edit the Makefiles to fix the
|
||||
problem. The "Makefile.common" is shared by all systems, and you shouldn't have
|
||||
to touch it. Just copy the Makefile of the OS most similar to your own and
|
||||
start hacking on it. Send me a patch if you get it working.
|
||||
|
||||
I realise this build system is horrible, and in the future I will probably
|
||||
replace it entirely.
|
||||
@@ -1,10 +1,10 @@
|
||||
I need to do these things:
|
||||
|
||||
* SAM raw support
|
||||
* SAM raw support (partially complete)
|
||||
* Write an instruction manual
|
||||
* Make dest a dynamic string
|
||||
* Change SAM parser to use a hashmap
|
||||
* Switch to GNU Autoconf (?)
|
||||
* Improve build system
|
||||
|
||||
Anyone can help with these things:
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* vi:set ts=4: */
|
||||
|
||||
v1.30
|
||||
* Added session to sam_namingback()
|
||||
* Removed stdint.h dependency
|
||||
* Improved WIRETAP to do more logging
|
||||
* Added "pinger.sh" shell script example for using i2p-ping
|
||||
* Added SAM_BAD_STYLE error
|
||||
|
||||
@@ -60,7 +60,8 @@ static void databack(sam_sess_t *session, sam_sid_t stream_id, void *data,
|
||||
size_t size);
|
||||
static void diedback(sam_sess_t *session);
|
||||
static void logback(char *s);
|
||||
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result);
|
||||
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result);
|
||||
static void statusback(sam_sess_t *session, sam_sid_t stream_id,
|
||||
samerr_t result);
|
||||
|
||||
@@ -78,7 +79,7 @@ int main(int argc, char *argv[])
|
||||
int count = INT_MAX; /* number of times to ping */
|
||||
int pongcount = -1;
|
||||
char *samhost = "localhost";
|
||||
uint16_t samport = 7656;
|
||||
unsigned short samport = 7656;
|
||||
|
||||
while ((ch = getopt(argc, argv, "ac:h:mp:qv")) != -1) {
|
||||
switch (ch) {
|
||||
@@ -103,7 +104,7 @@ int main(int argc, char *argv[])
|
||||
quiet = true;
|
||||
break;
|
||||
case 'v': /* version */
|
||||
puts("$Id: i2p-ping.c,v 1.4 2004/09/22 20:05:40 jrandom Exp $");
|
||||
puts("$Id: i2p-ping.c,v 1.6 2004/12/02 17:54:23 mpc Exp $");
|
||||
puts("Copyright (c) 2004, Matthew P. Cashdollar <mpc@innographx.com>");
|
||||
break;
|
||||
case '?':
|
||||
@@ -140,7 +141,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
pongcount = 0;
|
||||
for (int j = 0; j < argc; j++) {
|
||||
if (strlen(argv[j]) == 516) {
|
||||
if (strlen(argv[j]) == SAM_PUBKEY_LEN - 1) {
|
||||
memcpy(dest, argv[j], SAM_PUBKEY_LEN);
|
||||
gotdest = true;
|
||||
} else
|
||||
@@ -242,7 +243,8 @@ static void logback(char *s)
|
||||
* This is really hackish, but we know that we are only doing one lookup, so
|
||||
* what the hell
|
||||
*/
|
||||
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result)
|
||||
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result)
|
||||
{
|
||||
if (result != SAM_OK) {
|
||||
fprintf(stderr, "Naming lookup failed: %s\n", sam_strerror(result));
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
# Programs
|
||||
#
|
||||
|
||||
CC = C:\Dev-Cpp\bin\gcc
|
||||
RM = C:\Dev-Cpp\bin\rm
|
||||
CC = C:\MinGW\bin\gcc
|
||||
RM = C:\MinGW\bin\rm
|
||||
|
||||
#
|
||||
# Flags
|
||||
|
||||
@@ -47,7 +47,8 @@ static void dgramback(sam_sess_t *session, sam_pubkey_t dest, void *data,
|
||||
size_t size);
|
||||
static void diedback(sam_sess_t *session);
|
||||
static void logback(char *s);
|
||||
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result);
|
||||
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result);
|
||||
|
||||
/*
|
||||
* Just some ugly global variables. Don't do this in your program.
|
||||
@@ -88,12 +89,12 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether they've supplied a name or a base 64 destination
|
||||
* Check whether they've supplied a hostname or a base 64 destination
|
||||
*
|
||||
* Note that this is a hack. Jrandom says that once certificates are added,
|
||||
* the length could be different depending on the certificate's size.
|
||||
*/
|
||||
if (strlen(argv[1]) == 516) {
|
||||
if (strlen(argv[1]) == SAM_PUBKEY_LEN - 1) {
|
||||
memcpy(dest, argv[1], SAM_PUBKEY_LEN);
|
||||
gotdest = true;
|
||||
} else {
|
||||
@@ -155,7 +156,6 @@ static void dgramback(sam_sess_t *session, sam_pubkey_t dest, void *data,
|
||||
static void diedback(sam_sess_t *session)
|
||||
{
|
||||
fprintf(stderr, "Lost SAM connection!\n");
|
||||
/* high quality code would do a sam_session_free() here */
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -172,11 +172,11 @@ static void logback(char *s)
|
||||
* This is really hackish, but we know that we are only doing one lookup, so
|
||||
* what the hell
|
||||
*/
|
||||
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result)
|
||||
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result)
|
||||
{
|
||||
if (result != SAM_OK) {
|
||||
fprintf(stderr, "Naming lookup failed: %s\n", sam_strerror(result));
|
||||
/* high quality code would do a sam_session_free() here */
|
||||
exit(1);
|
||||
}
|
||||
memcpy(dest, pubkey, SAM_PUBKEY_LEN);
|
||||
|
||||
@@ -28,36 +28,19 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef PLATFORM_H
|
||||
#define PLATFORM_H
|
||||
#ifndef LIBSAM_PLATFORM_H
|
||||
#define LIBSAM_PLATFORM_H
|
||||
|
||||
/*
|
||||
* Operating system
|
||||
*/
|
||||
#define FREEBSD 0 // FreeBSD
|
||||
#define MINGW 1 // Windows native (Mingw)
|
||||
#define CYGWIN 1 // Cygwin
|
||||
#define LINUX 2 // Linux
|
||||
#define CYGWIN 3 // Cygwin
|
||||
|
||||
#if OS == MINGW
|
||||
#define INET_ADDRSTRLEN 16
|
||||
#define NO_GETHOSTBYNAME2
|
||||
#define NO_INET_ATON /* implies NO_INET_PTON */
|
||||
#define NO_INET_NTOP
|
||||
#define NO_SSIZE_T
|
||||
#define NO_STRL
|
||||
#define NO_Z_FORMAT
|
||||
#define WINSOCK
|
||||
#endif
|
||||
|
||||
#if OS == LINUX
|
||||
#define NO_GETHOSTBYNAME2
|
||||
#define NO_STRL
|
||||
#define NO_Z_FORMAT
|
||||
#endif
|
||||
#define MINGW 3 // Windows native (Mingw)
|
||||
#define MSVC 4 // Windows native (Visual C++ 2003)
|
||||
|
||||
#if OS == CYGWIN
|
||||
#define FAST32_IS_LONG
|
||||
#define INET_ADDRSTRLEN 16
|
||||
#define NO_GETHOSTBYNAME2
|
||||
#define NO_INET_NTOP
|
||||
@@ -68,13 +51,29 @@
|
||||
#define NO_Z_FORMAT
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Standard C99 includes - if your compiler doesn't have these, it's time to
|
||||
* upgrade
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#if OS == LINUX
|
||||
#define NO_GETHOSTBYNAME2
|
||||
#define NO_STRL
|
||||
#define NO_Z_FORMAT
|
||||
#endif
|
||||
|
||||
#if OS == MINGW
|
||||
#define INET_ADDRSTRLEN 16
|
||||
#define NO_GETHOSTBYNAME2
|
||||
#define NO_INET_ATON // implies NO_INET_PTON
|
||||
#define NO_INET_NTOP
|
||||
#define NO_SSIZE_T
|
||||
#define NO_STRL
|
||||
#define NO_Z_FORMAT
|
||||
#define WINSOCK
|
||||
#endif
|
||||
|
||||
#if OS == MSVC // FIXME: doesn't work
|
||||
#define NO_STDBOOL_H
|
||||
#define NO_SSIZE_T
|
||||
#define NO_STRL
|
||||
#define WINSOCK
|
||||
#endif
|
||||
|
||||
/*
|
||||
* System includes
|
||||
@@ -116,6 +115,13 @@
|
||||
typedef signed long ssize_t;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* I'm too lazy to type "unsigned"
|
||||
*/
|
||||
typedef unsigned char byte;
|
||||
typedef unsigned int uint;
|
||||
typedef unsigned short ushort;
|
||||
|
||||
/*
|
||||
* Prints out the file name, line number, and function name before log message
|
||||
*/
|
||||
@@ -136,4 +142,4 @@
|
||||
#include <ctype.h>
|
||||
#endif
|
||||
|
||||
#endif /* PLATFORM_H */
|
||||
#endif /* LIBSAM_PLATFORM_H */
|
||||
|
||||
@@ -28,15 +28,20 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef SAM_H
|
||||
#define SAM_H
|
||||
#ifndef LIBSAM_SAM_H
|
||||
#define LIBSAM_SAM_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#ifdef NO_STDBOOL_H
|
||||
typedef int bool;
|
||||
#define true 0
|
||||
#define false 1
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
#include <stddef.h> // size_t
|
||||
|
||||
|
||||
/*
|
||||
@@ -67,18 +72,6 @@ extern "C" {
|
||||
* Some LibSAM variable types
|
||||
*/
|
||||
|
||||
typedef signed char schar_t;
|
||||
typedef unsigned char uchar_t;
|
||||
typedef unsigned int uint_t;
|
||||
typedef unsigned long ulong_t;
|
||||
typedef unsigned short ushort_t;
|
||||
|
||||
#ifdef WINSOCK
|
||||
typedef SOCKET socket_t;
|
||||
#else
|
||||
typedef int socket_t;
|
||||
#endif
|
||||
|
||||
typedef enum {SAM_STREAM, SAM_DGRAM, SAM_RAW} sam_conn_t; /* SAM connection */
|
||||
|
||||
typedef char sam_pubkey_t[SAM_PUBKEY_LEN]; /* base 64 public key */
|
||||
@@ -88,12 +81,13 @@ typedef struct {
|
||||
size_t size;
|
||||
} sam_sendq_t; /* sending queue to encourage large stream packet sizes */
|
||||
|
||||
typedef int_fast32_t sam_sid_t; /* stream id number */
|
||||
typedef long sam_sid_t; /* stream id number */
|
||||
|
||||
typedef struct {
|
||||
socket_t sock; /* the socket used for communications with SAM */
|
||||
int sock; /* the socket used for communications with SAM */
|
||||
bool connected; /* whether the socket is connected */
|
||||
sam_sid_t prev_id; /* the last stream id number we used */
|
||||
void *child; /* whatever you want it to be */
|
||||
} sam_sess_t; /* a SAM session */
|
||||
|
||||
typedef enum { /* see sam_strerror() for detailed descriptions of these */
|
||||
@@ -119,8 +113,8 @@ void sam_session_free(sam_sess_t **session);
|
||||
/* SAM controls - connection */
|
||||
bool sam_close(sam_sess_t *session);
|
||||
samerr_t sam_connect(sam_sess_t *session, const char *samhost,
|
||||
uint16_t samport, const char *destname, sam_conn_t style,
|
||||
uint_t tunneldepth);
|
||||
unsigned short samport, const char *destname, sam_conn_t style,
|
||||
unsigned int tunneldepth);
|
||||
/* SAM controls - utilities */
|
||||
void sam_naming_lookup(sam_sess_t *session, const char *name);
|
||||
bool sam_read_buffer(sam_sess_t *session);
|
||||
@@ -128,7 +122,8 @@ const char *sam_strerror(samerr_t code);
|
||||
/* SAM controls - callbacks */
|
||||
void (*sam_diedback)(sam_sess_t *session);
|
||||
void (*sam_logback)(char *str);
|
||||
void (*sam_namingback)(char *name, sam_pubkey_t pubkey, samerr_t result);
|
||||
void (*sam_namingback)(sam_sess_t *session, char *name,
|
||||
sam_pubkey_t pubkey, samerr_t result);
|
||||
|
||||
/* Stream commands */
|
||||
void sam_stream_close(sam_sess_t *session, sam_sid_t stream_id);
|
||||
@@ -168,4 +163,4 @@ void (*sam_rawback)(sam_sess_t *session, void *data, size_t size);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* SAM_H */
|
||||
#endif /* LIBSAM_SAM_H */
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
* snprintf.c)
|
||||
*/
|
||||
|
||||
#ifndef SNPRINTF_H
|
||||
#define SNPRINTF_H
|
||||
#ifndef LIBSAM_SNPRINTF_H
|
||||
#define LIBSAM_SNPRINTF_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -46,4 +46,4 @@ int vsnprintf (char *str, size_t count, const char *fmt, va_list arg);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* SNPRINTF_H */
|
||||
#endif /* LIBSAM_SNPRINTF_H */
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
* Note: The strl.c file retains its original license (at the top of strl.c)
|
||||
*/
|
||||
|
||||
#ifndef STRL_H
|
||||
#define STRL_H
|
||||
#ifndef LIBSAM_STRL_H
|
||||
#define LIBSAM_STRL_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -44,4 +44,4 @@ extern size_t strlcpy(char *dst, const char *src, size_t siz);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* STRL_H */
|
||||
#endif /* LIBSAM_STRL_H */
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "platform.h"
|
||||
#include "sam.h"
|
||||
#include "platform.h"
|
||||
|
||||
static bool sam_hello(sam_sess_t *session);
|
||||
static void sam_log(const char *format, ...);
|
||||
@@ -40,9 +40,9 @@ static bool sam_readable(sam_sess_t *session);
|
||||
static sam_sendq_t *sam_sendq_create();
|
||||
static samerr_t sam_session_create(sam_sess_t *session,
|
||||
const char *destname, sam_conn_t style,
|
||||
uint_t tunneldepth);
|
||||
uint tunneldepth);
|
||||
static bool sam_socket_connect(sam_sess_t *session, const char *host,
|
||||
uint16_t port);
|
||||
ushort port);
|
||||
static bool sam_socket_resolve(const char *hostname, char *ipaddr);
|
||||
#ifdef WINSOCK
|
||||
static samerr_t sam_winsock_cleanup();
|
||||
@@ -79,7 +79,8 @@ void (*sam_diedback)(sam_sess_t *session) = NULL;
|
||||
void (*sam_logback)(char *str) = NULL;
|
||||
|
||||
/* naming lookup reply - `pubkey' will be NULL if `result' isn't SAM_OK */
|
||||
void (*sam_namingback)(char *name, sam_pubkey_t pubkey, samerr_t result) = NULL;
|
||||
void (*sam_namingback)(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
|
||||
samerr_t result) = NULL;
|
||||
|
||||
/* our connection to a peer has completed */
|
||||
void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
@@ -146,8 +147,8 @@ bool sam_close(sam_sess_t *session)
|
||||
*
|
||||
* Returns: SAM error code. If SAM_OK, `session' will be ready for use.
|
||||
*/
|
||||
samerr_t sam_connect(sam_sess_t *session, const char *samhost, uint16_t samport,
|
||||
const char *destname, sam_conn_t style, uint_t tunneldepth)
|
||||
samerr_t sam_connect(sam_sess_t *session, const char *samhost, ushort samport,
|
||||
const char *destname, sam_conn_t style, uint tunneldepth)
|
||||
{
|
||||
assert(session != NULL);
|
||||
samerr_t rc;
|
||||
@@ -386,7 +387,7 @@ static void sam_parse(sam_sess_t *session, char *s)
|
||||
q++;
|
||||
strlcpy(name, p, sizeof name);
|
||||
strlcpy(pubkey, q, sizeof pubkey);
|
||||
sam_namingback(name, pubkey, SAM_OK);
|
||||
sam_namingback(session, name, pubkey, SAM_OK);
|
||||
|
||||
} else if (strncmp(s, SAM_NAMING_REPLY_IK,
|
||||
strlen(SAM_NAMING_REPLY_IK)) == 0) {
|
||||
@@ -394,7 +395,7 @@ static void sam_parse(sam_sess_t *session, char *s)
|
||||
if (q != NULL)
|
||||
*q = '\0';
|
||||
strlcpy(name, p, sizeof name);
|
||||
sam_namingback(name, NULL, SAM_INVALID_KEY);
|
||||
sam_namingback(session, name, NULL, SAM_INVALID_KEY);
|
||||
|
||||
} else if (strncmp(s, SAM_NAMING_REPLY_KNF,
|
||||
strlen(SAM_NAMING_REPLY_KNF)) == 0) {
|
||||
@@ -402,14 +403,14 @@ static void sam_parse(sam_sess_t *session, char *s)
|
||||
if (q != NULL)
|
||||
*q = '\0';
|
||||
strlcpy(name, p, sizeof name);
|
||||
sam_namingback(name, NULL, SAM_KEY_NOT_FOUND);
|
||||
sam_namingback(session, name, NULL, SAM_KEY_NOT_FOUND);
|
||||
|
||||
} else {
|
||||
q = strchr(p, ' '); /* ' 'MES.. (optional) */
|
||||
if (q != NULL)
|
||||
*q = '\0';
|
||||
strlcpy(name, p, sizeof name);
|
||||
sam_namingback(name, NULL, SAM_UNKNOWN);
|
||||
sam_namingback(session, name, NULL, SAM_UNKNOWN);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -723,10 +724,10 @@ static ssize_t sam_read2(sam_sess_t *session, void *buf, size_t n)
|
||||
p = buf;
|
||||
printf("*RR* ");
|
||||
for (size_t x = 0; x < n; x++) {
|
||||
if (isprint(((uchar_t*)p)[x]))
|
||||
printf("%c,", ((uchar_t*)p)[x]);
|
||||
if (isprint(((byte*)p)[x]))
|
||||
printf("%c,", ((byte*)p)[x]);
|
||||
else
|
||||
printf("%03d,", ((uint8_t*)p)[x]);
|
||||
printf("%03d,", ((byte*)p)[x]);
|
||||
}
|
||||
printf("\n");
|
||||
printf("*RR* (read2() read %d bytes)\n", n);
|
||||
@@ -877,7 +878,7 @@ void sam_sendq_flush(sam_sess_t *session, sam_sid_t stream_id,
|
||||
* Returns: SAM error code
|
||||
*/
|
||||
static samerr_t sam_session_create(sam_sess_t *session, const char *destname,
|
||||
sam_conn_t style, uint_t tunneldepth)
|
||||
sam_conn_t style, uint tunneldepth)
|
||||
{
|
||||
assert(session != NULL);
|
||||
#define SAM_SESSTATUS_REPLY_OK "SESSION STATUS RESULT=OK"
|
||||
@@ -936,6 +937,7 @@ sam_sess_t *sam_session_init(sam_sess_t *session)
|
||||
SAMLOGS("Out of memory");
|
||||
abort();
|
||||
}
|
||||
session->child = NULL;
|
||||
}
|
||||
session->connected = false;
|
||||
session->prev_id = 0;
|
||||
@@ -963,7 +965,7 @@ void sam_session_free(sam_sess_t **session)
|
||||
*
|
||||
* Returns: true on sucess, false on error, with errno set
|
||||
*/
|
||||
bool sam_socket_connect(sam_sess_t *session, const char *host, uint16_t port)
|
||||
bool sam_socket_connect(sam_sess_t *session, const char *host, ushort port)
|
||||
{
|
||||
assert(session != NULL);
|
||||
struct sockaddr_in hostaddr;
|
||||
@@ -1080,11 +1082,7 @@ void sam_stream_close(sam_sess_t *session, sam_sid_t stream_id)
|
||||
assert(session != NULL);
|
||||
char cmd[SAM_CMD_LEN];
|
||||
|
||||
#ifdef FAST32_IS_LONG
|
||||
snprintf(cmd, sizeof cmd, "STREAM CLOSE ID=%ld\n", stream_id);
|
||||
#else
|
||||
snprintf(cmd, sizeof cmd, "STREAM CLOSE ID=%d\n", stream_id);
|
||||
#endif
|
||||
sam_write(session, cmd, strlen(cmd));
|
||||
|
||||
return;
|
||||
@@ -1103,13 +1101,8 @@ sam_sid_t sam_stream_connect(sam_sess_t *session, const sam_pubkey_t dest)
|
||||
char cmd[SAM_PKCMD_LEN];
|
||||
|
||||
session->prev_id++; /* increment the id for the connection */
|
||||
#ifdef FAST32_IS_LONG
|
||||
snprintf(cmd, sizeof cmd, "STREAM CONNECT ID=%ld DESTINATION=%s\n",
|
||||
session->prev_id, dest);
|
||||
#else
|
||||
snprintf(cmd, sizeof cmd, "STREAM CONNECT ID=%d DESTINATION=%s\n",
|
||||
session->prev_id, dest);
|
||||
#endif
|
||||
sam_write(session, cmd, strlen(cmd));
|
||||
|
||||
return session->prev_id;
|
||||
@@ -1141,15 +1134,9 @@ samerr_t sam_stream_send(sam_sess_t *session, sam_sid_t stream_id,
|
||||
return SAM_TOO_BIG;
|
||||
}
|
||||
#ifdef NO_Z_FORMAT
|
||||
#ifdef FAST32_IS_LONG
|
||||
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%ld SIZE=%u\n",
|
||||
stream_id, size);
|
||||
#else
|
||||
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%d SIZE=%u\n",
|
||||
stream_id, size);
|
||||
#endif
|
||||
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%ld SIZE=%u\n", stream_id, size);
|
||||
#else
|
||||
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%d SIZE=%zu\n",
|
||||
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%ld SIZE=%zu\n",
|
||||
stream_id, size);
|
||||
#endif
|
||||
sam_write(session, cmd, strlen(cmd));
|
||||
@@ -1399,7 +1386,7 @@ static ssize_t sam_write(sam_sess_t *session, const void *buf, size_t n)
|
||||
return -1;
|
||||
}
|
||||
#if SAM_WIRETAP
|
||||
const uchar_t *cp = buf;
|
||||
const byte *cp = buf;
|
||||
printf("*WW* ");
|
||||
for (size_t x = 0; x < n; x++) {
|
||||
if (isprint(cp[x]))
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
srcdir="./src:./test"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
|
||||
@@ -220,6 +220,15 @@ public class SAMBridge implements Runnable {
|
||||
}
|
||||
SAMBridge bridge = new SAMBridge(host, port, opts, keyfile);
|
||||
I2PThread t = new I2PThread(bridge, "SAMListener");
|
||||
if (Boolean.valueOf(System.getProperty("sam.shutdownOnOOM", "false")).booleanValue()) {
|
||||
t.addOOMEventListener(new I2PThread.OOMEventListener() {
|
||||
public void outOfMemory(OutOfMemoryError err) {
|
||||
err.printStackTrace();
|
||||
System.err.println("OOMed, die die die");
|
||||
System.exit(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
t.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,8 @@ public abstract class SAMHandler implements Runnable {
|
||||
* @return True is the string was successfully written, false otherwise
|
||||
*/
|
||||
protected final boolean writeString(String str) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending the client: [" + str + "]");
|
||||
try {
|
||||
writeBytes(str.getBytes("ISO-8859-1"));
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.net.Socket;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -36,14 +37,11 @@ public class SAMHandlerFactory {
|
||||
* @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;
|
||||
String line;
|
||||
StringTokenizer tok;
|
||||
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(s.getInputStream(),
|
||||
"ISO-8859-1"));
|
||||
line = br.readLine();
|
||||
line = DataHelper.readLine(s.getInputStream());
|
||||
if (line == null) {
|
||||
_log.debug("Connection closed by client");
|
||||
return null;
|
||||
|
||||
@@ -15,8 +15,10 @@ import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -28,8 +30,11 @@ import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -51,7 +56,10 @@ public class SAMStreamSession {
|
||||
private I2PSocketManager socketMgr = null;
|
||||
|
||||
private Object handlersMapLock = new Object();
|
||||
/** stream id (Long) to SAMStreamSessionSocketReader */
|
||||
private HashMap handlersMap = new HashMap();
|
||||
/** stream id (Long) to StreamSender */
|
||||
private HashMap sendersMap = new HashMap();
|
||||
|
||||
private Object idLock = new Object();
|
||||
private int lastNegativeId = 0;
|
||||
@@ -59,6 +67,14 @@ public class SAMStreamSession {
|
||||
// Can we create outgoing connections?
|
||||
private boolean canCreate = false;
|
||||
|
||||
/**
|
||||
* should we flush every time we get a STREAM SEND, or leave that up to
|
||||
* the streaming lib to decide?
|
||||
*/
|
||||
private boolean forceFlush = false;
|
||||
public static String PROP_FORCE_FLUSH = "sam.forceFlush";
|
||||
public static String DEFAULT_FORCE_FLUSH = "false";
|
||||
|
||||
/**
|
||||
* Create a new SAM STREAM session.
|
||||
*
|
||||
@@ -107,9 +123,6 @@ public class SAMStreamSession {
|
||||
} 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,
|
||||
@@ -120,6 +133,8 @@ public class SAMStreamSession {
|
||||
throw new SAMException("Error creating I2PSocketManager");
|
||||
}
|
||||
|
||||
forceFlush = Boolean.valueOf(allprops.getProperty(PROP_FORCE_FLUSH, DEFAULT_FORCE_FLUSH)).booleanValue();
|
||||
|
||||
boolean canReceive = false;
|
||||
if (dir.equals("BOTH")) {
|
||||
canCreate = true;
|
||||
@@ -197,18 +212,25 @@ public class SAMStreamSession {
|
||||
*
|
||||
* @param data Bytes to be sent
|
||||
*
|
||||
* @return True if the data was sent, false otherwise
|
||||
* @return True if the data was queued for sending, false otherwise
|
||||
*/
|
||||
public boolean sendBytes(int id, byte[] data) {
|
||||
Destination d = new Destination();
|
||||
SAMStreamSessionSocketHandler handler = getSocketHandler(id);
|
||||
public boolean sendBytes(int id, InputStream in, int size) throws IOException {
|
||||
StreamSender sender = getSender(id);
|
||||
|
||||
if (handler == null) {
|
||||
_log.error("Trying to send bytes through inexistent handler " +id);
|
||||
if (sender == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Trying to send bytes through nonexistent handler " +id);
|
||||
// even though it failed, we need to read those bytes!
|
||||
for (int i = 0; i < size; i++) {
|
||||
int c = in.read();
|
||||
if (c == -1)
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return handler.sendBytes(data);
|
||||
sender.sendBytes(in, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,13 +270,15 @@ public class SAMStreamSession {
|
||||
* @return An id associated to the socket handler
|
||||
*/
|
||||
private int createSocketHandler(I2PSocket s, int id) {
|
||||
SAMStreamSessionSocketHandler handler;
|
||||
SAMStreamSessionSocketReader reader = null;
|
||||
StreamSender sender = null;
|
||||
if (id == 0) {
|
||||
id = createUniqueId();
|
||||
}
|
||||
|
||||
try {
|
||||
handler = new SAMStreamSessionSocketHandler(s, id);
|
||||
reader = new SAMStreamSessionSocketReader(s, id);
|
||||
sender = new StreamSender(s, id);
|
||||
} catch (IOException e) {
|
||||
_log.error("IOException when creating SAM STREAM session socket handler", e);
|
||||
recv.stopStreamReceiving();
|
||||
@@ -262,10 +286,13 @@ public class SAMStreamSession {
|
||||
}
|
||||
|
||||
synchronized (handlersMapLock) {
|
||||
handlersMap.put(new Integer(id), handler);
|
||||
handlersMap.put(new Integer(id), reader);
|
||||
sendersMap.put(new Integer(id), sender);
|
||||
}
|
||||
|
||||
I2PThread t = new I2PThread(handler, "SAMStreamSessionSocketHandler");
|
||||
I2PThread t = new I2PThread(reader, "SAMReader" + id);
|
||||
t.start();
|
||||
t = new I2PThread(sender, "SAMSender" + id);
|
||||
t.start();
|
||||
|
||||
return id;
|
||||
@@ -283,9 +310,14 @@ public class SAMStreamSession {
|
||||
*
|
||||
* @param id Handler id
|
||||
*/
|
||||
private SAMStreamSessionSocketHandler getSocketHandler(int id) {
|
||||
private SAMStreamSessionSocketReader getSocketReader(int id) {
|
||||
synchronized (handlersMapLock) {
|
||||
return (SAMStreamSessionSocketHandler)handlersMap.get(new Integer(id));
|
||||
return (SAMStreamSessionSocketReader)handlersMap.get(new Integer(id));
|
||||
}
|
||||
}
|
||||
private StreamSender getSender(int id) {
|
||||
synchronized (handlersMapLock) {
|
||||
return (StreamSender)sendersMap.get(new Integer(id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,19 +338,19 @@ public class SAMStreamSession {
|
||||
* @param id Handler id to be removed
|
||||
*/
|
||||
private void removeSocketHandler(int id) {
|
||||
SAMStreamSessionSocketHandler removed;
|
||||
SAMStreamSessionSocketReader reader = null;
|
||||
StreamSender sender = null;
|
||||
|
||||
synchronized (handlersMapLock) {
|
||||
removed = (SAMStreamSessionSocketHandler)handlersMap.remove(new Integer(id));
|
||||
reader = (SAMStreamSessionSocketReader)handlersMap.remove(new Integer(id));
|
||||
sender = (StreamSender)sendersMap.remove(new Integer(id));
|
||||
}
|
||||
|
||||
if (removed == null) {
|
||||
_log.error("BUG! Trying to remove inexistent SAM STREAM session socket handler " + id);
|
||||
recv.stopStreamReceiving();
|
||||
} else {
|
||||
removed.stopRunning();
|
||||
_log.debug("Removed SAM STREAM session socket handler " + id);
|
||||
}
|
||||
if (reader != null)
|
||||
reader.stopRunning();
|
||||
if (sender != null)
|
||||
sender.stopRunning();
|
||||
_log.debug("Removed SAM STREAM session socket handler " + id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,9 +369,11 @@ public class SAMStreamSession {
|
||||
|
||||
while (iter.hasNext()) {
|
||||
id = (Integer)iter.next();
|
||||
((SAMStreamSessionSocketHandler)handlersMap.get(id)).stopRunning();
|
||||
((SAMStreamSessionSocketReader)handlersMap.get(id)).stopRunning();
|
||||
((StreamSender)sendersMap.get(id)).stopRunning();
|
||||
}
|
||||
handlersMap.clear();
|
||||
sendersMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,6 +453,8 @@ public class SAMStreamSession {
|
||||
} catch (I2PException e) {
|
||||
_log.debug("Caught I2PException", e);
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
_log.debug("Shutting down SAM STREAM session server");
|
||||
}
|
||||
@@ -431,10 +467,9 @@ public class SAMStreamSession {
|
||||
*
|
||||
* @author human
|
||||
*/
|
||||
public class SAMStreamSessionSocketHandler implements Runnable {
|
||||
public class SAMStreamSessionSocketReader implements Runnable {
|
||||
|
||||
private I2PSocket i2pSocket = null;
|
||||
private OutputStream i2pSocketOS = null;
|
||||
|
||||
private Object runningLock = new Object();
|
||||
private boolean stillRunning = true;
|
||||
@@ -442,44 +477,20 @@ public class SAMStreamSession {
|
||||
private int id;
|
||||
|
||||
/**
|
||||
* Create a new SAM STREAM session socket handler
|
||||
* Create a new SAM STREAM session socket reader
|
||||
*
|
||||
* @param s Socket to be handled
|
||||
* @param id Unique id assigned to the handler
|
||||
*/
|
||||
public SAMStreamSessionSocketHandler(I2PSocket s, int id) throws IOException {
|
||||
public SAMStreamSessionSocketReader(I2PSocket s, int id) throws IOException {
|
||||
_log.debug("Instantiating new SAM STREAM session socket handler");
|
||||
|
||||
i2pSocket = s;
|
||||
i2pSocketOS = s.getOutputStream();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send bytes through the SAM STREAM session socket handler
|
||||
*
|
||||
* @param data Data to be sent
|
||||
*
|
||||
* @return True if data has been sent without errors, false otherwise
|
||||
*/
|
||||
public boolean sendBytes(byte[] data) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Handler " + id + ": sending " + data.length
|
||||
+ " bytes");
|
||||
}
|
||||
try {
|
||||
i2pSocketOS.write(data);
|
||||
//i2pSocketOS.flush();
|
||||
} catch (IOException e) {
|
||||
_log.error("Error sending data through I2P socket", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a SAM STREAM session socket handler
|
||||
* Stop a SAM STREAM session socket reader
|
||||
*
|
||||
*/
|
||||
public void stopRunning() {
|
||||
@@ -538,4 +549,99 @@ public class SAMStreamSession {
|
||||
_log.debug("Shutting down SAM STREAM session socket handler " +id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lets us push data through the stream without blocking, (even after exceeding
|
||||
* the I2PSocket's buffer)
|
||||
*/
|
||||
private class StreamSender implements Runnable {
|
||||
private List _data;
|
||||
private int _id;
|
||||
private ByteCache _cache;
|
||||
private OutputStream _out = null;
|
||||
private boolean _stillRunning;
|
||||
|
||||
public StreamSender(I2PSocket s, int id) throws IOException {
|
||||
_data = new ArrayList(1);
|
||||
_id = id;
|
||||
_cache = ByteCache.getInstance(4, 32*1024);
|
||||
_out = s.getOutputStream();
|
||||
_stillRunning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send bytes through the SAM STREAM session socket sender
|
||||
*
|
||||
* @param data Data to be sent
|
||||
*
|
||||
* @throws IOException if the client didnt provide enough data
|
||||
*/
|
||||
public void sendBytes(InputStream in, int size) throws IOException {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Handler " + _id + ": sending " + size + " bytes");
|
||||
|
||||
ByteArray ba = _cache.acquire();
|
||||
int read = DataHelper.read(in, ba.getData(), 0, size);
|
||||
if (read != size)
|
||||
throw new IOException("Insufficient data from the SAM client (" + read + "/" + size + ")");
|
||||
|
||||
ba.setValid(read);
|
||||
synchronized (_data) {
|
||||
_data.add(ba);
|
||||
_data.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a SAM STREAM session socket sender
|
||||
*
|
||||
*/
|
||||
public void stopRunning() {
|
||||
_log.debug("stopRunning() invoked on socket sender " + _id);
|
||||
_stillRunning = false;
|
||||
synchronized (_data) {
|
||||
_data.clear();
|
||||
_data.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
ByteArray data = null;
|
||||
while (_stillRunning) {
|
||||
data = null;
|
||||
try {
|
||||
synchronized (_data) {
|
||||
if (_data.size() > 0)
|
||||
data = (ByteArray)_data.remove(0);
|
||||
else
|
||||
_data.wait(5000);
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
try {
|
||||
_out.write(data.getData(), 0, data.getValid());
|
||||
if (forceFlush) {
|
||||
// i dont like doing this, but it clears the buffer issues
|
||||
_out.flush();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// ok, the stream failed, but the SAM client didn't
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Stream failed", ioe);
|
||||
|
||||
removeSocketHandler(_id);
|
||||
stopRunning();
|
||||
|
||||
} finally {
|
||||
_cache.release(data);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
synchronized (_data) {
|
||||
_data.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -44,6 +45,9 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
private SAMDatagramSession datagramSession = null;
|
||||
private SAMStreamSession streamSession = null;
|
||||
|
||||
private long _id;
|
||||
private static volatile long __id = 0;
|
||||
|
||||
/**
|
||||
* Create a new SAM version 1 handler. This constructor expects
|
||||
* that the SAM HELLO message has been still answered (and
|
||||
@@ -68,6 +72,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
*/
|
||||
public SAMv1Handler(Socket s, int verMajor, int verMinor, Properties i2cpProps) throws SAMException, IOException {
|
||||
super(s, verMajor, verMinor, i2cpProps);
|
||||
_id = ++__id;
|
||||
_log.debug("SAM version 1 handler instantiated");
|
||||
|
||||
if ((this.verMajor != 1) || (this.verMinor != 0)) {
|
||||
@@ -76,13 +81,14 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
public void handle() {
|
||||
String msg, domain, opcode;
|
||||
String msg = null;
|
||||
String domain = null;
|
||||
String opcode = null;
|
||||
boolean canContinue = false;
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream(IN_BUFSIZE);
|
||||
StringTokenizer tok;
|
||||
Properties props;
|
||||
|
||||
this.thread.setName("SAMv1Handler");
|
||||
this.thread.setName("SAMv1Handler " + _id);
|
||||
_log.debug("SAM handling started");
|
||||
|
||||
try {
|
||||
@@ -95,22 +101,15 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
break;
|
||||
}
|
||||
|
||||
while ((b = in.read()) != -1) {
|
||||
if (b == '\n') {
|
||||
break;
|
||||
}
|
||||
buf.write(b);
|
||||
}
|
||||
if (b == -1) {
|
||||
msg = DataHelper.readLine(in);
|
||||
if (msg == null) {
|
||||
_log.debug("Connection closed by client");
|
||||
break;
|
||||
}
|
||||
|
||||
msg = buf.toString("ISO-8859-1").trim();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("New message received: [" + msg + "]");
|
||||
}
|
||||
buf.reset();
|
||||
|
||||
tok = new StringTokenizer(msg, " ");
|
||||
if (tok.countTokens() < 2) {
|
||||
@@ -150,14 +149,11 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
_log.error("Caught UnsupportedEncodingException ("
|
||||
+ e.getMessage() + ")", e);
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException ("
|
||||
+ e.getMessage() + ")", e);
|
||||
+ e.getMessage() + ") for message [" + msg + "]", e);
|
||||
} catch (Exception e) {
|
||||
_log.error("Unexpected exception", e);
|
||||
_log.error("Unexpected exception for message [" + msg + "]", e);
|
||||
} finally {
|
||||
_log.debug("Stopping handler");
|
||||
try {
|
||||
@@ -550,13 +546,9 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(getClientSocketInputStream());
|
||||
byte[] data = new byte[size];
|
||||
|
||||
in.readFully(data);
|
||||
|
||||
if (!streamSession.sendBytes(id, data)) {
|
||||
_log.error("STREAM SEND failed");
|
||||
if (!streamSession.sendBytes(id, getClientSocketInputStream(), size)) { // data)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("STREAM SEND [" + size + "] failed");
|
||||
boolean rv = writeString("STREAM CLOSED RESULT=CANT_REACH_PEER ID=" + id + " MESSAGE=\"Send of " + size + " bytes failed\"\n");
|
||||
streamSession.closeConnection(id);
|
||||
return rv;
|
||||
@@ -564,11 +556,11 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
|
||||
return true;
|
||||
} catch (EOFException e) {
|
||||
_log.debug("Too few bytes with RAW SEND message (expected: "
|
||||
_log.debug("Too few bytes with STREAM SEND message (expected: "
|
||||
+ size);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
_log.debug("Caught IOException while parsing RAW SEND message",
|
||||
_log.debug("Caught IOException while parsing STREAM SEND message",
|
||||
e);
|
||||
return false;
|
||||
}
|
||||
@@ -707,7 +699,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
try {
|
||||
closeClientSocket();
|
||||
} catch (IOException e) {
|
||||
_log.error("Error closing socket: " + e.getMessage());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error closing socket", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,7 +735,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
try {
|
||||
closeClientSocket();
|
||||
} catch (IOException e) {
|
||||
_log.error("Error closing socket: " + e.getMessage());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error closing socket", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -801,7 +795,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
public void stopStreamReceiving() {
|
||||
_log.debug("stopStreamReceiving() invoked");
|
||||
_log.debug("stopStreamReceiving() invoked", new Exception("stopped"));
|
||||
|
||||
if (streamSession == null) {
|
||||
_log.error("BUG! Got stream receiving stop, but session is null!");
|
||||
@@ -811,7 +805,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
try {
|
||||
closeClientSocket();
|
||||
} catch (IOException e) {
|
||||
_log.error("Error closing socket: " + e.getMessage());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error closing socket", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Basic noop client event listener
|
||||
*/
|
||||
public class SAMClientEventListenerImpl implements SAMReader.SAMClientEventListener {
|
||||
public void destReplyReceived(String publicKey, String privateKey) {}
|
||||
public void helloReplyReceived(boolean ok) {}
|
||||
public void namingReplyReceived(String name, String result, String value, String message) {}
|
||||
public void sessionStatusReceived(String result, String destination, String message) {}
|
||||
public void streamClosedReceived(String result, int id, String message) {}
|
||||
public void streamConnectedReceived(String remoteDestination, int id) {}
|
||||
public void streamDataReceived(int id, byte[] data, int offset, int length) {}
|
||||
public void streamStatusReceived(String result, int id, String message) {}
|
||||
public void unknownMessageReceived(String major, String minor, Properties params) {}
|
||||
}
|
||||
127
apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java
Normal file
127
apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple helper implementation of a the SAMClientEventListener
|
||||
*
|
||||
*/
|
||||
public class SAMEventHandler extends SAMClientEventListenerImpl {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Boolean _helloOk;
|
||||
private Object _helloLock = new Object();
|
||||
private Boolean _sessionCreateOk;
|
||||
private Object _sessionCreateLock = new Object();
|
||||
private Object _namingReplyLock = new Object();
|
||||
private Map _namingReplies = new HashMap();
|
||||
|
||||
public SAMEventHandler(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(getClass());
|
||||
}
|
||||
|
||||
public void helloReplyReceived(boolean ok) {
|
||||
synchronized (_helloLock) {
|
||||
if (ok)
|
||||
_helloOk = Boolean.TRUE;
|
||||
else
|
||||
_helloOk = Boolean.FALSE;
|
||||
_helloLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void sessionStatusReceived(String result, String destination, String msg) {
|
||||
synchronized (_sessionCreateLock) {
|
||||
if (SAMReader.SAMClientEventListener.SESSION_STATUS_OK.equals(result))
|
||||
_sessionCreateOk = Boolean.TRUE;
|
||||
else
|
||||
_sessionCreateOk = Boolean.FALSE;
|
||||
_sessionCreateLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void namingReplyReceived(String name, String result, String value, String msg) {
|
||||
synchronized (_namingReplyLock) {
|
||||
if (SAMReader.SAMClientEventListener.NAMING_REPLY_OK.equals(result))
|
||||
_namingReplies.put(name, value);
|
||||
else
|
||||
_namingReplies.put(name, result);
|
||||
_namingReplyLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void unknownMessageReceived(String major, String minor, Properties params) {
|
||||
_log.error("wrt, [" + major + "] [" + minor + "] [" + params + "]");
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// blocking lookup calls below
|
||||
//
|
||||
|
||||
/**
|
||||
* Wait for the connection to be established, returning true if everything
|
||||
* went ok
|
||||
*/
|
||||
public boolean waitForHelloReply() {
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (_helloLock) {
|
||||
if (_helloOk == null)
|
||||
_helloLock.wait();
|
||||
else
|
||||
return _helloOk.booleanValue();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the session to be created, returning true if everything went ok
|
||||
*
|
||||
*/
|
||||
public boolean waitForSessionCreateReply() {
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (_sessionCreateLock) {
|
||||
if (_sessionCreateOk == null)
|
||||
_sessionCreateLock.wait();
|
||||
else
|
||||
return _sessionCreateOk.booleanValue();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the destination found matching the name, or null if the key was
|
||||
* not able to be retrieved.
|
||||
*
|
||||
* @param name name to be looked for, or "ME"
|
||||
*/
|
||||
public String waitForNamingReply(String name) {
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (_namingReplyLock) {
|
||||
String val = (String)_namingReplies.remove(name);
|
||||
if (val == null) {
|
||||
_namingReplyLock.wait();
|
||||
} else {
|
||||
if (SAMReader.SAMClientEventListener.NAMING_REPLY_INVALID_KEY.equals(val))
|
||||
return null;
|
||||
else if (SAMReader.SAMClientEventListener.NAMING_REPLY_KEY_NOT_FOUND.equals(val))
|
||||
return null;
|
||||
else
|
||||
return val;
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
253
apps/sam/java/src/net/i2p/sam/client/SAMReader.java
Normal file
253
apps/sam/java/src/net/i2p/sam/client/SAMReader.java
Normal file
@@ -0,0 +1,253 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Read from a socket, producing events for any SAM message read
|
||||
*
|
||||
*/
|
||||
public class SAMReader {
|
||||
private Log _log;
|
||||
private InputStream _inRaw;
|
||||
private SAMClientEventListener _listener;
|
||||
private boolean _live;
|
||||
|
||||
public SAMReader(I2PAppContext context, InputStream samIn, SAMClientEventListener listener) {
|
||||
_log = context.logManager().getLog(SAMReader.class);
|
||||
_inRaw = samIn;
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
public void startReading() {
|
||||
_live = true;
|
||||
I2PThread t = new I2PThread(new Runner(), "SAM reader");
|
||||
t.start();
|
||||
}
|
||||
public void stopReading() { _live = false; }
|
||||
|
||||
/**
|
||||
* Async event notification interface for SAM clients
|
||||
*
|
||||
*/
|
||||
public interface SAMClientEventListener {
|
||||
public static final String SESSION_STATUS_OK = "OK";
|
||||
public static final String SESSION_STATUS_DUPLICATE_DEST = "DUPLICATE_DEST";
|
||||
public static final String SESSION_STATUS_I2P_ERROR = "I2P_ERROR";
|
||||
public static final String SESSION_STATUS_INVALID_KEY = "INVALID_KEY";
|
||||
|
||||
public static final String STREAM_STATUS_OK = "OK";
|
||||
public static final String STREAM_STATUS_CANT_REACH_PEER = "CANT_REACH_PEER";
|
||||
public static final String STREAM_STATUS_I2P_ERROR = "I2P_ERROR";
|
||||
public static final String STREAM_STATUS_INVALID_KEY = "INVALID_KEY";
|
||||
public static final String STREAM_STATUS_TIMEOUT = "TIMEOUT";
|
||||
|
||||
public static final String STREAM_CLOSED_OK = "OK";
|
||||
public static final String STREAM_CLOSED_CANT_REACH_PEER = "CANT_REACH_PEER";
|
||||
public static final String STREAM_CLOSED_I2P_ERROR = "I2P_ERROR";
|
||||
public static final String STREAM_CLOSED_PEER_NOT_FOUND = "PEER_NOT_FOUND";
|
||||
public static final String STREAM_CLOSED_TIMEOUT = "CLOSED";
|
||||
|
||||
public static final String NAMING_REPLY_OK = "OK";
|
||||
public static final String NAMING_REPLY_INVALID_KEY = "INVALID_KEY";
|
||||
public static final String NAMING_REPLY_KEY_NOT_FOUND = "KEY_NOT_FOUND";
|
||||
|
||||
public void helloReplyReceived(boolean ok);
|
||||
public void sessionStatusReceived(String result, String destination, String message);
|
||||
public void streamStatusReceived(String result, int id, String message);
|
||||
public void streamConnectedReceived(String remoteDestination, int id);
|
||||
public void streamClosedReceived(String result, int id, String message);
|
||||
public void streamDataReceived(int id, byte data[], int offset, int length);
|
||||
public void namingReplyReceived(String name, String result, String value, String message);
|
||||
public void destReplyReceived(String publicKey, String privateKey);
|
||||
|
||||
public void unknownMessageReceived(String major, String minor, Properties params);
|
||||
}
|
||||
|
||||
private class Runner implements Runnable {
|
||||
public void run() {
|
||||
Properties params = new Properties();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(80);
|
||||
while (_live) {
|
||||
|
||||
try {
|
||||
int c = -1;
|
||||
while ((c = _inRaw.read()) != -1) {
|
||||
if (c == '\n') {
|
||||
break;
|
||||
}
|
||||
baos.write(c);
|
||||
}
|
||||
if (c == -1) {
|
||||
_log.error("Error reading from the SAM bridge");
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error reading from SAM", ioe);
|
||||
}
|
||||
|
||||
String line = new String(baos.toByteArray());
|
||||
baos.reset();
|
||||
|
||||
if (line == null) {
|
||||
_log.info("No more data from the SAM bridge");
|
||||
break;
|
||||
}
|
||||
|
||||
_log.debug("Line read from the bridge: " + line);
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
|
||||
if (tok.countTokens() < 2) {
|
||||
_log.error("Invalid SAM line: [" + line + "]");
|
||||
_live = false;
|
||||
return;
|
||||
}
|
||||
|
||||
String major = tok.nextToken();
|
||||
String minor = tok.nextToken();
|
||||
|
||||
params.clear();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq > 0) && (eq < pair.length() - 1) ) {
|
||||
String name = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
while ( (val.charAt(0) == '\"') && (val.length() > 0) )
|
||||
val = val.substring(1);
|
||||
while ( (val.length() > 0) && (val.charAt(val.length()-1) == '\"') )
|
||||
val = val.substring(0, val.length()-1);
|
||||
params.setProperty(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
processEvent(major, minor, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Big ugly method parsing everything. If I cared, I'd factor this out into
|
||||
* a dozen tiny methods.
|
||||
*
|
||||
*/
|
||||
private void processEvent(String major, String minor, Properties params) {
|
||||
if ("HELLO".equals(major)) {
|
||||
if ("REPLY".equals(minor)) {
|
||||
String result = params.getProperty("RESULT");
|
||||
if ("OK".equals(result))
|
||||
_listener.helloReplyReceived(true);
|
||||
else
|
||||
_listener.helloReplyReceived(false);
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("SESSION".equals(major)) {
|
||||
if ("STATUS".equals(minor)) {
|
||||
String result = params.getProperty("RESULT");
|
||||
String dest = params.getProperty("DESTINATION");
|
||||
String msg = params.getProperty("MESSAGE");
|
||||
_listener.sessionStatusReceived(result, dest, msg);
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("STREAM".equals(major)) {
|
||||
if ("STATUS".equals(minor)) {
|
||||
String result = params.getProperty("RESULT");
|
||||
String id = params.getProperty("ID");
|
||||
String msg = params.getProperty("MESSAGE");
|
||||
if (id != null) {
|
||||
try {
|
||||
_listener.streamStatusReceived(result, Integer.parseInt(id), msg);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("CONNECTED".equals(minor)) {
|
||||
String dest = params.getProperty("DESTINATION");
|
||||
String id = params.getProperty("ID");
|
||||
if (id != null) {
|
||||
try {
|
||||
_listener.streamConnectedReceived(dest, Integer.parseInt(id));
|
||||
} catch (NumberFormatException nfe) {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("CLOSED".equals(minor)) {
|
||||
String result = params.getProperty("RESULT");
|
||||
String id = params.getProperty("ID");
|
||||
String msg = params.getProperty("MESSAGE");
|
||||
if (id != null) {
|
||||
try {
|
||||
_listener.streamClosedReceived(result, Integer.parseInt(id), msg);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("RECEIVED".equals(minor)) {
|
||||
String id = params.getProperty("ID");
|
||||
String size = params.getProperty("SIZE");
|
||||
if (id != null) {
|
||||
try {
|
||||
int idVal = Integer.parseInt(id);
|
||||
int sizeVal = Integer.parseInt(size);
|
||||
|
||||
byte data[] = new byte[sizeVal];
|
||||
int read = DataHelper.read(_inRaw, data);
|
||||
if (read != sizeVal) {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
} else {
|
||||
_listener.streamDataReceived(idVal, data, 0, sizeVal);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
} catch (IOException ioe) {
|
||||
_live = false;
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("NAMING".equals(major)) {
|
||||
if ("REPLY".equals(minor)) {
|
||||
String name = params.getProperty("NAME");
|
||||
String result = params.getProperty("RESULT");
|
||||
String value = params.getProperty("VALUE");
|
||||
String msg = params.getProperty("MESSAGE");
|
||||
_listener.namingReplyReceived(name, result, value, msg);
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else if ("DEST".equals(major)) {
|
||||
if ("REPLY".equals(minor)) {
|
||||
String pub = params.getProperty("PUB");
|
||||
String priv = params.getProperty("PRIV");
|
||||
_listener.destReplyReceived(pub, priv);
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
} else {
|
||||
_listener.unknownMessageReceived(major, minor, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
262
apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java
Normal file
262
apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java
Normal file
@@ -0,0 +1,262 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
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.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import net.i2p.sam.client.SAMEventHandler;
|
||||
import net.i2p.sam.client.SAMClientEventListenerImpl;
|
||||
import net.i2p.sam.client.SAMReader;
|
||||
|
||||
/**
|
||||
* Send a file to a peer
|
||||
*
|
||||
* Usage: SAMStreamSend samHost samPort peerDestFile dataFile
|
||||
*
|
||||
*/
|
||||
public class SAMStreamSend {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private String _samHost;
|
||||
private String _samPort;
|
||||
private String _destFile;
|
||||
private String _dataFile;
|
||||
private String _conOptions;
|
||||
private Socket _samSocket;
|
||||
private OutputStream _samOut;
|
||||
private InputStream _samIn;
|
||||
private SAMReader _reader;
|
||||
private boolean _dead;
|
||||
private SAMEventHandler _eventHandler;
|
||||
/** Connection id (Integer) to peer (Flooder) */
|
||||
private Map _remotePeers;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 4) {
|
||||
System.err.println("Usage: SAMStreamSend samHost samPort peerDestFile dataFile");
|
||||
return;
|
||||
}
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
String files[] = new String[args.length - 3];
|
||||
SAMStreamSend sender = new SAMStreamSend(ctx, args[0], args[1], args[2], args[3]);
|
||||
sender.startup();
|
||||
}
|
||||
|
||||
public SAMStreamSend(I2PAppContext ctx, String samHost, String samPort, String destFile, String dataFile) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(SAMStreamSend.class);
|
||||
_dead = false;
|
||||
_samHost = samHost;
|
||||
_samPort = samPort;
|
||||
_destFile = destFile;
|
||||
_dataFile = dataFile;;
|
||||
_conOptions = "";
|
||||
_eventHandler = new SendEventHandler(_context);
|
||||
_remotePeers = new HashMap();
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
_log.debug("Starting up");
|
||||
boolean ok = connect();
|
||||
_log.debug("Connected: " + ok);
|
||||
if (ok) {
|
||||
_reader = new SAMReader(_context, _samIn, _eventHandler);
|
||||
_reader.startReading();
|
||||
_log.debug("Reader created");
|
||||
String ourDest = handshake();
|
||||
_log.debug("Handshake complete. we are " + ourDest);
|
||||
if (ourDest != null) {
|
||||
send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SendEventHandler extends SAMEventHandler {
|
||||
public SendEventHandler(I2PAppContext ctx) { super(ctx); }
|
||||
public void streamClosedReceived(String result, int id, String message) {
|
||||
Sender sender = null;
|
||||
synchronized (_remotePeers) {
|
||||
sender = (Sender)_remotePeers.remove(new Integer(id));
|
||||
}
|
||||
if (sender != null) {
|
||||
sender.closed();
|
||||
_log.debug("Connection " + sender.getConnectionId() + " closed to " + sender.getDestination());
|
||||
} else {
|
||||
_log.error("wtf, not connected to " + id + " but we were just closed?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
try {
|
||||
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
|
||||
_samOut = _samSocket.getOutputStream();
|
||||
_samIn = _samSocket.getInputStream();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String handshake() {
|
||||
synchronized (_samOut) {
|
||||
try {
|
||||
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Hello sent");
|
||||
boolean ok = _eventHandler.waitForHelloReply();
|
||||
_log.debug("Hello reply found: " + ok);
|
||||
if (!ok)
|
||||
throw new IOException("wtf, hello failed?");
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=TRANSIENT " + _conOptions + "\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Session create sent");
|
||||
ok = _eventHandler.waitForSessionCreateReply();
|
||||
_log.debug("Session create reply found: " + ok);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Naming lookup sent");
|
||||
String destination = _eventHandler.waitForNamingReply("ME");
|
||||
_log.debug("Naming lookup reply found: " + destination);
|
||||
if (destination == null) {
|
||||
_log.error("No naming lookup reply found!");
|
||||
return null;
|
||||
} else {
|
||||
_log.info("We are " + destination);
|
||||
}
|
||||
return destination;
|
||||
} catch (Exception e) {
|
||||
_log.error("Error handshaking", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void send() {
|
||||
Sender sender = new Sender();
|
||||
boolean ok = sender.openConnection();
|
||||
if (ok) {
|
||||
I2PThread t = new I2PThread(sender, "Sender");
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
private class Sender implements Runnable {
|
||||
private int _connectionId;
|
||||
private String _remoteDestination;
|
||||
private InputStream _in;
|
||||
private boolean _closed;
|
||||
private long _started;
|
||||
private long _totalSent;
|
||||
|
||||
public Sender() {
|
||||
_closed = false;
|
||||
}
|
||||
|
||||
public boolean openConnection() {
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(_destFile);
|
||||
byte dest[] = new byte[1024];
|
||||
int read = DataHelper.read(fin, dest);
|
||||
|
||||
_remoteDestination = new String(dest, 0, read);
|
||||
synchronized (_remotePeers) {
|
||||
_connectionId = _remotePeers.size() + 1;
|
||||
_remotePeers.put(new Integer(_connectionId), Sender.this);
|
||||
}
|
||||
|
||||
_context.statManager().createRateStat("send." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("send." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("send." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
|
||||
byte msg[] = ("STREAM CONNECT ID=" + _connectionId + " DESTINATION=" + _remoteDestination + "\n").getBytes();
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.flush();
|
||||
}
|
||||
|
||||
_in = new FileInputStream(_dataFile);
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to connect", ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int getConnectionId() { return _connectionId; }
|
||||
public String getDestination() { return _remoteDestination; }
|
||||
|
||||
public void closed() {
|
||||
if (_closed) return;
|
||||
_closed = true;
|
||||
long lifetime = _context.clock().now() - _started;
|
||||
_context.statManager().addRateData("send." + _connectionId + ".lifetime", lifetime, lifetime);
|
||||
try { _in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_started = _context.clock().now();
|
||||
_context.statManager().addRateData("send." + _connectionId + ".started", 1, 0);
|
||||
byte data[] = new byte[1024];
|
||||
long value = 0;
|
||||
long lastSend = _context.clock().now();
|
||||
while (!_closed) {
|
||||
try {
|
||||
int read = _in.read(data);
|
||||
long now = _context.clock().now();
|
||||
if (read == -1) {
|
||||
_log.debug("EOF from the data for " + _connectionId + " after " + (now-lastSend));
|
||||
break;
|
||||
} else if (read > 0) {
|
||||
_log.debug("Sending " + read + " on " + _connectionId + " after " + (now-lastSend));
|
||||
lastSend = now;
|
||||
|
||||
byte msg[] = ("STREAM SEND ID=" + _connectionId + " SIZE=" + read + "\n").getBytes();
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.write(data, 0, read);
|
||||
_samOut.flush();
|
||||
}
|
||||
|
||||
_totalSent += read;
|
||||
_context.statManager().addRateData("send." + _connectionId + ".totalSent", _totalSent, 0);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error sending", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
byte msg[] = ("STREAM CLOSE ID=" + _connectionId + "\n").getBytes();
|
||||
try {
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.flush();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error closing", ioe);
|
||||
}
|
||||
|
||||
closed();
|
||||
}
|
||||
}
|
||||
}
|
||||
247
apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java
Normal file
247
apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java
Normal file
@@ -0,0 +1,247 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.File;
|
||||
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.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import net.i2p.sam.client.SAMEventHandler;
|
||||
import net.i2p.sam.client.SAMClientEventListenerImpl;
|
||||
import net.i2p.sam.client.SAMReader;
|
||||
|
||||
/**
|
||||
* Sit around on a SAM destination, receiving lots of data and
|
||||
* writing it to disk
|
||||
*
|
||||
* Usage: SAMStreamSink samHost samPort myKeyFile sinkDir
|
||||
*
|
||||
*/
|
||||
public class SAMStreamSink {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private String _samHost;
|
||||
private String _samPort;
|
||||
private String _destFile;
|
||||
private String _sinkDir;
|
||||
private String _conOptions;
|
||||
private Socket _samSocket;
|
||||
private OutputStream _samOut;
|
||||
private InputStream _samIn;
|
||||
private SAMReader _reader;
|
||||
private boolean _dead;
|
||||
private SAMEventHandler _eventHandler;
|
||||
/** Connection id (Integer) to peer (Flooder) */
|
||||
private Map _remotePeers;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 4) {
|
||||
System.err.println("Usage: SAMStreamSink samHost samPort myDestFile sinkDir");
|
||||
return;
|
||||
}
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
SAMStreamSink sink = new SAMStreamSink(ctx, args[0], args[1], args[2], args[3]);
|
||||
sink.startup();
|
||||
}
|
||||
|
||||
public SAMStreamSink(I2PAppContext ctx, String samHost, String samPort, String destFile, String sinkDir) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(SAMStreamSink.class);
|
||||
_dead = false;
|
||||
_samHost = samHost;
|
||||
_samPort = samPort;
|
||||
_destFile = destFile;
|
||||
_sinkDir = sinkDir;
|
||||
_conOptions = "";
|
||||
_eventHandler = new SinkEventHandler(_context);
|
||||
_remotePeers = new HashMap();
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
_log.debug("Starting up");
|
||||
boolean ok = connect();
|
||||
_log.debug("Connected: " + ok);
|
||||
if (ok) {
|
||||
_reader = new SAMReader(_context, _samIn, _eventHandler);
|
||||
_reader.startReading();
|
||||
_log.debug("Reader created");
|
||||
String ourDest = handshake();
|
||||
_log.debug("Handshake complete. we are " + ourDest);
|
||||
if (ourDest != null) {
|
||||
boolean written = writeDest(ourDest);
|
||||
_log.debug("Dest written");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SinkEventHandler extends SAMEventHandler {
|
||||
public SinkEventHandler(I2PAppContext ctx) { super(ctx); }
|
||||
public void streamClosedReceived(String result, int id, String message) {
|
||||
Sink sink = null;
|
||||
synchronized (_remotePeers) {
|
||||
sink = (Sink)_remotePeers.remove(new Integer(id));
|
||||
}
|
||||
if (sink != null) {
|
||||
sink.closed();
|
||||
_log.debug("Connection " + sink.getConnectionId() + " closed to " + sink.getDestination());
|
||||
} else {
|
||||
_log.error("wtf, not connected to " + id + " but we were just closed?");
|
||||
}
|
||||
}
|
||||
public void streamDataReceived(int id, byte data[], int offset, int length) {
|
||||
Sink sink = null;
|
||||
synchronized (_remotePeers) {
|
||||
sink = (Sink)_remotePeers.get(new Integer(id));
|
||||
}
|
||||
if (sink != null) {
|
||||
sink.received(data, offset, length);
|
||||
} else {
|
||||
_log.error("wtf, not connected to " + id + " but we received " + length + "?");
|
||||
}
|
||||
}
|
||||
public void streamConnectedReceived(String dest, int id) {
|
||||
_log.debug("Connection " + id + " received from " + dest);
|
||||
|
||||
try {
|
||||
Sink sink = new Sink(id, dest);
|
||||
synchronized (_remotePeers) {
|
||||
_remotePeers.put(new Integer(id), sink);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error creating a new sink", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
try {
|
||||
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
|
||||
_samOut = _samSocket.getOutputStream();
|
||||
_samIn = _samSocket.getInputStream();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String handshake() {
|
||||
synchronized (_samOut) {
|
||||
try {
|
||||
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Hello sent");
|
||||
boolean ok = _eventHandler.waitForHelloReply();
|
||||
_log.debug("Hello reply found: " + ok);
|
||||
if (!ok)
|
||||
throw new IOException("wtf, hello failed?");
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + _destFile + " " + _conOptions + "\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Session create sent");
|
||||
ok = _eventHandler.waitForSessionCreateReply();
|
||||
_log.debug("Session create reply found: " + ok);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Naming lookup sent");
|
||||
String destination = _eventHandler.waitForNamingReply("ME");
|
||||
_log.debug("Naming lookup reply found: " + destination);
|
||||
if (destination == null) {
|
||||
_log.error("No naming lookup reply found!");
|
||||
return null;
|
||||
} else {
|
||||
_log.info(_destFile + " is located at " + destination);
|
||||
}
|
||||
return destination;
|
||||
} catch (Exception e) {
|
||||
_log.error("Error handshaking", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean writeDest(String dest) {
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(_destFile);
|
||||
fos.write(dest.getBytes());
|
||||
fos.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
_log.error("Error writing to " + _destFile, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class Sink {
|
||||
private int _connectionId;
|
||||
private String _remoteDestination;
|
||||
private boolean _closed;
|
||||
private long _started;
|
||||
private long _totalReceived;
|
||||
private long _lastReceivedOn;
|
||||
private OutputStream _out;
|
||||
|
||||
public Sink(int conId, String remDest) throws IOException {
|
||||
_connectionId = conId;
|
||||
_remoteDestination = remDest;
|
||||
_closed = false;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
_context.statManager().createRateStat("sink." + conId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("sink." + conId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("sink." + conId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
|
||||
File sinkDir = new File(_sinkDir);
|
||||
if (!sinkDir.exists())
|
||||
sinkDir.mkdirs();
|
||||
|
||||
File out = File.createTempFile("sink", ".dat", sinkDir);
|
||||
_out = new FileOutputStream(out);
|
||||
}
|
||||
|
||||
public int getConnectionId() { return _connectionId; }
|
||||
public String getDestination() { return _remoteDestination; }
|
||||
|
||||
public void closed() {
|
||||
if (_closed) return;
|
||||
_closed = true;
|
||||
long lifetime = _context.clock().now() - _started;
|
||||
_context.statManager().addRateData("sink." + _connectionId + ".lifetime", lifetime, lifetime);
|
||||
try {
|
||||
_out.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error closing", ioe);
|
||||
}
|
||||
}
|
||||
public void received(byte data[], int offset, int len) {
|
||||
if (_closed) return;
|
||||
_totalReceived += len;
|
||||
try {
|
||||
_out.write(data, offset, len);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing received data");
|
||||
closed();
|
||||
return;
|
||||
}
|
||||
_log.debug("Received " + len + " on " + _connectionId + " after " + (_context.clock().now()-_lastReceivedOn)
|
||||
+ "ms with " + _remoteDestination.substring(0,6));
|
||||
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ public class TestStreamTransfer {
|
||||
_log.error("Incorrect size read - expected " + payloadSize + " got " + read);
|
||||
return;
|
||||
}
|
||||
_log.info("Received from the stream " + id + ": [" + new String(payload) + "]");
|
||||
_log.info("\n== Received from the stream " + id + ": [" + new String(payload) + "]");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
/*
|
||||
// now echo it back
|
||||
@@ -217,7 +217,12 @@ public class TestStreamTransfer {
|
||||
}
|
||||
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
|
||||
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
|
||||
_log.info("Sending data");
|
||||
_log.info("\n** Sending BlahBlah!!");
|
||||
out.write(req.getBytes());
|
||||
out.flush();
|
||||
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
|
||||
req = "STREAM SEND ID=42 SIZE=10\nFooBarBaz!";
|
||||
_log.info("\n** Sending FooBarBaz!");
|
||||
out.write(req.getBytes());
|
||||
out.flush();
|
||||
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}
|
||||
|
||||
312
apps/sam/java/test/net/i2p/sam/TestSwarm.java
Normal file
312
apps/sam/java/test/net/i2p/sam/TestSwarm.java
Normal file
@@ -0,0 +1,312 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
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.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import net.i2p.sam.client.SAMEventHandler;
|
||||
import net.i2p.sam.client.SAMClientEventListenerImpl;
|
||||
import net.i2p.sam.client.SAMReader;
|
||||
|
||||
/**
|
||||
* Sit around on a SAM destination, receiving lots of data and sending lots of
|
||||
* data to whomever talks to us.
|
||||
*
|
||||
* Usage: TestSwarm samHost samPort myKeyFile [peerDestFile ]*
|
||||
*
|
||||
*/
|
||||
public class TestSwarm {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private String _samHost;
|
||||
private String _samPort;
|
||||
private String _destFile;
|
||||
private String _peerDestFiles[];
|
||||
private String _conOptions;
|
||||
private Socket _samSocket;
|
||||
private OutputStream _samOut;
|
||||
private InputStream _samIn;
|
||||
private SAMReader _reader;
|
||||
private boolean _dead;
|
||||
private SAMEventHandler _eventHandler;
|
||||
/** Connection id (Integer) to peer (Flooder) */
|
||||
private Map _remotePeers;
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 3) {
|
||||
System.err.println("Usage: TestSwarm samHost samPort myDestFile [peerDestFile ]*");
|
||||
return;
|
||||
}
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
String files[] = new String[args.length - 3];
|
||||
System.arraycopy(args, 3, files, 0, files.length);
|
||||
TestSwarm swarm = new TestSwarm(ctx, args[0], args[1], args[2], files);
|
||||
swarm.startup();
|
||||
}
|
||||
|
||||
public TestSwarm(I2PAppContext ctx, String samHost, String samPort, String destFile, String peerDestFiles[]) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(TestSwarm.class);
|
||||
_dead = false;
|
||||
_samHost = samHost;
|
||||
_samPort = samPort;
|
||||
_destFile = destFile;
|
||||
_peerDestFiles = peerDestFiles;
|
||||
_conOptions = "";
|
||||
_eventHandler = new SwarmEventHandler(_context);
|
||||
_remotePeers = new HashMap();
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
_log.debug("Starting up");
|
||||
boolean ok = connect();
|
||||
_log.debug("Connected: " + ok);
|
||||
if (ok) {
|
||||
_reader = new SAMReader(_context, _samIn, _eventHandler);
|
||||
_reader.startReading();
|
||||
_log.debug("Reader created");
|
||||
String ourDest = handshake();
|
||||
_log.debug("Handshake complete. we are " + ourDest);
|
||||
if (ourDest != null) {
|
||||
boolean written = writeDest(ourDest);
|
||||
_log.debug("Dest written");
|
||||
if (written) {
|
||||
connectWithPeers();
|
||||
_log.debug("connected with peers");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SwarmEventHandler extends SAMEventHandler {
|
||||
public SwarmEventHandler(I2PAppContext ctx) { super(ctx); }
|
||||
public void streamClosedReceived(String result, int id, String message) {
|
||||
Flooder flooder = null;
|
||||
synchronized (_remotePeers) {
|
||||
flooder = (Flooder)_remotePeers.remove(new Integer(id));
|
||||
}
|
||||
if (flooder != null) {
|
||||
flooder.closed();
|
||||
_log.debug("Connection " + flooder.getConnectionId() + " closed to " + flooder.getDestination());
|
||||
} else {
|
||||
_log.error("wtf, not connected to " + id + " but we were just closed?");
|
||||
}
|
||||
}
|
||||
public void streamDataReceived(int id, byte data[], int offset, int length) {
|
||||
Flooder flooder = null;
|
||||
synchronized (_remotePeers) {
|
||||
flooder = (Flooder)_remotePeers.get(new Integer(id));
|
||||
}
|
||||
long value = DataHelper.fromLong(data, 0, 4);
|
||||
if (flooder != null) {
|
||||
flooder.received(length, value);
|
||||
} else {
|
||||
_log.error("wtf, not connected to " + id + " but we received " + value + "?");
|
||||
}
|
||||
}
|
||||
public void streamConnectedReceived(String dest, int id) {
|
||||
_log.debug("Connection " + id + " received from " + dest);
|
||||
|
||||
Flooder flooder = new Flooder(id, dest);
|
||||
synchronized (_remotePeers) {
|
||||
_remotePeers.put(new Integer(id), flooder);
|
||||
}
|
||||
I2PThread t = new I2PThread(flooder, "Flood " + id);
|
||||
t.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
try {
|
||||
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
|
||||
_samOut = _samSocket.getOutputStream();
|
||||
_samIn = _samSocket.getInputStream();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String handshake() {
|
||||
synchronized (_samOut) {
|
||||
try {
|
||||
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Hello sent");
|
||||
boolean ok = _eventHandler.waitForHelloReply();
|
||||
_log.debug("Hello reply found: " + ok);
|
||||
if (!ok)
|
||||
throw new IOException("wtf, hello failed?");
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + _destFile + " " + _conOptions + "\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Session create sent");
|
||||
ok = _eventHandler.waitForSessionCreateReply();
|
||||
_log.debug("Session create reply found: " + ok);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.flush();
|
||||
_log.debug("Naming lookup sent");
|
||||
String destination = _eventHandler.waitForNamingReply("ME");
|
||||
_log.debug("Naming lookup reply found: " + destination);
|
||||
if (destination == null) {
|
||||
_log.error("No naming lookup reply found!");
|
||||
return null;
|
||||
} else {
|
||||
_log.info(_destFile + " is located at " + destination);
|
||||
}
|
||||
return destination;
|
||||
} catch (Exception e) {
|
||||
_log.error("Error handshaking", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean writeDest(String dest) {
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(_destFile);
|
||||
fos.write(dest.getBytes());
|
||||
fos.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
_log.error("Error writing to " + _destFile, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void connectWithPeers() {
|
||||
if (_peerDestFiles != null) {
|
||||
for (int i = 0; i < _peerDestFiles.length; i++) {
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(_peerDestFiles[i]);
|
||||
byte dest[] = new byte[1024];
|
||||
int read = DataHelper.read(fin, dest);
|
||||
|
||||
String remDest = new String(dest, 0, read);
|
||||
int con = 0;
|
||||
Flooder flooder = null;
|
||||
synchronized (_remotePeers) {
|
||||
con = _remotePeers.size() + 1;
|
||||
flooder = new Flooder(con, remDest);
|
||||
_remotePeers.put(new Integer(con), flooder);
|
||||
}
|
||||
|
||||
byte msg[] = ("STREAM CONNECT ID=" + con + " DESTINATION=" + remDest + "\n").getBytes();
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.flush();
|
||||
}
|
||||
I2PThread flood = new I2PThread(flooder, "Flood " + con);
|
||||
flood.start();
|
||||
_log.debug("Starting flooder with peer from " + _peerDestFiles[i] + ": " + con);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to read the peer from " + _peerDestFiles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Flooder implements Runnable {
|
||||
private int _connectionId;
|
||||
private String _remoteDestination;
|
||||
private boolean _closed;
|
||||
private long _started;
|
||||
private long _totalSent;
|
||||
private long _totalReceived;
|
||||
private long _lastReceived;
|
||||
private long _lastReceivedOn;
|
||||
private boolean _outOfSync;
|
||||
|
||||
public Flooder(int conId, String remDest) {
|
||||
_connectionId = conId;
|
||||
_remoteDestination = remDest;
|
||||
_closed = false;
|
||||
_outOfSync = false;
|
||||
_lastReceived = -1;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
_context.statManager().createRateStat("swarm." + conId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + conId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + conId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
_context.statManager().createRateStat("swarm." + conId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
|
||||
}
|
||||
|
||||
public int getConnectionId() { return _connectionId; }
|
||||
public String getDestination() { return _remoteDestination; }
|
||||
|
||||
public void closed() {
|
||||
_closed = true;
|
||||
long lifetime = _context.clock().now() - _started;
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".lifetime", lifetime, lifetime);
|
||||
}
|
||||
public void run() {
|
||||
_started = _context.clock().now();
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".started", 1, 0);
|
||||
byte data[] = new byte[32*1024];
|
||||
long value = 0;
|
||||
long lastSend = _context.clock().now();
|
||||
while (!_closed) {
|
||||
byte msg[] = ("STREAM SEND ID=" + _connectionId + " SIZE=" + data.length + "\n").getBytes();
|
||||
DataHelper.toLong(data, 0, 4, value);
|
||||
try {
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.write(data);
|
||||
_samOut.flush();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error talking to SAM", ioe);
|
||||
return;
|
||||
}
|
||||
_totalSent += data.length;
|
||||
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
|
||||
value++;
|
||||
try { Thread.sleep(20); } catch (InterruptedException ie) {}
|
||||
long now = _context.clock().now();
|
||||
_log.debug("Sending " + value + " on " + _connectionId + " after " + (now-lastSend));
|
||||
lastSend = now;
|
||||
}
|
||||
}
|
||||
public void received(int len, long value) {
|
||||
_totalReceived += len;
|
||||
if ( (!_outOfSync) && (len % 32*1024 != 0) ) {
|
||||
_outOfSync = true;
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Out of sync (len=" + len + " after " + (_totalReceived-len) + ")");
|
||||
}
|
||||
_context.statManager().addRateData("swarm." + getConnectionId() + ".totalReceived", _totalReceived, 0);
|
||||
if (value != _lastReceived + 1) {
|
||||
if (!_outOfSync)
|
||||
_log.error("Received " + value + " when expecting " + (_lastReceived+1) + " on "
|
||||
+ _connectionId + " with " + _remoteDestination.substring(0,6));
|
||||
else
|
||||
_log.debug("(out of sync) Received " + value + " when expecting " + (_lastReceived+1) + " on "
|
||||
+ _connectionId + " with " + _remoteDestination.substring(0,6));
|
||||
} else {
|
||||
_log.debug("Received " + value + " on " + _connectionId + " after " + (_context.clock().now()-_lastReceivedOn)
|
||||
+ "ms with " + _remoteDestination.substring(0,6));
|
||||
}
|
||||
_lastReceived = value;
|
||||
_lastReceivedOn = _context.clock().now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,9 @@ public class Connection {
|
||||
private long _lastSendTime;
|
||||
private long _lastSendId;
|
||||
private boolean _resetReceived;
|
||||
private boolean _resetSent;
|
||||
private boolean _connected;
|
||||
private boolean _hardDisconnected;
|
||||
private MessageInputStream _inputStream;
|
||||
private MessageOutputStream _outputStream;
|
||||
private SchedulerChooser _chooser;
|
||||
@@ -112,6 +114,8 @@ public class Connection {
|
||||
_connectLock = new Object();
|
||||
_activeResends = 0;
|
||||
_context.statManager().createRateStat("stream.con.windowSizeAtCongestion", "How large was our send window when we send a dup?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.chokeSizeBegin", "How many messages were outstanding when we started to choke?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.chokeSizeEnd", "How many messages were outstanding when we stopped being choked?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
public long getNextOutboundPacketNum() {
|
||||
@@ -135,9 +139,14 @@ public class Connection {
|
||||
boolean packetSendChoke(long timeoutMs) {
|
||||
if (false) return true;
|
||||
long writeExpire = timeoutMs;
|
||||
long start = _context.clock().now();
|
||||
boolean started = false;
|
||||
while (true) {
|
||||
long timeLeft = writeExpire - _context.clock().now();
|
||||
synchronized (_outboundPackets) {
|
||||
if (!started)
|
||||
_context.statManager().addRateData("stream.chokeSizeBegin", _outboundPackets.size(), timeoutMs);
|
||||
started = true;
|
||||
if (_outboundPackets.size() >= _options.getWindowSize()) {
|
||||
if (writeExpire > 0) {
|
||||
if (timeLeft <= 0) {
|
||||
@@ -154,6 +163,7 @@ public class Connection {
|
||||
try { _outboundPackets.wait(); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} else {
|
||||
_context.statManager().addRateData("stream.chokeSizeEnd", _outboundPackets.size(), _context.clock().now() - start);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -163,6 +173,23 @@ public class Connection {
|
||||
void ackImmediately() {
|
||||
_receiver.send(null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* got a packet we shouldn't have, send 'em a reset
|
||||
*
|
||||
*/
|
||||
void sendReset() {
|
||||
_resetSent = true;
|
||||
if ( (_remotePeer == null) || (_sendStreamId == null) ) return;
|
||||
PacketLocal reply = new PacketLocal(_context, _remotePeer);
|
||||
reply.setFlag(Packet.FLAG_RESET);
|
||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
reply.setSendStreamId(_sendStreamId);
|
||||
reply.setReceiveStreamId(_receiveStreamId);
|
||||
reply.setOptionalFrom(_connectionManager.getSession().getMyDestination());
|
||||
// this just sends the packet - no retries or whatnot
|
||||
_outboundQueue.enqueue(reply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush any data that we can
|
||||
@@ -296,8 +323,8 @@ public class Connection {
|
||||
_ackedPackets++;
|
||||
if (p.getNumSends() > 1) {
|
||||
_activeResends--;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Active resend of " + p + " successful, # active left: " + _activeResends);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Active resend of " + p + " successful, # active left: " + _activeResends);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,14 +352,19 @@ public class Connection {
|
||||
_occurredEventCount++;
|
||||
} else {
|
||||
_occurredTime = now;
|
||||
if (_occurredEventCount > 100) {
|
||||
_log.log(Log.CRIT, "More than 100 events (" + _occurredEventCount + ") in a second on "
|
||||
+ toString() + ": scheduler = " + sched);
|
||||
if ( (_occurredEventCount > 1000) && (_log.shouldLog(Log.WARN)) ) {
|
||||
_log.warn("More than 1000 events (" + _occurredEventCount + ") in a second on "
|
||||
+ toString() + ": scheduler = " + sched);
|
||||
}
|
||||
_occurredEventCount = 0;
|
||||
}
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
|
||||
sched.eventOccurred(this);
|
||||
long elapsed = System.currentTimeMillis() - before;
|
||||
if ( (elapsed > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Took " + elapsed + "ms to pump through " + sched);
|
||||
}
|
||||
|
||||
void resetReceived() {
|
||||
@@ -349,6 +381,8 @@ public class Connection {
|
||||
public boolean getResetReceived() { return _resetReceived; }
|
||||
|
||||
public boolean getIsConnected() { return _connected; }
|
||||
public boolean getHardDisconnected() { return _hardDisconnected; }
|
||||
public boolean getResetSent() { return _resetSent; }
|
||||
|
||||
void disconnect(boolean cleanDisconnect) {
|
||||
disconnect(cleanDisconnect, true);
|
||||
@@ -358,6 +392,13 @@ public class Connection {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Disconnecting " + toString(), new Exception("discon"));
|
||||
|
||||
if (!cleanDisconnect) {
|
||||
_hardDisconnected = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Hard disconnecting and sending a reset on " + toString(), new Exception("cause"));
|
||||
sendReset();
|
||||
}
|
||||
|
||||
if (cleanDisconnect && _connected) {
|
||||
// send close packets and schedule stuff...
|
||||
_outputStream.closeInternal();
|
||||
@@ -381,15 +422,12 @@ public class Connection {
|
||||
if (_socket != null)
|
||||
_socket.destroy();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
if (_outputStream != null)
|
||||
_outputStream.destroy();
|
||||
_outputStream = null;
|
||||
_outboundQueue = null;
|
||||
if (_receiver != null)
|
||||
_receiver.destroy();
|
||||
if (_activityTimer != null)
|
||||
SimpleTimer.getInstance().addEvent(_activityTimer, 1);
|
||||
SimpleTimer.getInstance().removeEvent(_activityTimer);
|
||||
_activityTimer = null;
|
||||
|
||||
if (!_disconnectScheduled) {
|
||||
@@ -683,6 +721,9 @@ public class Connection {
|
||||
buf.append(" wsize: ").append(_options.getWindowSize());
|
||||
buf.append(" cwin: ").append(_congestionWindowEnd - _highestAckedThrough);
|
||||
buf.append(" rtt: ").append(_options.getRTT());
|
||||
// not synchronized to avoid some kooky races
|
||||
buf.append(" unacked outbound: ").append(_outboundPackets.size()).append(" ");
|
||||
/*
|
||||
buf.append(" unacked outbound: ");
|
||||
synchronized (_outboundPackets) {
|
||||
buf.append(_outboundPackets.size()).append(" [");
|
||||
@@ -691,6 +732,7 @@ public class Connection {
|
||||
}
|
||||
buf.append("] ");
|
||||
}
|
||||
*/
|
||||
buf.append("unacked inbound? ").append(getUnackedPacketsReceived());
|
||||
if (_inputStream != null) {
|
||||
buf.append(" [high ");
|
||||
@@ -714,6 +756,7 @@ public class Connection {
|
||||
public ResendPacketEvent(PacketLocal packet) {
|
||||
_packet = packet;
|
||||
_currentIsActiveResend = false;
|
||||
packet.setResendPacketEvent(ResendPacketEvent.this);
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
|
||||
@@ -17,13 +17,20 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Connection _connection;
|
||||
private MessageOutputStream.WriteStatus _dummyStatus;
|
||||
private static final MessageOutputStream.WriteStatus _dummyStatus = new DummyStatus();
|
||||
|
||||
public ConnectionDataReceiver(I2PAppContext ctx, Connection con) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(ConnectionDataReceiver.class);
|
||||
_connection = con;
|
||||
_dummyStatus = new DummyStatus();
|
||||
}
|
||||
|
||||
public boolean writeInProcess() {
|
||||
Connection con = _connection;
|
||||
if (con != null)
|
||||
return con.getUnackedPacketsSent() > 0;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,10 +45,12 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
* delivery.
|
||||
*/
|
||||
public MessageOutputStream.WriteStatus writeData(byte[] buf, int off, int size) {
|
||||
Connection con = _connection;
|
||||
if (con == null) return _dummyStatus;
|
||||
boolean doSend = true;
|
||||
if ( (size <= 0) && (_connection.getLastSendId() >= 0) ) {
|
||||
if (_connection.getOutputStream().getClosed()) {
|
||||
if (_connection.getCloseSentOn() < 0) {
|
||||
if ( (size <= 0) && (con.getLastSendId() >= 0) ) {
|
||||
if (con.getOutputStream().getClosed()) {
|
||||
if (con.getCloseSentOn() < 0) {
|
||||
doSend = true;
|
||||
} else {
|
||||
// closed, no new data, and we've already sent a close packet
|
||||
@@ -53,18 +62,20 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
if (_connection.getUnackedPacketsReceived() > 0)
|
||||
if (con.getUnackedPacketsReceived() > 0)
|
||||
doSend = true;
|
||||
|
||||
if (_log.shouldLog(Log.INFO) && !doSend)
|
||||
_log.info("writeData called: size="+size + " doSend=" + doSend
|
||||
+ " unackedReceived: " + _connection.getUnackedPacketsReceived()
|
||||
+ " con: " + _connection, new Exception("write called by"));
|
||||
+ " unackedReceived: " + con.getUnackedPacketsReceived()
|
||||
+ " con: " + con, new Exception("write called by"));
|
||||
|
||||
if (doSend) {
|
||||
PacketLocal packet = send(buf, off, size);
|
||||
if (packet == null) return _dummyStatus;
|
||||
|
||||
//dont wait for non-acks
|
||||
if ( (packet.getPayloadSize() > 0) || (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) )
|
||||
if ( (packet.getSequenceNum() > 0) || (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) )
|
||||
return packet;
|
||||
else
|
||||
return _dummyStatus;
|
||||
@@ -81,7 +92,7 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
* @param buf data to be sent - may be null
|
||||
* @param off offset into the buffer to start writing from
|
||||
* @param size how many bytes of the buffer to write (may be 0)
|
||||
* @return the packet sent
|
||||
* @return the packet sent, or null if the connection died
|
||||
*/
|
||||
public PacketLocal send(byte buf[], int off, int size) {
|
||||
return send(buf, off, size, false);
|
||||
@@ -95,23 +106,33 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
* @return the packet sent
|
||||
*/
|
||||
public PacketLocal send(byte buf[], int off, int size, boolean forceIncrement) {
|
||||
PacketLocal packet = buildPacket(buf, off, size, forceIncrement);
|
||||
_connection.sendPacket(packet);
|
||||
Connection con = _connection;
|
||||
if (con == null) return null;
|
||||
long before = System.currentTimeMillis();
|
||||
PacketLocal packet = buildPacket(con, buf, off, size, forceIncrement);
|
||||
long built = System.currentTimeMillis();
|
||||
con.sendPacket(packet);
|
||||
long sent = System.currentTimeMillis();
|
||||
|
||||
if ( (built-before > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("wtf, took " + (built-before) + "ms to build a packet: " + packet);
|
||||
if ( (sent-built> 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("wtf, took " + (sent-built) + "ms to send a packet: " + packet);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private boolean isAckOnly(int size) {
|
||||
private boolean isAckOnly(Connection con, int size) {
|
||||
boolean ackOnly = ( (size <= 0) && // no data
|
||||
(_connection.getLastSendId() >= 0) && // not a SYN
|
||||
( (!_connection.getOutputStream().getClosed()) || // not a CLOSE
|
||||
(_connection.getOutputStream().getClosed() &&
|
||||
_connection.getCloseSentOn() > 0) )); // or it is a dup CLOSE
|
||||
(con.getLastSendId() >= 0) && // not a SYN
|
||||
( (!con.getOutputStream().getClosed()) || // not a CLOSE
|
||||
(con.getOutputStream().getClosed() &&
|
||||
con.getCloseSentOn() > 0) )); // or it is a dup CLOSE
|
||||
return ackOnly;
|
||||
}
|
||||
|
||||
private PacketLocal buildPacket(byte buf[], int off, int size, boolean forceIncrement) {
|
||||
boolean ackOnly = isAckOnly(size);
|
||||
PacketLocal packet = new PacketLocal(_context, _connection.getRemotePeer(), _connection);
|
||||
private PacketLocal buildPacket(Connection con, byte buf[], int off, int size, boolean forceIncrement) {
|
||||
boolean ackOnly = isAckOnly(con, size);
|
||||
PacketLocal packet = new PacketLocal(_context, con.getRemotePeer(), con);
|
||||
byte data[] = new byte[size];
|
||||
if (size > 0)
|
||||
System.arraycopy(buf, off, data, 0, size);
|
||||
@@ -119,36 +140,36 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
if (ackOnly && !forceIncrement)
|
||||
packet.setSequenceNum(0);
|
||||
else
|
||||
packet.setSequenceNum(_connection.getNextOutboundPacketNum());
|
||||
packet.setSendStreamId(_connection.getSendStreamId());
|
||||
packet.setReceiveStreamId(_connection.getReceiveStreamId());
|
||||
packet.setSequenceNum(con.getNextOutboundPacketNum());
|
||||
packet.setSendStreamId(con.getSendStreamId());
|
||||
packet.setReceiveStreamId(con.getReceiveStreamId());
|
||||
|
||||
_connection.getInputStream().updateAcks(packet);
|
||||
packet.setOptionalDelay(_connection.getOptions().getChoke());
|
||||
packet.setOptionalMaxSize(_connection.getOptions().getMaxMessageSize());
|
||||
packet.setResendDelay(_connection.getOptions().getResendDelay());
|
||||
con.getInputStream().updateAcks(packet);
|
||||
packet.setOptionalDelay(con.getOptions().getChoke());
|
||||
packet.setOptionalMaxSize(con.getOptions().getMaxMessageSize());
|
||||
packet.setResendDelay(con.getOptions().getResendDelay());
|
||||
|
||||
if (_connection.getOptions().getProfile() == ConnectionOptions.PROFILE_INTERACTIVE)
|
||||
if (con.getOptions().getProfile() == ConnectionOptions.PROFILE_INTERACTIVE)
|
||||
packet.setFlag(Packet.FLAG_PROFILE_INTERACTIVE, true);
|
||||
else
|
||||
packet.setFlag(Packet.FLAG_PROFILE_INTERACTIVE, false);
|
||||
|
||||
packet.setFlag(Packet.FLAG_SIGNATURE_REQUESTED, _connection.getOptions().getRequireFullySigned());
|
||||
packet.setFlag(Packet.FLAG_SIGNATURE_REQUESTED, con.getOptions().getRequireFullySigned());
|
||||
|
||||
if ( (!ackOnly) && (packet.getSequenceNum() <= 0) ) {
|
||||
packet.setFlag(Packet.FLAG_SYNCHRONIZE);
|
||||
packet.setOptionalFrom(_connection.getSession().getMyDestination());
|
||||
packet.setOptionalFrom(con.getSession().getMyDestination());
|
||||
}
|
||||
|
||||
// don't set the closed flag if this is a plain ACK and there are outstanding
|
||||
// packets sent, otherwise the other side could receive the CLOSE prematurely,
|
||||
// since this ACK could arrive before the unacked payload message.
|
||||
if (_connection.getOutputStream().getClosed() &&
|
||||
( (size > 0) || (_connection.getUnackedPacketsSent() <= 0) ) ) {
|
||||
if (con.getOutputStream().getClosed() &&
|
||||
( (size > 0) || (con.getUnackedPacketsSent() <= 0) ) ) {
|
||||
packet.setFlag(Packet.FLAG_CLOSE);
|
||||
_connection.setCloseSentOn(_context.clock().now());
|
||||
con.setCloseSentOn(_context.clock().now());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closed is set for a new packet on " + _connection + ": " + packet);
|
||||
_log.debug("Closed is set for a new packet on " + con + ": " + packet);
|
||||
} else {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Closed is not set for a new packet on " + _connection + ": " + packet);
|
||||
|
||||
@@ -366,7 +366,8 @@ public class ConnectionManager {
|
||||
if (removed) {
|
||||
if (_notifier != null)
|
||||
_notifier.pingComplete(false);
|
||||
_log.error("Ping failed");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Ping failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,10 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 5));
|
||||
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
|
||||
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
|
||||
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_SEND));
|
||||
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_DISCONNECT));
|
||||
setInboundBufferSize((getMaxMessageSize() + 2) * Connection.MAX_WINDOW_SIZE);
|
||||
|
||||
setConnectTimeout(getInt(opts, PROP_CONNECT_TIMEOUT, Connection.DISCONNECT_TIMEOUT));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,17 +25,43 @@ public class ConnectionPacketHandler {
|
||||
_context.statManager().createRateStat("stream.con.receiveDuplicateSize", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.con.packetsAckedPerMessageReceived", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.sendsBeforeAck", "How many times a message was sent before it was ACKed?", "Stream", new long[] { 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.resetReceived", "How many messages had we sent successfully before receiving a RESET?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
/** distribute a packet to the connection specified */
|
||||
void receivePacket(Packet packet, Connection con) throws I2PException {
|
||||
boolean ok = verifyPacket(packet, con);
|
||||
if (!ok) return;
|
||||
if (!ok) {
|
||||
if ( (!packet.isFlagSet(Packet.FLAG_RESET)) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.error("Packet does NOT verify: " + packet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (con.getHardDisconnected()) {
|
||||
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ||
|
||||
(packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) || (packet.isFlagSet(Packet.FLAG_CLOSE)) ) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received a data packet after hard disconnect: " + packet + " on " + con);
|
||||
con.sendReset();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received a packet after hard disconnect, ignoring: " + packet + " on " + con);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
con.packetReceived();
|
||||
|
||||
if (con.getInputStream().getTotalQueuedSize() > con.getOptions().getInboundBufferSize()) {
|
||||
long ready = con.getInputStream().getHighestReadyBockId();
|
||||
int available = con.getOptions().getInboundBufferSize() - con.getInputStream().getTotalReadySize();
|
||||
int allowedBlocks = available/con.getOptions().getMaxMessageSize();
|
||||
if (packet.getSequenceNum() > ready + allowedBlocks) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Inbound buffer exceeded on connection " + con + ": dropping " + packet);
|
||||
_log.warn("Inbound buffer exceeded on connection " + con + " ("
|
||||
+ ready + "/"+ (ready+allowedBlocks) + "/" + available
|
||||
+ ": dropping " + packet);
|
||||
ack(con, packet.getAckThrough(), packet.getNacks(), null, false);
|
||||
con.getOptions().setChoke(5*1000);
|
||||
return;
|
||||
}
|
||||
@@ -55,6 +81,8 @@ public class ConnectionPacketHandler {
|
||||
if (packet.isFlagSet(Packet.FLAG_CLOSE) && packet.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED))
|
||||
con.closeReceived();
|
||||
|
||||
boolean fastAck = false;
|
||||
|
||||
if (isNew) {
|
||||
con.incrementUnackedPacketsReceived();
|
||||
con.incrementBytesReceived(packet.getPayloadSize());
|
||||
@@ -72,7 +100,8 @@ public class ConnectionPacketHandler {
|
||||
_log.debug("Scheduling ack in " + delay + "ms for received packet " + packet);
|
||||
}
|
||||
} else {
|
||||
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ) {
|
||||
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ||
|
||||
(packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) ) {
|
||||
_context.statManager().addRateData("stream.con.receiveDuplicateSize", packet.getPayloadSize(), 0);
|
||||
con.incrementDupMessagesReceived(1);
|
||||
|
||||
@@ -83,7 +112,8 @@ public class ConnectionPacketHandler {
|
||||
_log.warn("congestion.. dup " + packet);
|
||||
SimpleTimer.getInstance().addEvent(new AckDup(con), con.getOptions().getSendAckDelay());
|
||||
//con.incrementUnackedPacketsReceived();
|
||||
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
|
||||
//con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
|
||||
fastAck = true;
|
||||
} else {
|
||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||
//con.incrementUnackedPacketsReceived();
|
||||
@@ -95,8 +125,20 @@ public class ConnectionPacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fastAck = fastAck || ack(con, packet.getAckThrough(), packet.getNacks(), packet, isNew);
|
||||
con.eventOccurred();
|
||||
if (fastAck) {
|
||||
if (con.getLastSendTime() + 1000 < _context.clock().now()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fast ack for dup " + packet);
|
||||
con.ackImmediately();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ack(Connection con, long ackThrough, long nacks[], Packet packet, boolean isNew) {
|
||||
int numResends = 0;
|
||||
List acked = con.ackPackets(packet.getAckThrough(), packet.getNacks());
|
||||
List acked = con.ackPackets(ackThrough, nacks);
|
||||
if ( (acked != null) && (acked.size() > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(acked.size() + " of our packets acked with " + packet);
|
||||
@@ -130,18 +172,15 @@ public class ConnectionPacketHandler {
|
||||
_context.statManager().addRateData("stream.con.packetsAckedPerMessageReceived", acked.size(), highestRTT);
|
||||
}
|
||||
|
||||
boolean fastAck = adjustWindow(con, isNew, packet.getSequenceNum(), numResends, (acked != null ? acked.size() : 0));
|
||||
con.eventOccurred();
|
||||
if (fastAck) {
|
||||
if (con.getLastSendTime() + con.getOptions().getRTT() < _context.clock().now()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fast ack for dup " + packet);
|
||||
con.ackImmediately();
|
||||
}
|
||||
}
|
||||
if (packet != null)
|
||||
return adjustWindow(con, isNew, packet.getSequenceNum(), numResends, (acked != null ? acked.size() : 0));
|
||||
else
|
||||
return adjustWindow(con, false, -1, numResends, (acked != null ? acked.size() : 0));
|
||||
}
|
||||
|
||||
|
||||
private boolean adjustWindow(Connection con, boolean isNew, long sequenceNum, int numResends, int acked) {
|
||||
boolean congested = false;
|
||||
if ( (!isNew) && (sequenceNum > 0) ) {
|
||||
// dup real packet
|
||||
int oldSize = con.getOptions().getWindowSize();
|
||||
@@ -156,64 +195,38 @@ public class ConnectionPacketHandler {
|
||||
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
|
||||
+ ") for " + con);
|
||||
|
||||
return true;
|
||||
//} else if (numResends > 0) {
|
||||
// window sizes are shrunk on resend, not on ack
|
||||
} else {
|
||||
if (acked > 0) {
|
||||
long lowest = con.getHighestAckedThrough();
|
||||
if (lowest >= con.getCongestionWindowEnd()) {
|
||||
// new packet that ack'ed uncongested data, or an empty ack
|
||||
int newWindowSize = con.getOptions().getWindowSize();
|
||||
|
||||
if (numResends <= 0) {
|
||||
if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
|
||||
// congestion avoidance
|
||||
congested = true;
|
||||
}
|
||||
|
||||
long lowest = con.getHighestAckedThrough();
|
||||
if (lowest >= con.getCongestionWindowEnd()) {
|
||||
// new packet that ack'ed uncongested data, or an empty ack
|
||||
int newWindowSize = con.getOptions().getWindowSize();
|
||||
|
||||
// we can't use newWindowSize += 1/newWindowSize, since we're
|
||||
// integers, so lets use a random distribution instead
|
||||
int shouldIncrement = _context.random().nextInt(newWindowSize);
|
||||
if (shouldIncrement <= 0)
|
||||
newWindowSize += 1;
|
||||
} else {
|
||||
// slow start
|
||||
newWindowSize += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New window size " + newWindowSize + " congestionSeenAt: "
|
||||
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
|
||||
+ ") for " + con);
|
||||
con.getOptions().setWindowSize(newWindowSize);
|
||||
con.setCongestionWindowEnd(newWindowSize + lowest);
|
||||
if ( (!congested) && (acked > 0) && (numResends <= 0) ) {
|
||||
if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
|
||||
// congestion avoidance
|
||||
|
||||
// we can't use newWindowSize += 1/newWindowSize, since we're
|
||||
// integers, so lets use a random distribution instead
|
||||
int shouldIncrement = _context.random().nextInt(newWindowSize);
|
||||
if (shouldIncrement <= 0)
|
||||
newWindowSize += 1;
|
||||
} else {
|
||||
// slow start
|
||||
newWindowSize += 1;
|
||||
}
|
||||
} else {
|
||||
// received a message that doesn't contain a new ack
|
||||
|
||||
// ehh. cant do this, as we SACK and the acks may be
|
||||
// received out of order:
|
||||
// Alice: RECEIVE 2
|
||||
// Alice: SEND ack 2 nack 1
|
||||
// Alice: RECEIVE 1
|
||||
// Alice: SEND ack 2
|
||||
// Bob: RECEIVE ack 2
|
||||
// Bob: RECEIVE ack 2 nack 1 <-- NOT bad
|
||||
|
||||
/*
|
||||
if (con.getUnackedPacketsSent() > 0) {
|
||||
// peer got a dup
|
||||
int oldSize = con.getOptions().getWindowSize();
|
||||
oldSize >>>= 1;
|
||||
if (oldSize <= 0)
|
||||
oldSize = 1;
|
||||
con.getOptions().setWindowSize(oldSize);
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New window size " + newWindowSize + " congestionSeenAt: "
|
||||
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
|
||||
+ ") for " + con);
|
||||
con.getOptions().setWindowSize(newWindowSize);
|
||||
con.setCongestionWindowEnd(newWindowSize + lowest);
|
||||
}
|
||||
return false;
|
||||
|
||||
return congested;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,6 +284,8 @@ public class ConnectionPacketHandler {
|
||||
con.resetReceived();
|
||||
con.eventOccurred();
|
||||
|
||||
_context.statManager().addRateData("stream.resetReceived", con.getHighestAckedThrough(), con.getLifetime());
|
||||
|
||||
// no further processing
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ public class I2PSocketFull implements I2PSocket {
|
||||
destroy();
|
||||
}
|
||||
|
||||
Connection getConnection() { return _connection; }
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return _connection.getInputStream();
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ public class MessageInputStream extends InputStream {
|
||||
private List _readyDataBlocks;
|
||||
private int _readyDataBlockIndex;
|
||||
/** highest message ID used in the readyDataBlocks */
|
||||
private long _highestReadyBlockId;
|
||||
private volatile long _highestReadyBlockId;
|
||||
/** highest overall message ID */
|
||||
private long _highestBlockId;
|
||||
private volatile long _highestBlockId;
|
||||
/**
|
||||
* Message ID (Long) to ByteArray for blocks received
|
||||
* out of order when there are lower IDs not yet
|
||||
@@ -74,15 +74,13 @@ public class MessageInputStream extends InputStream {
|
||||
|
||||
/** What is the highest block ID we've completely received through? */
|
||||
public long getHighestReadyBockId() {
|
||||
synchronized (_dataLock) {
|
||||
return _highestReadyBlockId;
|
||||
}
|
||||
// not synchronized as it doesnt hurt to read a too-low value
|
||||
return _highestReadyBlockId;
|
||||
}
|
||||
|
||||
public long getHighestBlockId() {
|
||||
synchronized (_dataLock) {
|
||||
return _highestBlockId;
|
||||
}
|
||||
// not synchronized as it doesnt hurt to read a too-low value
|
||||
return _highestBlockId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,6 +392,21 @@ public class MessageInputStream extends InputStream {
|
||||
}
|
||||
}
|
||||
|
||||
public int getTotalReadySize() {
|
||||
synchronized (_dataLock) {
|
||||
if (_locallyClosed) return 0;
|
||||
int numBytes = 0;
|
||||
for (int i = 0; i < _readyDataBlocks.size(); i++) {
|
||||
ByteArray cur = (ByteArray)_readyDataBlocks.get(i);
|
||||
if (i == 0)
|
||||
numBytes += cur.getData().length - _readyDataBlockIndex;
|
||||
else
|
||||
numBytes += cur.getData().length;
|
||||
}
|
||||
return numBytes;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
synchronized (_dataLock) {
|
||||
_readyDataBlocks.clear();
|
||||
|
||||
@@ -47,7 +47,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
_written = 0;
|
||||
_closed = false;
|
||||
_writeTimeout = -1;
|
||||
_passiveFlushDelay = 5*1000;
|
||||
_passiveFlushDelay = 500;
|
||||
_flusher = new Flusher();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("MessageOutputStream created");
|
||||
@@ -83,8 +83,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
remaining = 0;
|
||||
_lastBuffered = _context.clock().now();
|
||||
if (_passiveFlushDelay > 0) {
|
||||
// if it is already enqueued, this just pushes it further out
|
||||
SimpleTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
|
||||
_flusher.enqueue();
|
||||
}
|
||||
} else {
|
||||
// buffer whatever we can fit then flush,
|
||||
@@ -115,9 +114,9 @@ public class MessageOutputStream extends OutputStream {
|
||||
ws.waitForAccept(_writeTimeout);
|
||||
if (!ws.writeAccepted()) {
|
||||
if (_writeTimeout > 0)
|
||||
throw new InterruptedIOException("Write not accepted within timeout");
|
||||
throw new InterruptedIOException("Write not accepted within timeout: " + ws);
|
||||
else
|
||||
throw new IOException("Write not accepted into the queue");
|
||||
throw new IOException("Write not accepted into the queue: " + ws);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -140,11 +139,42 @@ public class MessageOutputStream extends OutputStream {
|
||||
* period of inactivity
|
||||
*/
|
||||
private class Flusher implements SimpleTimer.TimedEvent {
|
||||
private boolean _enqueued;
|
||||
public void enqueue() {
|
||||
// no need to be overly worried about duplicates - it would just
|
||||
// push it further out
|
||||
if (!_enqueued) {
|
||||
SimpleTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Enqueueing the flusher for " + _passiveFlushDelay + "ms out");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("NOT enqueing the flusher");
|
||||
}
|
||||
_enqueued = true;
|
||||
}
|
||||
public void timeReached() {
|
||||
_enqueued = false;
|
||||
DataReceiver rec = _dataReceiver;
|
||||
long timeLeft = (_lastBuffered + _passiveFlushDelay - _context.clock().now());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("flusher time reached: left = " + timeLeft);
|
||||
if (timeLeft > 0)
|
||||
enqueue();
|
||||
else if ( (rec != null) && (rec.writeInProcess()) )
|
||||
enqueue(); // don't passive flush if there is a write being done (unacked outbound)
|
||||
else
|
||||
doFlush();
|
||||
}
|
||||
|
||||
private void doFlush() {
|
||||
boolean sent = false;
|
||||
WriteStatus ws = null;
|
||||
synchronized (_dataLock) {
|
||||
if ( (_valid > 0) && (_lastBuffered + _passiveFlushDelay > _context.clock().now()) ) {
|
||||
long flushTime = _lastBuffered + _passiveFlushDelay;
|
||||
if ( (_valid > 0) && (flushTime < _context.clock().now()) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("doFlush() valid = " + _valid);
|
||||
if ( (_buf != null) && (_dataReceiver != null) ) {
|
||||
ws = _dataReceiver.writeData(_buf, 0, _valid);
|
||||
_written += _valid;
|
||||
@@ -153,13 +183,15 @@ public class MessageOutputStream extends OutputStream {
|
||||
_dataLock.notifyAll();
|
||||
sent = true;
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("doFlush() rejected... valid = " + _valid);
|
||||
}
|
||||
}
|
||||
// ignore the ws
|
||||
if (sent && _log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Passive flush of " + ws);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,6 +307,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
}
|
||||
void flushAvailable(DataReceiver target, boolean blocking) throws IOException {
|
||||
WriteStatus ws = null;
|
||||
long before = System.currentTimeMillis();
|
||||
synchronized (_dataLock) {
|
||||
// _buf may be null, but the data receiver can handle that just fine,
|
||||
// deciding whether or not to send a packet
|
||||
@@ -284,6 +317,10 @@ public class MessageOutputStream extends OutputStream {
|
||||
_dataLock.notifyAll();
|
||||
_lastFlushed = _context.clock().now();
|
||||
}
|
||||
long afterBuild = System.currentTimeMillis();
|
||||
if ( (afterBuild - before > 1000) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Took " + (afterBuild-before) + "ms to build a packet? " + ws);
|
||||
|
||||
if (blocking && ws != null) {
|
||||
ws.waitForAccept(_writeTimeout);
|
||||
if (ws.writeFailed())
|
||||
@@ -291,6 +328,9 @@ public class MessageOutputStream extends OutputStream {
|
||||
else if (!ws.writeAccepted())
|
||||
throw new InterruptedIOException("Flush available timed out");
|
||||
}
|
||||
long afterAccept = System.currentTimeMillis();
|
||||
if ( (afterAccept - afterBuild > 1000) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Took " + (afterAccept-afterBuild) + "ms to accept a packet? " + ws);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -304,6 +344,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
* Nonblocking write
|
||||
*/
|
||||
public WriteStatus writeData(byte buf[], int off, int size);
|
||||
public boolean writeInProcess();
|
||||
}
|
||||
|
||||
/** Define a way to detect the status of a write */
|
||||
|
||||
@@ -104,7 +104,7 @@ public class PacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("hh:mm:ss.SSS");
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss.SSS");
|
||||
void displayPacket(Packet packet, String prefix) {
|
||||
String msg = null;
|
||||
synchronized (_fmt) {
|
||||
@@ -152,16 +152,28 @@ public class PacketHandler {
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Receive a syn packet with the wrong IDs: " + packet);
|
||||
_log.warn("Receive a syn packet with the wrong IDs, sending reset: " + packet);
|
||||
sendReset(packet);
|
||||
}
|
||||
} else {
|
||||
// someone is sending us a packet on the wrong stream
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received a packet on the wrong stream: " + packet);
|
||||
_log.warn("Received a packet on the wrong stream: " + packet + " connection: " + con);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendReset(Packet packet) {
|
||||
PacketLocal reply = new PacketLocal(_context, packet.getOptionalFrom());
|
||||
reply.setFlag(Packet.FLAG_RESET);
|
||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
reply.setSendStreamId(packet.getReceiveStreamId());
|
||||
reply.setReceiveStreamId(packet.getSendStreamId());
|
||||
reply.setOptionalFrom(_manager.getSession().getMyDestination());
|
||||
// this just sends the packet - no retries or whatnot
|
||||
_manager.getPacketQueue().enqueue(reply);
|
||||
}
|
||||
|
||||
private void receiveUnknownCon(Packet packet, byte sendId[]) {
|
||||
if (packet.isFlagSet(Packet.FLAG_ECHO)) {
|
||||
if (packet.getSendStreamId() != null) {
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.Set;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
* coordinate local attributes about a packet - send time, ack time, number of
|
||||
@@ -12,6 +14,7 @@ import net.i2p.data.SessionKey;
|
||||
*/
|
||||
public class PacketLocal extends Packet implements MessageOutputStream.WriteStatus {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Connection _connection;
|
||||
private Destination _to;
|
||||
private SessionKey _keyUsed;
|
||||
@@ -22,6 +25,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
private long _acceptedOn;
|
||||
private long _ackOn;
|
||||
private long _cancelledOn;
|
||||
private SimpleTimer.TimedEvent _resendEvent;
|
||||
|
||||
public PacketLocal(I2PAppContext ctx, Destination to) {
|
||||
this(ctx, to, null);
|
||||
@@ -29,6 +33,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
public PacketLocal(I2PAppContext ctx, Destination to, Connection con) {
|
||||
_context = ctx;
|
||||
_createdOn = ctx.clock().now();
|
||||
_log = ctx.logManager().getLog(PacketLocal.class);
|
||||
_to = to;
|
||||
_connection = con;
|
||||
_lastSend = -1;
|
||||
@@ -78,14 +83,16 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
_ackOn = _context.clock().now();
|
||||
notifyAll();
|
||||
}
|
||||
_connection = null;
|
||||
SimpleTimer.getInstance().removeEvent(_resendEvent);
|
||||
}
|
||||
public void cancelled() {
|
||||
synchronized (this) {
|
||||
_cancelledOn = _context.clock().now();
|
||||
notifyAll();
|
||||
}
|
||||
_connection = null;
|
||||
SimpleTimer.getInstance().removeEvent(_resendEvent);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Cancelled! " + toString(), new Exception("cancelled"));
|
||||
}
|
||||
|
||||
/** how long after packet creation was it acked? */
|
||||
@@ -99,6 +106,8 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
public long getLastSend() { return _lastSend; }
|
||||
public Connection getConnection() { return _connection; }
|
||||
|
||||
public void setResendPacketEvent(SimpleTimer.TimedEvent evt) { _resendEvent = evt; }
|
||||
|
||||
public String toString() {
|
||||
String str = super.toString();
|
||||
if (_ackOn > 0)
|
||||
@@ -110,17 +119,27 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
public void waitForAccept(int maxWaitMs) {
|
||||
if (_connection == null)
|
||||
throw new IllegalStateException("Cannot wait for accept with no connection");
|
||||
long expiration = _context.clock().now()+maxWaitMs;
|
||||
long before = _context.clock().now();
|
||||
long expiration = before+maxWaitMs;
|
||||
int queued = _connection.getUnackedPacketsSent();
|
||||
int window = _connection.getOptions().getWindowSize();
|
||||
boolean accepted = _connection.packetSendChoke(maxWaitMs);
|
||||
long after = _context.clock().now();
|
||||
if (accepted)
|
||||
_acceptedOn = _context.clock().now();
|
||||
_acceptedOn = after;
|
||||
else
|
||||
_acceptedOn = -1;
|
||||
_connection = null;
|
||||
int afterQueued = _connection.getUnackedPacketsSent();
|
||||
if ( (after - before > 1000) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Took " + (after-before) + "ms to get "
|
||||
+ (accepted ? " accepted" : " rejected")
|
||||
+ (_cancelledOn > 0 ? " and CANCELLED" : "")
|
||||
+ ", queued behind " + queued +" with a window size of " + window
|
||||
+ ", finally accepted with " + afterQueued + " queued: "
|
||||
+ toString());
|
||||
}
|
||||
|
||||
public void waitForCompletion(int maxWaitMs) {
|
||||
_connection = null;
|
||||
long expiration = _context.clock().now()+maxWaitMs;
|
||||
while (true) {
|
||||
long timeRemaining = expiration - _context.clock().now();
|
||||
|
||||
@@ -24,24 +24,17 @@ class PacketQueue {
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private ConnectionManager _connectionManager;
|
||||
private byte _buf[];
|
||||
private ByteCache _cache = ByteCache.getInstance(64, 36*1024);
|
||||
|
||||
public PacketQueue(I2PAppContext context, I2PSession session, ConnectionManager mgr) {
|
||||
_context = context;
|
||||
_session = session;
|
||||
_connectionManager = mgr;
|
||||
_buf = _cache.acquire().getData(); // new byte[36*1024];
|
||||
_log = context.logManager().getLog(PacketQueue.class);
|
||||
_context.statManager().createRateStat("stream.con.sendMessageSize", "Size of a message sent on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.con.sendDuplicateSize", "Size of a message resent on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
protected void finalize() throws Throwable {
|
||||
_cache.release(new ByteArray(_buf));
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new packet to be sent out ASAP
|
||||
*/
|
||||
@@ -53,7 +46,7 @@ class PacketQueue {
|
||||
keyUsed = new SessionKey();
|
||||
Set tagsSent = packet.getTagsSent();
|
||||
if (tagsSent == null)
|
||||
tagsSent = new HashSet();
|
||||
tagsSent = new HashSet(0);
|
||||
|
||||
// cache this from before sendMessage
|
||||
String conStr = (packet.getConnection() != null ? packet.getConnection().toString() : "");
|
||||
@@ -63,29 +56,36 @@ class PacketQueue {
|
||||
} else {
|
||||
_log.debug("Sending... " + packet);
|
||||
}
|
||||
|
||||
ByteArray ba = _cache.acquire();
|
||||
byte buf[] = ba.getData();
|
||||
|
||||
long begin = 0;
|
||||
long end = 0;
|
||||
boolean sent = false;
|
||||
try {
|
||||
int size = 0;
|
||||
synchronized (this) {
|
||||
Arrays.fill(_buf, (byte)0x0);
|
||||
if (packet.shouldSign())
|
||||
size = packet.writeSignedPacket(_buf, 0, _context, _session.getPrivateKey());
|
||||
else
|
||||
size = packet.writePacket(_buf, 0);
|
||||
long beforeWrite = System.currentTimeMillis();
|
||||
if (packet.shouldSign())
|
||||
size = packet.writeSignedPacket(buf, 0, _context, _session.getPrivateKey());
|
||||
else
|
||||
size = packet.writePacket(buf, 0);
|
||||
long writeTime = System.currentTimeMillis() - beforeWrite;
|
||||
if ( (writeTime > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("took " + writeTime + "ms to write the packet: " + packet);
|
||||
|
||||
// this should not block!
|
||||
begin = _context.clock().now();
|
||||
sent = _session.sendMessage(packet.getTo(), _buf, 0, size, keyUsed, tagsSent);
|
||||
end = _context.clock().now();
|
||||
}
|
||||
// this should not block!
|
||||
begin = _context.clock().now();
|
||||
sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent);
|
||||
end = _context.clock().now();
|
||||
|
||||
if ( (end-begin > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Took " + (end-begin) + "ms to sendMessage(...) " + packet);
|
||||
|
||||
_context.statManager().addRateData("stream.con.sendMessageSize", size, packet.getLifetime());
|
||||
if (packet.getNumSends() > 1)
|
||||
_context.statManager().addRateData("stream.con.sendDuplicateSize", size, packet.getLifetime());
|
||||
|
||||
|
||||
Connection con = packet.getConnection();
|
||||
if (con != null) {
|
||||
con.incrementBytesSent(size);
|
||||
@@ -97,6 +97,8 @@ class PacketQueue {
|
||||
_log.warn("Unable to send the packet " + packet, ise);
|
||||
}
|
||||
|
||||
_cache.release(ba);
|
||||
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Send failed for " + packet);
|
||||
|
||||
@@ -38,6 +38,7 @@ class SchedulerChooser {
|
||||
|
||||
private List createSchedulers() {
|
||||
List rv = new ArrayList(8);
|
||||
rv.add(new SchedulerHardDisconnected(_context));
|
||||
rv.add(new SchedulerPreconnect(_context));
|
||||
rv.add(new SchedulerConnecting(_context));
|
||||
rv.add(new SchedulerReceived(_context));
|
||||
@@ -54,8 +55,7 @@ class SchedulerChooser {
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Event occurred on " + con, new Exception("source"));
|
||||
_log.log(Log.CRIT, "Yell at jrandom: Event occurred on " + con, new Exception("source"));
|
||||
}
|
||||
public boolean accept(Connection con) { return true; }
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ class SchedulerConnecting extends SchedulerImpl {
|
||||
public boolean accept(Connection con) {
|
||||
if (con == null) return false;
|
||||
boolean notYetConnected = (con.getIsConnected()) &&
|
||||
(con.getSendStreamId() == null) &&
|
||||
//(con.getSendStreamId() == null) && // not null on recv
|
||||
(con.getLastSendId() >= 0) &&
|
||||
(con.getAckedPackets() <= 0) &&
|
||||
(!con.getResetReceived());
|
||||
@@ -55,6 +55,7 @@ class SchedulerConnecting extends SchedulerImpl {
|
||||
_log.debug("waited too long: " + waited);
|
||||
return;
|
||||
} else {
|
||||
// should we be doing a con.sendAvailable here?
|
||||
if (con.getOptions().getConnectTimeout() > 0)
|
||||
reschedule(con.getOptions().getConnectTimeout(), con);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Scheduler used after we've locally done a hard disconnect,
|
||||
* but the final timeout hasn't passed.</p>
|
||||
*
|
||||
* <h2>Entry conditions:</h2><ul>
|
||||
* <li>Locally disconnected hard.</li>
|
||||
* <li>Less than the final timeout period has passed since the last ACK.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Events:</h2><ul>
|
||||
* <li>Packets received</li>
|
||||
* <li>RESET received</li>
|
||||
* <li>Message sending fails (error talking to the session)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Next states:</h2>
|
||||
* <li>{@link SchedulerDead dead} - after the final timeout passes</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
*/
|
||||
class SchedulerHardDisconnected extends SchedulerImpl {
|
||||
private Log _log;
|
||||
public SchedulerHardDisconnected(I2PAppContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(SchedulerHardDisconnected.class);
|
||||
}
|
||||
|
||||
public boolean accept(Connection con) {
|
||||
if (con == null) return false;
|
||||
long timeSinceClose = _context.clock().now() - con.getCloseSentOn();
|
||||
boolean ok = (con.getHardDisconnected() || con.getResetSent()) &&
|
||||
(timeSinceClose < Connection.DISCONNECT_TIMEOUT);
|
||||
return ok;
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
// noop. we do the timeout through the simpleTimer anyway
|
||||
}
|
||||
}
|
||||
@@ -32,5 +32,6 @@ abstract class SchedulerImpl implements TaskScheduler {
|
||||
// _log.debug("firing event on " + _connection, _addedBy);
|
||||
_connection.eventOccurred();
|
||||
}
|
||||
public String toString() { return "event on " + _connection; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Have a client connect to a server, where the server waits 5
|
||||
* seconds and closes the socket and the client detect that
|
||||
* EOF.
|
||||
*
|
||||
*/
|
||||
public class ConnectCloseTest {
|
||||
private Log _log;
|
||||
private I2PSession _server;
|
||||
public void test() {
|
||||
try {
|
||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
||||
_log = context.logManager().getLog(ConnectCloseTest.class);
|
||||
_log.debug("creating server session");
|
||||
_server = createSession();
|
||||
_log.debug("running server");
|
||||
runServer(context, _server);
|
||||
_log.debug("running client");
|
||||
runClient(context, createSession());
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
try { Thread.sleep(10*60*1000); } catch (Exception e) {}
|
||||
}
|
||||
|
||||
private void runClient(I2PAppContext ctx, I2PSession session) {
|
||||
Thread t = new Thread(new ClientRunner(ctx, session));
|
||||
t.setName("client");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void runServer(I2PAppContext ctx, I2PSession session) {
|
||||
Thread t = new Thread(new ServerRunner(ctx, session));
|
||||
t.setName("server");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private class ServerRunner implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private I2PSession _session;
|
||||
private Log _log;
|
||||
public ServerRunner(I2PAppContext ctx, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(ServerRunner.class);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Properties opts = new Properties();
|
||||
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
|
||||
_log.debug("* manager created");
|
||||
I2PServerSocket ssocket = mgr.getServerSocket();
|
||||
_log.debug("* server socket created");
|
||||
while (true) {
|
||||
I2PSocket socket = ssocket.accept();
|
||||
_log.debug("* socket accepted: " + socket);
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
socket.close();
|
||||
_log.debug("* socket closed: " + socket);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ClientRunner implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private I2PSession _session;
|
||||
private Log _log;
|
||||
public ClientRunner(I2PAppContext ctx, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(ClientRunner.class);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Properties opts = new Properties();
|
||||
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
|
||||
_log.debug("* manager created");
|
||||
I2PSocket socket = mgr.connect(_server.getMyDestination());
|
||||
_log.debug("* socket created");
|
||||
InputStream in = socket.getInputStream();
|
||||
int c = in.read();
|
||||
if (c != -1)
|
||||
throw new RuntimeException("hrm, we got data? [" + c + "]");
|
||||
socket.close();
|
||||
_log.debug("* socket closed");
|
||||
mgr.destroySocketManager();
|
||||
mgr = null;
|
||||
socket = null;
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private I2PSession createSession() {
|
||||
try {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
Destination dest = client.createDestination(baos);
|
||||
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), System.getProperties());
|
||||
sess.connect();
|
||||
return sess;
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
throw new RuntimeException("b0rk b0rk b0rk");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
ConnectCloseTest ct = new ConnectCloseTest();
|
||||
ct.test();
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ public class MessageOutputStreamTest {
|
||||
_data.write(buf, off, size);
|
||||
return new DummyWriteStatus();
|
||||
}
|
||||
public boolean writeInProcess() { return false; }
|
||||
public byte[] getData() { return _data.toByteArray(); }
|
||||
}
|
||||
|
||||
|
||||
@@ -239,8 +239,6 @@
|
||||
<copy file="build/routerconsole.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/i2ptunnel.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/routerconsole.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="installer/resources/wrapper.config" todir="pkg-temp/" />
|
||||
<copy file="installer/resources/wrapper.config" tofile="pkg-temp/wrapper.config.updated" />
|
||||
<copy file="history.txt" todir="pkg-temp/" />
|
||||
<copy file="hosts.txt" todir="pkg-temp/" />
|
||||
<mkdir dir="pkg-temp/eepsite" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Prior to building the jbigi library, you will need to fetch the GMP source
|
||||
from http://www.swox.com/gmp/, saving it to jbigi/gmp-4.1.3.tar.bz2 (it will
|
||||
from http://www.swox.com/gmp/, saving it to jbigi/gmp-4.1.4.tar.bz2 (it will
|
||||
be unpacked and built as necessary).
|
||||
|
||||
To build the native jbigi and jcpuid libraries for the current host CPU,
|
||||
|
||||
@@ -7,7 +7,6 @@ mkdir -p t/freenet/support/CPUInformation/
|
||||
cp jcpuid/lib/freenet/support/CPUInformation/*jcpuid* t/freenet/support/CPUInformation/
|
||||
|
||||
mkdir -p t/net/i2p/util/
|
||||
cp jbigi/lib/net/i2p/util/*jbigi* t/net/i2p/util/
|
||||
cp jbigi/lib/*jbigi* t/
|
||||
|
||||
(cd t ; jar cf ../jbigi.jar . ; cd ..)
|
||||
|
||||
@@ -13,7 +13,7 @@ FreeBSD*)
|
||||
esac
|
||||
|
||||
echo "Extracting GMP..."
|
||||
tar -xjf gmp-4.1.3.tar.bz2
|
||||
tar -xjf gmp-4.1.4.tar.bz2
|
||||
echo "Building..."
|
||||
mkdir bin
|
||||
mkdir lib
|
||||
@@ -24,7 +24,7 @@ for x in none pentium pentiummmx pentium2 pentium3 pentium4 k6 k62 k63 athlon
|
||||
do
|
||||
mkdir bin/$x
|
||||
cd bin/$x
|
||||
../../gmp-4.1.3/configure --build=$x
|
||||
../../gmp-4.1.4/configure --build=$x
|
||||
make
|
||||
sh ../../build_jbigi.sh static
|
||||
case `uname -sr` in
|
||||
|
||||
@@ -1,41 +1,15 @@
|
||||
#/bin/sh
|
||||
|
||||
case `uname -sr` in
|
||||
MINGW*)
|
||||
echo "Building windows .dll's";;
|
||||
CYGWIN*)
|
||||
echo "Building windows .dll's";;
|
||||
Linux*)
|
||||
echo "Building linux .so's";;
|
||||
FreeBSD*)
|
||||
echo "Building freebsd .so's";;
|
||||
*)
|
||||
echo "Unsupported build environment"
|
||||
exit;;
|
||||
esac
|
||||
echo "Building the jbigi library with GMP"
|
||||
|
||||
echo "Extracting GMP..."
|
||||
tar -xjf gmp-4.1.3.tar.bz2
|
||||
tar -xjf gmp-4.1.4.tar.bz2
|
||||
echo "Building..."
|
||||
mkdir bin
|
||||
mkdir lib
|
||||
mkdir lib/net
|
||||
mkdir lib/net/i2p
|
||||
mkdir lib/net/i2p/util
|
||||
mkdir bin/local
|
||||
mkdir -p lib/
|
||||
mkdir -p bin/local
|
||||
cd bin/local
|
||||
../../gmp-4.1.3/configure
|
||||
../../gmp-4.1.4/configure
|
||||
make
|
||||
sh ../../build_jbigi.sh static
|
||||
case `uname -sr` in
|
||||
MINGW*)
|
||||
cp jbigi.dll ../../lib/jbigi;;
|
||||
CYGWIN*)
|
||||
cp jbigi.dll ../../lib/jbigi;;
|
||||
Linux*)
|
||||
cp libjbigi.so ../../lib/jbigi;;
|
||||
FreeBSD*)
|
||||
cp libjbigi.so ../../lib/jbigi;;
|
||||
esac
|
||||
cd ..
|
||||
cd ..
|
||||
cp *jbigi???* ../../lib/
|
||||
cd ../..
|
||||
|
||||
@@ -20,7 +20,7 @@ mkdir lib/freenet
|
||||
mkdir lib/freenet/support
|
||||
mkdir lib/freenet/support/CPUInformation
|
||||
|
||||
CPP="g++"
|
||||
CC="gcc"
|
||||
|
||||
case `uname -sr` in
|
||||
MINGW*)
|
||||
@@ -43,7 +43,7 @@ esac
|
||||
|
||||
echo "Compiling C code..."
|
||||
rm -f $LIBFILE
|
||||
$CPP $LINKFLAGS $INCLUDES src/*.cpp -o $LIBFILE
|
||||
$CC $LINKFLAGS $INCLUDES src/*.c -o $LIBFILE
|
||||
strip $LIBFILE
|
||||
echo Built $LIBFILE
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ JNIEXPORT jobject JNICALL Java_freenet_support_CPUInformation_CPUID_doCPUID
|
||||
(JNIEnv * env, jclass cls, jint iFunction)
|
||||
{
|
||||
int a,b,c,d;
|
||||
jclass clsResult = env->FindClass ("freenet/support/CPUInformation/CPUID$CPUIDResult");
|
||||
jmethodID constructor = env->GetMethodID(clsResult,"<init>","(IIII)V" );
|
||||
jclass clsResult = (*env)->FindClass(env, "freenet/support/CPUInformation/CPUID$CPUIDResult");
|
||||
jmethodID constructor = (*env)->GetMethodID(env, clsResult,"<init>","(IIII)V" );
|
||||
#ifdef _MSC_VER
|
||||
//Use MSVC assembler notation
|
||||
_asm
|
||||
@@ -30,6 +30,6 @@ JNIEXPORT jobject JNICALL Java_freenet_support_CPUInformation_CPUID_doCPUID
|
||||
:"a"(iFunction)
|
||||
);
|
||||
#endif
|
||||
return env->NewObject(clsResult,constructor,a,b,c,d);
|
||||
return (*env)->NewObject(env, clsResult,constructor,a,b,c,d);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ class I2CPMessageProducer {
|
||||
*
|
||||
*/
|
||||
public void disconnect(I2PSessionImpl session) throws I2PSessionException {
|
||||
if (session.isClosed()) return;
|
||||
DestroySessionMessage dmsg = new DestroySessionMessage();
|
||||
dmsg.setSessionId(session.getSessionId());
|
||||
session.sendMessage(dmsg);
|
||||
|
||||
@@ -108,6 +108,8 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
|
||||
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent)
|
||||
throws I2PSessionException {
|
||||
long begin = _context.clock().now();
|
||||
|
||||
SessionKey key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
|
||||
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
|
||||
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
|
||||
@@ -180,9 +182,17 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
+ " sync took " + (inSendingSync-beforeSendingSync)
|
||||
+ " add took " + (afterSendingSync-inSendingSync));
|
||||
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
|
||||
|
||||
// since this is 'best effort', all we're waiting for is a status update
|
||||
// saying that the router received it - in theory, that should come back
|
||||
// immediately, but in practice can take up to a second (though usually
|
||||
// much quicker). setting this to false will short-circuit that delay
|
||||
boolean actuallyWait = true;
|
||||
|
||||
long beforeWaitFor = _context.clock().now();
|
||||
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED,
|
||||
_context.clock().now() + getTimeout());
|
||||
if (actuallyWait)
|
||||
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED,
|
||||
_context.clock().now() + getTimeout());
|
||||
long afterWaitFor = _context.clock().now();
|
||||
long inRemovingSync = 0;
|
||||
synchronized (_sendingStates) {
|
||||
@@ -190,7 +200,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
_sendingStates.remove(state);
|
||||
}
|
||||
long afterRemovingSync = _context.clock().now();
|
||||
boolean found = state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
|
||||
boolean found = !actuallyWait || state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix() + "After waitFor sending state " + state.getMessageId()
|
||||
+ " / " + state.getNonce() + " found = " + found);
|
||||
@@ -200,6 +210,13 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
_log.warn("wtf, took " + timeToSend + "ms to send the message?!", new Exception("baz"));
|
||||
}
|
||||
|
||||
if ( (afterRemovingSync - begin > 500) && (_log.shouldLog(Log.WARN) ) ) {
|
||||
_log.warn("Took " + (afterRemovingSync-begin) + "ms to sendBestEffort, "
|
||||
+ (afterSendingSync-begin) + "ms to prepare, "
|
||||
+ (beforeWaitFor-afterSendingSync) + "ms to send, "
|
||||
+ (afterRemovingSync-beforeWaitFor) + "ms waiting for reply");
|
||||
}
|
||||
|
||||
if (found) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix() + "Message sent after " + state.getElapsed() + "ms with "
|
||||
|
||||
@@ -16,6 +16,7 @@ import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2cp.MessagePayloadMessage;
|
||||
import net.i2p.data.i2cp.ReceiveMessageEndMessage;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handle I2CP MessagePayloadMessages from the router delivering the contents
|
||||
@@ -30,7 +31,8 @@ class MessagePayloadMessageHandler extends HandlerImpl {
|
||||
}
|
||||
|
||||
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
|
||||
_log.debug("Handle message " + message);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Handle message " + message);
|
||||
try {
|
||||
MessagePayloadMessage msg = (MessagePayloadMessage) message;
|
||||
MessageId id = msg.getMessageId();
|
||||
@@ -55,9 +57,8 @@ class MessagePayloadMessageHandler extends HandlerImpl {
|
||||
Payload payload = msg.getPayload();
|
||||
byte[] data = _context.elGamalAESEngine().decrypt(payload.getEncryptedData(), session.getDecryptionKey());
|
||||
if (data == null) {
|
||||
_log
|
||||
.error("Error decrypting the payload to public key "
|
||||
+ session.getMyDestination().getPublicKey().toBase64() + "\nPayload: " + payload.calculateHash());
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error decrypting the payload");
|
||||
throw new DataFormatException("Unable to decrypt the payload");
|
||||
}
|
||||
payload.setUnencryptedData(data);
|
||||
|
||||
@@ -22,7 +22,8 @@ public abstract class NamingService {
|
||||
private final static Log _log = new Log(NamingService.class);
|
||||
protected I2PAppContext _context;
|
||||
|
||||
private static final String PROP_IMPL = "i2p.naming.impl";
|
||||
/** what classname should be used as the naming service impl? */
|
||||
public static final String PROP_IMPL = "i2p.naming.impl";
|
||||
private static final String DEFAULT_IMPL = "net.i2p.client.naming.HostsTxtNamingService";
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ but there are three other subpackages that are helpful. Specifically:<ul>
|
||||
<li>{@link net.i2p.client.streaming} - for applications that want to use
|
||||
a streaming API to provide reliable in order message delivery (<b>note</b>:
|
||||
the streaming library is packaged seperate from the main SDK - in the
|
||||
ministreaming.jar)</li>
|
||||
mstreaming.jar and streaming.jar)</li>
|
||||
</ul></p>
|
||||
|
||||
<p>The {@link net.i2p.client.I2PSession} implementation itself communicates with
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.io.Serializable;
|
||||
*/
|
||||
public class ByteArray implements Serializable, Comparable {
|
||||
private byte[] _data;
|
||||
private int _valid;
|
||||
|
||||
public ByteArray() {
|
||||
this(null);
|
||||
@@ -25,6 +26,7 @@ public class ByteArray implements Serializable, Comparable {
|
||||
|
||||
public ByteArray(byte[] data) {
|
||||
_data = data;
|
||||
_valid = 0;
|
||||
}
|
||||
|
||||
public final byte[] getData() {
|
||||
@@ -34,6 +36,14 @@ public class ByteArray implements Serializable, Comparable {
|
||||
public void setData(byte[] data) {
|
||||
_data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count how many of the bytes in the array are 'valid'.
|
||||
* this property does not necessarily have meaning for all byte
|
||||
* arrays.
|
||||
*/
|
||||
public final int getValid() { return _valid; }
|
||||
public final void setValid(int valid) { _valid = valid; }
|
||||
|
||||
public final boolean equals(Object o) {
|
||||
if (o == null) return false;
|
||||
|
||||
@@ -658,18 +658,53 @@ public class DataHelper {
|
||||
}
|
||||
|
||||
public static int read(InputStream in, byte target[]) throws IOException {
|
||||
int cur = 0;
|
||||
while (cur < target.length) {
|
||||
int numRead = in.read(target, cur, target.length - cur);
|
||||
return read(in, target, 0, target.length);
|
||||
}
|
||||
public static int read(InputStream in, byte target[], int offset, int length) throws IOException {
|
||||
int cur = offset;
|
||||
while (cur < length) {
|
||||
int numRead = in.read(target, cur, length - cur);
|
||||
if (numRead == -1) {
|
||||
if (cur == 0) return -1; // throw new EOFException("EOF Encountered during reading");
|
||||
|
||||
if (cur == offset) return -1; // throw new EOFException("EOF Encountered during reading");
|
||||
return cur;
|
||||
}
|
||||
cur += numRead;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a newline delimited line from the stream, returning the line (without
|
||||
* the newline), or null if EOF reached before the newline was found
|
||||
*/
|
||||
public static String readLine(InputStream in) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
boolean ok = readLine(in, buf);
|
||||
if (ok)
|
||||
return buf.toString();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Read in a line, placing it into the buffer (excluding the newline).
|
||||
*
|
||||
* @return true if the line was read, false if eof was reached before a
|
||||
* newline was found
|
||||
*/
|
||||
public static boolean readLine(InputStream in, StringBuffer buf) throws IOException {
|
||||
int c = -1;
|
||||
while ( (c = in.read()) != -1) {
|
||||
if (c == '\n')
|
||||
break;
|
||||
buf.append((char)c);
|
||||
}
|
||||
if (c == -1)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static List sortStructures(Collection dataStructures) {
|
||||
if (dataStructures == null) return new ArrayList();
|
||||
@@ -735,7 +770,7 @@ public class DataHelper {
|
||||
|
||||
/** decompress the GZIP compressed data (returning null on error) */
|
||||
public static byte[] decompress(byte orig[]) throws IOException {
|
||||
return decompress(orig, 0, orig.length);
|
||||
return (orig != null ? decompress(orig, 0, orig.length) : null);
|
||||
}
|
||||
public static byte[] decompress(byte orig[], int offset, int length) throws IOException {
|
||||
if ((orig == null) || (orig.length <= 0)) return orig;
|
||||
|
||||
@@ -124,7 +124,8 @@ public class Timestamper implements Runnable {
|
||||
alreadyBitched = true;
|
||||
}
|
||||
}
|
||||
try { Thread.sleep(_queryFrequency); } catch (InterruptedException ie) {}
|
||||
long sleepTime = _context.random().nextInt(_queryFrequency) + _queryFrequency;
|
||||
try { Thread.sleep(sleepTime); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Timestamper died!", t);
|
||||
|
||||
@@ -42,8 +42,8 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
|
||||
/** if the clock is skewed by 3+ days, fuck 'em */
|
||||
public final static long MAX_OFFSET = 3 * 24 * 60 * 60 * 1000;
|
||||
/** after we've started up and shifted the clock, don't allow shifts of more than a minute */
|
||||
public final static long MAX_LIVE_OFFSET = 60 * 1000;
|
||||
/** after we've started up and shifted the clock, don't allow shifts of more than 10 minutes */
|
||||
public final static long MAX_LIVE_OFFSET = 10 * 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 = 10 * 1000;
|
||||
|
||||
|
||||
@@ -18,20 +18,30 @@ import net.i2p.I2PAppContext;
|
||||
public class SimpleTimer {
|
||||
private static final SimpleTimer _instance = new SimpleTimer();
|
||||
public static SimpleTimer getInstance() { return _instance; }
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
/** event time (Long) to event (TimedEvent) mapping */
|
||||
private TreeMap _events;
|
||||
/** event (TimedEvent) to event time (Long) mapping */
|
||||
private Map _eventTimes;
|
||||
private List _readyEvents;
|
||||
|
||||
private SimpleTimer() {
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(SimpleTimer.class);
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(SimpleTimer.class);
|
||||
_events = new TreeMap();
|
||||
_eventTimes = new HashMap();
|
||||
_readyEvents = new ArrayList(4);
|
||||
I2PThread runner = new I2PThread(new SimpleTimerRunner());
|
||||
runner.setName("SimpleTimer");
|
||||
runner.setDaemon(true);
|
||||
runner.start();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
I2PThread executor = new I2PThread(new Executor());
|
||||
executor.setName("SimpleTimerExecutor " + i);
|
||||
executor.setDaemon(true);
|
||||
executor.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,18 +50,42 @@ public class SimpleTimer {
|
||||
*/
|
||||
public void addEvent(TimedEvent event, long timeoutMs) {
|
||||
long eventTime = System.currentTimeMillis() + timeoutMs;
|
||||
Long time = new Long(eventTime);
|
||||
synchronized (_events) {
|
||||
// remove the old scheduled position, then reinsert it
|
||||
if (_eventTimes.containsKey(event))
|
||||
_events.remove(_eventTimes.get(event));
|
||||
while (_events.containsKey(new Long(eventTime)))
|
||||
eventTime++;
|
||||
_events.put(new Long(eventTime), event);
|
||||
_eventTimes.put(event, new Long(eventTime));
|
||||
while (_events.containsKey(time))
|
||||
time = new Long(time.longValue() + 1);
|
||||
_events.put(time, event);
|
||||
_eventTimes.put(event, time);
|
||||
|
||||
if ( (_events.size() != _eventTimes.size()) ) {
|
||||
_log.error("Skewed events: " + _events.size() + " for " + _eventTimes.size());
|
||||
for (Iterator iter = _eventTimes.keySet().iterator(); iter.hasNext(); ) {
|
||||
TimedEvent evt = (TimedEvent)iter.next();
|
||||
Long when = (Long)_eventTimes.get(evt);
|
||||
TimedEvent cur = (TimedEvent)_events.get(when);
|
||||
if (cur != evt) {
|
||||
_log.error("event " + evt + " @ " + when + ": " + cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_events.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeEvent(TimedEvent evt) {
|
||||
if (evt == null) return false;
|
||||
synchronized (_events) {
|
||||
Long when = (Long)_eventTimes.remove(evt);
|
||||
if (when != null)
|
||||
_events.remove(when);
|
||||
return null != when;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple interface for events to be queued up and notified on expiration
|
||||
*/
|
||||
@@ -82,8 +116,8 @@ public class SimpleTimer {
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (_events) {
|
||||
if (_events.size() <= 0)
|
||||
_events.wait();
|
||||
//if (_events.size() <= 0)
|
||||
// _events.wait();
|
||||
//if (_events.size() > 100)
|
||||
// _log.warn("> 100 events! " + _events.values());
|
||||
long now = System.currentTimeMillis();
|
||||
@@ -97,7 +131,7 @@ public class SimpleTimer {
|
||||
if (evt != null) {
|
||||
_eventTimes.remove(evt);
|
||||
eventsToFire.add(evt);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nextEventDelay = when.longValue() - now;
|
||||
nextEvent = _events.get(when);
|
||||
@@ -128,32 +162,20 @@ public class SimpleTimer {
|
||||
long now = System.currentTimeMillis();
|
||||
now = now - (now % 1000);
|
||||
|
||||
for (int i = 0; i < eventsToFire.size(); i++) {
|
||||
TimedEvent evt = (TimedEvent)eventsToFire.get(i);
|
||||
try {
|
||||
evt.timeReached();
|
||||
} catch (Throwable t) {
|
||||
log("wtf, event borked: " + evt, t);
|
||||
}
|
||||
_recentEvents[4] = _recentEvents[3];
|
||||
_recentEvents[3] = _recentEvents[2];
|
||||
_recentEvents[2] = _recentEvents[1];
|
||||
_recentEvents[1] = _recentEvents[0];
|
||||
_recentEvents[0] = evt;
|
||||
synchronized (_readyEvents) {
|
||||
for (int i = 0; i < eventsToFire.size(); i++)
|
||||
_readyEvents.add(eventsToFire.get(i));
|
||||
_readyEvents.notifyAll();
|
||||
}
|
||||
|
||||
if (_occurredTime == now) {
|
||||
_occurredEventCount += eventsToFire.size();
|
||||
} else {
|
||||
_occurredTime = now;
|
||||
if (_occurredEventCount > 100) {
|
||||
StringBuffer buf = new StringBuffer(256);
|
||||
if (_occurredEventCount > 1000) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("Too many simpleTimerJobs (").append(_occurredEventCount);
|
||||
buf.append(") in a second! Last 5: \n");
|
||||
for (int i = 0; i < _recentEvents.length; i++) {
|
||||
if (_recentEvents[i] != null)
|
||||
buf.append(_recentEvents[i]).append('\n');
|
||||
}
|
||||
buf.append(") in a second!");
|
||||
_log.log(Log.CRIT, buf.toString());
|
||||
}
|
||||
_occurredEventCount = 0;
|
||||
@@ -163,4 +185,30 @@ public class SimpleTimer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Executor implements Runnable {
|
||||
public void run() {
|
||||
while (true) {
|
||||
TimedEvent evt = null;
|
||||
synchronized (_readyEvents) {
|
||||
if (_readyEvents.size() <= 0)
|
||||
try { _readyEvents.wait(); } catch (InterruptedException ie) {}
|
||||
if (_readyEvents.size() > 0)
|
||||
evt = (TimedEvent)_readyEvents.remove(0);
|
||||
}
|
||||
|
||||
if (evt != null) {
|
||||
long before = _context.clock().now();
|
||||
try {
|
||||
evt.timeReached();
|
||||
} catch (Throwable t) {
|
||||
log("wtf, event borked: " + evt, t);
|
||||
}
|
||||
long time = _context.clock().now() - before;
|
||||
if ( (time > 1000) && (_log != null) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("wtf, event execution took " + time + ": " + evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
206
core/java/test/net/i2p/crypto/ElGamalVerify.java
Normal file
206
core/java/test/net/i2p/crypto/ElGamalVerify.java
Normal file
@@ -0,0 +1,206 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
|
||||
/**
|
||||
* Unit test verifying the ElGamal encryption/decryption with some test
|
||||
* data. The keys generated & data stored were generated by jrandom on
|
||||
* a pentium4 w/ an optimized jbigi installed and verified with pure java.
|
||||
*
|
||||
*/
|
||||
public class ElGamalVerify {
|
||||
private I2PAppContext _context;
|
||||
|
||||
private static final String UNENCRYPTED[] = new String[] {
|
||||
"",
|
||||
"hello world",
|
||||
"1234567890123456789012345678901234567890123456789012345678901234567890" +
|
||||
"1234567890123456789012345678901234567890123456789012345678901234567890" +
|
||||
"1234567890123456789012345678901234567890123456789012345678901234567890" +
|
||||
"123456789012",
|
||||
"\0x00",
|
||||
"\0x00\0x00\0x00",
|
||||
"\0x00\0x01\0x02\0x00",
|
||||
};
|
||||
private static final String PUBLIC_KEY = new String(
|
||||
"pOvBUMrSUUeN5awynzbPbCAwe3MqWprhSpp3OR7pvdfm9PhWaNbPoKRLeEmDoUwyNDoHE0" +
|
||||
"E6mcZSG8qPQ8XUZFlczpilOl0MJBvsI9u9SMyi~bEqzSgzh9FNfS-NcGji3q2wI~Ux~q5B" +
|
||||
"KOjGlyMLgd1nxl5R5wIYL4uHKZNaYuArsRYmtV~MgMQPGvDtIbdGTV6aL6UbOYryzQSUMY" +
|
||||
"OuO3S~YoBjA6Nmi0SeJM3tyTxlI6U1EYjR6oQcI4SOFUW4L~8pfYWijcncCODAqpXVN6ZI" +
|
||||
"AJ3a6vjxGu56IDp4xCcKlOEHgdXvqmEC67dR5qf2btH6dtWoB3-Z6QPsS6tPTQ=="
|
||||
);
|
||||
private static final String PRIVATE_KEY = new String(
|
||||
"gMlIhURVXU8uPube20Xr8E1K11g-3qZxOj1riThHqt-rBx72MPq5ivT1rr28cE9mzOmsXi" +
|
||||
"bbsuBuQKYDvF7hGICRB3ROSPePYhcupV3j7XiXUIYjWNw9hvylHXK~nTT7jkpIBazBJZfr" +
|
||||
"LJPcDZTDB0YnCOHOL-KFn4N1R5B22g0iYRABN~O10AUjQmf1epklAXPqYlzmOYeJSfTPBI" +
|
||||
"E44nEccWJp0M0KynhKVbDI0v9VYm6sPFK7WrzRyWwHL~r735wiRkwywuMmKJtA7-PuJjcW" +
|
||||
"NLkJwx6WScH2msMzhzYPi8JSZJBl~PosX934l-L0T-KNV4jg1Ih6yoCnm1748A=="
|
||||
);
|
||||
private static final String ENCRYPTED[] = new String[] {
|
||||
"AMfISa8KvTpaC7KXZzSvC2axyiSk0xPexBAf29yU~IKq21DzaU19wQcGJg-ktpG4hjGSg7" +
|
||||
"u-mJ07b61yo-EGmVGZsv3nYuQYW-GjvsZQa9nm98VljlMtWrxu7TsRXw~SQlWQxMvthqJB" +
|
||||
"1A7Y7Qa~C7-UlRytkD-cpVdgUfM-esuMWmjGs6Vc33N5U-tce5Fywa-9y7PSn3ukBO8KGR" +
|
||||
"wm7T12~H2gvhgxrVeK2roOzsV7f5dGkvBQRZJ309Vg3j0kjaxWutgI3vli0pzDbSK9d5NR" +
|
||||
"-GUDtdOb6IIfLiOckBegcv6I-wlSXjYJe8mIoaK45Ok3rEpHwWKVKS2MeuI7AmsAWgkQmW" +
|
||||
"f8irmZaKc9X910VWSO5GYu6006hSc~r2TL3O7vwtW-Z9Oq~sAam9av1PPVJzAx8A4g~m~1" +
|
||||
"avtNnncwlChsGo6mZHXqz-QMdMJXXP57f4bx36ZomkvpM-ZLlFAn-a~42KQJAApo4LfEyk" +
|
||||
"7DPY2aTXL9ArOCNQIQB4f8QLyjvAvu6M3jzCoGo0wVX6oePfdiokGflriYOcD8rL4NbnCP" +
|
||||
"~MSnVzC8LKyRzQVN1tDYj8~njuFqekls6En8KFJ-qgtL4PiYxbnBQDUPoW6y61m-S9r9e9" +
|
||||
"y8qWd6~YtdAHAxVlw287~HEp9r7kqI-cjdo1337b7~5dm83KK45g5Nfw==",
|
||||
|
||||
"AIrd65mG1FJ~9J-DDSyhryVejJBSIjYOqV3GYmHDWgwLchTwq-bJS7dub3ENk9MZ-C6FIN" +
|
||||
"gjUFRaLBtfwJnySmNf8pIf1srmgdfqGV2h77ufG5Gs0jggKPmPV~7Z1kTcgsqpL8MyrfXr" +
|
||||
"Gi86X5ey-T0SZSFc0X1EhaE-47WlyWaGf-~xth6VOR~KG7clOxaOBpks-7WKZNQf7mpQRE" +
|
||||
"4IsPJyj5p1Rf-MeDbVKbK~52IfXSuUZQ8uZr34KMoy4chjn6e-jBhM4XuaQWhsM~a3Q-zE" +
|
||||
"pV-ea6t0bQTYfsbG9ch7pJuDPHM64o5mF9FS5-JGr7MOtfP7KDNHiYM2~-uC6BIAbiqBN8" +
|
||||
"WSLX1mrHVuhiM-hiJ7U4oq~HYB6N~U980sCIW0dgFBbhalzzQhJQSrC1DFDqGfL5-L25mj" +
|
||||
"ArP8dtvN0JY3LSnbcsm-pT9ttFHCPGomLfaAuP7ohknBoXK0j9e6~splg5sUA9TfLeBfqc" +
|
||||
"Lr0Sf8b3l~PvmrVkbVcaE8yUqSS6JFdt3pavjyyAQSmSlb2jVNKGPlrov5QLzlbH7G~AUv" +
|
||||
"IehsbGQX5ptRROtSojN~iYx3WQTOa-JLEC-AL7RbRu6B62p9I0pD0JgbUfCc4C4l9E9W~s" +
|
||||
"MuaJLAXxh0b2miF7C5bzZHxbt~MtZ7Ho5qpZMitXyoE3icb43B6Y1sbA==",
|
||||
|
||||
"ACjb0FkTIQbnEzCZlYXGxekznfJad5uW~F5Mbu~0wtsI1O2veqdr7Mb0N754xdIz7929Ti" +
|
||||
"1Kz-CxVEAkb3RBbVNcYHLfjy23oQ4BCioDKQaJcdkJqXa~Orm7Ta2tbkhM1Mx05MDrQaVF" +
|
||||
"gCVXtwTsPSLVK8VwScjPIFLXgQqqZ5osq~WhaMcYe2I2RCQLOx2VzaKbT21MMbtF70a-nK" +
|
||||
"WovkRUNfJEPeJosFwF2duAD0BHHrPiryK9BPDhyOiyN82ahOi2uim1Nt5yhlP3xo7cLV2p" +
|
||||
"6kTlR1BNC5pYjtsvetZf6wk-solNUrJWIzcuc18uRDNH5K90GTL6FXPMSulM~E4ATRQfhZ" +
|
||||
"fkW9xCrBIaIQM49ms2wONsp7fvI07b1r0rt7ZwCFOFit1HSAKl8UpsAYu-EsIO1qAK7vvO" +
|
||||
"UV~0OuBXkMZEyJT-uIVfbE~xrwPE0zPYE~parSVQgi~yNQBxukUM1smAM5xXVvJu8GjmE-" +
|
||||
"kJZw1cxaYLGsJjDHDk4HfEsyQVVPZ0V3bQvhB1tg5cCsTH~VNjts4taDTPWfDZmjtVaxxr" +
|
||||
"PRII4NEDKqEzg3JBevM~yft-RDfMc8RVlm-gCGANrRQORFii7uD3o9~y~4P2tLnO7Fy3m5" +
|
||||
"rdjRsOsWnCQZzw37mcBoT9rEZPrVpD8pjebJ1~HNc764xIpXDWVt8CbA==",
|
||||
|
||||
"AHDZBKiWeaIYQS9R1l70IlRnoplwKTkLP2dLlXmVh1gB33kx65uX8OMb3hdZEO0Bbzxkkx" +
|
||||
"quqlNn5w166nJO4nPbpEzVfgtY4ClUuv~W4H4CXBr0FcZM1COAkd6rtp6~lUp7cZ8FAkpH" +
|
||||
"spl95IxlFM-F1HwiPcbmTjRO1AwCal4sH8S5WmJCvBU6jH6pBPo~9B9vAtP7vX1EwsG2Jf" +
|
||||
"CQXkVkfvbWpSicbsWn77aECedS3HkIMrXrxojp7gAiPgQhX4NR387rcUPFsMHGeUraTUPZ" +
|
||||
"D7ctk5tpUuYYwRQc5cRKHa4zOq~AQyljx5w5~FByLda--6yCe7qDcILyTygudJ4AHRs1pJ" +
|
||||
"RU3uuRTHZx0XJQo~cPsoQ2piAOohITX9~yMCimCgv2EIhY3Z-mAgo8qQ4iMbItoE1cl93I" +
|
||||
"u2YV2n4wMq9laBx0shuKOJqO3rjRnszzCbqMuFAXfc3KgGDEaCpI7049s3i2yIcv4vT9uU" +
|
||||
"AlrM-dsrdw0JgJiFYl0JXh~TO0IyrcVcLpgZYgRhEvTAdkDNwTs-2GK4tzdPEd34os4a2c" +
|
||||
"DPL8joh3jhp~eGoRzrpcdRekxENdzheL4w3wD1fJ9W2-leil1FH6EPc3FSL6e~nqbw69gN" +
|
||||
"bsuXAMQ6CobukJdJEy37uKmEw4v6WPyfYMUUacchv1JoNfkHLpnAWifQ==",
|
||||
|
||||
"AGwvKAMJcPAliP-n7F0Rrj0JMRaFGjww~zvBjyzc~SPJrBF831cMqZFRmMHotgA7S5BrH2" +
|
||||
"6CL8okI2N-7as0F2l7OPx50dFEwSVSjqBjVV6SGRFC8oS-ii1FURMz2SCHSaj6kazAYq4s" +
|
||||
"DwyqR7vnUrOtPnZujHSU~a02jinyn-QOaHkxRiUp-Oo0jlZiU5xomXgLdkhtuz6725WUDj" +
|
||||
"3uVlMtIYfeKQsTdasujHe1oQhUmp58jfg5vgZ8g87cY8rn4p9DRwDBBuo6vi5on7T13sGx" +
|
||||
"tY9wz6HTpwzDhEqpNrj~h4JibElfi0Jo8ZllmNTO1ZCNpUQgASoTtyFLD5rk6cIAMK0R7A" +
|
||||
"7hjB0aelKM-V7AHkj-Fhrcm8xIgWhKaLn2wKbVNpAkllkiLALyfWJ9dhJ804RWQTMPE-GD" +
|
||||
"kBMIFOOJ9MhpEN533OBQDwUKcoxMjl0zOMNCLx8IdCE6cLtUDKJXLB0atnDpLkBer6FwXP" +
|
||||
"81EvKDYhtp1GsbiKvZDt8LSPJQnm2EdA3Pr9fpAisJ5Ocaxlfa6~uQCuqGA9nJ9n6w03u-" +
|
||||
"ZpSMhSh4zm2s1MqijmaJRc-QNKmN~u1hh3R2hwWNi7FoStMA87sutEBXMdFI8un7StHNSE" +
|
||||
"iCYwmmW2Nu3djkM-X8gGjSsdrphTU7uOXbwazmguobFGxI0JujYruM5Q==",
|
||||
|
||||
"ALFYtPSwEEW3eTO4hLw6PZNlBKoSIseQNBi034gq6FwYEZsJOAo-1VXcvMviKw2MCP9ZkH" +
|
||||
"lTNBfzc79ms2TU8kXxc7zwUc-l2HJLWh6dj2tIQLR8bbWM7U0iUx4XB1B-FEvdhbjz7dsu" +
|
||||
"6SBXVhxo2ulrk7Q7vX3kPrePhZZldcNZcS0t65DHYYwL~E~ROjQwOO4Cb~8FgiIUjb8CCN" +
|
||||
"w5zxJpBaEt7UvZffkVwj-EWTzFy3DIjWIRizxnsI~mUI-VspPE~xlmFX~TwPS9UbwJDpm8" +
|
||||
"-WzINFcehSzF3y9rzSMX-KbU8m4YZj07itZOiIbWgLeulTUB-UgwEkfJBG0xiSUAspZf2~" +
|
||||
"t~NthBlpcdrBLADXTJ7Jmkk4MIfysV~JpDB7IVg0v4WcUUwF3sYMmBCdPCwyYf0hTrl2Yb" +
|
||||
"L6kmm4u97WgQqf0TyzXtVZYwjct4LzZlyH591y6O6AQ4Fydqos9ABInzu-SbXq6S1Hi6vr" +
|
||||
"aNWU3mcy2myie32EEXtkX7P8eXWY35GCv9ThPEYHG5g1qKOk95ZCTYYwlpgeyaMKsnN3C~" +
|
||||
"x9TJA8K8T44v7vE6--Nw4Z4zjepwkIOht9iQsA6D6wRUQpeYX8bjIyYDPC7GUHq0WhXR6E" +
|
||||
"6Ojc9k8V5uh0SZ-rCQX6sccdk3JbyRhjGP4rSKr6MmvxVVsqBjcbpxsg=="
|
||||
};
|
||||
|
||||
public static void main(String args[]) {
|
||||
ElGamalVerify verify = new ElGamalVerify();
|
||||
verify.verifySelf();
|
||||
verify.verifyCompatability();
|
||||
if (args.length > 0)
|
||||
verify.generateEncrypted();
|
||||
}
|
||||
|
||||
public ElGamalVerify() {
|
||||
_context = new I2PAppContext();
|
||||
}
|
||||
|
||||
/** verify that we can decrypt what we encrypt */
|
||||
private void verifySelf() {
|
||||
try {
|
||||
Object keypair[] = _context.keyGenerator().generatePKIKeypair();
|
||||
PublicKey pub = (PublicKey)keypair[0];
|
||||
PrivateKey priv = (PrivateKey)keypair[1];
|
||||
|
||||
for (int i = 0; i < UNENCRYPTED.length; i++) {
|
||||
byte orig[] = UNENCRYPTED[i].getBytes();
|
||||
|
||||
byte encrypted[] = _context.elGamalEngine().encrypt(orig, pub);
|
||||
byte decrypted[] = _context.elGamalEngine().decrypt(encrypted, priv);
|
||||
|
||||
if (DataHelper.eq(decrypted, orig))
|
||||
log("OK : verifySelf[" + i + "] passed");
|
||||
else
|
||||
log("ERROR: verifySelf[" + i + "] failed");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log("ERROR: verifySelf blew up: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/** verify that we can decrypt what other people encrypt */
|
||||
private void verifyCompatability() {
|
||||
verifyDecrypt();
|
||||
}
|
||||
|
||||
private void verifyDecrypt() {
|
||||
try {
|
||||
PublicKey pub = new PublicKey();
|
||||
PrivateKey priv = new PrivateKey();
|
||||
pub.fromBase64(PUBLIC_KEY);
|
||||
priv.fromBase64(PRIVATE_KEY);
|
||||
|
||||
for (int i = 0; i < ENCRYPTED.length; i++) {
|
||||
byte enc[] = Base64.decode(ENCRYPTED[i]);
|
||||
byte decrypted[] = _context.elGamalEngine().decrypt(enc, priv);
|
||||
|
||||
if (DataHelper.eq(decrypted, UNENCRYPTED[i].getBytes()))
|
||||
log("OK : verifyDecrypt[" + i + "] passed");
|
||||
else
|
||||
log("ERROR: verifyDecrypt[" + i + "] failed");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log("ERROR: generateEncrypted blew up: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
private void generateEncrypted() {
|
||||
try {
|
||||
Object keypair[] = _context.keyGenerator().generatePKIKeypair();
|
||||
PublicKey pub = (PublicKey)keypair[0];
|
||||
PrivateKey priv = (PrivateKey)keypair[1];
|
||||
|
||||
log("PUBLIC : " + pub.toBase64());
|
||||
log("PRIVATE: " + priv.toBase64());
|
||||
|
||||
for (int i = 0; i < UNENCRYPTED.length; i++) {
|
||||
byte orig[] = UNENCRYPTED[i].getBytes();
|
||||
|
||||
byte encrypted[] = _context.elGamalEngine().encrypt(orig, pub);
|
||||
|
||||
System.out.println("Encrypted [" + i + "]: ");
|
||||
String enc = Base64.encode(encrypted);
|
||||
for (int j = 0; j < enc.length(); j++) {
|
||||
int remaining = enc.length() - j*70;
|
||||
if (remaining > 0) {
|
||||
String cur = enc.substring(j * 70, remaining > 70 ? (j+1)*70 : enc.length());
|
||||
System.out.println(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log("ERROR: generateEncrypted blew up: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void log(String msg) {
|
||||
System.out.println(msg);
|
||||
}
|
||||
}
|
||||
206
history.txt
206
history.txt
@@ -1,4 +1,208 @@
|
||||
$Id: history.txt,v 1.91 2004/12/01 17:31:56 jrandom Exp $
|
||||
$Id: history.txt,v 1.117 2004/12/21 11:32:50 jrandom Exp $
|
||||
|
||||
* 2004-12-21 0.4.2.5 released
|
||||
|
||||
2004-12-21 jrandom
|
||||
* Track a new stat for expired client leases (client.leaseSetExpired).
|
||||
|
||||
2004-12-21 jrandom
|
||||
* Cleaned up the postinstall/startup scripts a bit more to handle winME,
|
||||
and added windows info to the headless docs. (thanks ardvark!)
|
||||
* Fixed a harmless (yet NPE inspiring) race during the final shutdown of
|
||||
a stream (thanks frosk!)
|
||||
* Add a pair of new stats for monitoring tunnel participation -
|
||||
tunnel.participatingBytesProcessed (total # bytes transferred) and
|
||||
tunnel.participatingBytesProcessedActive (total # bytes transferred for
|
||||
tunnels whose byte count exceed the 10m average). This should help
|
||||
further monitor congestion issues.
|
||||
* Made the NamingService factory property public (thanks susi!)
|
||||
|
||||
2004-12-20 jrandom
|
||||
* No longer do a blocking DNS lookup within the jobqueue (thanks mule!)
|
||||
* Set a 60s dns cache TTL, instead of 0s. Most users who used to use
|
||||
dyndns/etc now just use IP autodetection, so the old "we need ttl=0"
|
||||
reasoning is gone.
|
||||
|
||||
2004-12-19 jrandom
|
||||
* Fix for a race on startup wrt the new stats (thanks susi!)
|
||||
|
||||
2004-12-19 jrandom
|
||||
* Added three new stats - router.activePeers, router.fastPeers, and
|
||||
router.highCapacityPeers, updated every minute
|
||||
|
||||
2004-12-19 jrandom
|
||||
* Added a new i2ptunnel type: 'httpserver', allowing you to specify what
|
||||
hostname should be sent to the webserver. By default, new installs will
|
||||
have an httpserver pointing at their jetty instance with the spoofed
|
||||
name 'mysite.i2p' (editable on the /i2ptunnel/edit.jsp page).
|
||||
|
||||
2004-12-19 scintilla
|
||||
* Convert native jcpuid code from C++ to C. This should alleviate build
|
||||
problems experienced by some users.
|
||||
|
||||
* 2004-12-18 0.4.2.4 released
|
||||
|
||||
2004-12-16 jrandom
|
||||
* Catch another oddball case for a reset connection in the streaming lib.
|
||||
* Add a dumpprofile.jsp page, called with ?peer=base64OfPeerHash, which
|
||||
dumps the current state of that peer's profile. Instead of the full
|
||||
base64, you can pass in however many characters you have and it will
|
||||
return the first match found.
|
||||
|
||||
2004-12-16 jrandom
|
||||
* Remove the randomized factor in the tunnel rejection by bandwidth -
|
||||
we now accept the request if we've allocated less than our limit
|
||||
and reject it if we've allocated more.
|
||||
* Stick to the standard capacity scale on tunnel rejection, even for
|
||||
the 10m period.
|
||||
* Build the time message at the very last possible moment
|
||||
|
||||
2004-12-15 jrandom
|
||||
* Handle hard disconnects more gracefully within the streaming lib, and
|
||||
log unmonitored events more aggressively.
|
||||
* If we drop a peer after connection due to clock skew, log it to the
|
||||
/logs.jsp#connectionlogs with relevent info. In addition, toss it in
|
||||
the stat 'tcp.disconnectAfterSkew'.
|
||||
* Fixed the formatting in the skew display
|
||||
* Added an ERROR message that is fired once after we run out of
|
||||
routerInfo files (thanks susi!)
|
||||
* Set the connect timeout equal to the streaming lib's disconnect timeout
|
||||
if not already specified (the I2PTunnel httpclient already enforces a
|
||||
60s connect timeout)
|
||||
* Fix for another connection startup problem in the streaming lib.
|
||||
* Fix for a stupid error in the probabalistic drop (rand <= P, not > P)
|
||||
* Adjust the capacity calculations so that tunnel failures alone in the
|
||||
last 10m will not trigger a 0 capacity rank.
|
||||
|
||||
2004-12-14 jrandom
|
||||
* Periodically send a message along all I2NP connections with the router's
|
||||
current time, allowing the receiving peer to determine that the clock
|
||||
has skewed too much, and hence, disconnect. For backwards compatability
|
||||
reasons, this is being kludged into a DeliveryStatusMessage (ewww). The
|
||||
next time we have a backwards compatability break, we can put in a proper
|
||||
message setup for it.
|
||||
|
||||
2004-12-14 jrandom
|
||||
* Reenable the probabalistic drop on the TCP queues to deal with good old
|
||||
fashioned bandwidth limiting. However, by default the probability is
|
||||
rigged to reserve 0% of the queue free - meaning we just aggressively
|
||||
fail messages in the queue if we're transferring too slowly. That
|
||||
reservation factor can be increased with 'tcp.queueFreeFactor=0.25'
|
||||
(or whatever) and the drop code can be disabled with the parameter
|
||||
'tcp.dropProbabalistically=false'.
|
||||
* Still penalize a peer on tunnel failure, but don't immediately drop
|
||||
their capacity to 0.
|
||||
* More aggressively ACK duplicates
|
||||
* Randomize the timestamper period
|
||||
* Display the clock skew on the connection logs when a peer sends it.
|
||||
* Allow the timestamper to fix skews of up to 10 minutes
|
||||
* Logging
|
||||
|
||||
2004-12-13 jrandom
|
||||
* Added some error checking on the new client send job (thanks duck!)
|
||||
* Implemented tunnel rejection based on bandwidth usage (rejecting tunnels
|
||||
proportional to the bytes allocated in existing tunnels vs the bytes
|
||||
allowed through the bandwidth limiter).
|
||||
* Enable a new configuration parameter for triggering a tunnel rebuild
|
||||
(tunnel.maxTunnelFailures), where that is the max allowed test failures
|
||||
before killing the tunnel (default 0).
|
||||
* Gather more data that we rank capacity by (now we monitor and balance the
|
||||
data from 10m/30m/60m/1d instead of just 10m/60m/1d).
|
||||
* Fix a truncation/type conversion problem on the long term capacity
|
||||
values (we were ignoring the daily stats outright)
|
||||
|
||||
2004-12-11 jrandom
|
||||
* Fix the missing HTTP timeout, which was caused by the deferred syn used
|
||||
by default. This, in turn, meant the I2PSocket creation doesn't fail
|
||||
on .connect, but is unable to transfer any data in any direction. We now
|
||||
detect that condition for the I2PTunnelHTTPClient and throw up the right
|
||||
error page.
|
||||
* Logging
|
||||
|
||||
2004-12-11 jrandom
|
||||
* Use a simpler and less memory intensive job for processing outbound
|
||||
client messages when the session is in mode=bestEffort. We can
|
||||
immediately discard the data as soon as its sent the first time,
|
||||
rather than wait for an ack, since we will never internally resend.
|
||||
* Reduce some synchronization to avoid a rare deadlock
|
||||
* Replaced 'localhost' with 127.0.0.1 in the i2ptunnel config, and special
|
||||
case it within the tunnel controller.
|
||||
* Script cleanup for building jbigi/jcpuid
|
||||
* Logging
|
||||
|
||||
* 2004-12-08 0.4.2.3 released
|
||||
|
||||
2004-12-08 jrandom
|
||||
* Revised the buffering when reading from the SAM client and writing
|
||||
to the stream. Also added a thread (sigh) so we don't block the
|
||||
SAM client from giving us more messages for abnormally long periods
|
||||
of time.
|
||||
* Display the router version in the logs on startup (oft requested)
|
||||
* Fix a race during the closing of a messageOutputStream
|
||||
|
||||
2004-12-06 jrandom
|
||||
* Don't do a 'passive flush' while there are already outbound messages
|
||||
unacked.
|
||||
* Show the reseed link if up to 10 peers profiles are active (thanks
|
||||
dburton!)
|
||||
|
||||
2004-12-06 jrandom
|
||||
* Don't propogate streaming connection failures out to the SAM bridge as
|
||||
fatal errors.
|
||||
* Dont barf on repeated I2CP closure.
|
||||
|
||||
2004-12-05 jrandom
|
||||
* Explicitly use "127.0.0.1" to bind the I2CP listener, not the JVM's
|
||||
getLocalhost call
|
||||
|
||||
2004-12-05 jrandom
|
||||
* Default the I2CP listener to localhost only, unless overridden by
|
||||
i2cp.tcp.bindAllInterfaces=true (thanks dm!)
|
||||
* More SAM fixes for things recently broken (whee)
|
||||
|
||||
2004-12-05 jrandom
|
||||
* Fix the recently broken SAM bridge (duh)
|
||||
* Add a new pair of SAM apps - net.i2p.sam.client.SAMStreamSink and
|
||||
net.i2p.sam.client.SAMStreamSend, mirroring the streaming lib's
|
||||
StreamSink and StreamSend apps for transferring files.
|
||||
* Make the passive flush timer fire more frequently.
|
||||
|
||||
2004-12-05 jrandom
|
||||
* Fixed some links in the console (thanks ugha!) and the javadoc
|
||||
(thanks dinoman!)
|
||||
* Fix the stream's passive flush timer (oh, its supposed to work?)
|
||||
|
||||
2004-12-03 jrandom
|
||||
* Toss in a small pool of threads (3) to execute the events queued up with
|
||||
the SimpleTimer, as we do currently see the occational event
|
||||
notification spiking up to a second or so.
|
||||
* Implement a SAM client API in java, useful for event based streaming (or
|
||||
for testing the SAM bridge)
|
||||
* Added support to shut down the SAM bridge on OOM (useful if the SAM
|
||||
bridge is being run outside of the router).
|
||||
* Include the SAM test code in the sam.jar
|
||||
* Remove an irrelevent warning message from SAM, which was caused by
|
||||
perfectly normal operation due to a session being closed.
|
||||
* Removed some unnecessary synchronization in the streaming lib's
|
||||
PacketQueue
|
||||
* More quickly clean up the memory used by the streaming lib by
|
||||
immediately killing each packet's resend job as soon as it is ACKed (or
|
||||
cancelled), so that there are no longer any valid pointers to the
|
||||
(potentially 32KB) packet.
|
||||
* Fixed the timestamps dumped to stdout when debugging the PacketHandler.
|
||||
* Drop packets that would expand our inbound window beyond our maximum
|
||||
buffer size (default 32 messages)
|
||||
* Always read the ACK/NACK data from the verified packets received, even
|
||||
if we are going to drop them
|
||||
* Always adjust the window when there are messages ACKed, though do not
|
||||
change its size except as before.
|
||||
* Streamlined some synchronization in the router's I2CP handling
|
||||
* Streamlined some memory allocation in the SAM bridge
|
||||
* Default the streaming lib to disconnect on inactivity, rather than send
|
||||
an empty message.
|
||||
|
||||
2004-12-01 jrandom
|
||||
* Fix for a race in the streaming lib as caused by some odd SAM activity
|
||||
|
||||
* 2004-12-01 0.4.2.2 released
|
||||
|
||||
|
||||
37
hosts.txt
37
hosts.txt
@@ -1,6 +1,22 @@
|
||||
; TC's hosts.txt guaranteed freshness
|
||||
; $Id: hosts.txt,v 1.82 2004/11/28 17:47:01 jrandom Exp $
|
||||
; $Id: hosts.txt,v 1.98 2004/12/18 01:31:22 jrandom Exp $
|
||||
; changelog:
|
||||
; (1.121) added up.i2p
|
||||
; (1.120) added dm.i2p
|
||||
; (1.119) added piespy.i2p
|
||||
; (1.118) added sciencebooks.i2p
|
||||
; (1.117) added forum.fr.i2p, fedo.i2p, and pastebin.i2p
|
||||
; (1.116) added frosk.i2p
|
||||
; (1.115) added theland.i2p
|
||||
; (1.114) added dox.i2p
|
||||
; (1.113) added amiga.i2p
|
||||
; (1.112) added frooze.i2p
|
||||
; (1.111) aliased gott.i2p as jrandom.i2p (ed. note: no, i am not gott)
|
||||
; (1.110) added sonax.i2p
|
||||
; (1.109) added 1.fcp.freenet.i2p and copied fcp.i2p to 2.fcp.freenet.i2p
|
||||
; (1.108) added asciiwhite.i2p
|
||||
; (1.107) added fcp.i2p
|
||||
; (1.106) added greenflog.i2p
|
||||
; (1.105) added bdl.i2p
|
||||
; (1.104) added bacardi.i2p and guttersnipe.i2p
|
||||
; (1.103) added evil.i2p
|
||||
@@ -235,4 +251,23 @@ evil.i2p=NGNaN9qrpk2dC~OQlo-1yHT~Tvb6SvU8VH9SiBkwNZxuNVRKN~2ZtU50nkTklevB5V08GMT
|
||||
bacardi.i2p=oZm0JRHiUFKwAzLz1wlNOK2h2fI8V6u1nUhgCpt1RcErs99QMPHqu4oR4cel5lsJbeg1X0GgHe72JYabsntjimjWs1zi0RzknddVvq0hMGnn9EA-9Atu9qViScXp42ddnYhIlMBNNswMp8AJ01jHkO3SUSDSVk-rF5wnrhpyN9BFyho8h18nHrr6S4jaGHsuLVage8ImwQRv~PYfr4hVULdiFn5HDRePdvgzKUD3o4Y7nFiQMXswP02ZhXXtE-rMGDs2TD15qYah6mkQWdZtRYFuKuCh7Myn35u2IHGyMs56zpnuVr~w~Uh7wydg26JShsAz4AyZhGy70eTFUC2dtEr7bUvQSE3V3xNKOeBuGYadipWB30xmGDX2kfaU5Efy45MLPBwTU-8f4owQBokgSD3jBEc4vV0DbJrGoNnA3zpAC0JwZ9rFLolQNzenRgkWYlO09PEkKPUyUMpUBGY~Wj7jcsGnt5uNskeHHpV4hsvdpboHnlPAcZKvN7rTCenoAAAA
|
||||
guttersnipe.i2p=yS6ECPvCobRLKMXJg-rLZ5PF6dRcXliR2e9KEX~aUZMEsznpzE-lCs~FgGd7w9-~GW8ObvKiyt47S7SuCunjndl874xPhrQ0dFRUNEsdovLBr6s9v2xi7kpWC1yKLbT-Ti3p8WhNPZkGsJl6YEIc357j4FjNVFx2-b~V16G0lhjhY1a55nsCoU7xXisdqOaKZL-4Y7vEVHqZNgM6cAZpY8vHtui30J3oGmV-RcJG9ahYkorqleKtb2G0fwbUMakxHv5uzlHyAU~r7OmS7NCIfoyBgU3MrSiwkYiW5elQ294XV-1Bciy9DFnJh4KA0rLIA7GanvkQ-NwqoGLsXz08mpi12vVssDhdavXooBeZLg7gipFYzle3-B1rgGKZ~51~9Tou~XSn6jGJGgtqKV7fTmys4~AXNy7vCWrMKQaWh6M1HI69yV466bJWv6nVhwNmOmtk48EIC3ik2IXC1wjA6Rgz2AcSiR-UEyaBkv251Vu6H92HZJTrsTzuzVmZcYt0AAAA
|
||||
bdl.i2p=IMzcrD4jQLBtyzV8TdL-jgbyDhRjMBS1BfI6xGuddMyJEoMnZ1ZWDUn3NwqdyEYRQD7zREgbsQgzpE7Gdmpp-vLrzba3W1mUR5Ddu13tgIiDrOWR38Omv7L~DTDimK9m0Y-HCJROVigRfxdZbsI6P37d77NArdpwLzFos6qyK2C40JQFIaNCPYGkf05DsXtHLPwTJXYVKVp0R2V7nGmI77BUdwDgt4zSotlyvmsX6U1mYqKwutwr~oxggdvgfoNbrGC0~xQCbfQtEOwFYwxc5oUeJlt4jjb1-C9HAb7r4LtJ3Daqr0bx1hXS5hADw68cfUHbEjbfrhJhBA0mMqgGMm~r~0II8R19EXprc91YY4d0QycR2Osdn9MVNXS01Ziy~JX-SfT8DTjj2ZZn7rpyjcq7rgVbspPLJOdNQNERiAm06yjosOrPjtl8mrWXxWcOcGlMO5ftoTvX2hFsfc-vmfN7S8IZEfUdJRpJxDytChNTUr~qMWuP0LicNv0xKtA3AAAA
|
||||
greenflog.i2p=4RdKwi7a5wrn-B1crYUuH8H5xHExl8oIxouASnG5Q-s--Sk2eQr7ajeXCdkxzz0Ep9CKhGY4rARRWvCuNYokkl3Jzpc-17oj-Qrr46kRdjqad2AlcJ0q920nmU3tjoqoE4qNshnP7H7NcephURg2GbHsrmUJm1EqA9SsTPk6~kUvvPiLtpbEAoas9uv0a5oI-Ykpwc2VSuZnk9hZQ25ndeW6pSH68FCbzZZfDVNAT1vcTtKGYoop2rmaGTkRsxT0vCG06S8iAoIBt4kHznZRldnnND5n31Dz8rYB7ICLplN54vlntrcjaL82HGSdxSmPg5crG3bwbvwC-7jspiZNGHFM8gun047A3b5g4YaNmCuzrLGgoW5pMlCWgOK7Wdf8NTQ9yrY1iBRIua2-zZwVEuCaIR8mlRdZ-UW37kad-OabJ18iavapc0iz~CaJBer4zlrJev3H9TfIMlk2WhG~SSDW9Y3dELIoZDfvgkfIcyPrNJThrcxpFZWGREhFuI5oAAAA
|
||||
fcp.i2p=yY8ahms44MDj2cdI6o7Fc2ozz-jM4ACEp8t4YJiRsKgEYx6XNB7QrNK8jZQoKIQ8G6GMmKMYsgRGmbwWFt60au5HFNv56NWsB4Nd80G9iSRLiEHOHhaUSFnR~ZnSdzL-nbMqA8-vzteaqZ8HNnC2NNjJXsMyULFdkBsIKYS0jsS5hl7ddf0v69X7sCSp1CCy5H28rmZSEtKA4aYXGcBHJ8bXFomV5ovy~Mh2xZzWdeVmjdmJACG~ekDBcMkm7G5mlYoTN2aEBghxiHv~plGlHFqERB3lgISlomYIwYwPqxcmpxwbg9OSEo53t45P9~dat8XMvabzXiUdm0UgVnFsNBAIyNgyTEktw38~5v2XHhqpn8RSoMUeNOqp~k740v9fYH7xxHYCrfN7y3rKg~~GlWekbNmX5g0UvKjV-QWHIvrdOVZmII9qNTTwm3Uf2Tyi2aFll7TF9CvKUJCI0Mt1BpQEmPQdY~FgFXNd0p-~DvlRrwaI4jorbn4eUUEJqgzFAAAA
|
||||
asciiwhite.i2p=kJJmuP~sE0SrQFX-nyV2cEFDffnqv3D4-vBNJX6wqEq7vbTjQzZVn3jtt5kYlv4FpMyJnhPloYJWZNw-Yt2opdRnxNGpY~AHnI~iW-rmjuwkv0Qp~NG5sKEfTd7b6tAcop87He9FZ~ANNMmPpJbS1CNGGxLtkcceg4El7pJvodCp9uPjtAfMqnsKUYK6HKg~Wlz-8yC1QUxufkoT6PF645BNWp9PUVioCnUIaqizqBmDjTuUNqf9NOXnoQnx2PuqXxHGphKJqLp8faexDSTvLgns~yudyACMexzdnQMdEQJCkUqvwFuyeS2-uw6OFfHfACIc1yr3UdHPPFzsHT0WtwF5rFBpJqeWTiWph~Vas26824PdM-sK-PKo388UemmDJmY1YknGxsky6qhDGYkFHtWXF2D7fuOL~6V1aVw8DpIGW4PS~pjug9wtPoUkcKPd4zey7HNlgnVae4P7hviKlwRPPnxfor9athm1omU75GlIEwUi79GqVPb4N~qrbZOfAAAA
|
||||
1.fcp.freenet.i2p=r2zbc34IQSzOIF4N0enKf0xXkJKgsj9yTGGspRnstKZf~4UoAljZOW5aFZywGo-NlaXwt~tIyj4NC0Til0vl1D5N9ip7OMYUCajNNgiXEH~FN33yl-AcJbeTlB-FychSmVfYciTQj6yd19~6wICwkdpy6AYo90bAejSVGpvtFeP5P2pnSwPmcB8m79wyq~C2XjQCe5UcBxnfYolWKgr3uDFrgbhqBVCCkO7zTiARwOWZLVOvZsvKZR4WvYAmQI6CQaxnmT5n1FKO6NBb-HOxVw4onERq86Sc6EQ5d48719Yk-73wq1Mxmr7Y2UwmL~FCnY33rT1FJY2KzUENICL1uEuiVmr9N924CT9RbtldOUUcXmM1gaHlPS40-Hz4AvPxFXHynbyySktN3hBLPwfwhyIQw95ezSNuiBB0xPcujazCw02103n2CO-59rMDmWpttLjpLMggP9IwsAPa9FVLnBqfuCn3NrC4fia50RDwfR41AD1GOOWiUT0avYzbbOdsAAAA
|
||||
2.fcp.freenet.i2p=yY8ahms44MDj2cdI6o7Fc2ozz-jM4ACEp8t4YJiRsKgEYx6XNB7QrNK8jZQoKIQ8G6GMmKMYsgRGmbwWFt60au5HFNv56NWsB4Nd80G9iSRLiEHOHhaUSFnR~ZnSdzL-nbMqA8-vzteaqZ8HNnC2NNjJXsMyULFdkBsIKYS0jsS5hl7ddf0v69X7sCSp1CCy5H28rmZSEtKA4aYXGcBHJ8bXFomV5ovy~Mh2xZzWdeVmjdmJACG~ekDBcMkm7G5mlYoTN2aEBghxiHv~plGlHFqERB3lgISlomYIwYwPqxcmpxwbg9OSEo53t45P9~dat8XMvabzXiUdm0UgVnFsNBAIyNgyTEktw38~5v2XHhqpn8RSoMUeNOqp~k740v9fYH7xxHYCrfN7y3rKg~~GlWekbNmX5g0UvKjV-QWHIvrdOVZmII9qNTTwm3Uf2Tyi2aFll7TF9CvKUJCI0Mt1BpQEmPQdY~FgFXNd0p-~DvlRrwaI4jorbn4eUUEJqgzFAAAA
|
||||
sonax.i2p=s~FYxHeR1n0aN0kuilNtJU9IbXRX~jWI6vGz4TUf6oZyjZBYYmY9OpgDa3sr-8AERqsuG6EKA~UBn4KiQdkI-LU16DwEHvbgfZXAdwY73G6SkHOgwVAnT2SIOuh90v1QevYtxiGpA7IVm6icoz5V4I6V29hPlUNJxBFF~Qn2cY6zj0d6wBk6ulYc0vbyOwu8Lfc8T8bTnBLut3SZWgKHYLbxabcXJtcQpoN7k815N1MEUi0APD45o5TwPhvpHLt8fF10aNcMScpL01RR~tKHgC6jPX~HKM~Ld4l0z0lvLeq9197Qp6zHRvRD3YUQxyvgHi2ANkYprvJof1Abgs8TD1JWkZUAzlfqFPyjO~f3pJSDd5eM7E0Fk69ym1Y4Rltih~Y2N1IVtJPfo9Tx9a3RQWOJZ0MN12xfiZjyF92gDRgfKW5RDpwCoTVmP79u~WUr8lQ-qQbKhsMQQqMn8Uy8DeXvKLRpHaECtevjohL4lWBBR0ezXlBLCXKqaVXKcMTjAAAA
|
||||
jrandom.i2p=BxciUZkGQwi6Sj1Ucg5GGagDdwujlj8ClePa4~3d-1nnRJsBhTNJtvs-UuQYY77gPGNlpNk9dt02mxeS7f~pEC4E1KxJH9mhnf0OlIGB4hOOhDlXokAaKE2u1E-vVCDJlZCq32r~Ne53w-L2m5h5FAq5Bx7NXrTzWAEPgAlC8A2wASJWIF-EKOX5kfkkYoF6sKZqam5CxAAAMAUwmnbD--7wo8d2mz0C-c~oE7hzuHsg2J8yME5Zd~-FOZUxkoNCBJdfrVlVRn9~6685zqpaotL-SIqFK6~28ZCNMNCtnZcZZLeG2gZxq2pe8HtgzDrMMSx45vs3JLUp8Bap6~D9oFX0o7k2DQm-5R9D5NWhsJgol5Ny~8ABTXBpW~WQQOuKxE5xJauXM5rQia2yyXgldfOyGjxYnkrTGu6LaxlLmhyAva3ZbOO0mtLvrKLZebLKCYUxP7~TtNmSWEAzPKFYbjdZ~NjE0q4TanLFBaWotefPmy3IuAc8Mr7PbCvi4GmdAAAA
|
||||
frooze.i2p=kyjnpna6HHapU~tLQXRCHrX6Dpb2YCMEtFTloKF298480axGCZFY-WtpjW2Mo1ci5FlE7dkmEQ1PU0GfHZ6s0O~Hert5AcZpKh5nxRIMtS7uc~oErYCiX0yQp2P9D47B9VU5D2UmjYKWovqbrbKEAS1F0NDxXh65Ppnv-oBHVksLp7IrKLU3IOesdYtGcc9cSYJB6~mdbtqXxgHNNLTkyouSJKgSlJoe-tk1T3eYEsOJ6vzpeS6pNa6Px2dknejT~aVeNKLhjsXy706g22Mbl1-zCL87OE1bL2qWQbQN43qpLwCSv0LbRovO3r6iXRWWWrKhYj5nti~EQEWapJ2OL26~ElrpD9V9gbUVLZoxeYuu~-GKKwg6CvZYKtdf6YBYTZ0Ef4PUcv9faSqp7S4emcGOLQnSW9LezXhGhn0OPaAO~85nhdY~OfXDZ2Dg2tYYF-D70SzKycfHFLgKBQ9fkCxpOP0MHhhgGmKHabPvDHyQRFppcKLN6AYmUH1Lke-jAAAA
|
||||
amiga.i2p=-lqgoqA5KcJCuGilYz~cOBK3X7MI5VFBK7UwpSEwqbiQgSe340wABmE8V-tX7xH7rjc4Z2rGxqas5NKuOL3pCJozeKF4Z61d05nCemSXbLrqZvJUkGuZJfUz9lzeZ5OGO8L32o-I6DIGIUgFk6zRpiRW0lqO4lzV~fbiHacDhUXPLBM5gYjvZ319MLoVPp-YWG-STJFhjmAnzvj~8h-jJXHowM72YsWPo-B~oD1YtcAcXkpxVJOaLlqQq2yfjJv7YgIde3tqQ6drH7xmHdG~nF~dsu2dacqqt~HjDiUnPd12vJEhR18DDkVJVhDUbnUCcI83bt5RDdsMK8mSKRTie3h1sUam~1fLr8~gnEGTFp-yivGV1JkeleElR-rTJC7PsrlPCWzW2N4l1IMzRGOedJNAyqeXbkfjHtDBd58iZPugS5BZZ~u5MGqXgfu9TYFnLnTIGuMc3a2wSpe3miMvf2PKFnM~1nzStN0zEjFwhn4tZhOWA1TfTH5HMutqrxGxAAAA
|
||||
dox.i2p=ygf2mdGRnU12BNXXF-26OFhfPpAMigwasf93JEHVYEICVmz6Fk2BBKiGBWL4YNFUtcFo7y1iG3ms8BdmTBBkgCgEasuFZNzphEMgcNYi0eYAg1QLNgmbr5HEDpBNuXIV7fOpxYn6iij76oA09e5opPNdOLUk80kWrgrlJTel61n90uzEzz46fWIbFEw5K8xR-8aqLgC2sJDzGj8vEBY2NDKmCtjRK7aNBUayh~8vFfJyo5oN0gw8b5cOCrfSoxzqdpzI5CJ7070GjznWbWh~pueKWF4CpHn92ll3jvyd86KvUwdQh1qr1B-8fk6Oh~M9l5HRzqdUsSzp8SiG7WvLQoXrNdVX2Sjw22xOZDr7kOfEHZWYUdmrcYyqNLuIT2vgMFXdvCZnLhhGSaihkzuFqZv-34oGSqGDVlCanAPC4v6492IRRK7RiYnUo-JPhuWgpg9CdFTZtULa~drZbWNCZvAekn44D42wDx5V5vAJxuRoyir1s4gAnXHL7n9r7xdiAAAA
|
||||
theland.i2p=Lf52G95rF1kjlRu29NMpPQ46Ivd2Nz-sdIhvx5D4qU3Y3MPs50GNTjg81yJmUgbg95Q90Eyf~SHsgbzdlnjr-qQEZOXIgLQErq2UhUFGwUX6muqkCYYDIAZ-f7EI-yJiU-3~KDwWu77z-8XDjaJIf78tNV6kN3MvRwUSbBRLD7pAa3HtdhNnNyO-PPBJJVMxorQ-~AgEhmdgLj39WfgetETiRm4ew0NsN6IOHWn~~vh26FJ4awhLpJ0fCBD-fPve~jTbCovjsszKMBumTslvQS~p-dfjfQSTMRZAE3fs262NjdEwoVZP-k~~0HmzOmV9M-Oe8yez6ORDKm8sDyeDoQ~e21b77SiE2tNrgHeB3siITcZoTNyICX~ZSG~Qnb4k4kXdllVkqmXaf7k0pYE1yTKXM3sKphP~VJFMBgdSzZPprhp6RGjWibKy~94Ns8IyCA6c08810yodEdoeU53acQPp~wk0FP1HgcF3tVggk-wHg65cD09ncjOmly5Gc7uZAAAA
|
||||
frosk.i2p=Dez-OBQLhsI7Wq~O8YmYYJjl~IPvqUCZbLIFVMnyyqOCG9TEO~kGUVpWVD5nF0B5fE8kEWq8CuGKeYbVfqt0Vuz6Uvvo4gCdGnthohthbolIOZ9ZCci9MTNE6t29mJ2thHqc9Nw5TvYJGcvCVNi569~o5PDtYhZjVsOtrv8r3f9fUcUV-wKokD-y0fdF3Dzw4I7tJ6pfohj4avzDwwndAHXnNjB9mDgQsfjPF2KtvWLzXyAsCmfCoanV65tkc09ZX4x5QkekdVgTbD0BcHDP-o71MVufRsm4~ojRkbdSLw-VlbEcYFyeHTPBTpM68gTjueJlRwmvpXyP46w~Xnl9IEDfWu92CV9xjRt8qDxiyQ9jXR6dbWLptTF2UCznkBWvWph7P2wliTiawt~1bWGw2YvkfB6EaENYvDNY2cGn0GxpPhL4K19slEwJqX9f~EzLKYx4xaB5I8~W9syJAJSX8PsJbUlk6OhbbRoYOndd6OCRqHRUyh1w7dM~LvbY0W8hAAAA
|
||||
forum.fr.i2p=glvyM8eKBTYnlwEl3hVWDRBvfIfe-utzjGAlvDM-rwB4tb4CaM49Aeb5k0lxm7IaFjkp0u9t~1a2KIxwrU~Mj-JTJPKf0TrElVfOT6P0sVFOD3U1wllFwne5e7~0d8MeuylMrBe3dlz5KSPQ69stpDTbJgxcynoKiPMWcbmq~yQjnIvtDIpAPaB1JVYNo8mDhqMqg8GLrk9ttTHO90dYUpdI5lgxB6vR1nuPPwyFczOjVn8GvmT1DXGNoc6yAW0gCLuk7Hq-jNcNtXkNRC5TTdNiBLdLpsNMD3kKWDM04fODM5N~qp-nww-lEqfuZ3f1RT3gpjDwpMMAtKbd~VsRaCEINL5iBaAlZupW6fYTiuGUUdOZ739JK9GpQDsPXBOy~VSoM~cLXJCih12v-GDODDN0i8sSkqZ7CXDGg~jkg0ab2Y-Y2JnpYFLsgYziPtNRij1~rMCcC~-huiSfQio15gxczb~RRS-lpBQgyfaZ5JgIR2uVjWWe-uwKD-WquuIPAAAA
|
||||
fedo.i2p=pklxM9~hpluoCoiPgzMBpTHXMpwEdCMMNEatVc4gnQDOXsKkW5FbUMQ9y0vj6EUs2vUZLkwp3FP3-PuJEHMCLCTBdkVlE5JErwhZU6V3S5lOZ2jTrSuYAhcph~ML~ntjDzc-IZWgAax98ChzCpwxhEk4eP4B9vEhMG7njoxbJLfwAYu1qgq0A-Whm08rAhAcDwl8a-21Kqj8~iGq0XytMqyJU5XArq4aArWEwZFFLKJD9uR9bKeMaHIkJLNAyKIvfpgwRjYIMlIf6ekN3cpBU3B6c8NgDNPNqNBI6DQ1XGfKOEqX6d31mTOSzyjs-zgIJkt5zg-MH1pnMSVAI-vQbiAaPJc3Rjeqarv5Z89ZX~dghQFNQX1Wd7ezJy0WltaY6V7lLxd6QPTosDCDawT7ZoNhmLUNtFXI7oGS4Cm4ZTZAjZV-ZFMUXWUKFTZGyh4s2WbYqJq1Sackk8JmCvZ7JHazJ9ghnrQQ2bpzWeeGgtCPIcTnaUMPOZdsU96ehme7AAAA
|
||||
pastebin.i2p=mUDz9K6KmWe2zE4wj~YjqwD5f8pCjEbze-DuafzjtXOaj9SnRBrLNqgOTt063y9foZett484g9PFm~3ibqFZfUk3LsJi6YhZje-V~RZndBElRJ1cX~MBOG5wdHA2BYpBt7jX-N5J9ww7POtcPFDjyYlJLhRrY5FuRfxdsWJ4BOUHOwvbTq7IWvqHReayte6vKavpyvNcAotcFHAhOGpR6Ua3RncS~b6NA5k2CQIeS4mR6~iNCh0sx1gj4cWWWqXSa6pN19io82L2fcNEcxE-UETj4HkG5fTdBlB4I2cNAm1KnxQw9ntRf19p5EzYkOFYST5ra~RWmy0bWzJMBFlK9QcogVi97gmszuNPyfIpJIE1ssZ6BNHKMkPJ-fjxtSAseTBV-Fa51TIBwNyXC6VxMsixKIBX4Cg64oipoGpm~-7SdRgabHbB0vWwiV6RYEQ88oNcV2ycDqjyfQymT0T5S9b0aVd2Ey1ZWly-PR44~uC3mCctcMqfZVfNFs3NoY3ZAAAA
|
||||
sciencebooks.i2p=Rrqf6EmeuqckygNrim-ZcZ97uEIL9Ykkr8cj1RSGbeih33~UUpjAp0x2fxrpbibXwuGufWMNoaMkZH8FxOxZr1hvBeV4Mvjrn05kKWyPpBB1x7rAcVCCoD~bl6CUU2k4NtFrO9~BCCZdoIvMxXYWRy4nj18RVaIlOS3qE6~F04pU7Fy0sj7xtd02wgeyKkJ6pXYGfETMr6phVusHSSQfCumDBpnqEt7EcXTIsPz58y9Zim4u1wp6xj~OvGWFIP8fKIxpH~zf6o0qYytXrf6SJbIEvwITKGIXZTM5KxQ0LbEydSgPn4q5PttQlKg~7hxuGG5RYqP2IMg-f9z4-1SfYTbz~EixEq8gv0R30c60tqsyAaw9o37R3k9inkT5WylYwBKM0pzfcAVjejcoPYnFF1iithGl0AYkP~~isxCErCNwLs39NyE688C5amDIpXl7AgB~IvbwZfONbd2cwMOB2RFzTShYZlwcILB7EzgMvEt2l8GBkstP3CzNrjQ2gAL0AAAA
|
||||
piespy.i2p=jTCW8w04d~Sucb7OXFBZtq7tEOxeFBNm5T4uVZW3xNapThxk0Tie8OAIQMmz4lzDUaHoc97DN1s6k3rTprRrhbr7Idyls5I-r6jAQnrljqUveIMA-lbzXDYTgo8TqBR4~fpSL4RM5u4~sM4ZRKdwufnEjCSlYtTe8qmrofFx3cIuIKhfypsYcu-BEdrw7P~cRpmqJqBV~igulwfIxeABa8ygX~Uk~i68NELte55J70z~kijYGMAgUJVhtQxD~jZD5l3FrGkND9lVaam0lHseujNyy~ZB-ma5IKqJWKLEo19e224EVhIK4jcKYdnr7bE86mZiWg36GvWFhjJOkEvCJ3BWxaMjcDXyk-9vCY7MA403OCMGGHoKlzB2AKq1PEc1teMtoqMG~a42DTKevUbbiALtauxCe-BOxEtxpYTvvql-RVumrUh3hckh0wqAd1Gqr2uqRx4VaCF3X3duOxdoKV6Kgf~icfVIFq5HR7AhN63BGFhAeQLcudp3oA828a06AAAA
|
||||
dm.i2p=ZPCnBTv~~EZTvbq4D4Lscn4AL~VMwbmKSLEF4Aq8bzynQm~kGnkSLhrDpNaJyAvO0F9wX-7bxyu3KLmpE~0KZt-6-J2spfBqkRTOd39j1YjJ1HHXivWiI8yl4uoHXZun6UW~QdwKQPzAMBnl2W4rXpQekGxwh7HlnQT22FyI-ELtzh8FE8JJ6DxfYsDKd49QnsePM3r2dB6hWb0lPEf-DwSD8JtsRmXMmDfa16-yWt5Wa0K-xrWbKh~oQ8K1aXhHoUxsfJ~LQnklt~QGld9Cbk7q1hJKXFUSqCoLaADZEmvzuNlzE53ChK~W6IanEZ4WeNT7dMJaMtta2Ot5Ym9dXGygFp2XE5zGsOpq38XGNHHI3iKNCexIh0eTL3jwwkUQXl7UPWxrykRff8uRSgBq6s669lOzUbZfdmAbkTG6NvwU0RboQQ-OYs7APDn5ovXHI3zl0bRljZWBxNVswz34fQ~jkA5aHUcgV~sNgwjjz5um7K9V-QEVUBi68AhJbmvRAAAA
|
||||
up.i2p=D7MtDuWfl9XOkSDC~hCH7QBrnDhg2KdxFq2dyq0e2jJypmXPFSw0Hl6HOMdI3myFTtweKZObHaPzy4c3-hk9ZwBsqzT8XquTVoa~z7OAtoj0QQDdIGXaHpDvVWkj9wAilpJfypqThVho08F0cq9~yChBh2s02JoQersRQ4l9oMQaOSVS85wGudFYSeWUhRPJyb0FN4ic7KPRvFlOBnESCtQUxMa0HV28UDyc9kGUN8AmgaV2qi-LcdrxHsYwzbYKZVa6Tg3mHEqksuzH5AK~9KYHlzLz2QRDesL0jkf7WdVRlKSiqkrYQVSG7MAqHfHNhQbb9oIpqDARyAqUaPJyEVQRAphJ9qv-K54QW1T2Js5tF5XVJoZcU04d9aHyxaUKqWJwIMrRlfeKVGAPlE7pcUjQrTKe6-22-46CzKpHsG8j4kwSyvrwlpGdsbN7-q9tq3KH4b9hJiv5nsQ4lSBQ9Um6YCEmUdwffnfbbfVGPCmj9XcMSrnHfvCD2rf4-V9sAAAA
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
$Id: install-headless.txt,v 1.2 2004/09/02 01:54:37 jrandom Exp $
|
||||
$Id: install-headless.txt,v 1.3 2004/09/29 14:38:15 jrandom Exp $
|
||||
Headless I2P installation instructions
|
||||
|
||||
1) tar xjf i2p.tar.bz2 (you've already done this)
|
||||
@@ -10,7 +10,8 @@ If you're having trouble, swing by http://forum.i2p.net/, check the
|
||||
website at http://www.i2p.net/, or get on irc://irc.freenode.net/#i2p
|
||||
|
||||
To run I2P explicitly:
|
||||
sh i2prouter start
|
||||
(*nix): sh i2prouter start
|
||||
(win*): I2Psvc.exe -c wrapper.config
|
||||
|
||||
To stop the router (gracefully):
|
||||
lynx http://localhost:7657/configservice.jsp ("Shutdown gracefully")
|
||||
@@ -26,4 +27,4 @@ Supported JVMs:
|
||||
Linux: Latest available from http://java.sun.com/ (1.3+ supported)
|
||||
FreeBSD: /usr/ports/java/linux-sun-jdk1.4
|
||||
various: http://www.kaffe.org/ using CVS HEAD as of Sept 1, 2004
|
||||
(or any subsequent releases)
|
||||
(or any subsequent releases)
|
||||
|
||||
26
install.txt
Normal file
26
install.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
$Id$
|
||||
I2P source installation instructions
|
||||
|
||||
To build and install I2P from source, you must first build
|
||||
and package up the appropriate installer by running:
|
||||
|
||||
ant dist
|
||||
|
||||
This will produce a few key files:
|
||||
* i2p.tar.bz2: the headless installation
|
||||
* install.jar: the GUI installer
|
||||
* i2pupdate.zip: the update package
|
||||
|
||||
From there, you can follow the headless installation instructions
|
||||
with the headless installer, run the GUI installer, or deploy
|
||||
the update into an existing installation.
|
||||
|
||||
You will need to have ant installed from http://ant.apache.org/
|
||||
(1.5 or newer)
|
||||
|
||||
Supported JVMs:
|
||||
Windows: Latest available from http://java.sun.com/ (1.3+ supported)
|
||||
Linux: Latest available from http://java.sun.com/ (1.3+ supported)
|
||||
FreeBSD: /usr/ports/java/linux-sun-jdk1.4
|
||||
various: http://www.kaffe.org/ using CVS HEAD as of Sept 1, 2004
|
||||
(or any subsequent releases)
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<info>
|
||||
<appname>i2p</appname>
|
||||
<appversion>0.4.2.2</appversion>
|
||||
<appversion>0.4.2.5</appversion>
|
||||
<authors>
|
||||
<author name="I2P" email="support@i2p.net"/>
|
||||
</authors>
|
||||
|
||||
@@ -1,39 +1,14 @@
|
||||
@echo off
|
||||
setlocal
|
||||
set INSTALL_PATH="%1"
|
||||
|
||||
rem
|
||||
rem Java Service Wrapper general startup script
|
||||
rem
|
||||
|
||||
rem
|
||||
rem Resolve the real path of the Wrapper.exe
|
||||
rem For non NT systems, the _REALPATH and _WRAPPER_CONF values
|
||||
rem can be hard-coded below and the following test removed.
|
||||
rem
|
||||
if "%OS%"=="Windows_NT" goto nt
|
||||
echo This script only works with NT-based versions of Windows.
|
||||
goto :eof
|
||||
set _WRAPPER_EXE=%INSTALL_PATH%I2Psvc.exe
|
||||
set _WRAPPER_CONF="%INSTALL_PATH%wrapper.config"
|
||||
|
||||
:nt
|
||||
rem
|
||||
rem Find the application home.
|
||||
rem
|
||||
rem %~dp0 is location of current script under NT
|
||||
set _REALPATH=%~dp0
|
||||
set _WRAPPER_EXE=%_REALPATH%I2Psvc.exe
|
||||
|
||||
rem
|
||||
rem Find the wrapper.conf
|
||||
rem
|
||||
:conf
|
||||
set _WRAPPER_CONF="%~f1"
|
||||
if not %_WRAPPER_CONF%=="" goto startup
|
||||
set _WRAPPER_CONF="%_REALPATH%wrapper.config"
|
||||
|
||||
rem
|
||||
rem Start the Wrapper
|
||||
rem
|
||||
:startup
|
||||
"%_WRAPPER_EXE%" -c %_WRAPPER_CONF%
|
||||
if not errorlevel 1 goto :eof
|
||||
pause
|
||||
|
||||
@@ -5,7 +5,7 @@ tunnel.0.type=httpclient
|
||||
tunnel.0.interface=127.0.0.1
|
||||
tunnel.0.listenPort=4444
|
||||
tunnel.0.proxyList=squid.i2p,www1.squid.i2p
|
||||
tunnel.0.i2cpHost=localhost
|
||||
tunnel.0.i2cpHost=127.0.0.1
|
||||
tunnel.0.i2cpPort=7654
|
||||
tunnel.0.option.tunnels.depthInbound=2
|
||||
tunnel.0.option.tunnels.numInbound=2
|
||||
@@ -19,7 +19,7 @@ tunnel.1.type=client
|
||||
tunnel.1.interface=127.0.0.1
|
||||
tunnel.1.listenPort=6668
|
||||
tunnel.1.targetDestination=irc.duck.i2p,irc.baffled.i2p
|
||||
tunnel.1.i2cpHost=localhost
|
||||
tunnel.1.i2cpHost=127.0.0.1
|
||||
tunnel.1.i2cpPort=7654
|
||||
tunnel.1.option.tunnels.depthInbound=2
|
||||
tunnel.1.option.tunnels.numInbound=2
|
||||
@@ -33,7 +33,7 @@ tunnel.2.type=client
|
||||
tunnel.2.interface=127.0.0.1
|
||||
tunnel.2.listenPort=2401
|
||||
tunnel.2.targetDestination=cvs.i2p
|
||||
tunnel.2.i2cpHost=localhost
|
||||
tunnel.2.i2cpHost=127.0.0.1
|
||||
tunnel.2.i2cpPort=7654
|
||||
tunnel.2.option.tunnels.depthInbound=2
|
||||
tunnel.2.option.tunnels.numInbound=2
|
||||
@@ -42,11 +42,12 @@ tunnel.2.startOnLoad=false
|
||||
# local eepserver
|
||||
tunnel.3.name=eepsite
|
||||
tunnel.3.description=My eepsite
|
||||
tunnel.3.type=server
|
||||
tunnel.3.targetHost=localhost
|
||||
tunnel.3.type=httpserver
|
||||
tunnel.3.targetHost=127.0.0.1
|
||||
tunnel.3.targetPort=7658
|
||||
tunnel.3.spoofedHost=mysite.i2p
|
||||
tunnel.3.privKeyFile=eepsite/eepPriv.dat
|
||||
tunnel.3.i2cpHost=localhost
|
||||
tunnel.3.i2cpHost=127.0.0.1
|
||||
tunnel.3.i2cpPort=7654
|
||||
tunnel.3.option.tunnels.depthInbound=2
|
||||
tunnel.3.option.tunnels.numInbound=2
|
||||
@@ -54,7 +55,7 @@ tunnel.3.startOnLoad=true
|
||||
|
||||
# postman's SMTP server - see www.postman.i2p
|
||||
tunnel.4.description=smtp server
|
||||
tunnel.4.i2cpHost=localhost
|
||||
tunnel.4.i2cpHost=127.0.0.1
|
||||
tunnel.4.i2cpPort=7654
|
||||
tunnel.4.interface=127.0.0.1
|
||||
tunnel.4.listenPort=7659
|
||||
@@ -69,7 +70,7 @@ tunnel.4.type=client
|
||||
# postman's POP3 server - see www.postman.i2p
|
||||
tunnel.5.name=pop3.postman.i2p
|
||||
tunnel.5.description=pop3 server
|
||||
tunnel.5.i2cpHost=localhost
|
||||
tunnel.5.i2cpHost=127.0.0.1
|
||||
tunnel.5.i2cpPort=7654
|
||||
tunnel.5.interface=127.0.0.1
|
||||
tunnel.5.listenPort=7660
|
||||
@@ -78,4 +79,4 @@ tunnel.5.option.tunnels.numInbound=2
|
||||
tunnel.5.option.i2p.streaming.connectDelay=1000
|
||||
tunnel.5.startOnLoad=false
|
||||
tunnel.5.targetDestination=pop.postman.i2p
|
||||
tunnel.5.type=client
|
||||
tunnel.5.type=client
|
||||
|
||||
@@ -33,7 +33,7 @@ del /f /q "%INSTALL_PATH%postinstall.sh"
|
||||
:: del /f /q "%INSTALL_PATH%uninstall_i2p_service_unix"
|
||||
del /f /q "%INSTALL_PATH%icons\*.xpm"
|
||||
rmdir /q /s "%INSTALL_PATH%lib\wrapper"
|
||||
start /b /i /d"%INSTALL_PATH%" i2prouter.bat
|
||||
start /b /i /d"%INSTALL_PATH%" i2prouter.bat %INSTALL_PATH%
|
||||
|
||||
) else (
|
||||
|
||||
@@ -47,6 +47,6 @@ del "%INSTALL_PATH%postinstall.sh"
|
||||
del "%INSTALL_PATH%uninstall_i2p_service_winnt.bat"
|
||||
del "%INSTALL_PATH%icons\*.xpm"
|
||||
deltree /Y "%INSTALL_PATH%lib\wrapper"
|
||||
start /M "%INSTALL_PATH%i2prouter.bat"
|
||||
start /M "%INSTALL_PATH%i2prouter.bat" %INSTALL_PATH%
|
||||
|
||||
)
|
||||
|
||||
@@ -8,8 +8,12 @@ package net.i2p.router;
|
||||
*
|
||||
*/
|
||||
|
||||
//import net.i2p.router.message.ProcessOutboundClientMessageJob;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.client.I2PClient;
|
||||
|
||||
import net.i2p.router.message.OutboundClientMessageJob;
|
||||
import net.i2p.router.message.OutboundClientMessageOneShotJob;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -55,7 +59,22 @@ public class ClientMessagePool {
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Adding message for remote delivery");
|
||||
_context.jobQueue().addJob(new OutboundClientMessageJob(_context, msg));
|
||||
if (isGuaranteed(msg))
|
||||
_context.jobQueue().addJob(new OutboundClientMessageJob(_context, msg));
|
||||
else
|
||||
_context.jobQueue().addJob(new OutboundClientMessageOneShotJob(_context, msg));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isGuaranteed(ClientMessage msg) {
|
||||
Properties opts = null;
|
||||
if (msg.getSenderConfig() != null)
|
||||
opts = msg.getSenderConfig().getOptions();
|
||||
if (opts != null) {
|
||||
String val = opts.getProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT);
|
||||
return val.equals(I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,9 +75,13 @@ public class Router {
|
||||
public final static String PROP_SHUTDOWN_IN_PROGRESS = "__shutdownInProgress";
|
||||
|
||||
static {
|
||||
// grumble about sun's java caching DNS entries *forever*
|
||||
System.setProperty("sun.net.inetaddr.ttl", "0");
|
||||
System.setProperty("networkaddress.cache.ttl", "0");
|
||||
// grumble about sun's java caching DNS entries *forever* by default
|
||||
// so lets just keep 'em for a minute
|
||||
System.setProperty("sun.net.inetaddr.ttl", "60");
|
||||
System.setProperty("networkaddress.cache.ttl", "60");
|
||||
// until we handle restricted routes and/or all peers support v6, try v4 first
|
||||
System.setProperty("java.net.preferIPv4Stack", "true");
|
||||
System.setProperty("http.agent", "I2P");
|
||||
// (no need for keepalive)
|
||||
System.setProperty("http.keepAlive", "false");
|
||||
System.setProperty("user.timezone", "GMT");
|
||||
@@ -272,6 +276,9 @@ public class Router {
|
||||
super(Router.this._context);
|
||||
Router.this._context.statManager().createRateStat("bw.receiveBps", "How fast we receive data", "Bandwidth", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
|
||||
Router.this._context.statManager().createRateStat("bw.sendBps", "How fast we send data", "Bandwidth", new long[] { 60*1000, 5*60*1000, 60*60*1000 });
|
||||
Router.this._context.statManager().createRateStat("router.activePeers", "How many peers we are actively talking with", "Throttle", new long[] { 5*60*1000, 60*60*1000 });
|
||||
Router.this._context.statManager().createRateStat("router.highCapacityPeers", "How many high capacity peers we know", "Throttle", new long[] { 5*60*1000, 60*60*1000 });
|
||||
Router.this._context.statManager().createRateStat("router.fastPeers", "How many fast peers we know", "Throttle", new long[] { 5*60*1000, 60*60*1000 });
|
||||
}
|
||||
public String getName() { return "Coalesce stats"; }
|
||||
public void runJob() {
|
||||
@@ -296,6 +303,15 @@ public class Router {
|
||||
Router.this._context.statManager().addRateData("bw.sendBps", (long)bps, 60*1000);
|
||||
}
|
||||
}
|
||||
|
||||
int active = Router.this._context.commSystem().countActivePeers();
|
||||
Router.this._context.statManager().addRateData("router.activePeers", active, 60*1000);
|
||||
|
||||
int fast = Router.this._context.profileOrganizer().countFastPeers();
|
||||
Router.this._context.statManager().addRateData("router.fastPeers", fast, 60*1000);
|
||||
|
||||
int highCap = Router.this._context.profileOrganizer().countHighCapacityPeers();
|
||||
Router.this._context.statManager().addRateData("router.highCapacityPeers", highCap, 60*1000);
|
||||
|
||||
requeue(60*1000);
|
||||
}
|
||||
@@ -792,6 +808,8 @@ public class Router {
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("Starting I2P " + RouterVersion.VERSION + "-" + RouterVersion.BUILD);
|
||||
System.out.println(RouterVersion.ID);
|
||||
installUpdates();
|
||||
verifyWrapperConfig();
|
||||
Router r = new Router();
|
||||
|
||||
@@ -30,6 +30,8 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
private static int THROTTLE_EVENT_LIMIT = 300;
|
||||
|
||||
private static final String PROP_MAX_TUNNELS = "router.maxParticipatingTunnels";
|
||||
private static final String PROP_DEFAULT_KBPS_THROTTLE = "router.defaultKBpsThrottle";
|
||||
private static final String PROP_BANDWIDTH_SHARE_PERCENTAGE = "router.sharePercentage";
|
||||
|
||||
public RouterThrottleImpl(RouterContext context) {
|
||||
_context = context;
|
||||
@@ -43,6 +45,7 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
_context.statManager().createRateStat("router.throttleTunnelMaxExceeded", "How many tunnels we are participating in when we refuse one due to excees?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("router.throttleTunnelProbTooFast", "How many tunnels beyond the previous 1h average are we participating in when we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("router.throttleTunnelProbTestSlow", "How slow are our tunnel tests when our average exceeds the old average and we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("router.throttleTunnelBandwidthExceeded", "How much bandwidth is allocated when we refuse due to bandwidth allocation?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
public boolean acceptNetworkMessage() {
|
||||
@@ -107,22 +110,7 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ok, we're not hosed, but can we handle the bandwidth requirements
|
||||
// of another tunnel?
|
||||
rs = _context.statManager().getRate("tunnel.participatingMessagesProcessed");
|
||||
r = null;
|
||||
if (rs != null)
|
||||
r = rs.getRate(10*60*1000);
|
||||
double msgsPerTunnel = (r != null ? r.getAverageValue() : 0);
|
||||
r = null;
|
||||
rs = _context.statManager().getRate("tunnel.relayMessageSize");
|
||||
if (rs != null)
|
||||
r = rs.getRate(10*60*1000);
|
||||
double bytesPerMsg = (r != null ? r.getAverageValue() : 0);
|
||||
double bytesPerTunnel = msgsPerTunnel * bytesPerMsg;
|
||||
|
||||
int numTunnels = _context.tunnelManager().getParticipatingCount();
|
||||
double bytesAllocated = (numTunnels + 1) * bytesPerTunnel;
|
||||
|
||||
if (_context.getProperty(Router.PROP_SHUTDOWN_IN_PROGRESS) != null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -206,10 +194,21 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
// no default, ignore it
|
||||
}
|
||||
}
|
||||
|
||||
// ok, we're not hosed, but can we handle the bandwidth requirements
|
||||
// of another tunnel?
|
||||
rs = _context.statManager().getRate("tunnel.participatingBytesProcessed");
|
||||
r = null;
|
||||
if (rs != null)
|
||||
r = rs.getRate(10*60*1000);
|
||||
double bytesAllocated = r.getCurrentTotalValue();
|
||||
|
||||
if (!allowTunnel(bytesAllocated, numTunnels)) {
|
||||
_context.statManager().addRateData("router.throttleTunnelBandwidthExceeded", (long)bytesAllocated, 0);
|
||||
return false;
|
||||
}
|
||||
_context.statManager().addRateData("tunnel.bytesAllocatedAtAccept", (long)bytesAllocated, msg.getTunnelDurationSeconds()*1000);
|
||||
// todo: um, throttle (include bw usage of the netDb, our own tunnels, the clients,
|
||||
// and check to see that they are less than the bandwidth limits
|
||||
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Accepting a new tunnel request (now allocating " + bytesAllocated + " bytes across " + numTunnels
|
||||
@@ -217,6 +216,99 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* with bytesAllocated already accounted for across the numTunnels existing
|
||||
* tunnels we have agreed to, can we handle another tunnel with our existing
|
||||
* bandwidth?
|
||||
*
|
||||
*/
|
||||
private boolean allowTunnel(double bytesAllocated, int numTunnels) {
|
||||
long bytesAllowed = getBytesAllowed();
|
||||
|
||||
bytesAllowed *= getSharePercentage();
|
||||
|
||||
double bytesPerTunnel = (numTunnels > 0 ? bytesAllocated / numTunnels : 0);
|
||||
double toAllocate = (numTunnels > 0 ? bytesPerTunnel * (numTunnels + 1) : 0);
|
||||
|
||||
double pctFull = toAllocate / bytesAllowed;
|
||||
|
||||
double allocatedKBps = toAllocate / (10 * 60 * 1024);
|
||||
|
||||
if (pctFull < 1.0) { // (_context.random().nextInt(100) > 100 * pctFull) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Probabalistically allowing the tunnel w/ " + pctFull + " of our " + bytesAllowed
|
||||
+ "bytes/" + allocatedKBps + "KBps allocated through " + numTunnels + " tunnels");
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Rejecting the tunnel w/ " + pctFull + " of our " + bytesAllowed
|
||||
+ "bytes allowed (" + toAllocate + "bytes / " + allocatedKBps
|
||||
+ "KBps) through " + numTunnels + " tunnels");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* What fraction of the bandwidth specified in our bandwidth limits should
|
||||
* we allow to be consumed by participating tunnels?
|
||||
*
|
||||
*/
|
||||
private double getSharePercentage() {
|
||||
String pct = _context.getProperty(PROP_BANDWIDTH_SHARE_PERCENTAGE, "0.8");
|
||||
if (pct != null) {
|
||||
try {
|
||||
return Double.parseDouble(pct);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unable to get the share percentage");
|
||||
}
|
||||
}
|
||||
return 0.8;
|
||||
}
|
||||
|
||||
/**
|
||||
* BytesPerSecond that we can pass along data
|
||||
*/
|
||||
private long getBytesAllowed() {
|
||||
String kbpsOutStr = _context.getProperty("i2np.bandwidth.outboundKBytesPerSecond");
|
||||
long kbpsOut = -1;
|
||||
if (kbpsOutStr != null) {
|
||||
try {
|
||||
kbpsOut = Integer.parseInt(kbpsOutStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unable to get the bytes allowed (outbound)");
|
||||
}
|
||||
}
|
||||
|
||||
String kbpsInStr = _context.getProperty("i2np.bandwidth.inboundKBytesPerSecond");
|
||||
long kbpsIn = -1;
|
||||
if (kbpsInStr != null) {
|
||||
try {
|
||||
kbpsIn = Integer.parseInt(kbpsInStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unable to get the bytes allowed (inbound)");
|
||||
}
|
||||
}
|
||||
|
||||
// whats our choke?
|
||||
long kbps = (kbpsOut > kbpsIn ? kbpsIn : kbpsOut);
|
||||
|
||||
if (kbps <= 0) {
|
||||
try {
|
||||
kbps = Integer.parseInt(_context.getProperty(PROP_DEFAULT_KBPS_THROTTLE, "64")); // absurd
|
||||
} catch (NumberFormatException nfe) {
|
||||
kbps = 64;
|
||||
}
|
||||
}
|
||||
|
||||
return kbps
|
||||
* 60 // per minute
|
||||
* 10 // per 10 minute period
|
||||
* 1024; // bytes;
|
||||
}
|
||||
|
||||
/** dont ever probabalistically throttle tunnels if we have less than this many */
|
||||
private int getMinThrottleTunnels() {
|
||||
try {
|
||||
|
||||
@@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
|
||||
*
|
||||
*/
|
||||
public class RouterVersion {
|
||||
public final static String ID = "$Revision: 1.96 $ $Date: 2004/12/01 17:31:56 $";
|
||||
public final static String VERSION = "0.4.2.2";
|
||||
public final static String ID = "$Revision: 1.122 $ $Date: 2004/12/21 11:32:50 $";
|
||||
public final static String VERSION = "0.4.2.5";
|
||||
public final static long BUILD = 0;
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Router version: " + VERSION);
|
||||
|
||||
@@ -103,6 +103,7 @@ public class StatisticsManager implements Service {
|
||||
|
||||
includeThroughput(stats);
|
||||
includeRate("transport.sendProcessingTime", stats, new long[] { 60*60*1000 });
|
||||
includeRate("tcp.probabalisticDropQueueSize", stats, new long[] { 60*1000l, 60*60*1000l });
|
||||
//includeRate("tcp.queueSize", stats);
|
||||
//includeRate("jobQueue.jobLag", stats, new long[] { 60*1000, 60*60*1000 });
|
||||
//includeRate("jobQueue.jobRun", stats, new long[] { 60*1000, 60*60*1000 });
|
||||
@@ -114,6 +115,8 @@ public class StatisticsManager implements Service {
|
||||
//includeRate("jobQueue.droppedJobs", stats, new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
//includeRate("inNetPool.dropped", stats, new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
includeRate("tunnel.participatingTunnels", stats, new long[] { 5*60*1000, 60*60*1000 });
|
||||
includeRate("tunnel.participatingBytesProcessed", stats, new long[] { 10*60*1000 });
|
||||
includeRate("tunnel.participatingBytesProcessedActive", stats, new long[] { 10*60*1000 });
|
||||
includeRate("tunnel.testSuccessTime", stats, new long[] { 60*60*1000l, 24*60*60*1000l });
|
||||
//includeRate("tunnel.outboundMessagesProcessed", stats, new long[] { 10*60*1000, 60*60*1000 });
|
||||
//includeRate("tunnel.inboundMessagesProcessed", stats, new long[] { 10*60*1000, 60*60*1000 });
|
||||
|
||||
@@ -56,6 +56,7 @@ public class TunnelInfo extends DataStructureImpl {
|
||||
private boolean _wasEverReady;
|
||||
private int _messagesProcessed;
|
||||
private int _tunnelFailures;
|
||||
private long _bytesProcessed;
|
||||
|
||||
public TunnelInfo(I2PAppContext context) {
|
||||
_context = context;
|
||||
@@ -79,6 +80,7 @@ public class TunnelInfo extends DataStructureImpl {
|
||||
_lastTested = -1;
|
||||
_messagesProcessed = 0;
|
||||
_tunnelFailures = 0;
|
||||
_bytesProcessed = 0;
|
||||
}
|
||||
|
||||
public TunnelId getTunnelId() { return _id; }
|
||||
@@ -182,7 +184,12 @@ public class TunnelInfo extends DataStructureImpl {
|
||||
return _messagesProcessed;
|
||||
}
|
||||
/** we have just processed a message for this tunnel */
|
||||
public void messageProcessed() { _messagesProcessed++; }
|
||||
public void messageProcessed(int size) {
|
||||
_messagesProcessed++;
|
||||
_bytesProcessed += size;
|
||||
}
|
||||
/** how many bytes have been pumped through this tunnel in its lifetime? */
|
||||
public long getBytesProcessed() { return _bytesProcessed; }
|
||||
|
||||
/**
|
||||
* the tunnel was (potentially) unable to pass a message through.
|
||||
|
||||
@@ -9,6 +9,7 @@ package net.i2p.router.client;
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
@@ -28,15 +29,21 @@ public class ClientListenerRunner implements Runnable {
|
||||
private ClientManager _manager;
|
||||
private ServerSocket _socket;
|
||||
private int _port;
|
||||
private boolean _bindAllInterfaces;
|
||||
private boolean _running;
|
||||
private long _nextFailDelay = 1000;
|
||||
|
||||
public static final String BIND_ALL_INTERFACES = "i2cp.tcp.bindAllInterfaces";
|
||||
|
||||
public ClientListenerRunner(RouterContext context, ClientManager manager, int port) {
|
||||
_context = context;
|
||||
_log = _context.logManager().getLog(ClientListenerRunner.class);
|
||||
_manager = manager;
|
||||
_port = port;
|
||||
_running = false;
|
||||
|
||||
String val = context.getProperty(BIND_ALL_INTERFACES, "False");
|
||||
_bindAllInterfaces = Boolean.valueOf(val).booleanValue();
|
||||
}
|
||||
|
||||
public void setPort(int port) { _port = port; }
|
||||
@@ -55,7 +62,11 @@ public class ClientListenerRunner implements Runnable {
|
||||
while (_running) {
|
||||
try {
|
||||
_log.info("Starting up listening for connections on port " + _port);
|
||||
_socket = new ServerSocket(_port);
|
||||
if (_bindAllInterfaces)
|
||||
_socket = new ServerSocket(_port);
|
||||
else
|
||||
_socket = new ServerSocket(_port, 0, InetAddress.getByName("127.0.0.1"));
|
||||
|
||||
curDelay = 0;
|
||||
while (_running) {
|
||||
try {
|
||||
@@ -82,7 +93,7 @@ public class ClientListenerRunner implements Runnable {
|
||||
if (_context.router().isAlive())
|
||||
_log.error("Error listening on port " + _port, ioe);
|
||||
}
|
||||
|
||||
|
||||
if (_socket != null) {
|
||||
try { _socket.close(); } catch (IOException ioe) {}
|
||||
_socket = null;
|
||||
|
||||
@@ -25,8 +25,6 @@ class ClientWriterRunner implements Runnable {
|
||||
|
||||
private static final long MAX_WAIT = 5*1000;
|
||||
|
||||
/** notify this lock when there are messages to write */
|
||||
private Object _activityLock = new Object();
|
||||
/** lock on this when updating the class level data structs */
|
||||
private Object _dataLock = new Object();
|
||||
|
||||
@@ -47,9 +45,7 @@ class ClientWriterRunner implements Runnable {
|
||||
synchronized (_dataLock) {
|
||||
_messagesToWrite.add(msg);
|
||||
_messagesToWriteTimes.add(new Long(_context.clock().now()));
|
||||
}
|
||||
synchronized (_activityLock) {
|
||||
_activityLock.notifyAll();
|
||||
_dataLock.notifyAll();
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("["+_id+"] addMessage completed for " + msg.getClass().getName());
|
||||
@@ -60,8 +56,8 @@ class ClientWriterRunner implements Runnable {
|
||||
*
|
||||
*/
|
||||
public void stopWriting() {
|
||||
synchronized (_activityLock) {
|
||||
_activityLock.notifyAll();
|
||||
synchronized (_dataLock) {
|
||||
_dataLock.notifyAll();
|
||||
}
|
||||
}
|
||||
public void run() {
|
||||
@@ -70,6 +66,9 @@ class ClientWriterRunner implements Runnable {
|
||||
List messageTimes = null;
|
||||
|
||||
synchronized (_dataLock) {
|
||||
if (_messagesToWrite.size() <= 0)
|
||||
try { _dataLock.wait(); } catch (InterruptedException ie) {}
|
||||
|
||||
if (_messagesToWrite.size() > 0) {
|
||||
messages = new ArrayList(_messagesToWrite.size());
|
||||
messageTimes = new ArrayList(_messagesToWriteTimes.size());
|
||||
@@ -80,19 +79,13 @@ class ClientWriterRunner implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
if (messages == null) {
|
||||
try {
|
||||
synchronized (_activityLock) {
|
||||
_activityLock.wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Interrupted while waiting for activity", ie);
|
||||
}
|
||||
} else {
|
||||
if (messages != null) {
|
||||
for (int i = 0; i < messages.size(); i++) {
|
||||
I2CPMessage msg = (I2CPMessage)messages.get(i);
|
||||
Long when = (Long)messageTimes.get(i);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("["+_id+"] writeMessage before writing "
|
||||
+ msg.getClass().getName());
|
||||
_runner.writeMessage(msg);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("["+_id+"] writeMessage time since addMessage(): "
|
||||
|
||||
@@ -240,7 +240,7 @@ public class HandleTunnelMessageJob extends JobImpl {
|
||||
if (info == null)
|
||||
return;
|
||||
|
||||
info.messageProcessed();
|
||||
info.messageProcessed(_message.getMessageSize());
|
||||
|
||||
//if ( (_message.getVerificationStructure() == null) && (info.getSigningKey() != null) ) {
|
||||
if (_message.getVerificationStructure() == null) {
|
||||
|
||||
@@ -0,0 +1,490 @@
|
||||
package net.i2p.router.message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Lease;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
|
||||
import net.i2p.data.i2np.DataMessage;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
|
||||
import net.i2p.router.message.PayloadGarlicConfig;
|
||||
|
||||
import net.i2p.router.ClientMessage;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.ReplyJob;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelSelectionCriteria;
|
||||
import net.i2p.router.MessageSelector;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Send a client message out a random outbound tunnel and into a random inbound
|
||||
* tunnel on the target leaseSet. This also bundles the sender's leaseSet and
|
||||
* a DeliveryStatusMessage (for ACKing any sessionTags used in the garlic).
|
||||
*
|
||||
*/
|
||||
public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
private Log _log;
|
||||
private long _overallExpiration;
|
||||
private boolean _shouldBundle;
|
||||
private ClientMessage _clientMessage;
|
||||
private MessageId _clientMessageId;
|
||||
private int _clientMessageSize;
|
||||
private Destination _from;
|
||||
private Destination _to;
|
||||
/** target destination's leaseSet, if known */
|
||||
private LeaseSet _leaseSet;
|
||||
/** Actual lease the message is being routed through */
|
||||
private Lease _lease;
|
||||
private PayloadGarlicConfig _clove;
|
||||
private long _cloveId;
|
||||
private long _start;
|
||||
private boolean _finished;
|
||||
|
||||
/**
|
||||
* final timeout (in milliseconds) that the outbound message will fail in.
|
||||
* This can be overridden in the router.config or the client's session config
|
||||
* (the client's session config takes precedence)
|
||||
*/
|
||||
public final static String OVERALL_TIMEOUT_MS_PARAM = "clientMessageTimeout";
|
||||
private final static long OVERALL_TIMEOUT_MS_DEFAULT = 60*1000;
|
||||
|
||||
/** priority of messages, that might get honored some day... */
|
||||
private final static int SEND_PRIORITY = 500;
|
||||
|
||||
/**
|
||||
* 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 = 100;
|
||||
|
||||
/**
|
||||
* Send the sucker
|
||||
*/
|
||||
public OutboundClientMessageOneShotJob(RouterContext ctx, ClientMessage msg) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(OutboundClientMessageOneShotJob.class);
|
||||
|
||||
ctx.statManager().createFrequencyStat("client.sendMessageFailFrequency", "How often does a client fail to send a message?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
ctx.statManager().createRateStat("client.sendMessageSize", "How large are messages sent by the client?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
ctx.statManager().createRateStat("client.sendAckTime", "How long does it take to get an ACK back from a message?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
ctx.statManager().createRateStat("client.timeoutCongestionTunnel", "How lagged our tunnels are when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
ctx.statManager().createRateStat("client.timeoutCongestionMessage", "How fast we process messages locally when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
ctx.statManager().createRateStat("client.timeoutCongestionInbound", "How much faster we are receiving data than our average bps when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
|
||||
long timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT;
|
||||
_clientMessage = msg;
|
||||
_clientMessageId = msg.getMessageId();
|
||||
_clientMessageSize = msg.getPayload().getSize();
|
||||
_from = msg.getFromDestination();
|
||||
_to = msg.getDestination();
|
||||
|
||||
String param = msg.getSenderConfig().getOptions().getProperty(OVERALL_TIMEOUT_MS_PARAM);
|
||||
if (param == null)
|
||||
param = ctx.router().getConfigSetting(OVERALL_TIMEOUT_MS_PARAM);
|
||||
if (param != null) {
|
||||
try {
|
||||
timeoutMs = Long.parseLong(param);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid client message timeout specified [" + param
|
||||
+ "], defaulting to " + OVERALL_TIMEOUT_MS_DEFAULT, nfe);
|
||||
timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
_start = getContext().clock().now();
|
||||
_overallExpiration = timeoutMs + _start;
|
||||
_shouldBundle = getShouldBundle();
|
||||
_finished = false;
|
||||
}
|
||||
|
||||
public String getName() { return "Outbound client message"; }
|
||||
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Send outbound client message job beginning");
|
||||
buildClove();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Clove built");
|
||||
long timeoutMs = _overallExpiration - getContext().clock().now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Send outbound client message - sending off leaseSet lookup job");
|
||||
getContext().netDb().lookupLeaseSet(_to.calculateHash(), new SendJob(), new LookupLeaseSetFailedJob(), timeoutMs);
|
||||
}
|
||||
|
||||
private boolean getShouldBundle() {
|
||||
Properties opts = _clientMessage.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 >= getContext().random().nextInt(100))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** send a message to a random lease */
|
||||
private class SendJob extends JobImpl {
|
||||
public SendJob() {
|
||||
super(OutboundClientMessageOneShotJob.this.getContext());
|
||||
}
|
||||
public String getName() { return "Send outbound client message through the lease"; }
|
||||
public void runJob() {
|
||||
boolean ok = getNextLease();
|
||||
if (ok)
|
||||
send();
|
||||
else
|
||||
dieFatal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch the next lease that we should try sending through, randomly chosen
|
||||
* from within the sorted leaseSet (sorted by # of failures through each
|
||||
* lease).
|
||||
*
|
||||
*/
|
||||
private boolean getNextLease() {
|
||||
_leaseSet = getContext().netDb().lookupLeaseSetLocally(_to.calculateHash());
|
||||
if (_leaseSet == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getJobId() + ": Lookup locally didn't find the leaseSet");
|
||||
return false;
|
||||
}
|
||||
long now = getContext().clock().now();
|
||||
|
||||
// get the possible leases
|
||||
List leases = new ArrayList(_leaseSet.getLeaseCount());
|
||||
for (int i = 0; i < _leaseSet.getLeaseCount(); i++) {
|
||||
Lease lease = _leaseSet.getLease(i);
|
||||
if (lease.isExpired(Router.CLOCK_FUDGE_FACTOR)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getJobId() + ": getNextLease() - expired lease! - " + lease);
|
||||
continue;
|
||||
} else {
|
||||
leases.add(lease);
|
||||
}
|
||||
}
|
||||
|
||||
if (leases.size() <= 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": No leases found from: " + _leaseSet);
|
||||
return false;
|
||||
}
|
||||
|
||||
// randomize the ordering (so leases with equal # of failures per next
|
||||
// sort are randomly ordered)
|
||||
Collections.shuffle(leases);
|
||||
|
||||
// ordered by lease number of failures
|
||||
TreeMap orderedLeases = new TreeMap();
|
||||
for (Iterator iter = leases.iterator(); iter.hasNext(); ) {
|
||||
Lease lease = (Lease)iter.next();
|
||||
long id = lease.getNumFailure();
|
||||
while (orderedLeases.containsKey(new Long(id)))
|
||||
id++;
|
||||
orderedLeases.put(new Long(id), lease);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": ranking lease we havent sent it down as " + id);
|
||||
}
|
||||
|
||||
_lease = (Lease)orderedLeases.get(orderedLeases.firstKey());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* we couldn't even find the leaseSet, but try again (or die
|
||||
* if we've already tried too hard)
|
||||
*
|
||||
*/
|
||||
private class LookupLeaseSetFailedJob extends JobImpl {
|
||||
public LookupLeaseSetFailedJob() {
|
||||
super(OutboundClientMessageOneShotJob.this.getContext());
|
||||
}
|
||||
public String getName() { return "Lookup for outbound client message failed"; }
|
||||
public void runJob() {
|
||||
dieFatal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message to the specified tunnel by creating a new garlic message containing
|
||||
* the (already created) payload clove as well as a new delivery status message. This garlic
|
||||
* message is sent out one of our tunnels, destined for the lease (tunnel+router) specified, and the delivery
|
||||
* status message is targetting one of our free inbound tunnels as well. We use a new
|
||||
* reply selector to keep an eye out for that delivery status message's token
|
||||
*
|
||||
*/
|
||||
private void send() {
|
||||
if (_finished) return;
|
||||
long token = getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE);
|
||||
PublicKey key = _leaseSet.getEncryptionKey();
|
||||
SessionKey sessKey = new SessionKey();
|
||||
Set tags = new HashSet();
|
||||
LeaseSet replyLeaseSet = null;
|
||||
if (_shouldBundle) {
|
||||
replyLeaseSet = getContext().netDb().lookupLeaseSetLocally(_from.calculateHash());
|
||||
}
|
||||
|
||||
GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token,
|
||||
_overallExpiration, key,
|
||||
_clove,
|
||||
_to,
|
||||
sessKey, tags,
|
||||
true, replyLeaseSet);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": send() - token expected " + token);
|
||||
|
||||
SendSuccessJob onReply = new SendSuccessJob(sessKey, tags);
|
||||
SendTimeoutJob onFail = new SendTimeoutJob();
|
||||
ReplySelector selector = new ReplySelector(token);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_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(getJobId() + ": Sending tunnel message out " + outTunnelId + " to "
|
||||
+ _lease.getTunnelId() + " on "
|
||||
+ _lease.getRouterIdentity().getHash().toBase64());
|
||||
SendTunnelMessageJob j = new SendTunnelMessageJob(getContext(), msg, outTunnelId,
|
||||
_lease.getRouterIdentity().getHash(),
|
||||
_lease.getTunnelId(), null, onReply,
|
||||
onFail, selector,
|
||||
_overallExpiration-getContext().clock().now(),
|
||||
SEND_PRIORITY);
|
||||
getContext().jobQueue().addJob(j);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(getJobId() + ": Could not find any outbound tunnels to send the payload through... wtf?");
|
||||
dieFatal();
|
||||
}
|
||||
_clientMessage = null;
|
||||
_clove = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick an arbitrary outbound tunnel to send the message through, or null if
|
||||
* there aren't any around
|
||||
*
|
||||
*/
|
||||
private TunnelId selectOutboundTunnel() {
|
||||
TunnelSelectionCriteria crit = new TunnelSelectionCriteria();
|
||||
crit.setMaximumTunnelsRequired(1);
|
||||
crit.setMinimumTunnelsRequired(1);
|
||||
List tunnelIds = getContext().tunnelManager().selectOutboundTunnelIds(crit);
|
||||
if (tunnelIds.size() <= 0)
|
||||
return null;
|
||||
else
|
||||
return (TunnelId)tunnelIds.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* give up the ghost, this message just aint going through. tell the client to fuck off.
|
||||
*
|
||||
* this is safe to call multiple times (only tells the client once)
|
||||
*/
|
||||
private void dieFatal() {
|
||||
if (_finished) return;
|
||||
_finished = true;
|
||||
|
||||
long sendTime = getContext().clock().now() - _start;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getJobId() + ": Failed to send the message " + _clientMessageId + " after "
|
||||
+ sendTime + "ms", new Exception("Message send failure"));
|
||||
|
||||
long messageDelay = getContext().throttle().getMessageDelay();
|
||||
long tunnelLag = getContext().throttle().getTunnelLag();
|
||||
long inboundDelta = (long)getContext().throttle().getInboundRateDelta();
|
||||
|
||||
getContext().statManager().addRateData("client.timeoutCongestionTunnel", tunnelLag, 1);
|
||||
getContext().statManager().addRateData("client.timeoutCongestionMessage", messageDelay, 1);
|
||||
getContext().statManager().addRateData("client.timeoutCongestionInbound", inboundDelta, 1);
|
||||
|
||||
getContext().messageHistory().sendPayloadMessage(_clientMessageId.getMessageId(), false, sendTime);
|
||||
getContext().clientManager().messageDeliveryStatusUpdate(_from, _clientMessageId, false);
|
||||
getContext().statManager().updateFrequency("client.sendMessageFailFrequency");
|
||||
_clientMessage = null;
|
||||
_clove = null;
|
||||
}
|
||||
|
||||
/** build the payload clove that will be used for all of the messages, placing the clove in the status structure */
|
||||
private void buildClove() {
|
||||
PayloadGarlicConfig clove = new PayloadGarlicConfig();
|
||||
|
||||
DeliveryInstructions instructions = new DeliveryInstructions();
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_DESTINATION);
|
||||
instructions.setDestination(_to.calculateHash());
|
||||
|
||||
instructions.setDelayRequested(false);
|
||||
instructions.setDelaySeconds(0);
|
||||
instructions.setEncrypted(false);
|
||||
|
||||
clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
clove.setDeliveryInstructions(instructions);
|
||||
clove.setExpiration(_overallExpiration);
|
||||
clove.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE));
|
||||
|
||||
DataMessage msg = new DataMessage(getContext());
|
||||
msg.setData(_clientMessage.getPayload().getEncryptedData());
|
||||
|
||||
clove.setPayload(msg);
|
||||
clove.setRecipientPublicKey(null);
|
||||
clove.setRequestAck(false);
|
||||
|
||||
_clove = clove;
|
||||
_cloveId = _clove.getId();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Built payload clove with id " + clove.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep an eye out for any of the delivery status message tokens that have been
|
||||
* sent down the various tunnels to deliver this message
|
||||
*
|
||||
*/
|
||||
private class ReplySelector implements MessageSelector {
|
||||
private long _pendingToken;
|
||||
public ReplySelector(long token) {
|
||||
_pendingToken = token;
|
||||
}
|
||||
|
||||
public boolean continueMatching() { return false; }
|
||||
public long getExpiration() { return _overallExpiration; }
|
||||
|
||||
public boolean isMatch(I2NPMessage inMsg) {
|
||||
if (inMsg.getType() == DeliveryStatusMessage.MESSAGE_TYPE) {
|
||||
return _pendingToken == ((DeliveryStatusMessage)inMsg).getMessageId();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after we get a confirmation that the message was delivered safely
|
||||
* (hoo-ray!)
|
||||
*
|
||||
*/
|
||||
private class SendSuccessJob extends JobImpl implements ReplyJob {
|
||||
private SessionKey _key;
|
||||
private Set _tags;
|
||||
|
||||
/**
|
||||
* Create a new success job that will be fired when the message encrypted with
|
||||
* the given session key and bearing the specified tags are confirmed delivered.
|
||||
*
|
||||
*/
|
||||
public SendSuccessJob(SessionKey key, Set tags) {
|
||||
super(OutboundClientMessageOneShotJob.this.getContext());
|
||||
_key = key;
|
||||
_tags = tags;
|
||||
}
|
||||
|
||||
public String getName() { return "Send client message successful to a lease"; }
|
||||
public void runJob() {
|
||||
if (_finished) return;
|
||||
_finished = true;
|
||||
long sendTime = getContext().clock().now() - _start;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(OutboundClientMessageOneShotJob.this.getJobId()
|
||||
+ ": SUCCESS! msg " + _clientMessageId
|
||||
+ " sent after " + sendTime + "ms");
|
||||
|
||||
if ( (_key != null) && (_tags != null) && (_tags.size() > 0) ) {
|
||||
if (_leaseSet != null)
|
||||
getContext().sessionKeyManager().tagsDelivered(_leaseSet.getEncryptionKey(),
|
||||
_key, _tags);
|
||||
}
|
||||
|
||||
long dataMsgId = _cloveId;
|
||||
getContext().messageHistory().sendPayloadMessage(dataMsgId, true, sendTime);
|
||||
getContext().clientManager().messageDeliveryStatusUpdate(_from, _clientMessageId, true);
|
||||
_lease.setNumSuccess(_lease.getNumSuccess()+1);
|
||||
|
||||
getContext().statManager().addRateData("client.sendAckTime", sendTime, 0);
|
||||
getContext().statManager().addRateData("client.sendMessageSize", _clientMessageSize, sendTime);
|
||||
}
|
||||
|
||||
public void setMessage(I2NPMessage msg) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired after the basic timeout for sending through the given tunnel has been reached.
|
||||
* We'll accept successes later, but won't expect them
|
||||
*
|
||||
*/
|
||||
private class SendTimeoutJob extends JobImpl {
|
||||
public SendTimeoutJob() {
|
||||
super(OutboundClientMessageOneShotJob.this.getContext());
|
||||
}
|
||||
|
||||
public String getName() { return "Send client message timed out through a lease"; }
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(OutboundClientMessageOneShotJob.this.getJobId()
|
||||
+ ": Soft timeout through the lease " + _lease);
|
||||
|
||||
_lease.setNumFailure(_lease.getNumFailure()+1);
|
||||
dieFatal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ public class SendTunnelMessageJob extends JobImpl {
|
||||
}
|
||||
}
|
||||
|
||||
info.messageProcessed();
|
||||
info.messageProcessed(_message.getMessageSize());
|
||||
|
||||
if (isEndpoint(info)) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
|
||||
@@ -147,8 +147,10 @@ class PersistentDataStore extends TransientDataStore {
|
||||
}
|
||||
|
||||
private class ReadJob extends JobImpl {
|
||||
private boolean _alreadyWarned;
|
||||
public ReadJob() {
|
||||
super(PersistentDataStore.this._context);
|
||||
_alreadyWarned = false;
|
||||
}
|
||||
public String getName() { return "DB Read Job"; }
|
||||
public void runJob() {
|
||||
@@ -158,6 +160,7 @@ class PersistentDataStore extends TransientDataStore {
|
||||
}
|
||||
|
||||
private void readFiles() {
|
||||
int routerCount = 0;
|
||||
try {
|
||||
File dbDir = getDbDir();
|
||||
File leaseSetFiles[] = dbDir.listFiles(LeaseSetFilter.getInstance());
|
||||
@@ -170,6 +173,9 @@ class PersistentDataStore extends TransientDataStore {
|
||||
}
|
||||
File routerInfoFiles[] = dbDir.listFiles(RouterInfoFilter.getInstance());
|
||||
if (routerInfoFiles != null) {
|
||||
routerCount += routerInfoFiles.length;
|
||||
if (routerInfoFiles.length > 5)
|
||||
_alreadyWarned = false;
|
||||
for (int i = 0; i < routerInfoFiles.length; i++) {
|
||||
Hash key = getRouterInfoHash(routerInfoFiles[i].getName());
|
||||
if ( (key != null) && (!isKnown(key)) )
|
||||
@@ -179,6 +185,11 @@ class PersistentDataStore extends TransientDataStore {
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error reading files in the db dir", ioe);
|
||||
}
|
||||
|
||||
if ( (routerCount <= 5) && (!_alreadyWarned) ) {
|
||||
_log.error("Very few routerInfo files remaining - please reseed");
|
||||
_alreadyWarned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user