Compare commits

..

58 Commits

Author SHA1 Message Date
723a2f2008 include timestamper in installer 2004-05-07 19:01:47 +00:00
ea9b9fbf17 0.3.1.1 (tastes like chicken)
backwards compatible but some config changes necessary
2004-05-07 17:52:49 +00:00
303e257841 synchronization reduction and keep track of the 'last' job for each runner (to help debug something i see once a week on kaffe) 2004-05-07 17:51:28 +00:00
07b6a8ba92 if we lose our I2CP connection to the router, die hard and fast.
(only relevent for people whose socket manager / i2ptunnel / etc are located remote from the router)
2004-05-07 07:01:26 +00:00
e216e18368 update the clock more liberally (since we've got the new ntp code) 2004-05-07 04:34:03 +00:00
e57c5b4bc2 log to CRIT for jbigi load failure 2004-05-07 04:23:30 +00:00
f772d6ddeb /me reboots brain, understands, and thanks mihi 2004-05-07 04:19:43 +00:00
cd37c301d9 if (log.shouldLog())
yeah, aspect oriented programming sure would be nice.
2004-05-07 04:07:14 +00:00
f5fa26639e minor html cleanup 2004-05-07 03:45:48 +00:00
45ec73c115 include a unique request id in the client runner thread name
minor cleanup
2004-05-07 03:33:23 +00:00
13952ebd8b include the expiration and messageId in the toString (shown on msg drop due to "unknown tunnel") 2004-05-07 03:31:33 +00:00
2df0007a10 logging 2004-05-07 03:29:06 +00:00
89bc5db3e1 increase the bundle probability to yet another arbitrary value
add the jobId to log messages to simplify tracing individual parallel sends
logging cleanup
2004-05-07 03:28:22 +00:00
4021deec7f poke jrandom's eyes into the semantic of an "else" clause
(you may remove both comments when you understood it)

[mihi]
2004-05-07 03:10:57 +00:00
a3977f37f7 javadoc, no functional changes 2004-05-07 03:06:41 +00:00
766c12242e logging, javadoc 2004-05-07 01:45:12 +00:00
a82b951aff made private things that don't need to be public
remove semantic inconsistency wrt getRemoteId(false) - it shouldn't ever timeout, since it always returns immediately
javadoc (though i wish i understood the close/close2/sendClose more clearly so i could javadoc that process)
2004-05-07 01:32:48 +00:00
997a94eecc removed PHTTP lines since they were only used for time sync
added more info wrt NTP entry
added filename for the SAM bridge
2004-05-06 07:48:45 +00:00
635535aac2 implement keyfile persistence (storing name=privKeyDataBase64\n for each name)
filename specified on the command line: SAMBridge [keyfile [listenHost] listenPortNum [ name=val]*]
2004-05-06 07:35:44 +00:00
25314fd91a make sure we mark the send as *failed* if we need to reconnect 2004-05-06 04:18:28 +00:00
9a06a5758d check shouldBundle only when its ready to be checked (duh) 2004-05-06 01:02:50 +00:00
e5a2a9644f *cough* [d'oh] 2004-05-05 23:01:36 +00:00
e0e7211852 lets default the read timeout to 5 minutes for clients (that hanging irc disconnect / not disconnected thing) 2004-05-05 22:57:43 +00:00
cdaeb4d176 track and publish two new stats:
* netDb.failedPeers (how many peers didn't reply to a lookup in time)
* netDb.searchCount (how many searches we send out in a 3 hour period)
probabalistically include the leaseSet of the sender in the garlic sent
to a peer if the client requests it to be included (aka if they want
replies).  By default, this is enabled (disable by setting the I2CP
prop "shouldBundleReplyInfo=false").  Also, by default the probability is
30% (w00 h00, arbitrary values!), which can be overridden via the I2CP
prop "bundleReplyInfoProbability=80" (or =10, or =100, etc).  The tradeoff
here is quicker replies in exchange for bandwidth (the dbStore).
Yeah, it'd be nice if there were something keeping track of which leaseSet
each client sent to each peer so that it could explicitly include it only
if it were necessary, but for now that's probably overkill.
2004-05-05 22:46:10 +00:00
07aa2e280d strip the Connection, Keep-Alive, and Proxy-Connection headers, and always inject Connection: close
(this is the cause of the intermittent "view $page through squid, try to view eepsite, end up requesting through squid" bug)
2004-05-05 07:29:48 +00:00
6c4bc67ff3 simplistic streaming test (w00t, the streams worked - no mods necessary. go human, its your birthday, go human, its your birthday) 2004-05-05 04:43:05 +00:00
d9f0cc27ef formatting 2004-05-05 03:37:26 +00:00
59aec9d289 expose the read timeout for the client and put the default read timeout back to -1 for the server (override via command line, etc) 2004-05-05 03:36:18 +00:00
451f4c503d fixed typo on timestamper, keep NetMonitor off by default 2004-05-05 01:32:08 +00:00
cd82089d4d upgrade deprecated argument
fix ze german
(duck)
2004-05-04 17:17:10 +00:00
3db8b63cde by default, set the readTimeout to 3 minutes, NOT infinity. Overridable as before (setting the timeout to -1)
add a unique id to the server thread
2004-05-04 08:16:41 +00:00
6edf5d1e4f add a unique id to the thread names 2004-05-04 08:15:18 +00:00
a23fa6fadd allow multiple concurrent connections to be created
added a unique ID to more threads
2004-05-04 08:14:19 +00:00
51eb77e409 logging 2004-05-04 08:13:01 +00:00
691326cea8 make sure we kill the threads that failed to ACK, rather than leave them sitting there, waiting forever
logging
2004-05-04 08:09:28 +00:00
3cac1238ed handle reclose, logging, more clear notification 2004-05-04 05:53:11 +00:00
b04512a4f6 add unique IDs to the threads for easier tracing 2004-05-04 04:46:04 +00:00
3a4d0549aa add accept timeouts (default is that if the server doesnt .accept() in 5s, refuse the con)
add unique IDs to the various threads for logging / tracing purposes
2004-05-04 04:44:05 +00:00
d7467f5dc3 disconnect isn't an error 2004-05-04 01:58:37 +00:00
141902b86d parseParams throws exception on bad formatting, and its perfectly valid to have params with 0 values (e.g. DEST GENERATE\n) 2004-05-04 01:35:09 +00:00
5aa680fc93 simple test of whether DEST GENERATE works 2004-05-04 01:32:39 +00:00
a790117f5a test the naming commnads (fetching ME, a known host, and an unknown host) 2004-05-04 01:11:44 +00:00
2a5a52c810 added xilog.i2p 2004-05-04 00:43:09 +00:00
2156f4c2f3 * more verbose errors (include MESSAGE data on the I2P_ERROR reply, not just in the log)
* don't create excess I2PAppContexts (if any old context will do, use the global)
keep track of keys per spec (when DESTINATION=blah, create (or reuse) the destination private
keys).  we still need to persist this data though.
* the DESTINATION in the SESSION STATUS is now the same as the one sent in the
SESSION CREATE, /not/ the base64 of the private key, per spec
* enum is a reserved word in 1.5, so s/enum/names/ for future compatability
* logging
2004-05-03 11:34:38 +00:00
2585460286 initial tests for HELLO and create session (style=stream). covers the basics, but doesn't cover a single normal scenario yet 2004-05-03 11:16:59 +00:00
1b4af66986 flag as closed /after/ we send the disconnect message *cough* 2004-05-03 11:13:44 +00:00
0324bac044 added lucky.i2p, removed lp.i2p 2004-05-03 07:04:12 +00:00
2bfbe1ca27 logging (toss a unique ID onto the handler / inactivity threads) 2004-05-03 03:36:38 +00:00
60584228d9 refactored packet handling into type specific methods
removed nested synchronization (which had been causing undetected deadlocks)
made sync blocks smaller, though this may have opened holes related to
resent ACK/SYN/CLOSE packets that are delivered in a race.  I'm not as
fluent in the ministreaming lib code as i should be (yet), but duck's thread
dumps were showing hundreds of threads waiting on a lock that'll never get
released (since the only way to release it would be to receive another
packet, and no more packets can be received until the lock is released, etc)
also, I2PSession is threadsafe - i can see no reason to synchronize on it
(and it was being synchronized on only part of the time?)
also, refactored the charset encoding stuff and minor log tweaking
i've been testing this for the last hour or so, on eepsites and squid (large
and small files), as well as irc, and there haven't been any glitches.  but
it needs more testing before it can be released, obviously.
2004-05-03 03:34:25 +00:00
44e34f7b11 trim the request line (StringTokenizer w/ " " as a delim doesn't include \n as a delim, etc) 2004-05-02 07:50:20 +00:00
7912050647 allow overriding the I2CP port/host 2004-05-02 07:49:22 +00:00
2231abd407 default the DIRECTION to BOTH for streams 2004-05-02 07:07:25 +00:00
8d17ba4d66 added sungo.i2p 2004-05-02 06:59:38 +00:00
8244bdb440 include the timestamper (and fire the client on startup, using a pair of arbitrary NTP hosts - to be configured later) 2004-05-02 05:11:06 +00:00
bc3b7ffd86 start the admin listener ASAP (right after reading the config)
fire the LoadClientAppsJob right after the admin listener is booted, which now includes support for the onBoot property (which causes the client to run immediately, instead of waiting 2+ minutes)
(yeah, it'd suck if all routers started up, tried to connect to people, got shitlisted, then 2 minutes later got the right NTP time, 'eh?)
2004-05-02 05:02:10 +00:00
e22cb62493 handle /setTime?blah&now=yyyyMMdd_HH:mm:ss.SSSS (updating the router's clock)
yes, we'll want to filter the access to the admin manager ;)
2004-05-02 04:46:52 +00:00
e923aa1f72 add the timestamper 2004-05-02 04:29:18 +00:00
68a21f1fbb NTP client, GPLed 2004-05-02 04:18:53 +00:00
59 changed files with 2963 additions and 873 deletions

View File

@ -17,6 +17,8 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
private static final Log _log = new Log(I2PTunnelClient.class);
protected Destination dest;
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
protected long readTimeout = DEFAULT_READ_TIMEOUT;
public I2PTunnelClient(int localPort, String destination, Logging l, boolean ownDest, EventDispatcher notifyThis) {
super(localPort, ownDest, l, notifyThis, "SynSender");
@ -45,9 +47,13 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
notifyEvent("openClientResult", "ok");
}
public void setReadTimeout(long ms) { readTimeout = ms; }
public long getReadTimeout() { return readTimeout; }
protected void clientConnectionRun(Socket s) {
try {
I2PSocket i2ps = createI2PSocket(dest);
i2ps.setReadTimeout(readTimeout);
new I2PTunnelRunner(s, i2ps, sockLock, null);
} catch (Exception ex) {
_log.info("Error connecting", ex);

View File

@ -33,6 +33,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
private static volatile long __clientId = 0;
private long _clientId;
protected Object sockLock = new Object(); // Guards sockMgr and mySockets
private I2PSocketManager sockMgr;
private List mySockets = new ArrayList();
@ -60,9 +62,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName) {
super(localPort + " (uninitialized)", notifyThis);
_clientId = ++__clientId;
this.localPort = localPort;
this.l = l;
this.handlerName = handlerName;
this.handlerName = handlerName + _clientId;
synchronized (sockLock) {
if (ownDest) {
@ -75,7 +78,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
l.log("I2P session created");
Thread t = new I2PThread(this);
t.setName("Client");
t.setName("Client " + _clientId);
listenerReady = false;
t.start();
open = true;
@ -179,8 +182,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
public I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
I2PSocket i2ps;
i2ps = sockMgr.connect(dest, opt);
synchronized (sockLock) {
i2ps = sockMgr.connect(dest, opt);
mySockets.add(i2ps);
}
@ -272,12 +275,14 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
}
private static volatile long __runnerId = 0;
public class ClientConnectionRunner extends I2PThread {
private Socket s;
public ClientConnectionRunner(Socket s, String name) {
this.s = s;
setName(name);
setName(name + '.' + (++__runnerId));
start();
}

View File

@ -10,6 +10,9 @@ import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
@ -20,6 +23,25 @@ import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Act as a mini HTTP proxy, handling various different types of requests,
* forwarding them through I2P appropriately, and displaying the reply. Supported
* request formats are: <pre>
* $method http://$site[$port]/$path $protocolVersion
* or
* $method $path $protocolVersion\nHost: $site
* or
* $method http://i2p/$site/$path $protocolVersion
* or
* $method /$site/$path $protocolVersion
* </pre>
*
* If the $site resolves with the I2P naming service, then it is directed towards
* that eepsite, otherwise it is directed towards this client's outproxy (typically
* "squid.i2p"). Only HTTP is supported (no HTTPS, ftp, mailto, etc). Both GET
* and POST have been tested, though other $methods should work.
*
*/
public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable {
private static final Log _log = new Log(I2PTunnelHTTPClient.class);
@ -43,7 +65,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"That Desitination was not found. Perhaps you pasted in the wrong "+
"BASE64 I2P Destination or the link you are following is bad. "+
"The host (or the WWW proxy, if you're using one) could also be "+
"temporarily offline. "+
"temporarily offline. You may want to <b>retry</b>. "+
"Could not find the following Destination:<BR><BR>")
.getBytes();
@ -53,19 +75,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"Cache-control: no-cache\r\n\r\n"+
"<html><body><H1>I2P ERROR: TIMEOUT</H1>"+
"That Desitination was reachable, but timed out getting a "+
"response. This may be a temporary error, so you should simply "+
"response. This is likely a temporary error, so you should simply "+
"try to refresh, though if the problem persists, the remote "+
"destination may have issues. Could not get a response from "+
"the following Destination:<BR><BR>")
.getBytes();
// public I2PTunnelHTTPClient(int localPort, Logging l,
// boolean ownDest, String wwwProxy) {
// this(localPort, l, ownDest, wwwProxy, (EventDispatcher)null);
// }
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
public I2PTunnelHTTPClient(int localPort, Logging l, boolean ownDest, String wwwProxy, EventDispatcher notifyThis) {
super(localPort, ownDest, l, notifyThis, "HTTPHandler");
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId));
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openHTTPClientResult", "error");
@ -92,7 +112,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
String line, method = null, protocol = null, host = null, destination = null;
StringBuffer newRequest = new StringBuffer();
while ((line = br.readLine()) != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Line=[" + line + "]");
if (line.startsWith("Connection: ") ||
line.startsWith("Keep-Alive: ") ||
line.startsWith("Proxy-Connection: "))
continue;
if (method == null) { // first line (GET /base64/realaddr)
if (_log.shouldLog(Log.DEBUG))
_log.debug("Method is null for [" + line + "]");
int pos = line.indexOf(" ");
if (pos == -1) break;
method = line.substring(0, pos);
@ -126,6 +157,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
// The request must be forwarded to a WWW proxy
destination = wwwProxy;
usingWWWProxy = true;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Host doesnt end with .i2p and it contains a period [" + host + "]: wwwProxy!");
} else {
request = request.substring(pos + 1);
pos = request.indexOf("/");
@ -150,14 +183,25 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
_log.debug("HOST :" + host + ":");
_log.debug("DEST :" + destination + ":");
}
} else if (line.startsWith("Host: ") && !usingWWWProxy) {
line = "Host: " + host;
if (_log.shouldLog(Log.INFO)) _log.info("Setting host = " + host);
} else {
if (line.startsWith("Host: ") && !usingWWWProxy) {
line = "Host: " + host;
if (_log.shouldLog(Log.INFO))
_log.info("Setting host = " + host);
}
}
if (line.length() == 0) {
newRequest.append("Connection: close\r\n\r\n");
break;
} else {
newRequest.append(line).append("\r\n"); // HTTP spec
}
newRequest.append(line).append("\r\n"); // HTTP spec
if (line.length() == 0) break;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("NewRequest header: [" + newRequest.toString() + "]");
while (br.ready()) { // empty the buffer (POST requests)
int i = br.read();
if (i != -1) {
@ -176,6 +220,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
s.close();
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Destination: " + destination);
Destination dest = I2PTunnel.destFromName(destination);
if (dest == null) {
l.log("Could not resolve " + destination + ".");
@ -211,6 +259,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
private static final long INACTIVITY_TIMEOUT = 120 * 1000;
private static volatile long __timeoutId = 0;
private class InactivityTimeoutThread extends I2PThread {
@ -230,7 +279,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
_targetRequest = targetRequest;
_useWWWProxy = useWWWProxy;
_disabled = false;
setName("InactivityThread");
long timeoutId = ++__timeoutId;
setName("InactivityThread " + timeoutId);
}
public void disable() {

View File

@ -21,6 +21,8 @@ import net.i2p.util.Log;
public class I2PTunnelRunner extends I2PThread {
private final static Log _log = new Log(I2PTunnelRunner.class);
private static volatile long __runnerId;
private long _runnerId;
/**
* max bytes streamed in a packet - smaller ones might be filled
* up to this size. Larger ones are not split (at least not on
@ -51,7 +53,8 @@ public class I2PTunnelRunner extends I2PThread {
lastActivityOn = -1;
startedOn = -1;
_log.info("I2PTunnelRunner started");
setName("I2PTunnelRunner");
_runnerId = ++__runnerId;
setName("I2PTunnelRunner " + _runnerId);
start();
}
@ -129,6 +132,8 @@ public class I2PTunnelRunner extends I2PThread {
}
}
private volatile long __forwarderId = 0;
private class StreamForwarder extends I2PThread {
InputStream in;
@ -137,7 +142,7 @@ public class I2PTunnelRunner extends I2PThread {
private StreamForwarder(InputStream in, OutputStream out) {
this.in = in;
this.out = out;
setName("StreamForwarder");
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
start();
}

View File

@ -40,7 +40,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
private Logging l;
private long readTimeout = -1;
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
/** default timeout to 3 minutes - override if desired */
private long readTimeout = DEFAULT_READ_TIMEOUT;
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis) {
super(host + ":" + port + " <- " + privData, notifyThis);
@ -81,13 +83,16 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
open = true;
}
private static volatile long __serverId = 0;
/**
* Start running the I2PTunnelServer.
*
*/
public void startRunning() {
Thread t = new I2PThread(this);
t.setName("Server");
t.setName("Server " + (++__serverId));
t.start();
}

View File

@ -4,71 +4,148 @@ import java.net.ConnectException;
import net.i2p.I2PException;
import net.i2p.util.Log;
import net.i2p.util.Clock;
import net.i2p.I2PAppContext;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
/**
* Initial stub implementation for the server socket
* Server socket implementation, allowing multiple threads to accept I2PSockets
* and pull from a queue populated by various threads (each of whom have their own
* timeout)
*
*/
class I2PServerSocketImpl implements I2PServerSocket {
private final static Log _log = new Log(I2PServerSocketImpl.class);
private I2PSocketManager mgr;
private I2PSocket cached = null; // buffer one socket here
private boolean closing = false; // Are we being closed?
private Object acceptLock = new Object();
/** list of sockets waiting for the client to accept them */
private List pendingSockets = Collections.synchronizedList(new ArrayList(4));
/** have we been closed */
private volatile boolean closing = false;
/** lock on this when accepting a pending socket, and wait on it for notification of acceptance */
private Object socketAcceptedLock = new Object();
/** lock on this when adding a new socket to the pending list, and wait on it accordingly */
private Object socketAddedLock = new Object();
public I2PServerSocketImpl(I2PSocketManager mgr) {
this.mgr = mgr;
}
public synchronized I2PSocket accept() throws I2PException, ConnectException {
I2PSocket ret;
synchronized (acceptLock) {
while ((cached == null) && !closing) {
myWait();
}
if (closing) {
throw new ConnectException("I2PServerSocket closed");
}
ret = cached;
cached = null;
acceptLock.notifyAll();
}
_log.debug("TIMING: handed out accept result " + ret.hashCode());
/**
* Waits for the next socket connecting. If a remote user tried to make a
* connection and the local application wasn't .accept()ing new connections,
* they should get refused (if .accept() doesnt occur in some small period -
* currently 5 seconds)
*
* @return a connected I2PSocket
*
* @throws I2PException if there is a problem with reading a new socket
* from the data available (aka the I2PSession closed, etc)
* @throws ConnectException if the I2PServerSocket is closed
*/
public I2PSocket accept() throws I2PException, ConnectException {
if (_log.shouldLog(Log.DEBUG))
_log.debug("accept() called, pending: " + pendingSockets.size());
I2PSocket ret = null;
while ( (ret == null) && (!closing) ){
while (pendingSockets.size() <= 0) {
if (closing) throw new ConnectException("I2PServerSocket closed");
try {
synchronized(socketAddedLock) {
socketAddedLock.wait();
}
} catch (InterruptedException ie) {}
}
synchronized (pendingSockets) {
if (pendingSockets.size() > 0) {
ret = (I2PSocket)pendingSockets.remove(0);
}
}
if (ret != null) {
synchronized (socketAcceptedLock) {
socketAcceptedLock.notifyAll();
}
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("TIMING: handed out accept result " + ret.hashCode());
return ret;
}
public boolean getNewSocket(I2PSocket s) {
synchronized (acceptLock) {
while (cached != null) {
myWait();
}
cached = s;
acceptLock.notifyAll();
}
/**
* Make the socket available and wait until the client app accepts it, or until
* the given timeout elapses. This doesn't have any limits on the queue size -
* perhaps it should add some choking (e.g. after 5 waiting for accept, refuse)
*
* @param timeoutMs how long to wait until accept
* @return true if the socket was accepted, false if the timeout expired
* or the socket was closed
*/
public boolean addWaitForAccept(I2PSocket s, long timeoutMs) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("addWaitForAccept [new socket arrived, pending: " + pendingSockets.size());
if (closing) {
if (_log.shouldLog(Log.WARN))
_log.warn("Already closing the socket");
return false;
}
Clock clock = I2PAppContext.getGlobalContext().clock();
long start = clock.now();
long end = start + timeoutMs;
pendingSockets.add(s);
synchronized (socketAddedLock) {
socketAddedLock.notifyAll();
}
// keep looping until the socket has been grabbed by the accept()
// (or the expiration passes, or the socket is closed)
while (pendingSockets.contains(s)) {
long now = clock.now();
if (now >= end) {
if (_log.shouldLog(Log.INFO))
_log.info("Expired while waiting for accept (time elapsed =" + (now - start) + "ms");
pendingSockets.remove(s);
return false;
}
if (closing) {
if (_log.shouldLog(Log.WARN))
_log.warn("Server socket closed while waiting for accept");
pendingSockets.remove(s);
return false;
}
long remaining = end - now;
try {
synchronized (socketAcceptedLock) {
socketAcceptedLock.wait(remaining);
}
} catch (InterruptedException ie) {}
}
long now = clock.now();
if (_log.shouldLog(Log.DEBUG))
_log.info("Socket accepted after " + (now-start) + "ms");
return true;
}
public void close() throws I2PException {
synchronized (acceptLock) {
closing = true;
acceptLock.notifyAll();
}
}
public I2PSocketManager getManager() {
return mgr;
}
private void myWait() {
try {
acceptLock.wait();
} catch (InterruptedException ex) {}
public void close() {
closing = true;
// let anyone .accept()ing know to fsck off
synchronized (socketAddedLock) {
socketAddedLock.notifyAll();
}
// let anyone addWaitForAccept()ing know to fsck off
synchronized (socketAcceptedLock) {
socketAcceptedLock.notifyAll();
}
}
public I2PSocketManager getManager() { return mgr; }
}

View File

@ -31,8 +31,32 @@ class I2PSocketImpl implements I2PSocket {
private I2POutputStream out;
private boolean outgoing;
private Object flagLock = new Object();
private boolean closed = false, sendClose = true, closed2 = false;
/**
* Whether the I2P socket has already been closed.
*/
private boolean closed = false;
/**
* Whether to send out a close packet when the socket is
* closed. (If the socket is closed because of an incoming close
* packet, we need not send one.)
*/
private boolean sendClose = true;
/**
* Whether the I2P socket has already been closed and all data
* (from I2P to the app, dunno whether to call this incoming or
* outgoing) has been processed.
*/
private boolean closed2 = false;
/**
* @param peer who this socket is (or should be) connected to
* @param mgr how we talk to the network
* @param outgoing did we initiate the connection (true) or did we receive it (false)?
* @param localID what is our half of the socket ID?
*/
public I2PSocketImpl(Destination peer, I2PSocketManager mgr, boolean outgoing, String localID) {
this.outgoing = outgoing;
manager = mgr;
@ -45,10 +69,17 @@ class I2PSocketImpl implements I2PSocket {
this.localID = localID;
}
/**
* Our half of the socket's unique ID
*
*/
public String getLocalID() {
return localID;
}
/**
* We've received the other side's half of the socket's unique ID
*/
public void setRemoteID(String id) {
synchronized (remoteIDWaiter) {
remoteID = id;
@ -56,10 +87,32 @@ class I2PSocketImpl implements I2PSocket {
}
}
public String getRemoteID(boolean wait) throws InterruptedIOException {
return getRemoteID(wait, -1);
/**
* Retrieve the other side's half of the socket's unique ID, or null if it
* isn't known yet
*
* @param wait if true, we should wait until we receive it from the peer, otherwise
* return what we know immediately (which may be null)
*/
public String getRemoteID(boolean wait) {
try {
return getRemoteID(wait, -1);
} catch (InterruptedIOException iie) {
_log.error("wtf, we said we didn't want it to time out! you smell", iie);
return null;
}
}
/**
* Retrieve the other side's half of the socket's unique ID, or null if it isn't
* known yet and we were instructed not to wait
*
* @param wait should we wait for the peer to send us their half of the ID, or
* just return immediately?
* @param maxWait if we're going to wait, after how long should we timeout and fail?
* (if this value is < 0, we wait indefinitely)
* @throws InterruptedIOException when the max waiting period has been exceeded
*/
public String getRemoteID(boolean wait, long maxWait) throws InterruptedIOException {
long dieAfter = System.currentTimeMillis() + maxWait;
synchronized (remoteIDWaiter) {
@ -75,17 +128,30 @@ class I2PSocketImpl implements I2PSocket {
if ((maxWait >= 0) && (System.currentTimeMillis() >= dieAfter))
throw new InterruptedIOException("Timed out waiting for remote ID");
_log.debug("TIMING: RemoteID set to " + I2PSocketManager.getReadableForm(remoteID) + " for "
+ this.hashCode());
if (_log.shouldLog(Log.DEBUG))
_log.debug("TIMING: RemoteID set to "
+ I2PSocketManager.getReadableForm(remoteID) + " for "
+ this.hashCode());
}
return remoteID;
}
}
public String getRemoteID() throws InterruptedIOException {
/**
* Retrieve the other side's half of the socket's unique ID, or null if it
* isn't known yet. This does not wait
*
*/
public String getRemoteID() {
return getRemoteID(false);
}
/**
* The other side has given us some data, so inject it into our socket's
* inputStream
*
* @param data the data to inject into our local inputStream
*/
public void queueData(byte[] data) {
in.queueData(data);
}
@ -121,7 +187,8 @@ class I2PSocketImpl implements I2PSocket {
}
/**
* Closes the socket if not closed yet
* Closes the socket if not closed yet (from the Application
* side).
*/
public void close() throws IOException {
synchronized (flagLock) {
@ -132,7 +199,10 @@ class I2PSocketImpl implements I2PSocket {
in.notifyClosed();
}
public void internalClose() {
/**
* Close the socket from the I2P side, e. g. by a close packet.
*/
protected void internalClose() {
synchronized (flagLock) {
closed = true;
closed2 = true;
@ -143,9 +213,17 @@ class I2PSocketImpl implements I2PSocket {
}
private byte getMask(int add) {
return (byte) ((outgoing ? (byte) 0xA0 : (byte) 0x50) + (byte) add);
if (outgoing)
return (byte)(I2PSocketManager.DATA_IN + (byte)add);
else
return (byte)(I2PSocketManager.DATA_OUT + (byte)add);
}
/**
* What is the longest we'll block on the input stream while waiting
* for more data? If this value is exceeded, the read() throws
* InterruptedIOException
*/
public long getReadTimeout() {
return in.getReadTimeout();
}
@ -155,7 +233,7 @@ class I2PSocketImpl implements I2PSocket {
}
//--------------------------------------------------
public class I2PInputStream extends InputStream {
private class I2PInputStream extends InputStream {
private ByteCollector bc = new ByteCollector();
@ -187,7 +265,7 @@ class I2PSocketImpl implements I2PSocket {
while (read.length == 0) {
synchronized (flagLock) {
if (closed) {
_log.debug("Closed is set, so closing stream: " + this.hashCode());
_log.debug("Closed is set, so closing stream: " + hashCode());
return -1;
}
}
@ -210,12 +288,13 @@ class I2PSocketImpl implements I2PSocket {
System.arraycopy(read, 0, b, off, read.length);
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Read from I2PInputStream " + this.hashCode() + " returned " + read.length + " bytes");
_log.debug("Read from I2PInputStream " + hashCode() + " returned "
+ read.length + " bytes");
}
//if (_log.shouldLog(Log.DEBUG)) {
// _log.debug("Read from I2PInputStream " + this.hashCode()
// + " returned "+read.length+" bytes:\n"
// + HexDump.dump(read));
// + " returned "+read.length+" bytes:\n"
// + HexDump.dump(read));
//}
return read.length;
}
@ -229,18 +308,24 @@ class I2PSocketImpl implements I2PSocket {
}
public synchronized void queueData(byte[] data, int off, int len) {
_log.debug("Insert " + len + " bytes into queue: " + this.hashCode());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Insert " + len + " bytes into queue: " + hashCode());
bc.append(data, off, len);
notifyAll();
}
public synchronized void notifyClosed() {
notifyAll();
I2PInputStream.this.notifyAll();
}
public void close() throws IOException {
super.close();
notifyClosed();
}
}
public class I2POutputStream extends OutputStream {
private class I2POutputStream extends OutputStream {
public I2PInputStream sendTo;
@ -261,74 +346,89 @@ class I2PSocketImpl implements I2PSocket {
}
}
public class I2PSocketRunner extends I2PThread {
private static volatile long __runnerId = 0;
private class I2PSocketRunner extends I2PThread {
public InputStream in;
public I2PSocketRunner(InputStream in) {
_log.debug("Runner's input stream is: " + in.hashCode());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Runner's input stream is: " + in.hashCode());
this.in = in;
setName("SocketRunner from " + I2PSocketImpl.this.remote.calculateHash().toBase64().substring(0, 4));
String peer = I2PSocketImpl.this.remote.calculateHash().toBase64();
setName("SocketRunner " + (++__runnerId) + " " + peer.substring(0, 4));
start();
}
/**
* Pump some more data
*
* @return true if we should keep on handling, false otherwise
*/
private boolean handleNextPacket(ByteCollector bc, byte buffer[])
throws IOException, I2PSessionException {
int len = in.read(buffer);
int bcsize = bc.getCurrentSize();
if (len != -1) {
bc.append(buffer, len);
} else if (bcsize == 0) {
// nothing left in the buffer, and read(..) got EOF (-1).
// the bart the
return false;
}
if ((bcsize < MAX_PACKET_SIZE) && (in.available() == 0)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Runner Point d: " + hashCode());
try {
Thread.sleep(PACKET_DELAY);
} catch (InterruptedException e) {
_log.warn("wtf", e);
}
}
if ((bcsize >= MAX_PACKET_SIZE) || (in.available() == 0)) {
byte[] data = bc.startToByteArray(MAX_PACKET_SIZE);
if (data.length > 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Message size is: " + data.length);
boolean sent = sendBlock(data);
if (!sent) {
_log.error("Error sending message to peer. Killing socket runner");
return false;
}
}
}
return true;
}
public void run() {
byte[] buffer = new byte[MAX_PACKET_SIZE];
ByteCollector bc = new ByteCollector();
boolean sent = true;
boolean keepHandling = true;
int packetsHandled = 0;
try {
int len, bcsize;
// try {
while (true) {
len = in.read(buffer);
bcsize = bc.getCurrentSize();
if (len != -1) {
bc.append(buffer, len);
} else if (bcsize == 0) {
break;
}
if ((bcsize < MAX_PACKET_SIZE) && (in.available() == 0)) {
_log.debug("Runner Point d: " + this.hashCode());
try {
Thread.sleep(PACKET_DELAY);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if ((bcsize >= MAX_PACKET_SIZE) || (in.available() == 0)) {
byte[] data = bc.startToByteArray(MAX_PACKET_SIZE);
if (data.length > 0) {
_log.debug("Message size is: " + data.length);
sent = sendBlock(data);
if (!sent) {
_log.error("Error sending message to peer. Killing socket runner");
break;
}
}
}
// try {
while (keepHandling) {
keepHandling = handleNextPacket(bc, buffer);
packetsHandled++;
}
if ((bc.getCurrentSize() > 0) && sent) {
_log.error("A SCARY MONSTER HAS EATEN SOME DATA! " + "(input stream: " + in.hashCode() + "; "
if ((bc.getCurrentSize() > 0) && (packetsHandled > 1)) {
_log.error("A SCARY MONSTER HAS EATEN SOME DATA! " + "(input stream: "
+ in.hashCode() + "; "
+ "queue size: " + bc.getCurrentSize() + ")");
}
synchronized (flagLock) {
closed2 = true;
}
// } catch (IOException ex) {
// if (_log.shouldLog(Log.INFO))
// _log.info("Error reading and writing", ex);
// }
boolean sc;
synchronized (flagLock) {
sc = sendClose;
} // FIXME: Race here?
if (sc) {
_log.info("Sending close packet: " + outgoing);
byte[] packet = I2PSocketManager.makePacket((byte) (getMask(0x02)), remoteID, new byte[0]);
synchronized (manager.getSession()) {
sent = manager.getSession().sendMessage(remote, packet);
}
if (_log.shouldLog(Log.INFO))
_log.info("Sending close packet: " + outgoing);
byte[] packet = I2PSocketManager.makePacket(getMask(0x02), remoteID, new byte[0]);
boolean sent = manager.getSession().sendMessage(remote, packet);
if (!sent) {
_log.error("Error sending close packet to peer");
}
@ -348,7 +448,8 @@ class I2PSocketImpl implements I2PSocket {
}
private boolean sendBlock(byte data[]) throws I2PSessionException {
_log.debug("TIMING: Block to send for " + I2PSocketImpl.this.hashCode());
if (_log.shouldLog(Log.DEBUG))
_log.debug("TIMING: Block to send for " + I2PSocketImpl.this.hashCode());
if (remoteID == null) {
_log.error("NULL REMOTEID");
return false;
@ -358,9 +459,7 @@ class I2PSocketImpl implements I2PSocket {
synchronized (flagLock) {
if (closed2) return false;
}
synchronized (manager.getSession()) {
sent = manager.getSession().sendMessage(remote, packet);
}
sent = manager.getSession().sendMessage(remote, packet);
return sent;
}
}

View File

@ -22,6 +22,7 @@ import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
import net.i2p.data.Base64;
import net.i2p.data.Destination;
import net.i2p.data.DataFormatException;
import net.i2p.util.Log;
/**
@ -40,11 +41,27 @@ public class I2PSocketManager implements I2PSessionListener {
private HashMap _outSockets;
private HashMap _inSockets;
private I2PSocketOptions _defaultOptions;
private long _acceptTimeout;
public static final short ACK = 0x51;
public static final short CLOSE_OUT = 0x52;
public static final short DATA_OUT = 0x50;
public static final short SYN = 0xA1;
public static final short CLOSE_IN = 0xA2;
public static final short DATA_IN = 0xA0;
public static final short CHAFF = 0xFF;
/**
* How long to wait for the client app to accept() before sending back CLOSE?
* This includes the time waiting in the queue. Currently set to 5 seconds.
*/
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
public I2PSocketManager() {
_session = null;
_inSockets = new HashMap(16);
_outSockets = new HashMap(16);
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
}
public I2PSession getSession() {
@ -55,15 +72,25 @@ public class I2PSocketManager implements I2PSessionListener {
_session = session;
if (session != null) session.setSessionListener(this);
}
/**
* How long should we wait for the client to .accept() a socket before
* sending back a NACK/Close?
*
* @param ms milliseconds to wait, maximum
*/
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
public long getAcceptTimeout() { return _acceptTimeout; }
public void disconnected(I2PSession session) {
_log.error("Disconnected from the session");
_log.info("Disconnected from the session");
destroySocketManager();
}
public void errorOccurred(I2PSession session, String message, Throwable error) {
_log.error("Error occurred: [" + message + "]", error);
}
public void messageAvailable(I2PSession session, int msgId, long size) {
try {
I2PSocketImpl s;
@ -77,157 +104,276 @@ public class I2PSocketManager implements I2PSessionListener {
return;
}
int type = msg[0] & 0xff;
String id = new String(new byte[] { msg[1], msg[2], msg[3]}, "ISO-8859-1");
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
byte[] payload = new byte[msg.length - 4];
System.arraycopy(msg, 4, payload, 0, payload.length);
_log.debug("Message read: type = [" + Integer.toHexString(type) + "] id = [" + getReadableForm(id)
+ "] payload length: " + payload.length + "]");
synchronized (lock) {
switch (type) {
case 0x51:
// ACK outgoing
s = (I2PSocketImpl) _outSockets.get(id);
if (s == null) {
_log.warn("No socket responsible for ACK packet");
return;
}
if (payload.length == 3 && s.getRemoteID(false) == null) {
String newID = new String(payload, "ISO-8859-1");
s.setRemoteID(newID);
return;
} else {
if (payload.length != 3)
_log.warn("Ack packet had " + payload.length + " bytes");
else
_log.warn("Remote ID already exists? " + s.getRemoteID());
return;
}
case 0x52:
// disconnect outgoing
_log.debug("*Disconnect outgoing!");
try {
s = (I2PSocketImpl) _outSockets.get(id);
if (s != null) {
if (payload.length > 0) {
_log.debug("Disconnect packet had "
+ payload.length + " bytes");
}
if (s.getRemoteID(false) == null) {
s.setRemoteID(null); // Just to wake up socket
return;
}
s.internalClose();
_outSockets.remove(id);
}
return;
} catch (Exception t) {
_log.error("Ignoring error on disconnect", t);
}
case 0x50:
// packet send outgoing
_log.debug("*Packet send outgoing [" + payload.length + "]");
s = (I2PSocketImpl) _outSockets.get(id);
if (s != null) {
s.queueData(payload);
return;
} else {
_log.error("Null socket with data available");
throw new IllegalStateException("Null socket with data available");
}
case 0xA1:
// SYN incoming
_log.debug("*Syn!");
String newLocalID = makeID(_inSockets);
Destination d = new Destination();
d.readBytes(new ByteArrayInputStream(payload));
if (_serverSocket == null) {
// The app did not instantiate an I2PServerSocket
byte[] packet = makePacket((byte) 0x52, id, newLocalID.getBytes("ISO-8859-1"));
boolean replySentOk = false;
synchronized (_session) {
replySentOk = _session.sendMessage(d, packet);
}
if (!replySentOk) {
_log.error("Error sending close to " + d.calculateHash().toBase64()
+ " in response to a new con message", new Exception("Failed creation"));
}
return;
}
s = new I2PSocketImpl(d, this, false, newLocalID);
s.setRemoteID(id);
if (_serverSocket.getNewSocket(s)) {
_inSockets.put(newLocalID, s);
byte[] packet = makePacket((byte) 0x51, id, newLocalID.getBytes("ISO-8859-1"));
boolean replySentOk = false;
synchronized (_session) {
replySentOk = _session.sendMessage(d, packet);
}
if (!replySentOk) {
_log.error("Error sending reply to " + d.calculateHash().toBase64()
+ " in response to a new con message", new Exception("Failed creation"));
s.internalClose();
}
} else {
byte[] packet = (" " + id).getBytes("ISO-8859-1");
packet[0] = 0x52;
boolean nackSent = session.sendMessage(d, packet);
if (!nackSent) {
_log.error("Error sending NACK for session creation");
}
s.internalClose();
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Message read: type = [" + Integer.toHexString(type)
+ "] id = [" + getReadableForm(id)
+ "] payload length: [" + payload.length + "]");
switch (type) {
case ACK:
ackAvailable(id, payload);
return;
case 0xA2:
// disconnect incoming
_log.debug("*Disconnect incoming!");
try {
s = (I2PSocketImpl) _inSockets.get(id);
if (payload.length == 0 && s != null) {
s.internalClose();
_inSockets.remove(id);
return;
} else {
if (payload.length > 0) _log.warn("Disconnect packet had " + payload.length + " bytes");
return;
}
} catch (Exception t) {
_log.error("Ignoring error on disconnect", t);
return;
}
case 0xA0:
// packet send incoming
_log.debug("*Packet send incoming [" + payload.length + "]");
s = (I2PSocketImpl) _inSockets.get(id);
if (s != null) {
s.queueData(payload);
return;
} else {
_log.error("Null socket with data available");
throw new IllegalStateException("Null socket with data available");
}
case 0xFF:
case CLOSE_OUT:
disconnectAvailable(id, payload);
return;
case DATA_OUT:
sendOutgoingAvailable(id, payload);
return;
case SYN:
synIncomingAvailable(id, payload, session);
return;
case CLOSE_IN:
disconnectIncoming(id, payload);
return;
case DATA_IN:
sendIncoming(id, payload);
case CHAFF:
// ignore
return;
}
_log.error("\n\n=============== Unknown packet! " + "============" + "\nType: " + (int) type
+ "\nID: " + getReadableForm(id) + "\nBase64'ed Data: " + Base64.encode(payload)
+ "\n\n\n");
if (id != null) {
_inSockets.remove(id);
_outSockets.remove(id);
}
default:
handleUnknown(type, id, payload);
return;
}
} catch (I2PException ise) {
_log.error("Error processing", ise);
} catch (IOException ioe) {
_log.error("Error processing", ioe);
} catch (IllegalStateException ise) {
_log.debug("Error processing", ise);
}
}
/**
* We've received an ACK packet (hopefully, in response to a SYN that we
* recently sent out). Notify the associated I2PSocket that we now have
* the remote stream ID (which should get things going, since the handshake
* is complete).
*
*/
private void ackAvailable(String id, byte payload[]) {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _outSockets.get(id);
}
if (s == null) {
_log.warn("No socket responsible for ACK packet");
return;
}
String remoteId = null;
remoteId = s.getRemoteID(false);
if ( (payload.length == 3) && (remoteId == null) ) {
String newID = toString(payload);
s.setRemoteID(newID);
return;
} else {
// (payload.length != 3 || getRemoteId != null)
if (_log.shouldLog(Log.WARN)) {
if (payload.length != 3)
_log.warn("Ack packet had " + payload.length + " bytes");
else
_log.warn("Remote ID already exists? " + remoteId);
}
return;
}
}
/**
* We received a disconnect packet, telling us to tear down the specified
* stream.
*/
private void disconnectAvailable(String id, byte payload[]) {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _outSockets.get(id);
}
_log.debug("*Disconnect outgoing!");
try {
if (s != null) {
if (payload.length > 0) {
_log.debug("Disconnect packet had "
+ payload.length + " bytes");
}
if (s.getRemoteID(false) == null) {
s.setRemoteID(null); // Just to wake up socket
return;
}
s.internalClose();
synchronized (lock) {
_outSockets.remove(id);
}
}
return;
} catch (Exception t) {
_log.error("Ignoring error on disconnect", t);
}
}
/**
* We've received data on a stream we created - toss the data onto
* the socket for handling.
*
* @throws IllegalStateException if the socket isn't open or isn't known
*/
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _outSockets.get(id);
}
// packet send outgoing
if (_log.shouldLog(Log.DEBUG))
_log.debug("*Packet send outgoing [" + payload.length + "]");
if (s != null) {
s.queueData(payload);
return;
} else {
_log.error("Null socket with data available");
throw new IllegalStateException("Null socket with data available");
}
}
/**
* We've received a SYN packet (a request for a new stream). If the client has
* said they want incoming sockets (by retrieving the serverSocket), the stream
* will be ACKed, but if they have not, they'll be NACKed)
*
* @throws DataFormatException if the destination in the SYN was invalid
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
*/
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
throws DataFormatException, I2PSessionException {
_log.debug("*Syn!");
Destination d = new Destination();
d.fromByteArray(payload);
I2PSocketImpl s = null;
boolean acceptConnections = (_serverSocket != null);
String newLocalID = null;
synchronized (lock) {
newLocalID = makeID(_inSockets);
if (acceptConnections) {
s = new I2PSocketImpl(d, this, false, newLocalID);
s.setRemoteID(id);
}
}
if (!acceptConnections) {
// The app did not instantiate an I2PServerSocket
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
boolean replySentOk = false;
synchronized (_session) {
replySentOk = _session.sendMessage(d, packet);
}
if (!replySentOk) {
_log.error("Error sending close to " + d.calculateHash().toBase64()
+ " in response to a new con message",
new Exception("Failed creation"));
}
return;
}
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
_inSockets.put(newLocalID, s);
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
boolean replySentOk = false;
replySentOk = _session.sendMessage(d, packet);
if (!replySentOk) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error sending reply to " + d.calculateHash().toBase64()
+ " in response to a new con message",
new Exception("Failed creation"));
s.internalClose();
}
} else {
// timed out or serverSocket closed
byte[] packet = toBytes(" " + id);
packet[0] = CLOSE_OUT;
boolean nackSent = session.sendMessage(d, packet);
if (!nackSent) {
_log.warn("Error sending NACK for session creation");
}
s.internalClose();
}
return;
}
/**
* We've received a disconnect for a socket we didn't initiate, so kill
* the socket.
*
*/
private void disconnectIncoming(String id, byte payload[]) {
_log.debug("*Disconnect incoming!");
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _inSockets.get(id);
if (payload.length == 0 && s != null) {
_inSockets.remove(id);
}
}
try {
if (payload.length == 0 && s != null) {
s.internalClose();
return;
} else {
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
_log.error("Disconnect packet had " + payload.length + " bytes");
if (s != null)
s.internalClose();
return;
}
} catch (Exception t) {
_log.error("Ignoring error on disconnect", t);
return;
}
}
/**
* We've received data on a stream we received - toss the data onto
* the socket for handling.
*
* @throws IllegalStateException if the socket isn't open or isn't known
*/
private void sendIncoming(String id, byte payload[]) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("*Packet send incoming [" + payload.length + "]");
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _inSockets.get(id);
}
if (s != null) {
s.queueData(payload);
return;
} else {
_log.info("Null socket with data available");
throw new IllegalStateException("Null socket with data available");
}
}
/**
* Unknown packet. moo.
*
*/
private void handleUnknown(int type, String id, byte payload[]) {
_log.error("\n\n=============== Unknown packet! " + "============"
+ "\nType: " + (int) type
+ "\nID: " + getReadableForm(id)
+ "\nBase64'ed Data: " + Base64.encode(payload)
+ "\n\n\n");
if (id != null) {
synchronized (lock) {
_inSockets.remove(id);
_outSockets.remove(id);
}
}
}
public void reportAbuse(I2PSession session, int severity) {
_log.error("Abuse reported [" + severity + "]");
}
@ -258,25 +404,24 @@ public class I2PSocketManager implements I2PSessionListener {
* @throws InterruptedIOException if the connection timeouts
* @throws I2PException if there is some other I2P-related problem
*/
public I2PSocket connect(Destination peer, I2PSocketOptions options) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
public I2PSocket connect(Destination peer, I2PSocketOptions options)
throws I2PException, ConnectException,
NoRouteToHostException, InterruptedIOException {
String localID, lcID;
I2PSocketImpl s;
synchronized (lock) {
localID = makeID(_outSockets);
lcID = getReadableForm(localID);
s = new I2PSocketImpl(peer, this, true, localID);
_outSockets.put(s.getLocalID(), s);
_outSockets.put(localID, s);
}
try {
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
_session.getMyDestination().writeBytes(pubkey);
String remoteID;
byte[] packet = makePacket((byte) 0xA1, localID, pubkey.toByteArray());
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
boolean sent = false;
synchronized (_session) {
sent = _session.sendMessage(peer, packet);
}
sent = _session.sendMessage(peer, packet);
if (!sent) {
_log.info("Unable to send & receive ack for SYN packet");
synchronized (lock) {
@ -285,32 +430,51 @@ public class I2PSocketManager implements I2PSessionListener {
throw new I2PException("Error sending through I2P network");
}
remoteID = s.getRemoteID(true, options.getConnectTimeout());
if (remoteID == null) { throw new ConnectException("Connection refused by peer"); }
if ("".equals(remoteID)) { throw new NoRouteToHostException("Unable to reach peer"); }
_log.debug("TIMING: s given out for remoteID " + getReadableForm(remoteID));
if (remoteID == null)
throw new ConnectException("Connection refused by peer");
if ("".equals(remoteID))
throw new NoRouteToHostException("Unable to reach peer");
if (_log.shouldLog(Log.DEBUG))
_log.debug("TIMING: s given out for remoteID "
+ getReadableForm(remoteID));
return s;
} catch (InterruptedIOException ioe) {
_log.error("Timeout waiting for ack from syn for id " + getReadableForm(lcID), ioe);
if (_log.shouldLog(Log.ERROR))
_log.error("Timeout waiting for ack from syn for id "
+ getReadableForm(lcID), ioe);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw new InterruptedIOException("Timeout waiting for ack");
} catch (ConnectException ex) {
s.internalClose();
throw ex;
} catch (NoRouteToHostException ex) {
s.internalClose();
throw ex;
} catch (IOException ex) {
_log.error("Error sending syn on id " + getReadableForm(lcID), ex);
if (_log.shouldLog(Log.ERROR))
_log.error("Error sending syn on id " + getReadableForm(lcID), ex);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw new I2PException("Unhandled IOException occurred");
} catch (I2PException ex) {
_log.info("Error sending syn on id " + getReadableForm(lcID), ex);
if (_log.shouldLog(Log.INFO))
_log.info("Error sending syn on id " + getReadableForm(lcID), ex);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw ex;
} catch (Exception e) {
s.internalClose();
_log.error("Unhandled error connecting", e);
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
}
}
@ -324,7 +488,8 @@ public class I2PSocketManager implements I2PSessionListener {
* @throws InterruptedIOException if the connection timeouts
* @throws I2PException if there is some other I2P-related problem
*/
public I2PSocket connect(Destination peer) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
NoRouteToHostException, InterruptedIOException {
return connect(peer, null);
}
@ -334,14 +499,9 @@ public class I2PSocketManager implements I2PSessionListener {
*
*/
public void destroySocketManager() {
try {
if (_serverSocket != null) {
_serverSocket.close();
_serverSocket = null;
}
} catch (I2PException ex) {
_log.error("Error closing I2PServerSocket", ex);
if (_serverSocket != null) {
_serverSocket.close();
_serverSocket = null;
}
synchronized (lock) {
@ -353,8 +513,9 @@ public class I2PSocketManager implements I2PSessionListener {
while (iter.hasNext()) {
id = (String)iter.next();
sock = (I2PSocketImpl)_inSockets.get(id);
_log.debug("Closing inSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Closing inSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
sock.internalClose();
}
@ -362,8 +523,9 @@ public class I2PSocketManager implements I2PSessionListener {
while (iter.hasNext()) {
id = (String)iter.next();
sock = (I2PSocketImpl)_outSockets.get(id);
_log.debug("Closing outSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Closing outSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
sock.internalClose();
}
}
@ -406,7 +568,7 @@ public class I2PSocketManager implements I2PSessionListener {
*/
public boolean ping(Destination peer, long timeoutMs) {
try {
return _session.sendMessage(peer, new byte[] { (byte) 0xFF});
return _session.sendMessage(peer, new byte[] { (byte) CHAFF});
} catch (I2PException ex) {
_log.error("I2PException:", ex);
return false;
@ -415,8 +577,8 @@ public class I2PSocketManager implements I2PSessionListener {
public void removeSocket(I2PSocketImpl sock) {
synchronized (lock) {
_log.debug("Removing socket \""
+ getReadableForm(sock.getLocalID()) + "\"");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Removing socket \"" + getReadableForm(sock.getLocalID()) + "\"");
_inSockets.remove(sock.getLocalID());
_outSockets.remove(sock.getLocalID());
lock.notify();
@ -424,14 +586,9 @@ public class I2PSocketManager implements I2PSessionListener {
}
public static String getReadableForm(String id) {
try {
if (id == null) return "(null)";
if (id.length() != 3) return "Bogus";
return Base64.encode(id.getBytes("ISO-8859-1"));
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
return null;
}
if (id == null) return "(null)";
if (id.length() != 3) return "Bogus";
return Base64.encode(toBytes(id));
}
/**
@ -439,22 +596,17 @@ public class I2PSocketManager implements I2PSessionListener {
*
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
*/
public static String makeID(HashMap uniqueIn) {
private static String makeID(HashMap uniqueIn) {
String newID;
try {
do {
int id = (int) (Math.random() * 16777215 + 1);
byte[] nid = new byte[3];
nid[0] = (byte) (id / 65536);
nid[1] = (byte) ((id / 256) % 256);
nid[2] = (byte) (id % 256);
newID = new String(nid, "ISO-8859-1");
} while (uniqueIn.get(newID) != null);
return newID;
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
return null;
}
do {
int id = (int) (Math.random() * 16777215 + 1);
byte[] nid = new byte[3];
nid[0] = (byte) (id / 65536);
nid[1] = (byte) ((id / 256) % 256);
nid[2] = (byte) (id % 256);
newID = toString(nid);
} while (uniqueIn.get(newID) != null);
return newID;
}
/**
@ -462,17 +614,28 @@ public class I2PSocketManager implements I2PSessionListener {
* the given payload
*/
public static byte[] makePacket(byte type, String id, byte[] payload) {
byte[] packet = new byte[payload.length + 4];
packet[0] = type;
byte[] temp = toBytes(id);
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
System.arraycopy(temp, 0, packet, 1, 3);
System.arraycopy(payload, 0, packet, 4, payload.length);
return packet;
}
private static final String toString(byte data[]) {
try {
byte[] packet = new byte[payload.length + 4];
packet[0] = type;
byte[] temp = id.getBytes("ISO-8859-1");
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
System.arraycopy(temp, 0, packet, 1, 3);
System.arraycopy(payload, 0, packet, 4, payload.length);
return packet;
} catch (UnsupportedEncodingException ex) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error building the packet", ex);
return new byte[0];
return new String(data, "ISO-8859-1");
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
}
}
private static final byte[] toBytes(String str) {
try {
return str.getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
}
}
}

View File

@ -2,7 +2,6 @@ package net.i2p.client.streaming;
/**
* Define the configuration for streaming and verifying data on the socket.
* No options available...
*
*/
public class I2PSocketOptions {

View File

@ -9,13 +9,28 @@ package net.i2p.sam;
*/
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileNotFoundException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import java.util.Iterator;
import net.i2p.data.Destination;
import net.i2p.data.DataFormatException;
import net.i2p.data.PrivateKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* SAM bridge implementation.
@ -23,15 +38,25 @@ import net.i2p.util.Log;
* @author human
*/
public class SAMBridge implements Runnable {
private final static Log _log = new Log(SAMBridge.class);
private ServerSocket serverSocket;
private Properties i2cpProps;
/**
* filename in which the name to private key mapping should
* be stored (and loaded from)
*/
private String persistFilename;
/**
* app designated destination name to the base64 of the I2P formatted
* destination keys (Destination+PrivateKey+SigningPrivateKey)
*/
private Map nameToPrivKeys = Collections.synchronizedMap(new HashMap(8));
private boolean acceptConnections = true;
private final static int SAM_LISTENPORT = 7656;
private static final int SAM_LISTENPORT = 7656;
public static final String DEFAULT_SAM_KEYFILE = "sam.keys";
private SAMBridge() {}
/**
@ -40,8 +65,11 @@ public class SAMBridge implements Runnable {
* @param listenHost hostname to listen for SAM connections on ("0.0.0.0" for all)
* @param listenPort port number to listen for SAM connections on
* @param i2cpProps set of I2CP properties for finding and communicating with the router
* @param persistFile location to store/load named keys to/from
*/
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps) {
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) {
persistFilename = persistFile;
loadKeys();
try {
if ( (listenHost != null) && !("0.0.0.0".equals(listenHost)) ) {
serverSocket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
@ -63,6 +91,97 @@ public class SAMBridge implements Runnable {
this.i2cpProps = i2cpProps;
}
/**
* Retrieve the destination associated with the given name
*
* @return null if the name does not exist, or if it is improperly formatted
*/
public Destination getDestination(String name) {
String val = (String)nameToPrivKeys.get(name);
if (val == null) return null;
try {
Destination d = new Destination();
d.fromBase64(val);
return d;
} catch (DataFormatException dfe) {
_log.error("Error retrieving the destination from " + name, dfe);
nameToPrivKeys.remove(name);
return null;
}
}
/**
* Retrieve the I2P private keystream for the given name, formatted
* as a base64 string (Destination+PrivateKey+SessionPrivateKey, as I2CP
* stores it).
*
* @return null if the name does not exist, else the stream
*/
public String getKeystream(String name) {
String val = (String)nameToPrivKeys.get(name);
if (val == null) return null;
return val;
}
/**
* Specify that the given keystream should be used for the given name
*
*/
public void addKeystream(String name, String stream) {
nameToPrivKeys.put(name, stream);
storeKeys();
}
/**
* Load up the keys from the persistFilename
*
*/
private synchronized void loadKeys() {
Map keys = new HashMap(16);
FileInputStream in = null;
try {
in = new FileInputStream(persistFilename);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line = null;
while ( (line = reader.readLine()) != null) {
int eq = line.indexOf('=');
String name = line.substring(0, eq);
String privKeys = line.substring(eq+1);
keys.put(name, privKeys);
}
} catch (FileNotFoundException fnfe) {
_log.warn("Key file does not exist at " + persistFilename);
} catch (IOException ioe) {
_log.error("Unable to read the keys from " + persistFilename, ioe);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
nameToPrivKeys = Collections.synchronizedMap(keys);
}
/**
* Store the current keys to disk in the location specified on creation
*
*/
private synchronized void storeKeys() {
FileOutputStream out = null;
try {
out = new FileOutputStream(persistFilename);
for (Iterator iter = nameToPrivKeys.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String privKeys = (String)nameToPrivKeys.get(name);
out.write(name.getBytes());
out.write('=');
out.write(privKeys.getBytes());
out.write('\n');
}
} catch (IOException ioe) {
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
}
/**
* Usage:
* <pre>SAMBridge [[listenHost ]listenPort[ name=val]*]</pre>
@ -72,15 +191,17 @@ public class SAMBridge implements Runnable {
* depth, etc.
*/
public static void main(String args[]) {
String keyfile = DEFAULT_SAM_KEYFILE;
int port = SAM_LISTENPORT;
String host = "0.0.0.0";
Properties opts = null;
if (args.length > 0) {
int portIndex = 0;
keyfile = args[0];
int portIndex = 1;
try {
port = Integer.parseInt(args[portIndex]);
} catch (NumberFormatException nfe) {
host = args[0];
host = args[1];
portIndex++;
try {
port = Integer.parseInt(args[portIndex]);
@ -91,7 +212,7 @@ public class SAMBridge implements Runnable {
}
opts = parseOptions(args, portIndex+1);
}
SAMBridge bridge = new SAMBridge(host, port, opts);
SAMBridge bridge = new SAMBridge(host, port, opts, keyfile);
I2PThread t = new I2PThread(bridge, "SAMListener");
t.start();
}
@ -114,7 +235,8 @@ public class SAMBridge implements Runnable {
}
private static void usage() {
System.err.println("Usage: SAMBridge [listenHost listenPortNum[ name=val]*]");
System.err.println("Usage: SAMBridge [keyfile [listenHost] listenPortNum[ name=val]*]");
System.err.println(" keyfile: location to persist private keys (default sam.keys)");
System.err.println(" listenHost: interface to listen on (0.0.0.0 for all interfaces)");
System.err.println(" listenPort: port to listen for SAM connections on (default 7656)");
System.err.println(" name=val: options to pass when connecting via I2CP, such as ");
@ -140,10 +262,18 @@ public class SAMBridge implements Runnable {
} catch (IOException e) {}
continue;
}
handler.setBridge(this);
handler.startHandling();
} catch (SAMException e) {
if (_log.shouldLog(Log.ERROR))
_log.error("SAM error: " + e.getMessage(), e);
try {
String reply = "HELLO REPLY RESULT=I2P_ERROR MESSAGE=\"" + e.getMessage() + "\"\n";
s.getOutputStream().write(reply.getBytes("ISO-8859-1"));
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("SAM Error sending error reply", ioe);
}
s.close();
}
}

View File

@ -30,6 +30,7 @@ import net.i2p.util.Log;
public class SAMDatagramSession extends SAMMessageSession {
private final static Log _log = new Log(SAMDatagramSession.class);
public static int DGRAM_SIZE_MAX = 31*1024;
private SAMDatagramReceiver recv = null;
@ -43,7 +44,8 @@ public class SAMDatagramSession extends SAMMessageSession {
* @param recv Object that will receive incoming data
*/
public SAMDatagramSession(String dest, Properties props,
SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
SAMDatagramReceiver recv) throws IOException,
DataFormatException, I2PSessionException {
super(dest, props);
this.recv = recv;
@ -58,7 +60,8 @@ public class SAMDatagramSession extends SAMMessageSession {
* @param recv Object that will receive incoming data
*/
public SAMDatagramSession(InputStream destStream, Properties props,
SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
SAMDatagramReceiver recv) throws IOException,
DataFormatException, I2PSessionException {
super(destStream, props);
this.recv = recv;
@ -73,6 +76,9 @@ public class SAMDatagramSession extends SAMMessageSession {
* @return True if the data was sent, false otherwise
*/
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
if (data.length > DGRAM_SIZE_MAX)
throw new DataFormatException("Datagram size exceeded (" + data.length + ")");
byte[] dgram = dgramMaker.makeI2PDatagram(data);
return sendBytesThroughMessageSession(dest, dgram);

View File

@ -30,6 +30,7 @@ public abstract class SAMHandler implements Runnable {
private final static Log _log = new Log(SAMHandler.class);
protected I2PThread thread = null;
protected SAMBridge bridge = null;
private Object socketWLock = new Object(); // Guards writings on socket
private Socket socket = null;
@ -71,6 +72,8 @@ public abstract class SAMHandler implements Runnable {
thread.start();
}
public void setBridge(SAMBridge bridge) { this.bridge = bridge; }
/**
* Actually handle the SAM protocol.
*
@ -124,7 +127,9 @@ public abstract class SAMHandler implements Runnable {
*
*/
protected final void closeClientSocket() throws IOException {
socket.close();
if (socket != null)
socket.close();
socket = null;
}
/**

View File

@ -32,8 +32,8 @@ public class SAMHandlerFactory {
*
* @param s Socket attached to SAM client
* @param i2cpProps config options for our i2cp connection
*
* @return A SAM protocol handler
* @throws SAMException if the connection handshake (HELLO message) was malformed
* @return A SAM protocol handler, or null if the client closed before the handshake
*/
public static SAMHandler createSAMHandler(Socket s, Properties i2cpProps) throws SAMException {
BufferedReader br;
@ -66,8 +66,8 @@ public class SAMHandlerFactory {
{
String opcode;
if (!(opcode = tok.nextToken()).equals("VERSION")) {
throw new SAMException("Unrecognized HELLO message opcode: \""
+ opcode + "\"");
throw new SAMException("Unrecognized HELLO message opcode: '"
+ opcode + "'");
}
}
@ -88,22 +88,8 @@ public class SAMHandlerFactory {
}
String ver = chooseBestVersion(minVer, maxVer);
if (ver == null) {
// Let's answer negatively
try {
OutputStream out = s.getOutputStream();
out.write("HELLO REPLY RESULT=NOVERSION\n".getBytes("ISO-8859-1"));
return null;
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")");
throw new SAMException("Character encoding error: "
+ e.getMessage());
} catch (IOException e) {
throw new SAMException("Error reading from socket: "
+ e.getMessage());
}
}
if (ver == null)
throw new SAMException("No version specified");
// Let's answer positively
try {
@ -135,8 +121,8 @@ public class SAMHandlerFactory {
throw new SAMException("BUG! (in handler instantiation)");
}
} catch (IOException e) {
_log.error("IOException caught during SAM handler instantiation");
return null;
_log.error("Error creating the v1 handler", e);
throw new SAMException("IOException caught during SAM handler instantiation");
}
return handler;
}

View File

@ -26,6 +26,7 @@ import net.i2p.util.Log;
public class SAMRawSession extends SAMMessageSession {
private final static Log _log = new Log(SAMRawSession.class);
public static final int RAW_SIZE_MAX = 32*1024;
private SAMRawReceiver recv = null;
/**
@ -64,6 +65,8 @@ public class SAMRawSession extends SAMMessageSession {
* @return True if the data was sent, false otherwise
*/
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
if (data.length > RAW_SIZE_MAX)
throw new DataFormatException("Data size limit exceeded (" + data.length + ")");
return sendBytesThroughMessageSession(dest, data);
}

View File

@ -26,6 +26,7 @@ import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.client.I2PClient;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
@ -100,11 +101,23 @@ public class SAMStreamSession {
allprops.putAll(System.getProperties());
allprops.putAll(props);
// FIXME: we should setup I2CP host and port, too
String i2cpHost = allprops.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
int i2cpPort = 7654;
String port = allprops.getProperty(I2PClient.PROP_TCP_PORT, "7654");
try {
i2cpPort = Integer.parseInt(port);
} catch (NumberFormatException nfe) {
throw new SAMException("Invalid I2CP port specified [" + port + "]");
}
// streams MUST be mode=guaranteed (though i think the socket manager
// enforces this anyway...
allprops.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
_log.debug("Creating I2PSocketManager...");
socketMgr = I2PSocketManagerFactory.createManager(destStream,
"127.0.0.1",
7654, allprops);
i2cpHost,
i2cpPort,
allprops);
if (socketMgr == null) {
throw new SAMException("Error creating I2PSocketManager");
}

View File

@ -33,7 +33,6 @@ import net.i2p.I2PAppContext;
public class SAMUtils {
private final static Log _log = new Log(SAMUtils.class);
private static I2PAppContext _context = new I2PAppContext();
/**
* Generate a random destination key
@ -86,7 +85,7 @@ public class SAMUtils {
* @return the Destination for the specified hostname, or null if not found
*/
public static Destination lookupHost(String name, OutputStream pubKey) {
NamingService ns = _context.namingService();
NamingService ns = I2PAppContext.getGlobalContext().namingService();
Destination dest = ns.lookup(name);
if ((pubKey != null) && (dest != null)) {
@ -109,9 +108,10 @@ public class SAMUtils {
*
* @param tok A StringTokenizer pointing to the SAM parameters
*
* @return Properties with the parsed SAM params, or null if none is found
* @throws SAMException if the data was formatted incorrectly
* @return Properties with the parsed SAM params
*/
public static Properties parseParams(StringTokenizer tok) {
public static Properties parseParams(StringTokenizer tok) throws SAMException {
int pos, nprops = 0, ntoks = tok.countTokens();
String token, param, value;
Properties props = new Properties();
@ -122,7 +122,7 @@ public class SAMUtils {
pos = token.indexOf("=");
if (pos == -1) {
_log.debug("Error in params format");
return null;
throw new SAMException("Bad formatting for param [" + token + "]");
}
param = token.substring(0, pos);
value = token.substring(pos + 1);
@ -135,22 +135,18 @@ public class SAMUtils {
_log.debug("Parsed properties: " + dumpProperties(props));
}
if (nprops != 0) {
return props;
} else {
return null;
}
return props;
}
/* Dump a Properties object in an human-readable form */
private static String dumpProperties(Properties props) {
Enumeration enum = props.propertyNames();
Enumeration names = props.propertyNames();
String msg = "";
String key, val;
boolean firstIter = true;
while (enum.hasMoreElements()) {
key = (String)enum.nextElement();
while (names.hasMoreElements()) {
key = (String)names.nextElement();
val = props.getProperty(key);
if (!firstIter) {

View File

@ -108,7 +108,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
break;
}
msg = buf.toString("ISO-8859-1");
msg = buf.toString("ISO-8859-1").trim();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("New message received: " + msg);
}
@ -154,10 +154,10 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")");
+ e.getMessage() + ")", e);
} catch (IOException e) {
_log.debug("Caught IOException ("
+ e.getMessage() + ")");
+ e.getMessage() + ")", e);
} catch (Exception e) {
_log.error("Unexpected exception", e);
} finally {
@ -189,32 +189,47 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
if ((rawSession != null) || (datagramSession != null)
|| (streamSession != null)) {
_log.debug("Trying to create a session, but one still exists");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n");
}
if (props == null) {
_log.debug("No parameters specified in SESSION CREATE message");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n");
}
dest = props.getProperty("DESTINATION");
if (dest == null) {
_log.debug("SESSION DESTINATION parameter not specified");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n");
}
props.remove("DESTINATION");
String destKeystream = null;
if (dest.equals("TRANSIENT")) {
_log.debug("TRANSIENT destination requested");
ByteArrayOutputStream priv = new ByteArrayOutputStream();
ByteArrayOutputStream priv = new ByteArrayOutputStream(640);
SAMUtils.genRandomKey(priv, null);
dest = Base64.encode(priv.toByteArray());
destKeystream = Base64.encode(priv.toByteArray());
} else {
destKeystream = bridge.getKeystream(dest);
if (destKeystream == null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Custom destination specified [" + dest + "] but it isnt know, creating a new one");
ByteArrayOutputStream baos = new ByteArrayOutputStream(640);
SAMUtils.genRandomKey(baos, null);
destKeystream = Base64.encode(baos.toByteArray());
bridge.addKeystream(dest, destKeystream);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Custom destination specified [" + dest + "] and it is already known");
}
}
String style = props.getProperty("STYLE");
if (style == null) {
_log.debug("SESSION STYLE parameter not specified");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n");
}
props.remove("STYLE");
@ -225,40 +240,40 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
} else if (style.equals("STREAM")) {
String dir = props.getProperty("DIRECTION");
if (dir == null) {
_log.debug("No DIRECTION parameter in STREAM session");
return false;
_log.debug("No DIRECTION parameter in STREAM session, defaulting to BOTH");
dir = "BOTH";
}
if (!dir.equals("CREATE") && !dir.equals("RECEIVE")
&& !dir.equals("BOTH")) {
_log.debug("Unknow DIRECTION parameter value: " + dir);
return false;
_log.debug("Unknow DIRECTION parameter value: [" + dir + "]");
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n");
}
props.remove("DIRECTION");
streamSession = new SAMStreamSession(dest, dir,props,this);
streamSession = new SAMStreamSession(destKeystream, dir,props,this);
} else {
_log.debug("Unrecognized SESSION STYLE: \"" + style +"\"");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n");
}
return writeString("SESSION STATUS RESULT=OK DESTINATION="
+ dest + "\n");
} else {
_log.debug("Unrecognized SESSION message opcode: \""
+ opcode + "\"");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n");
}
} catch (DataFormatException e) {
_log.debug("Invalid destination specified");
return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + "\n");
return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
} catch (I2PSessionException e) {
_log.debug("I2P error when instantiating session", e);
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
} catch (SAMException e) {
_log.error("Unexpected SAM error", e);
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
} catch (IOException e) {
_log.error("Unexpected IOException", e);
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
}
}
@ -266,7 +281,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
private boolean execDestMessage(String opcode, Properties props) {
if (opcode.equals("GENERATE")) {
if (props != null) {
if (props.size() > 0) {
_log.debug("Properties specified in DEST GENERATE message");
return false;
}
@ -483,159 +498,171 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
if (opcode.equals("SEND")) {
if (props == null) {
_log.debug("No parameters specified in STREAM SEND message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM SEND message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM SEND ID specified: " + strid);
return false;
}
}
int size;
{
String strsize = props.getProperty("SIZE");
if (strsize == null) {
_log.debug("Size not specified in STREAM SEND message");
return false;
}
try {
size = Integer.parseInt(strsize);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM SEND size specified: "+strsize);
return false;
}
if (!checkSize(size)) {
_log.debug("Specified size (" + size
+ ") is out of protocol limits");
return false;
}
}
try {
DataInputStream in = new DataInputStream(getClientSocketInputStream());
byte[] data = new byte[size];
in.readFully(data);
if (!streamSession.sendBytes(id, data)) {
_log.error("STREAM SEND failed");
return false;
}
return true;
} catch (EOFException e) {
_log.debug("Too few bytes with RAW SEND message (expected: "
+ size);
return false;
} catch (IOException e) {
_log.debug("Caught IOException while parsing RAW SEND message",
e);
return false;
}
return execStreamSend(props);
} else if (opcode.equals("CONNECT")) {
if (props == null) {
_log.debug("No parameters specified in STREAM CONNECT message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM SEND message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
return false;
}
if (id < 1) {
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
return false;
}
props.remove("ID");
}
String dest = props.getProperty("DESTINATION");
if (dest == null) {
_log.debug("Destination not specified in RAW SEND message");
return false;
}
props.remove("DESTINATION");
try {
if (!streamSession.connect(id, dest, props)) {
_log.debug("STREAM connection failed");
return false;
}
return writeString("STREAM STATUS RESULT=OK ID=" + id + "\n");
} catch (DataFormatException e) {
_log.debug("Invalid destination in STREAM CONNECT message");
return writeString("STREAM STATUS RESULT=INVALID_KEY ID="
+ id + "\n");
} catch (SAMInvalidDirectionException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=INVALID_DIRECTION ID="
+ id + "\n");
} catch (ConnectException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=CONNECTION_REFUSED ID="
+ id + "\n");
} catch (NoRouteToHostException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=CANT_REACH_PEER ID="
+ id + "\n");
} catch (InterruptedIOException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=TIMEOUT ID="
+ id + "\n");
} catch (I2PException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
+ id + "\n");
}
return execStreamConnect(props);
} else if (opcode.equals("CLOSE")) {
if (props == null) {
_log.debug("No parameters specified in STREAM CLOSE message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM CLOSE message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM CLOSE ID specified: " +strid);
return false;
}
}
return streamSession.closeConnection(id);
return execStreamClose(props);
} else {
_log.debug("Unrecognized RAW message opcode: \""
+ opcode + "\"");
return false;
}
}
private boolean execStreamSend(Properties props) {
if (props == null) {
_log.debug("No parameters specified in STREAM SEND message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM SEND message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM SEND ID specified: " + strid);
return false;
}
}
int size;
{
String strsize = props.getProperty("SIZE");
if (strsize == null) {
_log.debug("Size not specified in STREAM SEND message");
return false;
}
try {
size = Integer.parseInt(strsize);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM SEND size specified: "+strsize);
return false;
}
if (!checkSize(size)) {
_log.debug("Specified size (" + size
+ ") is out of protocol limits");
return false;
}
}
try {
DataInputStream in = new DataInputStream(getClientSocketInputStream());
byte[] data = new byte[size];
in.readFully(data);
if (!streamSession.sendBytes(id, data)) {
_log.error("STREAM SEND failed");
return false;
}
return true;
} catch (EOFException e) {
_log.debug("Too few bytes with RAW SEND message (expected: "
+ size);
return false;
} catch (IOException e) {
_log.debug("Caught IOException while parsing RAW SEND message",
e);
return false;
}
}
private boolean execStreamConnect(Properties props) {
if (props == null) {
_log.debug("No parameters specified in STREAM CONNECT message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM SEND message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
return false;
}
if (id < 1) {
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
return false;
}
props.remove("ID");
}
String dest = props.getProperty("DESTINATION");
if (dest == null) {
_log.debug("Destination not specified in RAW SEND message");
return false;
}
props.remove("DESTINATION");
try {
if (!streamSession.connect(id, dest, props)) {
_log.debug("STREAM connection failed");
return false;
}
return writeString("STREAM STATUS RESULT=OK ID=" + id + "\n");
} catch (DataFormatException e) {
_log.debug("Invalid destination in STREAM CONNECT message");
return writeString("STREAM STATUS RESULT=INVALID_KEY ID="
+ id + "\n");
} catch (SAMInvalidDirectionException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=INVALID_DIRECTION ID="
+ id + "\n");
} catch (ConnectException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=CONNECTION_REFUSED ID="
+ id + "\n");
} catch (NoRouteToHostException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=CANT_REACH_PEER ID="
+ id + "\n");
} catch (InterruptedIOException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=TIMEOUT ID="
+ id + "\n");
} catch (I2PException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
+ id + "\n");
}
}
private boolean execStreamClose(Properties props) {
if (props == null) {
_log.debug("No parameters specified in STREAM CLOSE message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM CLOSE message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM CLOSE ID specified: " +strid);
return false;
}
}
return streamSession.closeConnection(id);
}
/* Check whether a size is inside the limits allowed by this protocol */
private boolean checkSize(int size) {

View File

@ -0,0 +1,78 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.Log;
public class TestCreateSessionStream {
private static Log _log = new Log(TestCreateSessionStream.class);
private static void runTest(String samHost, int samPort, String conOptions) {
testTransient(samHost, samPort, conOptions);
testNewDest(samHost, samPort, conOptions);
testOldDest(samHost, samPort, conOptions);
}
private static void testTransient(String host, int port, String conOptions) {
testDest(host, port, conOptions, "TRANSIENT");
_log.debug("\n\nTest of transient complete\n\n\n");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
private static void testNewDest(String host, int port, String conOptions) {
String destName = "Alice" + Math.random();
testDest(host, port, conOptions, destName);
}
private static void testOldDest(String host, int port, String conOptions) {
String destName = "Alice" + Math.random();
testDest(host, port, conOptions, destName);
_log.debug("\n\nTest of initial contact for " + destName + " complete, waiting 90 seconds");
try { Thread.sleep(90*1000); } catch (InterruptedException ie) {}
_log.debug("now testing subsequent contact\n\n\n");
testDest(host, port, conOptions, destName);
_log.debug("\n\nTest of subsequent contact complete\n\n");
}
private static void testDest(String host, int port, String conOptions, String destName) {
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.debug("line read for valid version: " + line);
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + destName + " " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination " + destName + ": " + line);
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,62 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.Log;
public class TestDest {
private static Log _log = new Log(TestDest.class);
private static void runTest(String samHost, int samPort, String conOptions) {
test(samHost, samPort, conOptions);
}
private static void test(String host, int port, String conOptions) {
_log.info("\n\nTesting a DEST generate (should come back with 'DEST REPLY PUB=val PRIV=val')\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.debug("line read for valid version: " + line);
String req = "SESSION CREATE STYLE=STREAM DESTINATION=testNaming " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.debug("Response to creating the session with destination testNaming: " + line);
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
String lookup = "DEST GENERATE\n";
out.write(lookup.getBytes());
line = reader.readLine();
_log.info("Response from the dest generate: " + line);
_log.debug("The abouve should be a DEST REPLY");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0"; // "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,76 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.Log;
public class TestHello {
private static Log _log = new Log(TestHello.class);
private static void runTest(String samHost, int samPort) {
testValidVersion(samHost, samPort);
testInvalidVersion(samHost, samPort);
testCorruptLine(samHost, samPort);
}
private static void testValidVersion(String host, int port) {
_log.info("\n\nTesting valid version (should come back with an OK)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.info("line read for valid version: " + line);
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
private static void testInvalidVersion(String host, int port) {
_log.info("\n\nTesting invalid version (should come back with an error)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=9.0 MAX=8.3\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.info("line read for invalid version: " + line);
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
private static void testCorruptLine(String host, int port) {
_log.info("\n\nTesting corrupt line (should come back with an error)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO h0 h0 h0\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.info("line read for valid version: " + line);
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,82 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.Log;
public class TestNaming {
private static Log _log = new Log(TestNaming.class);
private static void runTest(String samHost, int samPort, String conOptions) {
testMe(samHost, samPort, conOptions);
testDuck(samHost, samPort, conOptions);
testUnknown(samHost, samPort, conOptions);
}
private static void testMe(String host, int port, String conOptions) {
testName(host, port, conOptions, "ME");
_log.debug("\n\nTest of ME complete\n\n\n");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
private static void testDuck(String host, int port, String conOptions) {
testName(host, port, conOptions, "duck.i2p");
_log.debug("\n\nTest of duck complete\n\n\n");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
private static void testUnknown(String host, int port, String conOptions) {
testName(host, port, conOptions, "www.odci.gov");
_log.debug("\n\nTest of unknown host complete\n\n\n");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
private static void testName(String host, int port, String conOptions, String name) {
_log.info("\n\nTesting a name lookup (should come back with 'NAMING REPLY RESULT=OK VALUE=someName)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.debug("line read for valid version: " + line);
String req = "SESSION CREATE STYLE=STREAM DESTINATION=testNaming " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.debug("Response to creating the session with destination testNaming: " + line);
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
String lookup = "NAMING LOOKUP NAME=" + name + "\n";
out.write(lookup.getBytes());
line = reader.readLine();
_log.info("Response from the lookup for [" + name +"]: " + line);
_log.debug("The abouve should be a NAMING REPLY");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,223 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
import java.util.Collections;
import java.util.HashMap;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
/**
* <ol>
* <li>start up SAM</li>
* <li>Alice connects as 'Alice', gets her destination, stashes it away, and
* listens for any streams, echoing back whatever she receives.</li>
* <li>Bob connects as 'Bob', establishes a stream to the destination Alice
* stashed away, sends a few bundles of data, and closes the stream.</li>
* <li>Alice and Bob disconnect from SAM</li>
* <li>SAM bridge taken down</li>
* </ol>
*/
public class TestStreamTransfer {
private static Log _log = new Log(TestStreamTransfer.class);
private static String _alice = null;
private static boolean _dead = false;
private static void runTest(String samHost, int samPort, String conOptions) {
startAlice(samHost, samPort, conOptions);
testBob(samHost, samPort, conOptions);
}
private static void startAlice(String host, int port, String conOptions) {
_log.info("\n\nStarting up Alice");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.debug("line read for valid version: " + line);
String req = "SESSION CREATE STYLE=STREAM DESTINATION=Alice " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination Alice: " + line);
req = "NAMING LOOKUP NAME=ME\n";
out.write(req.getBytes());
line = reader.readLine();
StringTokenizer tok = new StringTokenizer(line);
String maj = tok.nextToken();
String min = tok.nextToken();
Properties props = SAMUtils.parseParams(tok);
String value = props.getProperty("VALUE");
if (value == null) {
_log.error("No value for ME found! [" + line + "]");
return;
} else {
_log.info("Alice is located at " + value);
}
_alice = value;
I2PThread aliceThread = new I2PThread(new AliceRunner(reader, out, s));
aliceThread.setName("Alice");
aliceThread.start();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
private static class AliceRunner implements Runnable {
private BufferedReader _reader;
private OutputStream _out;
private Socket _s;
/** ID (string) to base64 destination */
private Map _streams;
public AliceRunner(BufferedReader reader, OutputStream out, Socket s) {
_reader = reader;
_out = out;
_s = s;
_streams = Collections.synchronizedMap(new HashMap(4));
}
public void run() {
while (!_dead) {
try {
doRun();
} catch (Exception e) {
_log.error("Error running alice", e);
try { _reader.close(); } catch (IOException ioe) {}
try { _out.close(); } catch (IOException ioe) {}
try { _s.close(); } catch (IOException ioe) {}
_streams.clear();
}
}
}
private void doRun() throws IOException, SAMException {
String line = _reader.readLine();
StringTokenizer tok = new StringTokenizer(line);
String maj = tok.nextToken();
String min = tok.nextToken();
Properties props = SAMUtils.parseParams(tok);
if ( ("STREAM".equals(maj)) && ("CONNECTED".equals(min)) ) {
String dest = props.getProperty("DESTINATION");
String id = props.getProperty("ID");
if ( (dest == null) || (id == null) ) {
_log.error("Invalid STREAM CONNECTED line: [" + line + "]");
return;
}
dest = dest.trim();
id = id.trim();
_streams.put(id, dest);
} else if ( ("STREAM".equals(maj)) && ("CLOSED".equals(min)) ) {
String id = props.getProperty("ID");
if (id == null) {
_log.error("Invalid STREAM CLOSED line: [" + line + "]");
return;
}
_streams.remove(id);
} else if ( ("STREAM".equals(maj)) && ("RECEIVED".equals(min)) ) {
String id = props.getProperty("ID");
String size = props.getProperty("SIZE");
if ( (id == null) || (size == null) ) {
_log.error("Invalid STREAM RECEIVED line: [" + line + "]");
return;
}
id = id.trim();
size = size.trim();
int payloadSize = -1;
try {
payloadSize = Integer.parseInt(size);
} catch (NumberFormatException nfe) {
_log.error("Invalid SIZE in message [" + size + "]");
return;
}
// i know, its bytes, but this test uses chars
char payload[] = new char[payloadSize];
int read = _reader.read(payload);
if (read != payloadSize) {
_log.error("Incorrect size read - expected " + payloadSize + " got " + read);
return;
}
_log.info("Received from the stream " + id + ": [" + new String(payload) + "]");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
// now echo it back
String reply = "STREAM SEND ID=" + id +
" SIZE=" + payloadSize +
"\n" + payload;
_out.write(reply.getBytes());
_out.flush();
} else {
_log.error("Received unsupported type [" + maj + "/"+ min + "]");
return;
}
}
}
private static void testBob(String host, int port, String conOptions) {
_log.info("\n\nTesting Bob\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.debug("line read for valid version: " + line);
String req = "SESSION CREATE STYLE=STREAM DESTINATION=Bob " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination Bob: " + line);
req = "STREAM CONNECT ID=42 DESTINATION=" + _alice + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to the stream connect from Bob to Alice: " + line);
StringTokenizer tok = new StringTokenizer(line);
String maj = tok.nextToken();
String min = tok.nextToken();
Properties props = SAMUtils.parseParams(tok);
String result = props.getProperty("RESULT");
if (!("OK".equals(result))) {
_log.error("Unable to connect!");
_dead = true;
return;
}
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
out.write(req.getBytes());
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}
req = "STREAM CLOSE ID=42\n";
out.write(req.getBytes());
try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
_dead = true;
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,9 @@
package net.i2p.sam;
public class TestUtil {
public static void startupBridge(int listenPort) {
// Usage: SAMBridge [listenHost listenPortNum[ name=val]*]
SAMBridge.main(new String[] { "0.0.0.0", listenPort+"" });
}
}

41
apps/time/java/build.xml Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="time">
<target name="all" depends="clean, build" />
<target name="build" depends="builddep, jar" />
<target name="builddep">
<ant dir="../../../core/java/" target="build" />
</target>
<target name="compile">
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac srcdir="./src" debug="true" destdir="./build/obj" includes="**/*.java" classpath="../../../core/java/build/i2p.jar" />
</target>
<target name="jar" depends="compile">
<jar destfile="./build/timestamper.jar" basedir="./build/obj" includes="**/*.class">
<manifest>
<attribute name="Main-Class" value="net.i2p.time.Timestamper" />
<attribute name="Class-Path" value="i2p.jar timestamper.jar" />
</manifest>
</jar>
</target>
<target name="javadoc">
<mkdir dir="./build" />
<mkdir dir="./build/javadoc" />
<javadoc
sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc"
packagenames="*"
use="true"
access="package"
splitindex="true"
windowtitle="I2P timestamper" />
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
<ant dir="../../../core/java/" target="cleandep" />
</target>
<target name="distclean" depends="clean">
<ant dir="../../../core/java/" target="distclean" />
</target>
</project>

View File

@ -0,0 +1,138 @@
package net.i2p.time;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.text.DecimalFormat;
/**
* NtpClient - an NTP client for Java. This program connects to an NTP server
* and prints the response to the console.
*
* The local clock offset calculation is implemented according to the SNTP
* algorithm specified in RFC 2030.
*
* Note that on windows platforms, the curent time-of-day timestamp is limited
* to an resolution of 10ms and adversely affects the accuracy of the results.
*
*
* This code is copyright (c) Adam Buckley 2004
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version. A HTML version of the GNU General Public License can be
* seen at http://www.gnu.org/licenses/gpl.html
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* @author Adam Buckley
* (minor refactoring by jrandom)
*/
public class NtpClient {
/** difference between the unix epoch and jan 1 1900 (NTP uses that) */
private final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
private final static int NTP_PORT = 123;
/**
* Query the ntp servers, returning the current time from first one we find
*
* @return milliseconds since january 1, 1970 (UTC)
* @throws IllegalArgumentException if none of the servers are reachable
*/
public static long currentTime(String serverNames[]) {
if (serverNames == null)
throw new IllegalArgumentException("No NTP servers specified");
for (int i = 0; i < serverNames.length; i++) {
long now = currentTime(serverNames[i]);
if (now > 0)
return now;
}
throw new IllegalArgumentException("No reachable NTP servers specified");
}
/**
* Query the given NTP server, returning the current internet time
*
* @return milliseconds since january 1, 1970 (UTC), or -1 on error
*/
public static long currentTime(String serverName) {
try {
// Send request
DatagramSocket socket = new DatagramSocket();
InetAddress address = InetAddress.getByName(serverName);
byte[] buf = new NtpMessage().toByteArray();
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, NTP_PORT);
// Set the transmit timestamp *just* before sending the packet
// ToDo: Does this actually improve performance or not?
NtpMessage.encodeTimestamp(packet.getData(), 40,
(System.currentTimeMillis()/1000.0)
+ SECONDS_1900_TO_EPOCH);
socket.send(packet);
// Get response
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
// Immediately record the incoming timestamp
double destinationTimestamp = (System.currentTimeMillis()/1000.0) + SECONDS_1900_TO_EPOCH;
// Process response
NtpMessage msg = new NtpMessage(packet.getData());
double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
(msg.receiveTimestamp-msg.transmitTimestamp);
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
(msg.transmitTimestamp - destinationTimestamp)) / 2;
socket.close();
//System.out.println("host: " + serverName + " rtt: " + roundTripDelay + " offset: " + localClockOffset + " seconds");
return (long)(System.currentTimeMillis() + localClockOffset*1000);
} catch (IOException ioe) {
ioe.printStackTrace();
return -1;
}
}
public static void main(String[] args) throws IOException {
// Process command-line args
if(args.length <= 0) {
printUsage();
return;
// args = new String[] { "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
}
long now = currentTime(args);
System.out.println("Current time: " + new java.util.Date(now));
}
/**
* Prints usage
*/
static void printUsage() {
System.out.println(
"NtpClient - an NTP client for Java.\n" +
"\n" +
"This program connects to an NTP server and prints the current time to the console.\n" +
"\n" +
"\n" +
"Usage: java NtpClient server[ server]*\n" +
"\n" +
"\n" +
"This program is copyright (c) Adam Buckley 2004 and distributed under the terms\n" +
"of the GNU General Public License. This program is distributed in the hope\n" +
"that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n" +
"warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" +
"General Public License available at http://www.gnu.org/licenses/gpl.html for\n" +
"more details.");
}
}

View File

@ -0,0 +1,451 @@
package net.i2p.time;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* This class represents a NTP message, as specified in RFC 2030. The message
* format is compatible with all versions of NTP and SNTP.
*
* This class does not support the optional authentication protocol, and
* ignores the key ID and message digest fields.
*
* For convenience, this class exposes message values as native Java types, not
* the NTP-specified data formats. For example, timestamps are
* stored as doubles (as opposed to the NTP unsigned 64-bit fixed point
* format).
*
* However, the contructor NtpMessage(byte[]) and the method toByteArray()
* allow the import and export of the raw NTP message format.
*
*
* Usage example
*
* // Send message
* DatagramSocket socket = new DatagramSocket();
* InetAddress address = InetAddress.getByName("ntp.cais.rnp.br");
* byte[] buf = new NtpMessage().toByteArray();
* DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 123);
* socket.send(packet);
*
* // Get response
* socket.receive(packet);
* System.out.println(msg.toString());
*
*
* This code is copyright (c) Adam Buckley 2004
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version. A HTML version of the GNU General Public License can be
* seen at http://www.gnu.org/licenses/gpl.html
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*
* Comments for member variables are taken from RFC2030 by David Mills,
* University of Delaware.
*
* Number format conversion code in NtpMessage(byte[] array) and toByteArray()
* inspired by http://www.pps.jussieu.fr/~jch/enseignement/reseaux/
* NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
*
* @author Adam Buckley
*/
public class NtpMessage {
/**
* This is a two-bit code warning of an impending leap second to be
* inserted/deleted in the last minute of the current day. It's values
* may be as follows:
*
* Value Meaning
* ----- -------
* 0 no warning
* 1 last minute has 61 seconds
* 2 last minute has 59 seconds)
* 3 alarm condition (clock not synchronized)
*/
public byte leapIndicator = 0;
/**
* This value indicates the NTP/SNTP version number. The version number
* is 3 for Version 3 (IPv4 only) and 4 for Version 4 (IPv4, IPv6 and OSI).
* If necessary to distinguish between IPv4, IPv6 and OSI, the
* encapsulating context must be inspected.
*/
public byte version = 3;
/**
* This value indicates the mode, with values defined as follows:
*
* Mode Meaning
* ---- -------
* 0 reserved
* 1 symmetric active
* 2 symmetric passive
* 3 client
* 4 server
* 5 broadcast
* 6 reserved for NTP control message
* 7 reserved for private use
*
* In unicast and anycast modes, the client sets this field to 3 (client)
* in the request and the server sets it to 4 (server) in the reply. In
* multicast mode, the server sets this field to 5 (broadcast).
*/
public byte mode = 0;
/**
* This value indicates the stratum level of the local clock, with values
* defined as follows:
*
* Stratum Meaning
* ----------------------------------------------
* 0 unspecified or unavailable
* 1 primary reference (e.g., radio clock)
* 2-15 secondary reference (via NTP or SNTP)
* 16-255 reserved
*/
public short stratum = 0;
/**
* This value indicates the maximum interval between successive messages,
* in seconds to the nearest power of two. The values that can appear in
* this field presently range from 4 (16 s) to 14 (16284 s); however, most
* applications use only the sub-range 6 (64 s) to 10 (1024 s).
*/
public byte pollInterval = 0;
/**
* This value indicates the precision of the local clock, in seconds to
* the nearest power of two. The values that normally appear in this field
* range from -6 for mains-frequency clocks to -20 for microsecond clocks
* found in some workstations.
*/
public byte precision = 0;
/**
* This value indicates the total roundtrip delay to the primary reference
* source, in seconds. Note that this variable can take on both positive
* and negative values, depending on the relative time and frequency
* offsets. The values that normally appear in this field range from
* negative values of a few milliseconds to positive values of several
* hundred milliseconds.
*/
public double rootDelay = 0;
/**
* This value indicates the nominal error relative to the primary reference
* source, in seconds. The values that normally appear in this field
* range from 0 to several hundred milliseconds.
*/
public double rootDispersion = 0;
/**
* This is a 4-byte array identifying the particular reference source.
* In the case of NTP Version 3 or Version 4 stratum-0 (unspecified) or
* stratum-1 (primary) servers, this is a four-character ASCII string, left
* justified and zero padded to 32 bits. In NTP Version 3 secondary
* servers, this is the 32-bit IPv4 address of the reference source. In NTP
* Version 4 secondary servers, this is the low order 32 bits of the latest
* transmit timestamp of the reference source. NTP primary (stratum 1)
* servers should set this field to a code identifying the external
* reference source according to the following list. If the external
* reference is one of those listed, the associated code should be used.
* Codes for sources not listed can be contrived as appropriate.
*
* Code External Reference Source
* ---- -------------------------
* LOCL uncalibrated local clock used as a primary reference for
* a subnet without external means of synchronization
* PPS atomic clock or other pulse-per-second source
* individually calibrated to national standards
* ACTS NIST dialup modem service
* USNO USNO modem service
* PTB PTB (Germany) modem service
* TDF Allouis (France) Radio 164 kHz
* DCF Mainflingen (Germany) Radio 77.5 kHz
* MSF Rugby (UK) Radio 60 kHz
* WWV Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz
* WWVB Boulder (US) Radio 60 kHz
* WWVH Kaui Hawaii (US) Radio 2.5, 5, 10, 15 MHz
* CHU Ottawa (Canada) Radio 3330, 7335, 14670 kHz
* LORC LORAN-C radionavigation system
* OMEG OMEGA radionavigation system
* GPS Global Positioning Service
* GOES Geostationary Orbit Environment Satellite
*/
public byte[] referenceIdentifier = {0, 0, 0, 0};
/**
* This is the time at which the local clock was last set or corrected, in
* seconds since 00:00 1-Jan-1900.
*/
public double referenceTimestamp = 0;
/**
* This is the time at which the request departed the client for the
* server, in seconds since 00:00 1-Jan-1900.
*/
public double originateTimestamp = 0;
/**
* This is the time at which the request arrived at the server, in seconds
* since 00:00 1-Jan-1900.
*/
public double receiveTimestamp = 0;
/**
* This is the time at which the reply departed the server for the client,
* in seconds since 00:00 1-Jan-1900.
*/
public double transmitTimestamp = 0;
/**
* Constructs a new NtpMessage from an array of bytes.
*/
public NtpMessage(byte[] array) {
// See the packet format diagram in RFC 2030 for details
leapIndicator = (byte) ((array[0] >> 6) & 0x3);
version = (byte) ((array[0] >> 3) & 0x7);
mode = (byte) (array[0] & 0x7);
stratum = unsignedByteToShort(array[1]);
pollInterval = array[2];
precision = array[3];
rootDelay = (array[4] * 256.0) +
unsignedByteToShort(array[5]) +
(unsignedByteToShort(array[6]) / 256.0) +
(unsignedByteToShort(array[7]) / 65536.0);
rootDispersion = (unsignedByteToShort(array[8]) * 256.0) +
unsignedByteToShort(array[9]) +
(unsignedByteToShort(array[10]) / 256.0) +
(unsignedByteToShort(array[11]) / 65536.0);
referenceIdentifier[0] = array[12];
referenceIdentifier[1] = array[13];
referenceIdentifier[2] = array[14];
referenceIdentifier[3] = array[15];
referenceTimestamp = decodeTimestamp(array, 16);
originateTimestamp = decodeTimestamp(array, 24);
receiveTimestamp = decodeTimestamp(array, 32);
transmitTimestamp = decodeTimestamp(array, 40);
}
/**
* Constructs a new NtpMessage in client -> server mode, and sets the
* transmit timestamp to the current time.
*/
public NtpMessage() {
// Note that all the other member variables are already set with
// appropriate default values.
this.mode = 3;
this.transmitTimestamp = (System.currentTimeMillis()/1000.0) + 2208988800.0;
}
/**
* This method constructs the data bytes of a raw NTP packet.
*/
public byte[] toByteArray() {
// All bytes are automatically set to 0
byte[] p = new byte[48];
p[0] = (byte) (leapIndicator << 6 | version << 3 | mode);
p[1] = (byte) stratum;
p[2] = (byte) pollInterval;
p[3] = (byte) precision;
// root delay is a signed 16.16-bit FP, in Java an int is 32-bits
int l = (int) (rootDelay * 65536.0);
p[4] = (byte) ((l >> 24) & 0xFF);
p[5] = (byte) ((l >> 16) & 0xFF);
p[6] = (byte) ((l >> 8) & 0xFF);
p[7] = (byte) (l & 0xFF);
// root dispersion is an unsigned 16.16-bit FP, in Java there are no
// unsigned primitive types, so we use a long which is 64-bits
long ul = (long) (rootDispersion * 65536.0);
p[8] = (byte) ((ul >> 24) & 0xFF);
p[9] = (byte) ((ul >> 16) & 0xFF);
p[10] = (byte) ((ul >> 8) & 0xFF);
p[11] = (byte) (ul & 0xFF);
p[12] = referenceIdentifier[0];
p[13] = referenceIdentifier[1];
p[14] = referenceIdentifier[2];
p[15] = referenceIdentifier[3];
encodeTimestamp(p, 16, referenceTimestamp);
encodeTimestamp(p, 24, originateTimestamp);
encodeTimestamp(p, 32, receiveTimestamp);
encodeTimestamp(p, 40, transmitTimestamp);
return p;
}
/**
* Returns a string representation of a NtpMessage
*/
public String toString() {
String precisionStr = new DecimalFormat("0.#E0").format(Math.pow(2, precision));
return "Leap indicator: " + leapIndicator + "\n" +
"Version: " + version + "\n" +
"Mode: " + mode + "\n" +
"Stratum: " + stratum + "\n" +
"Poll: " + pollInterval + "\n" +
"Precision: " + precision + " (" + precisionStr + " seconds)\n" +
"Root delay: " + new DecimalFormat("0.00").format(rootDelay*1000) + " ms\n" +
"Root dispersion: " + new DecimalFormat("0.00").format(rootDispersion*1000) + " ms\n" +
"Reference identifier: " + referenceIdentifierToString(referenceIdentifier, stratum, version) + "\n" +
"Reference timestamp: " + timestampToString(referenceTimestamp) + "\n" +
"Originate timestamp: " + timestampToString(originateTimestamp) + "\n" +
"Receive timestamp: " + timestampToString(receiveTimestamp) + "\n" +
"Transmit timestamp: " + timestampToString(transmitTimestamp);
}
/**
* Converts an unsigned byte to a short. By default, Java assumes that
* a byte is signed.
*/
public static short unsignedByteToShort(byte b) {
if((b & 0x80)==0x80)
return (short) (128 + (b & 0x7f));
else
return (short) b;
}
/**
* Will read 8 bytes of a message beginning at <code>pointer</code>
* and return it as a double, according to the NTP 64-bit timestamp
* format.
*/
public static double decodeTimestamp(byte[] array, int pointer) {
double r = 0.0;
for(int i=0; i<8; i++) {
r += unsignedByteToShort(array[pointer+i]) * Math.pow(2, (3-i)*8);
}
return r;
}
/**
* Encodes a timestamp in the specified position in the message
*/
public static void encodeTimestamp(byte[] array, int pointer, double timestamp) {
// Converts a double into a 64-bit fixed point
for(int i=0; i<8; i++) {
// 2^24, 2^16, 2^8, .. 2^-32
double base = Math.pow(2, (3-i)*8);
// Capture byte value
array[pointer+i] = (byte) (timestamp / base);
// Subtract captured value from remaining total
timestamp = timestamp - (double) (unsignedByteToShort(array[pointer+i]) * base);
}
// From RFC 2030: It is advisable to fill the non-significant
// low order bits of the timestamp with a random, unbiased
// bitstring, both to avoid systematic roundoff errors and as
// a means of loop detection and replay detection.
array[7] = (byte) (Math.random()*255.0);
}
/**
* Returns a timestamp (number of seconds since 00:00 1-Jan-1900) as a
* formatted date/time string.
*/
public static String timestampToString(double timestamp) {
if(timestamp==0) return "0";
// timestamp is relative to 1900, utc is used by Java and is relative
// to 1970
double utc = timestamp - (2208988800.0);
// milliseconds
long ms = (long) (utc * 1000.0);
// date/time
String date = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss").format(new Date(ms));
// fraction
double fraction = timestamp - ((long) timestamp);
String fractionSting = new DecimalFormat(".000000").format(fraction);
return date + fractionSting;
}
/**
* Returns a string representation of a reference identifier according
* to the rules set out in RFC 2030.
*/
public static String referenceIdentifierToString(byte[] ref, short stratum, byte version) {
// From the RFC 2030:
// In the case of NTP Version 3 or Version 4 stratum-0 (unspecified)
// or stratum-1 (primary) servers, this is a four-character ASCII
// string, left justified and zero padded to 32 bits.
if(stratum==0 || stratum==1) {
return new String(ref);
}
// In NTP Version 3 secondary servers, this is the 32-bit IPv4
// address of the reference source.
else if(version==3) {
return unsignedByteToShort(ref[0]) + "." +
unsignedByteToShort(ref[1]) + "." +
unsignedByteToShort(ref[2]) + "." +
unsignedByteToShort(ref[3]);
}
// In NTP Version 4 secondary servers, this is the low order 32 bits
// of the latest transmit timestamp of the reference source.
else if(version==4) {
return "" + ((unsignedByteToShort(ref[0]) / 256.0) +
(unsignedByteToShort(ref[1]) / 65536.0) +
(unsignedByteToShort(ref[2]) / 16777216.0) +
(unsignedByteToShort(ref[3]) / 4294967296.0));
}
return "";
}
}

View File

@ -0,0 +1,103 @@
package net.i2p.time;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.net.URL;
import java.net.MalformedURLException;
import java.io.IOException;
/**
* Periodically query a series of NTP servers and post the offset
* to a given URL. It tries the NTP servers in order, contacting them
* using UDP port 123, and sends the current date to the URL specified
* (specifically, URL+"&now=" + yyyyMMdd_HH:mm:ss.SSS in the UK locale).
* It does this every 5 minutes, forever.
*
* Usage: <pre>
* Timestamper URL ntpServer1[ ntpServer2]*
* </pre>
*/
public class Timestamper implements Runnable {
private static Log _log = new Log(Timestamper.class);
private String _targetURL;
private String _serverList[];
private int DELAY_MS = 5*60*1000;
public Timestamper(String url, String serverNames[]) {
if (_log.shouldLog(Log.INFO))
_log.info("Creating new timestamper pointing at " + url);
_targetURL = url;
_serverList = serverNames;
}
public void startTimestamper() {
if (_log.shouldLog(Log.INFO))
_log.info("Starting timestamper pointing at " + _targetURL);
I2PThread t = new I2PThread(this, "Timestamper");
t.setPriority(I2PThread.MIN_PRIORITY);
t.start();
}
public void run() {
if (_log.shouldLog(Log.INFO))
_log.info("Starting up timestamper");
while (true) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Querying servers " + _serverList);
long now = NtpClient.currentTime(_serverList);
if (now < 0) {
_log.error("Unable to contact any of the NTP servers - network disconnect?");
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Stamp time");
stampTime(now);
}
try { Thread.sleep(DELAY_MS); } catch (InterruptedException ie) {}
}
}
/**
* Send an HTTP request to a given URL specifying the current time
*/
private void stampTime(long now) {
try {
URL url = new URL(_targetURL + "&now=" + getNow(now));
Object o = url.getContent();
// ignore the content
} catch (MalformedURLException mue) {
_log.error("Invalid URL", mue);
} catch (IOException ioe) {
_log.error("Error stamping the time", ioe);
}
}
private SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd_HH:mm:ss.SSS", Locale.UK);
private String getNow(long now) {
synchronized (_fmt) {
return _fmt.format(new Date(now));
}
}
public static void main(String args[]) {
if ( (args == null) || (args.length < 2) ) {
usage();
return;
//args = new String[] { "http://dev.i2p.net:80/somePath?pass=password", "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
}
String servers[] = new String[args.length-1];
System.arraycopy(args, 1, servers, 0, servers.length);
Timestamper ts = new Timestamper(args[0], servers);
ts.startTimestamper();
}
private static void usage() {
System.err.println("Usage: Timestamper URL ntpServer[ ntpServer]*");
_log.error("Usage: Timestamper URL ntpServer[ ntpServer]*");
}
}

View File

@ -23,6 +23,7 @@
<ant dir="apps/sam/java/" target="jar" />
<ant dir="apps/heartbeat/java/" target="jar" />
<ant dir="apps/netmonitor/java/" target="jar" />
<ant dir="apps/time/java/" target="jar" />
<ant dir="installer/java/" target="jar" />
</target>
<target name="compile" />
@ -36,6 +37,7 @@
<copy file="apps/sam/java/build/sam.jar" todir="build/" />
<copy file="apps/heartbeat/java/build/heartbeat.jar" todir="build/" />
<copy file="apps/netmonitor/java/build/netmonitor.jar" todir="build/" />
<copy file="apps/time/java/build/timestamper.jar" todir="build/" />
<copy file="installer/java/build/install.jar" todir="build/" />
<copy file="installer/java/build/guiinstall.jar" todir="build/" />
<copy file="installer/java/build/fetchseeds.jar" todir="build/" />
@ -64,6 +66,7 @@
<ant dir="apps/sam/java/" target="distclean" />
<ant dir="apps/heartbeat/java/" target="distclean" />
<ant dir="apps/netmonitor/java/" target="distclean" />
<ant dir="apps/time/java/" target="distclean" />
<ant dir="installer/java/" target="distclean" />
<delete>
<fileset dir="." includes="**/*.class" />

View File

@ -14,8 +14,8 @@ package net.i2p;
*
*/
public class CoreVersion {
public final static String ID = "$Revision: 1.3 $ $Date: 2004/04/20 04:18:54 $";
public final static String VERSION = "0.3.1";
public final static String ID = "$Revision: 1.4 $ $Date: 2004/04/30 18:04:13 $";
public final static String VERSION = "0.3.1.1";
public static void main(String args[]) {
System.out.println("I2P Core version: " + VERSION);

View File

@ -318,6 +318,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
return tags;
}
private static volatile long __notifierId = 0;
/**
* Recieve a payload message and let the app know its available
*/
@ -337,9 +339,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_sessionListener.messageAvailable(I2PSessionImpl.this, id, size);
}
});
notifier.setName("Notifier [" + _sessionId + "/" + id + "]");
long nid = ++__notifierId;
notifier.setName("Notifier " + nid);
notifier.setDaemon(true);
notifier.start();
if (_log.shouldLog(Log.INFO))
_log.info("Notifier " + nid + " is for session " + _sessionId + ", message " + id + "]");
}
}
@ -447,7 +452,6 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
if (_closed) return;
if (_log.shouldLog(Log.DEBUG)) _log.debug("Destroy the session", new Exception("DestroySession()"));
_closed = true;
if (sendDisconnect) {
try {
_producer.disconnect(this);
@ -455,6 +459,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
propogateError("Error destroying the session", ipe);
}
}
_closed = true;
closeSocket();
if (_sessionListener != null) _sessionListener.disconnected(this);
}

View File

@ -65,6 +65,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
}
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
return sendMessage(dest, payload, new SessionKey(), new HashSet(64));
}
@ -225,12 +226,12 @@ class I2PSessionImpl2 extends I2PSessionImpl {
_log.error("State with nonce " + state.getNonce()
+ " was not accepted? (no messageId!! found=" + found
+ " msgId=" + state.getMessageId() + ")",
new Exception("Race on accept/success status messages?"));
new Exception("Race on accept/success status messages, or reconnected?"));
nackTags(state);
//if (_log.shouldLog(Log.CRIT))
// _log.log(Log.CRIT, "Disconnecting/reconnecting because we never were accepted!");
//disconnect();
//return false;
return false;
}
if (_log.shouldLog(Log.DEBUG))

View File

@ -30,6 +30,8 @@ public class I2CPMessageReader {
private I2CPMessageEventListener _listener;
private I2CPMessageReaderRunner _reader;
private Thread _readerThread;
private static volatile long __readerId = 0;
public I2CPMessageReader(InputStream stream, I2CPMessageEventListener lsnr) {
_stream = stream;
@ -37,7 +39,7 @@ public class I2CPMessageReader {
_reader = new I2CPMessageReaderRunner();
_readerThread = new I2PThread(_reader);
_readerThread.setDaemon(true);
_readerThread.setName("I2CP Reader");
_readerThread.setName("I2CP Reader " + (++__readerId));
}
public void setListener(I2CPMessageEventListener lsnr) {

View File

@ -30,7 +30,7 @@ class PersistenceHelper {
_log.error("Error formatting " + val + " into a long", nfe);
}
} else {
_log.error("Key " + prefix + name + " does not exist");
_log.warn("Key " + prefix + name + " does not exist");
}
return 0;
}

View File

@ -35,7 +35,7 @@ public class Clock {
/** if the clock is skewed by 3+ days, fuck 'em */
public final static long MAX_OFFSET = 3 * 24 * 60 * 60 * 1000;
/** if the clock skewed changes by less than 1s, ignore the update (so we don't slide all over the place) */
public final static long MIN_OFFSET_CHANGE = 30 * 1000;
public final static long MIN_OFFSET_CHANGE = 10 * 1000;
/**
* Specify how far away from the "correct" time the computer is - a positive

View File

@ -24,7 +24,8 @@ public class NativeBigInteger extends BigInteger {
_log.info("Native BigInteger library jbigi loaded");
} catch (UnsatisfiedLinkError ule) {
_nativeOk = false;
_log.warn("Native BigInteger library jbigi not loaded - using pure java", ule);
_log.log(Log.CRIT, "Native BigInteger library jbigi not loaded - using pure java");
_log.warn("jbigi not loaded", ule);
}
}

View File

@ -1,6 +1,9 @@
; TC's hosts.txt guaranteed freshness
; $Id: hosts.txt,v 1.28 2004/03/19 21:58:44 jrandom Exp $
; $Id: hosts.txt,v 1.3 2004/05/03 02:04:12 jrandom Exp $
; changelog:
; (1.30) added xilog.i2p
; (1.29) added lucky.i2p, removed lp.i2p
; (1.28) added sungo.i2p
; (1.27) added jar.i2p
; (1.26) added tor-www-proxy.i2p (WWW through tor by human - won't be up 24/7!)
; (1.25) added ugha.i2p
@ -62,7 +65,6 @@ mesh.firerabbit.i2p=BLt3pciNQcVIU-IGnTfSsrputh~b6drZpc1vH8qeA745XoE~nMCGtw8S7HGY
chess.fillament.i2p=8xvXLwcBYu2MxqMAoG6DIahvkNAwBQs43kdZTg9SlouzC3JSQ25RHvspbrxWoGIVAlB~XCxVmBB6EolYEHoSZv2YCziOj4UVxEbVP7nHkh32-7Uc5T7xlcjk8-rsmZzdgz9NhxKVn2Uhp3xtcdVAiyG4mpXisvK-7NgHY-mXPNvj6goaH58roeDUZ5otJN7nCFmcRAUpaUk-nlQs8YfgSCCEsWKOWhsVnAaHwtqtDlpdTo1YKooACMRSa-DcV5W75Il80JEWBD79qpSAeONGAOMMPT~VEMwNNg001VG-UZbvyspZdxHaETw2yd7N9l3-mwI-sYcveTTnNXLWO8IjdgDLdUIP5qrIW6WS9XZIHRKqT2kpwEw7xsEgCA~qSNiZWeai8n6Zs0rLmdyeZeafVEEW9vr6CKcLZ5W7i1SMDqCKnzSbZdd2Hvpb9aWFf5foEjLt33n8w2KSaCUe4zmN~xuQMq2yiB-vQ9~5fgPmphlMxo3ca5MTCfbhshw5137YAAAA
kaji.i2p=i-nivH~36AgabgzvS06oKHR~MLoy6NA0oSv8JuNJDLZB8FXEDzIiyzWISmw9XJm8I7QqZ1yFH8~xe84STCHHvMMIQQrfBmOUODLWbKZor~~KhiHdNLtfVZC5BpnXkBCJkklj~fMYSpWa0C~pVRrZl75RoGtBjDVP9P8hioTv5B6RC86g2ypBH5r093rY0wnzxSL8-ZuV3F~H48VYbqro8cRlbMmjx2oEsSHkDpQyjCMVkIYKaCijkSArqZTG~zX6op6Ng9CJwdrkjKSsbzjV6MLnE4aNv-jm2WaGGD5pR24h7e3ImDOGAr17tXRtmNX5ZEQ1udQp8qIhd8UMUumrnm962r8KJWK~9WNzcVeqDrIxaaxC7vcQmXxoPeEW2efbH0yKhVZ7OFu~I9cAapSe~aNWp9UK4URSpuJvOedt0axp3ORaaM-a5U7noW3Ao-HB83qfFEPU-6uUu16HNiPqCFMJiA0qODTOwHiyyx4HKQvbhjujh4mmknSbsuapdgR1AAAA
human.i2p=ji02vZzrp51aAsi~NZ8hwMLbr1rzMtdPUSiWAU94H89kO-~9Oc8Vucpf2vc6NOvStXpeTOqcRz-WhF01W8gj-YLP3WFskbjCcUwz0yF8dHonBeC4A5l4CjupAaztBSMbhu4vyN9FJkqZUFN01eZbQ9UqgXgLWMp4DtbUwf78y8VrzdAfmUOrVn6Iu89B~HUfOAKnpIlQXyGsQk1fnLw3PzDo2PVi8Q3C1Ntn0ybovD1xDKPrrHliTK4or2YujTcEOhSBLK4tQGvouN-tWqcVoF9O814yNGtze~uot62ACGJj9nvEU3J7QPgOl~fgBJ5Hvom0Qu-yPAGJuAZa29LSHnvRhih~z~6lWZYHREBYXQ58IzKktk90xJWcTwlwRRhyO-Sz3A5JYR3jM97h4SsoYBVrjK9TWnvGKj~fc8wYRDzt1oFVfubLlT-17LUzNc59H-2Vhxx8yaey8J~dqdWO0YdowqekxxlZf2~IVSGuLvIZYsr7~f--mLAxCgQBCjOjAAAA
lp.i2p=i~VkqY6fes7yCR6Yn4Nowuo3h621nKC9yvMVOEGW6nC61kLRKS5xcr7JdsFohF02Z7neR7Nv3GLB1qTvqguU2SekCPpfzNYNSDCZZVPMy1IXegZupmMMtDXnY7dAwcy~d5hdjwtODidfiG37~C-AE2g8cogJSAG5-sgOTOcA288fu0n9qYn9lejK6T8vQYMJIgqW73K5ErLg8F9C8yPfCNwRlqOZ8xSWowpvlyzWy5OFMmhk00S4JA5TAKlw-ILQDT20qFOHNcCWc8biXjNDozMxcw~h3rq-TQtWyW0-J8ERf2tmjPSoRr7zxKTFXlP0ibQz8HqAhlt9xDKlpd2LZyI9cKvMNfmGBIrFZHckO~rHtCTNtA7dN-UheC~k2bX0hDlQ8A6QWKovTnt3yktOJjwxBQU3iL7UGXbS8M~8t9LmiEKdTZgMUnAcBLkrPOfTAKpkCLE-~yAYBDj~U8ZKJKEtD-gLDY9~slVuo2AgNLYF6bipXxSMsoCzevJvcWnqAAAA
bt1.eco.i2p=SUHjD4QvbuY5VVTGTm49U8B~DtvTS2bwO1lREMNfJtZU6rxa4tgdqaIpUjRRrbZYLxZcgxjUYx4Bq7gcjriETIRaMy7lQtMx9zFk~XLE5baTmZeXc1~xuQCCTelnYv0yUswWSCZx7ll2a-Y6d88jvTydfxcCTNAYclT~gHnA0kU2kRHD5vgEteidiXFBVer2Ps68tnARUqURDKxTRyRnWtrxwQocmP5MJG29e4dIptcecxX8bKhgqgONzPAmYzAR7F694wEOXJZQO67ro0YQEuQmDXICBI8IwkbAt8qM~BG~JF1H26iWblWWs8mGJonwl34-CpVjMSXWVk~SC~60-xr97CsWSGAeZqrD8pLJyDGFsJ0jNFV0L6Q97ryUViakVwsHaAMlxZ3Hh7KAc6TUcJEGvuygRJ2DKeKA1wyuUDFc08m40HrRVwydn0hwzs4Qfb8hXfGHQ9-yauK6Gk-HvYFUL9qcIrOfrZGgq4xLVdgVh2wB1mIOTkcBMMti4941AAAA
gernika.i2p=1D9ee5J04ZI7pvCwQ3hXQMpeMvbNw9cz3V2pioQ9LakwRQzfMEb9CVAeiFt-wE4HyTFWKeof5rz8F5vmIqFMaH~oMJSkCyNtwPfqiRAENeNeALHbY2plMPfqCfEFR4GkTqXnalAkJGqDjo55CokUfalEVTGMvNiv6i6dmNvwS8~0X56sXIaXgLKuGIK~UNOg9hT8A~uEGQWXwTKD3EDmECsJL40iYcT1UR9rffzuyxOvDhL12HbJ8bIlUGarzEscH-jolj5ShvZAbEyw-MnVzR9LiEqy7DaniKpPtC0oXRZuz7PpcQTqzN-zgQaLq8bHTx7NHIfTuA4P~hhz-STO4SjPot7h~Gbdglc193OmGlL5QwbfjXfdOIccBDh6~jtFaa28GxHrTMoi9GafjnllLfWpvynN1y5H7Jh7Uw3E7KDtBGVsDg9-btyvyQLP3kkqPfIAn7Oj6ePHr9u-TN9ZwDbWj~QBmXXutsE~lLu7aT7kv5Jx6PFmLEeWPib82UuGAAAA
www.aum.i2p=8x3TYbh9aq6EVo8rSuPWBVSrwD~yS1al6z0RZYvRaQFXL9hFUUJYJNL2n3oR3tBg7Zr00MjqntIuBMd20LUKasnklTp4hDlDCE0KfeftYh~bFGykMRf0yTYEWaMHYpIRBY-IJEvSVlgHe8E4AWLMv-b6VKCDZ0~0AdUrsHQ0Qb4NQ-igBPZfU6c~UU0tUVUl1Efsuz1CdAp5pw5RdPviFtPH4tMvUca2t54Rwa-6v6QCqFZ7S2awyhAa73Zb9YlcqT4hP1JHF0wR0rL-OEoJV0gG47Co4Zr903SNW6cgKDj3lW1tIpzcVMoH3BE22SMEVjYyEHgAORdoYwaj19CUg1slDGmvUCoq4dPsnCIrvV7N0LeoUkZekt2pvr~yCH47ENV3oQYpFVLcMLN82tzI0ZFz5IyBHWGr22vlDlT1C-QVhAYQKN92XubDXSEgrhWv5IHPB0h~EgZi-rDcsJG2zb6ZqjtKFHp4CnNvTUxE1cCJh0aR1MDzM~o0iSMiMqh0AAAA
@ -79,4 +81,7 @@ reefer.i2p=OgiTTgA1xghkPcwgGpnyIJCBG1cZk17UKMAJ7H0YiroAvrAU4mexKPkWoy3JFmsenJuHn
ugha.i2p=318iVBG9JmuN5R~ClMb6rkWqkKoCZ3yGvD4dyIJAZum4GXFMHKSLrRiBf~2tFJ2KW1Lg9tKeyDgTC6sMt0aEQTcmzZl48BsFPZlH~WiI0JnPadihezDowBSdBhMi0RXoa3~xbfOKgAsHJv1zjrfRJHYz9fgG9bNQv9~oLeKz6YRdi97yrvKZLuzaUakYWfwdk7t9ZVhaXxsW8USXLSeHfdeQb0NYmsNc6is7Gp15HvsVUMgZXuBGea-AzMY1SSA6KdJwwPlXFvvcTM7neZKJzFVXyFsaOMEDjoUFfEC3tq277H0cqv4rAVmYp7WN78oBC7JgjfvFtXCY1r1l2-Qh1AEMwevVwI7tWdPUp50eMKEiccaHNZ7q2Zt0Uk0vlxKYW84p8ZTvXqfYDWyN8DQH2NKcy87MV1ZapDrbJSrF7cb-LvZy~nHtx~UwdKLS2gziM25JTtGiC1litArYS5KaY8rQXtL9kkSx9J66gc-05S-nbFMN-f-rEO9Fl27RvdQPAAAA
tor-www-proxy.i2p=9bZhTZvATJzpBa6UPslEwJCiDcsNhguT1mwbayD-rY0TN4Igj1PqPeVranzoO4Ity87ABVu2XJXatMzp~xHHtNiH6hF2XwJlpzx1Rr7A7SCTZwfBB1qLglwEBqYNEV5WT1faIDaArLc6o-ukhrOkIa8aJdaEpkjDkHjPtoOWt3nIIYlDxIliABrjFxPDeQZcOJyw7ftckqWfv7RKjdC8YpZrjXuojmi-TuhdRPu58tCNoH3j9laG4fUuU1RPK0YEj1HNSRHJVHDpCATtgHHfPiQvYK2HbMb0UBUfCtccmYqu6Cft5xZGHEOKvrMLXeEryV9ye-aczeNfBG2KzaF9pgQ2AN~eKBW7~UaNOQakyIaHDiY7aZ5qOCNW1CjuyJsEkjgRvqHogh6k5d3CkP82VlbEpTl5XaJFuflNJ0pHqZq2Le2T3wMkyECLbR0cX~qifE6Uw79AJnu-XEYQHFvHb0tV3XY2STDulgZu3fqZsjOVw2lZMHPHsszqlhiDhZIZAAAA
jar.i2p=xPIYObh2AirO1xoWCj7Wwc5RsGmQ3qulIAOHux9pOm9tzErjAfxv~2EazsZjyXCZ0zi3ylUjxqfj3L1pWEQBM-VM7HshHwg-PTuGWcdcUSRRFpQ7Gcp9u~~I3HSLRdHDj6ZkBNBk0jXM03tSKQEE4V1eum0tJwlOhBCNVqtt~FhyOBvo9~ypv6zW0sb6I3NppTYGq1LL4py4KrHSjb80e3Adfyhl1E2TfSHv6Uwp8qB~a2ac2IGhB2s-FK4gKSolpV-cUsn3roZRyq9jKJ2ciT4Y-PVcIDl6D8kV~LcNUbqD7vHy1yQxv1ByCCyIi3IYDacl5n5udwHO64L0M2mtZ1zWHS7K0~IxZTyk~mpO98qslbRfQqbk0ohZ-JGFhuB~cbVWlH6tSLmMJqmD-rOWnuxRfHLVtnbQstObQ9~KVIaX9KeLusgna2ZOhFjcy424BHtaVnbnLVyh-DEq~LkJGNx9Feyi6Z-aSQvThuhyE-ALiSvSw05x2G0yM9Me6MOWAAAA
sungo.i2p=S53R0ZV3gK3yA0woixx--czUOOsltTZABkFw0VyVuq-Dsx7inqAPYZKIWok3TP2vxBNM9I8LEExp6rlvJlPNvunzxYw0uZg3paN8V1vU3uRRhg4KDh~eAHxQo4cMC7Qng1jCt8Nb6acsg-NNm81fFJGvFaSqoi7Vwr-zZ9G1OuuzLI02Ald4RnvTOcsoOlh~gna0Y~IXGXJFI0KA76rKL3jgRAE~TqIIorGK2fTSPOwcjUPE9VXTq2LTpJayqMBdc6U6U8goYyt~cyX5rE-U4t49Vg0dVTkEfDWq-GVTiMSu0tbbyjgUcz3Ls038ugOJL11wO20QRvBkqIDcKo9gIX2ZFlKFhJWpYpNkRmXHAUobMx~zqjrpmbqy6dEA-HzRxlXYHBbIQwrX3qiAFSXPbnWrSMfm2kqMd57PZtxpb9pftKA-I7yhqqRFl0yukzG5Q5nN-ID7zo~6jLTChYsnj1ceEXn6~KMnZ3K3JTC7dR8PsWlGmSwgogHpU31ipyOhAAAA
lucky.i2p=Xzf7x5fOHaUDri-ZCNpg0S-CL7aojzaZ8NQ7Ax~zq60zhvz1yIQoPw38QYS7Vn7F5H3tMlrsUxoEoMd2tgV2gVCUesDMgThRNAkQiJSSsIf2dA5AKqD3FtyNtNTYVH1PjlrsXQ67VOmXoxYgiIRY1QVJj5A4Huw0X5FFzvM1QfHV8CSHI6P0lvFtHZwT~TyhH4SeqfZUL4uilvtRKJb1beayuALpXQE~9B7NUlr-Ws3w6D6g9PJ8mgePo1~iVFo7oKzOPmiWLnXBCh7IZXpSIKkQiSWXD6vA-QkCtlQClV6VPqhwIs63qX8MIE2yXsNQgOb-u9Tc~Y8vk0VjMgaGCDd9g6L8BmsxKHerELt4ZDLcBt9yuX3DWwGjwskZ8E~Fydk9ELnI6if3sOKoKv4Ov8-CwyL4WsgUJECyPelCaZxHkv~m-OygnRN~71jvGOpWDGGRvoVQmSB~JwTQkUVAbCZpXp~J1qQsR2QgK2onW8q1OZ7Q~UsVQS86DmfOwQmQAAAA
xilog.i2p=yHOthRzTaowYM0dH0H8LfHeBpNzfVnBL9TtVSPF1bAImcm0tI1jyw4dERfijVunXviLGQ0NahyOaSRvbn3pBth179n1w5KE-~m1m9EIevv~XgMjDrfkrMnrQXiubvnCiSV9f7u6Cu0fdjlwP7e4Bw3p~6Uy06zikJKdB4duwdWmqOr-7UFuoY3CZRjp6fXe1Xy~JKRxHdUoTfT1qYmEVQqTT5bfCDTt0eiUcKfFz7zMAJgkLVbV7CifasppgaCDG5L3O~87JjKlQ5TazDhCD5222WBw81cMlxlrVXbGMKoS56DZfvBr0oKebzPI2DXq52mjtVF4u5VBgDfEUx2pWQoRaF7iePx2nYg9WdRcc-r4idLPeoZgQLgoPu74iof~T-wXs5wWe9U6lyYQWmRf9VxIiOjEMBOoNjs657-~vWXDQlk6IS95bqRxYXvXZheFqY1uzmdSA1VgmrJX~yN-lZC2LXHJqi4PiSpMqVWjwyGUZINzlc3flUX-NwouSmp01AAAA

View File

@ -10,6 +10,7 @@
<ant dir="../../apps/sam/java/" target="build" />
<ant dir="../../apps/netmonitor/java/" target="build" />
<ant dir="../../apps/heartbeat/java/" target="build" />
<ant dir="../../apps/time/java/" target="build" />
</target>
<target name="compile">
<mkdir dir="./build" />
@ -39,6 +40,7 @@
<fileset file="../../apps/sam/java/build/sam.jar" />
<fileset file="../../apps/heartbeat/java/build/heartbeat.jar" />
<fileset file="../../apps/netmonitor/java/build/netmonitor.jar" />
<fileset file="../../apps/time/java/build/timestamper.jar" />
<fileset file="../doc/COPYING" />
<fileset file="../../readme.txt" />
<fileset file="../../hosts.txt" />
@ -62,6 +64,7 @@
<fileset file="../../apps/sam/java/build/sam.jar" />
<fileset file="../../apps/heartbeat/java/build/heartbeat.jar" />
<fileset file="../../apps/netmonitor/java/build/netmonitor.jar" />
<fileset file="../../apps/time/java/build/timestamper.jar" />
<fileset file="../doc/COPYING" />
<fileset file="../../readme.txt" />
<fileset file="../../hosts.txt" />
@ -83,6 +86,7 @@
<ant dir="../../apps/sam/java/" target="cleandep" />
<ant dir="../../apps/heartbeat/java" target="cleandep" />
<ant dir="../../apps/netmonitor/java" target="cleandep" />
<ant dir="../../apps/time/java" target="cleandep" />
</target>
<target name="distclean" depends="clean">
<ant dir="../../core/java/" target="distclean" />
@ -92,5 +96,6 @@
<ant dir="../../apps/sam/java/" target="distclean" />
<ant dir="../../apps/heartbeat/java" target="distclean" />
<ant dir="../../apps/netmonitor/java" target="distclean" />
<ant dir="../../apps/time/java" target="distclean" />
</target>
</project>

View File

@ -88,7 +88,7 @@ qs.0045.question=<none>
qs.0050.question=End of configuration.
libs.count=13
libs.count=14
libs.0001.name=i2p.jar
libs.0001.islib=true
libs.0002.name=i2ptunnel.jar
@ -114,4 +114,6 @@ libs.0011.name=netmonitor.jar
libs.0012.name=harvester.config
libs.0012.islib=false
libs.0013.name=heartbeat.config
libs.0013.islib=false
libs.0013.islib=false
libs.0014.name=timestamper.jar
libs.0014.islib=true

View File

@ -5,24 +5,11 @@
##_router_hn##
##_router_port##
##_router_lavalid##
# unless you really really know what you're doing, keep listenAddressIsValid=false
##_router_tcpdisable##
# maximum number of TCP connections we will want to
# attempt to establish at once (each of which
# requires a 2048bit DH exchange)
i2np.tcp.concurrentEstablishers=5
# Polling HTTP configuration, which is used to keep your router's clock in sync
# [also for communication when no inbound connections are possible, once its fixed up again]
##_router_phttpreg##
##_router_phttpsend##
# The following option specifies whether the router wants to keep the router's internal time in sync
# with the PHTTP relay's clock (which should be NTP synced). If however you are sure your local machine
# always has the correct time, you can set this to false (but your clock MUST be synced - see
# http://wiki.invisiblenet.net/iip-wiki?I2PTiming for more info.
i2np.phttp.trustRelayTime=true
# I2CP client port, for client connections
i2cp.port=##_router_i2cp_port##
@ -156,26 +143,42 @@ router.maxWaitingJobs=40
# applications it is up and running, all within the router's JVM. Uncomment the
# ones you want (revising the numbers and ports accordingly)
# Network monitor (harvests data from the network database and stores it under
# monitorData/, and with the netviewer GUI you can browse through its results)
clientApp.0.main=net.i2p.netmonitor.NetMonitor
clientApp.0.name=NetMonitor
clientApp.0.args=
# Keep the router's clock in sync by querying one of the specified NTP servers once
# a minute (uses UDP port 123)
# Please change the NTP server specified to include ones closer to you - see
# http://www.eecis.udel.edu/~mills/ntp/clock2a.html for a list (you can specify as
# many as you want on the args= line - they'll be tried in order until one answers).
# Some example servers you may want to try:
# US: dewey.lib.ci.phoenix.az.us
# US: clock.fmt.he.net
# BR: ntp1.pucpr.br
# BE: ntp2.belbone.be
# AU: ntp.saard.net
clientApp.0.main=net.i2p.time.Timestamper
clientApp.0.name=Timestamper
clientApp.0.onBoot=true
clientApp.0.args=http://localhost:7655/setTime?k=v clock.fmt.he.net ntp2.belbone.be
# SAM bridge (a simplified socket based protocol for using I2P - listens on port 7656. see
# the specs at http://www.i2p.net/node/view/144 for more info)
clientApp.1.main=net.i2p.sam.SAMBridge
clientApp.1.name=SAMBridge
clientApp.1.args=0.0.0.0 7656 i2cp.tcp.host=localhost i2cp.tcp.port=##_router_i2cp_port##
clientApp.1.args=sam.keys 0.0.0.0 7656 i2cp.tcp.host=localhost i2cp.tcp.port=##_router_i2cp_port##
# EepProxy (HTTP proxy that lets you browse both eepsites and the normal web via squid.i2p)
clientApp.2.main=net.i2p.i2ptunnel.I2PTunnel
clientApp.2.name=EepProxy
clientApp.2.args=-nogui -e "config localhost ##_router_i2cp_port##" -e "httpclient 4444"
clientApp.2.args=-nocli -e "config localhost ##_router_i2cp_port##" -e "httpclient 4444"
# Heartbeat engine (uber-simple ping/pong system, configured in heartbeat.config. By itself
# Network monitor (harvests data from the network database and stores it under
# monitorData/, and with the netviewer GUI you can browse through its results)
#clientApp.3.main=net.i2p.netmonitor.NetMonitor
#clientApp.3.name=NetMonitor
#clientApp.3.args=
# Heartbeat engine (ueber-simple ping/pong system, configured in heartbeat.config. By itself
# it just writes out stat data where its told to, but there's a seperate HeartbeatMonitor
# GUI to let you visualize things)
#clientApp.3.main=net.i2p.heartbeat.Heartbeat
#clientApp.3.name=Heartbeat
#clientApp.3.args=heartbeat.config
#clientApp.4.main=net.i2p.heartbeat.Heartbeat
#clientApp.4.name=Heartbeat
#clientApp.4.args=heartbeat.config

View File

@ -5,5 +5,5 @@ cd ##_scripts_installdir##
REM the -XX args are workarounds for bugs in java 1.4.2's garbage collector
REM replace java with javaw if you don't want a window to pop up
javaw -cp lib\i2p.jar;lib\router.jar;lib\mstreaming.jar;lib\heartbeat.jar;lib\i2ptunnel.jar;lib\netmonitor.jar;lib\sam.jar -Djava.library.path=. -DloggerFilenameOverride=logs\log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router
javaw -cp lib\i2p.jar;lib\router.jar;lib\mstreaming.jar;lib\heartbeat.jar;lib\i2ptunnel.jar;lib\netmonitor.jar;lib\sam.jar;lib\timestamper.jar -Djava.library.path=. -DloggerFilenameOverride=logs\log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router
echo Router started up, please see http://localhost:7655/

View File

@ -2,7 +2,7 @@
cd ##_scripts_installdir##
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
# the -XX args are workarounds for bugs in java 1.4.2's garbage collector
nohup nice java -cp lib/i2p.jar:lib/router.jar:lib/mstreaming.jar:lib/heartbeat.jar:lib/i2ptunnel.jar:lib/netmonitor.jar:lib/sam.jar -Djava.library.path=. -DloggerFilenameOverride=logs/log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router --quiet > /dev/null &
nohup nice java -cp lib/i2p.jar:lib/router.jar:lib/mstreaming.jar:lib/heartbeat.jar:lib/i2ptunnel.jar:lib/netmonitor.jar:lib/sam.jar:lib/timestamper.jar -Djava.library.path=. -DloggerFilenameOverride=logs/log-router-#.txt -XX:NewSize=4M -XX:MaxNewSize=8M -XX:PermSize=8M -XX:MaxPermSize=32M net.i2p.router.Router --quiet > /dev/null &
# Save the pid just in case we ever want to stop the router
echo $! > router.pid
echo I2P Router started

View File

@ -146,6 +146,8 @@ public class TunnelMessage extends I2NPMessageImpl {
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[TunnelMessage: ");
buf.append("\n\tMessageId: ").append(getUniqueId());
buf.append("\n\tExpiration: ").append(getMessageExpiration());
buf.append("\n\tTunnel ID: ").append(getTunnelId());
buf.append("\n\tVerification Structure: ").append(getVerificationStructure());
buf.append("\n\tEncrypted Instructions: ").append(getEncryptedDeliveryInstructions());

View File

@ -13,6 +13,8 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.TreeMap;
import java.util.SortedMap;
import java.util.Collections;
import net.i2p.router.message.HandleSourceRouteReplyMessageJob;
import net.i2p.router.networkdb.HandleDatabaseLookupMessageJob;
@ -43,7 +45,7 @@ public class JobQueue {
/** when true, don't run any new jobs or update any limits, etc */
private boolean _paused;
/** job name to JobStat for that job */
private TreeMap _jobStats;
private SortedMap _jobStats;
/** how many job queue runners can go concurrently */
private int _maxRunners;
private QueuePumper _pumper;
@ -116,7 +118,7 @@ public class JobQueue {
_timedJobs = new ArrayList();
_queueRunners = new HashMap();
_paused = false;
_jobStats = new TreeMap();
_jobStats = Collections.synchronizedSortedMap(new TreeMap());
_allowParallelOperation = false;
_pumper = new QueuePumper();
I2PThread pumperThread = new I2PThread(_pumper);
@ -153,9 +155,9 @@ public class JobQueue {
_context.statManager().addRateData("jobQueue.readyJobs", numReady, 0);
if (shouldDrop(job, numReady)) {
if (_log.shouldLog(Log.ERROR))
_log.error("Dropping job due to overload! # ready jobs: "
+ numReady + ": job = " + job);
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping job due to overload! # ready jobs: "
+ numReady + ": job = " + job);
job.dropped();
_context.statManager().addRateData("jobQueue.droppedJobs", 1, 1);
awaken(1);
@ -436,13 +438,15 @@ public class JobQueue {
MessageHistory hist = _context.messageHistory();
long uptime = _context.router().getUptime();
synchronized (_jobStats) {
if (!_jobStats.containsKey(key))
_jobStats.put(key, new JobStats(key));
JobStats stats = (JobStats)_jobStats.get(key);
stats.jobRan(duration, lag);
JobStats stats = null;
if (!_jobStats.containsKey(key)) {
_jobStats.put(key, new JobStats(key));
// yes, if two runners finish the same job at the same time, this could
// create an extra object. but, who cares, its pushed out of the map
// immediately anyway.
}
stats = (JobStats)_jobStats.get(key);
stats.jobRan(duration, lag);
String dieMsg = null;
@ -599,15 +603,20 @@ public class JobQueue {
ArrayList readyJobs = null;
ArrayList timedJobs = null;
ArrayList activeJobs = new ArrayList(4);
ArrayList justFinishedJobs = new ArrayList(4);
synchronized (_readyJobs) { readyJobs = new ArrayList(_readyJobs); }
synchronized (_timedJobs) { timedJobs = new ArrayList(_timedJobs); }
synchronized (_queueRunners) {
for (Iterator iter = _queueRunners.values().iterator(); iter.hasNext();) {
JobQueueRunner runner = (JobQueueRunner)iter.next();
Job job = runner.getCurrentJob();
if (job != null)
if (job != null) {
activeJobs.add(job.getName());
} else {
job = runner.getLastJob();
justFinishedJobs.add(job.getName());
}
}
}
StringBuffer buf = new StringBuffer(20*1024);
buf.append("<h2>JobQueue</h2>");
@ -621,6 +630,11 @@ public class JobQueue {
buf.append("<li>").append(activeJobs.get(i)).append("</li>\n");
}
buf.append("</ol>\n");
buf.append("# just finished jobs: ").append(justFinishedJobs.size()).append("<ol>\n");
for (int i = 0; i < justFinishedJobs.size(); i++) {
buf.append("<li>").append(justFinishedJobs.get(i)).append("</li>\n");
}
buf.append("</ol>\n");
buf.append("# ready/waiting jobs: ").append(readyJobs.size()).append(" <i>(lots of these mean there's likely a big problem)</i><ol>\n");
for (int i = 0; i < readyJobs.size(); i++) {
buf.append("<li>").append(readyJobs.get(i)).append("</li>\n");
@ -662,7 +676,7 @@ public class JobQueue {
TreeMap tstats = null;
synchronized (_jobStats) {
tstats = (TreeMap)_jobStats.clone();
tstats = new TreeMap(_jobStats);
}
for (Iterator iter = tstats.values().iterator(); iter.hasNext(); ) {

View File

@ -12,6 +12,7 @@ class JobQueueRunner implements Runnable {
private int _id;
private long _numJobs;
private Job _currentJob;
private Job _lastJob;
public JobQueueRunner(RouterContext context, int id) {
_context = context;
@ -19,6 +20,7 @@ class JobQueueRunner implements Runnable {
_keepRunning = true;
_numJobs = 0;
_currentJob = null;
_lastJob = null;
_log = _context.logManager().getLog(JobQueueRunner.class);
_context.statManager().createRateStat("jobQueue.jobRun", "How long jobs take", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("jobQueue.jobLag", "How long jobs have to wait before running", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
@ -27,6 +29,7 @@ class JobQueueRunner implements Runnable {
}
public Job getCurrentJob() { return _currentJob; }
public Job getLastJob() { return _lastJob; }
public int getRunnerId() { return _id; }
public void stopRunning() { _keepRunning = false; }
public void run() {
@ -51,6 +54,7 @@ class JobQueueRunner implements Runnable {
long betweenJobs = now - lastActive;
_context.statManager().addRateData("jobQueue.jobRunnerInactive", betweenJobs, betweenJobs);
_currentJob = job;
_lastJob = null;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Runner " + _id + " running job " + job.getJobId() + ": " + job.getName());
long origStartAfter = job.getTiming().getStartAfter();
@ -75,6 +79,7 @@ class JobQueueRunner implements Runnable {
_log.debug("Job duration " + duration + "ms for " + job.getName()
+ " with lag of " + (doStart-origStartAfter) + "ms");
lastActive = _context.clock().now();
_lastJob = _currentJob;
_currentJob = null;
} catch (Throwable t) {
if (_log.shouldLog(Log.CRIT))

View File

@ -5,71 +5,81 @@ import net.i2p.data.DataHelper;
/** glorified struct to contain basic job stats */
class JobStats {
private String _job;
private long _numRuns;
private long _totalTime;
private long _maxTime;
private long _minTime;
private long _totalPendingTime;
private long _maxPendingTime;
private long _minPendingTime;
private volatile long _numRuns;
private volatile long _totalTime;
private volatile long _maxTime;
private volatile long _minTime;
private volatile long _totalPendingTime;
private volatile long _maxPendingTime;
private volatile long _minPendingTime;
public JobStats(String name) {
_job = name;
_numRuns = 0;
_totalTime = 0;
_maxTime = -1;
_minTime = -1;
_totalPendingTime = 0;
_maxPendingTime = -1;
_minPendingTime = -1;
_job = name;
_numRuns = 0;
_totalTime = 0;
_maxTime = -1;
_minTime = -1;
_totalPendingTime = 0;
_maxPendingTime = -1;
_minPendingTime = -1;
}
public void jobRan(long runTime, long lag) {
_numRuns++;
_totalTime += runTime;
if ( (_maxTime < 0) || (runTime > _maxTime) )
_maxTime = runTime;
if ( (_minTime < 0) || (runTime < _minTime) )
_minTime = runTime;
_totalPendingTime += lag;
if ( (_maxPendingTime < 0) || (lag > _maxPendingTime) )
_maxPendingTime = lag;
if ( (_minPendingTime < 0) || (lag < _minPendingTime) )
_minPendingTime = lag;
_numRuns++;
_totalTime += runTime;
if ( (_maxTime < 0) || (runTime > _maxTime) )
_maxTime = runTime;
if ( (_minTime < 0) || (runTime < _minTime) )
_minTime = runTime;
_totalPendingTime += lag;
if ( (_maxPendingTime < 0) || (lag > _maxPendingTime) )
_maxPendingTime = lag;
if ( (_minPendingTime < 0) || (lag < _minPendingTime) )
_minPendingTime = lag;
}
public String getName() { return _job; }
public long getRuns() { return _numRuns; }
public long getTotalTime() { return _totalTime; }
public long getMaxTime() { return _maxTime; }
public long getMinTime() { return _minTime; }
public long getAvgTime() { if (_numRuns > 0) return _totalTime / _numRuns; else return 0; }
public long getAvgTime() {
if (_numRuns > 0)
return _totalTime / _numRuns;
else
return 0;
}
public long getTotalPendingTime() { return _totalPendingTime; }
public long getMaxPendingTime() { return _maxPendingTime; }
public long getMinPendingTime() { return _minPendingTime; }
public long getAvgPendingTime() { if (_numRuns > 0) return _totalPendingTime / _numRuns; else return 0; }
public long getAvgPendingTime() {
if (_numRuns > 0)
return _totalPendingTime / _numRuns;
else
return 0;
}
public int hashCode() { return _job.hashCode(); }
public boolean equals(Object obj) {
if ( (obj != null) && (obj instanceof JobStats) ) {
JobStats stats = (JobStats)obj;
return DataHelper.eq(getName(), stats.getName()) &&
getRuns() == stats.getRuns() &&
getTotalTime() == stats.getTotalTime() &&
getMaxTime() == stats.getMaxTime() &&
getMinTime() == stats.getMinTime();
} else {
return false;
}
if ( (obj != null) && (obj instanceof JobStats) ) {
JobStats stats = (JobStats)obj;
return DataHelper.eq(getName(), stats.getName()) &&
getRuns() == stats.getRuns() &&
getTotalTime() == stats.getTotalTime() &&
getMaxTime() == stats.getMaxTime() &&
getMinTime() == stats.getMinTime();
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("Over ").append(getRuns()).append(" runs, job <b>").append(getName()).append("</b> took ");
buf.append(getTotalTime()).append("ms (").append(getAvgTime()).append("ms/").append(getMaxTime()).append("ms/");
buf.append(getMinTime()).append("ms avg/max/min) after a total lag of ");
buf.append(getTotalPendingTime()).append("ms (").append(getAvgPendingTime()).append("ms/");
buf.append(getMaxPendingTime()).append("ms/").append(getMinPendingTime()).append("ms avg/max/min)");
return buf.toString();
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("Over ").append(getRuns()).append(" runs, job <b>").append(getName()).append("</b> took ");
buf.append(getTotalTime()).append("ms (").append(getAvgTime()).append("ms/").append(getMaxTime()).append("ms/");
buf.append(getMinTime()).append("ms avg/max/min) after a total lag of ");
buf.append(getTotalPendingTime()).append("ms (").append(getAvgPendingTime()).append("ms/");
buf.append(getMaxPendingTime()).append("ms/").append(getMinPendingTime()).append("ms avg/max/min)");
return buf.toString();
}
}

View File

@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
*
*/
public class RouterVersion {
public final static String ID = "$Revision: 1.2 $ $Date: 2004/04/20 04:18:53 $";
public final static String VERSION = "0.3.1";
public final static String ID = "$Revision: 1.3 $ $Date: 2004/04/30 18:04:13 $";
public final static String VERSION = "0.3.1.1";
public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION);
System.out.println("Router ID: " + RouterVersion.ID);

View File

@ -109,6 +109,8 @@ public class StatisticsManager implements Service {
includeRate("netDb.lookupsMatched", stats, new long[] { 5*60*1000, 60*60*1000 });
includeRate("netDb.storeSent", stats, new long[] { 5*60*1000, 60*60*1000 });
includeRate("netDb.successPeers", stats, new long[] { 60*60*1000 });
includeRate("netDb.failedPeers", stats, new long[] { 60*60*1000 });
includeRate("netDb.searchCount", stats, new long[] { 3*60*60*1000});
includeRate("transport.receiveMessageSize", stats, new long[] { 5*60*1000, 60*60*1000 });
includeRate("transport.sendMessageSize", stats, new long[] { 5*60*1000, 60*60*1000 });
includeRate("client.sendAckTime", stats, new long[] { 60*60*1000, 24*60*60*1000l }, true);

View File

@ -8,6 +8,9 @@ import java.io.OutputStream;
import java.net.Socket;
import java.util.Iterator;
import java.util.Set;
import java.util.Locale;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import net.i2p.data.Hash;
import net.i2p.router.Router;
@ -48,6 +51,9 @@ class AdminRunner implements Runnable {
reply(out, _generator.generateStatsPage());
} else if (command.indexOf("/profile/") >= 0) {
replyText(out, getProfile(command));
} else if (command.indexOf("setTime") >= 0) {
setTime(command);
reply(out, "<html><body>Time updated</body></html>");
} else if (true || command.indexOf("routerConsole.html") > 0) {
reply(out, _context.router().renderStatusHTML());
}
@ -105,4 +111,30 @@ class AdminRunner implements Runnable {
return "No such peer is being profiled\n";
}
private static final String FORMAT_STRING = "yyyyMMdd_HH:mm:ss.SSS";
private SimpleDateFormat _fmt = new SimpleDateFormat(FORMAT_STRING, Locale.UK);
private long getTime(String now) throws ParseException {
synchronized (_fmt) {
return _fmt.parse(now).getTime();
}
}
private void setTime(String cmd) {
int start = cmd.indexOf("now=");
String str = cmd.substring(start + 4, start+4+FORMAT_STRING.length());
try {
long now = getTime(str);
if (_log.shouldLog(Log.INFO))
_log.log(Log.INFO, "Admin time set to " + str);
setTime(now);
} catch (ParseException pe) {
_log.error("Invalid time specified [" + str + "]", pe);
}
}
private void setTime(long now) {
_context.clock().setNow(now);
}
}

View File

@ -7,6 +7,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.Properties;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.Certificate;
@ -60,6 +61,7 @@ public class OutboundClientMessageJob extends JobImpl {
private NextStepJob _nextStep;
private LookupLeaseSetFailedJob _lookupLeaseSetFailed;
private long _overallExpiration;
private boolean _shouldBundle;
/**
* final timeout (in milliseconds) that the outbound message will fail in.
@ -77,6 +79,29 @@ public class OutboundClientMessageJob extends JobImpl {
/** dont search for the lease more than 6 times */
private final static int MAX_LEASE_LOOKUPS = 6;
/**
* If the client's config specifies shouldBundleReplyInfo=true, messages sent from
* that client to any peers will probabalistically include the sending destination's
* current LeaseSet (allowing the recipient to reply without having to do a full
* netDb lookup). This should improve performance during the initial negotiations,
* but is not necessary for communication that isn't bidirectional.
*
*/
public static final String BUNDLE_REPLY_LEASESET = "shouldBundleReplyInfo";
/**
* Allow the override of the frequency of bundling the reply info in with a message.
* The client app can specify bundleReplyInfoProbability=80 (for instance) and that
* will cause the router to include the sender's leaseSet with 80% of the messages
* sent to the peer.
*
*/
public static final String BUNDLE_PROBABILITY = "bundleReplyInfoProbability";
/**
* How often do messages include the reply leaseSet (out of every 100 tries).
* Including it each time is probably overkill, but who knows.
*/
private static final int BUNDLE_PROBABILITY_DEFAULT = 80;
/**
* Send the sucker
*/
@ -109,20 +134,21 @@ public class OutboundClientMessageJob extends JobImpl {
_status = new OutboundClientMessageStatus(msg);
_nextStep = new NextStepJob();
_lookupLeaseSetFailed = new LookupLeaseSetFailedJob();
_shouldBundle = getShouldBundle();
}
public String getName() { return "Outbound client message"; }
public void runJob() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send outbound client message job beginning");
_log.debug(getJobId() + ": Send outbound client message job beginning");
buildClove();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Clove built");
_log.debug(getJobId() + ": Clove built");
Hash to = _status.getTo().calculateHash();
long timeoutMs = _overallExpiration - _context.clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send outbound client message - sending off leaseSet lookup job");
_log.debug(getJobId() + ": Send outbound client message - sending off leaseSet lookup job");
_status.incrementLookups();
_context.netDb().lookupLeaseSet(to, _nextStep, _lookupLeaseSetFailed, timeoutMs);
}
@ -132,24 +158,24 @@ public class OutboundClientMessageJob extends JobImpl {
*/
private void sendNext() {
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("sendNext() called with " + _status.getNumSent() + " already sent");
_log.debug(getJobId() + ": sendNext() called with " + _status.getNumSent() + " already sent");
}
if (_status.getSuccess()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("sendNext() - already successful!");
_log.debug(getJobId() + ": sendNext() - already successful!");
return;
}
if (_status.getFailure()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("sendNext() - already failed!");
_log.debug(getJobId() + ": sendNext() - already failed!");
return;
}
long now = _context.clock().now();
if (now >= _overallExpiration) {
if (_log.shouldLog(Log.WARN))
_log.warn("sendNext() - Expired");
_log.warn(getJobId() + ": sendNext() - Expired");
dieFatal();
return;
}
@ -157,26 +183,26 @@ public class OutboundClientMessageJob extends JobImpl {
Lease nextLease = getNextLease();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send outbound client message - next lease found for ["
_log.debug(getJobId() + ": Send outbound client message - next lease found for ["
+ _status.getTo().calculateHash().toBase64() + "] - "
+ nextLease);
if (nextLease == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("No more leases, and we still haven't heard back from the peer"
_log.warn(getJobId() + ": No more leases, and we still haven't heard back from the peer"
+ ", refetching the leaseSet to try again");
_status.setLeaseSet(null);
long remainingMs = _overallExpiration - _context.clock().now();
if (_status.getNumLookups() < MAX_LEASE_LOOKUPS) {
_status.incrementLookups();
Hash to = _status.getMessage().getDestination().calculateHash();
_status.clearAlreadySent();
_context.netDb().fail(to);
_status.clearAlreadySent(); // so we can send down old tunnels again
_context.netDb().fail(to); // so we don't just fetch what we have
_context.netDb().lookupLeaseSet(to, _nextStep, _lookupLeaseSetFailed, remainingMs);
return;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("sendNext() - max # lease lookups exceeded! "
_log.warn(getJobId() + ": sendNext() - max # lease lookups exceeded! "
+ _status.getNumLookups());
dieFatal();
return;
@ -199,12 +225,12 @@ public class OutboundClientMessageJob extends JobImpl {
if (ls == null) {
ls = _context.netDb().lookupLeaseSetLocally(_status.getTo().calculateHash());
if (ls == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Lookup locally didn't find the leaseSet");
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Lookup locally didn't find the leaseSet");
return null;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Lookup locally DID find the leaseSet");
_log.debug(getJobId() + ": Lookup locally DID find the leaseSet");
}
_status.setLeaseSet(ls);
}
@ -216,7 +242,7 @@ public class OutboundClientMessageJob extends JobImpl {
Lease lease = ls.getLease(i);
if (lease.isExpired(Router.CLOCK_FUDGE_FACTOR)) {
if (_log.shouldLog(Log.WARN))
_log.warn("getNextLease() - expired lease! - " + lease);
_log.warn(getJobId() + ": getNextLease() - expired lease! - " + lease);
continue;
}
@ -224,12 +250,19 @@ public class OutboundClientMessageJob extends JobImpl {
leases.add(lease);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("getNextLease() - skipping lease we've already sent it down - "
_log.debug(getJobId() + ": getNextLease() - skipping lease we've already sent it down - "
+ lease);
}
}
// randomize the ordering (so leases with equal # of failures per next sort are randomly ordered)
if (leases.size() <= 0) {
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": No leases found, since we've tried them all (so fail it and relookup)");
return null;
}
// randomize the ordering (so leases with equal # of failures per next
// sort are randomly ordered)
Collections.shuffle(leases);
// ordered by lease number of failures
@ -241,15 +274,32 @@ public class OutboundClientMessageJob extends JobImpl {
id++;
orderedLeases.put(new Long(id), lease);
if (_log.shouldLog(Log.DEBUG))
_log.debug("getNextLease() - ranking lease we havent sent it down as " + id);
_log.debug(getJobId() + ": ranking lease we havent sent it down as " + id);
}
if (orderedLeases.size() <= 0) {
if (_log.shouldLog(Log.WARN))
_log.warn("No leases in the ordered set found! all = " + leases.size());
return null;
return (Lease)orderedLeases.get(orderedLeases.firstKey());
}
private boolean getShouldBundle() {
Properties opts = _status.getMessage().getSenderConfig().getOptions();
String wantBundle = opts.getProperty(BUNDLE_REPLY_LEASESET, "true");
if ("true".equals(wantBundle)) {
int probability = BUNDLE_PROBABILITY_DEFAULT;
String str = opts.getProperty(BUNDLE_PROBABILITY);
try {
if (str != null)
probability = Integer.parseInt(str);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN))
_log.warn(getJobId() + ": Bundle leaseSet probability overridden incorrectly ["
+ str + "]", nfe);
}
if (probability >= _context.random().nextInt(100))
return true;
else
return false;
} else {
return (Lease)orderedLeases.get(orderedLeases.firstKey());
return false;
}
}
@ -262,19 +312,23 @@ public class OutboundClientMessageJob extends JobImpl {
*
*/
private void send(Lease lease) {
// send it as a garlic with a DeliveryStatusMessage clove and a message selector w/ successJob on reply
long token = _context.random().nextInt(Integer.MAX_VALUE);
PublicKey key = _status.getLeaseSet().getEncryptionKey();
SessionKey sessKey = new SessionKey();
Set tags = new HashSet();
LeaseSet replyLeaseSet = null;
if (_shouldBundle) {
replyLeaseSet = _context.netDb().lookupLeaseSetLocally(_status.getFrom().calculateHash());
}
GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(_context, token,
_overallExpiration, key,
_status.getClove(),
_status.getTo(), sessKey,
tags, true);
tags, true, replyLeaseSet);
if (_log.shouldLog(Log.DEBUG))
_log.debug("send(lease) - token expected " + token);
_log.debug(getJobId() + ": send(lease) - token expected " + token);
_status.sent(lease.getRouterIdentity().getHash(), lease.getTunnelId());
@ -283,14 +337,14 @@ public class OutboundClientMessageJob extends JobImpl {
ReplySelector selector = new ReplySelector(token);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Placing GarlicMessage into the new tunnel message bound for "
_log.debug(getJobId() + ": Placing GarlicMessage into the new tunnel message bound for "
+ lease.getTunnelId() + " on "
+ lease.getRouterIdentity().getHash().toBase64());
TunnelId outTunnelId = selectOutboundTunnel();
if (outTunnelId != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending tunnel message out " + outTunnelId + " to "
_log.debug(getJobId() + ": Sending tunnel message out " + outTunnelId + " to "
+ lease.getTunnelId() + " on "
+ lease.getRouterIdentity().getHash().toBase64());
SendTunnelMessageJob j = new SendTunnelMessageJob(_context, msg, outTunnelId,
@ -301,8 +355,8 @@ public class OutboundClientMessageJob extends JobImpl {
_context.jobQueue().addJob(j);
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Could not find any outbound tunnels to send the payload through... wtf?");
_context.jobQueue().addJob(onFail);
_log.error(getJobId() + ": Could not find any outbound tunnels to send the payload through... wtf?");
dieFatal();
}
}
@ -334,12 +388,12 @@ public class OutboundClientMessageJob extends JobImpl {
ClientMessage msg = _status.getMessage();
if (alreadyFailed) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("dieFatal() - already failed sending " + msg.getMessageId()
_log.debug(getJobId() + ": dieFatal() - already failed sending " + msg.getMessageId()
+ ", no need to do it again", new Exception("Duplicate death?"));
return;
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Failed to send the message " + msg.getMessageId() + " after "
_log.error(getJobId() + ": Failed to send the message " + msg.getMessageId() + " after "
+ _status.getNumSent() + " sends and " + _status.getNumLookups()
+ " lookups (and " + sendTime + "ms)",
new Exception("Message send failure"));
@ -378,7 +432,7 @@ public class OutboundClientMessageJob extends JobImpl {
_status.setClove(clove);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Built payload clove with id " + clove.getId());
_log.debug(getJobId() + ": Built payload clove with id " + clove.getId());
}
/**
@ -587,7 +641,8 @@ public class OutboundClientMessageJob extends JobImpl {
boolean alreadySuccessful = _status.success();
MessageId msgId = _status.getMessage().getMessageId();
if (_log.shouldLog(Log.DEBUG))
_log.debug("SUCCESS! Message delivered completely for message " + msgId
_log.debug(OutboundClientMessageJob.this.getJobId()
+ ": SUCCESS! Message delivered completely for message " + msgId
+ " after " + sendTime + "ms [for "
+ _status.getMessage().getMessageId() + "]");
@ -598,7 +653,8 @@ public class OutboundClientMessageJob extends JobImpl {
if (alreadySuccessful) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Success is a duplicate for " + _status.getMessage().getMessageId()
_log.debug(OutboundClientMessageJob.this.getJobId()
+ ": Success is a duplicate for " + _status.getMessage().getMessageId()
+ ", dont notify again...");
return;
}
@ -631,7 +687,8 @@ public class OutboundClientMessageJob extends JobImpl {
public String getName() { return "Send client message timed out through a lease"; }
public void runJob() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Soft timeout through the lease " + _lease);
_log.debug(OutboundClientMessageJob.this.getJobId()
+ ": Soft timeout through the lease " + _lease);
_lease.setNumFailure(_lease.getNumFailure()+1);
sendNext();
}

View File

@ -19,9 +19,11 @@ import net.i2p.data.Payload;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.TunnelId;
import net.i2p.data.LeaseSet;
import net.i2p.data.i2np.DataMessage;
import net.i2p.data.i2np.DeliveryInstructions;
import net.i2p.data.i2np.DeliveryStatusMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.GarlicMessage;
import net.i2p.router.Router;
import net.i2p.router.TunnelInfo;
@ -50,23 +52,32 @@ class OutboundClientMessageJobHelper {
*
* For now, its just a tunneled DeliveryStatusMessage
*
* @param bundledReplyLeaseSet if specified, the given LeaseSet will be packaged with the message (allowing
* much faster replies, since their netDb search will return almost instantly)
*/
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK, Payload data, Destination dest, SessionKey wrappedKey, Set wrappedTags, boolean requireAck) {
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
Payload data, Destination dest, SessionKey wrappedKey, Set wrappedTags,
boolean requireAck, LeaseSet bundledReplyLeaseSet) {
PayloadGarlicConfig dataClove = buildDataClove(ctx, data, dest, expiration);
return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, dest, wrappedKey, wrappedTags, requireAck);
return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, dest, wrappedKey,
wrappedTags, requireAck, bundledReplyLeaseSet);
}
/**
* Allow the app to specify the data clove directly, which enables OutboundClientMessage to resend the
* same payload (including expiration and unique id) in different garlics (down different tunnels)
*
*/
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK, PayloadGarlicConfig dataClove, Destination dest, SessionKey wrappedKey, Set wrappedTags, boolean requireAck) {
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, dest, requireAck);
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
PayloadGarlicConfig dataClove, Destination dest, SessionKey wrappedKey,
Set wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet) {
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, dest, requireAck, bundledReplyLeaseSet);
GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, config, wrappedKey, wrappedTags);
return msg;
}
private static GarlicConfig createGarlicConfig(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK, PayloadGarlicConfig dataClove, Destination dest, boolean requireAck) {
private static GarlicConfig createGarlicConfig(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
PayloadGarlicConfig dataClove, Destination dest, boolean requireAck,
LeaseSet bundledReplyLeaseSet) {
Log log = ctx.logManager().getLog(OutboundClientMessageJobHelper.class);
log.debug("Reply token: " + replyToken);
GarlicConfig config = new GarlicConfig();
@ -78,6 +89,11 @@ class OutboundClientMessageJobHelper {
config.addClove(ackClove);
}
if (bundledReplyLeaseSet != null) {
PayloadGarlicConfig leaseSetClove = buildLeaseSetClove(ctx, expiration, bundledReplyLeaseSet);
config.addClove(leaseSetClove);
}
DeliveryInstructions instructions = new DeliveryInstructions();
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL);
instructions.setDelayRequested(false);
@ -177,4 +193,32 @@ class OutboundClientMessageJobHelper {
return clove;
}
/**
* Build a clove that stores the leaseSet locally
*/
static PayloadGarlicConfig buildLeaseSetClove(RouterContext ctx, long expiration, LeaseSet replyLeaseSet) {
PayloadGarlicConfig clove = new PayloadGarlicConfig();
DeliveryInstructions instructions = new DeliveryInstructions();
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL);
instructions.setDelayRequested(false);
instructions.setDelaySeconds(0);
instructions.setEncrypted(false);
clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
clove.setDeliveryInstructions(instructions);
clove.setExpiration(expiration);
clove.setId(ctx.random().nextInt(Integer.MAX_VALUE));
DatabaseStoreMessage msg = new DatabaseStoreMessage(ctx);
msg.setLeaseSet(replyLeaseSet);
msg.setMessageExpiration(new Date(expiration));
msg.setKey(replyLeaseSet.getDestination().calculateHash());
clove.setPayload(msg);
clove.setRecipientPublicKey(null);
clove.setRequestAck(false);
return clove;
}
}

View File

@ -401,7 +401,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
// Iterate through the old failure / success count, copying over the old
// values (if any tunnels overlap between leaseSets). no need to be
// uberthreadsafe fascists here, since these values are just heuristics
// ueberthreadsafe fascists here, since these values are just heuristics
if (rv != null) {
for (int i = 0; i < rv.getLeaseCount(); i++) {
Lease old = rv.getLease(i);

View File

@ -80,7 +80,8 @@ class SearchJob extends JobImpl {
_context.statManager().createRateStat("netDb.successTime", "How long a successful search takes", "Network Database", new long[] { 60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("netDb.failedTime", "How long a failed search takes", "Network Database", new long[] { 60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("netDb.successPeers", "How many peers are contacted in a successful search", "Network Database", new long[] { 60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("netDb.failedPeers", "How many peers are contacted in a failed search", "Network Database", new long[] { 60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("netDb.failedPeers", "How many peers fail to respond to a lookup?", "Network Database", new long[] { 60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("netDb.searchCount", "Overall number of searches sent", "Network Database", new long[] { 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
if (_log.shouldLog(Log.DEBUG))
_log.debug("Search (" + getClass().getName() + " for " + key.toBase64(), new Exception("Search enqueued by"));
}
@ -88,6 +89,7 @@ class SearchJob extends JobImpl {
public void runJob() {
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Searching for " + _state.getTarget()); // , getAddedBy());
_context.statManager().addRateData("netDb.searchCount", 1, 0);
searchNext();
}
@ -474,6 +476,7 @@ class SearchJob extends JobImpl {
if (_log.shouldLog(Log.ERROR))
_log.error("NOT (!!) Penalizing peer for timeout on search: " + _peer.toBase64());
}
_context.statManager().addRateData("netDb.failedPeers", 1, 0);
searchNext();
}
public String getName() { return "Kademlia Search Failed"; }
@ -509,7 +512,6 @@ class SearchJob extends JobImpl {
if (_keepStats) {
long time = _context.clock().now() - _state.getWhenStarted();
_context.statManager().addRateData("netDb.failedTime", time, 0);
_context.statManager().addRateData("netDb.failedPeers", _state.getAttempted().size(), time);
}
if (_onFailure != null)
_context.jobQueue().addJob(_onFailure);

View File

@ -207,25 +207,25 @@ class PeerProfile {
public void expandProfile() {
if (_sendSuccessSize == null)
_sendSuccessSize = new RateStat("sendSuccessSize", "How large successfully sent messages are", "profile", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
if (_sendFailureSize == null)
_sendFailureSize = new RateStat("sendFailureSize", "How large messages that could not be sent were", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_receiveSize == null)
_receiveSize = new RateStat("receiveSize", "How large received messages are", "profile", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_dbResponseTime == null)
_dbResponseTime = new RateStat("dbResponseTime", "how long it takes to get a db response from the peer (in milliseconds)", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_tunnelCreateResponseTime == null)
_tunnelCreateResponseTime = new RateStat("tunnelCreateResponseTime", "how long it takes to get a tunnel create response from the peer (in milliseconds)", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_commError == null)
_commError = new RateStat("commErrorRate", "how long between communication errors with the peer (e.g. disconnection)", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_dbIntroduction == null)
_dbIntroduction = new RateStat("dbIntroduction", "how many new peers we get from dbSearchReplyMessages or dbStore messages", "profile", new long[] { 60*60*1000l, 24*60*60*1000l, 7*24*60*60*1000l });
if (_tunnelHistory == null)
_tunnelHistory = new TunnelHistory(_context);
if (_dbHistory == null)
_dbHistory = new DBHistory(_context);
_expanded = true;
if (_sendFailureSize == null)
_sendFailureSize = new RateStat("sendFailureSize", "How large messages that could not be sent were", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_receiveSize == null)
_receiveSize = new RateStat("receiveSize", "How large received messages are", "profile", new long[] { 60*1000l, 5*60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_dbResponseTime == null)
_dbResponseTime = new RateStat("dbResponseTime", "how long it takes to get a db response from the peer (in milliseconds)", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_tunnelCreateResponseTime == null)
_tunnelCreateResponseTime = new RateStat("tunnelCreateResponseTime", "how long it takes to get a tunnel create response from the peer (in milliseconds)", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_commError == null)
_commError = new RateStat("commErrorRate", "how long between communication errors with the peer (e.g. disconnection)", "profile", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_dbIntroduction == null)
_dbIntroduction = new RateStat("dbIntroduction", "how many new peers we get from dbSearchReplyMessages or dbStore messages", "profile", new long[] { 60*60*1000l, 24*60*60*1000l, 7*24*60*60*1000l });
if (_tunnelHistory == null)
_tunnelHistory = new TunnelHistory(_context);
if (_dbHistory == null)
_dbHistory = new DBHistory(_context);
_expanded = true;
}
/** update the stats and rates (this should be called once a minute) */

View File

@ -0,0 +1,168 @@
package net.i2p.router.startup;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
import java.lang.reflect.Method;
import java.util.List;
import java.util.ArrayList;
/**
* Run any client applications specified in the router.config. If any clientApp
* contains the config property ".onBoot=true" it'll be launched immediately, otherwise
* it'll get queued up for starting 2 minutes later.
*
*/
class LoadClientAppsJob extends JobImpl {
private Log _log;
/** wait 2 minutes before starting up client apps */
private final static long STARTUP_DELAY = 2*60*1000;
public LoadClientAppsJob(RouterContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(LoadClientAppsJob.class);
}
public void runJob() {
int i = 0;
while (true) {
String className = _context.router().getConfigSetting("clientApp."+i+".main");
String clientName = _context.router().getConfigSetting("clientApp."+i+".name");
String args = _context.router().getConfigSetting("clientApp."+i+".args");
String onBoot = _context.router().getConfigSetting("clientApp." + i + ".onBoot");
boolean onStartup = false;
if (onBoot != null)
onStartup = "true".equals(onBoot) || "yes".equals(onBoot);
if (className == null)
break;
String argVal[] = parseArgs(args);
if (onStartup) {
// run this guy now
runClient(className, clientName, argVal);
} else {
// wait 2 minutes
_context.jobQueue().addJob(new DelayedRunClient(className, clientName, argVal));
}
i++;
}
}
private class DelayedRunClient extends JobImpl {
private String _className;
private String _clientName;
private String _args[];
public DelayedRunClient(String className, String clientName, String args[]) {
super(LoadClientAppsJob.this._context);
_className = className;
_clientName = clientName;
_args = args;
getTiming().setStartAfter(LoadClientAppsJob.this._context.clock().now() + STARTUP_DELAY);
}
public String getName() { return "Delayed client job"; }
public void runJob() {
runClient(_className, _clientName, _args);
}
}
static String[] parseArgs(String args) {
List argList = new ArrayList(4);
if (args != null) {
char data[] = args.toCharArray();
StringBuffer buf = new StringBuffer(32);
boolean isQuoted = false;
for (int i = 0; i < data.length; i++) {
switch (data[i]) {
case '\'':
case '\"':
if (isQuoted) {
String str = buf.toString().trim();
if (str.length() > 0)
argList.add(str);
buf = new StringBuffer(32);
} else {
isQuoted = true;
}
break;
case ' ':
case '\t':
// whitespace - if we're in a quoted section, keep this as part of the quote,
// otherwise use it as a delim
if (isQuoted) {
buf.append(data[i]);
} else {
String str = buf.toString().trim();
if (str.length() > 0)
argList.add(str);
buf = new StringBuffer(32);
}
break;
default:
buf.append(data[i]);
break;
}
}
if (buf.length() > 0) {
String str = buf.toString().trim();
if (str.length() > 0)
argList.add(str);
}
}
String rv[] = new String[argList.size()];
for (int i = 0; i < argList.size(); i++)
rv[i] = (String)argList.get(i);
return rv;
}
private void runClient(String className, String clientName, String args[]) {
_log.info("Loading up the client application " + clientName + ": " + className + " " + args);
I2PThread t = new I2PThread(new RunApp(className, clientName, args));
t.setName(clientName);
t.setDaemon(true);
t.start();
}
private final class RunApp implements Runnable {
private String _className;
private String _appName;
private String _args[];
public RunApp(String className, String appName, String args[]) {
_className = className;
_appName = appName;
if (args == null)
_args = new String[0];
else
_args = args;
}
public void run() {
try {
Class cls = Class.forName(_className);
Method method = cls.getMethod("main", new Class[] { String[].class });
method.invoke(cls, new Object[] { _args });
} catch (Throwable t) {
_log.log(Log.CRIT, "Error starting up the client class " + _className, t);
}
_log.info("Done running client application " + _appName);
}
}
public String getName() { return "Load up any client applications"; }
public static void main(String args[]) {
test(null);
test("hi how are you?");
test("hi how are you? ");
test(" hi how are you? ");
test(" hi how are \"y\"ou? ");
test("-nogui -e \"config localhost 17654\" -e \"httpclient 4544\"");
test("-nogui -e 'config localhost 17654' -e 'httpclient 4544'");
}
private static void test(String args) {
String parsed[] = parseArgs(args);
System.out.print("Parsed [" + args + "] into " + parsed.length + " elements: ");
for (int i = 0; i < parsed.length; i++)
System.out.print("[" + parsed[i] + "] ");
System.out.println();
}
}

View File

@ -16,7 +16,6 @@ import net.i2p.router.ClientManagerFacade;
import net.i2p.router.JobImpl;
import net.i2p.router.JobQueue;
import net.i2p.router.Router;
import net.i2p.router.admin.AdminManager;
import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
@ -39,132 +38,6 @@ public class StartAcceptingClientsJob extends JobImpl {
_context.jobQueue().addJob(new ReadConfigJob(_context));
_context.jobQueue().addJob(new RebuildRouterInfoJob(_context));
new AdminManager(_context).startup();
_context.jobQueue().allowParallelOperation();
_context.jobQueue().addJob(new LoadClientAppsJob(_context));
}
public static void main(String args[]) {
test(null);
test("hi how are you?");
test("hi how are you? ");
test(" hi how are you? ");
test(" hi how are \"y\"ou? ");
test("-nogui -e \"config localhost 17654\" -e \"httpclient 4544\"");
test("-nogui -e 'config localhost 17654' -e 'httpclient 4544'");
}
private static void test(String args) {
String parsed[] = LoadClientAppsJob.parseArgs(args);
System.out.print("Parsed [" + args + "] into " + parsed.length + " elements: ");
for (int i = 0; i < parsed.length; i++)
System.out.print("[" + parsed[i] + "] ");
System.out.println();
}
}
class LoadClientAppsJob extends JobImpl {
private Log _log;
/** wait 2 minutes before starting up client apps */
private final static long STARTUP_DELAY = 2*60*1000;
public LoadClientAppsJob(RouterContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(LoadClientAppsJob.class);
getTiming().setStartAfter(STARTUP_DELAY + _context.clock().now());
}
public void runJob() {
int i = 0;
while (true) {
String className = _context.router().getConfigSetting("clientApp."+i+".main");
String clientName = _context.router().getConfigSetting("clientApp."+i+".name");
String args = _context.router().getConfigSetting("clientApp."+i+".args");
if (className == null) break;
String argVal[] = parseArgs(args);
_log.info("Loading up the client application " + clientName + ": " + className + " " + args);
runClient(className, clientName, argVal);
i++;
}
}
static String[] parseArgs(String args) {
List argList = new ArrayList(4);
if (args != null) {
char data[] = args.toCharArray();
StringBuffer buf = new StringBuffer(32);
boolean isQuoted = false;
for (int i = 0; i < data.length; i++) {
switch (data[i]) {
case '\'':
case '\"':
if (isQuoted) {
String str = buf.toString().trim();
if (str.length() > 0)
argList.add(str);
buf = new StringBuffer(32);
} else {
isQuoted = true;
}
break;
case ' ':
case '\t':
// whitespace - if we're in a quoted section, keep this as part of the quote,
// otherwise use it as a delim
if (isQuoted) {
buf.append(data[i]);
} else {
String str = buf.toString().trim();
if (str.length() > 0)
argList.add(str);
buf = new StringBuffer(32);
}
break;
default:
buf.append(data[i]);
break;
}
}
if (buf.length() > 0) {
String str = buf.toString().trim();
if (str.length() > 0)
argList.add(str);
}
}
String rv[] = new String[argList.size()];
for (int i = 0; i < argList.size(); i++)
rv[i] = (String)argList.get(i);
return rv;
}
private void runClient(String className, String clientName, String args[]) {
I2PThread t = new I2PThread(new RunApp(className, clientName, args));
t.setName(clientName);
t.setDaemon(true);
t.start();
}
private final class RunApp implements Runnable {
private String _className;
private String _appName;
private String _args[];
public RunApp(String className, String appName, String args[]) {
_className = className;
_appName = appName;
if (args == null)
_args = new String[0];
else
_args = args;
}
public void run() {
try {
Class cls = Class.forName(_className);
Method method = cls.getMethod("main", new Class[] { String[].class });
method.invoke(cls, new Object[] { _args });
} catch (Throwable t) {
_log.log(Log.CRIT, "Error starting up the client class " + _className, t);
}
_log.info("Done running client application " + _appName);
}
}
public String getName() { return "Load up any client applications"; }
}

View File

@ -12,6 +12,7 @@ package net.i2p.router.startup;
import net.i2p.router.JobImpl;
import net.i2p.router.JobQueue;
import net.i2p.router.StatisticsManager;
import net.i2p.router.admin.AdminManager;
import net.i2p.util.Log;
import net.i2p.router.RouterContext;
@ -35,8 +36,10 @@ public class StartupJob extends JobImpl {
}
public String getName() { return "Startup Router"; }
public void runJob() {
public void runJob() {
ReadConfigJob.doRead(_context);
new AdminManager(_context).startup();
_context.jobQueue().addJob(new LoadClientAppsJob(_context));
_context.statPublisher().startup();
_context.jobQueue().addJob(new LoadRouterInfoJob(_context));
}

View File

@ -62,7 +62,9 @@ public class TrivialBandwidthLimiter extends BandwidthLimiter {
_context.jobQueue().addJob(new UpdateBWJob());
updateLimits();
_log.info("Initializing the limiter with maximum inbound [" + MAX_IN_POOL + "] outbound [" + MAX_OUT_POOL + "]");
if (_log.shouldLog(Log.INFO))
_log.info("Initializing the limiter with maximum inbound [" + MAX_IN_POOL
+ "] outbound [" + MAX_OUT_POOL + "]");
}
public long getTotalSendBytes() { return _totalSendBytes; }
@ -80,7 +82,9 @@ public class TrivialBandwidthLimiter extends BandwidthLimiter {
// we don't have sufficient bytes.
// the delay = (needed/numPerMinute)
long val = MINUTE*(numBytes-_availableReceive)/_maxReceiveBytesPerMinute;
_log.debug("DelayInbound: " + val + " for " + numBytes + " (avail=" + _availableReceive + ", max=" + _maxReceiveBytesPerMinute + ")");
if (_log.shouldLog(Log.DEBUG))
_log.debug("DelayInbound: " + val + " for " + numBytes + " (avail="
+ _availableReceive + ", max=" + _maxReceiveBytesPerMinute + ")");
return val;
}
}
@ -97,7 +101,9 @@ public class TrivialBandwidthLimiter extends BandwidthLimiter {
// we don't have sufficient bytes.
// the delay = (needed/numPerMinute)
long val = MINUTE*(numBytes-_availableSend)/_maxSendBytesPerMinute;
_log.debug("DelayOutbound: " + val + " for " + numBytes + " (avail=" + _availableSend + ", max=" + _maxSendBytesPerMinute + ")");
if (_log.shouldLog(Log.DEBUG))
_log.debug("DelayOutbound: " + val + " for " + numBytes + " (avail="
+ _availableSend + ", max=" + _maxSendBytesPerMinute + ")");
return val;
}
}
@ -129,7 +135,8 @@ public class TrivialBandwidthLimiter extends BandwidthLimiter {
long oldReceive = _maxReceiveBytesPerMinute;
long oldSend = _maxSendBytesPerMinute;
_log.debug("Read limits ["+inBwStr+" in, " + outBwStr + " out] vs current [" + oldReceive + " in, " + oldSend + " out]");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read limits ["+inBwStr+" in, " + outBwStr + " out] vs current [" + oldReceive + " in, " + oldSend + " out]");
if ( (inBwStr != null) && (inBwStr.trim().length() > 0) ) {
try {
@ -179,16 +186,20 @@ public class TrivialBandwidthLimiter extends BandwidthLimiter {
_availableSend += numMinutes * _maxSendBytesPerMinute;
_lastResync = now;
_log.debug("Adding " + (numMinutes*_maxReceiveBytesPerMinute) + " bytes to availableReceive");
_log.debug("Adding " + (numMinutes*_maxSendBytesPerMinute) + " bytes to availableSend");
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Adding " + (numMinutes*_maxReceiveBytesPerMinute) + " bytes to availableReceive");
_log.debug("Adding " + (numMinutes*_maxSendBytesPerMinute) + " bytes to availableSend");
}
// if we're huge, trim
if (_availableReceive > MAX_IN_POOL) {
_log.debug("Trimming available receive to " + MAX_IN_POOL);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Trimming available receive to " + MAX_IN_POOL);
_availableReceive = MAX_IN_POOL;
}
if (_availableSend > MAX_OUT_POOL) {
_log.debug("Trimming available send to " + MAX_OUT_POOL);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Trimming available send to " + MAX_OUT_POOL);
_availableSend = MAX_OUT_POOL;
}

View File

@ -85,7 +85,7 @@ public class VMCommSystem extends CommSystemFacade {
_ctx = us;
_from = from;
_msg = msg;
// bah, uberspeed!
// bah, ueberspeed!
//getTiming().setStartAfter(us.clock().now() + 50);
}
public void runJob() {
@ -123,4 +123,4 @@ public class VMCommSystem extends CommSystemFacade {
public void startup() {
_commSystemFacades.put(_context.routerHash(), this);
}
}
}

View File

@ -538,17 +538,19 @@ public class TCPTransport extends TransportImpl {
buf.append("<li>");
RouterIdentity ident = (RouterIdentity)iter.next();
List curCons = (List)cons.get(ident);
buf.append("Connections to ").append(ident.getHash().toBase64()).append(": ").append(curCons.size()).append("<br/><ul>\n");
buf.append("Connection to ").append(ident.getHash().toBase64()).append(": ");
String lifetime = null;
for (int i = 0; i < curCons.size(); i++) {
TCPConnection con = (TCPConnection)curCons.get(i);
if (con.getLifetime() > 0) {
established++;
buf.append("<li>Connection ").append(con.getId()).append(": pending # messages to be sent: ").append(con.getPendingMessageCount()).append(" lifetime: ").append(DataHelper.formatDuration(con.getLifetime())).append("</li>\n");
} else {
buf.append("<li>Connection ").append(con.getId()).append(": [connection in progress]</li>\n");
lifetime = DataHelper.formatDuration(con.getLifetime());
}
}
buf.append("</ul>\n");
if (lifetime != null)
buf.append(lifetime);
else
buf.append("[pending]");
buf.append("</li>\n");
}
buf.append("</ul>\n");