Compare commits

...

69 Commits

Author SHA1 Message Date
4b2a734cda * 2004-12-18 0.4.2.4 released 2004-12-18 04:07:13 +00:00
97ae8f78a0 added piespy.i2p 2004-12-18 03:11:03 +00:00
834665c3ba 2004-12-16 jrandom
* Catch another oddball case for a reset connection in the streaming lib.
    * Add a dumpprofile.jsp page, called with ?peer=base64OfPeerHash, which
      dumps the current state of that peer's profile.  Instead of the full
      base64, you can pass in however many characters you have and it will
      return the first match found.
2004-12-16 10:32:26 +00:00
d969dd2d8d 2004-12-16 jrandom
* Catch another oddball case for a reset connection in the streaming lib.
    * Add a dumpprofile.jsp page, called with ?peer=base64OfPeerHash, which
      dumps the current state of that peer's profile.  Instead of the full
      base64, you can pass in however many characters you have and it will
      return the first match found.
2004-12-16 10:21:23 +00:00
3cb727561c uugly stat dumper. call via /dumpstats.jsp?peer=routerIdentHash 2004-12-16 09:45:31 +00:00
cbc89376d3 2004-12-16 jrandom
* Remove the randomized factor in the tunnel rejection by bandwidth -
      we now accept the request if we've allocated less than our limit
      and reject it if we've allocated more.
    * Stick to the standard capacity scale on tunnel rejection, even for
      the 10m period.
    * Build the time message at the very last possible moment
2004-12-16 05:42:03 +00:00
66aa29e3d4 2004-12-15 jrandom
* Handle hard disconnects more gracefully within the streaming lib, and
      log unmonitored events more aggressively.
    * If we drop a peer after connection due to clock skew, log it to the
      /logs.jsp#connectionlogs with relevent info.  In addition, toss it in
      the stat 'tcp.disconnectAfterSkew'.
    * Fixed the formatting in the skew display
    * Added an ERROR message that is fired once after we run out of
      routerInfo files (thanks susi!)
    * Set the connect timeout equal to the streaming lib's disconnect timeout
      if not already specified (the I2PTunnel httpclient already enforces a
      60s connect timeout)
    * Fix for another connection startup problem in the streaming lib.
    * Fix for a stupid error in the probabalistic drop (rand <= P, not > P)
    * Adjust the capacity calculations so that tunnel failures alone in the
      last 10m will not trigger a 0 capacity rank.
2004-12-16 02:45:55 +00:00
5c72aca5ee added sciencebooks.i2p 2004-12-15 04:23:16 +00:00
8824815d6d 2004-12-14 jrandom
* Periodically send a message along all I2NP connections with the router's
      current time, allowing the receiving peer to determine that the clock
      has skewed too much, and hence, disconnect.  For backwards compatability
      reasons, this is being kludged into a DeliveryStatusMessage (ewww).  The
      next time we have a backwards compatability break, we can put in a proper
      message setup for it.
2004-12-14 16:42:35 +00:00
ad72e5cbdf 2004-12-14 jrandom
* Reenable the probabalistic drop on the TCP queues to deal with good old
      fashioned bandwidth limiting.  However, by default the probability is
      rigged to reserve 0% of the queue free - meaning we just aggressively
      fail messages in the queue if we're transferring too slowly.  That
      reservation factor can be increased with 'tcp.queueFreeFactor=0.25'
      (or whatever) and the drop code can be disabled with the parameter
      'tcp.dropProbabalistically=false'.
    * Still penalize a peer on tunnel failure, but don't immediately drop
      their capacity to 0.
    * More aggressively ACK duplicates
    * Randomize the timestamper period
    * Display the clock skew on the connection logs when a peer sends it.
    * Allow the timestamper to fix skews of up to 10 minutes
    * Logging
2004-12-14 12:14:41 +00:00
b2f183fc17 2004-12-14 jrandom
* Reenable the probabalistic drop on the TCP queues to deal with good old
      fashioned bandwidth limiting.  However, by default the probability is
      rigged to reserve 0% of the queue free - meaning we just aggressively
      fail messages in the queue if we're transferring too slowly.  That
      reservation factor can be increased with 'tcp.queueFreeFactor=0.25'
      (or whatever) and the drop code can be disabled with the parameter
      'tcp.dropProbabalistically=false'.
    * Still penalize a peer on tunnel failure, but don't immediately drop
      their capacity to 0.
    * More aggressively ACK duplicates
    * Randomize the timestamper period
    * Display the clock skew on the connection logs when a peer sends it.
    * Allow the timestamper to fix skews of up to 10 minutes
    * Logging
2004-12-14 11:54:39 +00:00
9e16bc203a 2004-12-13 jrandom
* Added some error checking on the new client send job (thanks duck!)
    * Implemented tunnel rejection based on bandwidth usage (rejecting tunnels
      proportional to the bytes allocated in existing tunnels vs the bytes
      allowed through the bandwidth limiter).
    * Enable a new configuration parameter for triggering a tunnel rebuild
      (tunnel.maxTunnelFailures), where that is the max allowed test failures
      before killing the tunnel (default 0).
    * Gather more data that we rank capacity by (now we monitor and balance the
      data from 10m/30m/60m/1d instead of just 10m/60m/1d).
    * Fix a truncation/type conversion problem on the long term capacity
      values (we were ignoring the daily stats outright)
2004-12-13 13:45:52 +00:00
83c6eac017 added forum.fr.i2p, fedo.i2p, and pastebin.i2p 2004-12-13 08:48:29 +00:00
d5b277a536 test to verify that a closed socket is propogated to the client 2004-12-13 01:18:24 +00:00
77ce6c33e3 2004-12-11 jrandom
* Fix the missing HTTP timeout, which was caused by the deferred syn used
      by default.  This, in turn, meant the I2PSocket creation doesn't fail
      on .connect, but is unable to transfer any data in any direction.  We now
      detect that condition for the I2PTunnelHTTPClient and throw up the right
      error page.
    * Logging
2004-12-11 09:26:23 +00:00
60f8d349cf 2004-12-11 jrandom
* Use a simpler and less memory intensive job for processing outbound
      client messages when the session is in mode=bestEffort.  We can
      immediately discard the data as soon as its sent the first time,
      rather than wait for an ack, since we will never internally resend.
    * Reduce some synchronization to avoid a rare deadlock
    * Replaced 'localhost' with 127.0.0.1 in the i2ptunnel config, and special
      case it within the tunnel controller.
    * Script cleanup for building jbigi/jcpuid
    * Logging
2004-12-11 07:05:12 +00:00
f539c3df70 added frosk.i2p 2004-12-11 05:08:14 +00:00
fe1cf1758c added theland.i2p 2004-12-11 04:16:10 +00:00
8c71c26487 added dox.i2p 2004-12-11 01:22:03 +00:00
2ce39d1fd4 added amiga.i2p 2004-12-10 22:37:29 +00:00
88a994b712 cleaned up paths 2004-12-10 13:12:07 +00:00
24c8cc1a0c reference the new gmp 4.1.4 2004-12-10 10:22:17 +00:00
caf684394c crypto unit test to allow cross-architecture testing of jbigi and the crypto code 2004-12-10 08:48:23 +00:00
4b74510450 added source instructions (thanks bens\!) 2004-12-10 00:34:53 +00:00
mpc
0ddcfc423e *** empty log message *** 2004-12-09 14:05:22 +00:00
b4ac56e204 added frooze.i2p 2004-12-09 08:27:45 +00:00
3b19ac3942 use the standard I2CP port (7654) not jrandom's local I2CP port (duh) 2004-12-09 00:29:29 +00:00
af52cad4ea * 2004-12-08 0.4.2.3 released 2004-12-08 21:08:10 +00:00
d88396c1e2 2004-12-08 jrandom
* Revised the buffering when reading from the SAM client and writing
      to the stream.  Also added a thread (sigh) so we don't block the
      SAM client from giving us more messages for abnormally long periods
      of time.
    * Display the router version in the logs on startup (oft requested)
    * Fix a race during the closing of a messageOutputStream
2004-12-08 17:16:16 +00:00
4c5f7b9451 aliased gott.i2p as jrandom.i2p (ed. note: no, i am not gott) 2004-12-08 07:45:09 +00:00
e601cedbb8 2004-12-06 jrandom
* Don't do a 'passive flush' while there are already outbound messages
      unacked.
    * Show the reseed link if up to 10 peers profiles are active (thanks
      dburton!)
2004-12-07 01:35:53 +00:00
fa12dc867f 2004-12-06 jrandom
* Don't do a 'passive flush' while there are already outbound messages
      unacked.
    * Show the reseed link if up to 10 peers profiles are active (thanks
      dburton!)
2004-12-07 01:09:16 +00:00
acfb6c4578 Added sonax.i2p 2004-12-06 22:13:41 +00:00
e52d637092 2004-12-06 jrandom
* Don't propogate streaming connection failures out to the SAM bridge as
      fatal errors.
    * Dont barf on repeated I2CP closure.
2004-12-06 05:03:57 +00:00
2fba055696 2004-12-05 jrandom
* Explicitly use "127.0.0.1" to bind the I2CP listener, not the JVM's
      getLocalhost call
2004-12-06 02:08:02 +00:00
88bb176f3b 2004-12-05 jrandom
* Default the I2CP listener to localhost only, unless overridden by
      i2cp.tcp.bindAllInterfaces=true (thanks dm!)
    * More SAM fixes for things recently broken (whee)
2004-12-06 00:54:07 +00:00
499eeb275b added 1.fcp.freenet.i2p and copied fcp.i2p to 2.fcp.freenet.i2p 2004-12-05 22:18:57 +00:00
61a8d679bb 2004-12-05 jrandom
* Fix the recently broken SAM bridge (duh)
    * Add a new pair of SAM apps - net.i2p.sam.client.SAMStreamSink and
      net.i2p.sam.client.SAMStreamSend, mirroring the streaming lib's
      StreamSink and StreamSend apps for transferring files.
    * Make the passive flush timer fire more frequently.
2004-12-05 15:32:32 +00:00
2bbde91625 2004-12-05 jrandom
* Fixed some links in the console (thanks ugha!) and the javadoc
      (thanks dinoman!)
    * Fix the stream's passive flush timer (oh, its supposed to work?)
2004-12-05 10:22:57 +00:00
9ce098ee06 added asciiwhite.i2p 2004-12-05 08:47:02 +00:00
927ae57d24 added fcp.i2p 2004-12-05 03:16:30 +00:00
mpc
d65c2d3539 added installation instructions 2004-12-05 01:57:12 +00:00
2d9d8f32dc 2004-12-03 jrandom
* Toss in a small pool of threads (3) to execute the events queued up with
      the SimpleTimer, as we do currently see the occational event
      notification spiking up to a second or so.
    * Implement a SAM client API in java, useful for event based streaming (or
      for testing the SAM bridge)
    * Added support to shut down the SAM bridge on OOM (useful if the SAM
      bridge is being run outside of the router).
    * Include the SAM test code in the sam.jar
    * Remove an irrelevent warning message from SAM, which was caused by
      perfectly normal operation due to a session being closed.
    * Removed some unnecessary synchronization in the streaming lib's
      PacketQueue
    * More quickly clean up the memory used by the streaming lib by
      immediately killing each packet's resend job as soon as it is ACKed (or
      cancelled), so that there are no longer any valid pointers to the
      (potentially 32KB) packet.
    * Fixed the timestamps dumped to stdout when debugging the PacketHandler.
    * Drop packets that would expand our inbound window beyond our maximum
      buffer size (default 32 messages)
    * Always read the ACK/NACK data from the verified packets received, even
      if we are going to drop them
    * Always adjust the window when there are messages ACKed, though do not
      change its size except as before.
    * Streamlined some synchronization in the router's I2CP handling
    * Streamlined some memory allocation in the SAM bridge
    * Default the streaming lib to disconnect on inactivity, rather than send
      an empty message.
2004-12-04 23:43:07 +00:00
1a30cd5f4a 2004-12-03 jrandom
* Toss in a small pool of threads (3) to execute the events queued up with
      the SimpleTimer, as we do currently see the occational event
      notification spiking up to a second or so.
    * Implement a SAM client API in java, useful for event based streaming (or
      for testing the SAM bridge)
    * Added support to shut down the SAM bridge on OOM (useful if the SAM
      bridge is being run outside of the router).
    * Include the SAM test code in the sam.jar
    * Remove an irrelevent warning message from SAM, which was caused by
      perfectly normal operation due to a session being closed.
    * Removed some unnecessary synchronization in the streaming lib's
      PacketQueue
    * More quickly clean up the memory used by the streaming lib by
      immediately killing each packet's resend job as soon as it is ACKed (or
      cancelled), so that there are no longer any valid pointers to the
      (potentially 32KB) packet.
    * Fixed the timestamps dumped to stdout when debugging the PacketHandler.
    * Drop packets that would expand our inbound window beyond our maximum
      buffer size (default 32 messages)
    * Always read the ACK/NACK data from the verified packets received, even
      if we are going to drop them
    * Always adjust the window when there are messages ACKed, though do not
      change its size except as before.
    * Streamlined some synchronization in the router's I2CP handling
    * Streamlined some memory allocation in the SAM bridge
    * Default the streaming lib to disconnect on inactivity, rather than send
      an empty message.
this still doesnt get the BT to where it needs to be, or fix the timeout problem,
but i dont like having so many commits outstanding and these updates are sound
2004-12-04 23:40:50 +00:00
f54687f398 added greenflog.i2p 2004-12-03 20:54:01 +00:00
mpc
9f4b4c5de1 Still trying to get this to compile under VS.NET 2004-12-03 05:59:25 +00:00
mpc
33bfa94229 Added session to naming callback 2004-12-02 23:05:30 +00:00
mpc
61e5f190a6 Updated examples 2004-12-02 22:54:22 +00:00
mpc
a4946272d0 get rid of stdint.h stuff because it confuses the microsoft compiler 2004-12-02 22:16:27 +00:00
8abd99d134 2004-12-01 jrandom
* Fix for a race in the streaming lib as caused by some odd SAM activity
2004-12-02 03:20:03 +00:00
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
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
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
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
df61ae5c6f duh. thanks clayboy :) 2004-11-30 00:10:20 +00:00
9f6584b55e 2004-11-29 jrandom
* Minor fixes to avoid unnecessary errors on shutdown (thanks susi!)
2004-11-29 23:24:49 +00:00
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
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
d294d07919 added bdl.i2p 2004-11-29 20:55:38 +00:00
153eea2bd5 you mean i'm supposed to *test* it? 2004-11-29 03:35:39 +00:00
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
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
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
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
35e94a7f65 added evil.i2p 2004-11-27 06:56:29 +00:00
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
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
109 changed files with 4407 additions and 779 deletions

View File

@ -33,12 +33,12 @@ 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;
protected I2PSocketManager sockMgr;
protected List mySockets = new ArrayList();
protected Destination dest = null;
@ -184,13 +184,26 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
* create the default options (using the default timeout, etc)
*
*/
private I2PSocketOptions getDefaultOptions() {
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;
}
/**
* Create a new I2PSocket towards to the specified destination,

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,27 @@ 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, mySockets);
timeoutThread = new InactivityTimeoutThread(runner, out, targetRequest, usingWWWProxy, currentProxy, s, requestId);
timeoutThread.start();
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
} 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 +427,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 {
@ -476,6 +438,30 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
}
private class OnTimeout implements Runnable {
private Socket _socket;
private OutputStream _out;
private String _target;
private boolean _usingProxy;
private String _wwwProxy;
private long _requestId;
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
_socket = s;
_out = out;
_target = target;
_usingProxy = usingProxy;
_wwwProxy = wwwProxy;
_requestId = id;
}
public void run() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Timeout occured requesting " + _target);
handleHTTPClientException(new RuntimeException("Timeout"), _out,
_target, _usingProxy, _wwwProxy, _requestId);
closeSocket(_socket);
}
}
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
boolean usingWWWProxy, String wwwProxy) throws IOException {
if (out != null) {

View File

@ -46,15 +46,23 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
/** when the runner started up */
private long startedOn;
private List sockList;
/** if we die before receiving any data, run this job */
private Runnable onTimeout;
private long totalSent;
private long totalReceived;
private volatile long __forwarderId;
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData, List sockList) {
this(s, i2ps, slock, initialData, sockList, null);
}
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData, List sockList, Runnable onTimeout) {
this.sockList = sockList;
this.s = s;
this.i2ps = i2ps;
this.slock = slock;
this.initialData = initialData;
this.onTimeout = onTimeout;
lastActivityOn = -1;
startedOn = Clock.getInstance().now();
if (_log.shouldLog(Log.INFO))
@ -97,7 +105,6 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
}
public void run() {
boolean closedCleanly = false;
try {
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream(); // = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
@ -110,19 +117,33 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
//i2pout.flush();
}
}
Thread t1 = new StreamForwarder(in, i2pout, "toI2P");
Thread t2 = new StreamForwarder(i2pin, out, "fromI2P");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Initial data " + (initialData != null ? initialData.length : 0)
+ " written, starting forwarders");
Thread t1 = new StreamForwarder(in, i2pout, true);
Thread t2 = new StreamForwarder(i2pin, out, false);
synchronized (finishLock) {
while (!finished) {
finishLock.wait();
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("At least one forwarder completed, closing and joining");
// this task is useful for the httpclient
if (onTimeout != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("runner has a timeout job, totalReceived = " + totalReceived
+ " totalSent = " + totalSent + " job = " + onTimeout);
if ( (totalSent <= 0) && (totalReceived <= 0) )
onTimeout.run();
}
// now one connection is dead - kill the other as well.
s.close();
i2ps.close();
t1.join();
t2.join();
closedCleanly = true;
t1.join(30*1000);
t2.join(30*1000);
} catch (InterruptedException ex) {
if (_log.shouldLog(Log.ERROR))
_log.error("Interrupted", ex);
@ -135,21 +156,20 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
} finally {
removeRef();
try {
if ( (s != null) && (!closedCleanly) )
if (s != null)
s.close();
} catch (IOException ex) {
if (_log.shouldLog(Log.ERROR))
_log.error("Could not close java socket", ex);
}
try {
if (i2ps != null) {
if (!closedCleanly)
i2ps.close();
i2ps.setSocketErrorListener(null);
if (i2ps != null) {
try {
i2ps.close();
} catch (IOException ex) {
if (_log.shouldLog(Log.ERROR))
_log.error("Could not close I2PSocket", ex);
}
} catch (IOException ex) {
if (_log.shouldLog(Log.ERROR))
_log.error("Could not close I2PSocket", ex);
i2ps.setSocketErrorListener(null);
}
}
}
@ -176,12 +196,14 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
InputStream in;
OutputStream out;
String direction;
private boolean _toI2P;
private ByteCache _cache;
private StreamForwarder(InputStream in, OutputStream out, String dir) {
private StreamForwarder(InputStream in, OutputStream out, boolean toI2P) {
this.in = in;
this.out = out;
direction = dir;
_toI2P = toI2P;
direction = (toI2P ? "toI2P" : "fromI2P");
_cache = ByteCache.getInstance(256, NETWORK_BUFFER_SIZE);
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
start();
@ -202,6 +224,10 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
if (_toI2P)
totalSent += len;
else
totalReceived += len;
if (len > 0) updateActivity();
@ -221,7 +247,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
out.flush(); // make sure the data get though
}
}
out.flush();
//out.flush(); // close() flushes
} catch (SocketException ex) {
// this *will* occur when the other threads closes the socket
synchronized (finishLock) {
@ -237,8 +263,8 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
+ ex.getMessage() + "\")");
} catch (IOException ex) {
if (!finished) {
if (_log.shouldLog(Log.ERROR))
_log.error(direction + ": Error forwarding", ex);
if (_log.shouldLog(Log.WARN))
_log.warn(direction + ": Error forwarding", ex);
}
//else
// _log.warn("You may ignore this", ex);
@ -248,11 +274,16 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
+ from + " and " + to);
}
try {
out.close();
in.close();
} catch (IOException ex) {
if (_log.shouldLog(Log.WARN))
_log.warn(direction + ": Error closing streams", ex);
_log.warn(direction + ": Error closing input stream", ex);
}
try {
out.flush();
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn(direction + ": Error flushing to close", ioe);
}
synchronized (finishLock) {
finished = true;

View File

@ -233,6 +233,9 @@ public class TunnelController implements Logging {
String host = getI2CPHost();
if ( (host != null) && (host.length() > 0) )
_tunnel.host = host;
// woohah, special casing for people with ipv6/etc
if ("localhost".equals(_tunnel.host))
_tunnel.host = "127.0.0.1";
String port = getI2CPPort();
if ( (port != null) && (port.length() > 0) )
_tunnel.port = port;

View File

@ -103,7 +103,7 @@ class WebEditPageFormGenerator {
if ( (controller != null) && (controller.getTargetHost() != null) )
buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
else
buf.append("value=\"localhost\" ");
buf.append("value=\"127.0.0.1\" ");
buf.append(" /><br />\n");
buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
@ -285,7 +285,7 @@ class WebEditPageFormGenerator {
if ( (controller != null) && (controller.getI2CPHost() != null) )
buf.append(controller.getI2CPHost());
else
buf.append("localhost");
buf.append("127.0.0.1");
buf.append("\" /><br />\n");
buf.append("<b>I2CP port:</b> ");
buf.append("<input type=\"text\" name=\"clientPort\" size=\"20\" value=\"");

View File

@ -100,8 +100,10 @@ public class I2PSocketManagerFactory {
//p.setProperty("tunnels.depthInbound", "0");
}
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
if (i2cpHost != null)
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
if (i2cpPort > 0)
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
try {
I2PSession session = client.createSession(myPrivateKeyStream, opts);

View File

@ -0,0 +1,218 @@
package net.i2p.client.streaming;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
import java.util.Collections;
import java.util.HashMap;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PClientFactory;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
/**
* Sit around on a destination, receiving lots of data and sending lots of
* data to whomever talks to us.
*
* Usage: TestSwarm myKeyFile [peerDestFile ]*
*
*/
public class TestSwarm {
private I2PAppContext _context;
private Log _log;
private String _destFile;
private String _peerDestFiles[];
private String _conOptions;
private I2PSocketManager _manager;
private boolean _dead;
public static void main(String args[]) {
if (args.length < 1) {
System.err.println("Usage: TestSwarm myDestFile [peerDestFile ]*");
return;
}
I2PAppContext ctx = new I2PAppContext();
String files[] = new String[args.length - 1];
System.arraycopy(args, 1, files, 0, files.length);
TestSwarm swarm = new TestSwarm(ctx, args[0], files);
swarm.startup();
}
public TestSwarm(I2PAppContext ctx, String destFile, String peerDestFiles[]) {
_context = ctx;
_log = ctx.logManager().getLog(TestSwarm.class);
_dead = false;
_destFile = destFile;
_peerDestFiles = peerDestFiles;
_conOptions = "";
}
public void startup() {
_log.debug("Starting up");
File keys = new File(_destFile);
if (!keys.exists()) {
try {
I2PClientFactory.createClient().createDestination(new FileOutputStream(keys));
} catch (Exception e) {
_log.error("Error creating a new destination on " + keys, e);
return;
}
}
try {
_manager = I2PSocketManagerFactory.createManager(new FileInputStream(_destFile), null, -1, null);
} catch (Exception e) {
_log.error("Error creatign the manager", e);
return;
}
I2PThread listener = new I2PThread(new Listener(), "Listener");
listener.start();
connectWithPeers();
}
private void connectWithPeers() {
if (_peerDestFiles != null) {
for (int i = 0; i < _peerDestFiles.length; i++) {
try {
FileInputStream fin = new FileInputStream(_peerDestFiles[i]);
Destination dest = new Destination();
dest.readBytes(fin);
I2PThread flooder = new I2PThread(new Flooder(dest), "Flooder+" + dest.calculateHash().toBase64().substring(0,4));
flooder.start();
} catch (Exception e) {
_log.error("Unable to read the peer from " + _peerDestFiles[i], e);
}
}
}
}
private class Listener implements Runnable {
public void run() {
try {
I2PServerSocket ss = _manager.getServerSocket();
I2PSocket s = null;
while ( (s = ss.accept()) != null) {
I2PThread flooder = new I2PThread(new Flooder(s), "Flooder-" + s.getPeerDestination().calculateHash().toBase64().substring(0,4));
flooder.start();
}
} catch (Exception e) {
_log.error("Error listening", e);
}
}
}
private static volatile long __conId = 0;
private class Flooder implements Runnable {
private Destination _remoteDestination;
private I2PSocket _socket;
private boolean _closed;
private long _started;
private long _totalSent;
private long _totalReceived;
private long _lastReceived;
private long _lastReceivedOn;
private long _connectionId;
public Flooder(Destination dest) {
_socket = null;
_remoteDestination = dest;
_connectionId = ++__conId;
_closed = false;
_lastReceived = -1;
_lastReceivedOn = _context.clock().now();
_context.statManager().createRateStat("swarm." + _connectionId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
}
public Flooder(I2PSocket socket) {
_socket = socket;
_remoteDestination = socket.getPeerDestination();
_connectionId = ++__conId;
_closed = false;
_lastReceived = -1;
_lastReceivedOn = _context.clock().now();
_context.statManager().createRateStat("swarm." + _connectionId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
}
public long getConnectionId() { return _connectionId; }
public Destination getDestination() { return _remoteDestination; }
public void run() {
_started = _context.clock().now();
_context.statManager().addRateData("swarm." + _connectionId + ".started", 1, 0);
byte data[] = new byte[32*1024];
long value = 0;
long lastSend = _context.clock().now();
if (_socket == null) {
try {
_socket = _manager.connect(_remoteDestination);
} catch (Exception e) {
_log.error("Error connecting to " + _remoteDestination.calculateHash().toBase64().substring(0,4));
return;
}
}
I2PThread floodListener = new I2PThread(new FloodListener(), "FloodListener" + _connectionId);
floodListener.start();
try {
OutputStream out = _socket.getOutputStream();
while (!_closed) {
out.write(data);
// out.flush();
_totalSent += data.length;
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
//try { Thread.sleep(100); } catch (InterruptedException ie) {}
long now = _context.clock().now();
_log.debug("Sending " + _connectionId + " after " + (now-lastSend));
lastSend = now;
try { Thread.sleep(20); } catch (InterruptedException ie) {}
}
} catch (Exception e) {
_log.error("Error sending", e);
}
}
private class FloodListener implements Runnable {
public void run() {
long lastRead = System.currentTimeMillis();
long now = lastRead;
try {
InputStream in = _socket.getInputStream();
byte buf[] = new byte[32*1024];
int read = 0;
while ( (read = in.read(buf)) != -1) {
now = System.currentTimeMillis();
_totalReceived += read;
_context.statManager().addRateData("swarm." + getConnectionId() + ".totalReceived", _totalReceived, 0);
_log.debug("Receiving " + _connectionId + " with " + read + " after " + (now-lastRead));
lastRead = now;
}
} catch (Exception e) {
_log.error("Error listening to the flood", e);
}
}
}
}
}

View File

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

View File

@ -0,0 +1,42 @@
package net.i2p.router.web;
import java.util.Iterator;
import java.util.Set;
import java.io.ByteArrayOutputStream;
import java.io.Writer;
import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
/**
* uuuugly. dump the peer profile data if given a peer.
*
*/
public class StatHelper {
private String _peer;
private Writer _writer;
public void setPeer(String peer) { _peer = peer; }
public void setWriter(Writer writer) { _writer = writer; }
public String getProfile() {
RouterContext ctx = (RouterContext)net.i2p.router.RouterContext.listContexts().get(0);
Set peers = ctx.profileOrganizer().selectAllPeers();
for (Iterator iter = peers.iterator(); iter.hasNext(); ) {
Hash peer = (Hash)iter.next();
if (peer.toBase64().startsWith(_peer)) {
try {
WriterOutputStream wos = new WriterOutputStream(_writer);
ctx.profileOrganizer().exportProfile(peer, wos);
wos.flush();
_writer.flush();
return "";
} catch (Exception e) {
e.printStackTrace();
}
}
}
return "Unknown";
}
}

View File

@ -0,0 +1,17 @@
package net.i2p.router.web;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
/**
* Treat a writer as an output stream. Quick 'n dirty, none
* of that "intarnasheeonaleyzayshun" stuff. So we can treat
* the jsp's PrintWriter as an OutputStream
*/
public class WriterOutputStream extends OutputStream {
private Writer _writer;
public WriterOutputStream(Writer writer) { _writer = writer; }
public void write(int b) throws IOException { _writer.write(b); }
}

View File

@ -66,9 +66,9 @@
I2P will attempt to guess your IP address by having whomever it talks to tell it what
address they think you are. If and only if you have no working TCP connections and you
have not overridden the IP address, your router will believe them. If that doesn't sound
ok to you, thats fine - go to the <a href="/configadvanced.jsp">advanced config</a> page
ok to you, thats fine - go to the <a href="configadvanced.jsp">advanced config</a> page
and add "i2np.tcp.hostname=yourHostname", then go to the
<a href="/configservice.jsp">service</a> page and do a graceful restart. We used to make
<a href="configservice.jsp">service</a> page and do a graceful restart. We used to make
people enter a hostname/IP address on this page, but too many people got it wrong ;)</p>
<p>The other advanced network option has to do with reseeding - you should never need to

View File

@ -0,0 +1,5 @@
<%@page contentType="text/plain"
%><jsp:useBean id="helper" class="net.i2p.router.web.StatHelper"
/><jsp:setProperty name="helper" property="peer" value="<%=request.getParameter("peer")%>"
/><jsp:setProperty name="helper" property="writer" value="<%=out%>"
/><jsp:getProperty name="helper" property="profile" />

View File

@ -33,7 +33,7 @@ by their binary code license. This product includes software developed by the A
(http://www.apache.org/). </p>
<p>Another application you can see on this webpage is <a href="http://www.i2p.net/i2ptunnel">I2PTunnel</a>
(your <a href="/i2ptunnel/" target="_blank">web interface</a>) - a GPL'ed application written by mihi that
(your <a href="i2ptunnel/" target="_blank">web interface</a>) - a GPL'ed application written by mihi that
lets you tunnel normal TCP/IP traffic over I2P (such as the eepproxy and the irc proxy).</p>
<p>The router by default also includes human's public domain <a href="http://www.i2p.net/sam">SAM</a> bridge,

View File

@ -23,7 +23,7 @@
if (helper.getActivePeers() <= 0) {
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
}
if (helper.getActiveProfiles() <= 4) { // 4 is the min fallback
if (helper.getActiveProfiles() <= 10) { // 10 is the min fallback
if ("true".equals(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false"))) {
out.print(" <i>reseeding</i>");
} else {

View File

@ -21,9 +21,9 @@ SRCDIR = src
# Programs
#
AR = C:\Dev-Cpp\bin\ar
CC = C:\Dev-Cpp\bin\gcc
RM = C:\Dev-Cpp\bin\rm
AR = C:\MinGW\bin\ar
CC = C:\MinGW\bin\gcc
RM = C:\MinGW\bin\rm
#
# Flags

View File

@ -1 +1 @@
See the `docs' directory for documentation and license.
See the `docs' directory for the documentation and license.

View File

@ -1,7 +0,0 @@
If you would like to make a donation to the author of this library, you can use
the following methods:
* E-Gold account number 1043280
* Paypal email mpc@innographx.com
If you want to use some other method, just ask.

View File

@ -0,0 +1,32 @@
=====================
How to Install LibSAM
=====================
1) Be sure you have GNU Make installed.
2) Find the Makefile for your operating system in the LibSAM root. For example,
if you are on FreeBSD, you'd use Makefile.freebsd.
3) Run gmake with the -f option to build LibSAM. For example, on FreeBSD, you'd
run "gmake -f Makefile.freebsd". On Linux, GNU Make is just called 'make', so
you'd run "make -f Makefile.linux".
4) If that worked, you can now try to compile some of the example programs.
They are compiled in the same way as LibSAM, but the Makefile names are
different.
I2P-Ping should compile on any Unix system using the default Makefile. It won't
work on Win32, however, because Win32 doesn't have the getopt() function.
The Warhammer example should run on any OS. Use the Makefile.posix for Unix
sytems or the Makefile.mingw for Win32.
*** If you have trouble ***
If you have trouble compiling LibSAM, try to edit the Makefiles to fix the
problem. The "Makefile.common" is shared by all systems, and you shouldn't have
to touch it. Just copy the Makefile of the OS most similar to your own and
start hacking on it. Send me a patch if you get it working.
I realise this build system is horrible, and in the future I will probably
replace it entirely.

View File

@ -1,10 +1,10 @@
I need to do these things:
* SAM raw support
* SAM raw support (partially complete)
* Write an instruction manual
* Make dest a dynamic string
* Change SAM parser to use a hashmap
* Switch to GNU Autoconf (?)
* Improve build system
Anyone can help with these things:

View File

@ -1,6 +1,8 @@
/* vi:set ts=4: */
v1.30
* Added session to sam_namingback()
* Removed stdint.h dependency
* Improved WIRETAP to do more logging
* Added "pinger.sh" shell script example for using i2p-ping
* Added SAM_BAD_STYLE error

View File

@ -60,7 +60,8 @@ static void databack(sam_sess_t *session, sam_sid_t stream_id, void *data,
size_t size);
static void diedback(sam_sess_t *session);
static void logback(char *s);
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result);
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
samerr_t result);
static void statusback(sam_sess_t *session, sam_sid_t stream_id,
samerr_t result);
@ -78,7 +79,7 @@ int main(int argc, char *argv[])
int count = INT_MAX; /* number of times to ping */
int pongcount = -1;
char *samhost = "localhost";
uint16_t samport = 7656;
unsigned short samport = 7656;
while ((ch = getopt(argc, argv, "ac:h:mp:qv")) != -1) {
switch (ch) {
@ -103,7 +104,7 @@ int main(int argc, char *argv[])
quiet = true;
break;
case 'v': /* version */
puts("$Id: i2p-ping.c,v 1.4 2004/09/22 20:05:40 jrandom Exp $");
puts("$Id: i2p-ping.c,v 1.6 2004/12/02 17:54:23 mpc Exp $");
puts("Copyright (c) 2004, Matthew P. Cashdollar <mpc@innographx.com>");
break;
case '?':
@ -140,7 +141,7 @@ int main(int argc, char *argv[])
pongcount = 0;
for (int j = 0; j < argc; j++) {
if (strlen(argv[j]) == 516) {
if (strlen(argv[j]) == SAM_PUBKEY_LEN - 1) {
memcpy(dest, argv[j], SAM_PUBKEY_LEN);
gotdest = true;
} else
@ -242,7 +243,8 @@ static void logback(char *s)
* This is really hackish, but we know that we are only doing one lookup, so
* what the hell
*/
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result)
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
samerr_t result)
{
if (result != SAM_OK) {
fprintf(stderr, "Naming lookup failed: %s\n", sam_strerror(result));

View File

@ -6,8 +6,8 @@
# Programs
#
CC = C:\Dev-Cpp\bin\gcc
RM = C:\Dev-Cpp\bin\rm
CC = C:\MinGW\bin\gcc
RM = C:\MinGW\bin\rm
#
# Flags

View File

@ -47,7 +47,8 @@ static void dgramback(sam_sess_t *session, sam_pubkey_t dest, void *data,
size_t size);
static void diedback(sam_sess_t *session);
static void logback(char *s);
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result);
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
samerr_t result);
/*
* Just some ugly global variables. Don't do this in your program.
@ -88,12 +89,12 @@ int main(int argc, char *argv[])
}
/*
* Check whether they've supplied a name or a base 64 destination
* Check whether they've supplied a hostname or a base 64 destination
*
* Note that this is a hack. Jrandom says that once certificates are added,
* the length could be different depending on the certificate's size.
*/
if (strlen(argv[1]) == 516) {
if (strlen(argv[1]) == SAM_PUBKEY_LEN - 1) {
memcpy(dest, argv[1], SAM_PUBKEY_LEN);
gotdest = true;
} else {
@ -155,7 +156,6 @@ static void dgramback(sam_sess_t *session, sam_pubkey_t dest, void *data,
static void diedback(sam_sess_t *session)
{
fprintf(stderr, "Lost SAM connection!\n");
/* high quality code would do a sam_session_free() here */
exit(1);
}
@ -172,11 +172,11 @@ static void logback(char *s)
* This is really hackish, but we know that we are only doing one lookup, so
* what the hell
*/
static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result)
static void namingback(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
samerr_t result)
{
if (result != SAM_OK) {
fprintf(stderr, "Naming lookup failed: %s\n", sam_strerror(result));
/* high quality code would do a sam_session_free() here */
exit(1);
}
memcpy(dest, pubkey, SAM_PUBKEY_LEN);

View File

@ -28,36 +28,19 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef PLATFORM_H
#define PLATFORM_H
#ifndef LIBSAM_PLATFORM_H
#define LIBSAM_PLATFORM_H
/*
* Operating system
*/
#define FREEBSD 0 // FreeBSD
#define MINGW 1 // Windows native (Mingw)
#define CYGWIN 1 // Cygwin
#define LINUX 2 // Linux
#define CYGWIN 3 // Cygwin
#if OS == MINGW
#define INET_ADDRSTRLEN 16
#define NO_GETHOSTBYNAME2
#define NO_INET_ATON /* implies NO_INET_PTON */
#define NO_INET_NTOP
#define NO_SSIZE_T
#define NO_STRL
#define NO_Z_FORMAT
#define WINSOCK
#endif
#if OS == LINUX
#define NO_GETHOSTBYNAME2
#define NO_STRL
#define NO_Z_FORMAT
#endif
#define MINGW 3 // Windows native (Mingw)
#define MSVC 4 // Windows native (Visual C++ 2003)
#if OS == CYGWIN
#define FAST32_IS_LONG
#define INET_ADDRSTRLEN 16
#define NO_GETHOSTBYNAME2
#define NO_INET_NTOP
@ -68,13 +51,29 @@
#define NO_Z_FORMAT
#endif
/*
* Standard C99 includes - if your compiler doesn't have these, it's time to
* upgrade
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#if OS == LINUX
#define NO_GETHOSTBYNAME2
#define NO_STRL
#define NO_Z_FORMAT
#endif
#if OS == MINGW
#define INET_ADDRSTRLEN 16
#define NO_GETHOSTBYNAME2
#define NO_INET_ATON // implies NO_INET_PTON
#define NO_INET_NTOP
#define NO_SSIZE_T
#define NO_STRL
#define NO_Z_FORMAT
#define WINSOCK
#endif
#if OS == MSVC // FIXME: doesn't work
#define NO_STDBOOL_H
#define NO_SSIZE_T
#define NO_STRL
#define WINSOCK
#endif
/*
* System includes
@ -116,6 +115,13 @@
typedef signed long ssize_t;
#endif
/*
* I'm too lazy to type "unsigned"
*/
typedef unsigned char byte;
typedef unsigned int uint;
typedef unsigned short ushort;
/*
* Prints out the file name, line number, and function name before log message
*/
@ -136,4 +142,4 @@
#include <ctype.h>
#endif
#endif /* PLATFORM_H */
#endif /* LIBSAM_PLATFORM_H */

View File

@ -28,19 +28,26 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef SAM_H
#define SAM_H
#ifndef LIBSAM_SAM_H
#define LIBSAM_SAM_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef NO_STDBOOL_H
typedef int bool;
#define true 0
#define false 1
#else
#include <stdbool.h>
#endif
#include <stddef.h> // size_t
/*
* Lengths
*/
/* The maximum length a SAM command can be */
#define SAM_CMD_LEN 128
/* The maximum size of a single datagram packet */
@ -49,29 +56,21 @@ 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
*/
typedef signed char schar_t;
typedef unsigned char uchar_t;
typedef unsigned int uint_t;
typedef unsigned long ulong_t;
typedef unsigned short ushort_t;
#ifdef WINSOCK
typedef SOCKET socket_t;
#else
typedef int socket_t;
#endif
/*
* Some LibSAM variable types
*/
typedef enum {SAM_STREAM, SAM_DGRAM, SAM_RAW} sam_conn_t; /* SAM connection */
@ -82,12 +81,13 @@ typedef struct {
size_t size;
} sam_sendq_t; /* sending queue to encourage large stream packet sizes */
typedef int_fast32_t sam_sid_t; /* stream id number */
typedef long sam_sid_t; /* stream id number */
typedef struct {
socket_t sock; /* the socket used for communications with SAM */
int sock; /* the socket used for communications with SAM */
bool connected; /* whether the socket is connected */
sam_sid_t prev_id; /* the last stream id number we used */
void *child; /* whatever you want it to be */
} sam_sess_t; /* a SAM session */
typedef enum { /* see sam_strerror() for detailed descriptions of these */
@ -102,59 +102,65 @@ 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,
unsigned short samport, const char *destname, sam_conn_t style,
unsigned int 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)(sam_sess_t *session, 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
}
#endif
#endif /* SAM_H */
#endif /* LIBSAM_SAM_H */

View File

@ -33,8 +33,8 @@
* snprintf.c)
*/
#ifndef SNPRINTF_H
#define SNPRINTF_H
#ifndef LIBSAM_SNPRINTF_H
#define LIBSAM_SNPRINTF_H
#ifdef __cplusplus
extern "C" {
#endif
@ -46,4 +46,4 @@ int vsnprintf (char *str, size_t count, const char *fmt, va_list arg);
#ifdef __cplusplus
}
#endif
#endif /* SNPRINTF_H */
#endif /* LIBSAM_SNPRINTF_H */

View File

@ -32,8 +32,8 @@
* Note: The strl.c file retains its original license (at the top of strl.c)
*/
#ifndef STRL_H
#define STRL_H
#ifndef LIBSAM_STRL_H
#define LIBSAM_STRL_H
#ifdef __cplusplus
extern "C" {
#endif
@ -44,4 +44,4 @@ extern size_t strlcpy(char *dst, const char *src, size_t siz);
#ifdef __cplusplus
}
#endif
#endif /* STRL_H */
#endif /* LIBSAM_STRL_H */

View File

@ -28,8 +28,8 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "platform.h"
#include "sam.h"
#include "platform.h"
static bool sam_hello(sam_sess_t *session);
static void sam_log(const char *format, ...);
@ -40,9 +40,9 @@ static bool sam_readable(sam_sess_t *session);
static sam_sendq_t *sam_sendq_create();
static samerr_t sam_session_create(sam_sess_t *session,
const char *destname, sam_conn_t style,
uint_t tunneldepth);
uint tunneldepth);
static bool sam_socket_connect(sam_sess_t *session, const char *host,
uint16_t port);
ushort port);
static bool sam_socket_resolve(const char *hostname, char *ipaddr);
#ifdef WINSOCK
static samerr_t sam_winsock_cleanup();
@ -55,28 +55,41 @@ 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;
void (*sam_namingback)(sam_sess_t *session, char *name, sam_pubkey_t pubkey,
samerr_t result) = NULL;
/* our connection to a peer has completed */
void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
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
*
@ -134,8 +147,8 @@ bool sam_close(sam_sess_t *session)
*
* Returns: SAM error code. If SAM_OK, `session' will be ready for use.
*/
samerr_t sam_connect(sam_sess_t *session, const char *samhost, uint16_t samport,
const char *destname, sam_conn_t style, uint_t tunneldepth)
samerr_t sam_connect(sam_sess_t *session, const char *samhost, ushort samport,
const char *destname, sam_conn_t style, uint tunneldepth)
{
assert(session != NULL);
samerr_t rc;
@ -155,7 +168,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;
@ -295,6 +312,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 +323,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;
@ -365,7 +387,7 @@ static void sam_parse(sam_sess_t *session, char *s)
q++;
strlcpy(name, p, sizeof name);
strlcpy(pubkey, q, sizeof pubkey);
sam_namingback(name, pubkey, SAM_OK);
sam_namingback(session, name, pubkey, SAM_OK);
} else if (strncmp(s, SAM_NAMING_REPLY_IK,
strlen(SAM_NAMING_REPLY_IK)) == 0) {
@ -373,7 +395,7 @@ static void sam_parse(sam_sess_t *session, char *s)
if (q != NULL)
*q = '\0';
strlcpy(name, p, sizeof name);
sam_namingback(name, NULL, SAM_INVALID_KEY);
sam_namingback(session, name, NULL, SAM_INVALID_KEY);
} else if (strncmp(s, SAM_NAMING_REPLY_KNF,
strlen(SAM_NAMING_REPLY_KNF)) == 0) {
@ -381,14 +403,14 @@ static void sam_parse(sam_sess_t *session, char *s)
if (q != NULL)
*q = '\0';
strlcpy(name, p, sizeof name);
sam_namingback(name, NULL, SAM_KEY_NOT_FOUND);
sam_namingback(session, name, NULL, SAM_KEY_NOT_FOUND);
} else {
q = strchr(p, ' '); /* ' 'MES.. (optional) */
if (q != NULL)
*q = '\0';
strlcpy(name, p, sizeof name);
sam_namingback(name, NULL, SAM_UNKNOWN);
sam_namingback(session, name, NULL, SAM_UNKNOWN);
}
return;
@ -518,6 +540,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
*
@ -666,10 +724,10 @@ static ssize_t sam_read2(sam_sess_t *session, void *buf, size_t n)
p = buf;
printf("*RR* ");
for (size_t x = 0; x < n; x++) {
if (isprint(((uchar_t*)p)[x]))
printf("%c,", ((uchar_t*)p)[x]);
if (isprint(((byte*)p)[x]))
printf("%c,", ((byte*)p)[x]);
else
printf("%03d,", ((uint8_t*)p)[x]);
printf("%03d,", ((byte*)p)[x]);
}
printf("\n");
printf("*RR* (read2() read %d bytes)\n", n);
@ -820,7 +878,7 @@ void sam_sendq_flush(sam_sess_t *session, sam_sid_t stream_id,
* Returns: SAM error code
*/
static samerr_t sam_session_create(sam_sess_t *session, const char *destname,
sam_conn_t style, uint_t tunneldepth)
sam_conn_t style, uint tunneldepth)
{
assert(session != NULL);
#define SAM_SESSTATUS_REPLY_OK "SESSION STATUS RESULT=OK"
@ -879,6 +937,7 @@ sam_sess_t *sam_session_init(sam_sess_t *session)
SAMLOGS("Out of memory");
abort();
}
session->child = NULL;
}
session->connected = false;
session->prev_id = 0;
@ -906,7 +965,7 @@ void sam_session_free(sam_sess_t **session)
*
* Returns: true on sucess, false on error, with errno set
*/
bool sam_socket_connect(sam_sess_t *session, const char *host, uint16_t port)
bool sam_socket_connect(sam_sess_t *session, const char *host, ushort port)
{
assert(session != NULL);
struct sockaddr_in hostaddr;
@ -1023,11 +1082,7 @@ void sam_stream_close(sam_sess_t *session, sam_sid_t stream_id)
assert(session != NULL);
char cmd[SAM_CMD_LEN];
#ifdef FAST32_IS_LONG
snprintf(cmd, sizeof cmd, "STREAM CLOSE ID=%ld\n", stream_id);
#else
snprintf(cmd, sizeof cmd, "STREAM CLOSE ID=%d\n", stream_id);
#endif
sam_write(session, cmd, strlen(cmd));
return;
@ -1046,13 +1101,8 @@ sam_sid_t sam_stream_connect(sam_sess_t *session, const sam_pubkey_t dest)
char cmd[SAM_PKCMD_LEN];
session->prev_id++; /* increment the id for the connection */
#ifdef FAST32_IS_LONG
snprintf(cmd, sizeof cmd, "STREAM CONNECT ID=%ld DESTINATION=%s\n",
session->prev_id, dest);
#else
snprintf(cmd, sizeof cmd, "STREAM CONNECT ID=%d DESTINATION=%s\n",
session->prev_id, dest);
#endif
sam_write(session, cmd, strlen(cmd));
return session->prev_id;
@ -1084,15 +1134,9 @@ samerr_t sam_stream_send(sam_sess_t *session, sam_sid_t stream_id,
return SAM_TOO_BIG;
}
#ifdef NO_Z_FORMAT
#ifdef FAST32_IS_LONG
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%ld SIZE=%u\n",
stream_id, size);
#else
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%d SIZE=%u\n",
stream_id, size);
#endif
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%ld SIZE=%u\n", stream_id, size);
#else
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%d SIZE=%zu\n",
snprintf(cmd, sizeof cmd, "STREAM SEND ID=%ld SIZE=%zu\n",
stream_id, size);
#endif
sam_write(session, cmd, strlen(cmd));
@ -1342,7 +1386,7 @@ static ssize_t sam_write(sam_sess_t *session, const void *buf, size_t n)
return -1;
}
#if SAM_WIRETAP
const uchar_t *cp = buf;
const byte *cp = buf;
printf("*WW* ");
for (size_t x = 0; x < n; x++) {
if (isprint(cp[x]))

View File

@ -10,7 +10,7 @@
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac
srcdir="./src"
srcdir="./src:./test"
debug="true" deprecation="on" source="1.3" target="1.3"
destdir="./build/obj"
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />

View File

@ -220,6 +220,15 @@ public class SAMBridge implements Runnable {
}
SAMBridge bridge = new SAMBridge(host, port, opts, keyfile);
I2PThread t = new I2PThread(bridge, "SAMListener");
if (Boolean.valueOf(System.getProperty("sam.shutdownOnOOM", "false")).booleanValue()) {
t.addOOMEventListener(new I2PThread.OOMEventListener() {
public void outOfMemory(OutOfMemoryError err) {
err.printStackTrace();
System.err.println("OOMed, die die die");
System.exit(-1);
}
});
}
t.start();
}

View File

@ -119,6 +119,8 @@ public abstract class SAMHandler implements Runnable {
* @return True is the string was successfully written, false otherwise
*/
protected final boolean writeString(String str) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending the client: [" + str + "]");
try {
writeBytes(str.getBytes("ISO-8859-1"));
} catch (IOException e) {

View File

@ -17,6 +17,7 @@ import java.net.Socket;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
@ -36,14 +37,11 @@ public class SAMHandlerFactory {
* @return A SAM protocol handler, or null if the client closed before the handshake
*/
public static SAMHandler createSAMHandler(Socket s, Properties i2cpProps) throws SAMException {
BufferedReader br;
String line;
StringTokenizer tok;
try {
br = new BufferedReader(new InputStreamReader(s.getInputStream(),
"ISO-8859-1"));
line = br.readLine();
line = DataHelper.readLine(s.getInputStream());
if (line == null) {
_log.debug("Connection closed by client");
return null;

View File

@ -15,8 +15,10 @@ import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
@ -28,8 +30,11 @@ import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.ByteCache;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
@ -51,7 +56,10 @@ public class SAMStreamSession {
private I2PSocketManager socketMgr = null;
private Object handlersMapLock = new Object();
/** stream id (Long) to SAMStreamSessionSocketReader */
private HashMap handlersMap = new HashMap();
/** stream id (Long) to StreamSender */
private HashMap sendersMap = new HashMap();
private Object idLock = new Object();
private int lastNegativeId = 0;
@ -59,6 +67,14 @@ public class SAMStreamSession {
// Can we create outgoing connections?
private boolean canCreate = false;
/**
* should we flush every time we get a STREAM SEND, or leave that up to
* the streaming lib to decide?
*/
private boolean forceFlush = false;
public static String PROP_FORCE_FLUSH = "sam.forceFlush";
public static String DEFAULT_FORCE_FLUSH = "false";
/**
* Create a new SAM STREAM session.
*
@ -107,9 +123,6 @@ public class SAMStreamSession {
} catch (NumberFormatException nfe) {
throw new SAMException("Invalid I2CP port specified [" + port + "]");
}
// streams MUST be mode=guaranteed (though i think the socket manager
// enforces this anyway...
allprops.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
_log.debug("Creating I2PSocketManager...");
socketMgr = I2PSocketManagerFactory.createManager(destStream,
@ -120,6 +133,8 @@ public class SAMStreamSession {
throw new SAMException("Error creating I2PSocketManager");
}
forceFlush = Boolean.valueOf(allprops.getProperty(PROP_FORCE_FLUSH, DEFAULT_FORCE_FLUSH)).booleanValue();
boolean canReceive = false;
if (dir.equals("BOTH")) {
canCreate = true;
@ -197,18 +212,25 @@ public class SAMStreamSession {
*
* @param data Bytes to be sent
*
* @return True if the data was sent, false otherwise
* @return True if the data was queued for sending, false otherwise
*/
public boolean sendBytes(int id, byte[] data) {
Destination d = new Destination();
SAMStreamSessionSocketHandler handler = getSocketHandler(id);
public boolean sendBytes(int id, InputStream in, int size) throws IOException {
StreamSender sender = getSender(id);
if (handler == null) {
_log.error("Trying to send bytes through inexistent handler " +id);
if (sender == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Trying to send bytes through nonexistent handler " +id);
// even though it failed, we need to read those bytes!
for (int i = 0; i < size; i++) {
int c = in.read();
if (c == -1)
break;
}
return false;
}
return handler.sendBytes(data);
sender.sendBytes(in, size);
return true;
}
/**
@ -248,13 +270,15 @@ public class SAMStreamSession {
* @return An id associated to the socket handler
*/
private int createSocketHandler(I2PSocket s, int id) {
SAMStreamSessionSocketHandler handler;
SAMStreamSessionSocketReader reader = null;
StreamSender sender = null;
if (id == 0) {
id = createUniqueId();
}
try {
handler = new SAMStreamSessionSocketHandler(s, id);
reader = new SAMStreamSessionSocketReader(s, id);
sender = new StreamSender(s, id);
} catch (IOException e) {
_log.error("IOException when creating SAM STREAM session socket handler", e);
recv.stopStreamReceiving();
@ -262,10 +286,13 @@ public class SAMStreamSession {
}
synchronized (handlersMapLock) {
handlersMap.put(new Integer(id), handler);
handlersMap.put(new Integer(id), reader);
sendersMap.put(new Integer(id), sender);
}
I2PThread t = new I2PThread(handler, "SAMStreamSessionSocketHandler");
I2PThread t = new I2PThread(reader, "SAMReader" + id);
t.start();
t = new I2PThread(sender, "SAMSender" + id);
t.start();
return id;
@ -283,9 +310,14 @@ public class SAMStreamSession {
*
* @param id Handler id
*/
private SAMStreamSessionSocketHandler getSocketHandler(int id) {
private SAMStreamSessionSocketReader getSocketReader(int id) {
synchronized (handlersMapLock) {
return (SAMStreamSessionSocketHandler)handlersMap.get(new Integer(id));
return (SAMStreamSessionSocketReader)handlersMap.get(new Integer(id));
}
}
private StreamSender getSender(int id) {
synchronized (handlersMapLock) {
return (StreamSender)sendersMap.get(new Integer(id));
}
}
@ -306,19 +338,19 @@ public class SAMStreamSession {
* @param id Handler id to be removed
*/
private void removeSocketHandler(int id) {
SAMStreamSessionSocketHandler removed;
SAMStreamSessionSocketReader reader = null;
StreamSender sender = null;
synchronized (handlersMapLock) {
removed = (SAMStreamSessionSocketHandler)handlersMap.remove(new Integer(id));
reader = (SAMStreamSessionSocketReader)handlersMap.remove(new Integer(id));
sender = (StreamSender)sendersMap.remove(new Integer(id));
}
if (removed == null) {
_log.error("BUG! Trying to remove inexistent SAM STREAM session socket handler " + id);
recv.stopStreamReceiving();
} else {
removed.stopRunning();
_log.debug("Removed SAM STREAM session socket handler " + id);
}
if (reader != null)
reader.stopRunning();
if (sender != null)
sender.stopRunning();
_log.debug("Removed SAM STREAM session socket handler " + id);
}
/**
@ -337,9 +369,11 @@ public class SAMStreamSession {
while (iter.hasNext()) {
id = (Integer)iter.next();
((SAMStreamSessionSocketHandler)handlersMap.get(id)).stopRunning();
((SAMStreamSessionSocketReader)handlersMap.get(id)).stopRunning();
((StreamSender)sendersMap.get(id)).stopRunning();
}
handlersMap.clear();
sendersMap.clear();
}
}
@ -391,6 +425,8 @@ public class SAMStreamSession {
while (stillRunning) {
try {
i2ps = serverSocket.accept();
if (i2ps == null)
break;
_log.debug("New incoming connection");
@ -417,6 +453,8 @@ public class SAMStreamSession {
} catch (I2PException e) {
_log.debug("Caught I2PException", e);
}
close();
_log.debug("Shutting down SAM STREAM session server");
}
@ -429,10 +467,9 @@ public class SAMStreamSession {
*
* @author human
*/
public class SAMStreamSessionSocketHandler implements Runnable {
public class SAMStreamSessionSocketReader implements Runnable {
private I2PSocket i2pSocket = null;
private OutputStream i2pSocketOS = null;
private Object runningLock = new Object();
private boolean stillRunning = true;
@ -440,43 +477,20 @@ public class SAMStreamSession {
private int id;
/**
* Create a new SAM STREAM session socket handler
* Create a new SAM STREAM session socket reader
*
* @param s Socket to be handled
* @param id Unique id assigned to the handler
*/
public SAMStreamSessionSocketHandler(I2PSocket s, int id) throws IOException {
public SAMStreamSessionSocketReader(I2PSocket s, int id) throws IOException {
_log.debug("Instantiating new SAM STREAM session socket handler");
i2pSocket = s;
i2pSocketOS = s.getOutputStream();
this.id = id;
}
/**
* Send bytes through the SAM STREAM session socket handler
*
* @param data Data to be sent
*
* @return True if data has been sent without errors, false otherwise
*/
public boolean sendBytes(byte[] data) {
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Handler " + id + ": sending " + data.length
+ " bytes");
}
try {
i2pSocketOS.write(data);
} catch (IOException e) {
_log.error("Error sending data through I2P socket", e);
return false;
}
return true;
}
/**
* Stop a SAM STREAM session socket handler
* Stop a SAM STREAM session socket reader
*
*/
public void stopRunning() {
@ -535,4 +549,99 @@ public class SAMStreamSession {
_log.debug("Shutting down SAM STREAM session socket handler " +id);
}
}
/**
* Lets us push data through the stream without blocking, (even after exceeding
* the I2PSocket's buffer)
*/
private class StreamSender implements Runnable {
private List _data;
private int _id;
private ByteCache _cache;
private OutputStream _out = null;
private boolean _stillRunning;
public StreamSender(I2PSocket s, int id) throws IOException {
_data = new ArrayList(1);
_id = id;
_cache = ByteCache.getInstance(4, 32*1024);
_out = s.getOutputStream();
_stillRunning = true;
}
/**
* Send bytes through the SAM STREAM session socket sender
*
* @param data Data to be sent
*
* @throws IOException if the client didnt provide enough data
*/
public void sendBytes(InputStream in, int size) throws IOException {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handler " + _id + ": sending " + size + " bytes");
ByteArray ba = _cache.acquire();
int read = DataHelper.read(in, ba.getData(), 0, size);
if (read != size)
throw new IOException("Insufficient data from the SAM client (" + read + "/" + size + ")");
ba.setValid(read);
synchronized (_data) {
_data.add(ba);
_data.notifyAll();
}
}
/**
* Stop a SAM STREAM session socket sender
*
*/
public void stopRunning() {
_log.debug("stopRunning() invoked on socket sender " + _id);
_stillRunning = false;
synchronized (_data) {
_data.clear();
_data.notifyAll();
}
}
public void run() {
ByteArray data = null;
while (_stillRunning) {
data = null;
try {
synchronized (_data) {
if (_data.size() > 0)
data = (ByteArray)_data.remove(0);
else
_data.wait(5000);
}
if (data != null) {
try {
_out.write(data.getData(), 0, data.getValid());
if (forceFlush) {
// i dont like doing this, but it clears the buffer issues
_out.flush();
}
} catch (IOException ioe) {
// ok, the stream failed, but the SAM client didn't
if (_log.shouldLog(Log.WARN))
_log.warn("Stream failed", ioe);
removeSocketHandler(_id);
stopRunning();
} finally {
_cache.release(data);
}
}
} catch (InterruptedException ie) {}
}
synchronized (_data) {
_data.clear();
}
}
}
}

View File

@ -26,6 +26,7 @@ import net.i2p.I2PException;
import net.i2p.client.I2PSessionException;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.Log;
@ -44,6 +45,9 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
private SAMDatagramSession datagramSession = null;
private SAMStreamSession streamSession = null;
private long _id;
private static volatile long __id = 0;
/**
* Create a new SAM version 1 handler. This constructor expects
* that the SAM HELLO message has been still answered (and
@ -68,6 +72,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
*/
public SAMv1Handler(Socket s, int verMajor, int verMinor, Properties i2cpProps) throws SAMException, IOException {
super(s, verMajor, verMinor, i2cpProps);
_id = ++__id;
_log.debug("SAM version 1 handler instantiated");
if ((this.verMajor != 1) || (this.verMinor != 0)) {
@ -76,13 +81,14 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
public void handle() {
String msg, domain, opcode;
String msg = null;
String domain = null;
String opcode = null;
boolean canContinue = false;
ByteArrayOutputStream buf = new ByteArrayOutputStream(IN_BUFSIZE);
StringTokenizer tok;
Properties props;
this.thread.setName("SAMv1Handler");
this.thread.setName("SAMv1Handler " + _id);
_log.debug("SAM handling started");
try {
@ -95,22 +101,15 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
break;
}
while ((b = in.read()) != -1) {
if (b == '\n') {
break;
}
buf.write(b);
}
if (b == -1) {
msg = DataHelper.readLine(in);
if (msg == null) {
_log.debug("Connection closed by client");
break;
}
msg = buf.toString("ISO-8859-1").trim();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("New message received: [" + msg + "]");
}
buf.reset();
tok = new StringTokenizer(msg, " ");
if (tok.countTokens() < 2) {
@ -150,14 +149,11 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
break;
}
}
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")", e);
} catch (IOException e) {
_log.debug("Caught IOException ("
+ e.getMessage() + ")", e);
+ e.getMessage() + ") for message [" + msg + "]", e);
} catch (Exception e) {
_log.error("Unexpected exception", e);
_log.error("Unexpected exception for message [" + msg + "]", e);
} finally {
_log.debug("Stopping handler");
try {
@ -550,13 +546,9 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
try {
DataInputStream in = new DataInputStream(getClientSocketInputStream());
byte[] data = new byte[size];
in.readFully(data);
if (!streamSession.sendBytes(id, data)) {
_log.error("STREAM SEND failed");
if (!streamSession.sendBytes(id, getClientSocketInputStream(), size)) { // data)) {
if (_log.shouldLog(Log.WARN))
_log.warn("STREAM SEND [" + size + "] failed");
boolean rv = writeString("STREAM CLOSED RESULT=CANT_REACH_PEER ID=" + id + " MESSAGE=\"Send of " + size + " bytes failed\"\n");
streamSession.closeConnection(id);
return rv;
@ -564,11 +556,11 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
return true;
} catch (EOFException e) {
_log.debug("Too few bytes with RAW SEND message (expected: "
_log.debug("Too few bytes with STREAM SEND message (expected: "
+ size);
return false;
} catch (IOException e) {
_log.debug("Caught IOException while parsing RAW SEND message",
_log.debug("Caught IOException while parsing STREAM SEND message",
e);
return false;
}
@ -707,7 +699,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
try {
closeClientSocket();
} catch (IOException e) {
_log.error("Error closing socket: " + e.getMessage());
if (_log.shouldLog(Log.WARN))
_log.warn("Error closing socket", e);
}
}
@ -742,7 +735,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
try {
closeClientSocket();
} catch (IOException e) {
_log.error("Error closing socket: " + e.getMessage());
if (_log.shouldLog(Log.WARN))
_log.warn("Error closing socket", e);
}
}
@ -801,7 +795,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
public void stopStreamReceiving() {
_log.debug("stopStreamReceiving() invoked");
_log.debug("stopStreamReceiving() invoked", new Exception("stopped"));
if (streamSession == null) {
_log.error("BUG! Got stream receiving stop, but session is null!");
@ -811,7 +805,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
try {
closeClientSocket();
} catch (IOException e) {
_log.error("Error closing socket: " + e.getMessage());
if (_log.shouldLog(Log.WARN))
_log.warn("Error closing socket", e);
}
}
}

View File

@ -0,0 +1,18 @@
package net.i2p.sam.client;
import java.util.Properties;
/**
* Basic noop client event listener
*/
public class SAMClientEventListenerImpl implements SAMReader.SAMClientEventListener {
public void destReplyReceived(String publicKey, String privateKey) {}
public void helloReplyReceived(boolean ok) {}
public void namingReplyReceived(String name, String result, String value, String message) {}
public void sessionStatusReceived(String result, String destination, String message) {}
public void streamClosedReceived(String result, int id, String message) {}
public void streamConnectedReceived(String remoteDestination, int id) {}
public void streamDataReceived(int id, byte[] data, int offset, int length) {}
public void streamStatusReceived(String result, int id, String message) {}
public void unknownMessageReceived(String major, String minor, Properties params) {}
}

View File

@ -0,0 +1,127 @@
package net.i2p.sam.client;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* Simple helper implementation of a the SAMClientEventListener
*
*/
public class SAMEventHandler extends SAMClientEventListenerImpl {
private I2PAppContext _context;
private Log _log;
private Boolean _helloOk;
private Object _helloLock = new Object();
private Boolean _sessionCreateOk;
private Object _sessionCreateLock = new Object();
private Object _namingReplyLock = new Object();
private Map _namingReplies = new HashMap();
public SAMEventHandler(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(getClass());
}
public void helloReplyReceived(boolean ok) {
synchronized (_helloLock) {
if (ok)
_helloOk = Boolean.TRUE;
else
_helloOk = Boolean.FALSE;
_helloLock.notifyAll();
}
}
public void sessionStatusReceived(String result, String destination, String msg) {
synchronized (_sessionCreateLock) {
if (SAMReader.SAMClientEventListener.SESSION_STATUS_OK.equals(result))
_sessionCreateOk = Boolean.TRUE;
else
_sessionCreateOk = Boolean.FALSE;
_sessionCreateLock.notifyAll();
}
}
public void namingReplyReceived(String name, String result, String value, String msg) {
synchronized (_namingReplyLock) {
if (SAMReader.SAMClientEventListener.NAMING_REPLY_OK.equals(result))
_namingReplies.put(name, value);
else
_namingReplies.put(name, result);
_namingReplyLock.notifyAll();
}
}
public void unknownMessageReceived(String major, String minor, Properties params) {
_log.error("wrt, [" + major + "] [" + minor + "] [" + params + "]");
}
//
// blocking lookup calls below
//
/**
* Wait for the connection to be established, returning true if everything
* went ok
*/
public boolean waitForHelloReply() {
while (true) {
try {
synchronized (_helloLock) {
if (_helloOk == null)
_helloLock.wait();
else
return _helloOk.booleanValue();
}
} catch (InterruptedException ie) {}
}
}
/**
* Wait for the session to be created, returning true if everything went ok
*
*/
public boolean waitForSessionCreateReply() {
while (true) {
try {
synchronized (_sessionCreateLock) {
if (_sessionCreateOk == null)
_sessionCreateLock.wait();
else
return _sessionCreateOk.booleanValue();
}
} catch (InterruptedException ie) {}
}
}
/**
* Return the destination found matching the name, or null if the key was
* not able to be retrieved.
*
* @param name name to be looked for, or "ME"
*/
public String waitForNamingReply(String name) {
while (true) {
try {
synchronized (_namingReplyLock) {
String val = (String)_namingReplies.remove(name);
if (val == null) {
_namingReplyLock.wait();
} else {
if (SAMReader.SAMClientEventListener.NAMING_REPLY_INVALID_KEY.equals(val))
return null;
else if (SAMReader.SAMClientEventListener.NAMING_REPLY_KEY_NOT_FOUND.equals(val))
return null;
else
return val;
}
}
} catch (InterruptedException ie) {}
}
}
}

View File

@ -0,0 +1,253 @@
package net.i2p.sam.client;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
/**
* Read from a socket, producing events for any SAM message read
*
*/
public class SAMReader {
private Log _log;
private InputStream _inRaw;
private SAMClientEventListener _listener;
private boolean _live;
public SAMReader(I2PAppContext context, InputStream samIn, SAMClientEventListener listener) {
_log = context.logManager().getLog(SAMReader.class);
_inRaw = samIn;
_listener = listener;
}
public void startReading() {
_live = true;
I2PThread t = new I2PThread(new Runner(), "SAM reader");
t.start();
}
public void stopReading() { _live = false; }
/**
* Async event notification interface for SAM clients
*
*/
public interface SAMClientEventListener {
public static final String SESSION_STATUS_OK = "OK";
public static final String SESSION_STATUS_DUPLICATE_DEST = "DUPLICATE_DEST";
public static final String SESSION_STATUS_I2P_ERROR = "I2P_ERROR";
public static final String SESSION_STATUS_INVALID_KEY = "INVALID_KEY";
public static final String STREAM_STATUS_OK = "OK";
public static final String STREAM_STATUS_CANT_REACH_PEER = "CANT_REACH_PEER";
public static final String STREAM_STATUS_I2P_ERROR = "I2P_ERROR";
public static final String STREAM_STATUS_INVALID_KEY = "INVALID_KEY";
public static final String STREAM_STATUS_TIMEOUT = "TIMEOUT";
public static final String STREAM_CLOSED_OK = "OK";
public static final String STREAM_CLOSED_CANT_REACH_PEER = "CANT_REACH_PEER";
public static final String STREAM_CLOSED_I2P_ERROR = "I2P_ERROR";
public static final String STREAM_CLOSED_PEER_NOT_FOUND = "PEER_NOT_FOUND";
public static final String STREAM_CLOSED_TIMEOUT = "CLOSED";
public static final String NAMING_REPLY_OK = "OK";
public static final String NAMING_REPLY_INVALID_KEY = "INVALID_KEY";
public static final String NAMING_REPLY_KEY_NOT_FOUND = "KEY_NOT_FOUND";
public void helloReplyReceived(boolean ok);
public void sessionStatusReceived(String result, String destination, String message);
public void streamStatusReceived(String result, int id, String message);
public void streamConnectedReceived(String remoteDestination, int id);
public void streamClosedReceived(String result, int id, String message);
public void streamDataReceived(int id, byte data[], int offset, int length);
public void namingReplyReceived(String name, String result, String value, String message);
public void destReplyReceived(String publicKey, String privateKey);
public void unknownMessageReceived(String major, String minor, Properties params);
}
private class Runner implements Runnable {
public void run() {
Properties params = new Properties();
ByteArrayOutputStream baos = new ByteArrayOutputStream(80);
while (_live) {
try {
int c = -1;
while ((c = _inRaw.read()) != -1) {
if (c == '\n') {
break;
}
baos.write(c);
}
if (c == -1) {
_log.error("Error reading from the SAM bridge");
return;
}
} catch (IOException ioe) {
_log.error("Error reading from SAM", ioe);
}
String line = new String(baos.toByteArray());
baos.reset();
if (line == null) {
_log.info("No more data from the SAM bridge");
break;
}
_log.debug("Line read from the bridge: " + line);
StringTokenizer tok = new StringTokenizer(line);
if (tok.countTokens() < 2) {
_log.error("Invalid SAM line: [" + line + "]");
_live = false;
return;
}
String major = tok.nextToken();
String minor = tok.nextToken();
params.clear();
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int eq = pair.indexOf('=');
if ( (eq > 0) && (eq < pair.length() - 1) ) {
String name = pair.substring(0, eq);
String val = pair.substring(eq+1);
while ( (val.charAt(0) == '\"') && (val.length() > 0) )
val = val.substring(1);
while ( (val.length() > 0) && (val.charAt(val.length()-1) == '\"') )
val = val.substring(0, val.length()-1);
params.setProperty(name, val);
}
}
processEvent(major, minor, params);
}
}
}
/**
* Big ugly method parsing everything. If I cared, I'd factor this out into
* a dozen tiny methods.
*
*/
private void processEvent(String major, String minor, Properties params) {
if ("HELLO".equals(major)) {
if ("REPLY".equals(minor)) {
String result = params.getProperty("RESULT");
if ("OK".equals(result))
_listener.helloReplyReceived(true);
else
_listener.helloReplyReceived(false);
} else {
_listener.unknownMessageReceived(major, minor, params);
}
} else if ("SESSION".equals(major)) {
if ("STATUS".equals(minor)) {
String result = params.getProperty("RESULT");
String dest = params.getProperty("DESTINATION");
String msg = params.getProperty("MESSAGE");
_listener.sessionStatusReceived(result, dest, msg);
} else {
_listener.unknownMessageReceived(major, minor, params);
}
} else if ("STREAM".equals(major)) {
if ("STATUS".equals(minor)) {
String result = params.getProperty("RESULT");
String id = params.getProperty("ID");
String msg = params.getProperty("MESSAGE");
if (id != null) {
try {
_listener.streamStatusReceived(result, Integer.parseInt(id), msg);
} catch (NumberFormatException nfe) {
_listener.unknownMessageReceived(major, minor, params);
}
} else {
_listener.unknownMessageReceived(major, minor, params);
}
} else if ("CONNECTED".equals(minor)) {
String dest = params.getProperty("DESTINATION");
String id = params.getProperty("ID");
if (id != null) {
try {
_listener.streamConnectedReceived(dest, Integer.parseInt(id));
} catch (NumberFormatException nfe) {
_listener.unknownMessageReceived(major, minor, params);
}
} else {
_listener.unknownMessageReceived(major, minor, params);
}
} else if ("CLOSED".equals(minor)) {
String result = params.getProperty("RESULT");
String id = params.getProperty("ID");
String msg = params.getProperty("MESSAGE");
if (id != null) {
try {
_listener.streamClosedReceived(result, Integer.parseInt(id), msg);
} catch (NumberFormatException nfe) {
_listener.unknownMessageReceived(major, minor, params);
}
} else {
_listener.unknownMessageReceived(major, minor, params);
}
} else if ("RECEIVED".equals(minor)) {
String id = params.getProperty("ID");
String size = params.getProperty("SIZE");
if (id != null) {
try {
int idVal = Integer.parseInt(id);
int sizeVal = Integer.parseInt(size);
byte data[] = new byte[sizeVal];
int read = DataHelper.read(_inRaw, data);
if (read != sizeVal) {
_listener.unknownMessageReceived(major, minor, params);
} else {
_listener.streamDataReceived(idVal, data, 0, sizeVal);
}
} catch (NumberFormatException nfe) {
_listener.unknownMessageReceived(major, minor, params);
} catch (IOException ioe) {
_live = false;
_listener.unknownMessageReceived(major, minor, params);
}
} else {
_listener.unknownMessageReceived(major, minor, params);
}
} else {
_listener.unknownMessageReceived(major, minor, params);
}
} else if ("NAMING".equals(major)) {
if ("REPLY".equals(minor)) {
String name = params.getProperty("NAME");
String result = params.getProperty("RESULT");
String value = params.getProperty("VALUE");
String msg = params.getProperty("MESSAGE");
_listener.namingReplyReceived(name, result, value, msg);
} else {
_listener.unknownMessageReceived(major, minor, params);
}
} else if ("DEST".equals(major)) {
if ("REPLY".equals(minor)) {
String pub = params.getProperty("PUB");
String priv = params.getProperty("PRIV");
_listener.destReplyReceived(pub, priv);
} else {
_listener.unknownMessageReceived(major, minor, params);
}
} else {
_listener.unknownMessageReceived(major, minor, params);
}
}
}

View File

@ -0,0 +1,262 @@
package net.i2p.sam.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
import java.util.Collections;
import java.util.HashMap;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
import net.i2p.sam.client.SAMEventHandler;
import net.i2p.sam.client.SAMClientEventListenerImpl;
import net.i2p.sam.client.SAMReader;
/**
* Send a file to a peer
*
* Usage: SAMStreamSend samHost samPort peerDestFile dataFile
*
*/
public class SAMStreamSend {
private I2PAppContext _context;
private Log _log;
private String _samHost;
private String _samPort;
private String _destFile;
private String _dataFile;
private String _conOptions;
private Socket _samSocket;
private OutputStream _samOut;
private InputStream _samIn;
private SAMReader _reader;
private boolean _dead;
private SAMEventHandler _eventHandler;
/** Connection id (Integer) to peer (Flooder) */
private Map _remotePeers;
public static void main(String args[]) {
if (args.length < 4) {
System.err.println("Usage: SAMStreamSend samHost samPort peerDestFile dataFile");
return;
}
I2PAppContext ctx = new I2PAppContext();
String files[] = new String[args.length - 3];
SAMStreamSend sender = new SAMStreamSend(ctx, args[0], args[1], args[2], args[3]);
sender.startup();
}
public SAMStreamSend(I2PAppContext ctx, String samHost, String samPort, String destFile, String dataFile) {
_context = ctx;
_log = ctx.logManager().getLog(SAMStreamSend.class);
_dead = false;
_samHost = samHost;
_samPort = samPort;
_destFile = destFile;
_dataFile = dataFile;;
_conOptions = "";
_eventHandler = new SendEventHandler(_context);
_remotePeers = new HashMap();
}
public void startup() {
_log.debug("Starting up");
boolean ok = connect();
_log.debug("Connected: " + ok);
if (ok) {
_reader = new SAMReader(_context, _samIn, _eventHandler);
_reader.startReading();
_log.debug("Reader created");
String ourDest = handshake();
_log.debug("Handshake complete. we are " + ourDest);
if (ourDest != null) {
send();
}
}
}
private class SendEventHandler extends SAMEventHandler {
public SendEventHandler(I2PAppContext ctx) { super(ctx); }
public void streamClosedReceived(String result, int id, String message) {
Sender sender = null;
synchronized (_remotePeers) {
sender = (Sender)_remotePeers.remove(new Integer(id));
}
if (sender != null) {
sender.closed();
_log.debug("Connection " + sender.getConnectionId() + " closed to " + sender.getDestination());
} else {
_log.error("wtf, not connected to " + id + " but we were just closed?");
}
}
}
private boolean connect() {
try {
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
_samOut = _samSocket.getOutputStream();
_samIn = _samSocket.getInputStream();
return true;
} catch (Exception e) {
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
return false;
}
}
private String handshake() {
synchronized (_samOut) {
try {
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
_samOut.flush();
_log.debug("Hello sent");
boolean ok = _eventHandler.waitForHelloReply();
_log.debug("Hello reply found: " + ok);
if (!ok)
throw new IOException("wtf, hello failed?");
String req = "SESSION CREATE STYLE=STREAM DESTINATION=TRANSIENT " + _conOptions + "\n";
_samOut.write(req.getBytes());
_samOut.flush();
_log.debug("Session create sent");
ok = _eventHandler.waitForSessionCreateReply();
_log.debug("Session create reply found: " + ok);
req = "NAMING LOOKUP NAME=ME\n";
_samOut.write(req.getBytes());
_samOut.flush();
_log.debug("Naming lookup sent");
String destination = _eventHandler.waitForNamingReply("ME");
_log.debug("Naming lookup reply found: " + destination);
if (destination == null) {
_log.error("No naming lookup reply found!");
return null;
} else {
_log.info("We are " + destination);
}
return destination;
} catch (Exception e) {
_log.error("Error handshaking", e);
return null;
}
}
}
private void send() {
Sender sender = new Sender();
boolean ok = sender.openConnection();
if (ok) {
I2PThread t = new I2PThread(sender, "Sender");
t.start();
}
}
private class Sender implements Runnable {
private int _connectionId;
private String _remoteDestination;
private InputStream _in;
private boolean _closed;
private long _started;
private long _totalSent;
public Sender() {
_closed = false;
}
public boolean openConnection() {
try {
FileInputStream fin = new FileInputStream(_destFile);
byte dest[] = new byte[1024];
int read = DataHelper.read(fin, dest);
_remoteDestination = new String(dest, 0, read);
synchronized (_remotePeers) {
_connectionId = _remotePeers.size() + 1;
_remotePeers.put(new Integer(_connectionId), Sender.this);
}
_context.statManager().createRateStat("send." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("send." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
_context.statManager().createRateStat("send." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
byte msg[] = ("STREAM CONNECT ID=" + _connectionId + " DESTINATION=" + _remoteDestination + "\n").getBytes();
synchronized (_samOut) {
_samOut.write(msg);
_samOut.flush();
}
_in = new FileInputStream(_dataFile);
return true;
} catch (IOException ioe) {
_log.error("Unable to connect", ioe);
return false;
}
}
public int getConnectionId() { return _connectionId; }
public String getDestination() { return _remoteDestination; }
public void closed() {
if (_closed) return;
_closed = true;
long lifetime = _context.clock().now() - _started;
_context.statManager().addRateData("send." + _connectionId + ".lifetime", lifetime, lifetime);
try { _in.close(); } catch (IOException ioe) {}
}
public void run() {
_started = _context.clock().now();
_context.statManager().addRateData("send." + _connectionId + ".started", 1, 0);
byte data[] = new byte[1024];
long value = 0;
long lastSend = _context.clock().now();
while (!_closed) {
try {
int read = _in.read(data);
long now = _context.clock().now();
if (read == -1) {
_log.debug("EOF from the data for " + _connectionId + " after " + (now-lastSend));
break;
} else if (read > 0) {
_log.debug("Sending " + read + " on " + _connectionId + " after " + (now-lastSend));
lastSend = now;
byte msg[] = ("STREAM SEND ID=" + _connectionId + " SIZE=" + read + "\n").getBytes();
synchronized (_samOut) {
_samOut.write(msg);
_samOut.write(data, 0, read);
_samOut.flush();
}
_totalSent += read;
_context.statManager().addRateData("send." + _connectionId + ".totalSent", _totalSent, 0);
}
} catch (IOException ioe) {
_log.error("Error sending", ioe);
}
}
byte msg[] = ("STREAM CLOSE ID=" + _connectionId + "\n").getBytes();
try {
synchronized (_samOut) {
_samOut.write(msg);
_samOut.flush();
}
} catch (IOException ioe) {
_log.error("Error closing", ioe);
}
closed();
}
}
}

View File

@ -0,0 +1,247 @@
package net.i2p.sam.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.FileOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
import java.util.Collections;
import java.util.HashMap;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
import net.i2p.sam.client.SAMEventHandler;
import net.i2p.sam.client.SAMClientEventListenerImpl;
import net.i2p.sam.client.SAMReader;
/**
* Sit around on a SAM destination, receiving lots of data and
* writing it to disk
*
* Usage: SAMStreamSink samHost samPort myKeyFile sinkDir
*
*/
public class SAMStreamSink {
private I2PAppContext _context;
private Log _log;
private String _samHost;
private String _samPort;
private String _destFile;
private String _sinkDir;
private String _conOptions;
private Socket _samSocket;
private OutputStream _samOut;
private InputStream _samIn;
private SAMReader _reader;
private boolean _dead;
private SAMEventHandler _eventHandler;
/** Connection id (Integer) to peer (Flooder) */
private Map _remotePeers;
public static void main(String args[]) {
if (args.length < 4) {
System.err.println("Usage: SAMStreamSink samHost samPort myDestFile sinkDir");
return;
}
I2PAppContext ctx = new I2PAppContext();
SAMStreamSink sink = new SAMStreamSink(ctx, args[0], args[1], args[2], args[3]);
sink.startup();
}
public SAMStreamSink(I2PAppContext ctx, String samHost, String samPort, String destFile, String sinkDir) {
_context = ctx;
_log = ctx.logManager().getLog(SAMStreamSink.class);
_dead = false;
_samHost = samHost;
_samPort = samPort;
_destFile = destFile;
_sinkDir = sinkDir;
_conOptions = "";
_eventHandler = new SinkEventHandler(_context);
_remotePeers = new HashMap();
}
public void startup() {
_log.debug("Starting up");
boolean ok = connect();
_log.debug("Connected: " + ok);
if (ok) {
_reader = new SAMReader(_context, _samIn, _eventHandler);
_reader.startReading();
_log.debug("Reader created");
String ourDest = handshake();
_log.debug("Handshake complete. we are " + ourDest);
if (ourDest != null) {
boolean written = writeDest(ourDest);
_log.debug("Dest written");
}
}
}
private class SinkEventHandler extends SAMEventHandler {
public SinkEventHandler(I2PAppContext ctx) { super(ctx); }
public void streamClosedReceived(String result, int id, String message) {
Sink sink = null;
synchronized (_remotePeers) {
sink = (Sink)_remotePeers.remove(new Integer(id));
}
if (sink != null) {
sink.closed();
_log.debug("Connection " + sink.getConnectionId() + " closed to " + sink.getDestination());
} else {
_log.error("wtf, not connected to " + id + " but we were just closed?");
}
}
public void streamDataReceived(int id, byte data[], int offset, int length) {
Sink sink = null;
synchronized (_remotePeers) {
sink = (Sink)_remotePeers.get(new Integer(id));
}
if (sink != null) {
sink.received(data, offset, length);
} else {
_log.error("wtf, not connected to " + id + " but we received " + length + "?");
}
}
public void streamConnectedReceived(String dest, int id) {
_log.debug("Connection " + id + " received from " + dest);
try {
Sink sink = new Sink(id, dest);
synchronized (_remotePeers) {
_remotePeers.put(new Integer(id), sink);
}
} catch (IOException ioe) {
_log.error("Error creating a new sink", ioe);
}
}
}
private boolean connect() {
try {
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
_samOut = _samSocket.getOutputStream();
_samIn = _samSocket.getInputStream();
return true;
} catch (Exception e) {
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
return false;
}
}
private String handshake() {
synchronized (_samOut) {
try {
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
_samOut.flush();
_log.debug("Hello sent");
boolean ok = _eventHandler.waitForHelloReply();
_log.debug("Hello reply found: " + ok);
if (!ok)
throw new IOException("wtf, hello failed?");
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + _destFile + " " + _conOptions + "\n";
_samOut.write(req.getBytes());
_samOut.flush();
_log.debug("Session create sent");
ok = _eventHandler.waitForSessionCreateReply();
_log.debug("Session create reply found: " + ok);
req = "NAMING LOOKUP NAME=ME\n";
_samOut.write(req.getBytes());
_samOut.flush();
_log.debug("Naming lookup sent");
String destination = _eventHandler.waitForNamingReply("ME");
_log.debug("Naming lookup reply found: " + destination);
if (destination == null) {
_log.error("No naming lookup reply found!");
return null;
} else {
_log.info(_destFile + " is located at " + destination);
}
return destination;
} catch (Exception e) {
_log.error("Error handshaking", e);
return null;
}
}
}
private boolean writeDest(String dest) {
try {
FileOutputStream fos = new FileOutputStream(_destFile);
fos.write(dest.getBytes());
fos.close();
return true;
} catch (Exception e) {
_log.error("Error writing to " + _destFile, e);
return false;
}
}
private class Sink {
private int _connectionId;
private String _remoteDestination;
private boolean _closed;
private long _started;
private long _totalReceived;
private long _lastReceivedOn;
private OutputStream _out;
public Sink(int conId, String remDest) throws IOException {
_connectionId = conId;
_remoteDestination = remDest;
_closed = false;
_lastReceivedOn = _context.clock().now();
_context.statManager().createRateStat("sink." + conId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("sink." + conId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
_context.statManager().createRateStat("sink." + conId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
File sinkDir = new File(_sinkDir);
if (!sinkDir.exists())
sinkDir.mkdirs();
File out = File.createTempFile("sink", ".dat", sinkDir);
_out = new FileOutputStream(out);
}
public int getConnectionId() { return _connectionId; }
public String getDestination() { return _remoteDestination; }
public void closed() {
if (_closed) return;
_closed = true;
long lifetime = _context.clock().now() - _started;
_context.statManager().addRateData("sink." + _connectionId + ".lifetime", lifetime, lifetime);
try {
_out.close();
} catch (IOException ioe) {
_log.error("Error closing", ioe);
}
}
public void received(byte data[], int offset, int len) {
if (_closed) return;
_totalReceived += len;
try {
_out.write(data, offset, len);
} catch (IOException ioe) {
_log.error("Error writing received data");
closed();
return;
}
_log.debug("Received " + len + " on " + _connectionId + " after " + (_context.clock().now()-_lastReceivedOn)
+ "ms with " + _remoteDestination.substring(0,6));
_lastReceivedOn = _context.clock().now();
}
}
}

View File

@ -150,7 +150,7 @@ public class TestStreamTransfer {
_log.error("Incorrect size read - expected " + payloadSize + " got " + read);
return;
}
_log.info("Received from the stream " + id + ": [" + new String(payload) + "]");
_log.info("\n== Received from the stream " + id + ": [" + new String(payload) + "]");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
/*
// now echo it back
@ -217,7 +217,12 @@ public class TestStreamTransfer {
}
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
_log.info("Sending data");
_log.info("\n** Sending BlahBlah!!");
out.write(req.getBytes());
out.flush();
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
req = "STREAM SEND ID=42 SIZE=10\nFooBarBaz!";
_log.info("\n** Sending FooBarBaz!");
out.write(req.getBytes());
out.flush();
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}

View File

@ -0,0 +1,312 @@
package net.i2p.sam;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
import java.util.Collections;
import java.util.HashMap;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
import net.i2p.sam.client.SAMEventHandler;
import net.i2p.sam.client.SAMClientEventListenerImpl;
import net.i2p.sam.client.SAMReader;
/**
* Sit around on a SAM destination, receiving lots of data and sending lots of
* data to whomever talks to us.
*
* Usage: TestSwarm samHost samPort myKeyFile [peerDestFile ]*
*
*/
public class TestSwarm {
private I2PAppContext _context;
private Log _log;
private String _samHost;
private String _samPort;
private String _destFile;
private String _peerDestFiles[];
private String _conOptions;
private Socket _samSocket;
private OutputStream _samOut;
private InputStream _samIn;
private SAMReader _reader;
private boolean _dead;
private SAMEventHandler _eventHandler;
/** Connection id (Integer) to peer (Flooder) */
private Map _remotePeers;
public static void main(String args[]) {
if (args.length < 3) {
System.err.println("Usage: TestSwarm samHost samPort myDestFile [peerDestFile ]*");
return;
}
I2PAppContext ctx = new I2PAppContext();
String files[] = new String[args.length - 3];
System.arraycopy(args, 3, files, 0, files.length);
TestSwarm swarm = new TestSwarm(ctx, args[0], args[1], args[2], files);
swarm.startup();
}
public TestSwarm(I2PAppContext ctx, String samHost, String samPort, String destFile, String peerDestFiles[]) {
_context = ctx;
_log = ctx.logManager().getLog(TestSwarm.class);
_dead = false;
_samHost = samHost;
_samPort = samPort;
_destFile = destFile;
_peerDestFiles = peerDestFiles;
_conOptions = "";
_eventHandler = new SwarmEventHandler(_context);
_remotePeers = new HashMap();
}
public void startup() {
_log.debug("Starting up");
boolean ok = connect();
_log.debug("Connected: " + ok);
if (ok) {
_reader = new SAMReader(_context, _samIn, _eventHandler);
_reader.startReading();
_log.debug("Reader created");
String ourDest = handshake();
_log.debug("Handshake complete. we are " + ourDest);
if (ourDest != null) {
boolean written = writeDest(ourDest);
_log.debug("Dest written");
if (written) {
connectWithPeers();
_log.debug("connected with peers");
}
}
}
}
private class SwarmEventHandler extends SAMEventHandler {
public SwarmEventHandler(I2PAppContext ctx) { super(ctx); }
public void streamClosedReceived(String result, int id, String message) {
Flooder flooder = null;
synchronized (_remotePeers) {
flooder = (Flooder)_remotePeers.remove(new Integer(id));
}
if (flooder != null) {
flooder.closed();
_log.debug("Connection " + flooder.getConnectionId() + " closed to " + flooder.getDestination());
} else {
_log.error("wtf, not connected to " + id + " but we were just closed?");
}
}
public void streamDataReceived(int id, byte data[], int offset, int length) {
Flooder flooder = null;
synchronized (_remotePeers) {
flooder = (Flooder)_remotePeers.get(new Integer(id));
}
long value = DataHelper.fromLong(data, 0, 4);
if (flooder != null) {
flooder.received(length, value);
} else {
_log.error("wtf, not connected to " + id + " but we received " + value + "?");
}
}
public void streamConnectedReceived(String dest, int id) {
_log.debug("Connection " + id + " received from " + dest);
Flooder flooder = new Flooder(id, dest);
synchronized (_remotePeers) {
_remotePeers.put(new Integer(id), flooder);
}
I2PThread t = new I2PThread(flooder, "Flood " + id);
t.start();
}
}
private boolean connect() {
try {
_samSocket = new Socket(_samHost, Integer.parseInt(_samPort));
_samOut = _samSocket.getOutputStream();
_samIn = _samSocket.getInputStream();
return true;
} catch (Exception e) {
_log.error("Unable to connect to SAM at " + _samHost + ":" + _samPort, e);
return false;
}
}
private String handshake() {
synchronized (_samOut) {
try {
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
_samOut.flush();
_log.debug("Hello sent");
boolean ok = _eventHandler.waitForHelloReply();
_log.debug("Hello reply found: " + ok);
if (!ok)
throw new IOException("wtf, hello failed?");
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + _destFile + " " + _conOptions + "\n";
_samOut.write(req.getBytes());
_samOut.flush();
_log.debug("Session create sent");
ok = _eventHandler.waitForSessionCreateReply();
_log.debug("Session create reply found: " + ok);
req = "NAMING LOOKUP NAME=ME\n";
_samOut.write(req.getBytes());
_samOut.flush();
_log.debug("Naming lookup sent");
String destination = _eventHandler.waitForNamingReply("ME");
_log.debug("Naming lookup reply found: " + destination);
if (destination == null) {
_log.error("No naming lookup reply found!");
return null;
} else {
_log.info(_destFile + " is located at " + destination);
}
return destination;
} catch (Exception e) {
_log.error("Error handshaking", e);
return null;
}
}
}
private boolean writeDest(String dest) {
try {
FileOutputStream fos = new FileOutputStream(_destFile);
fos.write(dest.getBytes());
fos.close();
return true;
} catch (Exception e) {
_log.error("Error writing to " + _destFile, e);
return false;
}
}
private void connectWithPeers() {
if (_peerDestFiles != null) {
for (int i = 0; i < _peerDestFiles.length; i++) {
try {
FileInputStream fin = new FileInputStream(_peerDestFiles[i]);
byte dest[] = new byte[1024];
int read = DataHelper.read(fin, dest);
String remDest = new String(dest, 0, read);
int con = 0;
Flooder flooder = null;
synchronized (_remotePeers) {
con = _remotePeers.size() + 1;
flooder = new Flooder(con, remDest);
_remotePeers.put(new Integer(con), flooder);
}
byte msg[] = ("STREAM CONNECT ID=" + con + " DESTINATION=" + remDest + "\n").getBytes();
synchronized (_samOut) {
_samOut.write(msg);
_samOut.flush();
}
I2PThread flood = new I2PThread(flooder, "Flood " + con);
flood.start();
_log.debug("Starting flooder with peer from " + _peerDestFiles[i] + ": " + con);
} catch (IOException ioe) {
_log.error("Unable to read the peer from " + _peerDestFiles[i]);
}
}
}
}
private class Flooder implements Runnable {
private int _connectionId;
private String _remoteDestination;
private boolean _closed;
private long _started;
private long _totalSent;
private long _totalReceived;
private long _lastReceived;
private long _lastReceivedOn;
private boolean _outOfSync;
public Flooder(int conId, String remDest) {
_connectionId = conId;
_remoteDestination = remDest;
_closed = false;
_outOfSync = false;
_lastReceived = -1;
_lastReceivedOn = _context.clock().now();
_context.statManager().createRateStat("swarm." + conId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("swarm." + conId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("swarm." + conId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
_context.statManager().createRateStat("swarm." + conId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
}
public int getConnectionId() { return _connectionId; }
public String getDestination() { return _remoteDestination; }
public void closed() {
_closed = true;
long lifetime = _context.clock().now() - _started;
_context.statManager().addRateData("swarm." + _connectionId + ".lifetime", lifetime, lifetime);
}
public void run() {
_started = _context.clock().now();
_context.statManager().addRateData("swarm." + _connectionId + ".started", 1, 0);
byte data[] = new byte[32*1024];
long value = 0;
long lastSend = _context.clock().now();
while (!_closed) {
byte msg[] = ("STREAM SEND ID=" + _connectionId + " SIZE=" + data.length + "\n").getBytes();
DataHelper.toLong(data, 0, 4, value);
try {
synchronized (_samOut) {
_samOut.write(msg);
_samOut.write(data);
_samOut.flush();
}
} catch (IOException ioe) {
_log.error("Error talking to SAM", ioe);
return;
}
_totalSent += data.length;
_context.statManager().addRateData("swarm." + _connectionId + ".totalSent", _totalSent, 0);
value++;
try { Thread.sleep(20); } catch (InterruptedException ie) {}
long now = _context.clock().now();
_log.debug("Sending " + value + " on " + _connectionId + " after " + (now-lastSend));
lastSend = now;
}
}
public void received(int len, long value) {
_totalReceived += len;
if ( (!_outOfSync) && (len % 32*1024 != 0) ) {
_outOfSync = true;
if (_log.shouldLog(Log.ERROR))
_log.error("Out of sync (len=" + len + " after " + (_totalReceived-len) + ")");
}
_context.statManager().addRateData("swarm." + getConnectionId() + ".totalReceived", _totalReceived, 0);
if (value != _lastReceived + 1) {
if (!_outOfSync)
_log.error("Received " + value + " when expecting " + (_lastReceived+1) + " on "
+ _connectionId + " with " + _remoteDestination.substring(0,6));
else
_log.debug("(out of sync) Received " + value + " when expecting " + (_lastReceived+1) + " on "
+ _connectionId + " with " + _remoteDestination.substring(0,6));
} else {
_log.debug("Received " + value + " on " + _connectionId + " after " + (_context.clock().now()-_lastReceivedOn)
+ "ms with " + _remoteDestination.substring(0,6));
}
_lastReceived = value;
_lastReceivedOn = _context.clock().now();
}
}
}

View File

@ -32,7 +32,9 @@ public class Connection {
private long _lastSendTime;
private long _lastSendId;
private boolean _resetReceived;
private boolean _resetSent;
private boolean _connected;
private boolean _hardDisconnected;
private MessageInputStream _inputStream;
private MessageOutputStream _outputStream;
private SchedulerChooser _chooser;
@ -70,7 +72,7 @@ public class Connection {
private long _lifetimeDupMessageReceived;
public static final long MAX_RESEND_DELAY = 60*1000;
public static final long MIN_RESEND_DELAY = 20*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;
@ -112,6 +114,8 @@ public class Connection {
_connectLock = new Object();
_activeResends = 0;
_context.statManager().createRateStat("stream.con.windowSizeAtCongestion", "How large was our send window when we send a dup?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.chokeSizeBegin", "How many messages were outstanding when we started to choke?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.chokeSizeEnd", "How many messages were outstanding when we stopped being choked?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
}
public long getNextOutboundPacketNum() {
@ -135,9 +139,14 @@ public class Connection {
boolean packetSendChoke(long timeoutMs) {
if (false) return true;
long writeExpire = timeoutMs;
long start = _context.clock().now();
boolean started = false;
while (true) {
long timeLeft = writeExpire - _context.clock().now();
synchronized (_outboundPackets) {
if (!started)
_context.statManager().addRateData("stream.chokeSizeBegin", _outboundPackets.size(), timeoutMs);
started = true;
if (_outboundPackets.size() >= _options.getWindowSize()) {
if (writeExpire > 0) {
if (timeLeft <= 0) {
@ -154,6 +163,7 @@ public class Connection {
try { _outboundPackets.wait(); } catch (InterruptedException ie) {}
}
} else {
_context.statManager().addRateData("stream.chokeSizeEnd", _outboundPackets.size(), _context.clock().now() - start);
return true;
}
}
@ -163,6 +173,23 @@ public class Connection {
void ackImmediately() {
_receiver.send(null, 0, 0);
}
/**
* got a packet we shouldn't have, send 'em a reset
*
*/
void sendReset() {
_resetSent = true;
if ( (_remotePeer == null) || (_sendStreamId == null) ) return;
PacketLocal reply = new PacketLocal(_context, _remotePeer);
reply.setFlag(Packet.FLAG_RESET);
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
reply.setSendStreamId(_sendStreamId);
reply.setReceiveStreamId(_receiveStreamId);
reply.setOptionalFrom(_connectionManager.getSession().getMyDestination());
// this just sends the packet - no retries or whatnot
_outboundQueue.enqueue(reply);
}
/**
* Flush any data that we can
@ -296,8 +323,8 @@ public class Connection {
_ackedPackets++;
if (p.getNumSends() > 1) {
_activeResends--;
if (_log.shouldLog(Log.WARN))
_log.warn("Active resend of " + p + " successful, # active left: " + _activeResends);
if (_log.shouldLog(Log.INFO))
_log.info("Active resend of " + p + " successful, # active left: " + _activeResends);
}
}
}
@ -313,50 +340,73 @@ public class Connection {
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 > 1000) && (_log.shouldLog(Log.WARN)) ) {
_log.warn("More than 1000 events (" + _occurredEventCount + ") in a second on "
+ toString() + ": scheduler = " + sched);
}
_occurredEventCount = 0;
}
long before = System.currentTimeMillis();
sched.eventOccurred(this);
long elapsed = System.currentTimeMillis() - before;
if ( (elapsed > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("Took " + elapsed + "ms to pump through " + sched);
}
void resetReceived() {
_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; }
public boolean getIsConnected() { return _connected; }
public boolean getHardDisconnected() { return _hardDisconnected; }
public boolean getResetSent() { return _resetSent; }
void disconnect(boolean cleanDisconnect) {
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) {
_hardDisconnected = true;
if (_log.shouldLog(Log.WARN))
_log.warn("Hard disconnecting and sending a reset on " + toString(), new Exception("cause"));
sendReset();
}
if (cleanDisconnect && _connected) {
// send close packets and schedule stuff...
_outputStream.closeInternal();
_inputStream.close();
} else {
doClose();
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());
if (_connected)
doClose();
killOutstandingPackets();
}
if (removeFromConMgr) {
if (!_disconnectScheduled) {
@ -364,6 +414,7 @@ public class Connection {
SimpleTimer.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
}
}
_connected = false;
}
void disconnectComplete() {
@ -371,17 +422,12 @@ public class Connection {
if (_socket != null)
_socket.destroy();
_socket = null;
_inputStream = null;
if (_outputStream != null)
_outputStream.destroy();
_outputStream = null;
_outboundQueue = null;
_handler = null;
if (_receiver != null)
_receiver.destroy();
_receiver = null;
if (_activityTimer != null)
SimpleTimer.getInstance().addEvent(_activityTimer, 1);
SimpleTimer.getInstance().removeEvent(_activityTimer);
_activityTimer = null;
if (!_disconnectScheduled) {
@ -393,6 +439,10 @@ public class Connection {
_connectionManager.removeConnection(this);
}
killOutstandingPackets();
}
private void killOutstandingPackets() {
boolean tagsCancelled = false;
synchronized (_outboundPackets) {
for (Iterator iter = _outboundPackets.values().iterator(); iter.hasNext(); ) {
@ -406,7 +456,6 @@ public class Connection {
}
if (tagsCancelled)
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
}
private class DisconnectEvent implements SimpleTimer.TimedEvent {
@ -416,6 +465,7 @@ public class Connection {
+ Connection.this.toString());
}
public void timeReached() {
killOutstandingPackets();
if (_log.shouldLog(Log.INFO))
_log.info("Connection disconnect timer complete, drop the con "
+ Connection.this.toString());
@ -671,6 +721,9 @@ public class Connection {
buf.append(" wsize: ").append(_options.getWindowSize());
buf.append(" cwin: ").append(_congestionWindowEnd - _highestAckedThrough);
buf.append(" rtt: ").append(_options.getRTT());
// not synchronized to avoid some kooky races
buf.append(" unacked outbound: ").append(_outboundPackets.size()).append(" ");
/*
buf.append(" unacked outbound: ");
synchronized (_outboundPackets) {
buf.append(_outboundPackets.size()).append(" [");
@ -679,6 +732,7 @@ public class Connection {
}
buf.append("] ");
}
*/
buf.append("unacked inbound? ").append(getUnackedPacketsReceived());
if (_inputStream != null) {
buf.append(" [high ");
@ -702,6 +756,7 @@ public class Connection {
public ResendPacketEvent(PacketLocal packet) {
_packet = packet;
_currentIsActiveResend = false;
packet.setResendPacketEvent(ResendPacketEvent.this);
}
public void timeReached() {

View File

@ -26,6 +26,10 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
_dummyStatus = new DummyStatus();
}
public boolean writeInProcess() {
return _connection.getUnackedPacketsSent() > 0;
}
/**
* 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
@ -63,7 +67,11 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
if (doSend) {
PacketLocal packet = send(buf, off, size);
return packet;
//dont wait for non-acks
if ( (packet.getSequenceNum() > 0) || (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) )
return packet;
else
return _dummyStatus;
} else {
return _dummyStatus;
}
@ -91,8 +99,16 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
* @return the packet sent
*/
public PacketLocal send(byte buf[], int off, int size, boolean forceIncrement) {
long before = System.currentTimeMillis();
PacketLocal packet = buildPacket(buf, off, size, forceIncrement);
long built = System.currentTimeMillis();
_connection.sendPacket(packet);
long sent = System.currentTimeMillis();
if ( (built-before > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("wtf, took " + (built-before) + "ms to build a packet: " + packet);
if ( (sent-built> 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("wtf, took " + (sent-built) + "ms to send a packet: " + packet);
return packet;
}

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) {
@ -66,8 +73,17 @@ class ConnectionHandler {
while (true) {
if ( (timeoutMs > 0) && (expiration < _context.clock().now()) )
return null;
if (!_active)
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) {

View File

@ -366,7 +366,8 @@ public class ConnectionManager {
if (removed) {
if (_notifier != null)
_notifier.pingComplete(false);
_log.error("Ping failed");
if (_log.shouldLog(Log.INFO))
_log.info("Ping failed");
}
}
}

View File

@ -87,8 +87,10 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 5));
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_SEND));
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_DISCONNECT));
setInboundBufferSize((getMaxMessageSize() + 2) * Connection.MAX_WINDOW_SIZE);
setConnectTimeout(getInt(opts, PROP_CONNECT_TIMEOUT, Connection.DISCONNECT_TIMEOUT));
}
/**

View File

@ -25,17 +25,43 @@ public class ConnectionPacketHandler {
_context.statManager().createRateStat("stream.con.receiveDuplicateSize", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.con.packetsAckedPerMessageReceived", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.sendsBeforeAck", "How many times a message was sent before it was ACKed?", "Stream", new long[] { 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.resetReceived", "How many messages had we sent successfully before receiving a RESET?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
}
/** distribute a packet to the connection specified */
void receivePacket(Packet packet, Connection con) throws I2PException {
boolean ok = verifyPacket(packet, con);
if (!ok) return;
if (!ok) {
if ( (!packet.isFlagSet(Packet.FLAG_RESET)) && (_log.shouldLog(Log.ERROR)) )
_log.error("Packet does NOT verify: " + packet);
return;
}
if (con.getHardDisconnected()) {
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ||
(packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) || (packet.isFlagSet(Packet.FLAG_CLOSE)) ) {
if (_log.shouldLog(Log.WARN))
_log.warn("Received a data packet after hard disconnect: " + packet + " on " + con);
con.sendReset();
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Received a packet after hard disconnect, ignoring: " + packet + " on " + con);
}
return;
}
con.packetReceived();
if (con.getInputStream().getTotalQueuedSize() > con.getOptions().getInboundBufferSize()) {
long ready = con.getInputStream().getHighestReadyBockId();
int available = con.getOptions().getInboundBufferSize() - con.getInputStream().getTotalReadySize();
int allowedBlocks = available/con.getOptions().getMaxMessageSize();
if (packet.getSequenceNum() > ready + allowedBlocks) {
if (_log.shouldLog(Log.WARN))
_log.warn("Inbound buffer exceeded on connection " + con + ": dropping " + packet);
_log.warn("Inbound buffer exceeded on connection " + con + " ("
+ ready + "/"+ (ready+allowedBlocks) + "/" + available
+ ": dropping " + packet);
ack(con, packet.getAckThrough(), packet.getNacks(), null, false);
con.getOptions().setChoke(5*1000);
return;
}
@ -55,6 +81,8 @@ public class ConnectionPacketHandler {
if (packet.isFlagSet(Packet.FLAG_CLOSE) && packet.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED))
con.closeReceived();
boolean fastAck = false;
if (isNew) {
con.incrementUnackedPacketsReceived();
con.incrementBytesReceived(packet.getPayloadSize());
@ -72,7 +100,8 @@ public class ConnectionPacketHandler {
_log.debug("Scheduling ack in " + delay + "ms for received packet " + packet);
}
} else {
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ) {
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ||
(packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) ) {
_context.statManager().addRateData("stream.con.receiveDuplicateSize", packet.getPayloadSize(), 0);
con.incrementDupMessagesReceived(1);
@ -83,7 +112,8 @@ public class ConnectionPacketHandler {
_log.warn("congestion.. dup " + packet);
SimpleTimer.getInstance().addEvent(new AckDup(con), con.getOptions().getSendAckDelay());
//con.incrementUnackedPacketsReceived();
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
//con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
fastAck = true;
} else {
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
//con.incrementUnackedPacketsReceived();
@ -95,8 +125,20 @@ public class ConnectionPacketHandler {
}
}
fastAck = fastAck || ack(con, packet.getAckThrough(), packet.getNacks(), packet, isNew);
con.eventOccurred();
if (fastAck) {
if (con.getLastSendTime() + 1000 < _context.clock().now()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fast ack for dup " + packet);
con.ackImmediately();
}
}
}
private boolean ack(Connection con, long ackThrough, long nacks[], Packet packet, boolean isNew) {
int numResends = 0;
List acked = con.ackPackets(packet.getAckThrough(), packet.getNacks());
List acked = con.ackPackets(ackThrough, nacks);
if ( (acked != null) && (acked.size() > 0) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(acked.size() + " of our packets acked with " + packet);
@ -130,18 +172,15 @@ public class ConnectionPacketHandler {
_context.statManager().addRateData("stream.con.packetsAckedPerMessageReceived", acked.size(), highestRTT);
}
boolean fastAck = adjustWindow(con, isNew, packet.getSequenceNum(), numResends, (acked != null ? acked.size() : 0));
con.eventOccurred();
if (fastAck) {
if (con.getLastSendTime() + con.getOptions().getRTT() < _context.clock().now()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fast ack for dup " + packet);
con.ackImmediately();
}
}
if (packet != null)
return adjustWindow(con, isNew, packet.getSequenceNum(), numResends, (acked != null ? acked.size() : 0));
else
return adjustWindow(con, false, -1, numResends, (acked != null ? acked.size() : 0));
}
private boolean adjustWindow(Connection con, boolean isNew, long sequenceNum, int numResends, int acked) {
boolean congested = false;
if ( (!isNew) && (sequenceNum > 0) ) {
// dup real packet
int oldSize = con.getOptions().getWindowSize();
@ -156,62 +195,38 @@ public class ConnectionPacketHandler {
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
+ ") for " + con);
return true;
} else if (numResends > 0) {
// window sizes are shrunk on resend, not on ack
} else {
if (acked > 0) {
long lowest = con.getHighestAckedThrough();
if (lowest >= con.getCongestionWindowEnd()) {
// new packet that ack'ed uncongested data, or an empty ack
int newWindowSize = con.getOptions().getWindowSize();
if (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
congested = true;
}
long lowest = con.getHighestAckedThrough();
if (lowest >= con.getCongestionWindowEnd()) {
// new packet that ack'ed uncongested data, or an empty ack
int newWindowSize = con.getOptions().getWindowSize();
if ( (!congested) && (acked > 0) && (numResends <= 0) ) {
if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
// congestion avoidance
// we can't use newWindowSize += 1/newWindowSize, since we're
// integers, so lets use a random distribution instead
int shouldIncrement = _context.random().nextInt(newWindowSize);
if (shouldIncrement <= 0)
newWindowSize += 1;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("New window size " + newWindowSize + " congestionSeenAt: "
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
+ ") for " + con);
con.getOptions().setWindowSize(newWindowSize);
con.setCongestionWindowEnd(newWindowSize + lowest);
} else {
// slow start
newWindowSize += 1;
}
} else {
// received a message that doesn't contain a new ack
// ehh. cant do this, as we SACK and the acks may be
// received out of order:
// Alice: RECEIVE 2
// Alice: SEND ack 2 nack 1
// Alice: RECEIVE 1
// Alice: SEND ack 2
// Bob: RECEIVE ack 2
// Bob: RECEIVE ack 2 nack 1 <-- NOT bad
/*
if (con.getUnackedPacketsSent() > 0) {
// peer got a dup
int oldSize = con.getOptions().getWindowSize();
oldSize >>>= 1;
if (oldSize <= 0)
oldSize = 1;
con.getOptions().setWindowSize(oldSize);
return false;
}
*/
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("New window size " + newWindowSize + " congestionSeenAt: "
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
+ ") for " + con);
con.getOptions().setWindowSize(newWindowSize);
con.setCongestionWindowEnd(newWindowSize + lowest);
}
return false;
return congested;
}
/**
@ -269,6 +284,8 @@ public class ConnectionPacketHandler {
con.resetReceived();
con.eventOccurred();
_context.statManager().addRateData("stream.resetReceived", con.getHighestAckedThrough(), con.getLifetime());
// no further processing
return;
}

View File

@ -32,6 +32,8 @@ public class I2PSocketFull implements I2PSocket {
destroy();
}
Connection getConnection() { return _connection; }
public InputStream getInputStream() {
return _connection.getInputStream();
}

View File

@ -33,9 +33,9 @@ public class MessageInputStream extends InputStream {
private List _readyDataBlocks;
private int _readyDataBlockIndex;
/** highest message ID used in the readyDataBlocks */
private long _highestReadyBlockId;
private volatile long _highestReadyBlockId;
/** highest overall message ID */
private long _highestBlockId;
private volatile long _highestBlockId;
/**
* Message ID (Long) to ByteArray for blocks received
* out of order when there are lower IDs not yet
@ -74,15 +74,13 @@ public class MessageInputStream extends InputStream {
/** What is the highest block ID we've completely received through? */
public long getHighestReadyBockId() {
synchronized (_dataLock) {
return _highestReadyBlockId;
}
// not synchronized as it doesnt hurt to read a too-low value
return _highestReadyBlockId;
}
public long getHighestBlockId() {
synchronized (_dataLock) {
return _highestBlockId;
}
// not synchronized as it doesnt hurt to read a too-low value
return _highestBlockId;
}
/**
@ -272,6 +270,9 @@ public class MessageInputStream extends InputStream {
// 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
@ -391,6 +392,21 @@ public class MessageInputStream extends InputStream {
}
}
public int getTotalReadySize() {
synchronized (_dataLock) {
if (_locallyClosed) return 0;
int numBytes = 0;
for (int i = 0; i < _readyDataBlocks.size(); i++) {
ByteArray cur = (ByteArray)_readyDataBlocks.get(i);
if (i == 0)
numBytes += cur.getData().length - _readyDataBlockIndex;
else
numBytes += cur.getData().length;
}
return numBytes;
}
}
public void close() {
synchronized (_dataLock) {
_readyDataBlocks.clear();
@ -402,6 +418,7 @@ public class MessageInputStream extends InputStream {
ba.setData(null);
}
_locallyClosed = true;
_dataLock.notifyAll();
}
}

View File

@ -8,6 +8,7 @@ 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
@ -26,6 +27,11 @@ public class MessageOutputStream extends OutputStream {
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);
@ -41,6 +47,10 @@ public class MessageOutputStream extends OutputStream {
_written = 0;
_closed = false;
_writeTimeout = -1;
_passiveFlushDelay = 500;
_flusher = new Flusher();
if (_log.shouldLog(Log.DEBUG))
_log.debug("MessageOutputStream created");
}
public void setWriteTimeout(int ms) { _writeTimeout = ms; }
@ -51,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
@ -70,6 +81,10 @@ public class MessageOutputStream extends OutputStream {
cur += remaining;
_written += remaining;
remaining = 0;
_lastBuffered = _context.clock().now();
if (_passiveFlushDelay > 0) {
_flusher.enqueue();
}
} else {
// buffer whatever we can fit then flush,
// repeating until we've pushed all of the
@ -79,25 +94,38 @@ 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)
ws.waitForAccept(_writeTimeout);
if (!ws.writeAccepted()) {
if (_writeTimeout > 0)
throw new InterruptedIOException("Write not accepted within timeout");
throw new InterruptedIOException("Write not accepted within timeout: " + ws);
else
throw new IOException("Write not accepted into the queue");
throw new IOException("Write not accepted into the queue: " + ws);
}
} else {
if (_log.shouldLog(Log.DEBUG))
_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();
}
@ -106,6 +134,66 @@ 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 {
private boolean _enqueued;
public void enqueue() {
// no need to be overly worried about duplicates - it would just
// push it further out
if (!_enqueued) {
SimpleTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Enqueueing the flusher for " + _passiveFlushDelay + "ms out");
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("NOT enqueing the flusher");
}
_enqueued = true;
}
public void timeReached() {
_enqueued = false;
DataReceiver rec = _dataReceiver;
long timeLeft = (_lastBuffered + _passiveFlushDelay - _context.clock().now());
if (_log.shouldLog(Log.DEBUG))
_log.debug("flusher time reached: left = " + timeLeft);
if (timeLeft > 0)
enqueue();
else if ( (rec != null) && (rec.writeInProcess()) )
enqueue(); // don't passive flush if there is a write being done (unacked outbound)
else
doFlush();
}
private void doFlush() {
boolean sent = false;
WriteStatus ws = null;
synchronized (_dataLock) {
long flushTime = _lastBuffered + _passiveFlushDelay;
if ( (_valid > 0) && (flushTime < _context.clock().now()) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("doFlush() valid = " + _valid);
if ( (_buf != null) && (_dataReceiver != null) ) {
ws = _dataReceiver.writeData(_buf, 0, _valid);
_written += _valid;
_valid = 0;
_lastFlushed = _context.clock().now();
_dataLock.notifyAll();
sent = true;
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("doFlush() rejected... valid = " + _valid);
}
}
// ignore the ws
if (sent && _log.shouldLog(Log.DEBUG))
_log.debug("Passive flush of " + ws);
}
}
/**
* Flush the data already queued up, blocking until it has been
* delivered.
@ -114,12 +202,18 @@ public class MessageOutputStream extends OutputStream {
* @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();
}
@ -129,6 +223,8 @@ public class MessageOutputStream extends OutputStream {
( (_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))
@ -137,6 +233,10 @@ public class MessageOutputStream extends OutputStream {
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();
}
@ -164,7 +264,8 @@ public class MessageOutputStream extends OutputStream {
ByteArray ba = null;
synchronized (_dataLock) {
// flush any data, but don't wait for it
_dataReceiver.writeData(_buf, 0, _valid);
if (_dataReceiver != null)
_dataReceiver.writeData(_buf, 0, _valid);
_written += _valid;
_valid = 0;
@ -173,6 +274,7 @@ public class MessageOutputStream extends OutputStream {
_buf = null;
_valid = 0;
}
_lastFlushed = _context.clock().now();
_dataLock.notifyAll();
}
if (ba != null) {
@ -205,6 +307,7 @@ public class MessageOutputStream extends OutputStream {
}
void flushAvailable(DataReceiver target, boolean blocking) throws IOException {
WriteStatus ws = null;
long before = System.currentTimeMillis();
synchronized (_dataLock) {
// _buf may be null, but the data receiver can handle that just fine,
// deciding whether or not to send a packet
@ -212,7 +315,12 @@ public class MessageOutputStream extends OutputStream {
_written += _valid;
_valid = 0;
_dataLock.notifyAll();
_lastFlushed = _context.clock().now();
}
long afterBuild = System.currentTimeMillis();
if ( (afterBuild - before > 1000) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("Took " + (afterBuild-before) + "ms to build a packet? " + ws);
if (blocking && ws != null) {
ws.waitForAccept(_writeTimeout);
if (ws.writeFailed())
@ -220,6 +328,9 @@ public class MessageOutputStream extends OutputStream {
else if (!ws.writeAccepted())
throw new InterruptedIOException("Flush available timed out");
}
long afterAccept = System.currentTimeMillis();
if ( (afterAccept - afterBuild > 1000) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("Took " + (afterAccept-afterBuild) + "ms to accept a packet? " + ws);
return;
}
@ -233,6 +344,7 @@ public class MessageOutputStream extends OutputStream {
* Nonblocking write
*/
public WriteStatus writeData(byte buf[], int off, int size);
public boolean writeInProcess();
}
/** Define a way to detect the status of a write */

View File

@ -104,7 +104,7 @@ public class PacketHandler {
}
}
private static final SimpleDateFormat _fmt = new SimpleDateFormat("hh:mm:ss.SSS");
private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss.SSS");
void displayPacket(Packet packet, String prefix) {
String msg = null;
synchronized (_fmt) {
@ -152,16 +152,28 @@ public class PacketHandler {
}
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Receive a syn packet with the wrong IDs: " + packet);
_log.warn("Receive a syn packet with the wrong IDs, sending reset: " + packet);
sendReset(packet);
}
} else {
// someone is sending us a packet on the wrong stream
if (_log.shouldLog(Log.WARN))
_log.warn("Received a packet on the wrong stream: " + packet);
_log.warn("Received a packet on the wrong stream: " + packet + " connection: " + con);
}
}
}
private void sendReset(Packet packet) {
PacketLocal reply = new PacketLocal(_context, packet.getOptionalFrom());
reply.setFlag(Packet.FLAG_RESET);
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
reply.setSendStreamId(packet.getReceiveStreamId());
reply.setReceiveStreamId(packet.getSendStreamId());
reply.setOptionalFrom(_manager.getSession().getMyDestination());
// this just sends the packet - no retries or whatnot
_manager.getPacketQueue().enqueue(reply);
}
private void receiveUnknownCon(Packet packet, byte sendId[]) {
if (packet.isFlagSet(Packet.FLAG_ECHO)) {
if (packet.getSendStreamId() != null) {

View File

@ -5,6 +5,8 @@ import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
* coordinate local attributes about a packet - send time, ack time, number of
@ -12,6 +14,7 @@ import net.i2p.data.SessionKey;
*/
public class PacketLocal extends Packet implements MessageOutputStream.WriteStatus {
private I2PAppContext _context;
private Log _log;
private Connection _connection;
private Destination _to;
private SessionKey _keyUsed;
@ -22,6 +25,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
private long _acceptedOn;
private long _ackOn;
private long _cancelledOn;
private SimpleTimer.TimedEvent _resendEvent;
public PacketLocal(I2PAppContext ctx, Destination to) {
this(ctx, to, null);
@ -29,6 +33,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
public PacketLocal(I2PAppContext ctx, Destination to, Connection con) {
_context = ctx;
_createdOn = ctx.clock().now();
_log = ctx.logManager().getLog(PacketLocal.class);
_to = to;
_connection = con;
_lastSend = -1;
@ -78,14 +83,16 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
_ackOn = _context.clock().now();
notifyAll();
}
_connection = null;
SimpleTimer.getInstance().removeEvent(_resendEvent);
}
public void cancelled() {
synchronized (this) {
_cancelledOn = _context.clock().now();
notifyAll();
}
_connection = null;
SimpleTimer.getInstance().removeEvent(_resendEvent);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Cancelled! " + toString(), new Exception("cancelled"));
}
/** how long after packet creation was it acked? */
@ -99,6 +106,8 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
public long getLastSend() { return _lastSend; }
public Connection getConnection() { return _connection; }
public void setResendPacketEvent(SimpleTimer.TimedEvent evt) { _resendEvent = evt; }
public String toString() {
String str = super.toString();
if (_ackOn > 0)
@ -110,17 +119,27 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
public void waitForAccept(int maxWaitMs) {
if (_connection == null)
throw new IllegalStateException("Cannot wait for accept with no connection");
long expiration = _context.clock().now()+maxWaitMs;
long before = _context.clock().now();
long expiration = before+maxWaitMs;
int queued = _connection.getUnackedPacketsSent();
int window = _connection.getOptions().getWindowSize();
boolean accepted = _connection.packetSendChoke(maxWaitMs);
long after = _context.clock().now();
if (accepted)
_acceptedOn = _context.clock().now();
_acceptedOn = after;
else
_acceptedOn = -1;
_connection = null;
int afterQueued = _connection.getUnackedPacketsSent();
if ( (after - before > 1000) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("Took " + (after-before) + "ms to get "
+ (accepted ? " accepted" : " rejected")
+ (_cancelledOn > 0 ? " and CANCELLED" : "")
+ ", queued behind " + queued +" with a window size of " + window
+ ", finally accepted with " + afterQueued + " queued: "
+ toString());
}
public void waitForCompletion(int maxWaitMs) {
_connection = null;
long expiration = _context.clock().now()+maxWaitMs;
while (true) {
long timeRemaining = expiration - _context.clock().now();

View File

@ -24,24 +24,17 @@ class PacketQueue {
private Log _log;
private I2PSession _session;
private ConnectionManager _connectionManager;
private byte _buf[];
private ByteCache _cache = ByteCache.getInstance(64, 36*1024);
public PacketQueue(I2PAppContext context, I2PSession session, ConnectionManager mgr) {
_context = context;
_session = session;
_connectionManager = mgr;
_buf = _cache.acquire().getData(); // new byte[36*1024];
_log = context.logManager().getLog(PacketQueue.class);
_context.statManager().createRateStat("stream.con.sendMessageSize", "Size of a message sent on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.con.sendDuplicateSize", "Size of a message resent on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
}
protected void finalize() throws Throwable {
_cache.release(new ByteArray(_buf));
super.finalize();
}
/**
* Add a new packet to be sent out ASAP
*/
@ -53,7 +46,7 @@ class PacketQueue {
keyUsed = new SessionKey();
Set tagsSent = packet.getTagsSent();
if (tagsSent == null)
tagsSent = new HashSet();
tagsSent = new HashSet(0);
// cache this from before sendMessage
String conStr = (packet.getConnection() != null ? packet.getConnection().toString() : "");
@ -63,29 +56,36 @@ class PacketQueue {
} else {
_log.debug("Sending... " + packet);
}
ByteArray ba = _cache.acquire();
byte buf[] = ba.getData();
long begin = 0;
long end = 0;
boolean sent = false;
try {
int size = 0;
synchronized (this) {
Arrays.fill(_buf, (byte)0x0);
if (packet.shouldSign())
size = packet.writeSignedPacket(_buf, 0, _context, _session.getPrivateKey());
else
size = packet.writePacket(_buf, 0);
long beforeWrite = System.currentTimeMillis();
if (packet.shouldSign())
size = packet.writeSignedPacket(buf, 0, _context, _session.getPrivateKey());
else
size = packet.writePacket(buf, 0);
long writeTime = System.currentTimeMillis() - beforeWrite;
if ( (writeTime > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("took " + writeTime + "ms to write the packet: " + packet);
// this should not block!
begin = _context.clock().now();
sent = _session.sendMessage(packet.getTo(), _buf, 0, size, keyUsed, tagsSent);
end = _context.clock().now();
}
// this should not block!
begin = _context.clock().now();
sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent);
end = _context.clock().now();
if ( (end-begin > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("Took " + (end-begin) + "ms to sendMessage(...) " + packet);
_context.statManager().addRateData("stream.con.sendMessageSize", size, packet.getLifetime());
if (packet.getNumSends() > 1)
_context.statManager().addRateData("stream.con.sendDuplicateSize", size, packet.getLifetime());
Connection con = packet.getConnection();
if (con != null) {
con.incrementBytesSent(size);
@ -97,6 +97,8 @@ class PacketQueue {
_log.warn("Unable to send the packet " + packet, ise);
}
_cache.release(ba);
if (!sent) {
if (_log.shouldLog(Log.WARN))
_log.warn("Send failed for " + packet);

View File

@ -38,6 +38,7 @@ class SchedulerChooser {
private List createSchedulers() {
List rv = new ArrayList(8);
rv.add(new SchedulerHardDisconnected(_context));
rv.add(new SchedulerPreconnect(_context));
rv.add(new SchedulerConnecting(_context));
rv.add(new SchedulerReceived(_context));
@ -54,8 +55,7 @@ class SchedulerChooser {
}
public void eventOccurred(Connection con) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Event occurred on " + con, new Exception("source"));
_log.log(Log.CRIT, "Yell at jrandom: Event occurred on " + con, new Exception("source"));
}
public boolean accept(Connection con) { return true; }
};

View File

@ -47,7 +47,8 @@ class SchedulerClosed extends SchedulerImpl {
}
public void eventOccurred(Connection con) {
long timeLeft = con.getCloseSentOn() + Connection.DISCONNECT_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

@ -37,7 +37,7 @@ class SchedulerConnecting extends SchedulerImpl {
public boolean accept(Connection con) {
if (con == null) return false;
boolean notYetConnected = (con.getIsConnected()) &&
(con.getSendStreamId() == null) &&
//(con.getSendStreamId() == null) && // not null on recv
(con.getLastSendId() >= 0) &&
(con.getAckedPackets() <= 0) &&
(!con.getResetReceived());
@ -55,6 +55,7 @@ class SchedulerConnecting extends SchedulerImpl {
_log.debug("waited too long: " + waited);
return;
} else {
// should we be doing a con.sendAvailable here?
if (con.getOptions().getConnectTimeout() > 0)
reschedule(con.getOptions().getConnectTimeout(), con);
}

View File

@ -0,0 +1,45 @@
package net.i2p.client.streaming;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* <p>Scheduler used after we've locally done a hard disconnect,
* but the final timeout hasn't passed.</p>
*
* <h2>Entry conditions:</h2><ul>
* <li>Locally disconnected hard.</li>
* <li>Less than the final timeout period has passed since the last ACK.</li>
* </ul>
*
* <h2>Events:</h2><ul>
* <li>Packets received</li>
* <li>RESET received</li>
* <li>Message sending fails (error talking to the session)</li>
* </ul>
*
* <h2>Next states:</h2>
* <li>{@link SchedulerDead dead} - after the final timeout passes</li>
* </ul>
*
*
*/
class SchedulerHardDisconnected extends SchedulerImpl {
private Log _log;
public SchedulerHardDisconnected(I2PAppContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(SchedulerHardDisconnected.class);
}
public boolean accept(Connection con) {
if (con == null) return false;
long timeSinceClose = _context.clock().now() - con.getCloseSentOn();
boolean ok = (con.getHardDisconnected() || con.getResetSent()) &&
(timeSinceClose < Connection.DISCONNECT_TIMEOUT);
return ok;
}
public void eventOccurred(Connection con) {
// noop. we do the timeout through the simpleTimer anyway
}
}

View File

@ -32,5 +32,6 @@ abstract class SchedulerImpl implements TaskScheduler {
// _log.debug("firing event on " + _connection, _addedBy);
_connection.eventOccurred();
}
public String toString() { return "event on " + _connection; }
}
}

View File

@ -0,0 +1,138 @@
package net.i2p.client.streaming;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Have a client connect to a server, where the server waits 5
* seconds and closes the socket and the client detect that
* EOF.
*
*/
public class ConnectCloseTest {
private Log _log;
private I2PSession _server;
public void test() {
try {
I2PAppContext context = I2PAppContext.getGlobalContext();
_log = context.logManager().getLog(ConnectCloseTest.class);
_log.debug("creating server session");
_server = createSession();
_log.debug("running server");
runServer(context, _server);
_log.debug("running client");
runClient(context, createSession());
} catch (Exception e) {
_log.error("error running", e);
}
try { Thread.sleep(10*60*1000); } catch (Exception e) {}
}
private void runClient(I2PAppContext ctx, I2PSession session) {
Thread t = new Thread(new ClientRunner(ctx, session));
t.setName("client");
t.setDaemon(true);
t.start();
}
private void runServer(I2PAppContext ctx, I2PSession session) {
Thread t = new Thread(new ServerRunner(ctx, session));
t.setName("server");
t.setDaemon(true);
t.start();
}
private class ServerRunner implements Runnable {
private I2PAppContext _context;
private I2PSession _session;
private Log _log;
public ServerRunner(I2PAppContext ctx, I2PSession session) {
_context = ctx;
_session = session;
_log = ctx.logManager().getLog(ServerRunner.class);
}
public void run() {
try {
Properties opts = new Properties();
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
_log.debug("* manager created");
I2PServerSocket ssocket = mgr.getServerSocket();
_log.debug("* server socket created");
while (true) {
I2PSocket socket = ssocket.accept();
_log.debug("* socket accepted: " + socket);
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
socket.close();
_log.debug("* socket closed: " + socket);
}
} catch (Exception e) {
_log.error("error running", e);
}
}
}
private class ClientRunner implements Runnable {
private I2PAppContext _context;
private I2PSession _session;
private Log _log;
public ClientRunner(I2PAppContext ctx, I2PSession session) {
_context = ctx;
_session = session;
_log = ctx.logManager().getLog(ClientRunner.class);
}
public void run() {
try {
Properties opts = new Properties();
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
_log.debug("* manager created");
I2PSocket socket = mgr.connect(_server.getMyDestination());
_log.debug("* socket created");
InputStream in = socket.getInputStream();
int c = in.read();
if (c != -1)
throw new RuntimeException("hrm, we got data? [" + c + "]");
socket.close();
_log.debug("* socket closed");
mgr.destroySocketManager();
mgr = null;
socket = null;
} catch (Exception e) {
_log.error("error running", e);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}
private I2PSession createSession() {
try {
I2PClient client = I2PClientFactory.createClient();
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
Destination dest = client.createDestination(baos);
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), System.getProperties());
sess.connect();
return sess;
} catch (Exception e) {
_log.error("error running", e);
throw new RuntimeException("b0rk b0rk b0rk");
}
}
public static void main(String args[]) {
ConnectCloseTest ct = new ConnectCloseTest();
ct.test();
}
}

View File

@ -57,6 +57,7 @@ public class MessageOutputStreamTest {
_data.write(buf, off, size);
return new DummyWriteStatus();
}
public boolean writeInProcess() { return false; }
public byte[] getData() { return _data.toByteArray(); }
}

View File

@ -1,5 +1,5 @@
Prior to building the jbigi library, you will need to fetch the GMP source
from http://www.swox.com/gmp/, saving it to jbigi/gmp-4.1.3.tar.bz2 (it will
from http://www.swox.com/gmp/, saving it to jbigi/gmp-4.1.4.tar.bz2 (it will
be unpacked and built as necessary).
To build the native jbigi and jcpuid libraries for the current host CPU,

View File

@ -7,7 +7,6 @@ mkdir -p t/freenet/support/CPUInformation/
cp jcpuid/lib/freenet/support/CPUInformation/*jcpuid* t/freenet/support/CPUInformation/
mkdir -p t/net/i2p/util/
cp jbigi/lib/net/i2p/util/*jbigi* t/net/i2p/util/
cp jbigi/lib/*jbigi* t/
(cd t ; jar cf ../jbigi.jar . ; cd ..)

View File

@ -13,7 +13,7 @@ FreeBSD*)
esac
echo "Extracting GMP..."
tar -xjf gmp-4.1.3.tar.bz2
tar -xjf gmp-4.1.4.tar.bz2
echo "Building..."
mkdir bin
mkdir lib
@ -24,7 +24,7 @@ for x in none pentium pentiummmx pentium2 pentium3 pentium4 k6 k62 k63 athlon
do
mkdir bin/$x
cd bin/$x
../../gmp-4.1.3/configure --build=$x
../../gmp-4.1.4/configure --build=$x
make
sh ../../build_jbigi.sh static
case `uname -sr` in

View File

@ -1,41 +1,15 @@
#/bin/sh
case `uname -sr` in
MINGW*)
echo "Building windows .dll's";;
CYGWIN*)
echo "Building windows .dll's";;
Linux*)
echo "Building linux .so's";;
FreeBSD*)
echo "Building freebsd .so's";;
*)
echo "Unsupported build environment"
exit;;
esac
echo "Building the jbigi library with GMP"
echo "Extracting GMP..."
tar -xjf gmp-4.1.3.tar.bz2
tar -xjf gmp-4.1.4.tar.bz2
echo "Building..."
mkdir bin
mkdir lib
mkdir lib/net
mkdir lib/net/i2p
mkdir lib/net/i2p/util
mkdir bin/local
mkdir -p lib/
mkdir -p bin/local
cd bin/local
../../gmp-4.1.3/configure
../../gmp-4.1.4/configure
make
sh ../../build_jbigi.sh static
case `uname -sr` in
MINGW*)
cp jbigi.dll ../../lib/jbigi;;
CYGWIN*)
cp jbigi.dll ../../lib/jbigi;;
Linux*)
cp libjbigi.so ../../lib/jbigi;;
FreeBSD*)
cp libjbigi.so ../../lib/jbigi;;
esac
cd ..
cd ..
cp *jbigi???* ../../lib/
cd ../..

View File

@ -67,6 +67,7 @@ class I2CPMessageProducer {
*
*/
public void disconnect(I2PSessionImpl session) throws I2PSessionException {
if (session.isClosed()) return;
DestroySessionMessage dmsg = new DestroySessionMessage();
dmsg.setSessionId(session.getSessionId());
session.sendMessage(dmsg);

View File

@ -108,6 +108,8 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent)
throws I2PSessionException {
long begin = _context.clock().now();
SessionKey key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
@ -180,9 +182,17 @@ class I2PSessionImpl2 extends I2PSessionImpl {
+ " sync took " + (inSendingSync-beforeSendingSync)
+ " add took " + (afterSendingSync-inSendingSync));
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
// since this is 'best effort', all we're waiting for is a status update
// saying that the router received it - in theory, that should come back
// immediately, but in practice can take up to a second (though usually
// much quicker). setting this to false will short-circuit that delay
boolean actuallyWait = true;
long beforeWaitFor = _context.clock().now();
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED,
_context.clock().now() + getTimeout());
if (actuallyWait)
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED,
_context.clock().now() + getTimeout());
long afterWaitFor = _context.clock().now();
long inRemovingSync = 0;
synchronized (_sendingStates) {
@ -190,10 +200,23 @@ class I2PSessionImpl2 extends I2PSessionImpl {
_sendingStates.remove(state);
}
long afterRemovingSync = _context.clock().now();
boolean found = state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
boolean found = !actuallyWait || state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "After waitFor sending state " + state.getMessageId()
+ " / " + state.getNonce() + " found = " + found);
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 ( (afterRemovingSync - begin > 500) && (_log.shouldLog(Log.WARN) ) ) {
_log.warn("Took " + (afterRemovingSync-begin) + "ms to sendBestEffort, "
+ (afterSendingSync-begin) + "ms to prepare, "
+ (beforeWaitFor-afterSendingSync) + "ms to send, "
+ (afterRemovingSync-beforeWaitFor) + "ms waiting for reply");
}
if (found) {
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + "Message sent after " + state.getElapsed() + "ms with "

View File

@ -16,6 +16,7 @@ import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.data.i2cp.ReceiveMessageEndMessage;
import net.i2p.util.Log;
/**
* Handle I2CP MessagePayloadMessages from the router delivering the contents
@ -30,7 +31,8 @@ class MessagePayloadMessageHandler extends HandlerImpl {
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
_log.debug("Handle message " + message);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handle message " + message);
try {
MessagePayloadMessage msg = (MessagePayloadMessage) message;
MessageId id = msg.getMessageId();
@ -55,9 +57,8 @@ class MessagePayloadMessageHandler extends HandlerImpl {
Payload payload = msg.getPayload();
byte[] data = _context.elGamalAESEngine().decrypt(payload.getEncryptedData(), session.getDecryptionKey());
if (data == null) {
_log
.error("Error decrypting the payload to public key "
+ session.getMyDestination().getPublicKey().toBase64() + "\nPayload: " + payload.calculateHash());
if (_log.shouldLog(Log.ERROR))
_log.error("Error decrypting the payload");
throw new DataFormatException("Unable to decrypt the payload");
}
payload.setUnencryptedData(data);

View File

@ -27,7 +27,7 @@ but there are three other subpackages that are helpful. Specifically:<ul>
<li>{@link net.i2p.client.streaming} - for applications that want to use
a streaming API to provide reliable in order message delivery (<b>note</b>:
the streaming library is packaged seperate from the main SDK - in the
ministreaming.jar)</li>
mstreaming.jar and streaming.jar)</li>
</ul></p>
<p>The {@link net.i2p.client.I2PSession} implementation itself communicates with

View File

@ -18,6 +18,7 @@ import java.io.Serializable;
*/
public class ByteArray implements Serializable, Comparable {
private byte[] _data;
private int _valid;
public ByteArray() {
this(null);
@ -25,6 +26,7 @@ public class ByteArray implements Serializable, Comparable {
public ByteArray(byte[] data) {
_data = data;
_valid = 0;
}
public final byte[] getData() {
@ -34,6 +36,14 @@ public class ByteArray implements Serializable, Comparable {
public void setData(byte[] data) {
_data = data;
}
/**
* Count how many of the bytes in the array are 'valid'.
* this property does not necessarily have meaning for all byte
* arrays.
*/
public final int getValid() { return _valid; }
public final void setValid(int valid) { _valid = valid; }
public final boolean equals(Object o) {
if (o == null) return false;

View File

@ -658,18 +658,41 @@ public class DataHelper {
}
public static int read(InputStream in, byte target[]) throws IOException {
int cur = 0;
while (cur < target.length) {
int numRead = in.read(target, cur, target.length - cur);
return read(in, target, 0, target.length);
}
public static int read(InputStream in, byte target[], int offset, int length) throws IOException {
int cur = offset;
while (cur < length) {
int numRead = in.read(target, cur, length - cur);
if (numRead == -1) {
if (cur == 0) return -1; // throw new EOFException("EOF Encountered during reading");
if (cur == offset) return -1; // throw new EOFException("EOF Encountered during reading");
return cur;
}
cur += numRead;
}
return cur;
}
/**
* Read a newline delimited line from the stream, returning the line (without
* the newline), or null if EOF reached before the newline was found
*/
public static String readLine(InputStream in) throws IOException {
StringBuffer buf = new StringBuffer(128);
int c = -1;
while ( (c = in.read()) != -1) {
if (c == '\n')
break;
buf.append((char)c);
}
if (c == -1)
return null;
else
return buf.toString();
}
public static List sortStructures(Collection dataStructures) {
if (dataStructures == null) return new ArrayList();
@ -735,7 +758,7 @@ public class DataHelper {
/** decompress the GZIP compressed data (returning null on error) */
public static byte[] decompress(byte orig[]) throws IOException {
return decompress(orig, 0, orig.length);
return (orig != null ? decompress(orig, 0, orig.length) : null);
}
public static byte[] decompress(byte orig[], int offset, int length) throws IOException {
if ((orig == null) || (orig.length <= 0)) return orig;

View File

@ -124,7 +124,8 @@ public class Timestamper implements Runnable {
alreadyBitched = true;
}
}
try { Thread.sleep(_queryFrequency); } catch (InterruptedException ie) {}
long sleepTime = _context.random().nextInt(_queryFrequency) + _queryFrequency;
try { Thread.sleep(sleepTime); } catch (InterruptedException ie) {}
}
} catch (Throwable t) {
_log.log(Log.CRIT, "Timestamper died!", t);

View File

@ -42,8 +42,8 @@ public class Clock implements Timestamper.UpdateListener {
/** if the clock is skewed by 3+ days, fuck 'em */
public final static long MAX_OFFSET = 3 * 24 * 60 * 60 * 1000;
/** after we've started up and shifted the clock, don't allow shifts of more than a minute */
public final static long MAX_LIVE_OFFSET = 60 * 1000;
/** after we've started up and shifted the clock, don't allow shifts of more than 10 minutes */
public final static long MAX_LIVE_OFFSET = 10 * 60 * 1000;
/** if the clock skewed changes by less than 1s, ignore the update (so we don't slide all over the place) */
public final static long MIN_OFFSET_CHANGE = 10 * 1000;

View File

@ -18,20 +18,30 @@ import net.i2p.I2PAppContext;
public class SimpleTimer {
private static final SimpleTimer _instance = new SimpleTimer();
public static SimpleTimer getInstance() { return _instance; }
private I2PAppContext _context;
private Log _log;
/** event time (Long) to event (TimedEvent) mapping */
private TreeMap _events;
/** event (TimedEvent) to event time (Long) mapping */
private Map _eventTimes;
private List _readyEvents;
private SimpleTimer() {
_log = I2PAppContext.getGlobalContext().logManager().getLog(SimpleTimer.class);
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(SimpleTimer.class);
_events = new TreeMap();
_eventTimes = new HashMap();
_readyEvents = new ArrayList(4);
I2PThread runner = new I2PThread(new SimpleTimerRunner());
runner.setName("SimpleTimer");
runner.setDaemon(true);
runner.start();
for (int i = 0; i < 3; i++) {
I2PThread executor = new I2PThread(new Executor());
executor.setName("SimpleTimerExecutor " + i);
executor.setDaemon(true);
executor.start();
}
}
/**
@ -40,18 +50,42 @@ public class SimpleTimer {
*/
public void addEvent(TimedEvent event, long timeoutMs) {
long eventTime = System.currentTimeMillis() + timeoutMs;
Long time = new Long(eventTime);
synchronized (_events) {
// remove the old scheduled position, then reinsert it
if (_eventTimes.containsKey(event))
_events.remove(_eventTimes.get(event));
while (_events.containsKey(new Long(eventTime)))
eventTime++;
_events.put(new Long(eventTime), event);
_eventTimes.put(event, new Long(eventTime));
while (_events.containsKey(time))
time = new Long(time.longValue() + 1);
_events.put(time, event);
_eventTimes.put(event, time);
if ( (_events.size() != _eventTimes.size()) ) {
_log.error("Skewed events: " + _events.size() + " for " + _eventTimes.size());
for (Iterator iter = _eventTimes.keySet().iterator(); iter.hasNext(); ) {
TimedEvent evt = (TimedEvent)iter.next();
Long when = (Long)_eventTimes.get(evt);
TimedEvent cur = (TimedEvent)_events.get(when);
if (cur != evt) {
_log.error("event " + evt + " @ " + when + ": " + cur);
}
}
}
_events.notifyAll();
}
}
public boolean removeEvent(TimedEvent evt) {
if (evt == null) return false;
synchronized (_events) {
Long when = (Long)_eventTimes.remove(evt);
if (when != null)
_events.remove(when);
return null != when;
}
}
/**
* Simple interface for events to be queued up and notified on expiration
*/
@ -72,14 +106,18 @@ 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);
while (true) {
try {
synchronized (_events) {
if (_events.size() <= 0)
_events.wait();
//if (_events.size() <= 0)
// _events.wait();
//if (_events.size() > 100)
// _log.warn("> 100 events! " + _events.values());
long now = System.currentTimeMillis();
@ -93,7 +131,7 @@ public class SimpleTimer {
if (evt != null) {
_eventTimes.remove(evt);
eventsToFire.add(evt);
}
}
} else {
nextEventDelay = when.longValue() - now;
nextEvent = _events.get(when);
@ -121,15 +159,55 @@ public class SimpleTimer {
}
}
for (int i = 0; i < eventsToFire.size(); i++) {
TimedEvent evt = (TimedEvent)eventsToFire.get(i);
long now = System.currentTimeMillis();
now = now - (now % 1000);
synchronized (_readyEvents) {
for (int i = 0; i < eventsToFire.size(); i++)
_readyEvents.add(eventsToFire.get(i));
_readyEvents.notifyAll();
}
if (_occurredTime == now) {
_occurredEventCount += eventsToFire.size();
} else {
_occurredTime = now;
if (_occurredEventCount > 1000) {
StringBuffer buf = new StringBuffer(128);
buf.append("Too many simpleTimerJobs (").append(_occurredEventCount);
buf.append(") in a second!");
_log.log(Log.CRIT, buf.toString());
}
_occurredEventCount = 0;
}
eventsToFire.clear();
}
}
}
private class Executor implements Runnable {
public void run() {
while (true) {
TimedEvent evt = null;
synchronized (_readyEvents) {
if (_readyEvents.size() <= 0)
try { _readyEvents.wait(); } catch (InterruptedException ie) {}
if (_readyEvents.size() > 0)
evt = (TimedEvent)_readyEvents.remove(0);
}
if (evt != null) {
long before = _context.clock().now();
try {
evt.timeReached();
} catch (Throwable t) {
log("wtf, event borked: " + evt, t);
}
long time = _context.clock().now() - before;
if ( (time > 1000) && (_log != null) && (_log.shouldLog(Log.WARN)) )
_log.warn("wtf, event execution took " + time + ": " + evt);
}
eventsToFire.clear();
}
}
}

View File

@ -0,0 +1,206 @@
package net.i2p.crypto;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
/**
* Unit test verifying the ElGamal encryption/decryption with some test
* data. The keys generated & data stored were generated by jrandom on
* a pentium4 w/ an optimized jbigi installed and verified with pure java.
*
*/
public class ElGamalVerify {
private I2PAppContext _context;
private static final String UNENCRYPTED[] = new String[] {
"",
"hello world",
"1234567890123456789012345678901234567890123456789012345678901234567890" +
"1234567890123456789012345678901234567890123456789012345678901234567890" +
"1234567890123456789012345678901234567890123456789012345678901234567890" +
"123456789012",
"\0x00",
"\0x00\0x00\0x00",
"\0x00\0x01\0x02\0x00",
};
private static final String PUBLIC_KEY = new String(
"pOvBUMrSUUeN5awynzbPbCAwe3MqWprhSpp3OR7pvdfm9PhWaNbPoKRLeEmDoUwyNDoHE0" +
"E6mcZSG8qPQ8XUZFlczpilOl0MJBvsI9u9SMyi~bEqzSgzh9FNfS-NcGji3q2wI~Ux~q5B" +
"KOjGlyMLgd1nxl5R5wIYL4uHKZNaYuArsRYmtV~MgMQPGvDtIbdGTV6aL6UbOYryzQSUMY" +
"OuO3S~YoBjA6Nmi0SeJM3tyTxlI6U1EYjR6oQcI4SOFUW4L~8pfYWijcncCODAqpXVN6ZI" +
"AJ3a6vjxGu56IDp4xCcKlOEHgdXvqmEC67dR5qf2btH6dtWoB3-Z6QPsS6tPTQ=="
);
private static final String PRIVATE_KEY = new String(
"gMlIhURVXU8uPube20Xr8E1K11g-3qZxOj1riThHqt-rBx72MPq5ivT1rr28cE9mzOmsXi" +
"bbsuBuQKYDvF7hGICRB3ROSPePYhcupV3j7XiXUIYjWNw9hvylHXK~nTT7jkpIBazBJZfr" +
"LJPcDZTDB0YnCOHOL-KFn4N1R5B22g0iYRABN~O10AUjQmf1epklAXPqYlzmOYeJSfTPBI" +
"E44nEccWJp0M0KynhKVbDI0v9VYm6sPFK7WrzRyWwHL~r735wiRkwywuMmKJtA7-PuJjcW" +
"NLkJwx6WScH2msMzhzYPi8JSZJBl~PosX934l-L0T-KNV4jg1Ih6yoCnm1748A=="
);
private static final String ENCRYPTED[] = new String[] {
"AMfISa8KvTpaC7KXZzSvC2axyiSk0xPexBAf29yU~IKq21DzaU19wQcGJg-ktpG4hjGSg7" +
"u-mJ07b61yo-EGmVGZsv3nYuQYW-GjvsZQa9nm98VljlMtWrxu7TsRXw~SQlWQxMvthqJB" +
"1A7Y7Qa~C7-UlRytkD-cpVdgUfM-esuMWmjGs6Vc33N5U-tce5Fywa-9y7PSn3ukBO8KGR" +
"wm7T12~H2gvhgxrVeK2roOzsV7f5dGkvBQRZJ309Vg3j0kjaxWutgI3vli0pzDbSK9d5NR" +
"-GUDtdOb6IIfLiOckBegcv6I-wlSXjYJe8mIoaK45Ok3rEpHwWKVKS2MeuI7AmsAWgkQmW" +
"f8irmZaKc9X910VWSO5GYu6006hSc~r2TL3O7vwtW-Z9Oq~sAam9av1PPVJzAx8A4g~m~1" +
"avtNnncwlChsGo6mZHXqz-QMdMJXXP57f4bx36ZomkvpM-ZLlFAn-a~42KQJAApo4LfEyk" +
"7DPY2aTXL9ArOCNQIQB4f8QLyjvAvu6M3jzCoGo0wVX6oePfdiokGflriYOcD8rL4NbnCP" +
"~MSnVzC8LKyRzQVN1tDYj8~njuFqekls6En8KFJ-qgtL4PiYxbnBQDUPoW6y61m-S9r9e9" +
"y8qWd6~YtdAHAxVlw287~HEp9r7kqI-cjdo1337b7~5dm83KK45g5Nfw==",
"AIrd65mG1FJ~9J-DDSyhryVejJBSIjYOqV3GYmHDWgwLchTwq-bJS7dub3ENk9MZ-C6FIN" +
"gjUFRaLBtfwJnySmNf8pIf1srmgdfqGV2h77ufG5Gs0jggKPmPV~7Z1kTcgsqpL8MyrfXr" +
"Gi86X5ey-T0SZSFc0X1EhaE-47WlyWaGf-~xth6VOR~KG7clOxaOBpks-7WKZNQf7mpQRE" +
"4IsPJyj5p1Rf-MeDbVKbK~52IfXSuUZQ8uZr34KMoy4chjn6e-jBhM4XuaQWhsM~a3Q-zE" +
"pV-ea6t0bQTYfsbG9ch7pJuDPHM64o5mF9FS5-JGr7MOtfP7KDNHiYM2~-uC6BIAbiqBN8" +
"WSLX1mrHVuhiM-hiJ7U4oq~HYB6N~U980sCIW0dgFBbhalzzQhJQSrC1DFDqGfL5-L25mj" +
"ArP8dtvN0JY3LSnbcsm-pT9ttFHCPGomLfaAuP7ohknBoXK0j9e6~splg5sUA9TfLeBfqc" +
"Lr0Sf8b3l~PvmrVkbVcaE8yUqSS6JFdt3pavjyyAQSmSlb2jVNKGPlrov5QLzlbH7G~AUv" +
"IehsbGQX5ptRROtSojN~iYx3WQTOa-JLEC-AL7RbRu6B62p9I0pD0JgbUfCc4C4l9E9W~s" +
"MuaJLAXxh0b2miF7C5bzZHxbt~MtZ7Ho5qpZMitXyoE3icb43B6Y1sbA==",
"ACjb0FkTIQbnEzCZlYXGxekznfJad5uW~F5Mbu~0wtsI1O2veqdr7Mb0N754xdIz7929Ti" +
"1Kz-CxVEAkb3RBbVNcYHLfjy23oQ4BCioDKQaJcdkJqXa~Orm7Ta2tbkhM1Mx05MDrQaVF" +
"gCVXtwTsPSLVK8VwScjPIFLXgQqqZ5osq~WhaMcYe2I2RCQLOx2VzaKbT21MMbtF70a-nK" +
"WovkRUNfJEPeJosFwF2duAD0BHHrPiryK9BPDhyOiyN82ahOi2uim1Nt5yhlP3xo7cLV2p" +
"6kTlR1BNC5pYjtsvetZf6wk-solNUrJWIzcuc18uRDNH5K90GTL6FXPMSulM~E4ATRQfhZ" +
"fkW9xCrBIaIQM49ms2wONsp7fvI07b1r0rt7ZwCFOFit1HSAKl8UpsAYu-EsIO1qAK7vvO" +
"UV~0OuBXkMZEyJT-uIVfbE~xrwPE0zPYE~parSVQgi~yNQBxukUM1smAM5xXVvJu8GjmE-" +
"kJZw1cxaYLGsJjDHDk4HfEsyQVVPZ0V3bQvhB1tg5cCsTH~VNjts4taDTPWfDZmjtVaxxr" +
"PRII4NEDKqEzg3JBevM~yft-RDfMc8RVlm-gCGANrRQORFii7uD3o9~y~4P2tLnO7Fy3m5" +
"rdjRsOsWnCQZzw37mcBoT9rEZPrVpD8pjebJ1~HNc764xIpXDWVt8CbA==",
"AHDZBKiWeaIYQS9R1l70IlRnoplwKTkLP2dLlXmVh1gB33kx65uX8OMb3hdZEO0Bbzxkkx" +
"quqlNn5w166nJO4nPbpEzVfgtY4ClUuv~W4H4CXBr0FcZM1COAkd6rtp6~lUp7cZ8FAkpH" +
"spl95IxlFM-F1HwiPcbmTjRO1AwCal4sH8S5WmJCvBU6jH6pBPo~9B9vAtP7vX1EwsG2Jf" +
"CQXkVkfvbWpSicbsWn77aECedS3HkIMrXrxojp7gAiPgQhX4NR387rcUPFsMHGeUraTUPZ" +
"D7ctk5tpUuYYwRQc5cRKHa4zOq~AQyljx5w5~FByLda--6yCe7qDcILyTygudJ4AHRs1pJ" +
"RU3uuRTHZx0XJQo~cPsoQ2piAOohITX9~yMCimCgv2EIhY3Z-mAgo8qQ4iMbItoE1cl93I" +
"u2YV2n4wMq9laBx0shuKOJqO3rjRnszzCbqMuFAXfc3KgGDEaCpI7049s3i2yIcv4vT9uU" +
"AlrM-dsrdw0JgJiFYl0JXh~TO0IyrcVcLpgZYgRhEvTAdkDNwTs-2GK4tzdPEd34os4a2c" +
"DPL8joh3jhp~eGoRzrpcdRekxENdzheL4w3wD1fJ9W2-leil1FH6EPc3FSL6e~nqbw69gN" +
"bsuXAMQ6CobukJdJEy37uKmEw4v6WPyfYMUUacchv1JoNfkHLpnAWifQ==",
"AGwvKAMJcPAliP-n7F0Rrj0JMRaFGjww~zvBjyzc~SPJrBF831cMqZFRmMHotgA7S5BrH2" +
"6CL8okI2N-7as0F2l7OPx50dFEwSVSjqBjVV6SGRFC8oS-ii1FURMz2SCHSaj6kazAYq4s" +
"DwyqR7vnUrOtPnZujHSU~a02jinyn-QOaHkxRiUp-Oo0jlZiU5xomXgLdkhtuz6725WUDj" +
"3uVlMtIYfeKQsTdasujHe1oQhUmp58jfg5vgZ8g87cY8rn4p9DRwDBBuo6vi5on7T13sGx" +
"tY9wz6HTpwzDhEqpNrj~h4JibElfi0Jo8ZllmNTO1ZCNpUQgASoTtyFLD5rk6cIAMK0R7A" +
"7hjB0aelKM-V7AHkj-Fhrcm8xIgWhKaLn2wKbVNpAkllkiLALyfWJ9dhJ804RWQTMPE-GD" +
"kBMIFOOJ9MhpEN533OBQDwUKcoxMjl0zOMNCLx8IdCE6cLtUDKJXLB0atnDpLkBer6FwXP" +
"81EvKDYhtp1GsbiKvZDt8LSPJQnm2EdA3Pr9fpAisJ5Ocaxlfa6~uQCuqGA9nJ9n6w03u-" +
"ZpSMhSh4zm2s1MqijmaJRc-QNKmN~u1hh3R2hwWNi7FoStMA87sutEBXMdFI8un7StHNSE" +
"iCYwmmW2Nu3djkM-X8gGjSsdrphTU7uOXbwazmguobFGxI0JujYruM5Q==",
"ALFYtPSwEEW3eTO4hLw6PZNlBKoSIseQNBi034gq6FwYEZsJOAo-1VXcvMviKw2MCP9ZkH" +
"lTNBfzc79ms2TU8kXxc7zwUc-l2HJLWh6dj2tIQLR8bbWM7U0iUx4XB1B-FEvdhbjz7dsu" +
"6SBXVhxo2ulrk7Q7vX3kPrePhZZldcNZcS0t65DHYYwL~E~ROjQwOO4Cb~8FgiIUjb8CCN" +
"w5zxJpBaEt7UvZffkVwj-EWTzFy3DIjWIRizxnsI~mUI-VspPE~xlmFX~TwPS9UbwJDpm8" +
"-WzINFcehSzF3y9rzSMX-KbU8m4YZj07itZOiIbWgLeulTUB-UgwEkfJBG0xiSUAspZf2~" +
"t~NthBlpcdrBLADXTJ7Jmkk4MIfysV~JpDB7IVg0v4WcUUwF3sYMmBCdPCwyYf0hTrl2Yb" +
"L6kmm4u97WgQqf0TyzXtVZYwjct4LzZlyH591y6O6AQ4Fydqos9ABInzu-SbXq6S1Hi6vr" +
"aNWU3mcy2myie32EEXtkX7P8eXWY35GCv9ThPEYHG5g1qKOk95ZCTYYwlpgeyaMKsnN3C~" +
"x9TJA8K8T44v7vE6--Nw4Z4zjepwkIOht9iQsA6D6wRUQpeYX8bjIyYDPC7GUHq0WhXR6E" +
"6Ojc9k8V5uh0SZ-rCQX6sccdk3JbyRhjGP4rSKr6MmvxVVsqBjcbpxsg=="
};
public static void main(String args[]) {
ElGamalVerify verify = new ElGamalVerify();
verify.verifySelf();
verify.verifyCompatability();
if (args.length > 0)
verify.generateEncrypted();
}
public ElGamalVerify() {
_context = new I2PAppContext();
}
/** verify that we can decrypt what we encrypt */
private void verifySelf() {
try {
Object keypair[] = _context.keyGenerator().generatePKIKeypair();
PublicKey pub = (PublicKey)keypair[0];
PrivateKey priv = (PrivateKey)keypair[1];
for (int i = 0; i < UNENCRYPTED.length; i++) {
byte orig[] = UNENCRYPTED[i].getBytes();
byte encrypted[] = _context.elGamalEngine().encrypt(orig, pub);
byte decrypted[] = _context.elGamalEngine().decrypt(encrypted, priv);
if (DataHelper.eq(decrypted, orig))
log("OK : verifySelf[" + i + "] passed");
else
log("ERROR: verifySelf[" + i + "] failed");
}
} catch (Exception e) {
log("ERROR: verifySelf blew up: " + e.getMessage());
e.printStackTrace();
}
}
/** verify that we can decrypt what other people encrypt */
private void verifyCompatability() {
verifyDecrypt();
}
private void verifyDecrypt() {
try {
PublicKey pub = new PublicKey();
PrivateKey priv = new PrivateKey();
pub.fromBase64(PUBLIC_KEY);
priv.fromBase64(PRIVATE_KEY);
for (int i = 0; i < ENCRYPTED.length; i++) {
byte enc[] = Base64.decode(ENCRYPTED[i]);
byte decrypted[] = _context.elGamalEngine().decrypt(enc, priv);
if (DataHelper.eq(decrypted, UNENCRYPTED[i].getBytes()))
log("OK : verifyDecrypt[" + i + "] passed");
else
log("ERROR: verifyDecrypt[" + i + "] failed");
}
} catch (Exception e) {
log("ERROR: generateEncrypted blew up: " + e.getMessage());
e.printStackTrace();
}
}
private void generateEncrypted() {
try {
Object keypair[] = _context.keyGenerator().generatePKIKeypair();
PublicKey pub = (PublicKey)keypair[0];
PrivateKey priv = (PrivateKey)keypair[1];
log("PUBLIC : " + pub.toBase64());
log("PRIVATE: " + priv.toBase64());
for (int i = 0; i < UNENCRYPTED.length; i++) {
byte orig[] = UNENCRYPTED[i].getBytes();
byte encrypted[] = _context.elGamalEngine().encrypt(orig, pub);
System.out.println("Encrypted [" + i + "]: ");
String enc = Base64.encode(encrypted);
for (int j = 0; j < enc.length(); j++) {
int remaining = enc.length() - j*70;
if (remaining > 0) {
String cur = enc.substring(j * 70, remaining > 70 ? (j+1)*70 : enc.length());
System.out.println(cur);
}
}
}
} catch (Exception e) {
log("ERROR: generateEncrypted blew up: " + e.getMessage());
e.printStackTrace();
}
}
private void log(String msg) {
System.out.println(msg);
}
}

View File

@ -1,4 +1,240 @@
$Id: history.txt,v 1.82 2004/11/25 16:57:19 jrandom Exp $
$Id: history.txt,v 1.110 2004/12/16 05:21:24 jrandom Exp $
* 2004-12-18 0.4.2.4 released
2004-12-16 jrandom
* Catch another oddball case for a reset connection in the streaming lib.
* Add a dumpprofile.jsp page, called with ?peer=base64OfPeerHash, which
dumps the current state of that peer's profile. Instead of the full
base64, you can pass in however many characters you have and it will
return the first match found.
2004-12-16 jrandom
* Remove the randomized factor in the tunnel rejection by bandwidth -
we now accept the request if we've allocated less than our limit
and reject it if we've allocated more.
* Stick to the standard capacity scale on tunnel rejection, even for
the 10m period.
* Build the time message at the very last possible moment
2004-12-15 jrandom
* Handle hard disconnects more gracefully within the streaming lib, and
log unmonitored events more aggressively.
* If we drop a peer after connection due to clock skew, log it to the
/logs.jsp#connectionlogs with relevent info. In addition, toss it in
the stat 'tcp.disconnectAfterSkew'.
* Fixed the formatting in the skew display
* Added an ERROR message that is fired once after we run out of
routerInfo files (thanks susi!)
* Set the connect timeout equal to the streaming lib's disconnect timeout
if not already specified (the I2PTunnel httpclient already enforces a
60s connect timeout)
* Fix for another connection startup problem in the streaming lib.
* Fix for a stupid error in the probabalistic drop (rand <= P, not > P)
* Adjust the capacity calculations so that tunnel failures alone in the
last 10m will not trigger a 0 capacity rank.
2004-12-14 jrandom
* Periodically send a message along all I2NP connections with the router's
current time, allowing the receiving peer to determine that the clock
has skewed too much, and hence, disconnect. For backwards compatability
reasons, this is being kludged into a DeliveryStatusMessage (ewww). The
next time we have a backwards compatability break, we can put in a proper
message setup for it.
2004-12-14 jrandom
* Reenable the probabalistic drop on the TCP queues to deal with good old
fashioned bandwidth limiting. However, by default the probability is
rigged to reserve 0% of the queue free - meaning we just aggressively
fail messages in the queue if we're transferring too slowly. That
reservation factor can be increased with 'tcp.queueFreeFactor=0.25'
(or whatever) and the drop code can be disabled with the parameter
'tcp.dropProbabalistically=false'.
* Still penalize a peer on tunnel failure, but don't immediately drop
their capacity to 0.
* More aggressively ACK duplicates
* Randomize the timestamper period
* Display the clock skew on the connection logs when a peer sends it.
* Allow the timestamper to fix skews of up to 10 minutes
* Logging
2004-12-13 jrandom
* Added some error checking on the new client send job (thanks duck!)
* Implemented tunnel rejection based on bandwidth usage (rejecting tunnels
proportional to the bytes allocated in existing tunnels vs the bytes
allowed through the bandwidth limiter).
* Enable a new configuration parameter for triggering a tunnel rebuild
(tunnel.maxTunnelFailures), where that is the max allowed test failures
before killing the tunnel (default 0).
* Gather more data that we rank capacity by (now we monitor and balance the
data from 10m/30m/60m/1d instead of just 10m/60m/1d).
* Fix a truncation/type conversion problem on the long term capacity
values (we were ignoring the daily stats outright)
2004-12-11 jrandom
* Fix the missing HTTP timeout, which was caused by the deferred syn used
by default. This, in turn, meant the I2PSocket creation doesn't fail
on .connect, but is unable to transfer any data in any direction. We now
detect that condition for the I2PTunnelHTTPClient and throw up the right
error page.
* Logging
2004-12-11 jrandom
* Use a simpler and less memory intensive job for processing outbound
client messages when the session is in mode=bestEffort. We can
immediately discard the data as soon as its sent the first time,
rather than wait for an ack, since we will never internally resend.
* Reduce some synchronization to avoid a rare deadlock
* Replaced 'localhost' with 127.0.0.1 in the i2ptunnel config, and special
case it within the tunnel controller.
* Script cleanup for building jbigi/jcpuid
* Logging
* 2004-12-08 0.4.2.3 released
2004-12-08 jrandom
* Revised the buffering when reading from the SAM client and writing
to the stream. Also added a thread (sigh) so we don't block the
SAM client from giving us more messages for abnormally long periods
of time.
* Display the router version in the logs on startup (oft requested)
* Fix a race during the closing of a messageOutputStream
2004-12-06 jrandom
* Don't do a 'passive flush' while there are already outbound messages
unacked.
* Show the reseed link if up to 10 peers profiles are active (thanks
dburton!)
2004-12-06 jrandom
* Don't propogate streaming connection failures out to the SAM bridge as
fatal errors.
* Dont barf on repeated I2CP closure.
2004-12-05 jrandom
* Explicitly use "127.0.0.1" to bind the I2CP listener, not the JVM's
getLocalhost call
2004-12-05 jrandom
* Default the I2CP listener to localhost only, unless overridden by
i2cp.tcp.bindAllInterfaces=true (thanks dm!)
* More SAM fixes for things recently broken (whee)
2004-12-05 jrandom
* Fix the recently broken SAM bridge (duh)
* Add a new pair of SAM apps - net.i2p.sam.client.SAMStreamSink and
net.i2p.sam.client.SAMStreamSend, mirroring the streaming lib's
StreamSink and StreamSend apps for transferring files.
* Make the passive flush timer fire more frequently.
2004-12-05 jrandom
* Fixed some links in the console (thanks ugha!) and the javadoc
(thanks dinoman!)
* Fix the stream's passive flush timer (oh, its supposed to work?)
2004-12-03 jrandom
* Toss in a small pool of threads (3) to execute the events queued up with
the SimpleTimer, as we do currently see the occational event
notification spiking up to a second or so.
* Implement a SAM client API in java, useful for event based streaming (or
for testing the SAM bridge)
* Added support to shut down the SAM bridge on OOM (useful if the SAM
bridge is being run outside of the router).
* Include the SAM test code in the sam.jar
* Remove an irrelevent warning message from SAM, which was caused by
perfectly normal operation due to a session being closed.
* Removed some unnecessary synchronization in the streaming lib's
PacketQueue
* More quickly clean up the memory used by the streaming lib by
immediately killing each packet's resend job as soon as it is ACKed (or
cancelled), so that there are no longer any valid pointers to the
(potentially 32KB) packet.
* Fixed the timestamps dumped to stdout when debugging the PacketHandler.
* Drop packets that would expand our inbound window beyond our maximum
buffer size (default 32 messages)
* Always read the ACK/NACK data from the verified packets received, even
if we are going to drop them
* Always adjust the window when there are messages ACKed, though do not
change its size except as before.
* Streamlined some synchronization in the router's I2CP handling
* Streamlined some memory allocation in the SAM bridge
* Default the streaming lib to disconnect on inactivity, rather than send
an empty message.
2004-12-01 jrandom
* Fix for a race in the streaming lib as caused by some odd SAM activity
* 2004-12-01 0.4.2.2 released
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

View File

@ -1,6 +1,23 @@
; TC's hosts.txt guaranteed freshness
; $Id: hosts.txt,v 1.79 2004/11/22 22:58:46 jrandom Exp $
; $Id: hosts.txt,v 1.96 2004/12/14 23:23:16 jrandom Exp $
; changelog:
; (1.119) added piespy.i2p
; (1.118) added sciencebooks.i2p
; (1.117) added forum.fr.i2p, fedo.i2p, and pastebin.i2p
; (1.116) added frosk.i2p
; (1.115) added theland.i2p
; (1.114) added dox.i2p
; (1.113) added amiga.i2p
; (1.112) added frooze.i2p
; (1.111) aliased gott.i2p as jrandom.i2p (ed. note: no, i am not gott)
; (1.110) added sonax.i2p
; (1.109) added 1.fcp.freenet.i2p and copied fcp.i2p to 2.fcp.freenet.i2p
; (1.108) added asciiwhite.i2p
; (1.107) added fcp.i2p
; (1.106) added greenflog.i2p
; (1.105) added bdl.i2p
; (1.104) added bacardi.i2p and guttersnipe.i2p
; (1.103) added evil.i2p
; (1.102) added bsdm.i2p
; (1.101) added eschaton.i2p
; (1.100) added blog.curiosity.i2p
@ -228,4 +245,25 @@ slacker.i2p=BR~D1lQNF~NLjdrJILDBA4DaQZpDoQZhVFNAwBgUz3sEP2tHnuiS7-EO2jwXEPKHIVZN
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
greenflog.i2p=4RdKwi7a5wrn-B1crYUuH8H5xHExl8oIxouASnG5Q-s--Sk2eQr7ajeXCdkxzz0Ep9CKhGY4rARRWvCuNYokkl3Jzpc-17oj-Qrr46kRdjqad2AlcJ0q920nmU3tjoqoE4qNshnP7H7NcephURg2GbHsrmUJm1EqA9SsTPk6~kUvvPiLtpbEAoas9uv0a5oI-Ykpwc2VSuZnk9hZQ25ndeW6pSH68FCbzZZfDVNAT1vcTtKGYoop2rmaGTkRsxT0vCG06S8iAoIBt4kHznZRldnnND5n31Dz8rYB7ICLplN54vlntrcjaL82HGSdxSmPg5crG3bwbvwC-7jspiZNGHFM8gun047A3b5g4YaNmCuzrLGgoW5pMlCWgOK7Wdf8NTQ9yrY1iBRIua2-zZwVEuCaIR8mlRdZ-UW37kad-OabJ18iavapc0iz~CaJBer4zlrJev3H9TfIMlk2WhG~SSDW9Y3dELIoZDfvgkfIcyPrNJThrcxpFZWGREhFuI5oAAAA
fcp.i2p=yY8ahms44MDj2cdI6o7Fc2ozz-jM4ACEp8t4YJiRsKgEYx6XNB7QrNK8jZQoKIQ8G6GMmKMYsgRGmbwWFt60au5HFNv56NWsB4Nd80G9iSRLiEHOHhaUSFnR~ZnSdzL-nbMqA8-vzteaqZ8HNnC2NNjJXsMyULFdkBsIKYS0jsS5hl7ddf0v69X7sCSp1CCy5H28rmZSEtKA4aYXGcBHJ8bXFomV5ovy~Mh2xZzWdeVmjdmJACG~ekDBcMkm7G5mlYoTN2aEBghxiHv~plGlHFqERB3lgISlomYIwYwPqxcmpxwbg9OSEo53t45P9~dat8XMvabzXiUdm0UgVnFsNBAIyNgyTEktw38~5v2XHhqpn8RSoMUeNOqp~k740v9fYH7xxHYCrfN7y3rKg~~GlWekbNmX5g0UvKjV-QWHIvrdOVZmII9qNTTwm3Uf2Tyi2aFll7TF9CvKUJCI0Mt1BpQEmPQdY~FgFXNd0p-~DvlRrwaI4jorbn4eUUEJqgzFAAAA
asciiwhite.i2p=kJJmuP~sE0SrQFX-nyV2cEFDffnqv3D4-vBNJX6wqEq7vbTjQzZVn3jtt5kYlv4FpMyJnhPloYJWZNw-Yt2opdRnxNGpY~AHnI~iW-rmjuwkv0Qp~NG5sKEfTd7b6tAcop87He9FZ~ANNMmPpJbS1CNGGxLtkcceg4El7pJvodCp9uPjtAfMqnsKUYK6HKg~Wlz-8yC1QUxufkoT6PF645BNWp9PUVioCnUIaqizqBmDjTuUNqf9NOXnoQnx2PuqXxHGphKJqLp8faexDSTvLgns~yudyACMexzdnQMdEQJCkUqvwFuyeS2-uw6OFfHfACIc1yr3UdHPPFzsHT0WtwF5rFBpJqeWTiWph~Vas26824PdM-sK-PKo388UemmDJmY1YknGxsky6qhDGYkFHtWXF2D7fuOL~6V1aVw8DpIGW4PS~pjug9wtPoUkcKPd4zey7HNlgnVae4P7hviKlwRPPnxfor9athm1omU75GlIEwUi79GqVPb4N~qrbZOfAAAA
1.fcp.freenet.i2p=r2zbc34IQSzOIF4N0enKf0xXkJKgsj9yTGGspRnstKZf~4UoAljZOW5aFZywGo-NlaXwt~tIyj4NC0Til0vl1D5N9ip7OMYUCajNNgiXEH~FN33yl-AcJbeTlB-FychSmVfYciTQj6yd19~6wICwkdpy6AYo90bAejSVGpvtFeP5P2pnSwPmcB8m79wyq~C2XjQCe5UcBxnfYolWKgr3uDFrgbhqBVCCkO7zTiARwOWZLVOvZsvKZR4WvYAmQI6CQaxnmT5n1FKO6NBb-HOxVw4onERq86Sc6EQ5d48719Yk-73wq1Mxmr7Y2UwmL~FCnY33rT1FJY2KzUENICL1uEuiVmr9N924CT9RbtldOUUcXmM1gaHlPS40-Hz4AvPxFXHynbyySktN3hBLPwfwhyIQw95ezSNuiBB0xPcujazCw02103n2CO-59rMDmWpttLjpLMggP9IwsAPa9FVLnBqfuCn3NrC4fia50RDwfR41AD1GOOWiUT0avYzbbOdsAAAA
2.fcp.freenet.i2p=yY8ahms44MDj2cdI6o7Fc2ozz-jM4ACEp8t4YJiRsKgEYx6XNB7QrNK8jZQoKIQ8G6GMmKMYsgRGmbwWFt60au5HFNv56NWsB4Nd80G9iSRLiEHOHhaUSFnR~ZnSdzL-nbMqA8-vzteaqZ8HNnC2NNjJXsMyULFdkBsIKYS0jsS5hl7ddf0v69X7sCSp1CCy5H28rmZSEtKA4aYXGcBHJ8bXFomV5ovy~Mh2xZzWdeVmjdmJACG~ekDBcMkm7G5mlYoTN2aEBghxiHv~plGlHFqERB3lgISlomYIwYwPqxcmpxwbg9OSEo53t45P9~dat8XMvabzXiUdm0UgVnFsNBAIyNgyTEktw38~5v2XHhqpn8RSoMUeNOqp~k740v9fYH7xxHYCrfN7y3rKg~~GlWekbNmX5g0UvKjV-QWHIvrdOVZmII9qNTTwm3Uf2Tyi2aFll7TF9CvKUJCI0Mt1BpQEmPQdY~FgFXNd0p-~DvlRrwaI4jorbn4eUUEJqgzFAAAA
sonax.i2p=s~FYxHeR1n0aN0kuilNtJU9IbXRX~jWI6vGz4TUf6oZyjZBYYmY9OpgDa3sr-8AERqsuG6EKA~UBn4KiQdkI-LU16DwEHvbgfZXAdwY73G6SkHOgwVAnT2SIOuh90v1QevYtxiGpA7IVm6icoz5V4I6V29hPlUNJxBFF~Qn2cY6zj0d6wBk6ulYc0vbyOwu8Lfc8T8bTnBLut3SZWgKHYLbxabcXJtcQpoN7k815N1MEUi0APD45o5TwPhvpHLt8fF10aNcMScpL01RR~tKHgC6jPX~HKM~Ld4l0z0lvLeq9197Qp6zHRvRD3YUQxyvgHi2ANkYprvJof1Abgs8TD1JWkZUAzlfqFPyjO~f3pJSDd5eM7E0Fk69ym1Y4Rltih~Y2N1IVtJPfo9Tx9a3RQWOJZ0MN12xfiZjyF92gDRgfKW5RDpwCoTVmP79u~WUr8lQ-qQbKhsMQQqMn8Uy8DeXvKLRpHaECtevjohL4lWBBR0ezXlBLCXKqaVXKcMTjAAAA
jrandom.i2p=BxciUZkGQwi6Sj1Ucg5GGagDdwujlj8ClePa4~3d-1nnRJsBhTNJtvs-UuQYY77gPGNlpNk9dt02mxeS7f~pEC4E1KxJH9mhnf0OlIGB4hOOhDlXokAaKE2u1E-vVCDJlZCq32r~Ne53w-L2m5h5FAq5Bx7NXrTzWAEPgAlC8A2wASJWIF-EKOX5kfkkYoF6sKZqam5CxAAAMAUwmnbD--7wo8d2mz0C-c~oE7hzuHsg2J8yME5Zd~-FOZUxkoNCBJdfrVlVRn9~6685zqpaotL-SIqFK6~28ZCNMNCtnZcZZLeG2gZxq2pe8HtgzDrMMSx45vs3JLUp8Bap6~D9oFX0o7k2DQm-5R9D5NWhsJgol5Ny~8ABTXBpW~WQQOuKxE5xJauXM5rQia2yyXgldfOyGjxYnkrTGu6LaxlLmhyAva3ZbOO0mtLvrKLZebLKCYUxP7~TtNmSWEAzPKFYbjdZ~NjE0q4TanLFBaWotefPmy3IuAc8Mr7PbCvi4GmdAAAA
frooze.i2p=kyjnpna6HHapU~tLQXRCHrX6Dpb2YCMEtFTloKF298480axGCZFY-WtpjW2Mo1ci5FlE7dkmEQ1PU0GfHZ6s0O~Hert5AcZpKh5nxRIMtS7uc~oErYCiX0yQp2P9D47B9VU5D2UmjYKWovqbrbKEAS1F0NDxXh65Ppnv-oBHVksLp7IrKLU3IOesdYtGcc9cSYJB6~mdbtqXxgHNNLTkyouSJKgSlJoe-tk1T3eYEsOJ6vzpeS6pNa6Px2dknejT~aVeNKLhjsXy706g22Mbl1-zCL87OE1bL2qWQbQN43qpLwCSv0LbRovO3r6iXRWWWrKhYj5nti~EQEWapJ2OL26~ElrpD9V9gbUVLZoxeYuu~-GKKwg6CvZYKtdf6YBYTZ0Ef4PUcv9faSqp7S4emcGOLQnSW9LezXhGhn0OPaAO~85nhdY~OfXDZ2Dg2tYYF-D70SzKycfHFLgKBQ9fkCxpOP0MHhhgGmKHabPvDHyQRFppcKLN6AYmUH1Lke-jAAAA
amiga.i2p=-lqgoqA5KcJCuGilYz~cOBK3X7MI5VFBK7UwpSEwqbiQgSe340wABmE8V-tX7xH7rjc4Z2rGxqas5NKuOL3pCJozeKF4Z61d05nCemSXbLrqZvJUkGuZJfUz9lzeZ5OGO8L32o-I6DIGIUgFk6zRpiRW0lqO4lzV~fbiHacDhUXPLBM5gYjvZ319MLoVPp-YWG-STJFhjmAnzvj~8h-jJXHowM72YsWPo-B~oD1YtcAcXkpxVJOaLlqQq2yfjJv7YgIde3tqQ6drH7xmHdG~nF~dsu2dacqqt~HjDiUnPd12vJEhR18DDkVJVhDUbnUCcI83bt5RDdsMK8mSKRTie3h1sUam~1fLr8~gnEGTFp-yivGV1JkeleElR-rTJC7PsrlPCWzW2N4l1IMzRGOedJNAyqeXbkfjHtDBd58iZPugS5BZZ~u5MGqXgfu9TYFnLnTIGuMc3a2wSpe3miMvf2PKFnM~1nzStN0zEjFwhn4tZhOWA1TfTH5HMutqrxGxAAAA
dox.i2p=ygf2mdGRnU12BNXXF-26OFhfPpAMigwasf93JEHVYEICVmz6Fk2BBKiGBWL4YNFUtcFo7y1iG3ms8BdmTBBkgCgEasuFZNzphEMgcNYi0eYAg1QLNgmbr5HEDpBNuXIV7fOpxYn6iij76oA09e5opPNdOLUk80kWrgrlJTel61n90uzEzz46fWIbFEw5K8xR-8aqLgC2sJDzGj8vEBY2NDKmCtjRK7aNBUayh~8vFfJyo5oN0gw8b5cOCrfSoxzqdpzI5CJ7070GjznWbWh~pueKWF4CpHn92ll3jvyd86KvUwdQh1qr1B-8fk6Oh~M9l5HRzqdUsSzp8SiG7WvLQoXrNdVX2Sjw22xOZDr7kOfEHZWYUdmrcYyqNLuIT2vgMFXdvCZnLhhGSaihkzuFqZv-34oGSqGDVlCanAPC4v6492IRRK7RiYnUo-JPhuWgpg9CdFTZtULa~drZbWNCZvAekn44D42wDx5V5vAJxuRoyir1s4gAnXHL7n9r7xdiAAAA
theland.i2p=Lf52G95rF1kjlRu29NMpPQ46Ivd2Nz-sdIhvx5D4qU3Y3MPs50GNTjg81yJmUgbg95Q90Eyf~SHsgbzdlnjr-qQEZOXIgLQErq2UhUFGwUX6muqkCYYDIAZ-f7EI-yJiU-3~KDwWu77z-8XDjaJIf78tNV6kN3MvRwUSbBRLD7pAa3HtdhNnNyO-PPBJJVMxorQ-~AgEhmdgLj39WfgetETiRm4ew0NsN6IOHWn~~vh26FJ4awhLpJ0fCBD-fPve~jTbCovjsszKMBumTslvQS~p-dfjfQSTMRZAE3fs262NjdEwoVZP-k~~0HmzOmV9M-Oe8yez6ORDKm8sDyeDoQ~e21b77SiE2tNrgHeB3siITcZoTNyICX~ZSG~Qnb4k4kXdllVkqmXaf7k0pYE1yTKXM3sKphP~VJFMBgdSzZPprhp6RGjWibKy~94Ns8IyCA6c08810yodEdoeU53acQPp~wk0FP1HgcF3tVggk-wHg65cD09ncjOmly5Gc7uZAAAA
frosk.i2p=Dez-OBQLhsI7Wq~O8YmYYJjl~IPvqUCZbLIFVMnyyqOCG9TEO~kGUVpWVD5nF0B5fE8kEWq8CuGKeYbVfqt0Vuz6Uvvo4gCdGnthohthbolIOZ9ZCci9MTNE6t29mJ2thHqc9Nw5TvYJGcvCVNi569~o5PDtYhZjVsOtrv8r3f9fUcUV-wKokD-y0fdF3Dzw4I7tJ6pfohj4avzDwwndAHXnNjB9mDgQsfjPF2KtvWLzXyAsCmfCoanV65tkc09ZX4x5QkekdVgTbD0BcHDP-o71MVufRsm4~ojRkbdSLw-VlbEcYFyeHTPBTpM68gTjueJlRwmvpXyP46w~Xnl9IEDfWu92CV9xjRt8qDxiyQ9jXR6dbWLptTF2UCznkBWvWph7P2wliTiawt~1bWGw2YvkfB6EaENYvDNY2cGn0GxpPhL4K19slEwJqX9f~EzLKYx4xaB5I8~W9syJAJSX8PsJbUlk6OhbbRoYOndd6OCRqHRUyh1w7dM~LvbY0W8hAAAA
forum.fr.i2p=glvyM8eKBTYnlwEl3hVWDRBvfIfe-utzjGAlvDM-rwB4tb4CaM49Aeb5k0lxm7IaFjkp0u9t~1a2KIxwrU~Mj-JTJPKf0TrElVfOT6P0sVFOD3U1wllFwne5e7~0d8MeuylMrBe3dlz5KSPQ69stpDTbJgxcynoKiPMWcbmq~yQjnIvtDIpAPaB1JVYNo8mDhqMqg8GLrk9ttTHO90dYUpdI5lgxB6vR1nuPPwyFczOjVn8GvmT1DXGNoc6yAW0gCLuk7Hq-jNcNtXkNRC5TTdNiBLdLpsNMD3kKWDM04fODM5N~qp-nww-lEqfuZ3f1RT3gpjDwpMMAtKbd~VsRaCEINL5iBaAlZupW6fYTiuGUUdOZ739JK9GpQDsPXBOy~VSoM~cLXJCih12v-GDODDN0i8sSkqZ7CXDGg~jkg0ab2Y-Y2JnpYFLsgYziPtNRij1~rMCcC~-huiSfQio15gxczb~RRS-lpBQgyfaZ5JgIR2uVjWWe-uwKD-WquuIPAAAA
fedo.i2p=pklxM9~hpluoCoiPgzMBpTHXMpwEdCMMNEatVc4gnQDOXsKkW5FbUMQ9y0vj6EUs2vUZLkwp3FP3-PuJEHMCLCTBdkVlE5JErwhZU6V3S5lOZ2jTrSuYAhcph~ML~ntjDzc-IZWgAax98ChzCpwxhEk4eP4B9vEhMG7njoxbJLfwAYu1qgq0A-Whm08rAhAcDwl8a-21Kqj8~iGq0XytMqyJU5XArq4aArWEwZFFLKJD9uR9bKeMaHIkJLNAyKIvfpgwRjYIMlIf6ekN3cpBU3B6c8NgDNPNqNBI6DQ1XGfKOEqX6d31mTOSzyjs-zgIJkt5zg-MH1pnMSVAI-vQbiAaPJc3Rjeqarv5Z89ZX~dghQFNQX1Wd7ezJy0WltaY6V7lLxd6QPTosDCDawT7ZoNhmLUNtFXI7oGS4Cm4ZTZAjZV-ZFMUXWUKFTZGyh4s2WbYqJq1Sackk8JmCvZ7JHazJ9ghnrQQ2bpzWeeGgtCPIcTnaUMPOZdsU96ehme7AAAA
pastebin.i2p=mUDz9K6KmWe2zE4wj~YjqwD5f8pCjEbze-DuafzjtXOaj9SnRBrLNqgOTt063y9foZett484g9PFm~3ibqFZfUk3LsJi6YhZje-V~RZndBElRJ1cX~MBOG5wdHA2BYpBt7jX-N5J9ww7POtcPFDjyYlJLhRrY5FuRfxdsWJ4BOUHOwvbTq7IWvqHReayte6vKavpyvNcAotcFHAhOGpR6Ua3RncS~b6NA5k2CQIeS4mR6~iNCh0sx1gj4cWWWqXSa6pN19io82L2fcNEcxE-UETj4HkG5fTdBlB4I2cNAm1KnxQw9ntRf19p5EzYkOFYST5ra~RWmy0bWzJMBFlK9QcogVi97gmszuNPyfIpJIE1ssZ6BNHKMkPJ-fjxtSAseTBV-Fa51TIBwNyXC6VxMsixKIBX4Cg64oipoGpm~-7SdRgabHbB0vWwiV6RYEQ88oNcV2ycDqjyfQymT0T5S9b0aVd2Ey1ZWly-PR44~uC3mCctcMqfZVfNFs3NoY3ZAAAA
sciencebooks.i2p=Rrqf6EmeuqckygNrim-ZcZ97uEIL9Ykkr8cj1RSGbeih33~UUpjAp0x2fxrpbibXwuGufWMNoaMkZH8FxOxZr1hvBeV4Mvjrn05kKWyPpBB1x7rAcVCCoD~bl6CUU2k4NtFrO9~BCCZdoIvMxXYWRy4nj18RVaIlOS3qE6~F04pU7Fy0sj7xtd02wgeyKkJ6pXYGfETMr6phVusHSSQfCumDBpnqEt7EcXTIsPz58y9Zim4u1wp6xj~OvGWFIP8fKIxpH~zf6o0qYytXrf6SJbIEvwITKGIXZTM5KxQ0LbEydSgPn4q5PttQlKg~7hxuGG5RYqP2IMg-f9z4-1SfYTbz~EixEq8gv0R30c60tqsyAaw9o37R3k9inkT5WylYwBKM0pzfcAVjejcoPYnFF1iithGl0AYkP~~isxCErCNwLs39NyE688C5amDIpXl7AgB~IvbwZfONbd2cwMOB2RFzTShYZlwcILB7EzgMvEt2l8GBkstP3CzNrjQ2gAL0AAAA
piespy.i2p=jTCW8w04d~Sucb7OXFBZtq7tEOxeFBNm5T4uVZW3xNapThxk0Tie8OAIQMmz4lzDUaHoc97DN1s6k3rTprRrhbr7Idyls5I-r6jAQnrljqUveIMA-lbzXDYTgo8TqBR4~fpSL4RM5u4~sM4ZRKdwufnEjCSlYtTe8qmrofFx3cIuIKhfypsYcu-BEdrw7P~cRpmqJqBV~igulwfIxeABa8ygX~Uk~i68NELte55J70z~kijYGMAgUJVhtQxD~jZD5l3FrGkND9lVaam0lHseujNyy~ZB-ma5IKqJWKLEo19e224EVhIK4jcKYdnr7bE86mZiWg36GvWFhjJOkEvCJ3BWxaMjcDXyk-9vCY7MA403OCMGGHoKlzB2AKq1PEc1teMtoqMG~a42DTKevUbbiALtauxCe-BOxEtxpYTvvql-RVumrUh3hckh0wqAd1Gqr2uqRx4VaCF3X3duOxdoKV6Kgf~icfVIFq5HR7AhN63BGFhAeQLcudp3oA828a06AAAA

26
install.txt Normal file
View File

@ -0,0 +1,26 @@
$Id$
I2P source installation instructions
To build and install I2P from source, you must first build
and package up the appropriate installer by running:
ant dist
This will produce a few key files:
* i2p.tar.bz2: the headless installation
* install.jar: the GUI installer
* i2pupdate.zip: the update package
From there, you can follow the headless installation instructions
with the headless installer, run the GUI installer, or deploy
the update into an existing installation.
You will need to have ant installed from http://ant.apache.org/
(1.5 or newer)
Supported JVMs:
Windows: Latest available from http://java.sun.com/ (1.3+ supported)
Linux: Latest available from http://java.sun.com/ (1.3+ supported)
FreeBSD: /usr/ports/java/linux-sun-jdk1.4
various: http://www.kaffe.org/ using CVS HEAD as of Sept 1, 2004
(or any subsequent releases)

View File

@ -4,7 +4,7 @@
<info>
<appname>i2p</appname>
<appversion>0.4.2</appversion>
<appversion>0.4.2.4</appversion>
<authors>
<author name="I2P" email="support@i2p.net"/>
</authors>

View File

@ -5,7 +5,7 @@ tunnel.0.type=httpclient
tunnel.0.interface=127.0.0.1
tunnel.0.listenPort=4444
tunnel.0.proxyList=squid.i2p,www1.squid.i2p
tunnel.0.i2cpHost=localhost
tunnel.0.i2cpHost=127.0.0.1
tunnel.0.i2cpPort=7654
tunnel.0.option.tunnels.depthInbound=2
tunnel.0.option.tunnels.numInbound=2
@ -19,7 +19,7 @@ tunnel.1.type=client
tunnel.1.interface=127.0.0.1
tunnel.1.listenPort=6668
tunnel.1.targetDestination=irc.duck.i2p,irc.baffled.i2p
tunnel.1.i2cpHost=localhost
tunnel.1.i2cpHost=127.0.0.1
tunnel.1.i2cpPort=7654
tunnel.1.option.tunnels.depthInbound=2
tunnel.1.option.tunnels.numInbound=2
@ -33,7 +33,7 @@ tunnel.2.type=client
tunnel.2.interface=127.0.0.1
tunnel.2.listenPort=2401
tunnel.2.targetDestination=cvs.i2p
tunnel.2.i2cpHost=localhost
tunnel.2.i2cpHost=127.0.0.1
tunnel.2.i2cpPort=7654
tunnel.2.option.tunnels.depthInbound=2
tunnel.2.option.tunnels.numInbound=2
@ -43,10 +43,10 @@ tunnel.2.startOnLoad=false
tunnel.3.name=eepsite
tunnel.3.description=My eepsite
tunnel.3.type=server
tunnel.3.targetHost=localhost
tunnel.3.targetHost=127.0.0.1
tunnel.3.targetPort=7658
tunnel.3.privKeyFile=eepsite/eepPriv.dat
tunnel.3.i2cpHost=localhost
tunnel.3.i2cpHost=127.0.0.1
tunnel.3.i2cpPort=7654
tunnel.3.option.tunnels.depthInbound=2
tunnel.3.option.tunnels.numInbound=2
@ -54,7 +54,7 @@ tunnel.3.startOnLoad=true
# postman's SMTP server - see www.postman.i2p
tunnel.4.description=smtp server
tunnel.4.i2cpHost=localhost
tunnel.4.i2cpHost=127.0.0.1
tunnel.4.i2cpPort=7654
tunnel.4.interface=127.0.0.1
tunnel.4.listenPort=7659
@ -69,7 +69,7 @@ tunnel.4.type=client
# postman's POP3 server - see www.postman.i2p
tunnel.5.name=pop3.postman.i2p
tunnel.5.description=pop3 server
tunnel.5.i2cpHost=localhost
tunnel.5.i2cpHost=127.0.0.1
tunnel.5.i2cpPort=7654
tunnel.5.interface=127.0.0.1
tunnel.5.listenPort=7660
@ -78,4 +78,4 @@ tunnel.5.option.tunnels.numInbound=2
tunnel.5.option.i2p.streaming.connectDelay=1000
tunnel.5.startOnLoad=false
tunnel.5.targetDestination=pop.postman.i2p
tunnel.5.type=client
tunnel.5.type=client

View File

@ -103,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

View File

@ -8,8 +8,12 @@ package net.i2p.router;
*
*/
//import net.i2p.router.message.ProcessOutboundClientMessageJob;
import java.util.Properties;
import net.i2p.client.I2PClient;
import net.i2p.router.message.OutboundClientMessageJob;
import net.i2p.router.message.OutboundClientMessageOneShotJob;
import net.i2p.util.Log;
/**
@ -55,7 +59,22 @@ public class ClientMessagePool {
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Adding message for remote delivery");
_context.jobQueue().addJob(new OutboundClientMessageJob(_context, msg));
if (isGuaranteed(msg))
_context.jobQueue().addJob(new OutboundClientMessageJob(_context, msg));
else
_context.jobQueue().addJob(new OutboundClientMessageOneShotJob(_context, msg));
}
}
private boolean isGuaranteed(ClientMessage msg) {
Properties opts = null;
if (msg.getSenderConfig() != null)
opts = msg.getSenderConfig().getOptions();
if (opts != null) {
String val = opts.getProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT);
return val.equals(I2PClient.PROP_RELIABILITY_GUARANTEED);
} else {
return false;
}
}
}

View File

@ -36,7 +36,6 @@ import net.i2p.data.i2np.TunnelMessage;
import net.i2p.router.message.GarlicMessageHandler;
import net.i2p.router.message.TunnelMessageHandler;
import net.i2p.router.startup.StartupJob;
import net.i2p.router.startup.VerifyClasspath;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.util.FileUtil;
@ -793,8 +792,10 @@ public class Router {
}
public static void main(String args[]) {
System.out.println("Starting I2P " + RouterVersion.VERSION + "-" + RouterVersion.BUILD);
System.out.println(RouterVersion.ID);
installUpdates();
verifyClasspath();
verifyWrapperConfig();
Router r = new Router();
r.runRouter();
}
@ -820,10 +821,11 @@ public class Router {
}
}
private static void verifyClasspath() {
boolean updated = VerifyClasspath.updateClasspath();
if (updated) {
System.out.println("INFO: Classpath updated, but the service wrapper requires you to manually restart");
private static void verifyWrapperConfig() {
File cfgUpdated = new File("wrapper.config.updated");
if (cfgUpdated.exists()) {
cfgUpdated.delete();
System.out.println("INFO: Wrapper config updated, but the service wrapper requires you to manually restart");
System.out.println("INFO: Shutting down the router - please rerun it!");
System.exit(EXIT_HARD);
}

View File

@ -30,6 +30,8 @@ class RouterThrottleImpl implements RouterThrottle {
private static int THROTTLE_EVENT_LIMIT = 300;
private static final String PROP_MAX_TUNNELS = "router.maxParticipatingTunnels";
private static final String PROP_DEFAULT_KBPS_THROTTLE = "router.defaultKBpsThrottle";
private static final String PROP_BANDWIDTH_SHARE_PERCENTAGE = "router.sharePercentage";
public RouterThrottleImpl(RouterContext context) {
_context = context;
@ -43,6 +45,7 @@ class RouterThrottleImpl implements RouterThrottle {
_context.statManager().createRateStat("router.throttleTunnelMaxExceeded", "How many tunnels we are participating in when we refuse one due to excees?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("router.throttleTunnelProbTooFast", "How many tunnels beyond the previous 1h average are we participating in when we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("router.throttleTunnelProbTestSlow", "How slow are our tunnel tests when our average exceeds the old average and we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("router.throttleTunnelBandwidthExceeded", "How much bandwidth is allocated when we refuse due to bandwidth allocation?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
}
public boolean acceptNetworkMessage() {
@ -207,9 +210,12 @@ class RouterThrottleImpl implements RouterThrottle {
}
}
if (!allowTunnel(bytesAllocated, numTunnels)) {
_context.statManager().addRateData("router.throttleTunnelBandwidthExceeded", (long)bytesAllocated, 0);
return false;
}
_context.statManager().addRateData("tunnel.bytesAllocatedAtAccept", (long)bytesAllocated, msg.getTunnelDurationSeconds()*1000);
// todo: um, throttle (include bw usage of the netDb, our own tunnels, the clients,
// and check to see that they are less than the bandwidth limits
if (_log.shouldLog(Log.DEBUG))
_log.debug("Accepting a new tunnel request (now allocating " + bytesAllocated + " bytes across " + numTunnels
@ -217,6 +223,99 @@ class RouterThrottleImpl implements RouterThrottle {
return true;
}
/**
* with bytesAllocated already accounted for across the numTunnels existing
* tunnels we have agreed to, can we handle another tunnel with our existing
* bandwidth?
*
*/
private boolean allowTunnel(double bytesAllocated, int numTunnels) {
long bytesAllowed = getBytesAllowed();
bytesAllowed *= getSharePercentage();
double bytesPerTunnel = (numTunnels > 0 ? bytesAllocated / numTunnels : 0);
double toAllocate = (numTunnels > 0 ? bytesPerTunnel * (numTunnels + 1) : 0);
double pctFull = toAllocate / bytesAllowed;
double allocatedKBps = toAllocate / (10 * 60 * 1024);
if (pctFull < 1.0) { // (_context.random().nextInt(100) > 100 * pctFull) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Probabalistically allowing the tunnel w/ " + pctFull + " of our " + bytesAllowed
+ "bytes/" + allocatedKBps + "KBps allocated through " + numTunnels + " tunnels");
return true;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Rejecting the tunnel w/ " + pctFull + " of our " + bytesAllowed
+ "bytes allowed (" + toAllocate + "bytes / " + allocatedKBps
+ "KBps) through " + numTunnels + " tunnels");
return false;
}
}
/**
* What fraction of the bandwidth specified in our bandwidth limits should
* we allow to be consumed by participating tunnels?
*
*/
private double getSharePercentage() {
String pct = _context.getProperty(PROP_BANDWIDTH_SHARE_PERCENTAGE, "0.8");
if (pct != null) {
try {
return Double.parseDouble(pct);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.INFO))
_log.info("Unable to get the share percentage");
}
}
return 0.8;
}
/**
* BytesPerSecond that we can pass along data
*/
private long getBytesAllowed() {
String kbpsOutStr = _context.getProperty("i2np.bandwidth.outboundKBytesPerSecond");
long kbpsOut = -1;
if (kbpsOutStr != null) {
try {
kbpsOut = Integer.parseInt(kbpsOutStr);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.INFO))
_log.info("Unable to get the bytes allowed (outbound)");
}
}
String kbpsInStr = _context.getProperty("i2np.bandwidth.inboundKBytesPerSecond");
long kbpsIn = -1;
if (kbpsInStr != null) {
try {
kbpsIn = Integer.parseInt(kbpsInStr);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.INFO))
_log.info("Unable to get the bytes allowed (inbound)");
}
}
// whats our choke?
long kbps = (kbpsOut > kbpsIn ? kbpsIn : kbpsOut);
if (kbps <= 0) {
try {
kbps = Integer.parseInt(_context.getProperty(PROP_DEFAULT_KBPS_THROTTLE, "64")); // absurd
} catch (NumberFormatException nfe) {
kbps = 64;
}
}
return kbps
* 60 // per minute
* 10 // per 10 minute period
* 1024; // bytes;
}
/** dont ever probabalistically throttle tunnels if we have less than this many */
private int getMinThrottleTunnels() {
try {

View File

@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
*
*/
public class RouterVersion {
public final static String ID = "$Revision: 1.87 $ $Date: 2004/11/25 16:57:20 $";
public final static String VERSION = "0.4.2";
public final static String ID = "$Revision: 1.115 $ $Date: 2004/12/16 05:21:23 $";
public final static String VERSION = "0.4.2.4";
public final static long BUILD = 0;
public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION);

View File

@ -103,6 +103,7 @@ public class StatisticsManager implements Service {
includeThroughput(stats);
includeRate("transport.sendProcessingTime", stats, new long[] { 60*60*1000 });
includeRate("tcp.probabalisticDropQueueSize", stats, new long[] { 60*1000l, 60*60*1000l });
//includeRate("tcp.queueSize", stats);
//includeRate("jobQueue.jobLag", stats, new long[] { 60*1000, 60*60*1000 });
//includeRate("jobQueue.jobRun", stats, new long[] { 60*1000, 60*60*1000 });
@ -143,6 +144,8 @@ public class StatisticsManager implements Service {
//includeRate("transport.receiveMessageMedium", stats, new long[] { 5*60*1000, 60*60*1000 });
//includeRate("transport.receiveMessageLarge", stats, new long[] { 5*60*1000, 60*60*1000 });
includeRate("client.sendAckTime", stats, new long[] { 60*60*1000 }, true);
includeRate("stream.con.sendDuplicateSize", stats, new long[] { 60*60*1000 });
includeRate("stream.con.receiveDuplicateSize", stats, new long[] { 60*60*1000 });
//includeRate("client.sendsPerFailure", stats, new long[] { 60*60*1000, 24*60*60*1000l }, true);
//includeRate("client.timeoutCongestionTunnel", stats, new long[] { 60*60*1000, 24*60*60*1000l }, true);
//includeRate("client.timeoutCongestionMessage", stats, new long[] { 60*60*1000, 24*60*60*1000l }, true);

View File

@ -55,7 +55,7 @@ public class ClientConnectionRunner {
/** user's config */
private SessionConfig _config;
/** static mapping of MessageId to Payload, storing messages for retrieval */
private static Map _messages;
private Map _messages;
/** lease set request state, or null if there is no request pending on at the moment */
private LeaseRequestState _leaseRequest;
/** currently allocated leaseSet, or null if none is allocated */
@ -227,7 +227,7 @@ public class ClientConnectionRunner {
}
void disconnectClient(String reason) {
_log.error("Disconnecting the client: " + reason, new Exception("Disconnecting!"));
_log.error("Disconnecting the client: " + reason);
DisconnectMessage msg = new DisconnectMessage();
msg.setReason(reason);
try {

View File

@ -9,6 +9,7 @@ package net.i2p.router.client;
*/
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
@ -28,23 +29,26 @@ public class ClientListenerRunner implements Runnable {
private ClientManager _manager;
private ServerSocket _socket;
private int _port;
private boolean _bindAllInterfaces;
private boolean _running;
private long _nextFailDelay = 1000;
public static final String BIND_ALL_INTERFACES = "i2cp.tcp.bindAllInterfaces";
public ClientListenerRunner(RouterContext context, ClientManager manager, int port) {
_context = context;
_log = _context.logManager().getLog(ClientListenerRunner.class);
_manager = manager;
_port = port;
_running = false;
String val = context.getProperty(BIND_ALL_INTERFACES, "False");
_bindAllInterfaces = Boolean.valueOf(val).booleanValue();
}
public void setPort(int port) { _port = port; }
public int getPort() { return _port; }
/** max time to bind */
private final static int MAX_FAIL_DELAY = 5*60*1000;
/**
* Start up the socket listener, listens for connections, and
* fires those connections off via {@link #runConnection runConnection}.
@ -55,10 +59,14 @@ public class ClientListenerRunner implements Runnable {
public void runServer() {
_running = true;
int curDelay = 0;
while ( (_running) && (curDelay < MAX_FAIL_DELAY) ) {
while (_running) {
try {
_log.info("Starting up listening for connections on port " + _port);
_socket = new ServerSocket(_port);
if (_bindAllInterfaces)
_socket = new ServerSocket(_port);
else
_socket = new ServerSocket(_port, 0, InetAddress.getByName("127.0.0.1"));
curDelay = 0;
while (_running) {
try {
@ -85,7 +93,7 @@ public class ClientListenerRunner implements Runnable {
if (_context.router().isAlive())
_log.error("Error listening on port " + _port, ioe);
}
if (_socket != null) {
try { _socket.close(); } catch (IOException ioe) {}
_socket = null;

View File

@ -145,20 +145,21 @@ public class ClientManager {
if (runner != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Message " + msgId + " is targeting a local destination. distribute it as such");
runner.receiveMessage(toDest, fromDest, payload);
if (fromDest != null) {
ClientConnectionRunner sender = getRunner(fromDest);
if (sender != null) {
sender.updateMessageDeliveryStatus(msgId, true);
} else {
_log.log(Log.CRIT, "Um, wtf, we're sending a local message, but we can't find who sent it?", new Exception("wtf"));
}
ClientConnectionRunner sender = getRunner(fromDest);
if (sender == null) {
// sender went away
return;
}
_context.jobQueue().addJob(new DistributeLocal(toDest, runner, sender, fromDest, payload, msgId));
} else {
// remote. w00t
if (_log.shouldLog(Log.DEBUG))
_log.debug("Message " + msgId + " is targeting a REMOTE destination! Added to the client message pool");
runner = getRunner(fromDest);
if (runner == null) {
// sender went away
return;
}
ClientMessage msg = new ClientMessage();
msg.setDestination(toDest);
msg.setPayload(payload);
@ -170,6 +171,32 @@ public class ClientManager {
}
}
private class DistributeLocal extends JobImpl {
private Destination _toDest;
private ClientConnectionRunner _to;
private ClientConnectionRunner _from;
private Destination _fromDest;
private Payload _payload;
private MessageId _msgId;
public DistributeLocal(Destination toDest, ClientConnectionRunner to, ClientConnectionRunner from, Destination fromDest, Payload payload, MessageId id) {
super(_context);
_toDest = toDest;
_to = to;
_from = from;
_fromDest = fromDest;
_payload = payload;
_msgId = id;
}
public String getName() { return "Distribute local message"; }
public void runJob() {
_to.receiveMessage(_toDest, _fromDest, _payload);
if (_from != null) {
_from.updateMessageDeliveryStatus(_msgId, true);
}
}
}
/**
* Request that a particular client authorize the Leases contained in the

View File

@ -25,8 +25,6 @@ class ClientWriterRunner implements Runnable {
private static final long MAX_WAIT = 5*1000;
/** notify this lock when there are messages to write */
private Object _activityLock = new Object();
/** lock on this when updating the class level data structs */
private Object _dataLock = new Object();
@ -47,9 +45,7 @@ class ClientWriterRunner implements Runnable {
synchronized (_dataLock) {
_messagesToWrite.add(msg);
_messagesToWriteTimes.add(new Long(_context.clock().now()));
}
synchronized (_activityLock) {
_activityLock.notifyAll();
_dataLock.notifyAll();
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("["+_id+"] addMessage completed for " + msg.getClass().getName());
@ -60,8 +56,8 @@ class ClientWriterRunner implements Runnable {
*
*/
public void stopWriting() {
synchronized (_activityLock) {
_activityLock.notifyAll();
synchronized (_dataLock) {
_dataLock.notifyAll();
}
}
public void run() {
@ -70,6 +66,9 @@ class ClientWriterRunner implements Runnable {
List messageTimes = null;
synchronized (_dataLock) {
if (_messagesToWrite.size() <= 0)
try { _dataLock.wait(); } catch (InterruptedException ie) {}
if (_messagesToWrite.size() > 0) {
messages = new ArrayList(_messagesToWrite.size());
messageTimes = new ArrayList(_messagesToWriteTimes.size());
@ -80,19 +79,13 @@ class ClientWriterRunner implements Runnable {
}
}
if (messages == null) {
try {
synchronized (_activityLock) {
_activityLock.wait();
}
} catch (InterruptedException ie) {
if (_log.shouldLog(Log.WARN))
_log.warn("Interrupted while waiting for activity", ie);
}
} else {
if (messages != null) {
for (int i = 0; i < messages.size(); i++) {
I2CPMessage msg = (I2CPMessage)messages.get(i);
Long when = (Long)messageTimes.get(i);
if (_log.shouldLog(Log.DEBUG))
_log.debug("["+_id+"] writeMessage before writing "
+ msg.getClass().getName());
_runner.writeMessage(msg);
if (_log.shouldLog(Log.DEBUG))
_log.debug("["+_id+"] writeMessage time since addMessage(): "

View File

@ -0,0 +1,490 @@
package net.i2p.router.message;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import net.i2p.data.Certificate;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionTag;
import net.i2p.data.SessionKey;
import net.i2p.data.TunnelId;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2np.DataMessage;
import net.i2p.data.i2np.DeliveryStatusMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.GarlicMessage;
import net.i2p.data.i2np.DeliveryInstructions;
import net.i2p.router.message.PayloadGarlicConfig;
import net.i2p.router.ClientMessage;
import net.i2p.router.JobImpl;
import net.i2p.router.ReplyJob;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelSelectionCriteria;
import net.i2p.router.MessageSelector;
import net.i2p.util.Log;
/**
* Send a client message out a random outbound tunnel and into a random inbound
* tunnel on the target leaseSet. This also bundles the sender's leaseSet and
* a DeliveryStatusMessage (for ACKing any sessionTags used in the garlic).
*
*/
public class OutboundClientMessageOneShotJob extends JobImpl {
private Log _log;
private long _overallExpiration;
private boolean _shouldBundle;
private ClientMessage _clientMessage;
private MessageId _clientMessageId;
private int _clientMessageSize;
private Destination _from;
private Destination _to;
/** target destination's leaseSet, if known */
private LeaseSet _leaseSet;
/** Actual lease the message is being routed through */
private Lease _lease;
private PayloadGarlicConfig _clove;
private long _cloveId;
private long _start;
private boolean _finished;
/**
* final timeout (in milliseconds) that the outbound message will fail in.
* This can be overridden in the router.config or the client's session config
* (the client's session config takes precedence)
*/
public final static String OVERALL_TIMEOUT_MS_PARAM = "clientMessageTimeout";
private final static long OVERALL_TIMEOUT_MS_DEFAULT = 60*1000;
/** priority of messages, that might get honored some day... */
private final static int SEND_PRIORITY = 500;
/**
* If the client's config specifies shouldBundleReplyInfo=true, messages sent from
* that client to any peers will probabalistically include the sending destination's
* current LeaseSet (allowing the recipient to reply without having to do a full
* netDb lookup). This should improve performance during the initial negotiations,
* but is not necessary for communication that isn't bidirectional.
*
*/
public static final String BUNDLE_REPLY_LEASESET = "shouldBundleReplyInfo";
/**
* Allow the override of the frequency of bundling the reply info in with a message.
* The client app can specify bundleReplyInfoProbability=80 (for instance) and that
* will cause the router to include the sender's leaseSet with 80% of the messages
* sent to the peer.
*
*/
public static final String BUNDLE_PROBABILITY = "bundleReplyInfoProbability";
/**
* How often do messages include the reply leaseSet (out of every 100 tries).
* Including it each time is probably overkill, but who knows.
*/
private static final int BUNDLE_PROBABILITY_DEFAULT = 100;
/**
* Send the sucker
*/
public OutboundClientMessageOneShotJob(RouterContext ctx, ClientMessage msg) {
super(ctx);
_log = ctx.logManager().getLog(OutboundClientMessageOneShotJob.class);
ctx.statManager().createFrequencyStat("client.sendMessageFailFrequency", "How often does a client fail to send a message?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.sendMessageSize", "How large are messages sent by the client?", "ClientMessages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.sendAckTime", "How long does it take to get an ACK back from a message?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.timeoutCongestionTunnel", "How lagged our tunnels are when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.timeoutCongestionMessage", "How fast we process messages locally when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
ctx.statManager().createRateStat("client.timeoutCongestionInbound", "How much faster we are receiving data than our average bps when a send times out?", "ClientMessages", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
long timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT;
_clientMessage = msg;
_clientMessageId = msg.getMessageId();
_clientMessageSize = msg.getPayload().getSize();
_from = msg.getFromDestination();
_to = msg.getDestination();
String param = msg.getSenderConfig().getOptions().getProperty(OVERALL_TIMEOUT_MS_PARAM);
if (param == null)
param = ctx.router().getConfigSetting(OVERALL_TIMEOUT_MS_PARAM);
if (param != null) {
try {
timeoutMs = Long.parseLong(param);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid client message timeout specified [" + param
+ "], defaulting to " + OVERALL_TIMEOUT_MS_DEFAULT, nfe);
timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT;
}
}
_start = getContext().clock().now();
_overallExpiration = timeoutMs + _start;
_shouldBundle = getShouldBundle();
_finished = false;
}
public String getName() { return "Outbound client message"; }
public void runJob() {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Send outbound client message job beginning");
buildClove();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Clove built");
long timeoutMs = _overallExpiration - getContext().clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Send outbound client message - sending off leaseSet lookup job");
getContext().netDb().lookupLeaseSet(_to.calculateHash(), new SendJob(), new LookupLeaseSetFailedJob(), timeoutMs);
}
private boolean getShouldBundle() {
Properties opts = _clientMessage.getSenderConfig().getOptions();
String wantBundle = opts.getProperty(BUNDLE_REPLY_LEASESET, "true");
if ("true".equals(wantBundle)) {
int probability = BUNDLE_PROBABILITY_DEFAULT;
String str = opts.getProperty(BUNDLE_PROBABILITY);
try {
if (str != null)
probability = Integer.parseInt(str);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN))
_log.warn(getJobId() + ": Bundle leaseSet probability overridden incorrectly ["
+ str + "]", nfe);
}
if (probability >= getContext().random().nextInt(100))
return true;
else
return false;
} else {
return false;
}
}
/** send a message to a random lease */
private class SendJob extends JobImpl {
public SendJob() {
super(OutboundClientMessageOneShotJob.this.getContext());
}
public String getName() { return "Send outbound client message through the lease"; }
public void runJob() {
boolean ok = getNextLease();
if (ok)
send();
else
dieFatal();
}
}
/**
* fetch the next lease that we should try sending through, randomly chosen
* from within the sorted leaseSet (sorted by # of failures through each
* lease).
*
*/
private boolean getNextLease() {
_leaseSet = getContext().netDb().lookupLeaseSetLocally(_to.calculateHash());
if (_leaseSet == null) {
if (_log.shouldLog(Log.WARN))
_log.warn(getJobId() + ": Lookup locally didn't find the leaseSet");
return false;
}
long now = getContext().clock().now();
// get the possible leases
List leases = new ArrayList(_leaseSet.getLeaseCount());
for (int i = 0; i < _leaseSet.getLeaseCount(); i++) {
Lease lease = _leaseSet.getLease(i);
if (lease.isExpired(Router.CLOCK_FUDGE_FACTOR)) {
if (_log.shouldLog(Log.WARN))
_log.warn(getJobId() + ": getNextLease() - expired lease! - " + lease);
continue;
} else {
leases.add(lease);
}
}
if (leases.size() <= 0) {
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": No leases found from: " + _leaseSet);
return false;
}
// randomize the ordering (so leases with equal # of failures per next
// sort are randomly ordered)
Collections.shuffle(leases);
// ordered by lease number of failures
TreeMap orderedLeases = new TreeMap();
for (Iterator iter = leases.iterator(); iter.hasNext(); ) {
Lease lease = (Lease)iter.next();
long id = lease.getNumFailure();
while (orderedLeases.containsKey(new Long(id)))
id++;
orderedLeases.put(new Long(id), lease);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": ranking lease we havent sent it down as " + id);
}
_lease = (Lease)orderedLeases.get(orderedLeases.firstKey());
return true;
}
/**
* we couldn't even find the leaseSet, but try again (or die
* if we've already tried too hard)
*
*/
private class LookupLeaseSetFailedJob extends JobImpl {
public LookupLeaseSetFailedJob() {
super(OutboundClientMessageOneShotJob.this.getContext());
}
public String getName() { return "Lookup for outbound client message failed"; }
public void runJob() {
dieFatal();
}
}
/**
* Send the message to the specified tunnel by creating a new garlic message containing
* the (already created) payload clove as well as a new delivery status message. This garlic
* message is sent out one of our tunnels, destined for the lease (tunnel+router) specified, and the delivery
* status message is targetting one of our free inbound tunnels as well. We use a new
* reply selector to keep an eye out for that delivery status message's token
*
*/
private void send() {
if (_finished) return;
long token = getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE);
PublicKey key = _leaseSet.getEncryptionKey();
SessionKey sessKey = new SessionKey();
Set tags = new HashSet();
LeaseSet replyLeaseSet = null;
if (_shouldBundle) {
replyLeaseSet = getContext().netDb().lookupLeaseSetLocally(_from.calculateHash());
}
GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token,
_overallExpiration, key,
_clove,
_to,
sessKey, tags,
true, replyLeaseSet);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": send() - token expected " + token);
SendSuccessJob onReply = new SendSuccessJob(sessKey, tags);
SendTimeoutJob onFail = new SendTimeoutJob();
ReplySelector selector = new ReplySelector(token);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Placing GarlicMessage into the new tunnel message bound for "
+ _lease.getTunnelId() + " on "
+ _lease.getRouterIdentity().getHash().toBase64());
TunnelId outTunnelId = selectOutboundTunnel();
if (outTunnelId != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Sending tunnel message out " + outTunnelId + " to "
+ _lease.getTunnelId() + " on "
+ _lease.getRouterIdentity().getHash().toBase64());
SendTunnelMessageJob j = new SendTunnelMessageJob(getContext(), msg, outTunnelId,
_lease.getRouterIdentity().getHash(),
_lease.getTunnelId(), null, onReply,
onFail, selector,
_overallExpiration-getContext().clock().now(),
SEND_PRIORITY);
getContext().jobQueue().addJob(j);
} else {
if (_log.shouldLog(Log.ERROR))
_log.error(getJobId() + ": Could not find any outbound tunnels to send the payload through... wtf?");
dieFatal();
}
_clientMessage = null;
_clove = null;
}
/**
* Pick an arbitrary outbound tunnel to send the message through, or null if
* there aren't any around
*
*/
private TunnelId selectOutboundTunnel() {
TunnelSelectionCriteria crit = new TunnelSelectionCriteria();
crit.setMaximumTunnelsRequired(1);
crit.setMinimumTunnelsRequired(1);
List tunnelIds = getContext().tunnelManager().selectOutboundTunnelIds(crit);
if (tunnelIds.size() <= 0)
return null;
else
return (TunnelId)tunnelIds.get(0);
}
/**
* give up the ghost, this message just aint going through. tell the client to fuck off.
*
* this is safe to call multiple times (only tells the client once)
*/
private void dieFatal() {
if (_finished) return;
_finished = true;
long sendTime = getContext().clock().now() - _start;
if (_log.shouldLog(Log.WARN))
_log.warn(getJobId() + ": Failed to send the message " + _clientMessageId + " after "
+ sendTime + "ms", new Exception("Message send failure"));
long messageDelay = getContext().throttle().getMessageDelay();
long tunnelLag = getContext().throttle().getTunnelLag();
long inboundDelta = (long)getContext().throttle().getInboundRateDelta();
getContext().statManager().addRateData("client.timeoutCongestionTunnel", tunnelLag, 1);
getContext().statManager().addRateData("client.timeoutCongestionMessage", messageDelay, 1);
getContext().statManager().addRateData("client.timeoutCongestionInbound", inboundDelta, 1);
getContext().messageHistory().sendPayloadMessage(_clientMessageId.getMessageId(), false, sendTime);
getContext().clientManager().messageDeliveryStatusUpdate(_from, _clientMessageId, false);
getContext().statManager().updateFrequency("client.sendMessageFailFrequency");
_clientMessage = null;
_clove = null;
}
/** build the payload clove that will be used for all of the messages, placing the clove in the status structure */
private void buildClove() {
PayloadGarlicConfig clove = new PayloadGarlicConfig();
DeliveryInstructions instructions = new DeliveryInstructions();
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_DESTINATION);
instructions.setDestination(_to.calculateHash());
instructions.setDelayRequested(false);
instructions.setDelaySeconds(0);
instructions.setEncrypted(false);
clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
clove.setDeliveryInstructions(instructions);
clove.setExpiration(_overallExpiration);
clove.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE));
DataMessage msg = new DataMessage(getContext());
msg.setData(_clientMessage.getPayload().getEncryptedData());
clove.setPayload(msg);
clove.setRecipientPublicKey(null);
clove.setRequestAck(false);
_clove = clove;
_cloveId = _clove.getId();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Built payload clove with id " + clove.getId());
}
/**
* Keep an eye out for any of the delivery status message tokens that have been
* sent down the various tunnels to deliver this message
*
*/
private class ReplySelector implements MessageSelector {
private long _pendingToken;
public ReplySelector(long token) {
_pendingToken = token;
}
public boolean continueMatching() { return false; }
public long getExpiration() { return _overallExpiration; }
public boolean isMatch(I2NPMessage inMsg) {
if (inMsg.getType() == DeliveryStatusMessage.MESSAGE_TYPE) {
return _pendingToken == ((DeliveryStatusMessage)inMsg).getMessageId();
} else {
return false;
}
}
}
/**
* Called after we get a confirmation that the message was delivered safely
* (hoo-ray!)
*
*/
private class SendSuccessJob extends JobImpl implements ReplyJob {
private SessionKey _key;
private Set _tags;
/**
* Create a new success job that will be fired when the message encrypted with
* the given session key and bearing the specified tags are confirmed delivered.
*
*/
public SendSuccessJob(SessionKey key, Set tags) {
super(OutboundClientMessageOneShotJob.this.getContext());
_key = key;
_tags = tags;
}
public String getName() { return "Send client message successful to a lease"; }
public void runJob() {
if (_finished) return;
_finished = true;
long sendTime = getContext().clock().now() - _start;
if (_log.shouldLog(Log.INFO))
_log.info(OutboundClientMessageOneShotJob.this.getJobId()
+ ": SUCCESS! msg " + _clientMessageId
+ " sent after " + sendTime + "ms");
if ( (_key != null) && (_tags != null) && (_tags.size() > 0) ) {
if (_leaseSet != null)
getContext().sessionKeyManager().tagsDelivered(_leaseSet.getEncryptionKey(),
_key, _tags);
}
long dataMsgId = _cloveId;
getContext().messageHistory().sendPayloadMessage(dataMsgId, true, sendTime);
getContext().clientManager().messageDeliveryStatusUpdate(_from, _clientMessageId, true);
_lease.setNumSuccess(_lease.getNumSuccess()+1);
getContext().statManager().addRateData("client.sendAckTime", sendTime, 0);
getContext().statManager().addRateData("client.sendMessageSize", _clientMessageSize, sendTime);
}
public void setMessage(I2NPMessage msg) {}
}
/**
* Fired after the basic timeout for sending through the given tunnel has been reached.
* We'll accept successes later, but won't expect them
*
*/
private class SendTimeoutJob extends JobImpl {
public SendTimeoutJob() {
super(OutboundClientMessageOneShotJob.this.getContext());
}
public String getName() { return "Send client message timed out through a lease"; }
public void runJob() {
if (_log.shouldLog(Log.DEBUG))
_log.debug(OutboundClientMessageOneShotJob.this.getJobId()
+ ": Soft timeout through the lease " + _lease);
_lease.setNumFailure(_lease.getNumFailure()+1);
dieFatal();
}
}
}

View File

@ -147,8 +147,10 @@ class PersistentDataStore extends TransientDataStore {
}
private class ReadJob extends JobImpl {
private boolean _alreadyWarned;
public ReadJob() {
super(PersistentDataStore.this._context);
_alreadyWarned = false;
}
public String getName() { return "DB Read Job"; }
public void runJob() {
@ -158,6 +160,7 @@ class PersistentDataStore extends TransientDataStore {
}
private void readFiles() {
int routerCount = 0;
try {
File dbDir = getDbDir();
File leaseSetFiles[] = dbDir.listFiles(LeaseSetFilter.getInstance());
@ -170,6 +173,9 @@ class PersistentDataStore extends TransientDataStore {
}
File routerInfoFiles[] = dbDir.listFiles(RouterInfoFilter.getInstance());
if (routerInfoFiles != null) {
routerCount += routerInfoFiles.length;
if (routerInfoFiles.length > 5)
_alreadyWarned = false;
for (int i = 0; i < routerInfoFiles.length; i++) {
Hash key = getRouterInfoHash(routerInfoFiles[i].getName());
if ( (key != null) && (!isKnown(key)) )
@ -179,6 +185,11 @@ class PersistentDataStore extends TransientDataStore {
} catch (IOException ioe) {
_log.error("Error reading files in the db dir", ioe);
}
if ( (routerCount <= 5) && (!_alreadyWarned) ) {
_log.error("Very few routerInfo files remaining - please reseed");
_alreadyWarned = true;
}
}
}

View File

@ -45,18 +45,25 @@ public class CapacityCalculator extends Calculator {
private static long ESTIMATE_PERIOD = 60*60*1000;
public double calc(PeerProfile profile) {
double capacity = 0;
RateStat acceptStat = profile.getTunnelCreateResponseTime();
RateStat rejectStat = profile.getTunnelHistory().getRejectionRate();
RateStat failedStat = profile.getTunnelHistory().getFailedRate();
capacity += estimatePartial(acceptStat, rejectStat, failedStat, 10*60*1000);
capacity += estimatePartial(acceptStat, rejectStat, failedStat, 30*60*1000);
capacity += estimatePartial(acceptStat, rejectStat, failedStat, 60*60*1000);
capacity += estimatePartial(acceptStat, rejectStat, failedStat, 24*60*60*1000);
double capacity10m = estimateCapacity(acceptStat, rejectStat, failedStat, 10*60*1000);
double capacity30m = estimateCapacity(acceptStat, rejectStat, failedStat, 30*60*1000);
double capacity60m = estimateCapacity(acceptStat, rejectStat, failedStat, 60*60*1000);
double capacity1d = estimateCapacity(acceptStat, rejectStat, failedStat, 24*60*60*1000);
if (tooOld(profile))
double capacity = capacity10m * periodWeight(10*60*1000) +
capacity30m * periodWeight(30*60*1000) +
capacity60m * periodWeight(60*60*1000) +
capacity1d * periodWeight(24*60*60*1000);
// if we actively know they're bad, who cares if they used to be good?
if (capacity10m <= 0)
capacity = 0;
if (tooOld(profile))
capacity = 1;
capacity += profile.getReliabilityBonus();
@ -74,7 +81,7 @@ public class CapacityCalculator extends Calculator {
return true;
}
private double estimatePartial(RateStat acceptStat, RateStat rejectStat, RateStat failedStat, int period) {
private double estimateCapacity(RateStat acceptStat, RateStat rejectStat, RateStat failedStat, int period) {
Rate curAccepted = acceptStat.getRate(period);
Rate curRejected = rejectStat.getRate(period);
Rate curFailed = failedStat.getRate(period);
@ -82,27 +89,29 @@ public class CapacityCalculator extends Calculator {
long eventCount = 0;
if (curAccepted != null)
eventCount = curAccepted.getCurrentEventCount() + curAccepted.getLastEventCount();
double stretch = ESTIMATE_PERIOD / period;
double stretch = ((double)ESTIMATE_PERIOD) / period;
double val = eventCount * stretch;
long failed = 0;
if (curFailed != null)
failed = curFailed.getCurrentEventCount() + curFailed.getLastEventCount();
if (failed > 0) {
if ( (period == 10*60*1000) && (curFailed.getCurrentEventCount() > 0) )
return 0.0d; // their tunnels have failed in the last 0-10 minutes
else
val -= failed * stretch;
//if ( (period <= 10*60*1000) && (curFailed.getCurrentEventCount() > 0) )
// return 0.0d; // their tunnels have failed in the last 0-10 minutes
//else
val -= failed * stretch;
}
if ( (period == 10*60*1000) && (curRejected.getCurrentEventCount() + curRejected.getLastEventCount() > 0) )
return 0.0d;
else
//if ( (period <= 10*60*1000) && (curRejected.getCurrentEventCount() + curRejected.getLastEventCount() > 0) ) {
// //System.out.println("10m period has rejected " + (curRejected.getCurrentEventCount() + curRejected.getLastEventCount()) + " times");
// return 0.0d;
//} else
val -= stretch * (curRejected.getCurrentEventCount() + curRejected.getLastEventCount());
val += GROWTH_FACTOR;
if (val >= 0) {
return (val + GROWTH_FACTOR) * periodWeight(period);
return val;
} else {
// failed too much, don't grow
return 0.0d;
}
}

View File

@ -248,7 +248,7 @@ public class PeerProfile {
if (_dbResponseTime == null)
_dbResponseTime = new RateStat("dbResponseTime", "how long it takes to get a db response from the peer (in milliseconds)", group, new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_tunnelCreateResponseTime == null)
_tunnelCreateResponseTime = new RateStat("tunnelCreateResponseTime", "how long it takes to get a tunnel create response from the peer (in milliseconds)", group, new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000 } );
_tunnelCreateResponseTime = new RateStat("tunnelCreateResponseTime", "how long it takes to get a tunnel create response from the peer (in milliseconds)", group, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_tunnelTestResponseTime == null)
_tunnelTestResponseTime = new RateStat("tunnelTestResponseTime", "how long it takes to successfully test a tunnel this peer participates in (in milliseconds)", group, new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_commError == null)

View File

@ -109,7 +109,8 @@ class ProfileOrganizerRenderer {
buf.append("<td align=\"right\">").append(prof.getIsFailing()).append("</td>");
//buf.append("<td><a href=\"/profile/").append(prof.getPeer().toBase64().substring(0, 32)).append("\">profile.txt</a> ");
//buf.append(" <a href=\"#").append(prof.getPeer().toBase64().substring(0, 32)).append("\">netDb</a></td>");
buf.append("<td><a href=\"netdb.jsp#").append(peer.toBase64().substring(0,6)).append("\">netDb</a></td>\n");
buf.append("<td nowrap><a href=\"netdb.jsp#").append(peer.toBase64().substring(0,6)).append("\">netDb</a>");
buf.append("/<a href=\"dumpprofile.jsp?peer=").append(peer.toBase64().substring(0,6)).append("\">profile</a></td>\n");
buf.append("</tr>");
}
buf.append("</table>");

View File

@ -70,9 +70,9 @@ class ProfilePersistenceHelper {
groups = "not failing";
} else {
if (_context.profileOrganizer().isFast(profile.getPeer()))
groups = "fast and reliable";
groups = "fast and high capacity";
else
groups = "reliable";
groups = "high capacity";
if (_context.profileOrganizer().isWellIntegrated(profile.getPeer()))
groups = groups + ", well integrated";
@ -85,6 +85,7 @@ class ProfilePersistenceHelper {
buf.append("# as calculated by ").append(_us.toBase64()).append(NL);
buf.append("#").append(NL);
buf.append("# reliability: ").append(profile.getReliabilityValue()).append(NL);
buf.append("# capacity: ").append(profile.getCapacityValue()).append(NL);
buf.append("# integration: ").append(profile.getIntegrationValue()).append(NL);
buf.append("# speedValue: ").append(profile.getSpeedValue()).append(NL);
buf.append("#").append(NL);

View File

@ -1,87 +0,0 @@
package net.i2p.router.startup;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Properties;
import net.i2p.data.DataHelper;
/**
* Make sure that if there is a wrapper.config file, it includes
* all of the jar files necessary for the current build.
* HOLY CRAP THIS IS UGLY.
*
*/
public class VerifyClasspath {
private static final String NL = System.getProperty("line.separator");
private static final Set _jars = new HashSet();
static {
_jars.add("lib/ant.jar");
_jars.add("lib/heartbeat.jar");
_jars.add("lib/i2p.jar");
_jars.add("lib/i2ptunnel.jar");
_jars.add("lib/jasper-compiler.jar");
_jars.add("lib/jasper-runtime.jar");
_jars.add("lib/javax.servlet.jar");
_jars.add("lib/jnet.jar");
_jars.add("lib/mstreaming.jar");
_jars.add("lib/netmonitor.jar");
_jars.add("lib/org.mortbay.jetty.jar");
_jars.add("lib/router.jar");
_jars.add("lib/routerconsole.jar");
_jars.add("lib/sam.jar");
_jars.add("lib/wrapper.jar");
_jars.add("lib/xercesImpl.jar");
_jars.add("lib/xml-apis.jar");
_jars.add("lib/jbigi.jar");
_jars.add("lib/systray.jar");
_jars.add("lib/systray4j.jar");
_jars.add("lib/streaming.jar");
}
/**
* update the wrapper.config
*
* @return true if the classpath was updated and a restart is
* required, false otherwise.
*/
public static boolean updateClasspath() {
Properties p = new Properties();
File configFile = new File("wrapper.config");
Set needed = new HashSet(_jars);
try {
DataHelper.loadProps(p, configFile);
Set toAdd = new HashSet();
int entry = 1;
while (true) {
String value = p.getProperty("wrapper.java.classpath." + entry);
if (value == null) break;
needed.remove(value);
entry++;
}
if (needed.size() <= 0) {
// we have everything we need
return false;
} else {
// add on some new lines
FileWriter out = new FileWriter(configFile, true);
out.write(NL + "# Adding new libs as required by the update" + NL);
for (Iterator iter = needed.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
out.write("wrapper.java.classpath." + entry + "=" + name + NL);
}
out.close();
return true;
}
} catch (IOException ioe) {
ioe.printStackTrace();
return false;
}
}
}

View File

@ -364,5 +364,5 @@ public abstract class TransportImpl implements Transport {
/** Make this stuff pretty (only used in the old console) */
public String renderStatusHTML() { return null; }
protected RouterContext getContext() { return _context; }
public RouterContext getContext() { return _context; }
}

View File

@ -177,6 +177,8 @@ public class TransportManager implements TransportEventListener {
}
private List orderBids(HashSet bids, OutNetMessage msg) {
if (bids.size() <= 1)
return new ArrayList(bids);
// db messages should go as fast as possible, while the others
// should use as little bandwidth as possible.
I2NPMessage message = msg.getMessage();

View File

@ -142,6 +142,7 @@ public class ConnectionBuilder {
con.setRemoteRouterIdentity(_actualPeer.getIdentity());
con.setRemoteAddress(_remoteAddress);
con.setAttemptedPeer(_target.getIdentity().getHash());
con.setShownAddress(_localIP);
if (_error == null) {
if (_log.shouldLog(Log.INFO))
_log.info("Establishment successful! returning the con");
@ -409,12 +410,13 @@ public class ConnectionBuilder {
RouterInfo peer = new RouterInfo();
peer.readBytes(_rawIn);
int status = (int)_rawIn.read() & 0xFF;
boolean ok = validateStatus(status);
if (!ok) return false;
Properties props = DataHelper.readProperties(_rawIn);
// ignore these now
boolean ok = validateStatus(status, props);
if (!ok) return false;
Hash readHash = new Hash();
readHash.readBytes(_rawIn);
@ -563,12 +565,13 @@ public class ConnectionBuilder {
RouterInfo peer = new RouterInfo();
peer.readBytes(_rawIn);
int status = (int)_rawIn.read() & 0xFF;
boolean ok = validateStatus(status);
if (!ok) return false;
Properties props = DataHelper.readProperties(_rawIn);
// ignore these now
boolean ok = validateStatus(status, props);
if (!ok) return false;
Signature sig = new Signature();
sig.readBytes(_rawIn);
@ -619,7 +622,7 @@ public class ConnectionBuilder {
*
* @return true if ok, false if fail()ed
*/
private boolean validateStatus(int status) {
private boolean validateStatus(int status, Properties props) {
switch (status) {
case -1: // EOF
fail("Error reading the status from "
@ -635,7 +638,7 @@ public class ConnectionBuilder {
case ConnectionHandler.STATUS_SKEWED:
fail("According to "
+ _target.getIdentity().calculateHash().toBase64().substring(0,6)
+ ", our clock is off");
+ ", our clock is off (they think it is " + props.getProperty("SKEW") + ")");
return false;
case ConnectionHandler.STATUS_SIGNATURE_FAILED: // (only for new sessions)
fail("Signature failure talking to "

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