Compare commits

..

81 Commits

Author SHA1 Message Date
jrandom
97e8ab7c5b * 2004-12-01 0.4.2.2 released
2004-12-01  jrandom
    * Fixed a stupid typo that inadvertantly allowed persistent HTTP
      connections to work (thanks duck!)
    * Make sure we override the inactivity timeout too
2004-12-02 00:35:17 +00:00
jrandom
cb930a7ab5 * 2004-12-01 0.4.2.2 released
2004-12-01  jrandom
    * Fixed a stupid typo that inadvertantly allowed persistent HTTP
      connections to work (thanks duck!)
    * Make sure we override the inactivity timeout too
2004-12-02 00:27:27 +00:00
jrandom
610f1f7dd4 * 2004-12-01 0.4.2.1 released
2004-12-01  jrandom
    * Strip out any of the Accept-* HTTP header lines, and always make sure to
      include the forged User-agent header.
    * Adjust the default read timeout on the eepproxy to 60s, unless
      overridden.
    * Minor tweak on stream shutdown.
2004-12-01 22:31:55 +00:00
jrandom
516d0b4db8 2004-11-30 jrandom
* Render the burst rate fields on /config.jsp properly (thanks ugha!)
    * Build in a simple timeout to flush data queued into the I2PSocket but
      not yet flushed.
    * Don't explicitly flush after each SAM stream write, but leave it up to
      the [nonblocking] passive flush.
    * Don't whine about 10-99 connection events occurring in a second
    * Don't wait for completion of packets that will not be ACKed (duh)
    * Adjust the congestion window, even if the packet was resent (duh)
    * Make sure to wake up any blocking read()'s when the MessageInputStream
      is close()ed (duh)
    * Never wait more than the disconnect timeout for a write to complete
2004-11-30 23:41:51 +00:00
jrandom
df61ae5c6f duh. thanks clayboy :) 2004-11-30 00:10:20 +00:00
jrandom
9f6584b55e 2004-11-29 jrandom
* Minor fixes to avoid unnecessary errors on shutdown (thanks susi!)
2004-11-29 23:24:49 +00:00
jrandom
e4b41f5bb0 2004-11-29 jrandom
* Reduced contention for local client delivery
    * Drop the new code that munges the wrapper.config.  Instead, updates that
      need to change it will include their own wrapper.config in the
      i2pupdate.zip, overwriting the existing file.  If the file
      "wrapper.config.updated" is included, it is deleted at first opportunity
      and the router shut down, displaying a notice that the router must be
      started again cleanly to allow the changes to the wrapper.config to take
      effect.
    * Properly stop accept()ing I2PSocket connections if we close down the
      session (duh).
    * Make sure we cancel any outstanding Packets in flight when a connection
      is terminated (thanks susi!)
    * Split up the I2PTunnel closing a little further.
2004-11-29 22:27:39 +00:00
jrandom
8d0cea93e9 2004-11-29 jrandom
* Reduced contention for local client delivery
    * Drop the new code that munges the wrapper.config.  Instead, updates that
      need to change it will include their own wrapper.config in the
      i2pupdate.zip, overwriting the existing file.  If the file
      "wrapper.config.updated" is included, it is deleted at first opportunity
      and the router shut down, displaying a notice that the router must be
      started again cleanly to allow the changes to the wrapper.config to take
      effect.
    * Properly stop accept()ing I2PSocket connections if we close down the
      session (duh).
    * Make sure we cancel any outstanding Packets in flight when a connection
      is terminated (thanks susi!)
    * Split up the I2PTunnel closing a little further.
2004-11-29 21:57:14 +00:00
jrandom
d294d07919 added bdl.i2p 2004-11-29 20:55:38 +00:00
jrandom
153eea2bd5 you mean i'm supposed to *test* it? 2004-11-29 03:35:39 +00:00
jrandom
571e3c5c13 2004-11-28 jrandom
* Accept IP address detection changes with a 2-out-of-3 minimum.
    * As long as the router is up, keep retrying to bind the I2CP listener.
    * Decrease the java service wrapper ping frequency to once every 10
      minutes, rather than once every 5 seconds.
2004-11-29 02:09:27 +00:00
jrandom
a2d268f3d6 2004-11-28 jrandom
* Accept IP address detection changes with a 2-out-of-3 minimum.
    * As long as the router is up, keep retrying to bind the I2CP listener.
    * Decrease the java service wrapper ping frequency to once every 10
      minutes, rather than once every 5 seconds.
2004-11-29 01:58:38 +00:00
jrandom
02d456d7a0 added bacardi.i2p and guttersnipe.i2p 2004-11-28 22:47:01 +00:00
mpc
b3626ad86f should've tested it first 2004-11-28 05:11:39 +00:00
mpc
9b6eab451f partial raw handling 2004-11-28 05:10:29 +00:00
jrandom
72be9b5f04 2004-11-27 jrandom
* Some cleanup and bugfixes for the IP address detection code where we
      only consider connections that have actually sent and received messages
      recently as active, rather than the mere presence of a TCP socket as
      activity.
2004-11-27 21:02:06 +00:00
jrandom
35e94a7f65 added evil.i2p 2004-11-27 06:56:29 +00:00
jrandom
8e02586cc9 2004-11-27 jrandom
* Removed the I2PTunnel inactivity timeout thread, since the new streaming
      lib can do that (without an additional per-connection thread).
    * Close the I2PTunnel forwarder threads more aggressively
2004-11-27 05:17:06 +00:00
jrandom
0b5a640896 2004-11-27 jrandom
* Fix for a fast loop caused by a race in the new streaming library (thanks
      DrWoo, frontier, pwk_, and thetower!)
    * Minor updates to the SimpleTimer and Connection to help track down a
      high CPU usage problem (dumping debug info to stdout/wrapper.log if too
      many events/tasks fire in a second)
    * Minor fixes for races on client disconnects (causing NPEs)
2004-11-27 03:54:17 +00:00
jrandom
64b5089909 * 2004-11-26 0.4.2 released
2004-11-26  jrandom
    * Enable the new streaming lib as the default.  That means, for any
      substantial definition, it is NOT BACKWARDS COMPATIBLE.
2004-11-26 15:20:14 +00:00
jrandom
e0e09bfa45 (please wait until the release announcement before updating)
* 2004-11-26  0.4.2 released
2004-11-26  jrandom
    * Enable the new streaming lib as the default.  That means, for any
      substantial definition, it is NOT BACKWARDS COMPATIBLE.
2004-11-26 15:15:16 +00:00
jrandom
8c7f9f2c65 added bdsm.i2p 2004-11-26 02:12:16 +00:00
jrandom
aff5cea949 2004-11-25 jrandom
* Revised the installer to include start menu and desktop shortcuts for
      windows platforms, including pretty icons (thanks DrWoo!)
    * Allow clients specified in clients.config to have an explicit startup
      delay.
    * Update the default install to launch a browser pointing at the console
      whenever I2P starts up, rather than only the first time it starts up
      (configurable on /configservice.jsp, or in clients.config)
    * Bugfix to the clock skew checking code to monitor the delta between
      offsets, not the offset itself (duh)
    * Router console html update
    * New (and uuuuugly) code to verify that the wrapper.config contains
      the necessary classpath entries on update.  If it has to update the
      wrapper.config, it will stop the JVM and service completely, since the
      java service wrapper doesn't reread the wrapper.config on JVM restart -
      requiring the user to manually restart the service after an update.
    * Increase the TCP connection timeout to 30s (which is obscenely long)
2004-11-25 22:17:37 +00:00
jrandom
8bd99f699f 2004-11-25 jrandom
* Revised the installer to include start menu and desktop shortcuts for
      windows platforms, including pretty icons (thanks DrWoo!)
    * Allow clients specified in clients.config to have an explicit startup
      delay.
    * Update the default install to launch a browser pointing at the console
      whenever I2P starts up, rather than only the first time it starts up
      (configurable on /configservice.jsp, or in clients.config)
    * Bugfix to the clock skew checking code to monitor the delta between
      offsets, not the offset itself (duh)
    * Router console html update
    * New (and uuuuugly) code to verify that the wrapper.config contains
      the necessary classpath entries on update.  If it has to update the
      wrapper.config, it will stop the JVM and service completely, since the
      java service wrapper doesn't reread the wrapper.config on JVM restart -
      requiring the user to manually restart the service after an update.
    * Increase the TCP connection timeout to 30s (which is obscenely long)
------------------------------------------------
2004-11-25 21:57:19 +00:00
jrandom
b0513fff8a javadoc 2004-11-25 21:49:29 +00:00
jrandom
f10db9d91a added eschaton.i2p 2004-11-23 03:58:46 +00:00
jrandom
9b5fb17068 added blog.curiosity.i2p 2004-11-23 02:46:07 +00:00
jrandom
608d713dca 2004-11-22 jrandom
* Update to the SAM bridge to reduce some unnecessary memory allocation.
    * New stat to keep track of slow jobs (ones that take more than a second
      to excute).  This is published in the netDb as jobQueue.jobRunSlow
2004-11-23 01:12:34 +00:00
jrandom
6d5fc8ca21 2004-11-21 jrandom
* Update the I2PTunnel web interface to include an option for the new
      streaming lib (which is ignored until the 0.4.2 release).
    * Revised the I2PTunnel web interface to keep the I2CP options of client
      and httpclient tunnels in sync, as they all share the same I2CP session.
2004-11-22 17:57:16 +00:00
jrandom
8c3145b70f 2004-11-21 jrandom
* Only allow small clock skews after the first 10 minutes of operation
      (to prevent later network lag bouncing us way off course - yes, we
      really need an NTP impl to balance out the network burps...)
    * Revamp the I2PTunnel web interface startup process so that everything
      is shown immediately, so that different pieces hanging don't hang
      the rest, and other minor bugfixes.
    * Take note of SAM startup error (in case you're already running a SAM
      bridge...)
    * Increase the bandwidth limiter burst values available to 10-60s (or
      whatever is placed in /configadvanced.jsp, of course)
2004-11-21 23:23:42 +00:00
jrandom
12a6f3e938 2004-11-21 jrandom
* Only allow small clock skews after the first 10 minutes of operation
      (to prevent later network lag bouncing us way off course - yes, we
      really need an NTP impl to balance out the network burps...)
    * Revamp the I2PTunnel web interface startup process so that everything
      is shown immediately, so that different pieces hanging don't hang
      the rest, and other minor bugfixes.
    * Take note of SAM startup error (in case you're already running a SAM
      bridge...)
    * Increase the bandwidth limiter burst values available to 10-60s (or
      whatever is placed in /configadvanced.jsp, of course)
2004-11-21 22:31:33 +00:00
jrandom
2c59435762 2004-11-21 jrandom
* Allow end of line comments in the hosts.txt and other config files,
      using '#' to begin the comments (thanks susi!)
    * Add support to I2PTunnel's 'client' feature for picking between multiple
      target destinations (e.g. 'client 6668 irc.duck.i2p,irc.baffled.i2p')
    * Add a quick link on the left hand nav to reseed if there aren't enough
      known peers, as well as link to the config page if there are no active
      peers.  Revised config page accordingly.
2004-11-21 19:42:57 +00:00
jrandom
7336bf5c55 oops, forgot to commit this one. 2004-11-21 06:13:30 +00:00
jrandom
2b21b97277 * make sure we send the close message even if the I2PSocket is closed directly (rather than the outputStream)
* only fail the session tags as a last resort for the last resend, rather than on every resend after the 1st
2004-11-21 04:11:35 +00:00
jrandom
603bc99a2f 2004-11-21 jrandom
* Destroy ElGamal/AES+SessionTag keys after 15 minutes of inactivity
      rather that every 15 minutes, and increase the warning period in which
      we refresh tags from 30s to 2 minutes.
    * Bugfix for a rare problem closing an I2PTunnel stream where we'd fail
      to close the I2PSocket (leaving it to timeout).
2004-11-21 04:08:13 +00:00
jrandom
426ede1c99 * handle con b0rkage more gracefully
* close the session on socket manager destroy (the old lib does, and SAM wants it to)
2004-11-20 15:52:08 +00:00
jrandom
21506c1d1b handle poorly formatted packets more gracefully (in case someone uses the wrong streaming lib *cough*) 2004-11-20 02:40:57 +00:00
jrandom
0b48b18e7e 2004-11-19 jrandom
* Off-by-one fix to the tunnel pool management code, along side some
      explicit initialization.  This can affect clients whose lengths are
      shorter than the router's default (thanks duck!)
2004-11-19 23:04:27 +00:00
mpc
c8f6d9c7a1 improved logging 2004-11-19 02:17:20 +00:00
jrandom
ed8eced9dd * stats
if you want to get this data and you're running outside the router, just toss on a
"-Dstat.logFilters=* -Dstat.logFile=proxy.stats" to the java command line.  the resulting
file can be parsed w/ java -cp lib/i2p.jar net.i2p.stat.StatLogSplitter proxy.stats, then fed
into gnuplot or whatever
2004-11-19 00:00:05 +00:00
jrandom
4a029b7853 * if we timeout connecting or otherwise need to cancel tags that we've sent, go one step
further and cancel all of the tags we're using for that peer so that we can react to their
  potential restart / tag loss quicker.
* use the minimum resend delay as the base to be exponentiated if our RTT is too low
  (so we resend less)
* dont be such a wuss when flushing a closed stream
2004-11-18 19:42:11 +00:00
jrandom
6bd9e58ece * fix a reordering bug that can trim the end of a stream under heavy lag (thanks duck!)
* fix a pair of races on router crash
2004-11-18 13:47:27 +00:00
jrandom
cd075fc8a6 2004-11-17 jrandom
* Fix to propogate i2psocket options into the SAM bridge correctly (thanks
      Ragnarok!)
2004-11-17 19:42:53 +00:00
jrandom
e733427920 2004-11-17 jrandom
* Minor logging update.
2004-11-17 18:34:25 +00:00
jrandom
107da0ae22 i suppose i should commit this, 'eh? (thanks mule) 2004-11-17 03:43:04 +00:00
mpc
3629d7a32c *** empty log message *** 2004-11-17 03:42:00 +00:00
jrandom
71e1152cde if we've already sent our close packet but we still want to send something, send an ack packet 2004-11-17 00:57:33 +00:00
jrandom
d01ab7fd23 *cough* (lets not have everyone think they're the resend with a packet in the air...) 2004-11-16 22:47:16 +00:00
jrandom
f46d0a720c * fix up the propogation of client options to the streaming lib
* add new back-off logic to reduce payload resends during transient
  lag - only let one packet be resent at a time, even if the window size
  allows it (and the packet timers request it).  this should make
  congestion less painful, and reduce the overall number of messages
  resent (as the SACKs for the one packet actively resent should clarify
  what made it through)
2004-11-16 22:15:16 +00:00
jrandom
d943b4993a 2004-11-16 jrandom
* Clean up the propogation of i2psocket options so that various streaming
      libs can honor them more precisely
2004-11-16 22:11:11 +00:00
jrandom
4a4f57d6ac 2004-11-16 jrandom
* Minor logging update
(toss net.i2p.router.JobQueueRunner=WARN in /configlogging.jsp to see wtf is hanging your router)
2004-11-16 13:45:40 +00:00
jrandom
085da16268 run multiple connections 2004-11-15 14:40:08 +00:00
jrandom
306f6b0037 various tweaks to make sure we release appropriate references ASAP 2004-11-15 14:37:56 +00:00
jrandom
3780d290fa 2004-11-14 jrandom
* Fix a long standing leak in I2PTunnel (hanging on to i2psocket objects)
    * Fix a leak injected into the SimpleTimer
    * Fix a race condition in the tunnel message handling
2004-11-15 14:35:16 +00:00
jrandom
ad7dc66f90 2004-11-13 jrandom
* Added throttles on how many I2PTunnel client connections we open at once
    * Replaced some buffered streams in I2PTunnel with unbuffered streams, as
      the streaming library used should take care of any buffering.
    * Added a cache for some objects used in I2PTunnel, especially useful when
      there are many short lived connections.
    * Trimmed the SimpleTimer's processing a bit
2004-11-13 09:59:37 +00:00
jrandom
258244fed8 * ack packets with a payload, even if they have ID=0 (duh)
* properly implement the connection timeout
* make sure we clear the outbound packets on close
* don't b0rk on repeated close() calls
2004-11-13 09:49:31 +00:00
jrandom
5f7982540f 2004-11-13 jrandom
* Added throttles on how many I2PTunnel client connections we open at once
    * Replaced some buffered streams in I2PTunnel with unbuffered streams, as
      the streaming library used should take care of any buffering.
    * Added a cache for some objects used in I2PTunnel, especially useful when
      there are many short lived connections.
    * Trimmed the SimpleTimer's processing a bit
2004-11-13 09:43:35 +00:00
jrandom
b1c0de4b77 (oops forgot to commit before passing out) 2004-11-12 06:07:33 +00:00
jrandom
45b3fecfff allow throttles on the number of streams participated in, as well as how a timeout + queue for overflow 2004-11-11 21:19:59 +00:00
jrandom
9774ded4dd added slacker.i2p 2004-11-11 10:00:32 +00:00
jrandom
7ec027854e update connection options specification
tweak around with the connection delay and timeout activity to test delayed vs. immediate syn
2004-11-11 09:41:25 +00:00
jrandom
b457001b42 timeout gracefully even if the socket is stopped in odd places 2004-11-11 09:37:46 +00:00
jrandom
6fc6866eb4 *cough* 2004-11-10 13:28:39 +00:00
jrandom
299e5528bc * deal with nondeferred connections (block the mgr.connect(..) until either success or failure)
* allow loading the connection options from the env (or another Properties specified)
2004-11-10 12:55:06 +00:00
jrandom
881524a5e4 2004-11-10 jrandom
* Allow loading the (mini)streaming connection options from the
      environment.
    * More defensive programming in the DSA implementation.
2004-11-10 12:33:01 +00:00
jrandom
ffc405138d added pdforge.i2p and ses.i2p 2004-11-10 09:43:45 +00:00
jrandom
f6ff74af16 oops, properly choke the congestion detection for resend 2004-11-09 13:33:11 +00:00
jrandom
73a12d47de * you mean we should implement congestion *avoidance* too?
* ack properly on duplicates
* set the message input stream buffer large enough to fit the max window (duh)
2004-11-09 13:26:10 +00:00
jrandom
83165df7e5 * delay the ack of a syn
* make sure we ack duplicate messages received (if we aren't already doing so)
* implement a choke on the local buffer, in case we receive data faster than its
  removed from the i2psocket's MessageInputStream (handle via packet drop and
  explicit congestion notification)
2004-11-09 11:00:04 +00:00
jrandom
30074be5a5 logging 2004-11-09 05:54:39 +00:00
jrandom
16715aa309 * synchronize around the buffer used in the packet queue (duh)
* dont increment # unacked packets artificially
* dont try to push data after closing
* cleanup the packet serialization
* logging
2004-11-08 21:40:25 +00:00
jrandom
53f3802a81 dont keepalive if we're in the closing process (duh) 2004-11-08 16:49:23 +00:00
jrandom
07626b5cc2 two new tests - inactivity (seeing how we stay alive) and timeout (contacting someone who doesnt exist) 2004-11-08 15:27:41 +00:00
jrandom
9ea603caf2 * hang around for 5m (er, 2.5msl, i suppose) after connection closure no matter what, so we
can respond apropriately
* optional inactivty timer with three possible results:
  disconnect, send a (blank) message (to be ACKed), or do nothing.
2004-11-08 15:05:13 +00:00
jrandom
18ab9b80d2 testStaggered - read what we can while writing randomly 2004-11-08 05:48:36 +00:00
jrandom
71c1cb4e12 * min resend delay = 20s
* rework the messageInputStream to implement read(byte[], off, len), and fix some fencepost
  bugs in the byte retrieval
2004-11-08 05:42:57 +00:00
jrandom
0c049f39d9 2004-11-08 jrandom
* Remove spurious flush calls from I2PTunnel, and work with the
      I2PSocket's output stream directly (as the various implementations
      do their own buffering).
    * Another pass at a long standing JobQueue bug - dramatically simplify
      the job management synchronization since we dont need to deal with
      high contention (unlike last year when we had dozens of queue runners
      going at once).
    * Logging
2004-11-08 05:40:20 +00:00
jrandom
096b807c37 2004-11-08 jrandom
* Make the SAM bridge more resiliant to bad handshakes (thanks duck!)
2004-11-08 03:18:01 +00:00
mpc
9018af4765 Leave it up to the client application whether to poll or not 2004-11-07 05:06:36 +00:00
mpc
323f28e306 Now it works?? 2004-11-07 04:14:53 +00:00
mpc
e9dbd00f42 *** empty log message *** 2004-11-07 04:12:36 +00:00
123 changed files with 3794 additions and 1009 deletions

View File

@@ -284,7 +284,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log("textserver <host> <port> <privkey>");
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
l.log("gentextkeys");
l.log("client <port> <pubkey>|file:<pubkeyfile>");
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile>");
l.log("httpclient <port>");
l.log("lookup <name>");
l.log("quit");
@@ -449,9 +449,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("clientTaskId", new Integer(-1));
}
} else {
l.log("client <port> <pubkey>|file:<pubkeyfile>");
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>");
l.log(" creates a client that forwards port to the pubkey.\n"
+ " use 0 as port to get a free port assigned.");
+ " use 0 as port to get a free port assigned. If you specify\n"
+ " a comma delimited list of pubkeys, it will rotate among them\n"
+ " randomlyl");
notifyEvent("clientTaskId", new Integer(-1));
}
}

View File

@@ -4,7 +4,11 @@
package net.i2p.i2ptunnel;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
@@ -15,15 +19,17 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
private static final Log _log = new Log(I2PTunnelClient.class);
protected Destination dest;
/** list of Destination objects that we point at */
protected List dests;
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
protected long readTimeout = DEFAULT_READ_TIMEOUT;
/**
* @param destinations comma delimited list of peers we target
* @throws IllegalArgumentException if the I2PTunnel does not contain
* valid config to contact the router
*/
public I2PTunnelClient(int localPort, String destination, Logging l,
public I2PTunnelClient(int localPort, String destinations, Logging l,
boolean ownDest, EventDispatcher notifyThis,
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort, ownDest, l, notifyThis, "SynSender", tunnel);
@@ -33,19 +39,28 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
return;
}
try {
dest = I2PTunnel.destFromName(destination);
if (dest == null) {
l.log("Could not resolve " + destination + ".");
return;
StringTokenizer tok = new StringTokenizer(destinations, ",");
dests = new ArrayList(1);
while (tok.hasMoreTokens()) {
String destination = tok.nextToken();
try {
Destination dest = I2PTunnel.destFromName(destination);
if (dest == null)
l.log("Could not resolve " + destination);
else
dests.add(dest);
} catch (DataFormatException dfe) {
l.log("Bad format parsing \"" + destination + "\"");
}
} catch (DataFormatException e) {
l.log("Bad format in destination \"" + destination + "\".");
}
if (dests.size() <= 0) {
l.log("No target destinations found");
notifyEvent("openClientResult", "error");
return;
}
setName(getLocalPort() + " -> " + destination);
setName(getLocalPort() + " -> " + destinations);
startRunning();
@@ -56,14 +71,34 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
public long getReadTimeout() { return readTimeout; }
protected void clientConnectionRun(Socket s) {
Destination dest = pickDestination();
I2PSocket i2ps = null;
try {
I2PSocket i2ps = createI2PSocket(dest);
i2ps = createI2PSocket(dest);
i2ps.setReadTimeout(readTimeout);
new I2PTunnelRunner(s, i2ps, sockLock, null);
new I2PTunnelRunner(s, i2ps, sockLock, null, mySockets);
} catch (Exception ex) {
_log.info("Error connecting", ex);
l.log(ex.getMessage());
closeSocket(s);
if (i2ps != null) {
synchronized (sockLock) {
mySockets.remove(sockLock);
}
}
}
}
private final Destination pickDestination() {
int size = dests.size();
if (size <= 0) {
if (_log.shouldLog(Log.ERROR))
_log.error("No client targets?!");
return null;
}
if (size == 1) // skip the rand in the most common case
return (Destination)dests.get(0);
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
return (Destination)dests.get(index);
}
}

View File

@@ -33,13 +33,13 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private static final Log _log = new Log(I2PTunnelClientBase.class);
protected Logging l;
private static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
private static volatile long __clientId = 0;
protected long _clientId;
protected Object sockLock = new Object(); // Guards sockMgr and mySockets
private I2PSocketManager sockMgr;
private List mySockets = new ArrayList();
protected I2PSocketManager sockMgr;
protected List mySockets = new ArrayList();
protected Destination dest = null;
private int localPort;
@@ -57,6 +57,9 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private String handlerName;
private Object conLock = new Object();
private int pendingConnections = 0;
//public I2PTunnelClientBase(int localPort, boolean ownDest,
// Logging l) {
// I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
@@ -96,7 +99,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
t.start();
open = true;
synchronized (this) {
while (!listenerReady) {
while (!listenerReady && open) {
try {
wait();
} catch (InterruptedException e) {
@@ -109,7 +112,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
l.log("Ready! Port " + getLocalPort());
notifyEvent("openBaseClientResult", "ok");
} else {
l.log("Error!");
l.log("Error listening - please see the logs!");
notifyEvent("openBaseClientResult", "error");
}
}
@@ -181,9 +184,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
* create the default options (using the default timeout, etc)
*
*/
private I2PSocketOptions getDefaultOptions() {
I2PSocketOptions opts = new I2PSocketOptions();
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
protected I2PSocketOptions getDefaultOptions() {
Properties defaultOpts = getTunnel().getClientOptions();
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
return opts;
}
/**
* create the default options (using the default timeout, etc)
*
*/
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
Properties defaultOpts = getTunnel().getClientOptions();
defaultOpts.putAll(overrides);
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
return opts;
}
@@ -227,7 +245,13 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
public final void run() {
try {
InetAddress addr = getListenHost(l);
if (addr == null) return;
if (addr == null) {
open = false;
synchronized (this) {
notifyAll();
}
return;
}
ss = new ServerSocket(localPort, 0, addr);
// If a free port was requested, find out what we got
@@ -258,8 +282,15 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
manageConnection(s);
}
} catch (IOException ex) {
_log.error("Error listening for connections", ex);
_log.error("Error listening for connections on " + localPort, ex);
notifyEvent("openBaseClientResult", "error");
synchronized (sockLock) {
mySockets.clear();
}
open = false;
synchronized (this) {
notifyAll();
}
}
}
@@ -269,7 +300,16 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
* @param s Socket to take care of
*/
protected void manageConnection(Socket s) {
new ClientConnectionRunner(s, handlerName);
boolean useBlocking = false;
synchronized (conLock) {
pendingConnections++;
if (pendingConnections > 5)
useBlocking = true;
}
if (useBlocking)
clientConnectionRun(s);
else
new ClientConnectionRunner(s, handlerName);
}
public boolean close(boolean forced) {
@@ -326,6 +366,9 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
public void run() {
clientConnectionRun(s);
synchronized (conLock) {
pendingConnections--;
}
}
}

View File

@@ -14,10 +14,12 @@ import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import java.util.HashMap;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Clock;
@@ -142,14 +144,49 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
return proxy;
}
}
private static final int DEFAULT_READ_TIMEOUT = 60*1000;
/**
* create the default options (using the default timeout, etc)
*
*/
protected I2PSocketOptions getDefaultOptions() {
Properties defaultOpts = getTunnel().getClientOptions();
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
return opts;
}
/**
* create the default options (using the default timeout, etc)
*
*/
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
Properties defaultOpts = getTunnel().getClientOptions();
defaultOpts.putAll(overrides);
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
return opts;
}
private static long __requestId = 0;
protected void clientConnectionRun(Socket s) {
OutputStream out = null;
String targetRequest = null;
boolean usingWWWProxy = false;
String currentProxy = null;
InactivityTimeoutThread timeoutThread = null;
long requestId = ++__requestId;
try {
out = s.getOutputStream();
@@ -295,7 +332,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix(requestId) + "Setting host = " + host);
} else if (line.startsWith("User-Agent: ")) {
line = "User-Agent: MYOB/6.66 (AN/ON)";
// always stripped, added back at the end
line = null;
continue;
} else if (line.startsWith("Accept")) {
// strip the accept-blah headers, as they vary dramatically from
// browser to browser
line = null;
continue;
} else if (line.startsWith("Referer: ")) {
// Shouldn't we be more specific, like accepting in-site referers ?
//line = "Referer: i2p";
@@ -313,6 +357,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
if (line.length() == 0) {
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
newRequest.append("Connection: close\r\n\r\n");
break;
} else {
@@ -354,25 +399,26 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
return;
}
String remoteID;
I2PSocket i2ps = createI2PSocket(dest);
Properties opts = new Properties();
opts.setProperty("i2p.streaming.inactivityTimeout", ""+120*1000);
// 1 == disconnect. see ConnectionOptions in the new streaming lib, which i
// dont want to hard link to here
opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions(opts));
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data);
timeoutThread = new InactivityTimeoutThread(runner, out, targetRequest, usingWWWProxy, currentProxy, s, requestId);
timeoutThread.start();
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data, mySockets);
} catch (SocketException ex) {
if (timeoutThread != null) timeoutThread.disable();
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (IOException ex) {
if (timeoutThread != null) timeoutThread.disable();
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (I2PException ex) {
if (timeoutThread != null) timeoutThread.disable();
_log.info("getPrefix(requestId) + Error trying to connect", ex);
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
@@ -380,91 +426,6 @@ 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 {
private Socket s;
private I2PTunnelRunner _runner;
private OutputStream _out;
private String _targetRequest;
private boolean _useWWWProxy;
private String _currentProxy;
private long _requestId;
private boolean _disabled;
private Object _disableLock = new Object();
public InactivityTimeoutThread(I2PTunnelRunner runner, OutputStream out, String targetRequest,
boolean useWWWProxy, String currentProxy, Socket s, long requestId) {
this.s = s;
_runner = runner;
_out = out;
_targetRequest = targetRequest;
_useWWWProxy = useWWWProxy;
_currentProxy = currentProxy;
_disabled = false;
_requestId = requestId;
long timeoutId = ++__timeoutId;
setName("InactivityThread " + getPrefix(requestId) + timeoutId);
}
public void disable() {
_disabled = true;
synchronized (_disableLock) {
_disableLock.notifyAll();
}
}
public void run() {
while (!_disabled) {
if (_runner.isFinished()) {
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix(_requestId) + "HTTP client request completed prior to timeout");
return;
}
if (_runner.getLastActivityOn() < Clock.getInstance().now() - INACTIVITY_TIMEOUT) {
if (_runner.getStartedOn() < Clock.getInstance().now() - INACTIVITY_TIMEOUT) {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(_requestId) + "HTTP client request timed out (lastActivity: "
+ new Date(_runner.getLastActivityOn()) + ", startedOn: "
+ new Date(_runner.getStartedOn()) + ")");
timeout();
return;
} else {
// runner hasn't been going to long enough
}
} else {
// there has been activity in the period
}
synchronized (_disableLock) {
try {
_disableLock.wait(INACTIVITY_TIMEOUT);
} catch (InterruptedException ie) {
}
}
}
}
private void timeout() {
_log.info(getPrefix(_requestId) + "Inactivity timeout reached");
l.log("Inactivity timeout reached");
if (_out != null) {
try {
if (_runner.getLastActivityOn() > 0) {
// some data has been sent, so don't 404 it
} else {
writeErrorMessage(ERR_TIMEOUT, _out, _targetRequest, _useWWWProxy, _currentProxy);
}
} catch (IOException ioe) {
_log.warn(getPrefix(_requestId) + "Error writing out the 'timeout' message", ioe);
}
} else {
_log.warn(getPrefix(_requestId) + "Client disconnected before we could say we timed out");
}
closeSocket(s);
}
}
private final static String getHostName(String host) {
if (host == null) return null;
try {

View File

@@ -11,9 +11,11 @@ import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.List;
import net.i2p.client.I2PSession;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.ByteArray;
import net.i2p.util.ByteCache;
import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
@@ -38,14 +40,17 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
Object slock, finishLock = new Object();
boolean finished = false;
HashMap ostreams, sockets;
I2PSession session;
byte[] initialData;
/** when the last data was sent/received (or -1 if never) */
private long lastActivityOn;
/** when the runner started up */
private long startedOn;
private List sockList;
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData) {
private volatile long __forwarderId;
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData, List sockList) {
this.sockList = sockList;
this.s = s;
this.i2ps = i2ps;
this.slock = slock;
@@ -55,6 +60,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
if (_log.shouldLog(Log.INFO))
_log.info("I2PTunnelRunner started");
_runnerId = ++__runnerId;
__forwarderId = i2ps.hashCode();
setName("I2PTunnelRunner " + _runnerId);
start();
}
@@ -91,50 +97,68 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
}
public void run() {
boolean closedCleanly = false;
try {
InputStream in = s.getInputStream();
OutputStream out = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
OutputStream out = s.getOutputStream(); // = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
i2ps.setSocketErrorListener(this);
InputStream i2pin = i2ps.getInputStream();
OutputStream i2pout = new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
OutputStream i2pout = i2ps.getOutputStream(); //new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
if (initialData != null) {
synchronized (slock) {
i2pout.write(initialData);
i2pout.flush();
//i2pout.flush();
}
}
Thread t1 = new StreamForwarder(in, i2pout);
Thread t2 = new StreamForwarder(i2pin, out);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Initial data " + (initialData != null ? initialData.length : 0)
+ " written, starting forwarders");
Thread t1 = new StreamForwarder(in, i2pout, "toI2P");
Thread t2 = new StreamForwarder(i2pin, out, "fromI2P");
synchronized (finishLock) {
while (!finished) {
finishLock.wait();
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("At least one forwarder completed, closing and joining");
// now one connection is dead - kill the other as well.
s.close();
s = null;
i2ps.close();
i2ps = null;
t1.join();
t2.join();
t1.join(30*1000);
t2.join(30*1000);
closedCleanly = true;
} catch (InterruptedException ex) {
_log.error("Interrupted", ex);
if (_log.shouldLog(Log.ERROR))
_log.error("Interrupted", ex);
} catch (IOException ex) {
ex.printStackTrace();
_log.debug("Error forwarding", ex);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error forwarding", ex);
} catch (Exception e) {
_log.error("Internal error", e);
if (_log.shouldLog(Log.ERROR))
_log.error("Internal error", e);
} finally {
removeRef();
try {
if (s != null) s.close();
if (i2ps != null) i2ps.close();
if ( (s != null) && (!closedCleanly) )
s.close();
} catch (IOException ex) {
ex.printStackTrace();
_log.error("Could not close socket", ex);
if (_log.shouldLog(Log.ERROR))
_log.error("Could not close java socket", ex);
}
try {
if (i2ps != null) {
if (!closedCleanly)
i2ps.close();
i2ps.setSocketErrorListener(null);
}
} catch (IOException ex) {
if (_log.shouldLog(Log.ERROR))
_log.error("Could not close I2PSocket", ex);
}
}
}
public void errorOccurred() {
synchronized (finishLock) {
finished = true;
@@ -142,22 +166,43 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
}
}
private volatile long __forwarderId = 0;
private void removeRef() {
if (sockList != null) {
synchronized (slock) {
boolean removed = sockList.remove(i2ps);
//System.out.println("Removal of i2psocket " + i2ps + " successful? "
// + removed + " remaining: " + sockList.size());
}
}
}
private class StreamForwarder extends I2PThread {
InputStream in;
OutputStream out;
String direction;
private ByteCache _cache;
private StreamForwarder(InputStream in, OutputStream out) {
private StreamForwarder(InputStream in, OutputStream out, String dir) {
this.in = in;
this.out = out;
direction = dir;
_cache = ByteCache.getInstance(256, NETWORK_BUFFER_SIZE);
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
start();
}
public void run() {
byte[] buffer = new byte[NETWORK_BUFFER_SIZE];
String from = i2ps.getThisDestination().calculateHash().toBase64().substring(0,6);
String to = i2ps.getPeerDestination().calculateHash().toBase64().substring(0,6);
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(direction + ": Forwarding between "
+ from + " and " + to);
}
ByteArray ba = _cache.acquire();
byte[] buffer = ba.getData(); // new byte[NETWORK_BUFFER_SIZE];
try {
int len;
while ((len = in.read(buffer)) != -1) {
@@ -166,45 +211,65 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
if (len > 0) updateActivity();
if (in.available() == 0) {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Flushing after sending " + len + " bytes through");
if (_log.shouldLog(Log.DEBUG))
_log.debug(direction + ": " + len + " bytes flushed through to "
+ i2ps.getPeerDestination().calculateHash().toBase64().substring(0,6));
try {
Thread.sleep(I2PTunnel.PACKET_DELAY);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (in.available() == 0) {
out.flush(); // make sure the data get though
if (in.available() <= 0)
out.flush(); // make sure the data get though
}
}
//out.flush(); // close() flushes
} catch (SocketException ex) {
// this *will* occur when the other threads closes the socket
synchronized (finishLock) {
if (!finished) {
_log.debug("Socket closed - error reading and writing",
ex);
if (_log.shouldLog(Log.DEBUG))
_log.debug(direction + ": Socket closed - error reading and writing",
ex);
}
}
} catch (InterruptedIOException ex) {
_log.warn("Closing connection due to timeout (error: \""
+ ex.getMessage() + "\")");
if (_log.shouldLog(Log.WARN))
_log.warn(direction + ": Closing connection due to timeout (error: \""
+ ex.getMessage() + "\")");
} catch (IOException ex) {
if (!finished)
_log.error("Error forwarding", ex);
if (!finished) {
if (_log.shouldLog(Log.ERROR))
_log.error(direction + ": Error forwarding", ex);
}
//else
// _log.warn("You may ignore this", ex);
} finally {
if (_log.shouldLog(Log.INFO)) {
_log.info(direction + ": done forwarding between "
+ from + " and " + to);
}
try {
out.close();
in.close();
} catch (IOException ex) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error closing streams", ex);
_log.warn(direction + ": Error closing input stream", ex);
}
try {
out.close();
} catch (IOException ex) {
if (_log.shouldLog(Log.WARN))
_log.warn(direction + ": Error closing output stream", ex);
}
synchronized (finishLock) {
finished = true;
finishLock.notifyAll();
// the main thread will close sockets etc. now
}
_cache.release(ba);
}
}
}

View File

@@ -179,7 +179,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
_handleSocket.setReadTimeout(readTimeout);
Socket s = new Socket(remoteHost, remotePort);
afterSocket = I2PAppContext.getGlobalContext().clock().now();
new I2PTunnelRunner(s, _handleSocket, slock, null);
new I2PTunnelRunner(s, _handleSocket, slock, null, null);
} catch (SocketException ex) {
try {
_handleSocket.close();

View File

@@ -31,6 +31,7 @@ public class TunnelController implements Logging {
private List _messages;
private List _sessions;
private boolean _running;
private boolean _starting;
/**
* Create a new controller for a tunnel out of the specific config options.
@@ -58,8 +59,7 @@ public class TunnelController implements Logging {
_running = false;
if (createKey && ("server".equals(getType())) )
createPrivateKey();
if (getStartOnLoad())
startTunnel();
_starting = getStartOnLoad();
}
private void createPrivateKey() {
@@ -104,12 +104,14 @@ public class TunnelController implements Logging {
*
*/
public void startTunnel() {
_starting = true;
try {
doStartTunnel();
} catch (Exception e) {
_log.error("Error starting up the tunnel", e);
log("Error starting up the tunnel - " + e.getMessage());
}
_starting = false;
}
private void doStartTunnel() {
if (_running) {
@@ -299,6 +301,7 @@ public class TunnelController implements Logging {
public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
public boolean getIsRunning() { return _running; }
public boolean getIsStarting() { return _starting; }
public void getSummary(StringBuffer buf) {
String type = getType();

View File

@@ -8,6 +8,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -20,6 +21,8 @@ import java.util.TreeMap;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
@@ -43,28 +46,34 @@ public class TunnelControllerGroup {
*/
private Map _sessions;
public static TunnelControllerGroup getInstance() { return _instance; }
public static TunnelControllerGroup getInstance() {
synchronized (TunnelControllerGroup.class) {
if (_instance == null)
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
return _instance;
}
}
private TunnelControllerGroup(String configFile) {
_log = I2PAppContext.getGlobalContext().logManager().getLog(TunnelControllerGroup.class);
_instance = this;
_controllers = new ArrayList();
_controllers = Collections.synchronizedList(new ArrayList());
_configFile = configFile;
_sessions = new HashMap(4);
loadControllers(_configFile);
}
public static void main(String args[]) {
if ( (args == null) || (args.length <= 0) ) {
new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
} else if (args.length == 1) {
if (DEFAULT_CONFIG_FILE.equals(args[0]))
new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
else
new TunnelControllerGroup(args[0]);
} else {
System.err.println("Usage: TunnelControllerGroup [filename]");
return;
synchronized (TunnelControllerGroup.class) {
if (_instance != null) return; // already loaded through the web
if ( (args == null) || (args.length <= 0) ) {
_instance = new TunnelControllerGroup(DEFAULT_CONFIG_FILE);
} else if (args.length == 1) {
_instance = new TunnelControllerGroup(args[0]);
} else {
System.err.println("Usage: TunnelControllerGroup [filename]");
return;
}
}
}
@@ -89,10 +98,24 @@ public class TunnelControllerGroup {
_controllers.add(controller);
i++;
}
I2PThread startupThread = new I2PThread(new StartControllers(), "Startup tunnels");
startupThread.start();
if (_log.shouldLog(Log.INFO))
_log.info(i + " controllers loaded from " + configFile);
}
private class StartControllers implements Runnable {
public void run() {
for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = (TunnelController)_controllers.get(i);
if (controller.getStartOnLoad())
controller.startTunnel();
}
}
}
public void reloadControllers() {
unloadControllers();
loadControllers(_configFile);
@@ -143,7 +166,6 @@ public class TunnelControllerGroup {
controller.stopTunnel();
msgs.addAll(controller.clearMessages());
}
if (_log.shouldLog(Log.INFO))
_log.info(_controllers.size() + " controllers stopped");
return msgs;
@@ -161,7 +183,7 @@ public class TunnelControllerGroup {
controller.startTunnel();
msgs.addAll(controller.clearMessages());
}
if (_log.shouldLog(Log.INFO))
_log.info(_controllers.size() + " controllers started");
return msgs;
@@ -259,33 +281,13 @@ public class TunnelControllerGroup {
}
Properties props = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(cfgFile);
BufferedReader in = new BufferedReader(new InputStreamReader(fis));
String line = null;
while ( (line = in.readLine()) != null) {
line = line.trim();
if (line.length() <= 0) continue;
if (line.startsWith("#") || line.startsWith(";"))
continue;
int eq = line.indexOf('=');
if ( (eq <= 0) || (eq >= line.length() - 1) )
continue;
String key = line.substring(0, eq);
String val = line.substring(eq+1);
props.setProperty(key, val);
}
if (_log.shouldLog(Log.INFO))
_log.info("Props loaded with " + props.size() + " lines");
DataHelper.loadProps(props, cfgFile);
return props;
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error reading the controllers from " + configFile, ioe);
return null;
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
}

View File

@@ -60,6 +60,9 @@ class WebEditPageFormGenerator {
buf.append("value=\"squid.i2p\" ");
buf.append("/><br />\n");
buf.append("<hr />Note: the following options are shared across all client tunnels and");
buf.append(" HTTP proxies<br />\n");
addOptions(buf, controller);
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
@@ -80,6 +83,9 @@ class WebEditPageFormGenerator {
buf.append("value=\"").append(controller.getTargetDestination()).append("\" ");
buf.append(" /> (either the hosts.txt name or the full base64 destination)<br />\n");
buf.append("<hr />Note: the following options are shared across all client tunnels and");
buf.append(" HTTP proxies<br />\n");
addOptions(buf, controller);
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\"><br />\n");
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
@@ -145,6 +151,13 @@ class WebEditPageFormGenerator {
if ( (controller != null) && (controller.getDescription() != null) )
buf.append("value=\"").append(controller.getDescription()).append("\" ");
buf.append("/><br />\n");
buf.append("<b>Start automatically?</b> \n");
buf.append("<input type=\"checkbox\" name=\"startOnLoad\" value=\"true\" ");
if ( (controller != null) && (controller.getStartOnLoad()) )
buf.append(" checked=\"true\" />\n<br />\n");
else
buf.append(" />\n<br />\n");
}
/**
@@ -194,6 +207,7 @@ class WebEditPageFormGenerator {
private static void addOptions(StringBuffer buf, TunnelController controller) {
int tunnelDepth = 2;
int numTunnels = 2;
int connectDelay = 0;
Properties opts = getOptions(controller);
if (opts != null) {
String depth = opts.getProperty("tunnels.depthInbound");
@@ -212,6 +226,14 @@ class WebEditPageFormGenerator {
numTunnels = 2;
}
}
String delay = opts.getProperty("i2p.streaming.connectDelay");
if (delay != null) {
try {
connectDelay = Integer.parseInt(delay);
} catch (NumberFormatException nfe) {
connectDelay = 0;
}
}
}
buf.append("<b>Tunnel depth:</b> ");
@@ -251,6 +273,13 @@ class WebEditPageFormGenerator {
}
buf.append("</select><br />\n");
buf.append("<b>Delay connection briefly? </b> ");
buf.append("<input type=\"checkbox\" name=\"connectDelay\" value=\"");
buf.append((connectDelay > 0 ? connectDelay : 1000)).append("\" ");
if (connectDelay > 0)
buf.append("checked=\"true\" ");
buf.append("/> (useful for brief request/response connections)<br />\n");
buf.append("<b>I2CP host:</b> ");
buf.append("<input type=\"text\" name=\"clientHost\" size=\"20\" value=\"");
if ( (controller != null) && (controller.getI2CPHost() != null) )
@@ -275,18 +304,13 @@ class WebEditPageFormGenerator {
String val = opts.getProperty(key);
if ("tunnels.depthInbound".equals(key)) continue;
if ("tunnels.numInbound".equals(key)) continue;
if ("i2p.streaming.connectDelay".equals(key)) continue;
if (i != 0) buf.append(' ');
buf.append(key).append('=').append(val);
i++;
}
}
buf.append("\" /><br />\n");
buf.append("<b>Start automatically?</b> \n");
buf.append("<input type=\"checkbox\" name=\"startOnLoad\" value=\"true\" ");
if ( (controller != null) && (controller.getStartOnLoad()) )
buf.append(" checked=\"true\" />\n<br />\n");
else
buf.append(" />\n<br />\n");
}
/**

View File

@@ -29,6 +29,7 @@ public class WebEditPageHelper {
private String _i2cpPort;
private String _tunnelDepth;
private String _tunnelCount;
private boolean _connectDelay;
private String _customOptions;
private String _proxyList;
private String _port;
@@ -164,6 +165,9 @@ public class WebEditPageHelper {
public void setStartOnLoad(String moo) {
_startOnLoad = true;
}
public void setConnectDelay(String moo) {
_connectDelay = true;
}
/**
* Process the form and display any resulting messages
@@ -252,6 +256,27 @@ public class WebEditPageHelper {
cur.setConfig(config, "");
}
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
// all clients use the same I2CP session, and as such, use the same
// I2CP options
List controllers = TunnelControllerGroup.getInstance().getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelController c = (TunnelController)controllers.get(i);
if (c == cur) continue;
if ("httpclient".equals(c.getType()) || "client".equals(c.getType())) {
Properties cOpt = c.getConfig("");
if (_tunnelCount != null)
cOpt.setProperty("option.tunnels.numInbound", _tunnelCount);
if (_tunnelDepth != null)
cOpt.setProperty("option.tunnels.depthInbound", _tunnelDepth);
if (_connectDelay)
cOpt.setProperty("option.i2p.streaming.connectDelay", "1000");
else
cOpt.setProperty("option.i2p.streaming.connectDelay", "0");
c.setConfig(cOpt, "");
}
}
}
return getMessages(doSave());
}
@@ -324,6 +349,7 @@ public class WebEditPageHelper {
String val = pair.substring(eq+1);
if ("tunnels.numInbound".equals(key)) continue;
if ("tunnels.depthInbound".equals(key)) continue;
if ("i2p.streaming.connectDelay".equals(key)) continue;
config.setProperty("option." + key, val);
}
}
@@ -334,6 +360,10 @@ public class WebEditPageHelper {
config.setProperty("option.tunnels.numInbound", _tunnelCount);
if (_tunnelDepth != null)
config.setProperty("option.tunnels.depthInbound", _tunnelDepth);
if (_connectDelay)
config.setProperty("option.i2p.streaming.connectDelay", "1000");
else
config.setProperty("option.i2p.streaming.connectDelay", "0");
}
/**

View File

@@ -68,6 +68,8 @@ public class WebStatusPageHelper {
if (controller.getIsRunning()) {
buf.append("<i>running</i> ");
buf.append("<a href=\"index.jsp?num=").append(num).append("&action=stop\">stop</a> ");
} else if (controller.getIsStarting()) {
buf.append("<i>startup in progress (please be patient)</i>");
} else {
buf.append("<i>not running</i> ");
buf.append("<a href=\"index.jsp?num=").append(num).append("&action=start\">start</a> ");

View File

@@ -47,7 +47,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s);
Socket clientSock = serv.getClientSocket();
I2PSocket destSock = serv.getDestinationI2PSocket();
new I2PTunnelRunner(clientSock, destSock, sockLock, null);
new I2PTunnelRunner(clientSock, destSock, sockLock, null, mySockets);
} catch (SOCKSException e) {
_log.error("Error from SOCKS connection: " + e.getMessage());
closeSocket(s);

View File

@@ -81,7 +81,7 @@ public abstract class SOCKSServer {
if (connHostName.toLowerCase().endsWith(".i2p")) {
_log.debug("connecting to " + connHostName + "...");
I2PSocketManager sm = I2PSocketManagerFactory.createManager();
destSock = sm.connect(I2PTunnel.destFromName(connHostName), new I2PSocketOptions());
destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
confirmConnection();
_log.debug("connection confirmed - exchanging data...");
} else {

View File

@@ -5,7 +5,7 @@ package net.i2p.client.streaming;
* so care should be taken when using in a multithreaded environment.
*
*/
public class ByteCollector {
class ByteCollector {
byte[] contents;
int size;

View File

@@ -49,6 +49,9 @@ public interface I2PSocketManager {
public void setDefaultOptions(I2PSocketOptions options);
public I2PSocketOptions getDefaultOptions();
public I2PServerSocket getServerSocket();
public I2PSocketOptions buildOptions();
public I2PSocketOptions buildOptions(Properties opts);
/**
* Create a new connected socket (block until the socket is created)

View File

@@ -26,8 +26,8 @@ public class I2PSocketManagerFactory {
private final static Log _log = new Log(I2PSocketManagerFactory.class);
public static final String PROP_MANAGER = "i2p.streaming.manager";
public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerImpl";
//public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerFull";
//public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerImpl";
public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerFull";
/**
* Create a socket manager using a brand new destination connected to the
@@ -90,7 +90,7 @@ public class I2PSocketManagerFactory {
opts.setProperty(name, System.getProperty(name));
}
boolean oldLib = DEFAULT_MANAGER.equals(opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER));
if (oldLib) {
if (oldLib && false) {
// for the old streaming lib
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
//opts.setProperty("tunnels.depthInbound", "0");
@@ -106,7 +106,10 @@ public class I2PSocketManagerFactory {
try {
I2PSession session = client.createSession(myPrivateKeyStream, opts);
session.connect();
return createManager(session, opts, "manager");
I2PSocketManager sockMgr = createManager(session, opts, "manager");
if (sockMgr != null)
sockMgr.setDefaultOptions(sockMgr.buildOptions(opts));
return sockMgr;
} catch (I2PSessionException ise) {
_log.error("Error creating session for socket manager", ise);
return null;
@@ -117,7 +120,7 @@ public class I2PSocketManagerFactory {
if (false) {
I2PSocketManagerImpl mgr = new I2PSocketManagerImpl();
mgr.setSession(session);
mgr.setDefaultOptions(new I2PSocketOptions());
//mgr.setDefaultOptions(new I2PSocketOptions());
return mgr;
} else {
String classname = opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER);

View File

@@ -35,7 +35,7 @@ import net.i2p.util.Log;
* or receive any messages with its .receiveMessage
*
*/
public class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
private I2PAppContext _context;
private Log _log;
private I2PSession _session;
@@ -77,7 +77,7 @@ public class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListene
_outSockets = new HashMap(16);
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
setSession(session);
setDefaultOptions(new I2PSocketOptions());
setDefaultOptions(buildOptions(opts));
_context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.sent", "How many bytes are sent in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.received", "How many bytes are received in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
@@ -433,6 +433,11 @@ public class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListene
public I2PSocketOptions getDefaultOptions() {
return _defaultOptions;
}
public I2PSocketOptions buildOptions() { return buildOptions(null); }
public I2PSocketOptions buildOptions(Properties opts) {
return new I2PSocketOptionsImpl(opts);
}
public I2PServerSocket getServerSocket() {
if (_serverSocket == null) {

View File

@@ -6,67 +6,38 @@ import java.util.Properties;
* Define the configuration for streaming and verifying data on the socket.
*
*/
public class I2PSocketOptions {
private long _connectTimeout;
private long _readTimeout;
private long _writeTimeout;
private int _maxBufferSize;
public static final int DEFAULT_BUFFER_SIZE = 1024*64;
public static final int DEFAULT_WRITE_TIMEOUT = 60*1000;
public I2PSocketOptions() {
_connectTimeout = -1;
_readTimeout = -1;
_writeTimeout = DEFAULT_WRITE_TIMEOUT;
_maxBufferSize = DEFAULT_BUFFER_SIZE;
}
public I2PSocketOptions(I2PSocketOptions opts) {
_connectTimeout = opts.getConnectTimeout();
_readTimeout = opts.getReadTimeout();
_writeTimeout = opts.getWriteTimeout();
_maxBufferSize = opts.getMaxBufferSize();
}
public I2PSocketOptions(Properties opts) {
}
public interface I2PSocketOptions {
public static final String PROP_BUFFER_SIZE = "i2p.streaming.bufferSize";
public static final String PROP_CONNECT_TIMEOUT = "i2p.streaming.connectTimeout";
public static final String PROP_READ_TIMEOUT = "i2p.streaming.readTimeout";
public static final String PROP_WRITE_TIMEOUT = "i2p.streaming.writeTimeout";
/**
* How long we will wait for the ACK from a SYN, in milliseconds.
*
* @return milliseconds to wait, or -1 if we will wait indefinitely
*/
public long getConnectTimeout() {
return _connectTimeout;
}
public long getConnectTimeout();
/**
* Define how long we will wait for the ACK from a SYN, in milliseconds.
*
*/
public void setConnectTimeout(long ms) {
_connectTimeout = ms;
}
public void setConnectTimeout(long ms);
/**
* 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 _readTimeout;
}
public long getReadTimeout();
/**
* 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 void setReadTimeout(long ms) {
_readTimeout = ms;
}
public void setReadTimeout(long ms);
/**
* How much data will we accept that hasn't been written out yet. After
@@ -76,9 +47,7 @@ public class I2PSocketOptions {
*
* @return buffer size limit, in bytes
*/
public int getMaxBufferSize() {
return _maxBufferSize;
}
public int getMaxBufferSize();
/**
* How much data will we accept that hasn't been written out yet. After
@@ -87,9 +56,7 @@ public class I2PSocketOptions {
* less than or equal to zero, there is no limit (warning: can eat ram)
*
*/
public void setMaxBufferSize(int numBytes) {
_maxBufferSize = numBytes;
}
public void setMaxBufferSize(int numBytes);
/**
* What is the longest we'll block on the output stream while waiting
@@ -97,9 +64,7 @@ public class I2PSocketOptions {
* InterruptedIOException. If this is less than or equal to zero, there
* is no timeout.
*/
public long getWriteTimeout() {
return _writeTimeout;
}
public long getWriteTimeout();
/**
* What is the longest we'll block on the output stream while waiting
@@ -107,7 +72,5 @@ public class I2PSocketOptions {
* InterruptedIOException. If this is less than or equal to zero, there
* is no timeout.
*/
public void setWriteTimeout(long ms) {
_writeTimeout = ms;
}
public void setWriteTimeout(long ms);
}

View File

@@ -0,0 +1,136 @@
package net.i2p.client.streaming;
import java.util.Iterator;
import java.util.Properties;
/**
* Define the configuration for streaming and verifying data on the socket.
*
*/
class I2PSocketOptionsImpl implements I2PSocketOptions {
private long _connectTimeout;
private long _readTimeout;
private long _writeTimeout;
private int _maxBufferSize;
public static final int DEFAULT_BUFFER_SIZE = 1024*64;
public static final int DEFAULT_WRITE_TIMEOUT = 60*1000;
public static final int DEFAULT_CONNECT_TIMEOUT = 60*1000;
public I2PSocketOptionsImpl() {
this(System.getProperties());
}
public I2PSocketOptionsImpl(I2PSocketOptions opts) {
this(System.getProperties());
if (opts != null) {
_connectTimeout = opts.getConnectTimeout();
_readTimeout = opts.getReadTimeout();
_writeTimeout = opts.getWriteTimeout();
_maxBufferSize = opts.getMaxBufferSize();
}
}
public I2PSocketOptionsImpl(Properties opts) {
init(opts);
}
protected void init(Properties opts) {
_maxBufferSize = getInt(opts, PROP_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
_connectTimeout = getInt(opts, PROP_CONNECT_TIMEOUT, DEFAULT_CONNECT_TIMEOUT);
_readTimeout = getInt(opts, PROP_READ_TIMEOUT, -1);
_writeTimeout = getInt(opts, PROP_WRITE_TIMEOUT, DEFAULT_WRITE_TIMEOUT);
}
protected int getInt(Properties opts, String name, int defaultVal) {
if (opts == null) return defaultVal;
String val = opts.getProperty(name);
if (val == null) {
return defaultVal;
} else {
try {
return Integer.parseInt(val);
} catch (NumberFormatException nfe) {
return defaultVal;
}
}
}
/**
* How long we will wait for the ACK from a SYN, in milliseconds.
*
* @return milliseconds to wait, or -1 if we will wait indefinitely
*/
public long getConnectTimeout() {
return _connectTimeout;
}
/**
* Define how long we will wait for the ACK from a SYN, in milliseconds.
*
*/
public void setConnectTimeout(long ms) {
_connectTimeout = ms;
}
/**
* 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 _readTimeout;
}
/**
* 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 void setReadTimeout(long ms) {
_readTimeout = ms;
}
/**
* How much data will we accept that hasn't been written out yet. After
* this amount has been exceeded, subsequent .write calls will block until
* either some data is removed or the connection is closed. If this is
* less than or equal to zero, there is no limit (warning: can eat ram)
*
* @return buffer size limit, in bytes
*/
public int getMaxBufferSize() {
return _maxBufferSize;
}
/**
* How much data will we accept that hasn't been written out yet. After
* this amount has been exceeded, subsequent .write calls will block until
* either some data is removed or the connection is closed. If this is
* less than or equal to zero, there is no limit (warning: can eat ram)
*
*/
public void setMaxBufferSize(int numBytes) {
_maxBufferSize = numBytes;
}
/**
* What is the longest we'll block on the output stream while waiting
* for the data to flush. If this value is exceeded, the write() throws
* InterruptedIOException. If this is less than or equal to zero, there
* is no timeout.
*/
public long getWriteTimeout() {
return _writeTimeout;
}
/**
* What is the longest we'll block on the output stream while waiting
* for the data to flush. If this value is exceeded, the write() throws
* InterruptedIOException. If this is less than or equal to zero, there
* is no timeout.
*/
public void setWriteTimeout(long ms) {
_writeTimeout = ms;
}
}

View File

@@ -118,11 +118,15 @@ public class StreamSinkServer {
try {
InputStream in = _sock.getInputStream();
byte buf[] = new byte[4096];
long written = 0;
int read = 0;
while ( (read = in.read(buf)) != -1) {
_fos.write(buf, 0, read);
written += read;
if (_log.shouldLog(Log.DEBUG))
_log.debug("read and wrote " + read);
}
_log.error("Got EOF from client socket");
_log.error("Got EOF from client socket [written=" + written + "]");
} catch (IOException ioe) {
_log.error("Error writing the sink", ioe);
} finally {
@@ -143,6 +147,9 @@ public class StreamSinkServer {
public static void main(String args[]) {
StreamSinkServer server = null;
switch (args.length) {
case 0:
server = new StreamSinkServer("dataDir", "server.key", "localhost", 10001);
break;
case 2:
server = new StreamSinkServer(args[0], args[1]);
break;

View File

@@ -217,7 +217,10 @@ public class ConfigNetHandler extends FormHandler {
}
if ( (_port != null) && (_port.length() > 0) ) {
String oldPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_TCP_PORT);
if ( (oldPort == null) || (!oldPort.equalsIgnoreCase(_port)) ) {
if ( (oldPort == null) && (_port.equals("8887")) ) {
// still on default.. noop
} else if ( (oldPort == null) || (!oldPort.equalsIgnoreCase(_port)) ) {
// its not the default OR it has changed
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_TCP_PORT, _port);
addFormNotice("Updating TCP port from " + oldPort + " to " + _port);
restartRequired = true;

View File

@@ -120,15 +120,17 @@ public class ConfigNetHelper {
private static String getBurstFactor(int numSeconds, String name) {
StringBuffer buf = new StringBuffer(256);
buf.append("<select name=\"").append(name).append("\">\n");
for (int i = 1; i < 10; i++) {
boolean found = false;
for (int i = 10; i <= 60; i += 10) {
buf.append("<option value=\"").append(i).append("\" ");
if ( (i == numSeconds) || (i == 10) )
if (i == numSeconds) {
buf.append("selected ");
found = true;
} else if ( (i == 60) && (!found) ) {
buf.append("selected ");
}
buf.append(">");
if (i == 1)
buf.append("1 second (no burst)</option>\n");
else
buf.append(i).append(" seconds</option>\n");
buf.append(i).append(" seconds</option>\n");
}
buf.append("</select>\n");
return buf.toString();

View File

@@ -1,10 +1,17 @@
package net.i2p.router.web;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Iterator;
import java.util.Properties;
import java.util.TreeMap;
import net.i2p.data.DataHelper;
import net.i2p.router.ClientTunnelSettings;
import net.i2p.router.Router;
import net.i2p.apps.systray.SysTray;
import net.i2p.apps.systray.UrlLauncher;
import org.tanukisoftware.wrapper.WrapperManager;
/**
@@ -86,6 +93,12 @@ public class ConfigServiceHandler extends FormHandler {
} catch (Throwable t) {
addFormError("Warning: unable to contact the systray manager - " + t.getMessage());
}
} else if ("View console on startup".equals(_action)) {
browseOnStartup(true);
addFormNotice("Console is to be shown on startup");
} else if ("Do not view console on startup".equals(_action)) {
browseOnStartup(false);
addFormNotice("Console is not to be shown on startup");
} else {
addFormNotice("Blah blah blah. whatever. I'm not going to " + _action);
}
@@ -107,4 +120,81 @@ public class ConfigServiceHandler extends FormHandler {
addFormError("Warning: unable to remove the service - " + ioe.getMessage());
}
}
private final static String NL = System.getProperty("line.separator");
private void browseOnStartup(boolean shouldLaunchBrowser) {
File f = new File("clients.config");
Properties p = new Properties();
try {
DataHelper.loadProps(p, f);
int i = 0;
int launchIndex = -1;
while (true) {
String className = p.getProperty("clientApp." + i + ".main");
if (className == null) break;
if (UrlLauncher.class.getName().equals(className)) {
launchIndex = i;
break;
}
i++;
}
if ((launchIndex >= 0) && shouldLaunchBrowser)
return;
if ((launchIndex < 0) && !shouldLaunchBrowser)
return;
if (shouldLaunchBrowser) {
p.setProperty("clientApp." + i + ".main", UrlLauncher.class.getName());
p.setProperty("clientApp." + i + ".name", "BrowserLauncher");
p.setProperty("clientApp." + i + ".args", "http://localhost:7657/index.jsp");
p.setProperty("clientApp." + i + ".delay", "5");
} else {
p.remove("clientApp." + launchIndex + ".main");
p.remove("clientApp." + launchIndex + ".name");
p.remove("clientApp." + launchIndex + ".args");
p.remove("clientApp." + launchIndex + ".onBoot");
p.remove("clientApp." + launchIndex + ".delay");
i = launchIndex + 1;
while (true) {
String main = p.getProperty("clientApp." + i + ".main");
String name = p.getProperty("clientApp." + i + ".name");
String args = p.getProperty("clientApp." + i + ".args");
String boot = p.getProperty("clientApp." + i + ".onBoot");
String delay= p.getProperty("clientApp." + i + ".delay");
if (main == null) break;
p.setProperty("clientApp." + (i-1) + ".main", main);
p.setProperty("clientApp." + (i-1) + ".name", name);
p.setProperty("clientApp." + (i-1) + ".args", args);
if (boot != null)
p.setProperty("clientApp." + (i-1) + ".onBoot", boot);
if (delay != null)
p.setProperty("clientApp." + (i-1) + ".delay", delay);
p.remove("clientApp." + i + ".main");
p.remove("clientApp." + i + ".name");
p.remove("clientApp." + i + ".args");
p.remove("clientApp." + i + ".onBoot");
p.remove("clientApp." + i + ".delay");
i++;
}
}
TreeMap sorted = new TreeMap(p);
FileWriter out = new FileWriter(f);
for (Iterator iter = sorted.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String val = (String)sorted.get(name);
out.write(name + "=" + val + NL);
}
out.close();
} catch (IOException ioe) {
addFormError("Error updating the client config");
}
}
}

View File

@@ -10,6 +10,7 @@ import net.i2p.util.FileUtil;
public class ContentHelper {
private String _page;
private int _maxLines;
private boolean _startAtBeginning;
private RouterContext _context;
/**
* Configure this bean to query a particular router context
@@ -28,6 +29,10 @@ public class ContentHelper {
public ContentHelper() {}
public void setPage(String page) { _page = page; }
public void setStartAtBeginning(String moo) {
_startAtBeginning = Boolean.valueOf(""+moo).booleanValue();
}
public void setMaxLines(String lines) {
if (lines != null) {
try {
@@ -40,14 +45,14 @@ public class ContentHelper {
}
}
public String getContent() {
String str = FileUtil.readTextFile(_page, _maxLines);
String str = FileUtil.readTextFile(_page, _maxLines, _startAtBeginning);
if (str == null)
return "";
else
return str;
}
public String getTextContent() {
String str = FileUtil.readTextFile(_page, _maxLines);
String str = FileUtil.readTextFile(_page, _maxLines, _startAtBeginning);
if (str == null)
return "";
else

View File

@@ -42,7 +42,7 @@ public class LogsHelper {
}
public String getServiceLogs() {
String str = FileUtil.readTextFile("wrapper.log", 500);
String str = FileUtil.readTextFile("wrapper.log", 500, false);
if (str == null)
return "";
else

View File

@@ -0,0 +1,129 @@
package net.i2p.router.web;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Handler to deal with reseed requests. This reseed from the URL
* http://dev.i2p.net/i2pdb/ unless the java env property "i2p.reseedURL" is
* set. It always writes to ./netDb/, so don't mess with that.
*
*/
public class ReseedHandler {
private static ReseedRunner _reseedRunner = new ReseedRunner();
public void setReseedNonce(String nonce) {
if (nonce == null) return;
if (nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.nonce")) ||
nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.noncePrev"))) {
synchronized (_reseedRunner) {
if (_reseedRunner.isRunning()) {
return;
} else {
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
reseed.start();
}
}
}
}
public static class ReseedRunner implements Runnable {
private boolean _isRunning;
public ReseedRunner() { _isRunning = false; }
public boolean isRunning() { return _isRunning; }
public void run() {
_isRunning = true;
reseed();
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false");
_isRunning = false;
}
}
private static final String DEFAULT_SEED_URL = "http://dev.i2p.net/i2pdb/";
/**
* Reseed has been requested, so lets go ahead and do it. Fetch all of
* the routerInfo-*.dat files from the specified URL (or the default) and
* save them into this router's netDb dir.
*
*/
private static void reseed() {
String seedURL = System.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
seedURL = DEFAULT_SEED_URL;
try {
URL dir = new URL(seedURL);
String content = new String(readURL(dir));
Set urls = new HashSet();
int cur = 0;
while (true) {
int start = content.indexOf("href=\"routerInfo-", cur);
if (start < 0)
break;
int end = content.indexOf(".dat\">", start);
String name = content.substring(start+"href=\"routerInfo-".length(), end);
urls.add(name);
cur = end + 1;
}
int fetched = 0;
int errors = 0;
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
try {
fetchSeed(seedURL, (String)iter.next());
fetched++;
} catch (Exception e) {
errors++;
}
}
} catch (Throwable t) {
I2PAppContext.getGlobalContext().logManager().getLog(ReseedHandler.class).error("Error reseeding", t);
}
}
private static void fetchSeed(String seedURL, String peer) throws Exception {
URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + "routerInfo-" + peer + ".dat");
byte data[] = readURL(url);
writeSeed(peer, data);
}
private static byte[] readURL(URL url) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
URLConnection con = url.openConnection();
InputStream in = con.getInputStream();
byte buf[] = new byte[1024];
while (true) {
int read = in.read(buf);
if (read < 0)
break;
baos.write(buf, 0, read);
}
in.close();
return baos.toByteArray();
}
private static void writeSeed(String name, byte data[]) throws Exception {
String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb");
File netDbDir = new File(dirName);
if (!netDbDir.exists()) {
boolean ok = netDbDir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat"));
fos.write(data);
fos.close();
}
}

View File

@@ -28,44 +28,58 @@
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigNetHandler.nonce")%>" />
<input type="hidden" name="action" value="blah" />
<b>External hostname/IP address:</b>
<input name="hostname" type="text" size="32" value="<jsp:getProperty name="nethelper" property="hostname" />" />
<input type="submit" name="guesshost" value="Guess" /><br />
<b>Externally reachable TCP port:</b>
TCP port:
<input name="port" type="text" size="4" value="<jsp:getProperty name="nethelper" property="port" />" /> <br />
<i>The hostname/IP address and TCP port must be reachable from the outside world. If
you are behind a firewall or NAT, this means you must poke a hole for this port. If
you are using DHCP and do not have a static IP address, you should either use a service like
<a href="http://dyndns.org/">dyndns</a> or leave the hostname blank. If you leave it blank,
your router will autodetect the 'correct' IP address by asking a peer (and unconditionally
believing them if the address is routable and you don't have any established connections yet).
The "guess" functionality makes an HTTP request
to <a href="http://www.whatismyip.com/">www.whatismyip.com</a>.</i>
<hr />
<b>Enable internal time synchronization?</b> <input type="checkbox" <jsp:getProperty name="nethelper" property="enableTimeSyncChecked" /> name="enabletimesync" /><br />
<i>If disabled, your machine <b>must</b> be NTP synchronized - your clock must always
be within a few seconds of "correct".</i>
<b>You must poke a hole in your firewall or NAT (if applicable) so that you can receive inbound TCP
connections on it.</b> Nothing will work if you don't. Sorry. We know how to make it so
this restriction won't be necessary, but its later on in the
<a href="http://www.i2p.net/roadmap">roadmap</a> and we only have so many coder-hours (but if you want
to help, please <a href="http://www.i2p.net/getinvolved">get involved!</a>)
<hr />
<b>Bandwidth limiter</b><br />
<b>Inbound rate</b>:
Inbound rate:
<input name="inboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundRate" />" /> KBytes per second<br />
<b>Inbound burst duration:</b>
Inbound burst duration:
<jsp:getProperty name="nethelper" property="inboundBurstFactorBox" /><br />
<b>Outbound rate:</b>
Outbound rate:
<input name="outboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundRate" />" /> KBytes per second<br />
<b>Outbound burst duration:</b>
Outbound burst duration:
<jsp:getProperty name="nethelper" property="outboundBurstFactorBox" /><br />
<i>A negative rate means there is no limit</i><br />
<hr />
<b>Reseed</b> (from <input name="reseedfrom" type="text" size="40" value="http://dev.i2p.net/i2pdb/" />):
<input type="submit" name="reseed" value="now" /><br />
<i>May take some time to download the peer references</i>
Enable internal time synchronization? <input type="checkbox" <jsp:getProperty name="nethelper" property="enableTimeSyncChecked" /> name="enabletimesync" /><br />
<i>If disabled, your machine <b>must</b> be NTP synchronized - your clock must always
be within a few seconds of "correct". You will need to be able to send outbound UDP
packets on port 123 to one of the pool.ntp.org machines (or some other SNTP server).</i>
<hr />
<input type="submit" name="save" value="Save changes" /> <input type="reset" value="Cancel" /><br />
<i>Changing the hostname or TCP port will force a 'soft restart' - dropping your connections
and clients as if the router was stopped and restarted. <b>Please be patient</b> - it may take
<i>Changing the TCP port will force a 'soft restart' - dropping your connections and clients as
if the router was stopped and restarted. <b>Please be patient</b> - it may take
a few seconds to complete.</i>
</form>
<hr />
<b>Advanced network config:</b>
<p>
There are two other network settings, but no one reads this text so there's no reason
to tell you about them. In case you actually do read this, here's the deal: by default,
I2P will attempt to guess your IP address by having whomever it talks to tell it what
address they think you are. If and only if you have no working TCP connections and you
have not overridden the IP address, your router will believe them. If that doesn't sound
ok to you, thats fine - go to the <a href="/configadvanced.jsp">advanced config</a> page
and add "i2np.tcp.hostname=yourHostname", then go to the
<a href="/configservice.jsp">service</a> page and do a graceful restart. We used to make
people enter a hostname/IP address on this page, but too many people got it wrong ;)</p>
<p>The other advanced network option has to do with reseeding - you should never need to
reseed your router as long as you can find at least one other peer on the network. However,
when you do need to reseed, a link will show up on the left hand side which will
fetch all of the routerInfo-* files from http://dev.i2p.net/i2pdb/. That URL is just an
apache folder pointing at the netDb/ directory of a router - anyone can run one, and you can
configure your router to seed off an alternate URL by adding the java environmental property
"i2p.reseedURL=someURL" (e.g. java -Di2p.reseedURL=http://dev.i2p.net/i2pdb/ ...). You can
also do it manually by getting routerInfo-*.dat files from someone (a friend, someone on IRC,
whatever) and saving them to your netDb/ directory.</p>
</div>
</body>

View File

@@ -29,10 +29,12 @@
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigAdvancedHandler.nonce")%>" />
<input type="hidden" name="action" value="blah" />
<textarea rows="20" cols="100" name="config"><jsp:getProperty name="advancedhelper" property="settings" /></textarea><br />
<input type="submit" name="shouldsave" value="Apply" /> <input type="reset" value="Cancel" /> <br />
<input type="submit" name="shouldsave" value="Apply" /> <input type="reset" value="Cancel" /><!-- <br />
<b>Force restart:</b> <input type="checkbox" name="restart" value="force" /> <i>(specify this
if the changes made above require the router to reset itself - e.g. you are updating TCP ports
or hostnames, etc)</i>
or hostnames, etc)</i>-->
If you are changing any of the I2NP settings, you should go to the
<a href="configservice.jsp">service config</a> page and do a graceful restart after saving.
</form>
</div>

View File

@@ -67,6 +67,14 @@
please select the following option and review the thread dumped to
<a href="logs.jsp#servicelogs">wrapper.log</a>.</p>
<input type="submit" name="action" value="Dump threads" />
<h4>Launch browser on router startup?</h4>
<p>I2P's main configuration interface is this web console, so for your convenience
I2P can launch a web browser pointing at
<a href="http://localhost:7657/index.jsp">http://localhost:7657/index.jsp</a> whenever
the router starts up.</p>
<input type="submit" name="action" value="View console on startup" />
<input type="submit" name="action" value="Do not view console on startup" />
</form>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -37,7 +37,9 @@ by their binary code license. This product includes software developed by the A
lets you tunnel normal TCP/IP traffic over I2P (such as the eepproxy and the irc proxy).</p>
<p>The router by default also includes human's public domain <a href="http://www.i2p.net/sam">SAM</a> bridge,
which other client applications (such as aum's <a href="http://stasher.i2p/">stasher</a>) can use. For
which other client applications (such the <a href="http://duck.i2p/i2p-bt/">bittorrent port</a>) can use.
There is also an optimized library for doing large number calculations - jbigi - which in turn uses the
LGPL licensed <a href="http://swox.com/gmp/">GMP</a> library, tuned for various PC architectures. For
details on other applications available, as well as their licenses, please see the
<a href="http://www.i2p.net/licenses">license policy</a>. Source for the I2P code and most bundled
client applications can be found on our <a href="http://www.i2p.net/download">download page</a>, and is
@@ -47,7 +49,14 @@ in <a href="http://www.i2p.net/cvs">cvs</a>.</p>
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="contenthelper" scope="request" />
<jsp:setProperty name="contenthelper" property="page" value="history.txt" />
<jsp:setProperty name="contenthelper" property="maxLines" value="500" />
<jsp:setProperty name="contenthelper" property="startAtBeginning" value="true" />
<jsp:getProperty name="contenthelper" property="textContent" />
<p>
A more complete list of updates can be found
<a href="http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/history.txt?rev=HEAD">online</a>
(<a href="http://dev.i2p/cgi-bin/cvsweb.cgi/i2p/history.txt?rev=HEAD">anonymously</a>)
</p>
</div>
</body>

View File

@@ -2,6 +2,9 @@
<jsp:useBean class="net.i2p.router.web.SummaryHelper" id="helper" scope="request" />
<jsp:setProperty name="helper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<jsp:useBean class="net.i2p.router.web.ReseedHandler" id="reseed" scope="request" />
<jsp:setProperty name="reseed" property="*" />
<div class="routersummary">
<u><b>General</b></u><br />
<b>Ident:</b> <jsp:getProperty name="helper" property="ident" /><br />
@@ -16,8 +19,27 @@
<b>High capacity:</b> <jsp:getProperty name="helper" property="highCapacityPeers" /><br />
<b>Well integrated:</b> <jsp:getProperty name="helper" property="wellIntegratedPeers" /><br />
<b>Failing:</b> <jsp:getProperty name="helper" property="failingPeers" /><br />
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br />
<hr />
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br /><%
if (helper.getActivePeers() <= 0) {
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
}
if (helper.getActiveProfiles() <= 4) { // 4 is the min fallback
if ("true".equals(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false"))) {
out.print(" <i>reseeding</i>");
} else {
long nonce = new java.util.Random().nextLong();
String prev = System.getProperty("net.i2p.router.web.ReseedHandler.nonce");
if (prev != null) System.setProperty("net.i2p.router.web.ReseedHandler.noncePrev", prev);
System.setProperty("net.i2p.router.web.ReseedHandler.nonce", nonce+"");
String uri = request.getRequestURI();
if (uri.indexOf('?') > 0)
uri = uri + "&reseedNonce=" + nonce;
else
uri = uri + "?reseedNonce=" + nonce;
out.print(" <a href=\"" + uri + "\">reseed</a>");
}
}
%><hr />
<u><b>Bandwidth in/out</b></u><br />
<b>1m:</b> <jsp:getProperty name="helper" property="inboundMinuteKBps" />/<jsp:getProperty name="helper" property="outboundMinuteKBps" />KBps<br />

View File

@@ -4,6 +4,7 @@ I need to do these things:
* Write an instruction manual
* Make dest a dynamic string
* Change SAM parser to use a hashmap
* Switch to GNU Autoconf (?)
Anyone can help with these things:

View File

@@ -1,7 +1,10 @@
/* vi:set ts=4: */
v1.30
* Improved WIRETAP to do more logging
* Added "pinger.sh" shell script example for using i2p-ping
* Added SAM_BAD_STYLE error
* Added exit values for i2p-ping from xolo
v1.25 2004-07-31
* Created I2P-Ping, a new example program (it's a clone of I2Ping). Works

View File

@@ -38,9 +38,11 @@ extern "C" {
#include <stddef.h>
#include <stdint.h>
/*
* Lengths
*/
/* The maximum length a SAM command can be */
#define SAM_CMD_LEN 128
/* The maximum size of a single datagram packet */
@@ -49,18 +51,22 @@ extern "C" {
#define SAM_LOGMSG_LEN 256
/* The longest `name' arg for the naming lookup callback */
#define SAM_NAME_LEN 256
/* The max size of a single stream packet */
/* The maximum size of a single stream packet */
#define SAM_STREAM_PAYLOAD_MAX (32 * 1024)
/* The length of a base 64 public key - it's actually 516, but +1 for '\0' */
#define SAM_PUBKEY_LEN 517
/* A public key SAM command's length */
/* The maximum length of a SAM command with a public key */
#define SAM_PKCMD_LEN (SAM_PUBKEY_LEN + SAM_CMD_LEN)
/* The maximum size of a single raw packet */
#define SAM_RAW_PAYLOAD_MAX (32 * 1024)
/* The maximum length a SAM non-data reply can be */
#define SAM_REPLY_LEN 1024
/*
* Shorten some standard variable types
* Some LibSAM variable types
*/
typedef signed char schar_t;
typedef unsigned char uchar_t;
typedef unsigned int uint_t;
@@ -102,57 +108,62 @@ typedef enum { /* see sam_strerror() for detailed descriptions of these */
SAM_TOO_BIG
} samerr_t;
/*
* Public functions
*/
/* SAM controls - sessions */
extern sam_sess_t *sam_session_init(sam_sess_t *session);
extern void sam_session_free(sam_sess_t **session);
sam_sess_t *sam_session_init(sam_sess_t *session);
void sam_session_free(sam_sess_t **session);
/* SAM controls - connection */
extern bool sam_close(sam_sess_t *session);
extern samerr_t sam_connect(sam_sess_t *session, const char *samhost,
uint16_t samport, const char *destname, sam_conn_t style,
uint_t tunneldepth);
bool sam_close(sam_sess_t *session);
samerr_t sam_connect(sam_sess_t *session, const char *samhost,
uint16_t samport, const char *destname, sam_conn_t style,
uint_t tunneldepth);
/* SAM controls - utilities */
extern void sam_naming_lookup(sam_sess_t *session, const char *name);
extern bool sam_read_buffer(sam_sess_t *session);
extern const char *sam_strerror(samerr_t code);
void sam_naming_lookup(sam_sess_t *session, const char *name);
bool sam_read_buffer(sam_sess_t *session);
const char *sam_strerror(samerr_t code);
/* SAM controls - callbacks */
extern void (*sam_diedback)(sam_sess_t *session);
extern void (*sam_logback)(char *str);
extern void (*sam_namingback)(char *name, sam_pubkey_t pubkey,
samerr_t result);
void (*sam_diedback)(sam_sess_t *session);
void (*sam_logback)(char *str);
void (*sam_namingback)(char *name, sam_pubkey_t pubkey, samerr_t result);
/* Stream commands */
extern void sam_stream_close(sam_sess_t *session, sam_sid_t stream_id);
extern sam_sid_t sam_stream_connect(sam_sess_t *session,
const sam_pubkey_t dest);
extern samerr_t sam_stream_send(sam_sess_t *session, sam_sid_t stream_id,
const void *data, size_t size);
void sam_stream_close(sam_sess_t *session, sam_sid_t stream_id);
sam_sid_t sam_stream_connect(sam_sess_t *session, const sam_pubkey_t dest);
samerr_t sam_stream_send(sam_sess_t *session, sam_sid_t stream_id,
const void *data, size_t size);
/* Stream commands - callbacks */
extern void (*sam_closeback)(sam_sess_t *session, sam_sid_t stream_id,
samerr_t reason);
extern void (*sam_connectback)(sam_sess_t *session, sam_sid_t stream_id,
sam_pubkey_t dest);
extern void (*sam_databack)(sam_sess_t *session, sam_sid_t stream_id,
void *data, size_t size);
extern void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
samerr_t result);
void (*sam_closeback)(sam_sess_t *session, sam_sid_t stream_id,
samerr_t reason);
void (*sam_connectback)(sam_sess_t *session, sam_sid_t stream_id,
sam_pubkey_t dest);
void (*sam_databack)(sam_sess_t *session, sam_sid_t stream_id,
void *data, size_t size);
void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
samerr_t result);
/* Stream send queue (experimental) */
extern void sam_sendq_add(sam_sess_t *session, sam_sid_t stream_id,
sam_sendq_t **sendq, const void *data, size_t dsize);
extern void sam_sendq_flush(sam_sess_t *session, sam_sid_t stream_id,
sam_sendq_t **sendq);
void sam_sendq_add(sam_sess_t *session, sam_sid_t stream_id,
sam_sendq_t **sendq, const void *data, size_t dsize);
void sam_sendq_flush(sam_sess_t *session, sam_sid_t stream_id,
sam_sendq_t **sendq);
/* Datagram commands */
extern samerr_t sam_dgram_send(sam_sess_t *session, const sam_pubkey_t dest,
const void *data, size_t size);
samerr_t sam_dgram_send(sam_sess_t *session, const sam_pubkey_t dest,
const void *data, size_t size);
/* Datagram commands - callbacks */
extern void (*sam_dgramback)(sam_sess_t *session, sam_pubkey_t dest,
void *data, size_t size);
void (*sam_dgramback)(sam_sess_t *session, sam_pubkey_t dest, void *data,
size_t size);
/* Raw commands */
samerr_t sam_raw_send(sam_sess_t *session, const sam_pubkey_t dest,
const void *data, size_t size);
/* Raw commands - callbacks */
void (*sam_rawback)(sam_sess_t *session, void *data, size_t size);
#ifdef __cplusplus
}

View File

@@ -55,28 +55,40 @@ static ssize_t sam_write(sam_sess_t *session, const void *buf, size_t n);
* Callback functions
* Note: if you add a new callback be sure to check for non-NULL in sam_connect
*/
/* a peer closed the connection */
void (*sam_closeback)(sam_sess_t *session, sam_sid_t stream_id, samerr_t reason)
= NULL;
/* a peer connected to us */
void (*sam_connectback)(sam_sess_t *session, sam_sid_t stream_id,
sam_pubkey_t dest) = NULL;
/* a peer sent some stream data (`data' MUST be freed) */
void (*sam_databack)(sam_sess_t *session, sam_sid_t stream_id, void *data,
size_t size) = NULL;
/* a peer sent some datagram data (`data' MUST be freed) */
void (*sam_dgramback)(sam_sess_t *session, sam_pubkey_t dest, void *data,
size_t size) = NULL;
/* we lost the connection to the SAM host */
void (*sam_diedback)(sam_sess_t *session) = NULL;
/* logging callback */
void (*sam_logback)(char *str) = NULL;
/* naming lookup reply - `pubkey' will be NULL if `result' isn't SAM_OK */
void (*sam_namingback)(char *name, sam_pubkey_t pubkey, samerr_t result) = NULL;
/* our connection to a peer has completed */
void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
samerr_t result) = NULL;
/* a peer sent some raw data (`data' MUST be freed) */
void (*sam_rawback)(sam_sess_t *session, void *data, size_t size) = NULL;
/*
* Closes the connection to the SAM host
*
@@ -155,7 +167,11 @@ samerr_t sam_connect(sam_sess_t *session, const char *samhost, uint16_t samport,
return SAM_CALLBACKS_UNSET;
}
} else if (style == SAM_RAW) {
abort(); /* not implemented yet */
if (sam_diedback == NULL || sam_logback == NULL
|| sam_namingback == NULL || sam_rawback == NULL) {
SAMLOGS("Please set callback functions before connecting");
return SAM_CALLBACKS_UNSET;
}
} else {
SAMLOGS("Unknown connection style");
return SAM_BAD_STYLE;
@@ -197,7 +213,7 @@ samerr_t sam_connect(sam_sess_t *session, const char *samhost, uint16_t samport,
* data - the data we're sending
* size - the size of the data
*
* Returns: true on success, false on failure
* Returns: SAM_OK on success
*/
samerr_t sam_dgram_send(sam_sess_t *session, const sam_pubkey_t dest,
const void *data, size_t size)
@@ -295,6 +311,7 @@ static void sam_parse(sam_sess_t *session, char *s)
#define SAM_NAMING_REPLY_OK "NAMING REPLY RESULT=OK"
#define SAM_NAMING_REPLY_IK "NAMING REPLY RESULT=INVALID_KEY"
#define SAM_NAMING_REPLY_KNF "NAMING REPLY RESULT=KEY_NOT_FOUND"
#define SAM_RAW_RECEIVED_REPLY "RAW RECEIVED"
#define SAM_STREAM_CLOSED_REPLY "STREAM CLOSED"
#define SAM_STREAM_CONNECTED_REPLY "STREAM CONNECTED"
#define SAM_STREAM_RECEIVED_REPLY "STREAM RECEIVED"
@@ -305,6 +322,10 @@ static void sam_parse(sam_sess_t *session, char *s)
#define SAM_STREAM_STATUS_REPLY_IK "STREAM STATUS RESULT=INVALID_KEY"
#define SAM_STREAM_STATUS_REPLY_TO "STREAM STATUS RESULT=TIMEOUT"
/*
* TODO: add raw parsing
*/
if (strncmp(s, SAM_DGRAM_RECEIVED_REPLY,
strlen(SAM_DGRAM_RECEIVED_REPLY)) == 0) {
char *p;
@@ -518,6 +539,42 @@ static void sam_parse(sam_sess_t *session, char *s)
return;
}
/*
* Sends data to a destination in a raw packet
*
* dest - base 64 destination of who we're sending to
* data - the data we're sending
* size - the size of the data
*
* Returns: SAM_OK on success
*/
samerr_t sam_raw_send(sam_sess_t *session, const sam_pubkey_t dest,
const void *data, size_t size)
{
assert(session != NULL);
char cmd[SAM_PKCMD_LEN];
if (size < 1 || size > SAM_RAW_PAYLOAD_MAX) {
#ifdef NO_Z_FORMAT
SAMLOG("Invalid data send size (%u bytes)", size);
#else
SAMLOG("Invalid data send size (%zu bytes)", size);
#endif
return SAM_TOO_BIG;
}
#ifdef NO_Z_FORMAT
snprintf(cmd, sizeof cmd, "RAW SEND DESTINATION=%s SIZE=%u\n",
dest, size);
#else
snprintf(cmd, sizeof cmd, "RAW SEND DESTINATION=%s SIZE=%zu\n",
dest, size);
#endif
sam_write(session, cmd, strlen(cmd));
sam_write(session, data, size);
return SAM_OK;
}
/*
* Reads and callbacks everything in the SAM network buffer until it is clear
*
@@ -598,7 +655,7 @@ static ssize_t sam_read1(sam_sess_t *session, char *buf, size_t n)
if (*p == '\n') { /* end of SAM response */
*p = '\0';
#if SAM_WIRETAP
printf("<<<< %s\n", buf);
printf("*RR* %s\n", buf);
#endif
return n - nleft;
}
@@ -663,7 +720,16 @@ static ssize_t sam_read2(sam_sess_t *session, void *buf, size_t n)
p += nread;
}
#if SAM_WIRETAP
printf("<<<< (read2() %d bytes)\n", n);
p = buf;
printf("*RR* ");
for (size_t x = 0; x < n; x++) {
if (isprint(((uchar_t*)p)[x]))
printf("%c,", ((uchar_t*)p)[x]);
else
printf("%03d,", ((uint8_t*)p)[x]);
}
printf("\n");
printf("*RR* (read2() read %d bytes)\n", n);
#endif
assert(nleft == 0);/* <---\ */
return n - nleft; /* should be equal to initial n */
@@ -689,7 +755,7 @@ static bool sam_readable(sam_sess_t *session)
FD_ZERO(&rset);
FD_SET(session->sock, &rset);
tv.tv_sec = 0;
tv.tv_usec = 10;
tv.tv_usec = 0;
rc = select(session->sock + 1, &rset, NULL, NULL, &tv);
if (rc == 0)
return false;
@@ -1334,6 +1400,7 @@ static ssize_t sam_write(sam_sess_t *session, const void *buf, size_t n)
}
#if SAM_WIRETAP
const uchar_t *cp = buf;
printf("*WW* ");
for (size_t x = 0; x < n; x++) {
if (isprint(cp[x]))
printf("%c,", cp[x]);
@@ -1365,7 +1432,7 @@ static ssize_t sam_write(sam_sess_t *session, const void *buf, size_t n)
p += nwritten;
}
#if SAM_WIRETAP
printf(">>>> (write() %d bytes)\n", n);
printf("*WW* (write() wrote %d bytes)\n", n);
#endif
return n;

View File

@@ -250,6 +250,7 @@ public class SAMBridge implements Runnable {
}
public void run() {
if (serverSocket == null) return;
try {
while (acceptConnections) {
Socket s = serverSocket.accept();
@@ -280,8 +281,11 @@ public class SAMBridge implements Runnable {
if (_log.shouldLog(Log.ERROR))
_log.error("SAM Error sending error reply", ioe);
}
s.close();
}
try { s.close(); } catch (IOException ioe) {}
} catch (Exception ee) {
try { s.close(); } catch (IOException ioe) {}
_log.log(Log.CRIT, "Unexpected error handling SAM connection", ee);
}
}
} catch (Exception e) {
if (_log.shouldLog(Log.ERROR))
@@ -290,7 +294,8 @@ public class SAMBridge implements Runnable {
try {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Shutting down, closing server socket");
serverSocket.close();
if (serverSocket != null)
serverSocket.close();
} catch (IOException e) {}
}
}

View File

@@ -100,6 +100,14 @@ public abstract class SAMHandler implements Runnable {
socketOS.flush();
}
}
/**
* If you're crazy enough to write to the raw socket, grab the write lock
* with getWriteLock(), synchronize against it, and write to the getOut()
*
*/
protected Object getWriteLock() { return socketWLock; }
protected OutputStream getOut() { return socketOS; }
/**
* Write a string to the handler's socket. This method must

View File

@@ -152,6 +152,8 @@ public class SAMHandlerFactory {
/* Get the major protocol version from a string */
private static int getMajor(String ver) {
if ( (ver == null) || (ver.indexOf('.') < 0) )
return -1;
try {
String major = ver.substring(0, ver.indexOf("."));
return Integer.parseInt(major);
@@ -164,6 +166,8 @@ public class SAMHandlerFactory {
/* Get the minor protocol version from a string */
private static int getMinor(String ver) {
if ( (ver == null) || (ver.indexOf('.') < 0) )
return -1;
try {
String major = ver.substring(ver.indexOf(".") + 1);
return Integer.parseInt(major);

View File

@@ -180,9 +180,9 @@ public class SAMStreamSession {
Destination d = new Destination();
d.fromBase64(dest);
// FIXME: we should config I2PSocketOptions here
I2PSocketOptions opts = new I2PSocketOptions();
opts.setConnectTimeout(60 * 1000);
I2PSocketOptions opts = socketMgr.buildOptions(props);
if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
opts.setConnectTimeout(60 * 1000);
_log.debug("Connecting new I2PSocket...");
I2PSocket i2ps = socketMgr.connect(d, opts);
@@ -391,6 +391,8 @@ public class SAMStreamSession {
while (stillRunning) {
try {
i2ps = serverSocket.accept();
if (i2ps == null)
break;
_log.debug("New incoming connection");
@@ -467,6 +469,7 @@ public class SAMStreamSession {
}
try {
i2pSocketOS.write(data);
//i2pSocketOS.flush();
} catch (IOException e) {
_log.error("Error sending data through I2P socket", e);
return false;

View File

@@ -14,6 +14,7 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
@@ -685,9 +686,12 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
ByteArrayOutputStream msg = new ByteArrayOutputStream();
msg.write(("RAW RECEIVED SIZE=" + data.length
+ "\n").getBytes("ISO-8859-1"));
String msgText = "RAW RECEIVED SIZE=" + data.length + "\n";
msg.write(msgText.getBytes("ISO-8859-1"));
msg.write(data);
if (_log.shouldLog(Log.DEBUG))
_log.debug("sending to client: " + msgText);
writeBytes(msg.toByteArray());
}
@@ -716,9 +720,12 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
ByteArrayOutputStream msg = new ByteArrayOutputStream();
msg.write(("DATAGRAM RECEIVED DESTINATION=" + sender.toBase64()
+ " SIZE=" + data.length
+ "\n").getBytes("ISO-8859-1"));
String msgText = "DATAGRAM RECEIVED DESTINATION=" + sender.toBase64()
+ " SIZE=" + data.length + "\n";
msg.write(msgText.getBytes("ISO-8859-1"));
if (_log.shouldLog(Log.DEBUG))
_log.debug("sending to client: " + msgText);
msg.write(data);
writeBytes(msg.toByteArray());
@@ -759,13 +766,24 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
throw new NullPointerException("BUG! STREAM session is null!");
}
ByteArrayOutputStream msg = new ByteArrayOutputStream();
msg.write(("STREAM RECEIVED ID=" + id
+" SIZE=" + len + "\n").getBytes("ISO-8859-1"));
msg.write(data, 0, len);
writeBytes(msg.toByteArray());
String msgText = "STREAM RECEIVED ID=" + id +" SIZE=" + len + "\n";
if (_log.shouldLog(Log.DEBUG))
_log.debug("sending to client: " + msgText);
byte prefix[] = msgText.getBytes("ISO-8859-1");
// dont waste so much memory
//ByteArrayOutputStream msg = new ByteArrayOutputStream();
//msg.write(msgText.getBytes("ISO-8859-1"));
//msg.write(data, 0, len);
// writeBytes(msg.toByteArray());
Object writeLock = getWriteLock();
OutputStream out = getOut();
synchronized (writeLock) {
out.write(prefix);
out.write(data, 0, len);
out.flush();
}
}
public void notifyStreamDisconnection(int id, String result, String msg) throws IOException {

View File

@@ -53,8 +53,30 @@ public class Connection {
private I2PSocketFull _socket;
/** set to an error cause if the connection could not be established */
private String _connectionError;
private boolean _disconnectScheduled;
private long _lastReceivedOn;
private ActivityTimer _activityTimer;
/** window size when we last saw congestion */
private int _lastCongestionSeenAt;
private boolean _ackSinceCongestion;
/** Notify this on connection (or connection failure) */
private Object _connectLock;
/** how many messages have been resent and not yet ACKed? */
private int _activeResends;
private long _lifetimeBytesSent;
private long _lifetimeBytesReceived;
private long _lifetimeDupMessageSent;
private long _lifetimeDupMessageReceived;
public static final long MAX_RESEND_DELAY = 60*1000;
public static final long MIN_RESEND_DELAY = 40*1000;
/** wait up to 5 minutes after disconnection so we can ack/close packets */
public static int DISCONNECT_TIMEOUT = 5*60*1000;
/** lets be sane- no more than 32 packets in the air in each dir */
public static final int MAX_WINDOW_SIZE = 32;
public Connection(I2PAppContext ctx, ConnectionManager manager, SchedulerChooser chooser, PacketQueue queue, ConnectionPacketHandler handler) {
this(ctx, manager, chooser, queue, handler, null);
@@ -79,9 +101,17 @@ public class Connection {
_unackedPacketsReceived = 0;
_congestionWindowEnd = 0;
_highestAckedThrough = -1;
_lastCongestionSeenAt = MAX_WINDOW_SIZE;
_connectionManager = manager;
_resetReceived = false;
_connected = true;
_disconnectScheduled = false;
_lastReceivedOn = -1;
_activityTimer = new ActivityTimer();
_ackSinceCongestion = true;
_connectLock = new Object();
_activeResends = 0;
_context.statManager().createRateStat("stream.con.windowSizeAtCongestion", "How large was our send window when we send a dup?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
}
public long getNextOutboundPacketNum() {
@@ -93,6 +123,7 @@ public class Connection {
void closeReceived() {
setCloseReceivedOn(_context.clock().now());
_inputStream.closeReceived();
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
/**
@@ -147,6 +178,8 @@ public class Connection {
}
void sendPacket(PacketLocal packet) {
if (packet == null) return;
setNextSendTime(-1);
_unackedPacketsReceived = 0;
if (_options.getRequireFullySigned()) {
@@ -178,7 +211,7 @@ public class Connection {
}
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
long timeout = (_options.getRTT() < 10000 ? 10000 : _options.getRTT());
long timeout = (_options.getRTT() < MIN_RESEND_DELAY ? MIN_RESEND_DELAY : _options.getRTT());
if (timeout > MAX_RESEND_DELAY)
timeout = MAX_RESEND_DELAY;
if (_log.shouldLog(Log.DEBUG))
@@ -189,13 +222,14 @@ public class Connection {
_lastSendTime = _context.clock().now();
_outboundQueue.enqueue(packet);
resetActivityTimer();
if (ackOnly) {
// ACK only, don't schedule this packet for retries
// however, if we are running low on sessionTags we want to send
// something that will get a reply so that we can deliver some new tags -
// ACKs don't get ACKed, but pings do.
if (packet.getTagsSent().size() > 0) {
if ( (packet.getTagsSent() != null) && (packet.getTagsSent().size() > 0) ) {
_log.warn("Sending a ping since the ACK we just sent has " + packet.getTagsSent().size() + " tags");
_connectionManager.ping(_remotePeer, _options.getRTT()*2, false, packet.getKeyUsed(), packet.getTagsSent(), new PingNotifier());
}
@@ -260,21 +294,57 @@ public class Connection {
PacketLocal p = (PacketLocal)acked.get(i);
_outboundPackets.remove(new Long(p.getSequenceNum()));
_ackedPackets++;
if (p.getNumSends() > 1) {
_activeResends--;
if (_log.shouldLog(Log.WARN))
_log.warn("Active resend of " + p + " successful, # active left: " + _activeResends);
}
}
}
if ( (_outboundPackets.size() <= 0) && (_activeResends != 0) ) {
if (_log.shouldLog(Log.WARN))
_log.warn("All outbound packets acked, clearing " + _activeResends);
_activeResends = 0;
}
_outboundPackets.notifyAll();
}
if ((acked != null) && (acked.size() > 0) )
_ackSinceCongestion = true;
return acked;
}
private long _occurredTime;
private long _occurredEventCount;
void eventOccurred() {
_chooser.getScheduler(this).eventOccurred(this);
long now = System.currentTimeMillis();
TaskScheduler sched = _chooser.getScheduler(this);
now = now - now % 1000;
if (_occurredTime == now) {
_occurredEventCount++;
} else {
_occurredTime = now;
if (_occurredEventCount > 100) {
_log.log(Log.CRIT, "More than 100 events (" + _occurredEventCount + ") in a second on "
+ toString() + ": scheduler = " + sched);
}
_occurredEventCount = 0;
}
sched.eventOccurred(this);
}
void resetReceived() {
_resetReceived = true;
_outputStream.streamErrorOccurred(new IOException("Reset received"));
_inputStream.streamErrorOccurred(new IOException("Reset received"));
MessageOutputStream mos = _outputStream;
MessageInputStream mis = _inputStream;
if (mos != null)
mos.streamErrorOccurred(new IOException("Reset received"));
if (mis != null)
mis.streamErrorOccurred(new IOException("Reset received"));
_connectionError = "Connection reset";
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
public boolean getResetReceived() { return _resetReceived; }
@@ -284,37 +354,91 @@ public class Connection {
disconnect(cleanDisconnect, true);
}
void disconnect(boolean cleanDisconnect, boolean removeFromConMgr) {
if (!_connected) return;
_connected = false;
synchronized (_connectLock) { _connectLock.notifyAll(); }
if (_log.shouldLog(Log.DEBUG))
_log.debug("Disconnecting " + toString(), new Exception("discon"));
if (cleanDisconnect) {
if (cleanDisconnect && _connected) {
// send close packets and schedule stuff...
_outputStream.closeInternal();
_inputStream.close();
} else {
doClose();
synchronized (_outboundPackets) {
for (Iterator iter = _outboundPackets.values().iterator(); iter.hasNext(); ) {
PacketLocal pl = (PacketLocal)iter.next();
pl.cancelled();
}
_outboundPackets.clear();
_outboundPackets.notifyAll();
}
if (removeFromConMgr)
_connectionManager.removeConnection(this);
if (_connected)
doClose();
killOutstandingPackets();
}
if (removeFromConMgr) {
if (!_disconnectScheduled) {
_disconnectScheduled = true;
SimpleTimer.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
}
}
_connected = false;
}
void disconnectComplete() {
_connectionManager.removeConnection(this);
_connected = false;
if (_socket != null)
_socket.destroy();
_socket = null;
_inputStream = null;
if (_outputStream != null)
_outputStream.destroy();
_outputStream = null;
_outboundQueue = null;
if (_receiver != null)
_receiver.destroy();
if (_activityTimer != null)
SimpleTimer.getInstance().addEvent(_activityTimer, 1);
_activityTimer = null;
if (!_disconnectScheduled) {
_disconnectScheduled = true;
if (_log.shouldLog(Log.INFO))
_log.info("Connection disconnect complete from dead, drop the con "
+ toString());
_connectionManager.removeConnection(this);
}
killOutstandingPackets();
}
private void killOutstandingPackets() {
boolean tagsCancelled = false;
synchronized (_outboundPackets) {
for (Iterator iter = _outboundPackets.values().iterator(); iter.hasNext(); ) {
PacketLocal pl = (PacketLocal)iter.next();
if ( (pl.getTagsSent() != null) && (pl.getTagsSent().size() > 0) )
tagsCancelled = true;
pl.cancelled();
}
_outboundPackets.clear();
_outboundPackets.notifyAll();
}
if (tagsCancelled)
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
}
private class DisconnectEvent implements SimpleTimer.TimedEvent {
public DisconnectEvent() {
if (_log.shouldLog(Log.INFO))
_log.info("Connection disconnect timer initiated: 5 minutes to drop "
+ Connection.this.toString());
}
public void timeReached() {
killOutstandingPackets();
if (_log.shouldLog(Log.INFO))
_log.info("Connection disconnect timer complete, drop the con "
+ Connection.this.toString());
_connectionManager.removeConnection(Connection.this);
}
}
private void doClose() {
_outputStream.streamErrorOccurred(new IOException("Hard disconnect"));
_inputStream.closeReceived();
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
/** who are we talking with */
@@ -327,7 +451,10 @@ public class Connection {
/** stream the peer sends data to us on. (may be null) */
public byte[] getReceiveStreamId() { return _receiveStreamId; }
public void setReceiveStreamId(byte[] id) { _receiveStreamId = id; }
public void setReceiveStreamId(byte[] id) {
_receiveStreamId = id;
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
/** when did we last send anything to the peer? */
public long getLastSendTime() { return _lastSendTime; }
@@ -347,8 +474,24 @@ public class Connection {
public String getConnectionError() { return _connectionError; }
public void setConnectionError(String err) { _connectionError = err; }
public long getLifetime() {
if (_closeSentOn <= 0)
return _context.clock().now() - _createdOn;
else
return _closeSentOn - _createdOn;
}
public ConnectionPacketHandler getPacketHandler() { return _handler; }
public long getLifetimeBytesSent() { return _lifetimeBytesSent; }
public long getLifetimeBytesReceived() { return _lifetimeBytesReceived; }
public long getLifetimeDupMessagesSent() { return _lifetimeDupMessageSent; }
public long getLifetimeDupMessagesReceived() { return _lifetimeDupMessageReceived; }
public void incrementBytesSent(int bytes) { _lifetimeBytesSent += bytes; }
public void incrementDupMessagesSent(int msgs) { _lifetimeDupMessageSent += msgs; }
public void incrementBytesReceived(int bytes) { _lifetimeBytesReceived += bytes; }
public void incrementDupMessagesReceived(int msgs) { _lifetimeDupMessageReceived += msgs; }
/**
* Time when the scheduler next want to send a packet, or -1 if
* never. This should be set when we want to send on timeout, for
@@ -397,6 +540,129 @@ public class Connection {
public long getHighestAckedThrough() { return _highestAckedThrough; }
public void setHighestAckedThrough(long msgNum) { _highestAckedThrough = msgNum; }
public long getLastActivityOn() {
return (_lastSendTime > _lastReceivedOn ? _lastSendTime : _lastReceivedOn);
}
public int getLastCongestionSeenAt() { return _lastCongestionSeenAt; }
void congestionOccurred() {
// if we hit congestion and e.g. 5 packets are resent,
// dont set the size to (winSize >> 4). only set the
if (_ackSinceCongestion) {
_lastCongestionSeenAt = _options.getWindowSize();
_ackSinceCongestion = false;
}
}
void packetReceived() {
_lastReceivedOn = _context.clock().now();
resetActivityTimer();
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
/**
* wait until a connection is made or the connection fails within the
* timeout period, setting the error accordingly.
*/
void waitForConnect() {
long expiration = _context.clock().now() + _options.getConnectTimeout();
while (true) {
if (_connected && (_receiveStreamId != null) && (_sendStreamId != null) ) {
// w00t
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): Connected and we have stream IDs");
return;
}
if (_connectionError != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): connection error found: " + _connectionError);
return;
}
if (!_connected) {
_connectionError = "Connection failed";
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): not connected");
return;
}
long timeLeft = expiration - _context.clock().now();
if ( (timeLeft <= 0) && (_options.getConnectTimeout() > 0) ) {
if (_connectionError == null) {
_connectionError = "Connection timed out";
disconnect(false);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): timed out: " + _connectionError);
return;
}
if (timeLeft > 60*1000)
timeLeft = 60*1000;
if (_options.getConnectTimeout() <= 0)
timeLeft = 60*1000;
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): wait " + timeLeft);
try {
synchronized (_connectLock) {
_connectLock.wait(timeLeft);
}
} catch (InterruptedException ie) {}
}
}
private void resetActivityTimer() {
if (_options.getInactivityTimeout() <= 0) return;
if (_activityTimer == null) return;
long howLong = _activityTimer.getTimeLeft();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Resetting the inactivity timer to " + howLong);
// this will get rescheduled, and rescheduled, and rescheduled...
SimpleTimer.getInstance().addEvent(_activityTimer, howLong);
}
private class ActivityTimer implements SimpleTimer.TimedEvent {
public void timeReached() {
// uh, nothing more to do...
if (!_connected) return;
// we got rescheduled already
if (getTimeLeft() > 0) return;
// these are either going to time out or cause further rescheduling
if (getUnackedPacketsSent() > 0) return;
// wtf, this shouldn't have been scheduled
if (_options.getInactivityTimeout() <= 0) return;
// if one of us can't talk...
if ( (_closeSentOn > 0) || (_closeReceivedOn > 0) ) return;
// bugger it, might as well do the hard work now
switch (_options.getInactivityAction()) {
case ConnectionOptions.INACTIVITY_ACTION_SEND:
if (_log.shouldLog(Log.WARN))
_log.warn("Sending some data due to inactivity");
_receiver.send(null, 0, 0, true);
break;
case ConnectionOptions.INACTIVITY_ACTION_NOOP:
if (_log.shouldLog(Log.WARN))
_log.warn("Inactivity timer expired, but we aint doin' shit");
break;
case ConnectionOptions.INACTIVITY_ACTION_DISCONNECT:
// fall through
default:
if (_log.shouldLog(Log.WARN))
_log.warn("Closing connection due to inactivity");
disconnect(true);
break;
}
}
public final long getTimeLeft() {
if (getLastActivityOn() > 0)
return getLastActivityOn() + _options.getInactivityTimeout() - _context.clock().now();
else
return _createdOn + _options.getInactivityTimeout() - _context.clock().now();
}
}
/** stream that the local peer receives data on */
public MessageInputStream getInputStream() { return _inputStream; }
/** stream that the local peer sends data to the remote peer on */
@@ -426,12 +692,15 @@ public class Connection {
buf.append("] ");
}
buf.append("unacked inbound? ").append(getUnackedPacketsReceived());
buf.append(" [high ").append(_inputStream.getHighestBlockId());
long nacks[] = _inputStream.getNacks();
if (nacks != null)
for (int i = 0; i < nacks.length; i++)
buf.append(" ").append(nacks[i]);
buf.append("]");
if (_inputStream != null) {
buf.append(" [high ");
buf.append(_inputStream.getHighestBlockId());
long nacks[] = _inputStream.getNacks();
if (nacks != null)
for (int i = 0; i < nacks.length; i++)
buf.append(" ").append(nacks[i]);
buf.append("]");
}
buf.append("]");
return buf.toString();
}
@@ -441,12 +710,20 @@ public class Connection {
*/
private class ResendPacketEvent implements SimpleTimer.TimedEvent {
private PacketLocal _packet;
private boolean _currentIsActiveResend;
public ResendPacketEvent(PacketLocal packet) {
_packet = packet;
_currentIsActiveResend = false;
}
public void timeReached() {
if (!_connected) return;
if (_packet.getAckTime() > 0)
return;
if (!_connected) {
_packet.cancelled();
return;
}
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Resend period reached for " + _packet);
@@ -456,6 +733,16 @@ public class Connection {
resend = true;
}
if ( (resend) && (_packet.getAckTime() < 0) ) {
if ( (_activeResends > 0) && (!_currentIsActiveResend) ) {
// we want to resend this packet, but there are already active
// resends in the air and we dont want to make a bad situation
// worse. wait another second
if (_log.shouldLog(Log.WARN))
_log.warn("Delaying resend of " + _packet + " as there are "
+ _activeResends + " active resends already in play");
SimpleTimer.getInstance().addEvent(ResendPacketEvent.this, 1000);
return;
}
// revamp various fields, in case we need to ack more, etc
_inputStream.updateAcks(_packet);
_packet.setOptionalDelay(getOptions().getChoke());
@@ -466,6 +753,8 @@ public class Connection {
// shrink the window
int newWindowSize = getOptions().getWindowSize();
congestionOccurred();
_context.statManager().addRateData("stream.con.windowSizeAtCongestion", newWindowSize, _packet.getLifetime());
newWindowSize /= 2;
if (newWindowSize <= 0)
newWindowSize = 1;
@@ -473,13 +762,25 @@ public class Connection {
int numSends = _packet.getNumSends() + 1;
if (numSends == 2) {
// first resend for this packet
_activeResends++;
_currentIsActiveResend = true;
}
// in case things really suck, the other side may have lost thier
// session tags (e.g. they restarted), so jump back to ElGamal.
if ( (newWindowSize == 1) && (numSends > 2) )
int failTagsAt = _options.getMaxResends() - 1;
if ( (newWindowSize == 1) && (numSends == failTagsAt) ) {
if (_log.shouldLog(Log.WARN))
_log.warn("Optimistically failing tags at resend " + numSends);
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
}
if (_log.shouldLog(Log.WARN))
_log.warn("Resend packet " + _packet + " time " + numSends + " (wsize "
_log.warn("Resend packet " + _packet + " time " + numSends +
" activeResends: " + _activeResends +
" (wsize "
+ newWindowSize + " lifetime "
+ (_context.clock().now() - _packet.getCreatedOn()) + "ms)");
_outboundQueue.enqueue(_packet);
@@ -487,10 +788,14 @@ public class Connection {
if (numSends > _options.getMaxResends()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Too many resends");
_packet.cancelled();
disconnect(false);
} else {
//long timeout = _options.getResendDelay() << numSends;
long timeout = _options.getRTT() << (numSends-1);
long rtt = _options.getRTT();
if (rtt < MIN_RESEND_DELAY)
rtt = MIN_RESEND_DELAY;
long timeout = rtt << (numSends-1);
if ( (timeout > MAX_RESEND_DELAY) || (timeout <= 0) )
timeout = MAX_RESEND_DELAY;
if (_log.shouldLog(Log.DEBUG))

View File

@@ -6,6 +6,11 @@ import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* Receive data from the MessageOutputStream, build a packet,
* and send it through a connection. The write calls on this
* do NOT block, but they also do not necessary imply immediate
* delivery, or even the generation of a new packet. This class
* is the only one that builds useful outbound Packet objects.
*
*/
class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
@@ -21,6 +26,17 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
_dummyStatus = new DummyStatus();
}
/**
* Send some data through the connection, or if there is no new data, this
* may generate a packet with a plain ACK/NACK or CLOSE, or nothing whatsoever
* if there's nothing new to send.
*
* @param buf data to be sent - may be null
* @param off offset into the buffer to start writing from
* @param size how many bytes of the buffer to write (may be 0)
* @return an object to allow optional blocking for data acceptance or
* delivery.
*/
public MessageOutputStream.WriteStatus writeData(byte[] buf, int off, int size) {
boolean doSend = true;
if ( (size <= 0) && (_connection.getLastSendId() >= 0) ) {
@@ -40,22 +56,46 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
if (_connection.getUnackedPacketsReceived() > 0)
doSend = true;
if (_log.shouldLog(Log.ERROR) && !doSend)
_log.error("writeData called: size="+size + " doSend=" + doSend
if (_log.shouldLog(Log.INFO) && !doSend)
_log.info("writeData called: size="+size + " doSend=" + doSend
+ " unackedReceived: " + _connection.getUnackedPacketsReceived()
+ " con: " + _connection, new Exception("write called by"));
if (doSend) {
PacketLocal packet = send(buf, off, size);
return packet;
//dont wait for non-acks
if ( (packet.getPayloadSize() > 0) || (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) )
return packet;
else
return _dummyStatus;
} else {
return _dummyStatus;
}
}
/**
* Send some data through the connection, attaching any appropriate flags
* onto the packet.
*
* @param buf data to be sent - may be null
* @param off offset into the buffer to start writing from
* @param size how many bytes of the buffer to write (may be 0)
* @return the packet sent
*/
public PacketLocal send(byte buf[], int off, int size) {
PacketLocal packet = buildPacket(buf, off, size);
return send(buf, off, size, false);
}
/**
* @param buf data to be sent - may be null
* @param off offset into the buffer to start writing from
* @param size how many bytes of the buffer to write (may be 0)
* @param forceIncrement even if the buffer is empty, increment the packetId
* so we get an ACK back
* @return the packet sent
*/
public PacketLocal send(byte buf[], int off, int size, boolean forceIncrement) {
PacketLocal packet = buildPacket(buf, off, size, forceIncrement);
_connection.sendPacket(packet);
return packet;
}
@@ -69,14 +109,14 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
return ackOnly;
}
private PacketLocal buildPacket(byte buf[], int off, int size) {
private PacketLocal buildPacket(byte buf[], int off, int size, boolean forceIncrement) {
boolean ackOnly = isAckOnly(size);
PacketLocal packet = new PacketLocal(_context, _connection.getRemotePeer(), _connection);
byte data[] = new byte[size];
if (size > 0)
System.arraycopy(buf, off, data, 0, size);
packet.setPayload(data);
if (ackOnly)
if (ackOnly && !forceIncrement)
packet.setSequenceNum(0);
else
packet.setSequenceNum(_connection.getNextOutboundPacketNum());
@@ -100,7 +140,11 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
packet.setOptionalFrom(_connection.getSession().getMyDestination());
}
if (_connection.getOutputStream().getClosed()) {
// don't set the closed flag if this is a plain ACK and there are outstanding
// packets sent, otherwise the other side could receive the CLOSE prematurely,
// since this ACK could arrive before the unacked payload message.
if (_connection.getOutputStream().getClosed() &&
( (size > 0) || (_connection.getUnackedPacketsSent() <= 0) ) ) {
packet.setFlag(Packet.FLAG_CLOSE);
_connection.setCloseSentOn(_context.clock().now());
if (_log.shouldLog(Log.DEBUG))
@@ -112,7 +156,9 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
return packet;
}
/**
* Used if no new packet was sent.
*/
private static final class DummyStatus implements MessageOutputStream.WriteStatus {
public final void waitForAccept(int maxWaitMs) { return; }
public final void waitForCompletion(int maxWaitMs) { return; }
@@ -120,4 +166,8 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
public final boolean writeFailed() { return false; }
public final boolean writeSuccessful() { return true; }
}
void destroy() {
_connection = null;
}
}

View File

@@ -31,7 +31,14 @@ class ConnectionHandler {
_acceptTimeout = DEFAULT_ACCEPT_TIMEOUT;
}
public void setActive(boolean active) { _active = active; }
public void setActive(boolean active) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("setActive(" + active + ") called");
synchronized (_synQueue) {
_active = active;
_synQueue.notifyAll(); // so we break from the accept()
}
}
public boolean getActive() { return _active; }
public void receiveNewSyn(Packet packet) {
@@ -50,49 +57,61 @@ class ConnectionHandler {
}
}
/**
* Receive an incoming connection (built from a received SYN)
*
* @param timeoutMs max amount of time to wait for a connection (if less
* than 1ms, wait indefinitely)
* @return connection received, or null if there was a timeout or the
* handler was shut down
*/
public Connection accept(long timeoutMs) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Accept("+ timeoutMs+") called");
long expiration = timeoutMs;
if (expiration > 0)
expiration += _context.clock().now();
Packet syn = null;
synchronized (_synQueue) {
while ( _active && (_synQueue.size() <= 0) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Accept("+ timeoutMs+"): active=" + _active + " queue: " + _synQueue.size());
if (timeoutMs <= 0) {
try { _synQueue.wait(); } catch (InterruptedException ie) {}
} else {
long remaining = expiration - _context.clock().now();
if (remaining < 0)
break;
try { _synQueue.wait(remaining); } catch (InterruptedException ie) {}
long expiration = timeoutMs + _context.clock().now();
while (true) {
if ( (timeoutMs > 0) && (expiration < _context.clock().now()) )
return null;
if (!_active) {
// fail all the ones we had queued up
synchronized (_synQueue) {
for (int i = 0; i < _synQueue.size(); i++) {
Packet packet = (Packet)_synQueue.get(i);
sendReset(packet);
}
_synQueue.clear();
}
return null;
}
Packet syn = null;
synchronized (_synQueue) {
while ( _active && (_synQueue.size() <= 0) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Accept("+ timeoutMs+"): active=" + _active + " queue: "
+ _synQueue.size());
if (timeoutMs <= 0) {
try { _synQueue.wait(); } catch (InterruptedException ie) {}
} else {
long remaining = expiration - _context.clock().now();
if (remaining < 0)
break;
try { _synQueue.wait(remaining); } catch (InterruptedException ie) {}
}
}
if (_active && _synQueue.size() > 0) {
syn = (Packet)_synQueue.remove(0);
}
}
if (_active && _synQueue.size() > 0) {
syn = (Packet)_synQueue.remove(0);
if (syn != null) {
// deal with forged / invalid syn packets
Connection con = _manager.receiveConnection(syn);
if (con != null)
return con;
}
}
if (syn != null) {
// deal with forged / invalid syn packets
Connection con = _manager.receiveConnection(syn);
if (con != null) {
return con;
} else if (timeoutMs > 0) {
long remaining = expiration - _context.clock().now();
if (remaining <= 0) {
return null;
} else {
return accept(remaining);
}
} else {
return accept(timeoutMs);
}
} else {
return null;
// keep looping...
}
}

View File

@@ -10,6 +10,7 @@ import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PSession;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.SessionKey;
import net.i2p.util.SimpleTimer;
@@ -35,9 +36,11 @@ public class ConnectionManager {
/** Ping ID (ByteArray) to PingRequest */
private Map _pendingPings;
private boolean _allowIncoming;
private int _maxConcurrentStreams;
private volatile int _numWaiting;
private Object _connectionLock;
public ConnectionManager(I2PAppContext context, I2PSession session) {
public ConnectionManager(I2PAppContext context, I2PSession session, int maxConcurrent) {
_context = context;
_log = context.logManager().getLog(ConnectionManager.class);
_connectionByInboundId = new HashMap(32);
@@ -50,8 +53,19 @@ public class ConnectionManager {
_conPacketHandler = new ConnectionPacketHandler(context);
_session = session;
session.setSessionListener(_messageHandler);
_outboundQueue = new PacketQueue(context, session);
_outboundQueue = new PacketQueue(context, session, this);
_allowIncoming = false;
_maxConcurrentStreams = maxConcurrent;
_numWaiting = 0;
_context.statManager().createRateStat("stream.con.lifetimeMessagesSent", "How many messages do we send on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeMessagesReceived", "How many messages do we receive on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeBytesSent", "How many bytes do we send on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeBytesReceived", "How many bytes do we receive on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeDupMessagesSent", "How many duplicate messages do we send on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeDupMessagesReceived", "How many duplicate messages do we receive on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeRTT", "What is the final RTT when a stream closes?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeCongestionSeenAt", "When was the last congestion seen at when a stream closes?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeSendWindowSize", "What is the final send window size when a stream closes?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
}
Connection getConnectionByInboundId(byte[] id) {
@@ -59,10 +73,25 @@ public class ConnectionManager {
return (Connection)_connectionByInboundId.get(new ByteArray(id));
}
}
/**
* not guaranteed to be unique, but in case we receive more than one packet
* on an inbound connection that we havent ack'ed yet...
*/
Connection getConnectionByOutboundId(byte[] id) {
synchronized (_connectionLock) {
for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) {
Connection con = (Connection)iter.next();
if (DataHelper.eq(con.getSendStreamId(), id))
return con;
}
}
return null;
}
public void setAllowIncomingConnections(boolean allow) {
_connectionHandler.setActive(allow);
}
/** should we acceot connections, or just reject everyone? */
public boolean getAllowIncomingConnections() {
return _connectionHandler.getActive();
}
@@ -77,19 +106,41 @@ public class ConnectionManager {
Connection con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler);
byte receiveId[] = new byte[4];
_context.random().nextBytes(receiveId);
boolean reject = false;
synchronized (_connectionLock) {
while (true) {
Connection oldCon = (Connection)_connectionByInboundId.put(new ByteArray(receiveId), con);
if (oldCon == null) {
break;
} else {
_connectionByInboundId.put(new ByteArray(receiveId), oldCon);
// receiveId already taken, try another
_context.random().nextBytes(receiveId);
if (locked_tooManyStreams()) {
reject = true;
} else {
while (true) {
ByteArray ba = new ByteArray(receiveId);
Connection oldCon = (Connection)_connectionByInboundId.put(ba, con);
if (oldCon == null) {
break;
} else {
_connectionByInboundId.put(ba, oldCon);
// receiveId already taken, try another
_context.random().nextBytes(receiveId);
}
}
}
}
if (reject) {
if (_log.shouldLog(Log.WARN))
_log.warn("Refusing connection since we have exceeded our max of "
+ _maxConcurrentStreams + " connections");
PacketLocal reply = new PacketLocal(_context, synPacket.getOptionalFrom());
reply.setFlag(Packet.FLAG_RESET);
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
reply.setAckThrough(synPacket.getSequenceNum());
reply.setSendStreamId(synPacket.getReceiveStreamId());
reply.setReceiveStreamId(null);
reply.setOptionalFrom(_session.getMyDestination());
// this just sends the packet - no retries or whatnot
_outboundQueue.enqueue(reply);
return null;
}
con.setReceiveStreamId(receiveId);
try {
con.getPacketHandler().receivePacket(synPacket, con);
@@ -99,30 +150,97 @@ public class ConnectionManager {
}
return null;
}
_context.statManager().addRateData("stream.connectionReceived", 1, 0);
return con;
}
private static final long DEFAULT_STREAM_DELAY_MAX = 10*1000;
/**
* Build a new connection to the given peer
* Build a new connection to the given peer. This blocks if there is no
* connection delay, otherwise it returns immediately.
*
* @return new connection, or null if we have exceeded our limit
*/
public Connection connect(Destination peer, ConnectionOptions opts) {
Connection con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler, opts);
con.setRemotePeer(peer);
Connection con = null;
byte receiveId[] = new byte[4];
_context.random().nextBytes(receiveId);
synchronized (_connectionLock) {
ByteArray ba = new ByteArray(receiveId);
while (_connectionByInboundId.containsKey(ba)) {
_context.random().nextBytes(receiveId);
long expiration = _context.clock().now() + opts.getConnectTimeout();
if (opts.getConnectTimeout() <= 0)
expiration = _context.clock().now() + DEFAULT_STREAM_DELAY_MAX;
_numWaiting++;
while (true) {
long remaining = expiration - _context.clock().now();
if (remaining <= 0) {
if (_log.shouldLog(Log.WARN))
_log.warn("Refusing to connect since we have exceeded our max of "
+ _maxConcurrentStreams + " connections");
_numWaiting--;
return null;
}
boolean reject = false;
synchronized (_connectionLock) {
if (locked_tooManyStreams()) {
// allow a full buffer of pending/waiting streams
if (_numWaiting > _maxConcurrentStreams) {
if (_log.shouldLog(Log.WARN))
_log.warn("Refusing connection since we have exceeded our max of "
+ _maxConcurrentStreams + " and there are " + _numWaiting
+ " waiting already");
_numWaiting--;
return null;
}
// no remaining streams, lets wait a bit
try { _connectionLock.wait(remaining); } catch (InterruptedException ie) {}
} else {
con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler, opts);
con.setRemotePeer(peer);
ByteArray ba = new ByteArray(receiveId);
while (_connectionByInboundId.containsKey(ba)) {
_context.random().nextBytes(receiveId);
}
_connectionByInboundId.put(ba, con);
break; // stop looping as a psuedo-wait
}
}
_connectionByInboundId.put(ba, con);
}
// ok we're in...
con.setReceiveStreamId(receiveId);
con.eventOccurred();
_log.debug("Connect() conDelay = " + opts.getConnectDelay());
if (opts.getConnectDelay() <= 0) {
con.waitForConnect();
}
if (_numWaiting > 0)
_numWaiting--;
_context.statManager().addRateData("stream.connectionCreated", 1, 0);
return con;
}
private boolean locked_tooManyStreams() {
int active = 0;
for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) {
Connection con = (Connection)iter.next();
if (con.getIsConnected())
active++;
}
if ( (_connectionByInboundId.size() > 100) && (_log.shouldLog(Log.INFO)) )
_log.info("More than 100 connections! " + active
+ " total: " + _connectionByInboundId.size());
if (_maxConcurrentStreams <= 0) return false;
if (_connectionByInboundId.size() < _maxConcurrentStreams) return false;
return (active >= _maxConcurrentStreams);
}
public MessageHandler getMessageHandler() { return _messageHandler; }
public PacketHandler getPacketHandler() { return _packetHandler; }
public ConnectionHandler getConnectionHandler() { return _connectionHandler; }
@@ -141,15 +259,40 @@ public class ConnectionManager {
con.disconnect(false, false);
}
_connectionByInboundId.clear();
_connectionLock.notifyAll();
}
}
/**
* Drop the (already closed) connection on the floor.
*
*/
public void removeConnection(Connection con) {
boolean removed = false;
synchronized (_connectionLock) {
_connectionByInboundId.remove(new ByteArray(con.getReceiveStreamId()));
Object o = _connectionByInboundId.remove(new ByteArray(con.getReceiveStreamId()));
removed = (o == con);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Connection removed? " + removed + " remaining: "
+ _connectionByInboundId.size() + ": " + con);
if (!removed && _log.shouldLog(Log.DEBUG))
_log.debug("Failed to remove " + con +"\n" + _connectionByInboundId.values());
_connectionLock.notifyAll();
}
if (removed) {
_context.statManager().addRateData("stream.con.lifetimeMessagesSent", con.getLastSendId(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeMessagesReceived", con.getHighestAckedThrough(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeBytesSent", con.getLifetimeBytesSent(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeBytesReceived", con.getLifetimeBytesReceived(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeDupMessagesSent", con.getLifetimeDupMessagesSent(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeDupMessagesReceived", con.getLifetimeDupMessagesReceived(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeRTT", con.getOptions().getRTT(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeCongestionSeenAt", con.getLastCongestionSeenAt(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeSendWindowSize", con.getOptions().getWindowSize(), con.getLifetime());
}
}
/** return a set of Connection objects */
public Set listConnections() {
synchronized (_connectionLock) {
return new HashSet(_connectionByInboundId.values());

View File

@@ -6,7 +6,7 @@ import java.util.Properties;
* Define the current options for the con (and allow custom tweaking midstream)
*
*/
public class ConnectionOptions extends I2PSocketOptions {
public class ConnectionOptions extends I2PSocketOptionsImpl {
private int _connectDelay;
private boolean _fullySigned;
private int _windowSize;
@@ -18,26 +18,46 @@ public class ConnectionOptions extends I2PSocketOptions {
private int _maxMessageSize;
private int _choke;
private int _maxResends;
private int _inactivityTimeout;
private int _inactivityAction;
private int _inboundBufferSize;
public static final int PROFILE_BULK = 1;
public static final int PROFILE_INTERACTIVE = 2;
/** on inactivity timeout, do nothing */
public static final int INACTIVITY_ACTION_NOOP = 0;
/** on inactivity timeout, close the connection */
public static final int INACTIVITY_ACTION_DISCONNECT = 1;
/** on inactivity timeout, send a payload message */
public static final int INACTIVITY_ACTION_SEND = 2;
public static final String PROP_CONNECT_DELAY = "i2p.streaming.connectDelay";
public static final String PROP_PROFILE = "i2p.streaming.profile";
public static final String PROP_MAX_MESSAGE_SIZE = "i2p.streaming.maxMessageSize";
public static final String PROP_MAX_RESENDS = "i2p.streaming.maxResends";
public static final String PROP_INITIAL_RTT = "i2p.streaming.initialRTT";
public static final String PROP_INITIAL_RESEND_DELAY = "i2p.streaming.initialResendDelay";
public static final String PROP_INITIAL_ACK_DELAY = "i2p.streaming.initialAckDelay";
public static final String PROP_INITIAL_WINDOW_SIZE = "i2p.streaming.initialWindowSize";
public static final String PROP_INITIAL_RECEIVE_WINDOW = "i2p.streaming.initialReceiveWindow";
public static final String PROP_INACTIVITY_TIMEOUT = "i2p.streaming.inactivityTimeout";
public static final String PROP_INACTIVITY_ACTION = "i2p.streaming.inactivityAction";
public ConnectionOptions() {
super();
init(null);
}
public ConnectionOptions(Properties opts) {
super(opts);
}
public ConnectionOptions(I2PSocketOptions opts) {
super(opts);
init(null);
}
public ConnectionOptions(ConnectionOptions opts) {
super(opts);
init(opts);
}
private void init(ConnectionOptions opts) {
if (opts != null) {
setConnectDelay(opts.getConnectDelay());
setProfile(opts.getProfile());
@@ -48,23 +68,27 @@ public class ConnectionOptions extends I2PSocketOptions {
setMaxMessageSize(opts.getMaxMessageSize());
setChoke(opts.getChoke());
setMaxResends(opts.getMaxResends());
} else {
setConnectDelay(2*1000);
setProfile(PROFILE_BULK);
setMaxMessageSize(Packet.MAX_PAYLOAD_SIZE);
setRTT(30*1000);
setReceiveWindow(1);
setResendDelay(5*1000);
setSendAckDelay(2*1000);
setWindowSize(1);
setMaxResends(5);
setWriteTimeout(-1);
setInactivityTimeout(opts.getInactivityTimeout());
setInactivityAction(opts.getInactivityAction());
setInboundBufferSize(opts.getInboundBufferSize());
}
}
public ConnectionOptions(Properties opts) {
super(opts);
// load the options;
protected void init(Properties opts) {
super.init(opts);
setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1));
setProfile(getInt(opts, PROP_PROFILE, PROFILE_BULK));
setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, Packet.MAX_PAYLOAD_SIZE));
setRTT(getInt(opts, PROP_INITIAL_RTT, 30*1000));
setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 5*1000));
setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, 2*1000));
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, 1));
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 5));
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_SEND));
setInboundBufferSize((getMaxMessageSize() + 2) * Connection.MAX_WINDOW_SIZE);
}
/**
@@ -87,15 +111,14 @@ public class ConnectionOptions extends I2PSocketOptions {
public boolean getRequireFullySigned() { return _fullySigned; }
public void setRequireFullySigned(boolean sign) { _fullySigned = sign; }
private static final int MAX_WINDOW_SIZE = 32;
/**
* How many messages will we send before waiting for an ACK?
*
*/
public int getWindowSize() { return _windowSize; }
public void setWindowSize(int numMsgs) {
if (numMsgs > MAX_WINDOW_SIZE)
numMsgs = MAX_WINDOW_SIZE;
if (numMsgs > Connection.MAX_WINDOW_SIZE)
numMsgs = Connection.MAX_WINDOW_SIZE;
_windowSize = numMsgs;
}
@@ -151,7 +174,11 @@ public class ConnectionOptions extends I2PSocketOptions {
*
*/
public int getProfile() { return _profile; }
public void setProfile(int profile) { _profile = profile; }
public void setProfile(int profile) {
if (profile != PROFILE_BULK)
throw new IllegalArgumentException("Only bulk is supported so far");
_profile = profile;
}
/**
* How many times will we try to send a message before giving up?
@@ -159,4 +186,48 @@ public class ConnectionOptions extends I2PSocketOptions {
*/
public int getMaxResends() { return _maxResends; }
public void setMaxResends(int numSends) { _maxResends = numSends; }
/**
* What period of inactivity qualifies as "too long"?
*
*/
public int getInactivityTimeout() { return _inactivityTimeout; }
public void setInactivityTimeout(int timeout) { _inactivityTimeout = timeout; }
public int getInactivityAction() { return _inactivityAction; }
public void setInactivityAction(int action) { _inactivityAction = action; }
/**
* how much data are we willing to accept in our buffer?
*
*/
public int getInboundBufferSize() { return _inboundBufferSize; }
public void setInboundBufferSize(int bytes) { _inboundBufferSize = bytes; }
public String toString() {
StringBuffer buf = new StringBuffer(128);
buf.append("conDelay=").append(_connectDelay);
buf.append(" maxSize=").append(_maxMessageSize);
buf.append(" rtt=").append(_rtt);
buf.append(" rwin=").append(_receiveWindow);
buf.append(" resendDelay=").append(_resendDelay);
buf.append(" ackDelay=").append(_sendAckDelay);
buf.append(" cwin=").append(_windowSize);
buf.append(" maxResends=").append(_maxResends);
buf.append(" writeTimeout=").append(getWriteTimeout());
buf.append(" inactivityTimeout=").append(_inactivityTimeout);
buf.append(" inboundBuffer=").append(_inboundBufferSize);
return buf.toString();
}
public static void main(String args[]) {
Properties p = new Properties();
p.setProperty(PROP_CONNECT_DELAY, "1000");
ConnectionOptions c = new ConnectionOptions(p);
System.out.println("opts: " + c);
c = new ConnectionOptions(new I2PSocketOptionsImpl(p));
System.out.println("opts: " + c);
}
}

View File

@@ -7,6 +7,7 @@ import net.i2p.I2PException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
* Receive a packet for a particular connection - placing the data onto the
@@ -20,13 +21,35 @@ public class ConnectionPacketHandler {
public ConnectionPacketHandler(I2PAppContext context) {
_context = context;
_log = context.logManager().getLog(ConnectionPacketHandler.class);
_context.statManager().createRateStat("stream.con.receiveMessageSize", "Size of a message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.con.receiveDuplicateSize", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.con.packetsAckedPerMessageReceived", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.sendsBeforeAck", "How many times a message was sent before it was ACKed?", "Stream", new long[] { 10*60*1000, 60*60*1000 });
}
/** distribute a packet to the connection specified */
void receivePacket(Packet packet, Connection con) throws I2PException {
boolean ok = verifyPacket(packet, con);
if (!ok) return;
con.packetReceived();
if (con.getInputStream().getTotalQueuedSize() > con.getOptions().getInboundBufferSize()) {
if (_log.shouldLog(Log.WARN))
_log.warn("Inbound buffer exceeded on connection " + con + ": dropping " + packet);
con.getOptions().setChoke(5*1000);
return;
}
con.getOptions().setChoke(0);
_context.statManager().addRateData("stream.con.receiveMessageSize", packet.getPayloadSize(), 0);
boolean isNew = con.getInputStream().messageReceived(packet.getSequenceNum(), packet.getPayload());
if ( (packet.getSequenceNum() == 0) && (packet.getPayloadSize() > 0) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("seq=0 && size=" + packet.getPayloadSize() + ": isNew? " + isNew
+ " packet: " + packet + " con: " + con);
}
// close *after* receiving the data, as well as after verifying the signatures / etc
if (packet.isFlagSet(Packet.FLAG_CLOSE) && packet.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED))
@@ -34,6 +57,8 @@ public class ConnectionPacketHandler {
if (isNew) {
con.incrementUnackedPacketsReceived();
con.incrementBytesReceived(packet.getPayloadSize());
if (packet.isFlagSet(Packet.FLAG_DELAY_REQUESTED) && (packet.getOptionalDelay() <= 0) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Scheduling immediate ack for " + packet);
@@ -47,17 +72,21 @@ public class ConnectionPacketHandler {
_log.debug("Scheduling ack in " + delay + "ms for received packet " + packet);
}
} else {
if (packet.getSequenceNum() > 0) {
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ) {
_context.statManager().addRateData("stream.con.receiveDuplicateSize", packet.getPayloadSize(), 0);
con.incrementDupMessagesReceived(1);
// take note of congestion
con.getOptions().setResendDelay(con.getOptions().getResendDelay()*2);
//con.getOptions().setResendDelay(con.getOptions().getResendDelay()*2);
//con.getOptions().setWindowSize(con.getOptions().getWindowSize()/2);
if (_log.shouldLog(Log.WARN))
_log.warn("congestion.. dup " + packet);
con.incrementUnackedPacketsReceived();
_log.warn("congestion.. dup " + packet);
SimpleTimer.getInstance().addEvent(new AckDup(con), con.getOptions().getSendAckDelay());
//con.incrementUnackedPacketsReceived();
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
} else {
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
con.incrementUnackedPacketsReceived();
//con.incrementUnackedPacketsReceived();
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
} else {
if (_log.shouldLog(Log.DEBUG))
@@ -80,6 +109,7 @@ public class ConnectionPacketHandler {
//if (p.getNumSends() <= 1)
highestRTT = p.getAckTime();
}
_context.statManager().addRateData("stream.sendsBeforeAck", p.getNumSends(), p.getAckTime());
if (p.getNumSends() > 1)
numResends++;
@@ -97,6 +127,7 @@ public class ConnectionPacketHandler {
if (highestRTT > 0) {
con.getOptions().updateRTT(highestRTT);
}
_context.statManager().addRateData("stream.con.packetsAckedPerMessageReceived", acked.size(), highestRTT);
}
boolean fastAck = adjustWindow(con, isNew, packet.getSequenceNum(), numResends, (acked != null ? acked.size() : 0));
@@ -114,12 +145,19 @@ public class ConnectionPacketHandler {
if ( (!isNew) && (sequenceNum > 0) ) {
// dup real packet
int oldSize = con.getOptions().getWindowSize();
con.congestionOccurred();
oldSize >>>= 1;
if (oldSize <= 0)
oldSize = 1;
con.getOptions().setWindowSize(oldSize);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Congestion occurred - new windowSize " + oldSize + " congestionSeenAt: "
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
+ ") for " + con);
return true;
} else if (numResends > 0) {
//} else if (numResends > 0) {
// window sizes are shrunk on resend, not on ack
} else {
if (acked > 0) {
@@ -127,9 +165,25 @@ public class ConnectionPacketHandler {
if (lowest >= con.getCongestionWindowEnd()) {
// new packet that ack'ed uncongested data, or an empty ack
int newWindowSize = con.getOptions().getWindowSize();
newWindowSize += 1; // acked; // 1
if (numResends <= 0) {
if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
// congestion avoidance
// we can't use newWindowSize += 1/newWindowSize, since we're
// integers, so lets use a random distribution instead
int shouldIncrement = _context.random().nextInt(newWindowSize);
if (shouldIncrement <= 0)
newWindowSize += 1;
} else {
// slow start
newWindowSize += 1;
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("New window size " + newWindowSize + " (#resends: " + numResends
_log.debug("New window size " + newWindowSize + " congestionSeenAt: "
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
+ ") for " + con);
con.getOptions().setWindowSize(newWindowSize);
con.setCongestionWindowEnd(newWindowSize + lowest);
@@ -248,4 +302,24 @@ public class ConnectionPacketHandler {
}
}
}
private class AckDup implements SimpleTimer.TimedEvent {
private long _created;
private Connection _con;
public AckDup(Connection con) {
_created = _context.clock().now();
_con = con;
}
public void timeReached() {
if (_con.getLastSendTime() <= _created) {
if (!_con.getIsConnected()) return;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Last sent was a while ago, and we want to ack a dup");
// we haven't done anything since receiving the dup, send an
// ack now
_con.ackImmediately();
}
}
}
}

View File

@@ -19,12 +19,17 @@ public class I2PSocketFull implements I2PSocket {
}
public void close() throws IOException {
if (_connection.getIsConnected()) {
_connection.getOutputStream().close();
_connection.disconnect(true);
Connection c = _connection;
if (c == null) return;
if (c.getIsConnected()) {
OutputStream out = c.getOutputStream();
if (out != null)
out.close();
c.disconnect(true);
} else {
throw new IOException("Not connected");
//throw new IOException("Not connected");
}
destroy();
}
public InputStream getInputStream() {
@@ -65,4 +70,9 @@ public class I2PSocketFull implements I2PSocket {
public void setSocketErrorListener(I2PSocket.SocketErrorListener lsnr) {
_listener = lsnr;
}
void destroy() {
_connection = null;
_listener = null;
}
}

View File

@@ -36,6 +36,7 @@ public class I2PSocketManagerFull implements I2PSocketManager {
private ConnectionOptions _defaultOptions;
private long _acceptTimeout;
private String _name;
private int _maxStreams;
private static int __managerId = 0;
private ConnectionManager _connectionManager;
@@ -54,6 +55,9 @@ public class I2PSocketManagerFull implements I2PSocketManager {
init(context, session, opts, name);
}
/** how many streams will we allow at once? */
public static final String PROP_MAX_STREAMS = "i2p.streaming.maxConcurrentStreams";
/**
*
*/
@@ -61,13 +65,33 @@ public class I2PSocketManagerFull implements I2PSocketManager {
_context = context;
_session = session;
_log = _context.logManager().getLog(I2PSocketManagerFull.class);
_connectionManager = new ConnectionManager(_context, _session);
_maxStreams = -1;
try {
String num = (opts != null ? opts.getProperty(PROP_MAX_STREAMS, "-1") : "-1");
_maxStreams = Integer.parseInt(num);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid max # of concurrent streams, defaulting to unlimited", nfe);
_maxStreams = -1;
}
_connectionManager = new ConnectionManager(_context, _session, _maxStreams);
_name = name + " " + (++__managerId);
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
_defaultOptions = new ConnectionOptions(opts);
_serverSocket = new I2PServerSocketFull(this);
if (_log.shouldLog(Log.INFO)) {
_log.info("Socket manager created. \ndefault options: " + _defaultOptions
+ "\noriginal properties: " + opts);
}
}
public I2PSocketOptions buildOptions() { return buildOptions(null); }
public I2PSocketOptions buildOptions(Properties opts) {
return new ConnectionOptions(opts);
}
public I2PSession getSession() {
return _session;
}
@@ -134,7 +158,16 @@ public class I2PSocketManagerFull implements I2PSocketManager {
throws I2PException, NoRouteToHostException {
if (_connectionManager.getSession().isClosed())
throw new I2PException("Session is closed");
Connection con = _connectionManager.connect(peer, new ConnectionOptions(options));
if (options == null)
options = _defaultOptions;
ConnectionOptions opts = null;
if (options instanceof ConnectionOptions)
opts = (ConnectionOptions)options;
else
opts = new ConnectionOptions(options);
Connection con = _connectionManager.connect(peer, opts);
if (con == null)
throw new TooManyStreamsException("Too many streams (max " + _maxStreams + ")");
I2PSocketFull socket = new I2PSocketFull(con);
con.setSocket(socket);
if (con.getConnectionError() != null) {
@@ -165,6 +198,14 @@ public class I2PSocketManagerFull implements I2PSocketManager {
_connectionManager.disconnectAllHard();
_connectionManager.setAllowIncomingConnections(false);
// should we destroy the _session too?
// yes, since the old lib did (and SAM wants it to, and i dont know why not)
if ( (_session != null) && (!_session.isClosed()) ) {
try {
_session.destroySession();
} catch (I2PSessionException ise) {
_log.warn("Unable to destroy the session", ise);
}
}
}
/**

View File

@@ -7,6 +7,8 @@ import net.i2p.client.I2PSessionException;
import net.i2p.util.Log;
/**
* Receive raw information from the I2PSession and turn it into
* Packets, if we can.
*
*/
public class MessageHandler implements I2PSessionListener {
@@ -18,6 +20,7 @@ public class MessageHandler implements I2PSessionListener {
_manager = mgr;
_context = ctx;
_log = ctx.logManager().getLog(MessageHandler.class);
_context.statManager().createRateStat("stream.packetReceiveFailure", "When do we fail to decrypt or otherwise receive a packet sent to us?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
}
/** Instruct the client that the given session has received a message with
@@ -31,6 +34,7 @@ public class MessageHandler implements I2PSessionListener {
try {
data = session.receiveMessage(msgId);
} catch (I2PSessionException ise) {
_context.statManager().addRateData("stream.packetReceiveFailure", 1, 0);
if (_log.shouldLog(Log.WARN))
_log.warn("Error receiving the message", ise);
return;
@@ -40,6 +44,7 @@ public class MessageHandler implements I2PSessionListener {
packet.readPacket(data, 0, data.length);
_manager.getPacketHandler().receivePacket(packet);
} catch (IllegalArgumentException iae) {
_context.statManager().addRateData("stream.packetReceiveFailure", 1, 0);
if (_log.shouldLog(Log.WARN))
_log.warn("Received an invalid packet", iae);
}

View File

@@ -53,6 +53,8 @@ public class MessageInputStream extends InputStream {
private IOException _streamError;
private long _readTotal;
private byte[] _oneByte = new byte[1];
private Object _dataLock;
public MessageInputStream(I2PAppContext ctx) {
@@ -205,6 +207,7 @@ public class MessageInputStream extends InputStream {
if (messageId <= _highestReadyBlockId) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("ignoring dup message " + messageId);
_dataLock.notifyAll();
return false; // already received
}
if (messageId > _highestBlockId)
@@ -238,76 +241,121 @@ public class MessageInputStream extends InputStream {
_notYetReadyBlocks.put(new Long(messageId), new ByteArray(null));
else
_notYetReadyBlocks.put(new Long(messageId), new ByteArray(payload));
_dataLock.notifyAll();
}
}
return true;
}
public int read() throws IOException {
int read = read(_oneByte, 0, 1);
if (read < 0)
return -1;
else
return _oneByte[0];
}
public int read(byte target[]) throws IOException {
return read(target, 0, target.length);
}
public int read(byte target[], int offset, int length) throws IOException {
if (_locallyClosed) throw new IOException("Already locally closed");
throwAnyError();
long expiration = -1;
if (_readTimeout > 0)
expiration = _readTimeout + System.currentTimeMillis();
synchronized (_dataLock) {
while (_readyDataBlocks.size() <= 0) {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("read() with readyBlocks.size = " + _readyDataBlocks.size() + " on " + toString());
if ( (_notYetReadyBlocks.size() <= 0) && (_closeReceived) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("read() got EOF after " + _readTotal + " " + toString());
return -1;
} else {
if (_readTimeout < 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("read() with no timeout: " + toString());
try { _dataLock.wait(); } catch (InterruptedException ie) { }
if (_log.shouldLog(Log.DEBUG))
_log.debug("read() with no timeout complete: " + toString());
throwAnyError();
} else if (_readTimeout > 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("read() with timeout: " + _readTimeout + ": " + toString());
try { _dataLock.wait(_readTimeout); } catch (InterruptedException ie) { }
if (_log.shouldLog(Log.DEBUG))
_log.debug("read() with timeout complete: " + _readTimeout + ": " + toString());
throwAnyError();
} else { // readTimeout == 0
// noop, don't block
if (_log.shouldLog(Log.DEBUG))
_log.debug("read() with nonblocking setup: " + toString());
for (int i = 0; i < length; i++) {
if ( (_readyDataBlocks.size() <= 0) && (i == 0) ) {
// ok, we havent found anything, so lets block until we get
// at least one byte
while (_readyDataBlocks.size() <= 0) {
if (_locallyClosed)
throw new IOException("Already closed, you wanker");
if ( (_notYetReadyBlocks.size() <= 0) && (_closeReceived) ) {
if (_log.shouldLog(Log.INFO))
_log.info("read(...," + offset + ", " + length + ")[" + i
+ "] got EOF after " + _readTotal + " " + toString());
return -1;
} else {
if (_readTimeout < 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("read(...," + offset+", " + length+ ")[" + i
+ ") with no timeout: " + toString());
try { _dataLock.wait(); } catch (InterruptedException ie) { }
if (_log.shouldLog(Log.DEBUG))
_log.debug("read(...," + offset+", " + length+ ")[" + i
+ ") with no timeout complete: " + toString());
throwAnyError();
} else if (_readTimeout > 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("read(...," + offset+", " + length+ ")[" + i
+ ") with timeout: " + _readTimeout + ": " + toString());
try { _dataLock.wait(_readTimeout); } catch (InterruptedException ie) { }
if (_log.shouldLog(Log.DEBUG))
_log.debug("read(...," + offset+", " + length+ ")[" + i
+ ") with timeout complete: " + _readTimeout + ": " + toString());
throwAnyError();
} else { // readTimeout == 0
// noop, don't block
if (_log.shouldLog(Log.INFO))
_log.info("read(...," + offset+", " + length+ ")[" + i
+ ") with nonblocking setup: " + toString());
return i;
}
if (_readyDataBlocks.size() <= 0) {
if ( (_readTimeout > 0) && (expiration < System.currentTimeMillis()) ) {
if (_log.shouldLog(Log.INFO))
_log.info("read(...," + offset+", " + length+ ")[" + i
+ ") expired: " + toString());
return i;
}
}
}
}
if (_readyDataBlocks.size() <= 0) {
if ( (_readTimeout > 0) && (expiration > System.currentTimeMillis()) )
throw new InterruptedIOException("Timeout reading (timeout=" + _readTimeout + ")");
// we looped a few times then got data, so this pass doesnt count
i--;
} else if (_readyDataBlocks.size() <= 0) {
if (_log.shouldLog(Log.INFO))
_log.info("read(...," + offset+", " + length+ ")[" + i
+ "] no more ready blocks, returning");
return i;
} else {
// either was already ready, or we wait()ed and it arrived
ByteArray cur = (ByteArray)_readyDataBlocks.get(0);
byte rv = cur.getData()[_readyDataBlockIndex];
_readyDataBlockIndex++;
if (cur.getData().length <= _readyDataBlockIndex) {
_readyDataBlockIndex = 0;
_readyDataBlocks.remove(0);
}
_readTotal++;
target[offset + i] = rv; // rv < 0 ? rv + 256 : rv
if ( (_readyDataBlockIndex <= 3) || (_readyDataBlockIndex >= cur.getData().length - 5) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("read(...," + offset+", " + length+ ")[" + i
+ "] after ready data: readyDataBlockIndex=" + _readyDataBlockIndex
+ " readyBlocks=" + _readyDataBlocks.size()
+ " readTotal=" + _readTotal);
}
}
}
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("read() readyBlocks = " + _readyDataBlocks.size() + ": " + toString());
// either was already ready, or we wait()ed and it arrived
ByteArray cur = (ByteArray)_readyDataBlocks.get(0);
byte rv = cur.getData()[_readyDataBlockIndex];
_readyDataBlockIndex++;
if (cur.getData().length <= _readyDataBlockIndex) {
_readyDataBlockIndex = 0;
_readyDataBlocks.remove(0);
}
_readTotal++;
return (rv < 0 ? rv + 256 : rv);
}
} // for (int i = 0; i < length; i++) {
} // synchronized (_dataLock)
if (_log.shouldLog(Log.DEBUG))
_log.info("read(...," + offset+", " + length+ ") read fully total read: " +_readTotal);
return length;
}
public int available() throws IOException {
if (_locallyClosed) throw new IOException("Already closed, you wanker");
throwAnyError();
int numBytes = 0;
synchronized (_dataLock) {
if (_readyDataBlocks.size() <= 0)
return 0;
int numBytes = 0;
for (int i = 0; i < _readyDataBlocks.size(); i++) {
ByteArray cur = (ByteArray)_readyDataBlocks.get(i);
if (i == 0)
@@ -315,8 +363,11 @@ public class MessageInputStream extends InputStream {
else
numBytes += cur.getData().length;
}
return numBytes;
}
if (_log.shouldLog(Log.DEBUG))
_log.info("available(): " + numBytes + " " + toString());
return numBytes;
}
/**
@@ -354,6 +405,7 @@ public class MessageInputStream extends InputStream {
ba.setData(null);
}
_locallyClosed = true;
_dataLock.notifyAll();
}
}

View File

@@ -5,10 +5,15 @@ import java.io.InterruptedIOException;
import java.io.OutputStream;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
*
* A stream that we can shove data into that fires off those bytes
* on flush or when the buffer is full. It also blocks according
* to the data receiver's needs.
*/
public class MessageOutputStream extends OutputStream {
private I2PAppContext _context;
@@ -21,20 +26,31 @@ public class MessageOutputStream extends OutputStream {
private boolean _closed;
private long _written;
private int _writeTimeout;
private ByteCache _dataCache;
private Flusher _flusher;
private long _lastFlushed;
private long _lastBuffered;
/** if we enqueue data but don't flush it in this period, flush it passively */
private int _passiveFlushDelay;
public MessageOutputStream(I2PAppContext ctx, DataReceiver receiver) {
this(ctx, receiver, Packet.MAX_PAYLOAD_SIZE);
}
public MessageOutputStream(I2PAppContext ctx, DataReceiver receiver, int bufSize) {
super();
_dataCache = ByteCache.getInstance(128, bufSize);
_context = ctx;
_log = ctx.logManager().getLog(MessageOutputStream.class);
_buf = new byte[bufSize];
_buf = _dataCache.acquire().getData(); // new byte[bufSize];
_dataReceiver = receiver;
_dataLock = new Object();
_written = 0;
_closed = false;
_writeTimeout = -1;
_passiveFlushDelay = 5*1000;
_flusher = new Flusher();
if (_log.shouldLog(Log.DEBUG))
_log.debug("MessageOutputStream created");
}
public void setWriteTimeout(int ms) { _writeTimeout = ms; }
@@ -45,10 +61,11 @@ public class MessageOutputStream extends OutputStream {
}
public void write(byte b[], int off, int len) throws IOException {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("write(b[], " + off + ", " + len + ")");
if (_log.shouldLog(Log.DEBUG))
_log.debug("write(b[], " + off + ", " + len + ") ");
int cur = off;
int remaining = len;
long begin = _context.clock().now();
while (remaining > 0) {
WriteStatus ws = null;
// we do any waiting outside the synchronized() block because we
@@ -56,6 +73,7 @@ public class MessageOutputStream extends OutputStream {
// this is the only method that *adds* to the _buf, and all
// code that reads from it is synchronized
synchronized (_dataLock) {
if (_buf == null) throw new IOException("closed (buffer went away)");
if (_valid + remaining < _buf.length) {
// simply buffer the data, no flush
System.arraycopy(b, cur, _buf, _valid, remaining);
@@ -63,6 +81,11 @@ public class MessageOutputStream extends OutputStream {
cur += remaining;
_written += remaining;
remaining = 0;
_lastBuffered = _context.clock().now();
if (_passiveFlushDelay > 0) {
// if it is already enqueued, this just pushes it further out
SimpleTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
}
} else {
// buffer whatever we can fit then flush,
// repeating until we've pushed all of the
@@ -72,13 +95,20 @@ public class MessageOutputStream extends OutputStream {
remaining -= toWrite;
cur += toWrite;
_valid = _buf.length;
if (_dataReceiver == null) {
throwAnyError();
return;
}
ws = _dataReceiver.writeData(_buf, 0, _valid);
_written += _valid;
_valid = 0;
throwAnyError();
_lastFlushed = _context.clock().now();
}
}
if (ws != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Waiting " + _writeTimeout + "ms for accept of " + ws);
// ok, we've actually added a new packet - lets wait until
// its accepted into the queue before moving on (so that we
// dont fill our buffer instantly)
@@ -89,8 +119,14 @@ public class MessageOutputStream extends OutputStream {
else
throw new IOException("Write not accepted into the queue");
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Queued " + len + " without sending to the receiver");
}
}
long elapsed = _context.clock().now() - begin;
if ( (elapsed > 10*1000) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("wtf, took " + elapsed + "ms to write to the stream?", new Exception("foo"));
throwAnyError();
}
@@ -99,40 +135,119 @@ public class MessageOutputStream extends OutputStream {
throwAnyError();
}
/**
* Flush data that has been enqued but not flushed after a certain
* period of inactivity
*/
private class Flusher implements SimpleTimer.TimedEvent {
public void timeReached() {
boolean sent = false;
WriteStatus ws = null;
synchronized (_dataLock) {
if ( (_valid > 0) && (_lastBuffered + _passiveFlushDelay > _context.clock().now()) ) {
if ( (_buf != null) && (_dataReceiver != null) ) {
ws = _dataReceiver.writeData(_buf, 0, _valid);
_written += _valid;
_valid = 0;
_lastFlushed = _context.clock().now();
_dataLock.notifyAll();
sent = true;
}
}
}
// ignore the ws
if (sent && _log.shouldLog(Log.DEBUG))
_log.debug("Passive flush of " + ws);
}
}
/**
* Flush the data already queued up, blocking until it has been
* delivered.
*
* @throws IOException if the write fails
* @throws InterruptedIOException if the write times out
*/
public void flush() throws IOException {
long begin = _context.clock().now();
WriteStatus ws = null;
synchronized (_dataLock) {
if (_buf == null) throw new IOException("closed (buffer went away)");
if (_dataReceiver == null) {
throwAnyError();
return;
}
ws = _dataReceiver.writeData(_buf, 0, _valid);
_written += _valid;
_valid = 0;
_lastFlushed = _context.clock().now();
_dataLock.notifyAll();
}
ws.waitForCompletion(_writeTimeout);
if (_log.shouldLog(Log.DEBUG))
_log.debug("before waiting " + _writeTimeout + "ms for completion of " + ws);
if (_closed &&
( (_writeTimeout > Connection.DISCONNECT_TIMEOUT) ||
(_writeTimeout <= 0) ) )
ws.waitForCompletion(Connection.DISCONNECT_TIMEOUT);
else if ( (_writeTimeout <= 0) || (_writeTimeout > Connection.DISCONNECT_TIMEOUT) )
ws.waitForCompletion(Connection.DISCONNECT_TIMEOUT);
else
ws.waitForCompletion(_writeTimeout);
if (_log.shouldLog(Log.DEBUG))
_log.debug("after waiting " + _writeTimeout + "ms for completion of " + ws);
if (ws.writeFailed() && (_writeTimeout > 0) )
throw new InterruptedIOException("Timed out during write");
else if (ws.writeFailed())
throw new IOException("Write failed");
long elapsed = _context.clock().now() - begin;
if ( (elapsed > 10*1000) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("wtf, took " + elapsed + "ms to flush the stream?\n" + ws, new Exception("bar"));
throwAnyError();
}
public void close() throws IOException {
if (_closed) return;
_closed = true;
flush();
_log.debug("Output stream closed after writing " + _written);
ByteArray ba = null;
synchronized (_dataLock) {
if (_buf != null) {
ba = new ByteArray(_buf);
_buf = null;
_valid = 0;
}
}
if (ba != null) {
_dataCache.release(ba);
}
}
/** nonblocking close */
public void closeInternal() {
_closed = true;
_streamError = new IOException("Closed internally");
ByteArray ba = null;
synchronized (_dataLock) {
// flush any data, but don't wait for it
if (_valid > 0) {
if (_dataReceiver != null)
_dataReceiver.writeData(_buf, 0, _valid);
_written += _valid;
_written += _valid;
_valid = 0;
if (_buf != null) {
ba = new ByteArray(_buf);
_buf = null;
_valid = 0;
}
_lastFlushed = _context.clock().now();
_dataLock.notifyAll();
}
if (ba != null) {
_dataCache.release(ba);
}
}
public boolean getClosed() { return _closed; }
@@ -161,12 +276,15 @@ public class MessageOutputStream extends OutputStream {
void flushAvailable(DataReceiver target, boolean blocking) throws IOException {
WriteStatus ws = null;
synchronized (_dataLock) {
// _buf may be null, but the data receiver can handle that just fine,
// deciding whether or not to send a packet
ws = target.writeData(_buf, 0, _valid);
_written += _valid;
_valid = 0;
_dataLock.notifyAll();
_lastFlushed = _context.clock().now();
}
if (blocking) {
if (blocking && ws != null) {
ws.waitForAccept(_writeTimeout);
if (ws.writeFailed())
throw new IOException("Flush available failed");
@@ -176,6 +294,11 @@ public class MessageOutputStream extends OutputStream {
return;
}
void destroy() {
_dataReceiver = null;
}
/** Define a component to receive data flushed from this stream */
public interface DataReceiver {
/**
* Nonblocking write
@@ -183,6 +306,7 @@ public class MessageOutputStream extends OutputStream {
public WriteStatus writeData(byte buf[], int off, int size);
}
/** Define a way to detect the status of a write */
public interface WriteStatus {
/** wait until the data written either fails or succeeds */
public void waitForCompletion(int maxWaitMs);

View File

@@ -206,6 +206,9 @@ public class Packet {
if ( (payload != null) && (payload.length > MAX_PAYLOAD_SIZE) )
throw new IllegalArgumentException("Too large payload: " + payload.length);
}
public int getPayloadSize() {
return (_payload == null ? 0 : _payload.length);
}
/** is a particular flag set on this packet? */
public boolean isFlagSet(int flag) { return 0 != (_flags & flag); }
@@ -273,12 +276,12 @@ public class Packet {
*/
private int writePacket(byte buffer[], int offset, boolean includeSig) throws IllegalStateException {
int cur = offset;
if (_sendStreamId != null)
if ( (_sendStreamId != null) && (_sendStreamId.length == 4) )
System.arraycopy(_sendStreamId, 0, buffer, cur, _sendStreamId.length);
else
System.arraycopy(STREAM_ID_UNKNOWN, 0, buffer, cur, STREAM_ID_UNKNOWN.length);
cur += 4;
if (_receiveStreamId != null)
if ( (_receiveStreamId != null) && (_receiveStreamId.length == 4) )
System.arraycopy(_receiveStreamId, 0, buffer, cur, _receiveStreamId.length);
else
System.arraycopy(STREAM_ID_UNKNOWN, 0, buffer, cur, STREAM_ID_UNKNOWN.length);
@@ -354,8 +357,8 @@ public class Packet {
*/
public int writtenSize() throws IllegalStateException {
int size = 0;
size += _sendStreamId.length;
size += _receiveStreamId.length;
size += 4; // _sendStreamId.length;
size += 4; // _receiveStreamId.length;
size += 4; // sequenceNum
size += 4; // ackThrough
if (_nacks != null) {
@@ -396,6 +399,10 @@ public class Packet {
* @throws IllegalArgumentException if the data is b0rked
*/
public void readPacket(byte buffer[], int offset, int length) throws IllegalArgumentException {
if (buffer.length - offset < length)
throw new IllegalArgumentException("len=" + buffer.length + " off=" + offset + " length=" + length);
if (length < 22) // min header size
throw new IllegalArgumentException("Too small: len=" + buffer.length);
int cur = offset;
_sendStreamId = new byte[4];
System.arraycopy(buffer, cur, _sendStreamId, 0, 4);
@@ -409,6 +416,8 @@ public class Packet {
cur += 4;
int numNacks = (int)DataHelper.fromLong(buffer, cur, 1);
cur++;
if (length < 22 + numNacks*4)
throw new IllegalArgumentException("Too small with " + numNacks + " nacks: " + length);
if (numNacks > 0) {
_nacks = new long[numNacks];
for (int i = 0; i < numNacks; i++) {
@@ -425,13 +434,19 @@ public class Packet {
int optionSize = (int)DataHelper.fromLong(buffer, cur, 2);
cur += 2;
if (length < 22 + numNacks*4 + optionSize)
throw new IllegalArgumentException("Too small with " + numNacks + " nacks and "
+ optionSize + " options: " + length);
int payloadBegin = cur + optionSize;
int payloadSize = length - payloadBegin;
if ( (payloadSize < 0) || (payloadSize > MAX_PAYLOAD_SIZE) )
throw new IllegalArgumentException("length: " + length + " offset: " + offset + " begin: " + payloadBegin);
// skip ahead to the payload
_payload = new byte[offset + length - payloadBegin];
if (_payload.length > MAX_PAYLOAD_SIZE)
throw new IllegalArgumentException("length: " + length + " offset: " + offset + " begin: " + payloadBegin);
System.arraycopy(buffer, payloadBegin, _payload, 0, _payload.length);
_payload = new byte[payloadSize];
System.arraycopy(buffer, payloadBegin, _payload, 0, payloadSize);
// ok now lets go back and deal with the options
if (isFlagSet(FLAG_DELAY_REQUESTED)) {
@@ -480,7 +495,7 @@ public class Packet {
}
boolean ok = ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, from.getSigningPublicKey());
if (!ok) {
ctx.logManager().getLog(Packet.class).error("Signature failed with sig " + Base64.encode(_optionSignature.getData()), new Exception("moo"));
ctx.logManager().getLog(Packet.class).error("Signature failed on " + toString(), new Exception("moo"));
}
return ok;
}

View File

@@ -105,12 +105,13 @@ public class PacketHandler {
}
private static final SimpleDateFormat _fmt = new SimpleDateFormat("hh:mm:ss.SSS");
static void displayPacket(Packet packet, String prefix) {
void displayPacket(Packet packet, String prefix) {
String msg = null;
synchronized (_fmt) {
msg = _fmt.format(new Date()) + ": " + prefix + " " + packet.toString();
}
System.out.println(msg);
if (_log.shouldLog(Log.DEBUG))
System.out.println(msg);
}
private void receiveKnownCon(Connection con, Packet packet) {
@@ -173,22 +174,20 @@ public class PacketHandler {
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received on an unknown stream (and not a SYN): " + packet);
_log.debug("Packet received on an unknown stream (and not an ECHO): " + packet);
if (sendId == null) {
for (Iterator iter = _manager.listConnections().iterator(); iter.hasNext(); ) {
Connection con = (Connection)iter.next();
if (DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) {
if (con.getAckedPackets() <= 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received additional packets before the syn on " + con + ": " + packet);
receiveKnownCon(con, packet);
return;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("hrmph, received while ack of syn was in flight on " + con + ": " + packet + " acked: " + con.getAckedPackets());
receiveKnownCon(con, packet);
return;
}
Connection con = _manager.getConnectionByOutboundId(packet.getReceiveStreamId());
if (con != null) {
if (con.getAckedPackets() <= 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received additional packets before the syn on " + con + ": " + packet);
receiveKnownCon(con, packet);
return;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("hrmph, received while ack of syn was in flight on " + con + ": " + packet + " acked: " + con.getAckedPackets());
receiveKnownCon(con, packet);
return;
}
}
}

View File

@@ -56,7 +56,8 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
public boolean shouldSign() {
return isFlagSet(FLAG_SIGNATURE_INCLUDED) ||
isFlagSet(FLAG_SYNCHRONIZE) ||
isFlagSet(FLAG_CLOSE);
isFlagSet(FLAG_CLOSE) ||
isFlagSet(FLAG_ECHO);
}
/** last minute update of ack fields, just before write/sign */
@@ -66,6 +67,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
}
public long getCreatedOn() { return _createdOn; }
public long getLifetime() { return _context.clock().now() - _createdOn; }
public void incrementSends() {
_numSends++;
_lastSend = _context.clock().now();
@@ -76,12 +78,14 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
_ackOn = _context.clock().now();
notifyAll();
}
_connection = null;
}
public void cancelled() {
synchronized (this) {
_cancelledOn = _context.clock().now();
notifyAll();
}
_connection = null;
}
/** how long after packet creation was it acked? */
@@ -112,18 +116,26 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
_acceptedOn = _context.clock().now();
else
_acceptedOn = -1;
_connection = null;
}
public void waitForCompletion(int maxWaitMs) {
_connection = null;
long expiration = _context.clock().now()+maxWaitMs;
while ((maxWaitMs <= 0) || (expiration < _context.clock().now())) {
synchronized (this) {
if (_ackOn > 0)
return;
if (_cancelledOn > 0)
return;
try { wait(); } catch (InterruptedException ie) {}
}
while (true) {
long timeRemaining = expiration - _context.clock().now();
if ( (timeRemaining <= 0) && (maxWaitMs > 0) ) return;
try {
synchronized (this) {
if (_ackOn > 0) return;
if (_cancelledOn > 0) return;
if (timeRemaining > 60*1000)
timeRemaining = 60*1000;
else if (timeRemaining <= 0)
timeRemaining = 10*1000;
wait(timeRemaining);
}
} catch (InterruptedException ie) {}
}
}

View File

@@ -1,28 +1,45 @@
package net.i2p.client.streaming;
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.ByteArray;
import net.i2p.data.SessionKey;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**
* Queue out packets to be sent through the session.
* Well, thats the theory at least... in practice we just
* send them immediately with no blocking, since the
* mode=bestEffort doesnt block in the SDK.
*
*/
class PacketQueue {
private I2PAppContext _context;
private Log _log;
private I2PSession _session;
private ConnectionManager _connectionManager;
private byte _buf[];
private ByteCache _cache = ByteCache.getInstance(64, 36*1024);
public PacketQueue(I2PAppContext context, I2PSession session) {
public PacketQueue(I2PAppContext context, I2PSession session, ConnectionManager mgr) {
_context = context;
_session = session;
_buf = new byte[36*1024];
_connectionManager = mgr;
_buf = _cache.acquire().getData(); // new byte[36*1024];
_log = context.logManager().getLog(PacketQueue.class);
_context.statManager().createRateStat("stream.con.sendMessageSize", "Size of a message sent on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.con.sendDuplicateSize", "Size of a message resent on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
}
protected void finalize() throws Throwable {
_cache.release(new ByteArray(_buf));
super.finalize();
}
/**
@@ -30,11 +47,6 @@ class PacketQueue {
*/
public void enqueue(PacketLocal packet) {
packet.prepare();
int size = 0;
if (packet.shouldSign())
size = packet.writeSignedPacket(_buf, 0, _context, _session.getPrivateKey());
else
size = packet.writePacket(_buf, 0);
SessionKey keyUsed = packet.getKeyUsed();
if (keyUsed == null)
@@ -42,42 +54,70 @@ class PacketQueue {
Set tagsSent = packet.getTagsSent();
if (tagsSent == null)
tagsSent = new HashSet();
// cache this from before sendMessage
String conStr = (packet.getConnection() != null ? packet.getConnection().toString() : "");
if (packet.getAckTime() > 0) {
_log.debug("Not resending " + packet);
return;
} else {
_log.debug("Sending... " + packet);
}
long begin = 0;
long end = 0;
boolean sent = false;
try {
// cache this from before sendMessage
String conStr = (packet.getConnection() != null ? packet.getConnection().toString() : "");
if (packet.getAckTime() > 0) {
_log.debug("Not resending " + packet);
return;
} else {
_log.debug("Sending... " + packet);
int size = 0;
synchronized (this) {
Arrays.fill(_buf, (byte)0x0);
if (packet.shouldSign())
size = packet.writeSignedPacket(_buf, 0, _context, _session.getPrivateKey());
else
size = packet.writePacket(_buf, 0);
// this should not block!
begin = _context.clock().now();
sent = _session.sendMessage(packet.getTo(), _buf, 0, size, keyUsed, tagsSent);
end = _context.clock().now();
}
// this should not block!
long begin = _context.clock().now();
boolean sent = _session.sendMessage(packet.getTo(), _buf, 0, size, keyUsed, tagsSent);
long end = _context.clock().now();
if (!sent) {
if (_log.shouldLog(Log.WARN))
_log.warn("Send failed for " + packet);
packet.getConnection().disconnect(false);
} else {
packet.setKeyUsed(keyUsed);
packet.setTagsSent(tagsSent);
packet.incrementSends();
if (_log.shouldLog(Log.DEBUG)) {
String msg = "SEND " + packet + (tagsSent.size() > 0
? " with " + tagsSent.size() + " tags"
: "")
+ " send # " + packet.getNumSends()
+ " sendTime: " + (end-begin)
+ " con: " + conStr;
_log.debug(msg);
}
PacketHandler.displayPacket(packet, "SEND");
_context.statManager().addRateData("stream.con.sendMessageSize", size, packet.getLifetime());
if (packet.getNumSends() > 1)
_context.statManager().addRateData("stream.con.sendDuplicateSize", size, packet.getLifetime());
Connection con = packet.getConnection();
if (con != null) {
con.incrementBytesSent(size);
if (packet.getNumSends() > 1)
con.incrementDupMessagesSent(1);
}
} catch (I2PSessionException ise) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to send the packet " + packet, ise);
}
if (!sent) {
if (_log.shouldLog(Log.WARN))
_log.warn("Send failed for " + packet);
Connection c = packet.getConnection();
if (c != null) // handle race on b0rk
c.disconnect(false);
} else {
packet.setKeyUsed(keyUsed);
packet.setTagsSent(tagsSent);
packet.incrementSends();
if (_log.shouldLog(Log.DEBUG)) {
String msg = "SEND " + packet + (tagsSent.size() > 0
? " with " + tagsSent.size() + " tags"
: "")
+ " send # " + packet.getNumSends()
+ " sendTime: " + (end-begin)
+ " con: " + conStr;
_log.debug(msg);
}
_connectionManager.getPacketHandler().displayPacket(packet, "SEND");
}
}
}

View File

@@ -31,21 +31,24 @@ class SchedulerClosed extends SchedulerImpl {
_log = ctx.logManager().getLog(SchedulerClosed.class);
}
static final long CLOSE_TIMEOUT = 30*1000;
public boolean accept(Connection con) {
boolean ok = (con != null) &&
(con.getCloseSentOn() > 0) &&
if (con == null) return false;
long timeSinceClose = _context.clock().now() - con.getCloseSentOn();
boolean ok = (con.getCloseSentOn() > 0) &&
(con.getCloseReceivedOn() > 0) &&
(con.getUnackedPacketsReceived() <= 0) &&
(con.getUnackedPacketsSent() <= 0) &&
(!con.getResetReceived()) &&
(con.getCloseSentOn() + CLOSE_TIMEOUT > _context.clock().now());
return ok;
(timeSinceClose < Connection.DISCONNECT_TIMEOUT);
boolean conTimeout = (con.getOptions().getConnectTimeout() < con.getLifetime()) &&
con.getSendStreamId() == null &&
con.getLifetime() < Connection.DISCONNECT_TIMEOUT;
return (ok || conTimeout);
}
public void eventOccurred(Connection con) {
long timeLeft = con.getCloseSentOn() + CLOSE_TIMEOUT - _context.clock().now();
reschedule(timeLeft, con);
// noop. we do the timeout through the simpleTimer anyway
//long timeLeft = con.getCloseSentOn() + Connection.DISCONNECT_TIMEOUT - _context.clock().now();
//reschedule(timeLeft, con);
}
}

View File

@@ -45,9 +45,20 @@ class SchedulerClosing extends SchedulerImpl {
if (con.getNextSendTime() <= 0)
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
long remaining = con.getNextSendTime() - _context.clock().now();
if (remaining <= 0)
con.sendAvailable();
else
if (_log.shouldLog(Log.DEBUG))
_log.debug("Event occurred w/ remaining: " + remaining + " on " + con);
if (remaining <= 0) {
if (con.getCloseSentOn() <= 0) {
con.sendAvailable();
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
} else {
con.ackImmediately();
}
} else {
//if (remaining < 5*1000)
// remaining = 5*1000;
//con.setNextSendTime(when
reschedule(remaining, con);
}
}
}

View File

@@ -43,9 +43,9 @@ class SchedulerConnectedBulk extends SchedulerImpl {
(!con.getResetReceived()) &&
( (con.getCloseSentOn() <= 0) || (con.getCloseReceivedOn() <= 0) );
if (!ok) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("con: " + con + " closeSentOn: " + con.getCloseSentOn()
+ " closeReceivedOn: " + con.getCloseReceivedOn());
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("con: " + con + " closeSentOn: " + con.getCloseSentOn()
// + " closeReceivedOn: " + con.getCloseReceivedOn());
}
return ok;
}

View File

@@ -35,10 +35,13 @@ class SchedulerConnecting extends SchedulerImpl {
}
public boolean accept(Connection con) {
return (con != null) &&
(con.getLastSendId() >= 0) &&
(con.getAckedPackets() <= 0) &&
(!con.getResetReceived());
if (con == null) return false;
boolean notYetConnected = (con.getIsConnected()) &&
(con.getSendStreamId() == null) &&
(con.getLastSendId() >= 0) &&
(con.getAckedPackets() <= 0) &&
(!con.getResetReceived());
return notYetConnected;
}
public void eventOccurred(Connection con) {

View File

@@ -31,14 +31,17 @@ class SchedulerDead extends SchedulerImpl {
}
public boolean accept(Connection con) {
boolean ok = (con != null) &&
(con.getResetReceived()) ||
((con.getCloseSentOn() > 0) &&
(con.getCloseReceivedOn() > 0) &&
(con.getUnackedPacketsReceived() <= 0) &&
(con.getUnackedPacketsSent() <= 0) &&
(con.getCloseSentOn() + SchedulerClosed.CLOSE_TIMEOUT <= _context.clock().now()));
return ok;
if (con == null) return false;
long timeSinceClose = _context.clock().now() - con.getCloseSentOn();
boolean nothingLeftToDo = (con.getCloseSentOn() > 0) &&
(con.getCloseReceivedOn() > 0) &&
(con.getUnackedPacketsReceived() <= 0) &&
(con.getUnackedPacketsSent() <= 0) &&
(timeSinceClose >= Connection.DISCONNECT_TIMEOUT);
boolean timedOut = (con.getOptions().getConnectTimeout() < con.getLifetime()) &&
con.getSendStreamId() == null &&
con.getLifetime() >= Connection.DISCONNECT_TIMEOUT;
return con.getResetReceived() || nothingLeftToDo || timedOut;
}
public void eventOccurred(Connection con) {

View File

@@ -31,10 +31,15 @@ class SchedulerReceived extends SchedulerImpl {
long timeTillSend = con.getNextSendTime() - _context.clock().now();
if (timeTillSend <= 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("received con... send a packet");
con.sendAvailable();
con.setNextSendTime(-1);
if (con.getNextSendTime() > 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("received con... send a packet");
con.sendAvailable();
con.setNextSendTime(-1);
} else {
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
reschedule(con.getOptions().getSendAckDelay(), con);
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("received con... time till next send: " + timeTillSend);

View File

@@ -0,0 +1,21 @@
package net.i2p.client.streaming;
import net.i2p.I2PException;
/**
* We attempted to have more open streams than we are willing to put up with
*
*/
public class TooManyStreamsException extends I2PException {
public TooManyStreamsException(String message, Throwable parent) {
super(message, parent);
}
public TooManyStreamsException(String message) {
super(message);
}
public TooManyStreamsException() {
super();
}
}

View File

@@ -0,0 +1,131 @@
package net.i2p.client.streaming;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
*
*/
public class ConnectInactivityTest {
private Log _log;
private I2PSession _client;
private I2PSession _server;
public void test() {
try {
I2PAppContext context = I2PAppContext.getGlobalContext();
_log = context.logManager().getLog(ConnectTest.class);
_log.debug("creating server session");
_server = createSession();
_log.debug("running server");
runServer(context, _server);
_log.debug("creating client session");
_client = createSession();
_log.debug("running client");
runClient(context, _client);
} catch (Exception e) {
_log.error("error running", e);
}
}
private void runClient(I2PAppContext ctx, I2PSession session) {
Thread t = new Thread(new ClientRunner(ctx, session));
t.setName("client");
t.setDaemon(false);
t.start();
}
private void runServer(I2PAppContext ctx, I2PSession session) {
Thread t = new Thread(new ServerRunner(ctx, session));
t.setName("server");
t.setDaemon(false);
t.start();
}
private class ServerRunner implements Runnable {
private I2PAppContext _context;
private I2PSession _session;
private Log _log;
public ServerRunner(I2PAppContext ctx, I2PSession session) {
_context = ctx;
_session = session;
_log = ctx.logManager().getLog(ServerRunner.class);
}
public void run() {
try {
Properties opts = new Properties();
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
_log.debug("manager created");
I2PServerSocket ssocket = mgr.getServerSocket();
_log.debug("server socket created");
I2PSocket socket = ssocket.accept();
_log.debug("socket accepted: " + socket);
try { Thread.sleep(10*60*1000); } catch (InterruptedException ie) {}
socket.close();
ssocket.close();
_session.destroySession();
} catch (Exception e) {
_log.error("error running", e);
}
}
}
private class ClientRunner implements Runnable {
private I2PAppContext _context;
private I2PSession _session;
private Log _log;
public ClientRunner(I2PAppContext ctx, I2PSession session) {
_context = ctx;
_session = session;
_log = ctx.logManager().getLog(ClientRunner.class);
}
public void run() {
try {
Properties opts = new Properties();
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
_log.debug("manager created");
I2PSocket socket = mgr.connect(_server.getMyDestination());
_log.debug("socket created");
try { Thread.sleep(10*60*1000); } catch (InterruptedException ie) {}
socket.close();
_log.debug("socket closed");
//_session.destroySession();
} catch (Exception e) {
_log.error("error running", e);
}
}
}
private I2PSession createSession() {
try {
I2PClient client = I2PClientFactory.createClient();
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
Destination dest = client.createDestination(baos);
Properties p = new Properties();
p.setProperty(I2PClient.PROP_TCP_HOST, "localhost");
p.setProperty(I2PClient.PROP_TCP_PORT, "10001");
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), p);
sess.connect();
return sess;
} catch (Exception e) {
_log.error("error running", e);
throw new RuntimeException("b0rk b0rk b0rk");
}
}
public static void main(String args[]) {
ConnectInactivityTest ct = new ConnectInactivityTest();
ct.test();
}
}

View File

@@ -16,7 +16,6 @@ import net.i2p.util.Log;
*/
public class ConnectTest {
private Log _log;
private I2PSession _client;
private I2PSession _server;
public void test() {
try {
@@ -26,14 +25,14 @@ public class ConnectTest {
_server = createSession();
_log.debug("running server");
runServer(context, _server);
_log.debug("creating client session");
_client = createSession();
_log.debug("running client");
runClient(context, _client);
for (int i = 0; i < 5; i++) {
_log.debug("running client");
runClient(context, createSession());
}
} catch (Exception e) {
_log.error("error running", e);
}
try { Thread.sleep(30*1000); } catch (Exception e) {}
try { Thread.sleep(10*60*1000); } catch (Exception e) {}
}
private void runClient(I2PAppContext ctx, I2PSession session) {
@@ -70,7 +69,7 @@ public class ConnectTest {
while (true) {
I2PSocket socket = ssocket.accept();
_log.debug("socket accepted: " + socket);
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
socket.close();
}
} catch (Exception e) {
@@ -97,8 +96,12 @@ public class ConnectTest {
_log.debug("manager created");
I2PSocket socket = mgr.connect(_server.getMyDestination());
_log.debug("socket created");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
socket.close();
_log.debug("socket closed");
mgr.destroySocketManager();
mgr = null;
socket = null;
} catch (Exception e) {
_log.error("error running", e);
}
@@ -111,7 +114,7 @@ public class ConnectTest {
I2PClient client = I2PClientFactory.createClient();
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
Destination dest = client.createDestination(baos);
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), new Properties());
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), System.getProperties());
sess.connect();
return sess;
} catch (Exception e) {
@@ -121,6 +124,8 @@ public class ConnectTest {
}
public static void main(String args[]) {
System.setProperty(I2PClient.PROP_TCP_HOST, "localhost");
System.setProperty(I2PClient.PROP_TCP_PORT, "11001");
ConnectTest ct = new ConnectTest();
ct.test();
}

View File

@@ -0,0 +1,110 @@
package net.i2p.client.streaming;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Try to connect to a new nonexistant peer and, of course,
* timeout.
*/
public class ConnectTimeoutTest {
private Log _log;
private I2PSession _client;
private I2PSession _server;
private Destination _serverDest;
public void testNonexistant() {
try {
I2PAppContext context = I2PAppContext.getGlobalContext();
_log = context.logManager().getLog(ConnectTest.class);
_log.debug("creating server dest");
try {
_serverDest = I2PClientFactory.createClient().createDestination(new ByteArrayOutputStream());
} catch (Exception e) {}
_log.debug("creating client session");
_client = createSession();
_log.debug("running client");
runClient(context, _client);
} catch (Exception e) {
_log.error("error running", e);
}
while (true) { synchronized (this) { try { wait(); } catch (Exception e) {} } }
}
private void runClient(I2PAppContext ctx, I2PSession session) {
Thread t = new Thread(new ClientRunner(ctx, session));
t.setName("client");
t.setDaemon(true);
t.start();
}
private class ClientRunner implements Runnable {
private I2PAppContext _context;
private I2PSession _session;
private Log _log;
public ClientRunner(I2PAppContext ctx, I2PSession session) {
_context = ctx;
_session = session;
_log = ctx.logManager().getLog(ClientRunner.class);
}
public void run() {
try {
I2PSocketManager mgr = I2PSocketManagerFactory.createManager("localhost", 10001, getProps());
_log.debug("manager created");
_log.debug("options: " + mgr.getDefaultOptions());
I2PSocket socket = mgr.connect(_serverDest);
_log.debug("socket created");
socket.getOutputStream().write("you smell".getBytes());
socket.getOutputStream().flush();
_log.error("wtf, shouldn't have flushed");
socket.close();
_log.debug("socket closed");
} catch (Exception e) {
_log.error("error running (yay!)", e);
}
}
}
private I2PSession createSession() {
try {
I2PClient client = I2PClientFactory.createClient();
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
Destination dest = client.createDestination(baos);
Properties p = getProps();
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), p);
sess.connect();
return sess;
} catch (Exception e) {
_log.error("error running", e);
throw new RuntimeException("b0rk b0rk b0rk");
}
}
public static void main(String args[]) {
ConnectTimeoutTest ct = new ConnectTimeoutTest();
ct.testNonexistant();
}
private static Properties getProps() {
Properties p = new Properties();
p.setProperty(I2PSocketManagerFactory.PROP_MANAGER, I2PSocketManagerFull.class.getName());
p.setProperty("tunnels.depthInbound", "0");
p.setProperty(I2PClient.PROP_TCP_HOST, "localhost");
p.setProperty(I2PClient.PROP_TCP_PORT, "10001");
p.setProperty(ConnectionOptions.PROP_CONNECT_TIMEOUT, "30000");
//p.setProperty(ConnectionOptions.PROP_CONNECT_DELAY, "10000");
p.setProperty(ConnectionOptions.PROP_CONNECT_DELAY, "0");
return p;
}
}

View File

@@ -110,12 +110,52 @@ public class MessageInputStreamTest {
}
}
public void testStaggered() {
byte orig[] = new byte[256*1024];
byte read[] = new byte[orig.length];
_context.random().nextBytes(orig);
MessageInputStream in = new MessageInputStream(_context);
ArrayList order = new ArrayList(32);
for (int i = 0; i < orig.length / 1024; i++)
order.add(new Integer(i));
Collections.shuffle(order);
int offset = 0;
for (int i = 0; i < orig.length / 1024; i++) {
byte msg[] = new byte[1024];
Integer cur = (Integer)order.get(i);
System.arraycopy(orig, cur.intValue()*1024, msg, 0, 1024);
in.messageReceived(cur.intValue(), msg);
_log.debug("Injecting " + cur);
try {
if (in.available() > 0) {
int curRead = in.read(read, offset, read.length-offset);
_log.debug("read " + curRead);
if (curRead == -1)
throw new RuntimeException("EOF with offset " + offset);
else
offset += curRead;
}
} catch (IOException ioe) {
throw new RuntimeException("IOE: " + ioe.getMessage());
}
}
if (!DataHelper.eq(orig, read))
throw new RuntimeException("Failed test: data read is not equal");
_log.info("Passed test: staggered");
}
public static void main(String args[]) {
MessageInputStreamTest t = new MessageInputStreamTest();
try {
t.testInOrder();
t.testRandomOrder();
t.testRandomDups();
t.testStaggered();
} catch (Exception e) {
e.printStackTrace();
}

View File

@@ -19,7 +19,7 @@ public class PingTest {
try {
I2PAppContext context = I2PAppContext.getGlobalContext();
I2PSession session = createSession();
ConnectionManager mgr = new ConnectionManager(context, session);
ConnectionManager mgr = new ConnectionManager(context, session, -1);
Log log = context.logManager().getLog(PingTest.class);
for (int i = 0; i < 10; i++) {
log.debug("ping " + i);

View File

@@ -46,8 +46,8 @@ public class SysTray implements SysTrayMenuListener {
_portString = _configFile.getProperty("port", "7657");
_showIcon = Boolean.valueOf(_configFile.getProperty("visible", "true")).booleanValue();
if (!(new File("router.config")).exists())
openRouterConsole("http://localhost:" + _portString + "/index.jsp");
//if (!(new File("router.config")).exists())
// openRouterConsole("http://localhost:" + _portString + "/index.jsp");
if ( (System.getProperty("os.name").startsWith("Windows")) && (!Boolean.getBoolean("systray.disable")) )
_instance = new SysTray();

View File

@@ -147,4 +147,14 @@ public class UrlLauncher {
}
return true;
}
public static void main(String args[]) {
UrlLauncher launcher = new UrlLauncher();
try {
if (args.length > 0)
launcher.openUrl(args[0]);
else
launcher.openUrl("http://localhost:7657/index.jsp");
} catch (Exception e) {}
}
}

View File

@@ -181,7 +181,6 @@
<copy file="installer/resources/clients.config" todir="pkg-temp/" />
<copy file="installer/resources/i2prouter" todir="pkg-temp/" />
<copy file="installer/resources/i2prouter.bat" todir="pkg-temp/" />
<copy file="installer/resources/i2prouter_win9x.bat" todir="pkg-temp/" />
<copy file="installer/resources/i2ptunnel.config" todir="pkg-temp/" />
<!-- <copy file="installer/resources/install_i2p_service_unix" todir="pkg-temp/" /> -->
<copy file="installer/resources/install_i2p_service_winnt.bat" todir="pkg-temp/" />
@@ -212,11 +211,16 @@
<copy file="history.txt" todir="pkg-temp/" />
<mkdir dir="pkg-temp/docs" />
<copy file="readme.html" todir="pkg-temp/docs/" />
<copy file="installer/resources/startconsole.html" todir="pkg-temp/docs/" />
<copy file="installer/resources/start.ico" todir="pkg-temp/docs/" />
<copy file="installer/resources/console.ico" todir="pkg-temp/docs/" />
<copy file="installer/resources/uninstall.ico" todir="pkg-temp/docs/" />
<mkdir dir="pkg-temp/eepsite" />
<mkdir dir="pkg-temp/eepsite/webapps" />
<mkdir dir="pkg-temp/eepsite/logs" />
<mkdir dir="pkg-temp/eepsite/docroot" />
<copy file="installer/resources/eepsite_index.html" tofile="pkg-temp/eepsite/docroot/index.html" />
<copy file="installer/resources/favicon.ico" tofile="pkg-temp/eepsite/docroot/favicon.ico" />
<copy file="installer/resources/jetty.xml" tofile="pkg-temp/eepsite/jetty.xml" />
</target>
<target name="tarball" depends="preppkg">
@@ -235,6 +239,8 @@
<copy file="build/routerconsole.jar" todir="pkg-temp/lib/" />
<copy file="build/i2ptunnel.war" todir="pkg-temp/webapps/" />
<copy file="build/routerconsole.war" todir="pkg-temp/webapps/" />
<copy file="installer/resources/wrapper.config" todir="pkg-temp/" />
<copy file="installer/resources/wrapper.config" tofile="pkg-temp/wrapper.config.updated" />
<copy file="history.txt" todir="pkg-temp/" />
<copy file="hosts.txt" todir="pkg-temp/" />
<mkdir dir="pkg-temp/eepsite" />
@@ -244,6 +250,15 @@
</target>
<taskdef name="izpack" classpath="${basedir}/installer/lib/izpack/standalone-compiler.jar" classname="com.izforge.izpack.ant.IzPackTask" />
<target name="installer" depends="preppkg">
<jar destfile="./pkg-temp/lib/copy.jar" basedir="./core/java/build/obj" includes="net/i2p/util/*.class">
<manifest><attribute name="Main-Class" value="net.i2p.util.Copy" /></manifest>
</jar>
<jar destfile="./pkg-temp/lib/delete.jar" basedir="./core/java/build/obj" includes="net/i2p/util/*.class">
<manifest><attribute name="Main-Class" value="net.i2p.util.Delete" /></manifest>
</jar>
<jar destfile="./pkg-temp/lib/exec.jar" basedir="./core/java/build/obj" includes="net/i2p/util/*.class">
<manifest><attribute name="Main-Class" value="net.i2p.util.Exec" /></manifest>
</jar>
<izpack input="${basedir}/installer/install.xml" output="${basedir}/install.jar" installerType="standard" basedir="${basedir}" />
</target>
</project>

View File

@@ -14,8 +14,8 @@ package net.i2p;
*
*/
public class CoreVersion {
public final static String ID = "$Revision: 1.24 $ $Date: 2004/10/18 14:08:00 $";
public final static String VERSION = "0.4.1.4";
public final static String ID = "$Revision: 1.25 $ $Date: 2004/11/06 22:00:57 $";
public final static String VERSION = "0.4.2";
public static void main(String args[]) {
System.out.println("I2P Core version: " + VERSION);

View File

@@ -118,15 +118,23 @@ class I2PSessionImpl2 extends I2PSessionImpl {
if ( (tagsSent == null) || (tagsSent.size() <= 0) ) {
if (oldTags < 10) {
sentTags = createNewTags(50);
//_log.error("** sendBestEffort only had " + oldTags + " adding 50");
} else if (availTimeLeft < 30 * 1000) {
// if we have > 10 tags, but they expire in under 30 seconds, we want more
if (_log.shouldLog(Log.DEBUG))
_log.debug("** sendBestEffort only had " + oldTags + " with " + availTimeLeft + ", adding 50");
} else if (availTimeLeft < 2 * 60 * 1000) {
// if we have > 10 tags, but they expire in under 2 minutes, we want more
sentTags = createNewTags(50);
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Tags are almost expired, adding 50 new ones");
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "Tags expiring in " + availTimeLeft + ", adding 50 new ones");
//_log.error("** sendBestEffort available time left " + availTimeLeft);
} else {
//_log.error("sendBestEffort old tags: " + oldTags + " available time left: " + availTimeLeft);
if (_log.shouldLog(Log.DEBUG))
_log.debug("sendBestEffort old tags: " + oldTags + " available time left: " + availTimeLeft);
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("sendBestEffort is sending " + tagsSent.size() + " with " + availTimeLeft
+ "ms left, " + oldTags + " tags known and "
+ (tag == null ? "no tag" : " a valid tag"));
}
SessionKey newKey = null;
@@ -184,8 +192,14 @@ class I2PSessionImpl2 extends I2PSessionImpl {
long afterRemovingSync = _context.clock().now();
boolean found = state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "After waitFor sending state " + state.getMessageId().getMessageId()
_log.debug(getPrefix() + "After waitFor sending state " + state.getMessageId()
+ " / " + state.getNonce() + " found = " + found);
long timeToSend = afterRemovingSync - beforeSendingSync;
if ( (timeToSend > 10*1000) && (_log.shouldLog(Log.WARN)) ) {
_log.warn("wtf, took " + timeToSend + "ms to send the message?!", new Exception("baz"));
}
if (found) {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + "Message sent after " + state.getElapsed() + "ms with "
@@ -210,7 +224,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
Set sentTags = null;
if (_context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key) < 10) {
sentTags = createNewTags(50);
} else if (_context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key) < 30 * 1000) {
} else if (_context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key) < 2 * 60 * 1000) {
// if we have > 10 tags, but they expire in under 30 seconds, we want more
sentTags = createNewTags(50);
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Tags are almost expired, adding 50 new ones");
@@ -267,9 +281,10 @@ class I2PSessionImpl2 extends I2PSessionImpl {
_sendingStates.remove(state);
}
long afterRemovingSync = _context.clock().now();
boolean guaranteed = isGuaranteed();
boolean found = false;
boolean accepted = state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
if (isGuaranteed())
if (guaranteed)
found = state.received(MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS);
else
found = accepted;
@@ -286,7 +301,8 @@ class I2PSessionImpl2 extends I2PSessionImpl {
+ ")");
//if (true)
// throw new OutOfMemoryError("not really an OOM, but more of jr fucking shit up");
nackTags(state);
if (guaranteed)
nackTags(state);
return false;
}
@@ -294,19 +310,24 @@ class I2PSessionImpl2 extends I2PSessionImpl {
_log.debug(getPrefix() + "After waitFor sending state " + state.getMessageId().getMessageId()
+ " / " + state.getNonce() + " found = " + found);
// WARNING: this will always be false for mode=BestEffort, even though the message may go
// through, causing every datagram to be ElGamal encrypted!
// TODO: Fix this to include support for acks received after the sendMessage completes
if (found) {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + "Message sent after " + state.getElapsed() + "ms with "
+ payload.length + " bytes");
ackTags(state);
// the 'found' value is only useful for mode=Guaranteed, as mode=BestEffort
// doesn't block
if (guaranteed) {
if (found) {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + "Message sent after " + state.getElapsed() + "ms with "
+ payload.length + " bytes");
ackTags(state);
} else {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + "Message send failed after " + state.getElapsed() + "ms with "
+ payload.length + " bytes");
nackTags(state);
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + "Message send failed after " + state.getElapsed() + "ms with "
_log.info(getPrefix() + "Message send enqueued after " + state.getElapsed() + "ms with "
+ payload.length + " bytes");
nackTags(state);
}
return found;
}

View File

@@ -57,37 +57,41 @@ public class DSAEngine {
public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) {
long start = _context.clock().now();
byte[] sigbytes = signature.getData();
byte rbytes[] = new byte[20];
byte sbytes[] = new byte[20];
for (int x = 0; x < 40; x++) {
if (x < 20) {
rbytes[x] = sigbytes[x];
} else {
sbytes[x - 20] = sigbytes[x];
try {
byte[] sigbytes = signature.getData();
byte rbytes[] = new byte[20];
byte sbytes[] = new byte[20];
for (int x = 0; x < 40; x++) {
if (x < 20) {
rbytes[x] = sigbytes[x];
} else {
sbytes[x - 20] = sigbytes[x];
}
}
BigInteger s = new NativeBigInteger(1, sbytes);
BigInteger r = new NativeBigInteger(1, rbytes);
BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
BigInteger w = s.modInverse(CryptoConstants.dsaq);
byte data[] = calculateHash(signedData, offset, size).getData();
NativeBigInteger bi = new NativeBigInteger(1, data);
BigInteger u1 = bi.multiply(w).mod(CryptoConstants.dsaq);
BigInteger u2 = r.multiply(w).mod(CryptoConstants.dsaq);
BigInteger modval = CryptoConstants.dsag.modPow(u1, CryptoConstants.dsap);
BigInteger modmulval = modval.multiply(y.modPow(u2,CryptoConstants.dsap));
BigInteger v = (modmulval).mod(CryptoConstants.dsap).mod(CryptoConstants.dsaq);
boolean ok = v.compareTo(r) == 0;
long diff = _context.clock().now() - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN))
_log.warn("Took too long to verify the signature (" + diff + "ms)");
}
return ok;
} catch (Exception e) {
_log.log(Log.CRIT, "Error verifying the signature", e);
return false;
}
BigInteger s = new NativeBigInteger(1, sbytes);
BigInteger r = new NativeBigInteger(1, rbytes);
BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
BigInteger w = s.modInverse(CryptoConstants.dsaq);
byte data[] = calculateHash(signedData, offset, size).getData();
NativeBigInteger bi = new NativeBigInteger(1, data);
BigInteger u1 = bi.multiply(w).mod(CryptoConstants.dsaq);
BigInteger u2 = r.multiply(w).mod(CryptoConstants.dsaq);
BigInteger modval = CryptoConstants.dsag.modPow(u1, CryptoConstants.dsap);
BigInteger modmulval = modval.multiply(y.modPow(u2,CryptoConstants.dsap));
BigInteger v = (modmulval).mod(CryptoConstants.dsap).mod(CryptoConstants.dsaq);
boolean ok = v.compareTo(r) == 0;
long diff = _context.clock().now() - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN))
_log.warn("Took too long to verify the signature (" + diff + "ms)");
}
return ok;
}
public Signature sign(byte data[], SigningPrivateKey signingKey) {

View File

@@ -156,7 +156,7 @@ public class PersistentSessionKeyManager extends TransientSessionKeyManager {
tag.setData(val);
tags.add(tag);
}
TagSet ts = new TagSet(tags, key);
TagSet ts = new TagSet(tags, key, _context.clock().now());
ts.setDate(date.getTime());
return ts;
}

View File

@@ -36,6 +36,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
private Log _log;
private Map _outboundSessions; // PublicKey --> OutboundSession
private Map _inboundTagSets; // SessionTag --> TagSet
protected I2PAppContext _context;
/**
* Let session tags sit around for 10 minutes before expiring them. We can now have such a large
@@ -62,6 +63,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
public TransientSessionKeyManager(I2PAppContext context) {
super(context);
_log = context.logManager().getLog(TransientSessionKeyManager.class);
_context = context;
_outboundSessions = new HashMap(64);
_inboundTagSets = new HashMap(1024);
}
@@ -116,12 +118,14 @@ class TransientSessionKeyManager extends SessionKeyManager {
public SessionKey getCurrentKey(PublicKey target) {
OutboundSession sess = getSession(target);
if (sess == null) return null;
long now = Clock.getInstance().now();
if (sess.getEstablishedDate() < now - SESSION_LIFETIME_MAX_MS) {
long now = _context.clock().now();
if (sess.getLastUsedDate() < now - SESSION_LIFETIME_MAX_MS) {
if (_log.shouldLog(Log.INFO))
_log.info("Expiring old session key established on "
+ new Date(sess.getEstablishedDate())
+ " with target " + target);
+ " but not used for "
+ (now-sess.getLastUsedDate())
+ "ms with target " + target);
return null;
}
return sess.getCurrentKey();
@@ -185,7 +189,11 @@ class TransientSessionKeyManager extends SessionKeyManager {
OutboundSession sess = getSession(target);
if (sess == null) { return 0; }
if (sess.getCurrentKey().equals(key)) {
return (sess.getLastExpirationDate() + SESSION_TAG_DURATION_MS) - Clock.getInstance().now();
long end = sess.getLastExpirationDate();
if (end <= 0)
return 0;
else
return end - _context.clock().now();
}
return 0;
}
@@ -203,7 +211,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
sess = getSession(target);
}
sess.setCurrentKey(key);
TagSet set = new TagSet(sessionTags, key);
TagSet set = new TagSet(sessionTags, key, _context.clock().now());
sess.addTags(set);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Tags delivered to set " + set + " on session " + sess);
@@ -226,7 +234,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
*
*/
public void tagsReceived(SessionKey key, Set sessionTags) {
TagSet tagSet = new TagSet(sessionTags, key);
TagSet tagSet = new TagSet(sessionTags, key, _context.clock().now());
for (Iterator iter = sessionTags.iterator(); iter.hasNext();) {
SessionTag tag = (SessionTag) iter.next();
if (_log.shouldLog(Log.DEBUG))
@@ -285,9 +293,14 @@ class TransientSessionKeyManager extends SessionKeyManager {
private void removeSession(PublicKey target) {
if (target == null) return;
OutboundSession session = null;
synchronized (_outboundSessions) {
_outboundSessions.remove(target);
session = (OutboundSession)_outboundSessions.remove(target);
}
if ( (session != null) && (_log.shouldLog(Log.WARN)) )
_log.warn("Removing session tags with " + session.availableTags() + " available for "
+ (session.getLastExpirationDate()-_context.clock().now())
+ "ms more", new Exception("Removed by"));
}
/**
@@ -297,32 +310,47 @@ class TransientSessionKeyManager extends SessionKeyManager {
*/
public int aggressiveExpire() {
int removed = 0;
long now = Clock.getInstance().now();
Set tagsToDrop = new HashSet(64);
long now = _context.clock().now();
Set tagsToDrop = null; // new HashSet(64);
synchronized (_inboundTagSets) {
for (Iterator iter = _inboundTagSets.keySet().iterator(); iter.hasNext();) {
SessionTag tag = (SessionTag) iter.next();
TagSet ts = (TagSet) _inboundTagSets.get(tag);
if (ts.getDate() < now - SESSION_LIFETIME_MAX_MS) {
if (tagsToDrop == null)
tagsToDrop = new HashSet(4);
tagsToDrop.add(tag);
}
}
removed += tagsToDrop.size();
for (Iterator iter = tagsToDrop.iterator(); iter.hasNext();)
_inboundTagSets.remove(iter.next());
if (tagsToDrop != null) {
removed += tagsToDrop.size();
for (Iterator iter = tagsToDrop.iterator(); iter.hasNext();)
_inboundTagSets.remove(iter.next());
}
}
//_log.warn("Expiring tags: [" + tagsToDrop + "]");
synchronized (_outboundSessions) {
Set sessionsToDrop = new HashSet(64);
Set sessionsToDrop = null;
for (Iterator iter = _outboundSessions.keySet().iterator(); iter.hasNext();) {
PublicKey key = (PublicKey) iter.next();
OutboundSession sess = (OutboundSession) _outboundSessions.get(key);
removed += sess.expireTags();
if (sess.getTagSets().size() <= 0) sessionsToDrop.add(key);
if (sess.getTagSets().size() <= 0) {
if (sessionsToDrop == null)
sessionsToDrop = new HashSet(4);
sessionsToDrop.add(key);
}
}
if (sessionsToDrop != null) {
for (Iterator iter = sessionsToDrop.iterator(); iter.hasNext();) {
OutboundSession cur = (OutboundSession)_outboundSessions.remove(iter.next());
if ( (cur != null) && (_log.shouldLog(Log.WARN)) )
_log.warn("Removing session tags with " + cur.availableTags() + " available for "
+ (cur.getLastExpirationDate()-_context.clock().now())
+ "ms more", new Exception("Removed by"));
}
}
for (Iterator iter = sessionsToDrop.iterator(); iter.hasNext();)
_outboundSessions.remove(iter.next());
}
return removed;
}
@@ -388,7 +416,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
private List _tagSets;
public OutboundSession(PublicKey target) {
this(target, null, Clock.getInstance().now(), Clock.getInstance().now(), new ArrayList());
this(target, null, _context.clock().now(), _context.clock().now(), new ArrayList());
}
OutboundSession(PublicKey target, SessionKey curKey, long established, long lastUsed, List tagSets) {
@@ -415,6 +443,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
}
public void setCurrentKey(SessionKey key) {
_lastUsed = _context.clock().now();
if (_currentKey != null) {
if (!_currentKey.equals(key)) {
int dropped = 0;
@@ -445,22 +474,24 @@ class TransientSessionKeyManager extends SessionKeyManager {
* Expire old tags, returning the number of tag sets removed
*/
public int expireTags() {
long now = Clock.getInstance().now();
Set toRemove = new HashSet(64);
long now = _context.clock().now();
int removed = 0;
synchronized (_tagSets) {
for (int i = 0; i < _tagSets.size(); i++) {
TagSet set = (TagSet) _tagSets.get(i);
if (set.getDate() + SESSION_TAG_DURATION_MS <= now) {
toRemove.add(set);
_tagSets.remove(i);
i--;
removed++;
}
}
_tagSets.removeAll(toRemove);
}
return toRemove.size();
return removed;
}
public SessionTag consumeNext() {
long now = Clock.getInstance().now();
long now = _context.clock().now();
_lastUsed = now;
synchronized (_tagSets) {
while (_tagSets.size() > 0) {
TagSet set = (TagSet) _tagSets.get(0);
@@ -479,10 +510,12 @@ class TransientSessionKeyManager extends SessionKeyManager {
public int availableTags() {
int tags = 0;
long now = _context.clock().now();
synchronized (_tagSets) {
for (int i = 0; i < _tagSets.size(); i++) {
TagSet set = (TagSet) _tagSets.get(i);
tags += set.getTags().size();
if (set.getDate() + SESSION_TAG_DURATION_MS > now)
tags += set.getTags().size();
}
}
return tags;
@@ -498,13 +531,18 @@ class TransientSessionKeyManager extends SessionKeyManager {
synchronized (_tagSets) {
for (Iterator iter = _tagSets.iterator(); iter.hasNext();) {
TagSet set = (TagSet) iter.next();
if (set.getDate() > last) last = set.getDate();
if ( (set.getDate() > last) && (set.getTags().size() > 0) )
last = set.getDate();
}
}
return last + SESSION_TAG_DURATION_MS;
if (last > 0)
return last + SESSION_TAG_DURATION_MS;
else
return -1;
}
public void addTags(TagSet set) {
_lastUsed = _context.clock().now();
synchronized (_tagSets) {
_tagSets.add(set);
}
@@ -516,12 +554,12 @@ class TransientSessionKeyManager extends SessionKeyManager {
private SessionKey _key;
private long _date;
public TagSet(Set tags, SessionKey key) {
public TagSet(Set tags, SessionKey key, long date) {
if (key == null) throw new IllegalArgumentException("Missing key");
if (tags == null) throw new IllegalArgumentException("Missing tags");
_sessionTags = tags;
_key = key;
_date = Clock.getInstance().now();
_date = date;
}
public long getDate() {

View File

@@ -34,6 +34,7 @@ import java.util.TreeMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import net.i2p.util.ByteCache;
import net.i2p.util.OrderedProperties;
/**
@@ -153,6 +154,9 @@ public class DataHelper {
while ( (line = in.readLine()) != null) {
if (line.trim().length() <= 0) continue;
if (line.charAt(0) == '#') continue;
if (line.charAt(0) == ';') continue;
if (line.indexOf('#') > 0) // trim off any end of line comment
line = line.substring(0, line.indexOf('#')).trim();
int split = line.indexOf('=');
if (split <= 0) continue;
String key = line.substring(0, split);
@@ -310,7 +314,7 @@ public class DataHelper {
throw new IllegalArgumentException("wtf, fromLong got a negative? " + rv + ": offset="+ offset +" numBytes="+numBytes);
return rv;
}
public static void main(String args[]) {
for (int i = 0; i <= 0xFF; i++)
testLong(1, i);
@@ -737,13 +741,16 @@ public class DataHelper {
if ((orig == null) || (orig.length <= 0)) return orig;
GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(orig, offset, length), length);
ByteArrayOutputStream baos = new ByteArrayOutputStream(length * 2);
byte buf[] = new byte[4 * 1024];
ByteCache cache = ByteCache.getInstance(10, 4*1024);
ByteArray ba = cache.acquire();
byte buf[] = ba.getData(); // new byte[4 * 1024];
while (true) {
int read = in.read(buf);
if (read == -1) break;
baos.write(buf, 0, read);
}
byte rv[] = baos.toByteArray();
cache.release(ba);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Decompression of " + orig.length + " into " + rv.length + " (or " + 100.0d
// * (((double) rv.length) / ((double) orig.length)) + "% savings)");

View File

@@ -0,0 +1,108 @@
package net.i2p.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
/**
* Cache the objects frequently used to reduce memory churn. The ByteArray
* should be held onto as long as the data referenced in it is needed.
*
*/
public final class ByteCache {
private static Map _caches = new HashMap(16);
/**
* Get a cache responsible for objects of the given size
*
* @param cacheSize how large we want the cache to grow before using on
* demand allocation
* @param size how large should the objects cached be?
*/
public static ByteCache getInstance(int cacheSize, int size) {
Integer sz = new Integer(size);
synchronized (_caches) {
if (!_caches.containsKey(sz))
_caches.put(sz, new ByteCache(cacheSize, size));
return (ByteCache)_caches.get(sz);
}
}
private Log _log;
/** list of available and available entries */
private List _available;
private int _maxCached;
private int _entrySize;
private long _lastOverflow;
/** do we actually want to cache? */
private static final boolean _cache = true;
/** how often do we cleanup the cache */
private static final int CLEANUP_FREQUENCY = 30*1000;
/** if we haven't exceeded the cache size in 2 minutes, cut our cache in half */
private static final long EXPIRE_PERIOD = 2*60*1000;
private ByteCache(int maxCachedEntries, int entrySize) {
if (_cache)
_available = new ArrayList(maxCachedEntries);
_maxCached = maxCachedEntries;
_entrySize = entrySize;
_lastOverflow = -1;
SimpleTimer.getInstance().addEvent(new Cleanup(), CLEANUP_FREQUENCY);
_log = I2PAppContext.getGlobalContext().logManager().getLog(ByteCache.class);
}
/**
* Get the next available structure, either from the cache or a brand new one
*
*/
public final ByteArray acquire() {
if (_cache) {
synchronized (_available) {
if (_available.size() > 0)
return (ByteArray)_available.remove(0);
}
}
_lastOverflow = System.currentTimeMillis();
byte data[] = new byte[_entrySize];
return new ByteArray(data);
}
/**
* Put this structure back onto the available cache for reuse
*
*/
public final void release(ByteArray entry) {
if (_cache) {
if ( (entry == null) || (entry.getData() == null) )
return;
Arrays.fill(entry.getData(), (byte)0x0);
synchronized (_available) {
if (_available.size() < _maxCached)
_available.add(entry);
}
}
}
private class Cleanup implements SimpleTimer.TimedEvent {
public void timeReached() {
if (System.currentTimeMillis() - _lastOverflow > EXPIRE_PERIOD) {
// we haven't exceeded the cache size in a few minutes, so lets
// shrink the cache
synchronized (_available) {
int toRemove = _available.size() / 2;
for (int i = 0; i < toRemove; i++)
_available.remove(0);
if ( (toRemove > 0) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("Removing " + toRemove + " cached entries of size " + _entrySize);
}
}
SimpleTimer.getInstance().addEvent(Cleanup.this, CLEANUP_FREQUENCY);
}
}
}

View File

@@ -17,6 +17,7 @@ import net.i2p.time.Timestamper;
public class Clock implements Timestamper.UpdateListener {
private I2PAppContext _context;
private Timestamper _timestamper;
private long _startedOn;
public Clock(I2PAppContext context) {
_context = context;
@@ -24,6 +25,7 @@ public class Clock implements Timestamper.UpdateListener {
_alreadyChanged = false;
_listeners = new HashSet(64);
_timestamper = new Timestamper(context, this);
_startedOn = System.currentTimeMillis();
}
public static Clock getInstance() {
return I2PAppContext.getGlobalContext().clock();
@@ -40,6 +42,8 @@ public class Clock implements Timestamper.UpdateListener {
/** if the clock is skewed by 3+ days, fuck 'em */
public final static long MAX_OFFSET = 3 * 24 * 60 * 60 * 1000;
/** after we've started up and shifted the clock, don't allow shifts of more than a minute */
public final static long MAX_LIVE_OFFSET = 60 * 1000;
/** if the clock skewed changes by less than 1s, ignore the update (so we don't slide all over the place) */
public final static long MIN_OFFSET_CHANGE = 10 * 1000;
@@ -60,6 +64,15 @@ public class Clock implements Timestamper.UpdateListener {
return;
}
// only allow substantial modifications before the first 10 minutes
if (_alreadyChanged && (System.currentTimeMillis() - _startedOn > 10 * 60 * 1000)) {
if ( (delta > MAX_LIVE_OFFSET) || (delta < 0 - MAX_LIVE_OFFSET) ) {
getLog().log(Log.CRIT, "The clock has already been updated, but you want to change it by "
+ delta + " to " + offsetMs + "? Did something break?");
return;
}
}
if ((delta < MIN_OFFSET_CHANGE) && (delta > 0 - MIN_OFFSET_CHANGE)) {
getLog().debug("Not changing offset since it is only " + delta + "ms");
return;

View File

@@ -0,0 +1,11 @@
package net.i2p.util;
/**
* Usage: Copy from to
*
*/
public class Copy {
public static void main(String args[]) {
FileUtil.copy(args[0], args[1], true);
}
}

View File

@@ -0,0 +1,11 @@
package net.i2p.util;
/**
* Usage: Delete name
*
*/
public class Delete {
public static void main(String args[]) {
FileUtil.rmdir(args[0], false);
}
}

View File

@@ -0,0 +1,25 @@
package net.i2p.util;
import java.io.File;
/**
* Usage: Exec dir command [args ...]
*
*/
public class Exec {
public static void main(String args[]) {
try {
String cmd[] = new String[args.length - 1];
System.arraycopy(args, 1, cmd, 0, cmd.length);
Process proc = Runtime.getRuntime().exec(cmd, (String[])null, new File(args[0]));
// ugly hack, because it seems we'll block otherwise!
// http://cephas.net/blog/2004/03/23/external_applications_javas_runtimeexec.html
try { proc.exitValue(); } catch (Throwable t) { }
Runtime.getRuntime().halt(0);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -127,10 +127,13 @@ public class FileUtil {
/**
* Read in the last few lines of a (newline delimited) textfile, or null if
* the file doesn't exist.
* the file doesn't exist.
*
* @param startAtBeginning if true, read the first maxNumLines, otherwise read
* the last maxNumLines
*
*/
public static String readTextFile(String filename, int maxNumLines) {
public static String readTextFile(String filename, int maxNumLines, boolean startAtBeginning) {
File f = new File(filename);
if (!f.exists()) return null;
FileInputStream fis = null;
@@ -141,8 +144,12 @@ public class FileUtil {
String line = null;
while ( (line = in.readLine()) != null) {
lines.add(line);
while (lines.size() > maxNumLines)
lines.remove(0);
if (lines.size() >= maxNumLines) {
if (startAtBeginning)
break;
else
lines.remove(0);
}
}
StringBuffer buf = new StringBuffer(lines.size() * 80);
for (int i = 0; i < lines.size(); i++)
@@ -155,9 +162,53 @@ public class FileUtil {
}
}
public static void main(String args[]) {
testRmdir();
/** return true if it was copied successfully */
public static boolean copy(String source, String dest, boolean overwriteExisting) {
File src = new File(source);
File dst = new File(dest);
if (dst.exists() && dst.isDirectory())
dst = new File(dst, src.getName());
if (!src.exists()) return false;
if (dst.exists() && !overwriteExisting) return false;
byte buf[] = new byte[4096];
try {
FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dst);
int read = 0;
while ( (read = in.read(buf)) != -1)
out.write(buf, 0, read);
in.close();
out.close();
return true;
} catch (IOException ioe) {
ioe.printStackTrace();
return false;
}
}
/**
* Usage: FileUtil (delete path | copy source dest)
*
*/
public static void main(String args[]) {
if ( (args == null) || (args.length < 2) ) {
testRmdir();
} else if ("delete".equals(args[0])) {
boolean deleted = FileUtil.rmdir(args[1], false);
if (!deleted)
System.err.println("Error deleting [" + args[1] + "]");
} else if ("copy".equals(args[0])) {
boolean copied = FileUtil.copy(args[1], args[2], false);
if (!copied)
System.err.println("Error copying [" + args[1] + "] to [" + args[2] + "]");
}
}
private static void testRmdir() {
File t = new File("rmdirTest/test/subdir/blah");
boolean created = t.mkdirs();

View File

@@ -27,7 +27,7 @@ class LogRecordFormatter {
private final static int MAX_PRIORITY_LENGTH = 5;
public static String formatRecord(LogManager manager, LogRecord rec) {
int size = 64 + rec.getMessage().length();
int size = 128 + rec.getMessage().length();
if (rec.getThrowable() != null)
size += 512;
StringBuffer buf = new StringBuffer(size);

View File

@@ -349,7 +349,9 @@ public class ShellCommand {
for (int i = 0; i < args.length; i++) {
command.append("\"").append(args[i]).append("\" ");
}
cmd.execute(command.toString());
try {
cmd.executeSilent(command.toString());
} catch (IOException ioe) { ioe.printStackTrace(); }
return;
}

View File

@@ -20,11 +20,12 @@ public class SimpleTimer {
public static SimpleTimer getInstance() { return _instance; }
private Log _log;
/** event time (Long) to event (TimedEvent) mapping */
private Map _events;
private TreeMap _events;
/** event (TimedEvent) to event time (Long) mapping */
private Map _eventTimes;
private SimpleTimer() {
_log = I2PAppContext.getGlobalContext().logManager().getLog(SimpleTimer.class);
_events = new TreeMap();
_eventTimes = new HashMap();
I2PThread runner = new I2PThread(new SimpleTimerRunner());
@@ -71,38 +72,46 @@ public class SimpleTimer {
_log.log(Log.CRIT, msg, t);
}
private long _occurredTime;
private long _occurredEventCount;
private TimedEvent _recentEvents[] = new TimedEvent[5];
private class SimpleTimerRunner implements Runnable {
public void run() {
List eventsToFire = new ArrayList(1);
List timesToRemove = new ArrayList(1);
while (true) {
try {
synchronized (_events) {
if (_events.size() <= 0)
_events.wait();
//if (_events.size() > 100)
// _log.warn("> 100 events! " + _events.values());
long now = System.currentTimeMillis();
long nextEventDelay = -1;
for (Iterator iter = _events.keySet().iterator(); iter.hasNext(); ) {
Long when = (Long)iter.next();
Object nextEvent = null;
while (true) {
if (_events.size() <= 0) break;
Long when = (Long)_events.firstKey();
if (when.longValue() <= now) {
TimedEvent evt = (TimedEvent)_events.get(when);
eventsToFire.add(evt);
timesToRemove.add(when);
TimedEvent evt = (TimedEvent)_events.remove(when);
if (evt != null) {
_eventTimes.remove(evt);
eventsToFire.add(evt);
}
} else {
nextEventDelay = when.longValue() - now;
nextEvent = _events.get(when);
break;
}
}
if (timesToRemove.size() > 0) {
for (int i = 0; i < timesToRemove.size(); i++)
_events.remove(timesToRemove.get(i));
for (int i = 0; i < eventsToFire.size(); i++)
_eventTimes.remove(eventsToFire.get(i));
} else {
if (nextEventDelay != -1)
if (eventsToFire.size() <= 0) {
if (nextEventDelay != -1) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Next event in " + nextEventDelay + ": " + nextEvent);
_events.wait(nextEventDelay);
else
} else {
_events.wait();
}
}
}
} catch (InterruptedException ie) {
@@ -116,6 +125,9 @@ public class SimpleTimer {
}
}
long now = System.currentTimeMillis();
now = now - (now % 1000);
for (int i = 0; i < eventsToFire.size(); i++) {
TimedEvent evt = (TimedEvent)eventsToFire.get(i);
try {
@@ -123,9 +135,31 @@ public class SimpleTimer {
} catch (Throwable t) {
log("wtf, event borked: " + evt, t);
}
_recentEvents[4] = _recentEvents[3];
_recentEvents[3] = _recentEvents[2];
_recentEvents[2] = _recentEvents[1];
_recentEvents[1] = _recentEvents[0];
_recentEvents[0] = evt;
}
if (_occurredTime == now) {
_occurredEventCount += eventsToFire.size();
} else {
_occurredTime = now;
if (_occurredEventCount > 100) {
StringBuffer buf = new StringBuffer(256);
buf.append("Too many simpleTimerJobs (").append(_occurredEventCount);
buf.append(") in a second! Last 5: \n");
for (int i = 0; i < _recentEvents.length; i++) {
if (_recentEvents[i] != null)
buf.append(_recentEvents[i]).append('\n');
}
_log.log(Log.CRIT, buf.toString());
}
_occurredEventCount = 0;
}
eventsToFire.clear();
timesToRemove.clear();
}
}
}

View File

@@ -1,4 +1,189 @@
$Id: history.txt,v 1.65 2004/11/06 21:25:13 jrandom Exp $
$Id: history.txt,v 1.91 2004/12/01 17:31:56 jrandom Exp $
* 2004-12-01 0.4.2.2 released
2004-12-01 jrandom
* Fixed a stupid typo that inadvertantly allowed persistent HTTP
connections to work (thanks duck!)
* Make sure we override the inactivity timeout too
* 2004-12-01 0.4.2.1 released
2004-12-01 jrandom
* Strip out any of the Accept-* HTTP header lines, and always make sure to
include the forged User-agent header.
* Adjust the default read timeout on the eepproxy to 60s, unless
overridden.
* Minor tweak on stream shutdown.
2004-11-30 jrandom
* Render the burst rate fields on /config.jsp properly (thanks ugha!)
* Build in a simple timeout to flush data queued into the I2PSocket but
not yet flushed.
* Don't explicitly flush after each SAM stream write, but leave it up to
the [nonblocking] passive flush.
* Don't whine about 10-99 connection events occurring in a second
* Don't wait for completion of packets that will not be ACKed (duh)
* Adjust the congestion window, even if the packet was resent (duh)
* Make sure to wake up any blocking read()'s when the MessageInputStream
is close()ed (duh)
* Never wait more than the disconnect timeout for a write to complete
2004-11-29 jrandom
* Minor fixes to avoid unnecessary errors on shutdown (thanks susi!)
2004-11-29 jrandom
* Reduced contention for local client delivery
* Drop the new code that munges the wrapper.config. Instead, updates that
need to change it will include their own wrapper.config in the
i2pupdate.zip, overwriting the existing file. If the file
"wrapper.config.updated" is included, it is deleted at first opportunity
and the router shut down, displaying a notice that the router must be
started again cleanly to allow the changes to the wrapper.config to take
effect.
* Properly stop accept()ing I2PSocket connections if we close down the
session (duh).
* Make sure we cancel any outstanding Packets in flight when a connection
is terminated (thanks susi!)
* Split up the I2PTunnel closing a little further.
2004-11-28 jrandom
* Accept IP address detection changes with a 2-out-of-3 minimum.
* As long as the router is up, keep retrying to bind the I2CP listener.
* Decrease the java service wrapper ping frequency to once every 10
minutes, rather than once every 5 seconds.
2004-11-27 jrandom
* Some cleanup and bugfixes for the IP address detection code where we
only consider connections that have actually sent and received messages
recently as active, rather than the mere presence of a TCP socket as
activity.
2004-11-27 jrandom
* Removed the I2PTunnel inactivity timeout thread, since the new streaming
lib can do that (without an additional per-connection thread).
* Close the I2PTunnel forwarder threads more aggressively
2004-11-27 jrandom
* Fix for a fast loop caused by a race in the new streaming library (thanks
DrWoo, frontier, pwk_, and thetower!)
* Minor updates to the SimpleTimer and Connection to help track down a
high CPU usage problem (dumping debug info to stdout/wrapper.log if too
many events/tasks fire in a second)
* Minor fixes for races on client disconnects (causing NPEs)
* 2004-11-26 0.4.2 released
2004-11-26 jrandom
* Enable the new streaming lib as the default. That means, for any
substantial definition, it is NOT BACKWARDS COMPATIBLE.
2004-11-25 jrandom
* Revised the installer to include start menu and desktop shortcuts for
windows platforms, including pretty icons (thanks DrWoo!)
* Allow clients specified in clients.config to have an explicit startup
delay.
* Update the default install to launch a browser pointing at the console
whenever I2P starts up, rather than only the first time it starts up
(configurable on /configservice.jsp, or in clients.config)
* Bugfix to the clock skew checking code to monitor the delta between
offsets, not the offset itself (duh)
* Router console html update
* New (and uuuuugly) code to verify that the wrapper.config contains
the necessary classpath entries on update. If it has to update the
wrapper.config, it will stop the JVM and service completely, since the
java service wrapper doesn't reread the wrapper.config on JVM restart -
requiring the user to manually restart the service after an update.
* Increase the TCP connection timeout to 30s (which is obscenely long)
2004-11-22 jrandom
* Update to the SAM bridge to reduce some unnecessary memory allocation.
* New stat to keep track of slow jobs (ones that take more than a second
to excute). This is published in the netDb as jobQueue.jobRunSlow
2004-11-21 jrandom
* Update the I2PTunnel web interface to include an option for the new
streaming lib (which is ignored until the 0.4.2 release).
* Revised the I2PTunnel web interface to keep the I2CP options of client
and httpclient tunnels in sync, as they all share the same I2CP session.
2004-11-21 jrandom
* Only allow small clock skews after the first 10 minutes of operation
(to prevent later network lag bouncing us way off course - yes, we
really need an NTP impl to balance out the network burps...)
* Revamp the I2PTunnel web interface startup process so that everything
is shown immediately, so that different pieces hanging don't hang
the rest, and other minor bugfixes.
* Take note of SAM startup error (in case you're already running a SAM
bridge...)
* Increase the bandwidth limiter burst values available to 10-60s (or
whatever is placed in /configadvanced.jsp, of course)
2004-11-21 jrandom
* Allow end of line comments in the hosts.txt and other config files,
using '#' to begin the comments (thanks susi!)
* Add support to I2PTunnel's 'client' feature for picking between multiple
target destinations (e.g. 'client 6668 irc.duck.i2p,irc.baffled.i2p')
* Add a quick link on the left hand nav to reseed if there aren't enough
known peers, as well as link to the config page if there are no active
peers. Revised config page accordingly.
2004-11-21 jrandom
* Destroy ElGamal/AES+SessionTag keys after 15 minutes of inactivity
rather that every 15 minutes, and increase the warning period in which
we refresh tags from 30s to 2 minutes.
* Bugfix for a rare problem closing an I2PTunnel stream where we'd fail
to close the I2PSocket (leaving it to timeout).
2004-11-19 jrandom
* Off-by-one fix to the tunnel pool management code, along side some
explicit initialization. This can affect clients whose lengths are
shorter than the router's default (thanks duck!)
2004-11-17 jrandom
* Fix to propogate i2psocket options into the SAM bridge correctly (thanks
Ragnarok!)
2004-11-17 jrandom
* Minor logging update.
2004-11-16 jrandom
* Clean up the propogation of i2psocket options so that various streaming
libs can honor them more precisely
2004-11-16 jrandom
* Minor logging update
2004-11-14 jrandom
* Fix a long standing leak in I2PTunnel (hanging on to i2psocket objects)
* Fix a leak injected into the SimpleTimer
* Fix a race condition in the tunnel message handling
2004-11-13 jrandom
* Added throttles on how many I2PTunnel client connections we open at once
* Replaced some buffered streams in I2PTunnel with unbuffered streams, as
the streaming library used should take care of any buffering.
* Added a cache for some objects used in I2PTunnel, especially useful when
there are many short lived connections.
* Trimmed the SimpleTimer's processing a bit
2004-11-10 jrandom
* Allow loading the (mini)streaming connection options from the
environment.
* More defensive programming in the DSA implementation.
2004-11-08 jrandom
* Remove spurious flush calls from I2PTunnel, and work with the
I2PSocket's output stream directly (as the various implementations
do their own buffering).
* Another pass at a long standing JobQueue bug - dramatically simplify
the job management synchronization since we dont need to deal with
high contention (unlike last year when we had dozens of queue runners
going at once).
* Logging
2004-11-08 jrandom
* Make the SAM bridge more resiliant to bad handshakes (thanks duck!)
* 2004-11-06 0.4.1.4 released

View File

@@ -1,6 +1,14 @@
; TC's hosts.txt guaranteed freshness
; $Id: hosts.txt,v 1.74 2004/11/05 06:22:56 jrandom Exp $
; $Id: hosts.txt,v 1.82 2004/11/28 17:47:01 jrandom Exp $
; changelog:
; (1.105) added bdl.i2p
; (1.104) added bacardi.i2p and guttersnipe.i2p
; (1.103) added evil.i2p
; (1.102) added bsdm.i2p
; (1.101) added eschaton.i2p
; (1.100) added blog.curiosity.i2p
; (1.99) added slacker.i2p
; (1.98) added ses.i2p and pdforge.i2p
; (1.97) added orion.i2p and protokol.i2p
; (1.96) added blog.polecat.i2p
; (1.95) added ciaran.i2p
@@ -217,4 +225,14 @@ ciaran.i2p=3Ot-weeZY96NbdDSOwJD6t0plEwIMUMyZPSO71AtkNEhO8aIKqAMyAU14EDH~F27OZqe~
blog.polecat.i2p=sdnTAgmDs1zgSWw8P64avIzhBrtuzl2gKxoPTKz7kAr2oAWxaLTAvRHRs0I~KCi8CAhPGwOVk5qGmlgeoeLobojlJI5u2mxL08fslrvhUxEFbAPQVpyQoSo0VITXLSeYsdlwXHEVPh5ONpR78HKVP0~bF4LV9by9982UdgZvp9IW64VRZI1fQ7qCZftwZBKyziSIyV8lp6P9SF5EBlRlvOC9JWtYE7gPGYnDqDt6WcSNGP5eWcWuWzaiQutD~LsmfDPSVWt4fCtj8aSDy2qqLeoo-dF52OgcPVi32AfjlY4LpTnUfFJoFIxi0qE37nOsRY4e9QA~rULJVMRKinab~C24hpKJlM4IZTe46PZ1iVs40aRYtX9BkD6F8M5mCuDQTTbb5g9ib34ZsH-lHgiqwFQhtrL5R~Cx32uTVvfVFxcP9GRau1w5k4jYfIzSPpLUQRwek1pY35PCGf4tH8J4ZO16Cahfz~u2ahuHNeRrin1cgdtd~GT1SEJikUsvw9X9AAAA
orion.i2p=gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA
protokol.i2p=l8e~NR26yXd3C6Hi8XDRrn3Q6~FztVMb1Sg1JzV6yortHBs7aSYt8UgokX1XbhzIM1LCq~k-KzXScmwh-VOjBu-hEdacFO4MgyJEiUJdMd2qwwxrwmiUDVns~CGmywJ9K2dVRK1o5GwgTY1vuS11~AMobXbd~-xCE1a~V2xSW9HHrGgaRVA0KlVF9U-LHCzSK-37vxDTeCSoP5CW-q5VOqGDoTcCbbo0JgABaSZjpzbTa9bfwZDjTy26Wx1rHoEpP5C1T6iRO4lkAuy6VUau~S1Tovb5w4T-SLtbx~~B5brDQJG6m9jFMp~OKffnZ51fsqVke-~snct33PJptg8UDnBkpij3mlJELEi54KhpN-EGHH56Y7hfAq-sTkIqUFwiFCCcHpgyBtekwmwgBXoDqUsvtV22~Wq7qK6jRcO2A0LrcKZ20y-2yQPyPvqXR1cKnFOt7j6LALiHWvM70urysWcIEohwuVirq9eY2xikRHwJEI0LsKLyDLFCJHtlImUiAAAA
ses.i2p=OqfisCUxgHRMyk2CJaOxGsBMv2Y2NkqSLY3lwNIYIMClybDtKGWq-xsydwV~uQkWoQAI0SYMeUKcOSg94~h8pu6q3TwlLSCRFrQDmrtqhFLIa8q7hxDkP5DAdfe2~gbg6JRyek~IoYsi1LVMd1KZh2PfP4T-VJKSw2EXB2n--HPwKzrNcBBLJZl7aYzZBKD7eixLD2tx5tYq0ONCJcKeWPeO2ZhFKU9Xi53VA0jM9L~J-NfnlBeGlYw-9w4Q49jBqlRVx9GMpQiYrAsaKqyLmKjM9ExAwkeXDoAz3fj5MTLdKZhQ78Bvznu9QTYfUtM~Qg8QCSzTLizeZqF3STDs9RW~4Zsq5CH05KP-cpwFKqC2HZpIj0VtGsD-IReDyyJVD8NycUjaWRqef4Bt045ApRsUkEFFcsEO0ahpMqLdjPWLqW5aelS0m-iZ6tb33PrAGb8Tzc5fIfyt0lQjCjp3X0dETuGr7LqIdIL5G9geqFI4k4c~OzTNRjn~DaWkGTkHAAAA
pdforge.i2p=2pFasuY6uXebywQncY3IGxMiJ8GEyaTKARnU3W3fB0IAgQ-4HxAVmLRZ2HyCtkiCJrLQlKC8TIrK0xls~IhaYxWcSwkVsRVvVSskin6sdS9TzyqwL7OKoxLJC~4lvR8c6HyHC9RmY7-jh2eDDWJYz7w1uSwfkAWVp9Hj74bzjjEta8uqVChdtqldEy-a8TJ9CXLAD~KnRSs40kR3o4ZKS9XekA7Kvm95jI6ALaim7WWeRqPCn0cmsjBgGtyvswol4MbpkRAu4nn8gMHpX7~cCOllvE-ciV6W2ZGIaLZ6CC2n7chbBaC1EvC6Du0dlHWy420BImLLu9LttwT8ch23JnAzo~BRC0vCaUdS1PUYOiP7K3U3RWyC0U3WZ3pz8Lz2nZIIGKkgG43OrRA4mxIJE-57ExnBjJjNx0MTVA-8tkk~RQA6M81ECl5j0tP0~oSWAcNUJwoDKjL9kafj5CYW5xWJg33YUe7ExUvLYVCYjabdsuMlmc59WGP2pBkFFnsrAAAA
slacker.i2p=BR~D1lQNF~NLjdrJILDBA4DaQZpDoQZhVFNAwBgUz3sEP2tHnuiS7-EO2jwXEPKHIVZNGSNNDcO-7iapYcPJU~40AirUep9IX7IIAh3blGsHdasPha2OEls0lH3IAQvpUKUyhH85acP5DLUcR3q6X4VY2uHfgJHfxf5hKh~8P6EAgSaxt2XcolU4l5k9pKibxjivz0mMlZgcmrSmeFJkXy9Pko-0cBaSniqESTkzVhsCk0PcjO2A6yu9Zs1T~dWn-AvxG1Fd79xL71Xmq~c2AjJHQoJ44Kz~Kre0wdKF6~LMUgr-zs-Wdz8Ay8bU4pjOt6ygNKLLP3NjBBvpMVpyJx9XfNVFcCfb0ty7z4cFcHD3bT0A~J3SD6XLImYQaMTUEOprmD3YiuWXiyqlue6bwXrzzi9L0S0YaPs1vmZYU0w5ra35v-uDxqJA1e9wM7OWDNN3oPNt5F~XsYGKVR0dvXZXHc0vWHa0pwuo8Dh6MrfNjGpzTwCPH9S5x96lHpdSAAAA
blog.curiosity.i2p=GqgGNWvNcbEABjAxBNJmILDIVJq0GSpf3eyjQt5EQsJFPCA3fYFYUULh4WoPFEk3I4pOV0lPGlkFX6Q52nkIk~kW~YGsCjxDJJ~08m31KgPJ70-svh7Fj-D8Bs27VaG9nXEltCJfjzfHsNY6m5jCCf400-14jTEghAjguIwPbpCpJ351jrE36sSU861LhhKnCiTItIN6ig9LROIUOJnTvh4-vRHrHS~oQVesAUqZ0zMPRNYlC1GHgTvou8iKLXlPnq3u1ITfCQYjeC6lpNn3bnnJOfrzGsPW3Y70jG5fqTPRqCyef5j0gNa~ZewMAQvV~oKxpDFTixNx8pBRv913pRXTdZfxT8D-fpYbXP1O0GtaHTgPO7PYWFKHCAaxmleD7tzSpIyfl4LpO0Y1Be5eHAZh5Lh1nE~tqVJZUbcVqZzDPmrmT20oCDljsZ8kEIRb3mn3VpzN5wOikPTh8AHg1jfic9hGBv5Lqu5SBffSCQZi7znTB7peX3ZavGODotR2AAAA
eschaton.i2p=YTlIyW42pdmE~D8fc3eUSdyN9x~SsddHcAXi3hISTTovLO-CphmfAqyvAZ1GV2-xExVlK7u~WBT8w9NBeig48NsAXCkNuWvZS~hzw9AERj5HVe5Jwzvd78x6rsFLaYbAFeSngtH~Bxi~ZLfLtXbcD7aZsXh2bCSV8OwKPRmPiKsV48Bwi9BSJA84VQLtpnbg3MtSDAciLVVH2Prvu939CciLAkdaST6JWBDY553c~4RlaHkxR~uHsskO0sNnCp2ASGRR8zoaDTP9eKhy9vXcCJZ6EYKDmRGcb~0hpuB67lP47PJaiNCWNk5jI~VNdCUE2O9D-XBPA88qvDZVK0wZWTL3Hxh5uvqr-rKZ2Txlh-qZ1vispjv47VC88ht~mQi~PGbqZxvPIOe9LxemIjlBedDjcS675cyVm-stwx-8G8au-hE7QNhZ3XN8kRlNcCTtMJxX2QRXT6juCZVNXfnO~sCexp7inl7~Q~yCeBbOoWhQoOOsm7kHMNEGxOkpkxjxAAAA
bdsm.i2p=qOcqbBo4vxHbldI758R76ZJZu92zWefovF6IEEv~XKA-UkHNbY-y6v9w-9ts-tbZxpak8l1QyB6Mp1Rwy-sE1YxevjIPhV-UQZSsJHRJHVZasR3ULncY-g-dvzGLab9YGtkdfub2GMlCJzDm9F8sUvGnLxlZ28mj3~ZLC0vEt5BeCl5hDuT9CiAzWarcSC2M1PIOMxIUYz9K5ueanOZjmAkNet4igKM0XmfXL8c85VeiPO8jx9G0ZDARwObXxvNdPq5pDwOZSY-5f-crDWMV1KesdmqsKIgydZ5GSPzq5jKLJdY3bUsxViNoWzjIWKfbwm6Wgl3eb9wzLEXDP1m2WGWAetNCgwSbT30A2yhTEWFwEqSE0GvWKvlztyG3oGm8eKN-VymRwduORnpKYbALNZ2MG4ZtVhKklER4hrgYSLuuXIi7KhQ0WXrKjPFnnL2MmnpPsFYn0ZQz6ilYOSAYcV3rBsd83RAFPWodlCjt0TpnpvcFfYZqfKZtYQryzkdFAAAA
evil.i2p=NGNaN9qrpk2dC~OQlo-1yHT~Tvb6SvU8VH9SiBkwNZxuNVRKN~2ZtU50nkTklevB5V08GMT5tkpyZCyVwVgZNNl50uz25t~xP3Pte6GUxzBfBJ8n2GoFdlhn5jGHhmM6QI3nlDgLO7AaNdIQu8LxaoO4lln~fReKvY3sNRMAOJlBO0mHH1PSsemJmqkhl02VX6-3KllgZ99E7uVT-ap2i7Pf3wvLtBtoHryZXvYtKL9zV17tBfhmIVT~VSuhsGRVoy43eoep~fPNCE3s0jr4GLEeWfx2LF0WsvN2avii43DFB23BaZAKd7myLV~pmN9L7pOILnsrktO2MQXy2q2OriZs8dxN3w0yboE5iAfTt8IWtHfNLp8aGx2HzmF1TacrPxuJmg~C9mawTGtZ2aKMBr7vHtN1VuZnOdVi5hcC7tQ0YuwPtAkOB1iO2Wbh3csYtfusPoIqezP1O-iUndNCRg86u3PZ71jXhSq4mMZaNZGYiiqB7nXVkBi15nUbQRb8AAAA
bacardi.i2p=oZm0JRHiUFKwAzLz1wlNOK2h2fI8V6u1nUhgCpt1RcErs99QMPHqu4oR4cel5lsJbeg1X0GgHe72JYabsntjimjWs1zi0RzknddVvq0hMGnn9EA-9Atu9qViScXp42ddnYhIlMBNNswMp8AJ01jHkO3SUSDSVk-rF5wnrhpyN9BFyho8h18nHrr6S4jaGHsuLVage8ImwQRv~PYfr4hVULdiFn5HDRePdvgzKUD3o4Y7nFiQMXswP02ZhXXtE-rMGDs2TD15qYah6mkQWdZtRYFuKuCh7Myn35u2IHGyMs56zpnuVr~w~Uh7wydg26JShsAz4AyZhGy70eTFUC2dtEr7bUvQSE3V3xNKOeBuGYadipWB30xmGDX2kfaU5Efy45MLPBwTU-8f4owQBokgSD3jBEc4vV0DbJrGoNnA3zpAC0JwZ9rFLolQNzenRgkWYlO09PEkKPUyUMpUBGY~Wj7jcsGnt5uNskeHHpV4hsvdpboHnlPAcZKvN7rTCenoAAAA
guttersnipe.i2p=yS6ECPvCobRLKMXJg-rLZ5PF6dRcXliR2e9KEX~aUZMEsznpzE-lCs~FgGd7w9-~GW8ObvKiyt47S7SuCunjndl874xPhrQ0dFRUNEsdovLBr6s9v2xi7kpWC1yKLbT-Ti3p8WhNPZkGsJl6YEIc357j4FjNVFx2-b~V16G0lhjhY1a55nsCoU7xXisdqOaKZL-4Y7vEVHqZNgM6cAZpY8vHtui30J3oGmV-RcJG9ahYkorqleKtb2G0fwbUMakxHv5uzlHyAU~r7OmS7NCIfoyBgU3MrSiwkYiW5elQ294XV-1Bciy9DFnJh4KA0rLIA7GanvkQ-NwqoGLsXz08mpi12vVssDhdavXooBeZLg7gipFYzle3-B1rgGKZ~51~9Tou~XSn6jGJGgtqKV7fTmys4~AXNy7vCWrMKQaWh6M1HI69yV466bJWv6nVhwNmOmtk48EIC3ik2IXC1wjA6Rgz2AcSiR-UEyaBkv251Vu6H92HZJTrsTzuzVmZcYt0AAAA
bdl.i2p=IMzcrD4jQLBtyzV8TdL-jgbyDhRjMBS1BfI6xGuddMyJEoMnZ1ZWDUn3NwqdyEYRQD7zREgbsQgzpE7Gdmpp-vLrzba3W1mUR5Ddu13tgIiDrOWR38Omv7L~DTDimK9m0Y-HCJROVigRfxdZbsI6P37d77NArdpwLzFos6qyK2C40JQFIaNCPYGkf05DsXtHLPwTJXYVKVp0R2V7nGmI77BUdwDgt4zSotlyvmsX6U1mYqKwutwr~oxggdvgfoNbrGC0~xQCbfQtEOwFYwxc5oUeJlt4jjb1-C9HAb7r4LtJ3Daqr0bx1hXS5hADw68cfUHbEjbfrhJhBA0mMqgGMm~r~0II8R19EXprc91YY4d0QycR2Osdn9MVNXS01Ziy~JX-SfT8DTjj2ZZn7rpyjcq7rgVbspPLJOdNQNERiAm06yjosOrPjtl8mrWXxWcOcGlMO5ftoTvX2hFsfc-vmfN7S8IZEfUdJRpJxDytChNTUr~qMWuP0LicNv0xKtA3AAAA

View File

@@ -1 +1,85 @@
<?xml version="1.0" encoding="iso-8859-1" standalone="yes" ?>
<?xml version="1.0" encoding="iso-8859-1" standalone="yes" ?>
<installation version="1.0">
<info>
<appname>i2p</appname>
<appversion>0.4.2.2</appversion>
<authors>
<author name="I2P" email="support@i2p.net"/>
</authors>
<url>http://www.i2p.net</url>
</info>
<guiprefs width="590" height="356" resizable="yes">
<laf name="liquid">
<os family="unix"/>
</laf>
</guiprefs>
<locale>
<langpack iso3="eng"/>
</locale>
<native type="izpack" name="ShellLink.dll" />
<resources>
<res id="Installer.image" src="installer/resources/i2plogo.png" />
<res id="InfoPanel.info" src="installer/resources/readme.license.txt"/>
<!-- <res id="ProcessPanel.Spec.xml" src="installer/resources/ProcessPanel.Spec.xml"/> -->
<res id="shortcutSpec.xml" src="installer/resources/shortcutSpec.xml" />
</resources>
<panels>
<panel classname="HelloPanel"/>
<panel classname="InfoPanel"/>
<panel classname="TargetPanel"/>
<panel classname="InstallPanel"/>
<panel classname="ShortcutPanel"><os family="windows" /></panel>
<!-- <panel classname="ProcessPanel"><os family="windows" /></panel> -->
<panel classname="SimpleFinishPanel"/>
</panels>
<packs>
<pack name="Base" required="yes">
<description>Base installation files</description>
<fileset dir="pkg-temp" includes="**/*" targetdir="$INSTALL_PATH"/>
<!-- postinstall stuff for windows -->
<executable targetfile="$INSTALL_PATH/lib/copy.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\lib\wrapper\win32\I2Psvc.exe" /><arg value="$INSTALL_PATH" /></args></executable>
<executable targetfile="$INSTALL_PATH/lib/copy.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\lib\wrapper\win32\wrapper.dll" /><arg value="$INSTALL_PATH\lib" /></args></executable>
<executable targetfile="$INSTALL_PATH/lib/copy.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\lib\wrapper\win32\wrapper.jar" /><arg value="$INSTALL_PATH\lib" /></args></executable>
<executable targetfile="$INSTALL_PATH/lib/delete.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\i2prouter" /></args></executable>
<executable targetfile="$INSTALL_PATH/lib/delete.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\install_i2p_service_unix" /></args></executable>
<executable targetfile="$INSTALL_PATH/lib/delete.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\install-headless.txt" /></args></executable>
<executable targetfile="$INSTALL_PATH/lib/delete.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\osid" /></args></executable>
<executable targetfile="$INSTALL_PATH/lib/delete.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\postinstall.sh" /></args></executable>
<executable targetfile="$INSTALL_PATH/lib/delete.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\postinstall.bat" /></args></executable>
<executable targetfile="$INSTALL_PATH/lib/delete.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\uninstall_i2p_service_unix" /></args></executable>
<executable targetfile="$INSTALL_PATH/lib/delete.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH\lib\wrapper" /></args></executable>
<!--
<executable targetfile="$INSTALL_PATH/lib/exec.jar" type="jar" stage="postinstall" keep="true" failure="warn"> <os family="windows" />
<args><arg value="$INSTALL_PATH" /><arg value="$INSTALL_PATH\I2Psvc.exe" /><arg value="-c" /><arg value="$INSTALL_PATH\wrapper.config" /></args></executable>
-->
<!-- postinstall stuff for *nix -->
<!-- stage=never means chmod a+x -->
<executable targetfile="$INSTALL_PATH/postinstall.sh" type="bin" stage="never" keep="true" failure="warn"><os family="unix" /></executable>
<executable targetfile="$INSTALL_PATH/postinstall.sh" type="bin" stage="postinstall" keep="true" failure="warn"><os family="unix" />
<args><arg value="$INSTALL_PATH" /></args></executable>
</pack>
</packs>
</installation>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1" standalone="yes" ?>
<processing>
<job name="Launching I2P...">
<os family="windows" />
<executefile name="$INSTALL_PATH\postinstall.bat">
<arg>$INSTALL_PATH</arg>
</executefile>
</job>
<job name="Launching I2P...">
<os family="unix" />
<executefile name="/bin/sh">
<arg>$INSTALL_PATH/postinstall.sh</arg><arg>$INSTALL_PATH</arg>
</executefile>
</job>
</processing>

View File

@@ -17,4 +17,11 @@ clientApp.2.args=i2ptunnel.config
# run our own eepsite with a seperate jetty instance
clientApp.3.main=org.mortbay.jetty.Server
clientApp.3.name=eepsite
clientApp.3.args=eepsite/jetty.xml
clientApp.3.args=eepsite/jetty.xml
clientApp.3.delay=30
# load a browser pointing at the web console whenever we start up
clientApp.4.main=net.i2p.apps.systray.UrlLauncher
clientApp.4.name=consoleBrowser
clientApp.4.args=http://localhost:7657/
clientApp.4.delay=5

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +0,0 @@
@echo off
start /m I2Psvc.exe -c wrapper.config
exit

View File

@@ -9,6 +9,7 @@ tunnel.0.i2cpHost=localhost
tunnel.0.i2cpPort=7654
tunnel.0.option.tunnels.depthInbound=2
tunnel.0.option.tunnels.numInbound=2
tunnel.0.option.i2p.streaming.connectDelay=1000
tunnel.0.startOnLoad=true
# irc
@@ -17,11 +18,12 @@ tunnel.1.description=IRC proxy to access the anonymous irc net
tunnel.1.type=client
tunnel.1.interface=127.0.0.1
tunnel.1.listenPort=6668
tunnel.1.targetDestination=irc.duck.i2p
tunnel.1.targetDestination=irc.duck.i2p,irc.baffled.i2p
tunnel.1.i2cpHost=localhost
tunnel.1.i2cpPort=7654
tunnel.1.option.tunnels.depthInbound=2
tunnel.1.option.tunnels.numInbound=2
tunnel.1.option.i2p.streaming.connectDelay=1000
tunnel.1.startOnLoad=true
# I2P's cvs server
@@ -59,6 +61,7 @@ tunnel.4.listenPort=7659
tunnel.4.name=smtp.postman.i2p
tunnel.4.option.tunnels.depthInbound=2
tunnel.4.option.tunnels.numInbound=2
tunnel.4.option.i2p.streaming.connectDelay=1000
tunnel.4.startOnLoad=false
tunnel.4.targetDestination=smtp.postman.i2p
tunnel.4.type=client
@@ -72,19 +75,7 @@ tunnel.5.interface=127.0.0.1
tunnel.5.listenPort=7660
tunnel.5.option.tunnels.depthInbound=2
tunnel.5.option.tunnels.numInbound=2
tunnel.5.option.i2p.streaming.connectDelay=1000
tunnel.5.startOnLoad=false
tunnel.5.targetDestination=pop.postman.i2p
tunnel.5.type=client
# another irc server, linked with irc.duck.i2p and IIP
tunnel.6.description=irc.baffled.i2p
tunnel.6.i2cpHost=localhost
tunnel.6.i2cpPort=7654
tunnel.6.interface=localhost
tunnel.6.listenPort=7661
tunnel.6.name=irc.baffled.i2p
tunnel.6.option.tunnels.depthInbound=2
tunnel.6.option.tunnels.numInbound=2
tunnel.6.startOnLoad=false
tunnel.6.targetDestination=irc.baffled.i2p
tunnel.6.type=client
tunnel.5.type=client

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<shortcuts>
<programGroup defaultName="I2P" location="startMenu" />
<shortcut name="Start I2P"
target="$INSTALL_PATH\I2Psvc.exe"
commandLine="-c wrapper.config"
workingDirectory="$INSTALL_PATH"
iconFile="$INSTALL_PATH\docs\start.ico"
initialState="noShow"
programGroup="yes"
startMenu="no"
desktop="yes"
startup="no" />
<shortcut name="I2P router console"
target="$INSTALL_PATH\docs\startconsole.html"
commandLine=""
workingDirectory="$INSTALL_PATH"
iconFile="$INSTALL_PATH\docs\console.ico"
initialState="noShow"
programGroup="yes"
startMenu="no"
desktop="yes"
startup="no" />
<shortcut name="Uninstall I2P"
target="$INSTALL_PATH\uninstaller\uninstaller.jar"
commandLine=""
workingDirectory="$INSTALL_PATH"
iconFile="$INSTALL_PATH\docs\uninstall.ico"
initialState="noShow"
startMenu="no"
programGroup="yes"
startup="no" />
</shortcuts>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,6 @@
<html><body>
<meta http-equiv="Refresh" CONTENT="0;URL=http://localhost:7657/index.jsp" />
Continue to your <a href="http://localhost:7657/index.jsp">I2P Router console</a>.
If that page does not load, please make sure I2P is running, or review the file
wrapper.log for critical errors.
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -32,6 +32,7 @@ wrapper.java.classpath.17=lib/xml-apis.jar
wrapper.java.classpath.18=lib/jbigi.jar
wrapper.java.classpath.19=lib/systray.jar
wrapper.java.classpath.20=lib/systray4j.jar
wrapper.java.classpath.21=lib/streaming.jar
# Java Library Path (location of Wrapper.DLL or libwrapper.so)
wrapper.java.library.path.1=.
@@ -102,6 +103,9 @@ wrapper.jvm_exit.timeout=10
# give the OS 60s to clear all the old sockets / etc before restarting
wrapper.restart.delay=60
wrapper.ping.interval=600
wrapper.ping.timeout=605
# use the wrapper's internal timer thread. otherwise this would
# force a restart of the router during daylight savings time as well
# as any time that the OS clock changes

Some files were not shown because too many files have changed in this diff Show More