Compare commits

...

147 Commits

Author SHA1 Message Date
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
64b5089909 * 2004-11-26 0.4.2 released
2004-11-26  jrandom
    * Enable the new streaming lib as the default.  That means, for any
      substantial definition, it is NOT BACKWARDS COMPATIBLE.
2004-11-26 15:20:14 +00:00
e0e09bfa45 (please wait until the release announcement before updating)
* 2004-11-26  0.4.2 released
2004-11-26  jrandom
    * Enable the new streaming lib as the default.  That means, for any
      substantial definition, it is NOT BACKWARDS COMPATIBLE.
2004-11-26 15:15:16 +00:00
8c7f9f2c65 added bdsm.i2p 2004-11-26 02:12:16 +00:00
aff5cea949 2004-11-25 jrandom
* Revised the installer to include start menu and desktop shortcuts for
      windows platforms, including pretty icons (thanks DrWoo!)
    * Allow clients specified in clients.config to have an explicit startup
      delay.
    * Update the default install to launch a browser pointing at the console
      whenever I2P starts up, rather than only the first time it starts up
      (configurable on /configservice.jsp, or in clients.config)
    * Bugfix to the clock skew checking code to monitor the delta between
      offsets, not the offset itself (duh)
    * Router console html update
    * New (and uuuuugly) code to verify that the wrapper.config contains
      the necessary classpath entries on update.  If it has to update the
      wrapper.config, it will stop the JVM and service completely, since the
      java service wrapper doesn't reread the wrapper.config on JVM restart -
      requiring the user to manually restart the service after an update.
    * Increase the TCP connection timeout to 30s (which is obscenely long)
2004-11-25 22:17:37 +00:00
8bd99f699f 2004-11-25 jrandom
* Revised the installer to include start menu and desktop shortcuts for
      windows platforms, including pretty icons (thanks DrWoo!)
    * Allow clients specified in clients.config to have an explicit startup
      delay.
    * Update the default install to launch a browser pointing at the console
      whenever I2P starts up, rather than only the first time it starts up
      (configurable on /configservice.jsp, or in clients.config)
    * Bugfix to the clock skew checking code to monitor the delta between
      offsets, not the offset itself (duh)
    * Router console html update
    * New (and uuuuugly) code to verify that the wrapper.config contains
      the necessary classpath entries on update.  If it has to update the
      wrapper.config, it will stop the JVM and service completely, since the
      java service wrapper doesn't reread the wrapper.config on JVM restart -
      requiring the user to manually restart the service after an update.
    * Increase the TCP connection timeout to 30s (which is obscenely long)
------------------------------------------------
2004-11-25 21:57:19 +00:00
b0513fff8a javadoc 2004-11-25 21:49:29 +00:00
f10db9d91a added eschaton.i2p 2004-11-23 03:58:46 +00:00
9b5fb17068 added blog.curiosity.i2p 2004-11-23 02:46:07 +00:00
608d713dca 2004-11-22 jrandom
* Update to the SAM bridge to reduce some unnecessary memory allocation.
    * New stat to keep track of slow jobs (ones that take more than a second
      to excute).  This is published in the netDb as jobQueue.jobRunSlow
2004-11-23 01:12:34 +00:00
6d5fc8ca21 2004-11-21 jrandom
* Update the I2PTunnel web interface to include an option for the new
      streaming lib (which is ignored until the 0.4.2 release).
    * Revised the I2PTunnel web interface to keep the I2CP options of client
      and httpclient tunnels in sync, as they all share the same I2CP session.
2004-11-22 17:57:16 +00:00
8c3145b70f 2004-11-21 jrandom
* Only allow small clock skews after the first 10 minutes of operation
      (to prevent later network lag bouncing us way off course - yes, we
      really need an NTP impl to balance out the network burps...)
    * Revamp the I2PTunnel web interface startup process so that everything
      is shown immediately, so that different pieces hanging don't hang
      the rest, and other minor bugfixes.
    * Take note of SAM startup error (in case you're already running a SAM
      bridge...)
    * Increase the bandwidth limiter burst values available to 10-60s (or
      whatever is placed in /configadvanced.jsp, of course)
2004-11-21 23:23:42 +00:00
12a6f3e938 2004-11-21 jrandom
* Only allow small clock skews after the first 10 minutes of operation
      (to prevent later network lag bouncing us way off course - yes, we
      really need an NTP impl to balance out the network burps...)
    * Revamp the I2PTunnel web interface startup process so that everything
      is shown immediately, so that different pieces hanging don't hang
      the rest, and other minor bugfixes.
    * Take note of SAM startup error (in case you're already running a SAM
      bridge...)
    * Increase the bandwidth limiter burst values available to 10-60s (or
      whatever is placed in /configadvanced.jsp, of course)
2004-11-21 22:31:33 +00:00
2c59435762 2004-11-21 jrandom
* Allow end of line comments in the hosts.txt and other config files,
      using '#' to begin the comments (thanks susi!)
    * Add support to I2PTunnel's 'client' feature for picking between multiple
      target destinations (e.g. 'client 6668 irc.duck.i2p,irc.baffled.i2p')
    * Add a quick link on the left hand nav to reseed if there aren't enough
      known peers, as well as link to the config page if there are no active
      peers.  Revised config page accordingly.
2004-11-21 19:42:57 +00:00
7336bf5c55 oops, forgot to commit this one. 2004-11-21 06:13:30 +00:00
2b21b97277 * make sure we send the close message even if the I2PSocket is closed directly (rather than the outputStream)
* only fail the session tags as a last resort for the last resend, rather than on every resend after the 1st
2004-11-21 04:11:35 +00:00
603bc99a2f 2004-11-21 jrandom
* Destroy ElGamal/AES+SessionTag keys after 15 minutes of inactivity
      rather that every 15 minutes, and increase the warning period in which
      we refresh tags from 30s to 2 minutes.
    * Bugfix for a rare problem closing an I2PTunnel stream where we'd fail
      to close the I2PSocket (leaving it to timeout).
2004-11-21 04:08:13 +00:00
426ede1c99 * handle con b0rkage more gracefully
* close the session on socket manager destroy (the old lib does, and SAM wants it to)
2004-11-20 15:52:08 +00:00
21506c1d1b handle poorly formatted packets more gracefully (in case someone uses the wrong streaming lib *cough*) 2004-11-20 02:40:57 +00:00
0b48b18e7e 2004-11-19 jrandom
* Off-by-one fix to the tunnel pool management code, along side some
      explicit initialization.  This can affect clients whose lengths are
      shorter than the router's default (thanks duck!)
2004-11-19 23:04:27 +00:00
mpc
c8f6d9c7a1 improved logging 2004-11-19 02:17:20 +00:00
ed8eced9dd * stats
if you want to get this data and you're running outside the router, just toss on a
"-Dstat.logFilters=* -Dstat.logFile=proxy.stats" to the java command line.  the resulting
file can be parsed w/ java -cp lib/i2p.jar net.i2p.stat.StatLogSplitter proxy.stats, then fed
into gnuplot or whatever
2004-11-19 00:00:05 +00:00
4a029b7853 * if we timeout connecting or otherwise need to cancel tags that we've sent, go one step
further and cancel all of the tags we're using for that peer so that we can react to their
  potential restart / tag loss quicker.
* use the minimum resend delay as the base to be exponentiated if our RTT is too low
  (so we resend less)
* dont be such a wuss when flushing a closed stream
2004-11-18 19:42:11 +00:00
6bd9e58ece * fix a reordering bug that can trim the end of a stream under heavy lag (thanks duck!)
* fix a pair of races on router crash
2004-11-18 13:47:27 +00:00
cd075fc8a6 2004-11-17 jrandom
* Fix to propogate i2psocket options into the SAM bridge correctly (thanks
      Ragnarok!)
2004-11-17 19:42:53 +00:00
e733427920 2004-11-17 jrandom
* Minor logging update.
2004-11-17 18:34:25 +00:00
107da0ae22 i suppose i should commit this, 'eh? (thanks mule) 2004-11-17 03:43:04 +00:00
mpc
3629d7a32c *** empty log message *** 2004-11-17 03:42:00 +00:00
71e1152cde if we've already sent our close packet but we still want to send something, send an ack packet 2004-11-17 00:57:33 +00:00
d01ab7fd23 *cough* (lets not have everyone think they're the resend with a packet in the air...) 2004-11-16 22:47:16 +00:00
f46d0a720c * fix up the propogation of client options to the streaming lib
* add new back-off logic to reduce payload resends during transient
  lag - only let one packet be resent at a time, even if the window size
  allows it (and the packet timers request it).  this should make
  congestion less painful, and reduce the overall number of messages
  resent (as the SACKs for the one packet actively resent should clarify
  what made it through)
2004-11-16 22:15:16 +00:00
d943b4993a 2004-11-16 jrandom
* Clean up the propogation of i2psocket options so that various streaming
      libs can honor them more precisely
2004-11-16 22:11:11 +00:00
4a4f57d6ac 2004-11-16 jrandom
* Minor logging update
(toss net.i2p.router.JobQueueRunner=WARN in /configlogging.jsp to see wtf is hanging your router)
2004-11-16 13:45:40 +00:00
085da16268 run multiple connections 2004-11-15 14:40:08 +00:00
306f6b0037 various tweaks to make sure we release appropriate references ASAP 2004-11-15 14:37:56 +00:00
3780d290fa 2004-11-14 jrandom
* Fix a long standing leak in I2PTunnel (hanging on to i2psocket objects)
    * Fix a leak injected into the SimpleTimer
    * Fix a race condition in the tunnel message handling
2004-11-15 14:35:16 +00:00
ad7dc66f90 2004-11-13 jrandom
* Added throttles on how many I2PTunnel client connections we open at once
    * Replaced some buffered streams in I2PTunnel with unbuffered streams, as
      the streaming library used should take care of any buffering.
    * Added a cache for some objects used in I2PTunnel, especially useful when
      there are many short lived connections.
    * Trimmed the SimpleTimer's processing a bit
2004-11-13 09:59:37 +00:00
258244fed8 * ack packets with a payload, even if they have ID=0 (duh)
* properly implement the connection timeout
* make sure we clear the outbound packets on close
* don't b0rk on repeated close() calls
2004-11-13 09:49:31 +00:00
5f7982540f 2004-11-13 jrandom
* Added throttles on how many I2PTunnel client connections we open at once
    * Replaced some buffered streams in I2PTunnel with unbuffered streams, as
      the streaming library used should take care of any buffering.
    * Added a cache for some objects used in I2PTunnel, especially useful when
      there are many short lived connections.
    * Trimmed the SimpleTimer's processing a bit
2004-11-13 09:43:35 +00:00
b1c0de4b77 (oops forgot to commit before passing out) 2004-11-12 06:07:33 +00:00
45b3fecfff allow throttles on the number of streams participated in, as well as how a timeout + queue for overflow 2004-11-11 21:19:59 +00:00
9774ded4dd added slacker.i2p 2004-11-11 10:00:32 +00:00
7ec027854e update connection options specification
tweak around with the connection delay and timeout activity to test delayed vs. immediate syn
2004-11-11 09:41:25 +00:00
b457001b42 timeout gracefully even if the socket is stopped in odd places 2004-11-11 09:37:46 +00:00
6fc6866eb4 *cough* 2004-11-10 13:28:39 +00:00
299e5528bc * deal with nondeferred connections (block the mgr.connect(..) until either success or failure)
* allow loading the connection options from the env (or another Properties specified)
2004-11-10 12:55:06 +00:00
881524a5e4 2004-11-10 jrandom
* Allow loading the (mini)streaming connection options from the
      environment.
    * More defensive programming in the DSA implementation.
2004-11-10 12:33:01 +00:00
ffc405138d added pdforge.i2p and ses.i2p 2004-11-10 09:43:45 +00:00
f6ff74af16 oops, properly choke the congestion detection for resend 2004-11-09 13:33:11 +00:00
73a12d47de * you mean we should implement congestion *avoidance* too?
* ack properly on duplicates
* set the message input stream buffer large enough to fit the max window (duh)
2004-11-09 13:26:10 +00:00
83165df7e5 * delay the ack of a syn
* make sure we ack duplicate messages received (if we aren't already doing so)
* implement a choke on the local buffer, in case we receive data faster than its
  removed from the i2psocket's MessageInputStream (handle via packet drop and
  explicit congestion notification)
2004-11-09 11:00:04 +00:00
30074be5a5 logging 2004-11-09 05:54:39 +00:00
16715aa309 * synchronize around the buffer used in the packet queue (duh)
* dont increment # unacked packets artificially
* dont try to push data after closing
* cleanup the packet serialization
* logging
2004-11-08 21:40:25 +00:00
53f3802a81 dont keepalive if we're in the closing process (duh) 2004-11-08 16:49:23 +00:00
07626b5cc2 two new tests - inactivity (seeing how we stay alive) and timeout (contacting someone who doesnt exist) 2004-11-08 15:27:41 +00:00
9ea603caf2 * hang around for 5m (er, 2.5msl, i suppose) after connection closure no matter what, so we
can respond apropriately
* optional inactivty timer with three possible results:
  disconnect, send a (blank) message (to be ACKed), or do nothing.
2004-11-08 15:05:13 +00:00
18ab9b80d2 testStaggered - read what we can while writing randomly 2004-11-08 05:48:36 +00:00
71c1cb4e12 * min resend delay = 20s
* rework the messageInputStream to implement read(byte[], off, len), and fix some fencepost
  bugs in the byte retrieval
2004-11-08 05:42:57 +00:00
0c049f39d9 2004-11-08 jrandom
* Remove spurious flush calls from I2PTunnel, and work with the
      I2PSocket's output stream directly (as the various implementations
      do their own buffering).
    * Another pass at a long standing JobQueue bug - dramatically simplify
      the job management synchronization since we dont need to deal with
      high contention (unlike last year when we had dozens of queue runners
      going at once).
    * Logging
2004-11-08 05:40:20 +00:00
096b807c37 2004-11-08 jrandom
* Make the SAM bridge more resiliant to bad handshakes (thanks duck!)
2004-11-08 03:18:01 +00:00
mpc
9018af4765 Leave it up to the client application whether to poll or not 2004-11-07 05:06:36 +00:00
mpc
323f28e306 Now it works?? 2004-11-07 04:14:53 +00:00
mpc
e9dbd00f42 *** empty log message *** 2004-11-07 04:12:36 +00:00
98b5252a2d 0.4.1.4 2004-11-07 03:00:56 +00:00
8abf42023a removed fproxy2, added postman 2004-11-07 02:49:40 +00:00
mpc
b792238f3f added my ping everyone script 2004-11-07 02:45:40 +00:00
2486e5e75f on some resends, drop our session tags for the peer and revert to ElGamal, since
they may have restarted or otherwise lost the tags delivered to them.
2004-11-07 02:36:42 +00:00
5f113f1610 2004-11-06 jrandom
* Expose a drop down on the /configclients.jsp to enter the outbound
      tunnel depth.
    * Improved *hosts.txt loading
    * Explicitly override the JVM's timezone settings to use GMT so that
      any client applications which use timezones won't leak sensitive
      data (thanks gott!)
    * Bundle sam.jar in the update (thanks duck!)
2004-11-07 02:25:13 +00:00
592e9dc3ff * limit the resend delay to a max of 60s between resends
* reduce the max # resends to 5
* if the session.sendMessage fails, we're fucked, so kill the socket
2004-11-06 08:02:02 +00:00
314316cee0 2004-11-06 jrandom
* Fix for a long standing synchronization bug in the SDK that in rare
      instances can add a few seconds of lag.
2004-11-06 07:59:54 +00:00
9cf663063e added orion and protokol 2004-11-06 02:36:12 +00:00
9ea9210a4b 2004-11-05 jrandom
* Bugfixes and unit tests for the SAM bridge to handle quoted message
      parameters, verify proper operation after multiple session lifetimes,
      as well as some synchronization problems.
    * New properties method on the DataHelper class.
    * Address a race on fast disconnecting clients
2004-11-05 11:59:07 +00:00
2bf1a94608 added blog.polecat.i2p 2004-11-05 11:22:56 +00:00
7a0236ad29 2004-11-05 jrandom
* Bugfixes and unit tests for the SAM bridge to handle quoted message
      parameters, verify proper operation after multiple session lifetimes,
      as well as some synchronization problems.
    * New properties method on the DataHelper class.
    * Address a race on fast disconnecting clients
2004-11-05 10:53:40 +00:00
4341a0c198 added ciaran.i2p 2004-11-05 10:35:58 +00:00
8071612c12 added polecat.i2p 2004-11-05 09:35:49 +00:00
ea9cc3da04 added susi.i2p 2004-11-05 08:39:07 +00:00
0df588fffb blackdown workarounds 2004-11-04 11:57:26 +00:00
a622311dbc make the updater clean things (thanks nickster\!) 2004-11-04 08:31:11 +00:00
1c95ac2470 Dont need no authentication 2004-11-03 01:34:22 +00:00
6ef22166f9 2004-11-02 jrandom
* Fix for a long standing synchronization bug in the JobQueue (and added
      some kooky flags to make sure it stays dead)
    * Update the ministreaming lib to force mode=guaranteed if the default
      lib is used, and mode=best_effort for all other libs.
2004-11-02 11:57:07 +00:00
1107e50108 2004-11-02 jrandom
* Fixed up the configuration overrides for the streaming socket lib
      integration so that it properly honors env settings.
    * More memory usage streamlining (last major revamp for now, i promise)
2004-11-02 08:27:55 +00:00
c19355a7b2 2004-11-01 jrandom
* Increase the tunnel test timeout rapidly if our tunnels are failing.
    * Honor message expirations for some tunnel jobs that were prematurely
      expired.
    * Streamline memory usage with temporary object caches and more efficient
      serialization for SHA256 calculation, logging, and both I2CP and I2NP
      message handling.
    * Fix some situations where we forward messages too eagerly.  For a
      request at the tunnel endpoint, if the tunnel is inbound and the target
      is remote, honor the message by tunnel routing the data rather than
      sending it directly to the requested location.
2004-11-01 13:31:29 +00:00
65d415fade javadoc fixes 2004-10-30 23:58:50 +00:00
b37313d3f9 a chunk of streaming lib updates (cwin calc & timed win, pings influencing rtt, etc) 2004-10-30 23:46:01 +00:00
58fcbad20a (mmMMmm profiling)
2004-10-30  jrandom
    * Cache the temporary objects used in the AES encryption/decryption
      process so that AES doesn't require any memory allocation to process
      data.
    * Dramatically reduce memory usage within various crypto implementations
      by avoiding unnecessary (though simplifying) buffers.
    * If we specify some tags to be sent in an I2CP message explicitly, use
      only those, not those plus a new set (otherwise we aren't sure on ACK
      which set was delivered)
    * Allow configuration for the partial send timeout (how long before
      resending a message down a different tunnel in a lease).  This can be
      updated with the "router.clientPartialSendTimeout" router config prop.
    * Logging
2004-10-30 23:43:59 +00:00
b571f331ec removed duplicate beyond.i2p, added edge.i2p 2004-10-30 18:56:07 +00:00
mpc
2547d4b3e7 *** empty log message *** 2004-10-30 12:41:18 +00:00
892786bf0c 2004-10-29 jrandom
* Strip the Referer, Via, and From headers completely, rather than
      inserting a bogus value ("i2p").  This should help with the use of
      SnipSnap and Geeklog (thanks nickster and DrWoo!)
2004-10-30 02:40:52 +00:00
0c51f2b583 2004-10-27 jrandom
* Fix a strange race condition on i2cp client disconnect.
    * win98 startup fixes (thanks tester-1 and ardvark!)
    * include build scripts for the new streaming lib (which is NOT ready
      for use yet, but you can hack around with it)
(enjoy, duck)
2004-10-28 02:11:52 +00:00
d5607ca195 * updated output stream test to match new API
* new paired stream server and client helpers
2004-10-28 02:05:51 +00:00
48cdf17a4f * revamped locking to block on flush and close until all of the
packets through that point have been ACKed, throwing an
  InterruptedIOException if there was a writeTimeout or an IOException
  if the con failed
* revamped the ack/nack field settings to ack as much as possible
* handle some strange timeout/resend errors on connection
* pass 1/2rtt as the packet 'optional delay' field, and use that to
  schedule the ack time (the 'last' messages in a window set the
  optional delay to 0, asking for immediate ack of all received)
* increase the optional delay to 2 bytes (#ms to delay)
* inject random failures and delays if configured to do so in
  PacketHandler.choke
* fix up the window size adjustment (increment on ack, /= 2 on resend)
* use the highest RTT in the new RTT calculation so that we fit more
  in (via SACK)
* fix up the SACK handling (duh)
* revise the resend time calculation
2004-10-28 02:03:38 +00:00
669a8fae15 iterate through the dict values
(thanks dinoman)
2004-10-27 04:07:00 +00:00
d592936873 * mark the input stream as closed after receiving the packet's data
* properly close the source file in StreamSinkSend
* always adjust the rtt on ack, not just for packets with 1 send
* handle dup SYN gracefully
* revamp the default connection options
* logging
2004-10-25 20:04:07 +00:00
87898dd2f1 added shiftfox.i2p 2004-10-25 06:15:47 +00:00
15c227f568 * sliding windows w/ additive increase / multiplicitive decrease
* immediately send an ack on receiving a duplicate payload message
  (unless we've sent one within the last RTT)
* only adjust the RTT when there have been no resends
* added some (disabled) throttles - randomly injecting delays on
  received packets, as well as randomly dropping them
* logging
2004-10-25 03:22:29 +00:00
8de41acfe1 * if we send a blank ACK message (that will not in turn be ACKed) and it
has session tags within it, send an additional ping to the peer,
  bundling those tags a second time, ACKing those tags on the pong.
* handle packets transferred during a race after the receiver ACKs the
  connection but before the establisher receives the ACK.
* notify the messageInputStream reader on close (duh)
* new stream sink test, shoving lots and lots of data down a stream
  with the existing StreamSinkServer and StreamSinkClient apps
* logging
2004-10-24 23:23:35 +00:00
9680effb9f 2004-10-24 jrandom
* Allow explicit inclusion of session tags in the SDK, enabling the
      resending of tags bundled with messages that would not otherwise
      be ACKed.
    * Don't force mode=guaranteed for end to end delivery - if mode=bestEffort
      no DeliveryStatusMessage will be bundled (and as such, client apps using
      it will need to do their own session tag ack/nack).
    * Handle client errors when notifying them of message availability.
    * New StreamSinkSend which sends a file to a destination and disconnects.
    * Update the I2PSocketManagerFactory to build the specific
      I2PSocketManager instance based on the "i2p.streaming.manager" property,
      containing the class name of the I2PSocketManager implementation to instantiate.
2004-10-24 23:00:44 +00:00
40df846e3f logging 2004-10-24 04:59:42 +00:00
eee94fbf84 * deal with writes > the packet size limit
* deal with window size > 1, especially before receiving the first ACK
* disable congestion control for the moment (aka unlimited window size)
2004-10-24 04:56:26 +00:00
813679ba25 2004-10-23 jrandom
* Minor ministreaming lib refactoring to simplify integration of the full
      streaming lib.
    * Minor bugfixes to data structure serialization.
2004-10-24 01:42:34 +00:00
2b9e16c9c9 very basic tests pass (ping, open then pause then close, open then echo back and forth a few times then close) 2004-10-24 00:59:29 +00:00
f9bb7f7cff added underground.i2p 2004-10-22 05:05:28 +00:00
41e9569094 renamed newsbytetest to newsbyte 2004-10-21 23:30:21 +00:00
336ee07191 added newsbytetest.i2p 2004-10-20 03:35:52 +00:00
81e0a145f1 foil evil typo plot 2004-10-18 23:37:49 +00:00
223 changed files with 13096 additions and 2614 deletions

View File

@ -274,9 +274,9 @@ public class PeerData {
_lostRate.addData(numTimedOut, 0);
_receiveRate.coallesceStats();
_sendRate.coallesceStats();
_lostRate.coallesceStats();
_receiveRate.coalesceStats();
_sendRate.coalesceStats();
_lostRate.coalesceStats();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Peer data cleaned up " + numTimedOut + " timed out pings and removed " + numDropped
@ -409,4 +409,4 @@ public class PeerData {
_wasPonged = pong;
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ class I2PSocketImpl implements I2PSocket {
public static final int MAX_PACKET_SIZE = 1024 * 32;
public static final int PACKET_DELAY = 100;
private I2PSocketManager manager;
private I2PSocketManagerImpl manager;
private Destination local;
private Destination remote;
private String localID;
@ -69,7 +69,7 @@ class I2PSocketImpl implements I2PSocket {
* @param outgoing did we initiate the connection (true) or did we receive it (false)?
* @param localID what is our half of the socket ID?
*/
public I2PSocketImpl(Destination peer, I2PSocketManager mgr, boolean outgoing, String localID) {
public I2PSocketImpl(Destination peer, I2PSocketManagerImpl mgr, boolean outgoing, String localID) {
this.outgoing = outgoing;
manager = mgr;
remote = peer;
@ -157,7 +157,7 @@ class I2PSocketImpl implements I2PSocket {
if (_log.shouldLog(Log.DEBUG))
_log.debug("TIMING: RemoteID set to "
+ I2PSocketManager.getReadableForm(remoteID) + " for "
+ I2PSocketManagerImpl.getReadableForm(remoteID) + " for "
+ this.hashCode());
}
return remoteID;
@ -249,9 +249,9 @@ class I2PSocketImpl implements I2PSocket {
private byte getMask(int add) {
if (outgoing)
return (byte)(I2PSocketManager.DATA_IN + (byte)add);
return (byte)(I2PSocketManagerImpl.DATA_IN + (byte)add);
else
return (byte)(I2PSocketManager.DATA_OUT + (byte)add);
return (byte)(I2PSocketManagerImpl.DATA_OUT + (byte)add);
}
public void setOptions(I2PSocketOptions options) {
@ -614,7 +614,7 @@ class I2PSocketImpl implements I2PSocket {
_log.info(getPrefix() + ":" + Thread.currentThread().getName()
+ "Sending close packet: (we started? " + outgoing
+ ") after reading " + _bytesRead + " and writing " + _bytesWritten);
byte[] packet = I2PSocketManager.makePacket(getMask(0x02), remoteID, new byte[0]);
byte[] packet = I2PSocketManagerImpl.makePacket(getMask(0x02), remoteID, new byte[0]);
boolean sent = manager.getSession().sendMessage(remote, packet);
if (!sent) {
if (_log.shouldLog(Log.WARN))
@ -645,7 +645,7 @@ class I2PSocketImpl implements I2PSocket {
_log.error(getPrefix() + "NULL REMOTEID");
return false;
}
byte[] packet = I2PSocketManager.makePacket(getMask(0x00), remoteID, data);
byte[] packet = I2PSocketManagerImpl.makePacket(getMask(0x00), remoteID, data);
boolean sent;
synchronized (flagLock) {
if (closed2) return false;

View File

@ -13,6 +13,7 @@ import java.net.NoRouteToHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
@ -34,62 +35,8 @@ import net.i2p.util.Log;
* or receive any messages with its .receiveMessage
*
*/
public class I2PSocketManager implements I2PSessionListener {
private I2PAppContext _context;
private Log _log;
private I2PSession _session;
private I2PServerSocketImpl _serverSocket = null;
private Object lock = new Object(); // for locking socket lists
private HashMap _outSockets;
private HashMap _inSockets;
private I2PSocketOptions _defaultOptions;
private long _acceptTimeout;
private String _name;
private static int __managerId = 0;
public static final short ACK = 0x51;
public static final short CLOSE_OUT = 0x52;
public static final short DATA_OUT = 0x50;
public static final short SYN = 0xA1;
public static final short CLOSE_IN = 0xA2;
public static final short DATA_IN = 0xA0;
public static final short CHAFF = 0xFF;
/**
* How long to wait for the client app to accept() before sending back CLOSE?
* This includes the time waiting in the queue. Currently set to 5 seconds.
*/
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
public I2PSocketManager() {
this("SocketManager " + (++__managerId));
}
public I2PSocketManager(String name) {
_name = name;
_session = null;
_inSockets = new HashMap(16);
_outSockets = new HashMap(16);
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(I2PSocketManager.class);
_context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.sent", "How many bytes are sent in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.received", "How many bytes are received in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.transferBalance", "How many streams send more than they receive (positive means more sent, negative means more received)?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.synNoAck", "How many times have we sent a SYN but not received an ACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.ackSendFailed", "How many times have we tried to send an ACK to a SYN and failed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.nackSent", "How many times have we refused a SYN with a NACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.nackReceived", "How many times have we received a NACK to our SYN?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
}
public I2PSession getSession() {
return _session;
}
public void setSession(I2PSession session) {
_session = session;
if (session != null) session.setSessionListener(this);
}
public interface I2PSocketManager {
public I2PSession getSession();
/**
* How long should we wait for the client to .accept() a socket before
@ -97,343 +44,14 @@ public class I2PSocketManager implements I2PSessionListener {
*
* @param ms milliseconds to wait, maximum
*/
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
public long getAcceptTimeout() { return _acceptTimeout; }
public void disconnected(I2PSession session) {
_log.info(getName() + ": Disconnected from the session");
destroySocketManager();
}
public void errorOccurred(I2PSession session, String message, Throwable error) {
_log.error(getName() + ": Error occurred: [" + message + "]", error);
}
public void setAcceptTimeout(long ms);
public long getAcceptTimeout();
public void setDefaultOptions(I2PSocketOptions options);
public I2PSocketOptions getDefaultOptions();
public I2PServerSocket getServerSocket();
public void messageAvailable(I2PSession session, int msgId, long size) {
try {
I2PSocketImpl s;
byte msg[] = session.receiveMessage(msgId);
if (msg.length == 1 && msg[0] == -1) {
_log.debug(getName() + ": Ping received");
return;
}
if (msg.length < 4) {
_log.warn(getName() + ": ==== packet too short ====");
return;
}
int type = msg[0] & 0xff;
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
byte[] payload = new byte[msg.length - 4];
System.arraycopy(msg, 4, payload, 0, payload.length);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": Message read: type = [" + Integer.toHexString(type)
+ "] id = [" + getReadableForm(id)
+ "] payload length: [" + payload.length + "]");
switch (type) {
case ACK:
ackAvailable(id, payload);
return;
case CLOSE_OUT:
disconnectAvailable(id, payload);
return;
case DATA_OUT:
sendOutgoingAvailable(id, payload);
return;
case SYN:
synIncomingAvailable(id, payload, session);
return;
case CLOSE_IN:
disconnectIncoming(id, payload);
return;
case DATA_IN:
sendIncoming(id, payload);
case CHAFF:
// ignore
return;
default:
handleUnknown(type, id, payload);
return;
}
} catch (I2PException ise) {
_log.warn(getName() + ": Error processing", ise);
} catch (IllegalStateException ise) {
_log.debug(getName() + ": Error processing", ise);
}
}
/**
* We've received an ACK packet (hopefully, in response to a SYN that we
* recently sent out). Notify the associated I2PSocket that we now have
* the remote stream ID (which should get things going, since the handshake
* is complete).
*
*/
private void ackAvailable(String id, byte payload[]) {
long begin = _context.clock().now();
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _outSockets.get(id);
}
if (s == null) {
_log.warn(getName() + ": No socket responsible for ACK packet for id " + getReadableForm(id));
return;
}
long socketRetrieved = _context.clock().now();
String remoteId = null;
remoteId = s.getRemoteID(false);
if ( (payload.length == 3) && (remoteId == null) ) {
String newID = toString(payload);
long beforeSetRemId = _context.clock().now();
s.setRemoteID(newID);
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getName() + ": ackAvailable - socket retrieval took "
+ (socketRetrieved-begin) + "ms, getRemoteId took "
+ (beforeSetRemId-socketRetrieved) + "ms, setRemoteId took "
+ (_context.clock().now()-beforeSetRemId) + "ms");
}
return;
} else {
// (payload.length != 3 || getRemoteId != null)
if (_log.shouldLog(Log.WARN)) {
if (payload.length != 3)
_log.warn(getName() + ": Ack packet had " + payload.length + " bytes");
else
_log.warn(getName() + ": Remote ID already exists? " + remoteId);
}
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getName() + ": invalid ack - socket retrieval took "
+ (socketRetrieved-begin) + "ms, overall took "
+ (_context.clock().now()-begin) + "ms");
}
return;
}
}
/**
* We received a disconnect packet, telling us to tear down the specified
* stream.
*/
private void disconnectAvailable(String id, byte payload[]) {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _outSockets.get(id);
}
_log.debug(getName() + ": *Disconnect outgoing for socket " + s + " on id "
+ getReadableForm(id));
try {
if (s != null) {
if (payload.length > 0) {
_log.debug(getName() + ": Disconnect packet had "
+ payload.length + " bytes");
}
if (s.getRemoteID(false) == null) {
s.setRemoteID(null); // Just to wake up socket
return;
}
s.internalClose();
synchronized (lock) {
_outSockets.remove(id);
}
}
return;
} catch (Exception t) {
_log.warn(getName() + ": Ignoring error on disconnect for socket " + s, t);
}
}
/**
* We've received data on a stream we created - toss the data onto
* the socket for handling.
*
* @throws IllegalStateException if the socket isn't open or isn't known
*/
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _outSockets.get(id);
}
// packet send outgoing
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": *Packet send outgoing [" + payload.length + "] for socket "
+ s + " on id " + getReadableForm(id));
if (s != null) {
s.queueData(payload);
return;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn(getName() + ": Null socket with data available");
throw new IllegalStateException("Null socket with data available");
}
}
/**
* We've received a SYN packet (a request for a new stream). If the client has
* said they want incoming sockets (by retrieving the serverSocket), the stream
* will be ACKed, but if they have not, they'll be NACKed)
*
* @throws DataFormatException if the destination in the SYN was invalid
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
*/
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
throws DataFormatException, I2PSessionException {
Destination d = new Destination();
d.fromByteArray(payload);
I2PSocketImpl s = null;
boolean acceptConnections = (_serverSocket != null);
String newLocalID = null;
synchronized (lock) {
newLocalID = makeID(_inSockets);
if (acceptConnections) {
s = new I2PSocketImpl(d, this, false, newLocalID);
s.setRemoteID(id);
}
}
_log.debug(getName() + ": *Syn! for socket " + s + " on id " + getReadableForm(newLocalID)
+ " from " + d.calculateHash().toBase64().substring(0,6));
if (!acceptConnections) {
// The app did not instantiate an I2PServerSocket
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
boolean replySentOk = false;
synchronized (_session) {
replySentOk = _session.sendMessage(d, packet);
}
if (!replySentOk) {
_log.warn(getName() + ": Error sending close to " + d.calculateHash().toBase64()
+ " in response to a new con message",
new Exception("Failed creation"));
}
_context.statManager().addRateData("streaming.nackSent", 1, 1);
return;
}
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
_inSockets.put(newLocalID, s);
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
boolean replySentOk = false;
replySentOk = _session.sendMessage(d, packet);
if (!replySentOk) {
if (_log.shouldLog(Log.WARN))
_log.warn(getName() + ": Error sending reply to " + d.calculateHash().toBase64()
+ " in response to a new con message for socket " + s,
new Exception("Failed creation"));
s.internalClose();
_context.statManager().addRateData("streaming.ackSendFailed", 1, 1);
}
} else {
// timed out or serverSocket closed
byte[] packet = toBytes(" " + id);
packet[0] = CLOSE_OUT;
boolean nackSent = session.sendMessage(d, packet);
if (!nackSent) {
_log.warn(getName() + ": Error sending NACK for session creation for socket " + s);
}
s.internalClose();
_context.statManager().addRateData("streaming,nackSent", 1, 1);
}
return;
}
/**
* We've received a disconnect for a socket we didn't initiate, so kill
* the socket.
*
*/
private void disconnectIncoming(String id, byte payload[]) {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _inSockets.get(id);
if (payload.length == 0 && s != null) {
_inSockets.remove(id);
}
}
_log.debug(getName() + ": *Disconnect incoming for socket " + s);
try {
if (payload.length == 0 && s != null) {
s.internalClose();
return;
} else {
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
_log.warn(getName() + ": Disconnect packet had " + payload.length + " bytes");
if (s != null)
s.internalClose();
return;
}
} catch (Exception t) {
_log.warn(getName() + ": Ignoring error on disconnect", t);
return;
}
}
/**
* We've received data on a stream we received - toss the data onto
* the socket for handling.
*
* @throws IllegalStateException if the socket isn't open or isn't known
*/
private void sendIncoming(String id, byte payload[]) throws IllegalStateException {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _inSockets.get(id);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": *Packet send incoming [" + payload.length + "] for socket " + s);
if (s != null) {
s.queueData(payload);
return;
} else {
_log.info(getName() + ": Null socket with data available");
throw new IllegalStateException("Null socket with data available");
}
}
/**
* Unknown packet. moo.
*
*/
private void handleUnknown(int type, String id, byte payload[]) {
_log.error(getName() + ": \n\n=============== Unknown packet! " + "============"
+ "\nType: " + (int) type
+ "\nID: " + getReadableForm(id)
+ "\nBase64'ed Data: " + Base64.encode(payload)
+ "\n\n\n");
if (id != null) {
synchronized (lock) {
_inSockets.remove(id);
_outSockets.remove(id);
}
}
}
public void reportAbuse(I2PSession session, int severity) {
_log.error(getName() + ": Abuse reported [" + severity + "]");
}
public void setDefaultOptions(I2PSocketOptions options) {
_defaultOptions = options;
}
public I2PSocketOptions getDefaultOptions() {
return _defaultOptions;
}
public I2PServerSocket getServerSocket() {
if (_serverSocket == null) {
_serverSocket = new I2PServerSocketImpl(this);
}
return _serverSocket;
}
public I2PSocketOptions buildOptions();
public I2PSocketOptions buildOptions(Properties opts);
/**
* Create a new connected socket (block until the socket is created)
@ -448,117 +66,7 @@ public class I2PSocketManager implements I2PSessionListener {
*/
public I2PSocket connect(Destination peer, I2PSocketOptions options)
throws I2PException, ConnectException,
NoRouteToHostException, InterruptedIOException {
String localID, lcID;
I2PSocketImpl s;
synchronized (lock) {
localID = makeID(_outSockets);
lcID = getReadableForm(localID);
s = new I2PSocketImpl(peer, this, true, localID);
_outSockets.put(localID, s);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": connect(" + peer.calculateHash().toBase64().substring(0,6)
+ ", ...): localID = " + lcID);
try {
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
_session.getMyDestination().writeBytes(pubkey);
String remoteID;
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
boolean sent = false;
sent = _session.sendMessage(peer, packet);
if (!sent) {
_log.info(getName() + ": Unable to send & receive ack for SYN packet for socket "
+ s + " with localID = " + lcID);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
throw new I2PException("Error sending through I2P network");
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": syn sent ok to "
+ peer.calculateHash().toBase64().substring(0,6)
+ " with localID = " + lcID);
}
if (options != null)
remoteID = s.getRemoteID(true, options.getConnectTimeout());
else
remoteID = s.getRemoteID(true, getDefaultOptions().getConnectTimeout());
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": remoteID received from "
+ peer.calculateHash().toBase64().substring(0,6)
+ ": " + getReadableForm(remoteID)
+ " with localID = " + lcID);
if (remoteID == null) {
_context.statManager().addRateData("streaming.nackReceived", 1, 1);
throw new ConnectException("Connection refused by peer for socket " + s);
}
if ("".equals(remoteID)) {
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
throw new NoRouteToHostException("Unable to reach peer for socket " + s);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": TIMING: s given out for remoteID "
+ getReadableForm(remoteID) + " for socket " + s);
return s;
} catch (InterruptedIOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn(getName() + ": Timeout waiting for ack from syn for id "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, ioe);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
throw new InterruptedIOException("Timeout waiting for ack");
} catch (ConnectException ex) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": Connection error waiting for ack from syn for id "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, ex);
s.internalClose();
throw ex;
} catch (NoRouteToHostException ex) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": No route to host waiting for ack from syn for id "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, ex);
s.internalClose();
throw ex;
} catch (IOException ex) {
if (_log.shouldLog(Log.WARN))
_log.warn(getName() + ": Error sending syn on id "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, ex);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw new I2PException("Unhandled IOException occurred");
} catch (I2PException ex) {
if (_log.shouldLog(Log.INFO))
_log.info(getName() + ": Error sending syn on id "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, ex);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw ex;
} catch (Exception e) {
s.internalClose();
_log.warn(getName() + ": Unhandled error connecting on "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, e);
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
}
}
NoRouteToHostException, InterruptedIOException;
/**
* Create a new connected socket (block until the socket is created)
@ -571,182 +79,30 @@ public class I2PSocketManager implements I2PSessionListener {
* @throws I2PException if there is some other I2P-related problem
*/
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
NoRouteToHostException, InterruptedIOException {
return connect(peer, null);
}
NoRouteToHostException, InterruptedIOException;
/**
* Destroy the socket manager, freeing all the associated resources. This
* method will block untill all the managed sockets are closed.
*
*/
public void destroySocketManager() {
if (_serverSocket != null) {
_serverSocket.close();
_serverSocket = null;
}
synchronized (lock) {
Iterator iter;
String id = null;
I2PSocketImpl sock;
iter = _inSockets.keySet().iterator();
while (iter.hasNext()) {
id = (String)iter.next();
sock = (I2PSocketImpl)_inSockets.get(id);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": Closing inSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
sock.internalClose();
}
iter = _outSockets.keySet().iterator();
while (iter.hasNext()) {
id = (String)iter.next();
sock = (I2PSocketImpl)_outSockets.get(id);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": Closing outSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
sock.internalClose();
}
}
_log.debug(getName() + ": Waiting for all open sockets to really close...");
synchronized (lock) {
while ((_inSockets.size() != 0) || (_outSockets.size() != 0)) {
try {
lock.wait();
} catch (InterruptedException e) {}
}
}
try {
_log.debug(getName() + ": Destroying I2P session...");
_session.destroySession();
_log.debug(getName() + ": I2P session destroyed");
} catch (I2PSessionException e) {
_log.warn(getName() + ": Error destroying I2P session", e);
}
}
public void destroySocketManager();
/**
* Retrieve a set of currently connected I2PSockets, either initiated locally or remotely.
*
*/
public Set listSockets() {
Set sockets = new HashSet(8);
synchronized (lock) {
sockets.addAll(_inSockets.values());
sockets.addAll(_outSockets.values());
}
return sockets;
}
public Set listSockets();
/**
* Ping the specified peer, returning true if they replied to the ping within
* the timeout specified, false otherwise. This call blocks.
*
*/
public boolean ping(Destination peer, long timeoutMs) {
try {
return _session.sendMessage(peer, new byte[] { (byte) CHAFF});
} catch (I2PException ex) {
_log.warn(getName() + ": I2PException:", ex);
return false;
}
}
public boolean ping(Destination peer, long timeoutMs);
public void removeSocket(I2PSocketImpl sock) {
String localId = sock.getLocalID();
boolean removed = false;
synchronized (lock) {
removed = (null != _inSockets.remove(localId));
removed = removed || (null != _outSockets.remove(localId));
lock.notify();
}
public String getName();
public void setName(String name);
long now = _context.clock().now();
long lifetime = now - sock.getCreatedOn();
long timeSinceClose = now - sock.getClosedOn();
long sent = sock.getBytesSent();
long recv = sock.getBytesReceived();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getName() + ": Removing socket \"" + getReadableForm(localId) + "\" [" + sock
+ ", send: " + sent + ", recv: " + recv
+ ", lifetime: " + lifetime + "ms, time since close: " + timeSinceClose
+ " removed? " + removed + ")]",
new Exception("removeSocket called"));
}
_context.statManager().addRateData("streaming.lifetime", lifetime, lifetime);
_context.statManager().addRateData("streaming.sent", sent, lifetime);
_context.statManager().addRateData("streaming.received", recv, lifetime);
if (sent > recv) {
_context.statManager().addRateData("streaming.transferBalance", 1, lifetime);
} else if (recv > sent) {
_context.statManager().addRateData("streaming.transferBalance", -1, lifetime);
} else {
// noop
}
}
public String getName() { return _name; }
public void setName(String name) { _name = name; }
public static String getReadableForm(String id) {
if (id == null) return "(null)";
if (id.length() != 3) return "Bogus";
return Base64.encode(toBytes(id));
}
/**
* Create a new part the connection ID that is locally unique
*
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
*/
private static String makeID(HashMap uniqueIn) {
String newID;
do {
int id = (int) (Math.random() * 16777215 + 1);
byte[] nid = new byte[3];
nid[0] = (byte) (id / 65536);
nid[1] = (byte) ((id / 256) % 256);
nid[2] = (byte) (id % 256);
newID = toString(nid);
} while (uniqueIn.get(newID) != null);
return newID;
}
/**
* Create a new packet of the given type for the specified connection containing
* the given payload
*/
public static byte[] makePacket(byte type, String id, byte[] payload) {
byte[] packet = new byte[payload.length + 4];
packet[0] = type;
byte[] temp = toBytes(id);
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
System.arraycopy(temp, 0, packet, 1, 3);
System.arraycopy(payload, 0, packet, 4, payload.length);
return packet;
}
private static final String toString(byte data[]) {
try {
return new String(data, "ISO-8859-1");
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
}
}
private static final byte[] toBytes(String str) {
try {
return str.getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
}
}
public void init(I2PAppContext context, I2PSession session, Properties opts, String name);
}

View File

@ -4,8 +4,10 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
@ -23,6 +25,10 @@ import net.i2p.util.Log;
public class I2PSocketManagerFactory {
private final static Log _log = new Log(I2PSocketManagerFactory.class);
public static final String PROP_MANAGER = "i2p.streaming.manager";
//public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerImpl";
public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerFull";
/**
* Create a socket manager using a brand new destination connected to the
* I2CP router on the local machine on the default port (7654).
@ -76,23 +82,75 @@ public class I2PSocketManagerFactory {
public static I2PSocketManager createManager(InputStream myPrivateKeyStream, String i2cpHost, int i2cpPort,
Properties opts) {
I2PClient client = I2PClientFactory.createClient();
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
if (opts == null)
opts = new Properties();
for (Iterator iter = System.getProperties().keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
if (!opts.containsKey(name))
opts.setProperty(name, System.getProperty(name));
}
boolean oldLib = DEFAULT_MANAGER.equals(opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER));
if (oldLib && false) {
// for the old streaming lib
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
//opts.setProperty("tunnels.depthInbound", "0");
} else {
// for new streaming lib:
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT);
//p.setProperty("tunnels.depthInbound", "0");
}
if (i2cpHost != null)
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
try {
I2PSession session = client.createSession(myPrivateKeyStream, opts);
session.connect();
return createManager(session);
I2PSocketManager sockMgr = createManager(session, opts, "manager");
if (sockMgr != null)
sockMgr.setDefaultOptions(sockMgr.buildOptions(opts));
return sockMgr;
} catch (I2PSessionException ise) {
_log.error("Error creating session for socket manager", ise);
return null;
}
}
private static I2PSocketManager createManager(I2PSession session) {
I2PSocketManager mgr = new I2PSocketManager();
mgr.setSession(session);
mgr.setDefaultOptions(new I2PSocketOptions());
return mgr;
private static I2PSocketManager createManager(I2PSession session, Properties opts, String name) {
if (false) {
I2PSocketManagerImpl mgr = new I2PSocketManagerImpl();
mgr.setSession(session);
//mgr.setDefaultOptions(new I2PSocketOptions());
return mgr;
} else {
String classname = opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER);
if (classname != null) {
try {
Class cls = Class.forName(classname);
Object obj = cls.newInstance();
if (obj instanceof I2PSocketManager) {
I2PSocketManager mgr = (I2PSocketManager)obj;
I2PAppContext context = I2PAppContext.getGlobalContext();
mgr.init(context, session, opts, name);
return mgr;
} else {
throw new IllegalStateException("Invalid manager class [" + classname + "]");
}
} catch (ClassNotFoundException cnfe) {
_log.error("Error loading " + classname, cnfe);
throw new IllegalStateException("Invalid manager class [" + classname + "] - not found");
} catch (InstantiationException ie) {
_log.error("Error loading " + classname, ie);
throw new IllegalStateException("Invalid manager class [" + classname + "] - unable to instantiate");
} catch (IllegalAccessException iae) {
_log.error("Error loading " + classname, iae);
throw new IllegalStateException("Invalid manager class [" + classname + "] - illegal access");
}
} else {
throw new IllegalStateException("No manager class specified");
}
}
}
}

View File

@ -0,0 +1,763 @@
/*
* licensed under BSD license...
* (if you know the proper clause for that, add it ...)
*/
package net.i2p.client.streaming;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Centralize the coordination and multiplexing of the local client's streaming.
* There should be one I2PSocketManager for each I2PSession, and if an application
* is sending and receiving data through the streaming library using an
* I2PSocketManager, it should not attempt to call I2PSession's setSessionListener
* or receive any messages with its .receiveMessage
*
*/
class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
private I2PAppContext _context;
private Log _log;
private I2PSession _session;
private I2PServerSocketImpl _serverSocket = null;
private Object lock = new Object(); // for locking socket lists
private HashMap _outSockets;
private HashMap _inSockets;
private I2PSocketOptions _defaultOptions;
private long _acceptTimeout;
private String _name;
private static int __managerId = 0;
public static final short ACK = 0x51;
public static final short CLOSE_OUT = 0x52;
public static final short DATA_OUT = 0x50;
public static final short SYN = 0xA1;
public static final short CLOSE_IN = 0xA2;
public static final short DATA_IN = 0xA0;
public static final short CHAFF = 0xFF;
/**
* How long to wait for the client app to accept() before sending back CLOSE?
* This includes the time waiting in the queue. Currently set to 5 seconds.
*/
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
public I2PSocketManagerImpl() {
this("SocketManager " + (++__managerId));
}
public I2PSocketManagerImpl(String name) {
init(I2PAppContext.getGlobalContext(), null, null, name);
}
public void init(I2PAppContext context, I2PSession session, Properties opts, String name) {
_name = name;
_context = context;
_log = _context.logManager().getLog(I2PSocketManager.class);
_inSockets = new HashMap(16);
_outSockets = new HashMap(16);
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
setSession(session);
setDefaultOptions(buildOptions(opts));
_context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.sent", "How many bytes are sent in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.received", "How many bytes are received in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.transferBalance", "How many streams send more than they receive (positive means more sent, negative means more received)?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.synNoAck", "How many times have we sent a SYN but not received an ACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.ackSendFailed", "How many times have we tried to send an ACK to a SYN and failed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.nackSent", "How many times have we refused a SYN with a NACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("streaming.nackReceived", "How many times have we received a NACK to our SYN?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
}
public I2PSession getSession() {
return _session;
}
public void setSession(I2PSession session) {
_session = session;
if (session != null) session.setSessionListener(this);
}
/**
* How long should we wait for the client to .accept() a socket before
* sending back a NACK/Close?
*
* @param ms milliseconds to wait, maximum
*/
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
public long getAcceptTimeout() { return _acceptTimeout; }
public void disconnected(I2PSession session) {
_log.info(getName() + ": Disconnected from the session");
destroySocketManager();
}
public void errorOccurred(I2PSession session, String message, Throwable error) {
_log.error(getName() + ": Error occurred: [" + message + "]", error);
}
public void messageAvailable(I2PSession session, int msgId, long size) {
try {
I2PSocketImpl s;
byte msg[] = session.receiveMessage(msgId);
if (msg.length == 1 && msg[0] == -1) {
_log.debug(getName() + ": Ping received");
return;
}
if (msg.length < 4) {
_log.warn(getName() + ": ==== packet too short ====");
return;
}
int type = msg[0] & 0xff;
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
byte[] payload = new byte[msg.length - 4];
System.arraycopy(msg, 4, payload, 0, payload.length);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": Message read: type = [" + Integer.toHexString(type)
+ "] id = [" + getReadableForm(id)
+ "] payload length: [" + payload.length + "]");
switch (type) {
case ACK:
ackAvailable(id, payload);
return;
case CLOSE_OUT:
disconnectAvailable(id, payload);
return;
case DATA_OUT:
sendOutgoingAvailable(id, payload);
return;
case SYN:
synIncomingAvailable(id, payload, session);
return;
case CLOSE_IN:
disconnectIncoming(id, payload);
return;
case DATA_IN:
sendIncoming(id, payload);
case CHAFF:
// ignore
return;
default:
handleUnknown(type, id, payload);
return;
}
} catch (I2PException ise) {
_log.warn(getName() + ": Error processing", ise);
} catch (IllegalStateException ise) {
_log.debug(getName() + ": Error processing", ise);
}
}
/**
* We've received an ACK packet (hopefully, in response to a SYN that we
* recently sent out). Notify the associated I2PSocket that we now have
* the remote stream ID (which should get things going, since the handshake
* is complete).
*
*/
private void ackAvailable(String id, byte payload[]) {
long begin = _context.clock().now();
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _outSockets.get(id);
}
if (s == null) {
_log.warn(getName() + ": No socket responsible for ACK packet for id " + getReadableForm(id));
return;
}
long socketRetrieved = _context.clock().now();
String remoteId = null;
remoteId = s.getRemoteID(false);
if ( (payload.length == 3) && (remoteId == null) ) {
String newID = toString(payload);
long beforeSetRemId = _context.clock().now();
s.setRemoteID(newID);
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getName() + ": ackAvailable - socket retrieval took "
+ (socketRetrieved-begin) + "ms, getRemoteId took "
+ (beforeSetRemId-socketRetrieved) + "ms, setRemoteId took "
+ (_context.clock().now()-beforeSetRemId) + "ms");
}
return;
} else {
// (payload.length != 3 || getRemoteId != null)
if (_log.shouldLog(Log.WARN)) {
if (payload.length != 3)
_log.warn(getName() + ": Ack packet had " + payload.length + " bytes");
else
_log.warn(getName() + ": Remote ID already exists? " + remoteId);
}
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getName() + ": invalid ack - socket retrieval took "
+ (socketRetrieved-begin) + "ms, overall took "
+ (_context.clock().now()-begin) + "ms");
}
return;
}
}
/**
* We received a disconnect packet, telling us to tear down the specified
* stream.
*/
private void disconnectAvailable(String id, byte payload[]) {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _outSockets.get(id);
}
_log.debug(getName() + ": *Disconnect outgoing for socket " + s + " on id "
+ getReadableForm(id));
try {
if (s != null) {
if (payload.length > 0) {
_log.debug(getName() + ": Disconnect packet had "
+ payload.length + " bytes");
}
if (s.getRemoteID(false) == null) {
s.setRemoteID(null); // Just to wake up socket
return;
}
s.internalClose();
synchronized (lock) {
_outSockets.remove(id);
}
}
return;
} catch (Exception t) {
_log.warn(getName() + ": Ignoring error on disconnect for socket " + s, t);
}
}
/**
* We've received data on a stream we created - toss the data onto
* the socket for handling.
*
* @throws IllegalStateException if the socket isn't open or isn't known
*/
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _outSockets.get(id);
}
// packet send outgoing
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": *Packet send outgoing [" + payload.length + "] for socket "
+ s + " on id " + getReadableForm(id));
if (s != null) {
s.queueData(payload);
return;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn(getName() + ": Null socket with data available");
throw new IllegalStateException("Null socket with data available");
}
}
/**
* We've received a SYN packet (a request for a new stream). If the client has
* said they want incoming sockets (by retrieving the serverSocket), the stream
* will be ACKed, but if they have not, they'll be NACKed)
*
* @throws DataFormatException if the destination in the SYN was invalid
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
*/
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
throws DataFormatException, I2PSessionException {
Destination d = new Destination();
d.fromByteArray(payload);
I2PSocketImpl s = null;
boolean acceptConnections = (_serverSocket != null);
String newLocalID = null;
synchronized (lock) {
newLocalID = makeID(_inSockets);
if (acceptConnections) {
s = new I2PSocketImpl(d, this, false, newLocalID);
s.setRemoteID(id);
}
}
_log.debug(getName() + ": *Syn! for socket " + s + " on id " + getReadableForm(newLocalID)
+ " from " + d.calculateHash().toBase64().substring(0,6));
if (!acceptConnections) {
// The app did not instantiate an I2PServerSocket
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
boolean replySentOk = false;
synchronized (_session) {
replySentOk = _session.sendMessage(d, packet);
}
if (!replySentOk) {
_log.warn(getName() + ": Error sending close to " + d.calculateHash().toBase64()
+ " in response to a new con message",
new Exception("Failed creation"));
}
_context.statManager().addRateData("streaming.nackSent", 1, 1);
return;
}
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
_inSockets.put(newLocalID, s);
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
boolean replySentOk = false;
replySentOk = _session.sendMessage(d, packet);
if (!replySentOk) {
if (_log.shouldLog(Log.WARN))
_log.warn(getName() + ": Error sending reply to " + d.calculateHash().toBase64()
+ " in response to a new con message for socket " + s,
new Exception("Failed creation"));
s.internalClose();
_context.statManager().addRateData("streaming.ackSendFailed", 1, 1);
}
} else {
// timed out or serverSocket closed
byte[] packet = toBytes(" " + id);
packet[0] = CLOSE_OUT;
boolean nackSent = session.sendMessage(d, packet);
if (!nackSent) {
_log.warn(getName() + ": Error sending NACK for session creation for socket " + s);
}
s.internalClose();
_context.statManager().addRateData("streaming,nackSent", 1, 1);
}
return;
}
/**
* We've received a disconnect for a socket we didn't initiate, so kill
* the socket.
*
*/
private void disconnectIncoming(String id, byte payload[]) {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _inSockets.get(id);
if (payload.length == 0 && s != null) {
_inSockets.remove(id);
}
}
_log.debug(getName() + ": *Disconnect incoming for socket " + s);
try {
if (payload.length == 0 && s != null) {
s.internalClose();
return;
} else {
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
_log.warn(getName() + ": Disconnect packet had " + payload.length + " bytes");
if (s != null)
s.internalClose();
return;
}
} catch (Exception t) {
_log.warn(getName() + ": Ignoring error on disconnect", t);
return;
}
}
/**
* We've received data on a stream we received - toss the data onto
* the socket for handling.
*
* @throws IllegalStateException if the socket isn't open or isn't known
*/
private void sendIncoming(String id, byte payload[]) throws IllegalStateException {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _inSockets.get(id);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": *Packet send incoming [" + payload.length + "] for socket " + s);
if (s != null) {
s.queueData(payload);
return;
} else {
_log.info(getName() + ": Null socket with data available");
throw new IllegalStateException("Null socket with data available");
}
}
/**
* Unknown packet. moo.
*
*/
private void handleUnknown(int type, String id, byte payload[]) {
_log.error(getName() + ": \n\n=============== Unknown packet! " + "============"
+ "\nType: " + (int) type
+ "\nID: " + getReadableForm(id)
+ "\nBase64'ed Data: " + Base64.encode(payload)
+ "\n\n\n");
if (id != null) {
synchronized (lock) {
_inSockets.remove(id);
_outSockets.remove(id);
}
}
}
public void reportAbuse(I2PSession session, int severity) {
_log.error(getName() + ": Abuse reported [" + severity + "]");
}
public void setDefaultOptions(I2PSocketOptions options) {
_defaultOptions = options;
}
public I2PSocketOptions getDefaultOptions() {
return _defaultOptions;
}
public I2PSocketOptions buildOptions() { return buildOptions(null); }
public I2PSocketOptions buildOptions(Properties opts) {
return new I2PSocketOptionsImpl(opts);
}
public I2PServerSocket getServerSocket() {
if (_serverSocket == null) {
_serverSocket = new I2PServerSocketImpl(this);
}
return _serverSocket;
}
/**
* Create a new connected socket (block until the socket is created)
*
* @param peer Destination to connect to
* @param options I2P socket options to be used for connecting
*
* @throws ConnectException if the peer refuses the connection
* @throws NoRouteToHostException if the peer is not found or not reachable
* @throws InterruptedIOException if the connection timeouts
* @throws I2PException if there is some other I2P-related problem
*/
public I2PSocket connect(Destination peer, I2PSocketOptions options)
throws I2PException, ConnectException,
NoRouteToHostException, InterruptedIOException {
String localID, lcID;
I2PSocketImpl s;
synchronized (lock) {
localID = makeID(_outSockets);
lcID = getReadableForm(localID);
s = new I2PSocketImpl(peer, this, true, localID);
_outSockets.put(localID, s);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": connect(" + peer.calculateHash().toBase64().substring(0,6)
+ ", ...): localID = " + lcID);
try {
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
_session.getMyDestination().writeBytes(pubkey);
String remoteID;
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
boolean sent = false;
sent = _session.sendMessage(peer, packet);
if (!sent) {
_log.info(getName() + ": Unable to send & receive ack for SYN packet for socket "
+ s + " with localID = " + lcID);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
throw new I2PException("Error sending through I2P network");
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": syn sent ok to "
+ peer.calculateHash().toBase64().substring(0,6)
+ " with localID = " + lcID);
}
if (options != null)
remoteID = s.getRemoteID(true, options.getConnectTimeout());
else
remoteID = s.getRemoteID(true, getDefaultOptions().getConnectTimeout());
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": remoteID received from "
+ peer.calculateHash().toBase64().substring(0,6)
+ ": " + getReadableForm(remoteID)
+ " with localID = " + lcID);
if (remoteID == null) {
_context.statManager().addRateData("streaming.nackReceived", 1, 1);
throw new ConnectException("Connection refused by peer for socket " + s);
}
if ("".equals(remoteID)) {
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
throw new NoRouteToHostException("Unable to reach peer for socket " + s);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": TIMING: s given out for remoteID "
+ getReadableForm(remoteID) + " for socket " + s);
return s;
} catch (InterruptedIOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn(getName() + ": Timeout waiting for ack from syn for id "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, ioe);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
throw new InterruptedIOException("Timeout waiting for ack");
} catch (ConnectException ex) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": Connection error waiting for ack from syn for id "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, ex);
s.internalClose();
throw ex;
} catch (NoRouteToHostException ex) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": No route to host waiting for ack from syn for id "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, ex);
s.internalClose();
throw ex;
} catch (IOException ex) {
if (_log.shouldLog(Log.WARN))
_log.warn(getName() + ": Error sending syn on id "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, ex);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw new I2PException("Unhandled IOException occurred");
} catch (I2PException ex) {
if (_log.shouldLog(Log.INFO))
_log.info(getName() + ": Error sending syn on id "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, ex);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw ex;
} catch (Exception e) {
s.internalClose();
_log.warn(getName() + ": Unhandled error connecting on "
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
+ " for socket " + s, e);
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
}
}
/**
* Create a new connected socket (block until the socket is created)
*
* @param peer Destination to connect to
*
* @throws ConnectException if the peer refuses the connection
* @throws NoRouteToHostException if the peer is not found or not reachable
* @throws InterruptedIOException if the connection timeouts
* @throws I2PException if there is some other I2P-related problem
*/
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
NoRouteToHostException, InterruptedIOException {
return connect(peer, null);
}
/**
* Destroy the socket manager, freeing all the associated resources. This
* method will block untill all the managed sockets are closed.
*
*/
public void destroySocketManager() {
if (_serverSocket != null) {
_serverSocket.close();
_serverSocket = null;
}
synchronized (lock) {
Iterator iter;
String id = null;
I2PSocketImpl sock;
iter = _inSockets.keySet().iterator();
while (iter.hasNext()) {
id = (String)iter.next();
sock = (I2PSocketImpl)_inSockets.get(id);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": Closing inSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
sock.internalClose();
}
iter = _outSockets.keySet().iterator();
while (iter.hasNext()) {
id = (String)iter.next();
sock = (I2PSocketImpl)_outSockets.get(id);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getName() + ": Closing outSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
sock.internalClose();
}
}
_log.debug(getName() + ": Waiting for all open sockets to really close...");
synchronized (lock) {
while ((_inSockets.size() != 0) || (_outSockets.size() != 0)) {
try {
lock.wait();
} catch (InterruptedException e) {}
}
}
try {
_log.debug(getName() + ": Destroying I2P session...");
_session.destroySession();
_log.debug(getName() + ": I2P session destroyed");
} catch (I2PSessionException e) {
_log.warn(getName() + ": Error destroying I2P session", e);
}
}
/**
* Retrieve a set of currently connected I2PSockets, either initiated locally or remotely.
*
*/
public Set listSockets() {
Set sockets = new HashSet(8);
synchronized (lock) {
sockets.addAll(_inSockets.values());
sockets.addAll(_outSockets.values());
}
return sockets;
}
/**
* Ping the specified peer, returning true if they replied to the ping within
* the timeout specified, false otherwise. This call blocks.
*
*/
public boolean ping(Destination peer, long timeoutMs) {
try {
return _session.sendMessage(peer, new byte[] { (byte) CHAFF});
} catch (I2PException ex) {
_log.warn(getName() + ": I2PException:", ex);
return false;
}
}
public void removeSocket(I2PSocketImpl sock) {
String localId = sock.getLocalID();
boolean removed = false;
synchronized (lock) {
removed = (null != _inSockets.remove(localId));
removed = removed || (null != _outSockets.remove(localId));
lock.notify();
}
long now = _context.clock().now();
long lifetime = now - sock.getCreatedOn();
long timeSinceClose = now - sock.getClosedOn();
long sent = sock.getBytesSent();
long recv = sock.getBytesReceived();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getName() + ": Removing socket \"" + getReadableForm(localId) + "\" [" + sock
+ ", send: " + sent + ", recv: " + recv
+ ", lifetime: " + lifetime + "ms, time since close: " + timeSinceClose
+ " removed? " + removed + ")]",
new Exception("removeSocket called"));
}
_context.statManager().addRateData("streaming.lifetime", lifetime, lifetime);
_context.statManager().addRateData("streaming.sent", sent, lifetime);
_context.statManager().addRateData("streaming.received", recv, lifetime);
if (sent > recv) {
_context.statManager().addRateData("streaming.transferBalance", 1, lifetime);
} else if (recv > sent) {
_context.statManager().addRateData("streaming.transferBalance", -1, lifetime);
} else {
// noop
}
}
public String getName() { return _name; }
public void setName(String name) { _name = name; }
public static String getReadableForm(String id) {
if (id == null) return "(null)";
if (id.length() != 3) return "Bogus";
return Base64.encode(toBytes(id));
}
/**
* Create a new part the connection ID that is locally unique
*
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
*/
private static String makeID(HashMap uniqueIn) {
String newID;
do {
int id = (int) (Math.random() * 16777215 + 1);
byte[] nid = new byte[3];
nid[0] = (byte) (id / 65536);
nid[1] = (byte) ((id / 256) % 256);
nid[2] = (byte) (id % 256);
newID = toString(nid);
} while (uniqueIn.get(newID) != null);
return newID;
}
/**
* Create a new packet of the given type for the specified connection containing
* the given payload
*/
public static byte[] makePacket(byte type, String id, byte[] payload) {
byte[] packet = new byte[payload.length + 4];
packet[0] = type;
byte[] temp = toBytes(id);
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
System.arraycopy(temp, 0, packet, 1, 3);
System.arraycopy(payload, 0, packet, 4, payload.length);
return packet;
}
private static final String toString(byte data[]) {
try {
return new String(data, "ISO-8859-1");
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
}
}
private static final byte[] toBytes(String str) {
try {
return str.getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
}
}
}

View File

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

View File

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

View File

@ -8,6 +8,7 @@ import java.io.OutputStream;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.util.Properties;
import java.util.Random;
import net.i2p.I2PAppContext;
@ -26,6 +27,8 @@ public class StreamSinkClient {
private int _sendSize;
private int _writeDelay;
private String _peerDestFile;
private String _i2cpHost;
private int _i2cpPort;
/**
@ -35,6 +38,11 @@ public class StreamSinkClient {
* @param serverDestFile file containing the StreamSinkServer's binary Destination
*/
public StreamSinkClient(int sendSize, int writeDelayMs, String serverDestFile) {
this(null, -1, sendSize, writeDelayMs, serverDestFile);
}
public StreamSinkClient(String i2cpHost, int i2cpPort, int sendSize, int writeDelayMs, String serverDestFile) {
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
_sendSize = sendSize;
_writeDelay = writeDelayMs;
_peerDestFile = serverDestFile;
@ -46,7 +54,11 @@ public class StreamSinkClient {
*
*/
public void runClient() {
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
I2PSocketManager mgr = null;
if (_i2cpHost != null)
mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties());
else
mgr = I2PSocketManagerFactory.createManager();
Destination peer = null;
FileInputStream fis = null;
try {
@ -81,9 +93,9 @@ public class StreamSinkClient {
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
}
}
sock.close();
long afterSending = System.currentTimeMillis();
System.out.println("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms");
sock.close();
} catch (InterruptedIOException iie) {
_log.error("Timeout connecting to the peer", iie);
return;
@ -103,7 +115,7 @@ public class StreamSinkClient {
}
/**
* Fire up the client. <code>Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile</code> <br />
* Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile</code> <br />
* <ul>
* <li><b>sendSizeKB</b>: how many KB to send</li>
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
@ -111,25 +123,40 @@ public class StreamSinkClient {
* </ul>
*/
public static void main(String args[]) {
if (args.length != 3) {
System.out.println("Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile");
} else {
int sendSizeKB = -1;
int writeDelayMs = -1;
try {
sendSizeKB = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
System.err.println("Send size invalid [" + args[0] + "]");
return;
}
try {
writeDelayMs = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
System.err.println("Write delay ms invalid [" + args[1] + "]");
return;
}
StreamSinkClient client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
client.runClient();
StreamSinkClient client = null;
int sendSizeKB = -1;
int writeDelayMs = -1;
switch (args.length) {
case 3:
try {
sendSizeKB = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
System.err.println("Send size invalid [" + args[0] + "]");
return;
}
try {
writeDelayMs = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
System.err.println("Write delay ms invalid [" + args[1] + "]");
return;
}
client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
break;
case 5:
try {
int port = Integer.parseInt(args[1]);
sendSizeKB = Integer.parseInt(args[2]);
writeDelayMs = Integer.parseInt(args[3]);
client = new StreamSinkClient(args[0], port, sendSizeKB, writeDelayMs, args[4]);
} catch (NumberFormatException nfe) {
System.err.println("arg error");
}
break;
default:
System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile");
}
if (client != null)
client.runClient();
}
}

View File

@ -0,0 +1,132 @@
package net.i2p.client.streaming;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.util.Random;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.data.Destination;
import net.i2p.data.DataFormatException;
import net.i2p.util.Log;
/**
* Simple streaming lib test app that connects to a given destination and sends
* the contents of a file, then disconnects. See the {@link #main}
*
*/
public class StreamSinkSend {
private Log _log;
private String _sendFile;
private int _writeDelay;
private String _peerDestFile;
/**
* Build the client but don't fire it up.
* @param filename file to send
* @param writeDelayMs how long to wait between each .write (0 for no delay)
* @param serverDestFile file containing the StreamSinkServer's binary Destination
*/
public StreamSinkSend(String filename, int writeDelayMs, String serverDestFile) {
_sendFile = filename;
_writeDelay = writeDelayMs;
_peerDestFile = serverDestFile;
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkClient.class);
}
/**
* Actually connect and run the client - this call blocks until completion.
*
*/
public void runClient() {
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
Destination peer = null;
FileInputStream fis = null;
try {
fis = new FileInputStream(_peerDestFile);
peer = new Destination();
peer.readBytes(fis);
} catch (IOException ioe) {
_log.error("Error finding the peer destination to contact in " + _peerDestFile, ioe);
return;
} catch (DataFormatException dfe) {
_log.error("Peer destination is not valid in " + _peerDestFile, dfe);
return;
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
System.out.println("Send " + _sendFile + " to " + peer.calculateHash().toBase64());
try {
I2PSocket sock = mgr.connect(peer);
byte buf[] = new byte[32*1024];
OutputStream out = sock.getOutputStream();
long beforeSending = System.currentTimeMillis();
fis = new FileInputStream(_sendFile);
long size = 0;
while (true) {
int read = fis.read(buf);
if (read < 0)
break;
out.write(buf, 0, read);
size += read;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Wrote " + read);
if (_writeDelay > 0) {
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
}
}
fis.close();
sock.close();
long afterSending = System.currentTimeMillis();
System.out.println("Sent " + (size / 1024) + "KB in " + (afterSending-beforeSending) + "ms");
} catch (InterruptedIOException iie) {
_log.error("Timeout connecting to the peer", iie);
return;
} catch (NoRouteToHostException nrthe) {
_log.error("Unable to connect to the peer", nrthe);
return;
} catch (ConnectException ce) {
_log.error("Connection already dropped", ce);
return;
} catch (I2PException ie) {
_log.error("Error connecting to the peer", ie);
return;
} catch (IOException ioe) {
_log.error("IO error sending", ioe);
return;
}
}
/**
* Fire up the client. <code>Usage: StreamSinkClient sendFile writeDelayMs serverDestFile</code> <br />
* <ul>
* <li><b>sendFile</b>: filename to send</li>
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
* <li><b>serverDestFile</b>: file containing the StreamSinkServer's binary Destination</li>
* </ul>
*/
public static void main(String args[]) {
if (args.length != 3) {
System.out.println("Usage: StreamSinkClient sendFile writeDelayMs serverDestFile");
} else {
int writeDelayMs = -1;
try {
writeDelayMs = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
System.err.println("Write delay ms invalid [" + args[1] + "]");
return;
}
StreamSinkSend client = new StreamSinkSend(args[0], writeDelayMs, args[2]);
client.runClient();
}
}
}

View File

@ -6,6 +6,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
@ -23,6 +24,8 @@ public class StreamSinkServer {
private Log _log;
private String _sinkDir;
private String _destFile;
private String _i2cpHost;
private int _i2cpPort;
/**
* Create but do not start the streaming server.
@ -31,8 +34,13 @@ public class StreamSinkServer {
* @param ourDestFile filename to write our binary destination to
*/
public StreamSinkServer(String sinkDir, String ourDestFile) {
this(sinkDir, ourDestFile, null, -1);
}
public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort) {
_sinkDir = sinkDir;
_destFile = ourDestFile;
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkServer.class);
}
@ -42,7 +50,11 @@ public class StreamSinkServer {
*
*/
public void runServer() {
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
I2PSocketManager mgr = null;
if (_i2cpHost != null)
mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties());
else
mgr = I2PSocketManagerFactory.createManager();
Destination dest = mgr.getSession().getMyDestination();
System.out.println("Listening for connections on: " + dest.calculateHash().toBase64());
FileOutputStream fos = null;
@ -95,6 +107,7 @@ public class StreamSinkServer {
sink.mkdirs();
File cur = File.createTempFile("clientSink", ".dat", sink);
_fos = new FileOutputStream(cur);
System.out.println("Writing to " + cur.getAbsolutePath());
} catch (IOException ioe) {
_log.error("Error creating sink", ioe);
_fos = null;
@ -105,31 +118,53 @@ public class StreamSinkServer {
try {
InputStream in = _sock.getInputStream();
byte buf[] = new byte[4096];
long written = 0;
int read = 0;
while ( (read = in.read(buf)) != -1) {
_fos.write(buf, 0, read);
written += read;
if (_log.shouldLog(Log.DEBUG))
_log.debug("read and wrote " + read);
}
_log.error("Got EOF from client socket [written=" + written + "]");
} catch (IOException ioe) {
_log.error("Error writing the sink", ioe);
} finally {
if (_fos != null) try { _fos.close(); } catch (IOException ioe) {}
if (_sock != null) try { _sock.close(); } catch (IOException ioe) {}
_log.error("Client socket closed");
}
}
}
/**
* Fire up the streaming server. <code>Usage: StreamSinkServer sinkDir ourDestFile</code><br />
* Fire up the streaming server. <code>Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile</code><br />
* <ul>
* <li><b>sinkDir</b>: Directory to store received files in</li>
* <li><b>ourDestFile</b>: filename to write our binary destination to</li>
* </ul>
*/
public static void main(String args[]) {
if (args.length != 2) {
System.out.println("Usage: StreamSinkServer sinkDir ourDestFile");
} else {
StreamSinkServer server = new StreamSinkServer(args[0], args[1]);
server.runServer();
StreamSinkServer server = null;
switch (args.length) {
case 0:
server = new StreamSinkServer("dataDir", "server.key", "localhost", 10001);
break;
case 2:
server = new StreamSinkServer(args[0], args[1]);
break;
case 4:
try {
int port = Integer.parseInt(args[1]);
server = new StreamSinkServer(args[2], args[3], args[0], port);
} catch (NumberFormatException nfe) {
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
}
break;
default:
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
}
if (server != null)
server.runServer();
}
}

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), "localhost", 10001, 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

@ -206,11 +206,11 @@ public class NetMonitor {
}
/** drop all the old summary data */
public void coallesceData() {
public void coalesceData() {
synchronized (_peerSummaries) {
for (Iterator iter = _peerSummaries.values().iterator(); iter.hasNext(); ) {
PeerSummary summary = (PeerSummary)iter.next();
summary.coallesceData(_summaryDurationHours * 60*60*1000);
summary.coalesceData(_summaryDurationHours * 60*60*1000);
}
}
}

View File

@ -44,7 +44,7 @@ class NetMonitorRunner implements Runnable {
long nextExport = now + _monitor.getExportDelay() * 1000;
while (_monitor.isRunning()) {
now = Clock.getInstance().now();
_monitor.coallesceData();
_monitor.coalesceData();
if (now >= nextHarvest) {
runHarvest();
nextHarvest = now + _monitor.getHarvestDelay() * 1000;

View File

@ -22,7 +22,7 @@ public class PeerSummary {
/** statName to a List of PeerStat elements (sorted by sample date, earliest first) */
private Map _stats;
/** lock on this when accessing stat data */
private Object _coallesceLock = new Object();
private Object _coalesceLock = new Object();
public PeerSummary(String peer) {
_peer = peer;
@ -38,7 +38,7 @@ public class PeerSummary {
* @param val actual data harvested
*/
public void addData(String stat, String description, String valueDescriptions[], long when, double val[]) {
synchronized (_coallesceLock) {
synchronized (_coalesceLock) {
TreeMap stats = locked_getData(stat);
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
}
@ -53,7 +53,7 @@ public class PeerSummary {
* @param val actual data harvested
*/
public void addData(String stat, String description, String valueDescriptions[], long when, long val[]) {
synchronized (_coallesceLock) {
synchronized (_coalesceLock) {
TreeMap stats = locked_getData(stat);
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
}
@ -68,7 +68,7 @@ public class PeerSummary {
*
*/
public List getData(String statName) {
synchronized (_coallesceLock) {
synchronized (_coalesceLock) {
return new ArrayList(((TreeMap)_stats.get(statName)).values());
}
}
@ -78,21 +78,21 @@ public class PeerSummary {
*
*/
public Set getStatNames() {
synchronized (_coallesceLock) {
synchronized (_coalesceLock) {
return new HashSet(_stats.keySet());
}
}
/** drop old data points */
public void coallesceData(long summaryDurationMs) {
public void coalesceData(long summaryDurationMs) {
long earliest = Clock.getInstance().now() - summaryDurationMs;
synchronized (_coallesceLock) {
locked_coallesce(earliest);
synchronized (_coalesceLock) {
locked_coalesce(earliest);
}
}
/** go through all the stats and remove ones from before the given date */
private void locked_coallesce(long earliestSampleDate) {
private void locked_coalesce(long earliestSampleDate) {
if (true) return;
for (Iterator iter = _stats.keySet().iterator(); iter.hasNext(); ) {
String statName = (String)iter.next();
@ -116,4 +116,4 @@ public class PeerSummary {
_stats.put(statName, new TreeMap());
return (TreeMap)_stats.get(statName);
}
}
}

View File

@ -87,7 +87,7 @@ class PeerSummaryReader {
}
if (summary == null)
return;
summary.coallesceData(monitor.getSummaryDurationHours() * 60*60*1000);
summary.coalesceData(monitor.getSummaryDurationHours() * 60*60*1000);
monitor.addSummary(summary);
}
@ -103,4 +103,4 @@ class PeerSummaryReader {
return _fmt.parse(when).getTime();
}
}
}
}

View File

@ -21,6 +21,7 @@
<pathelement location="../../../core/java/build/i2p.jar" />
<pathelement location="../../../router/java/build/router.jar" />
<pathelement location="../../jetty/jettylib/org.mortbay.jetty-jdk1.2.jar" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<pathelement location="../../systray/java/build/systray.jar" />
<pathelement location="../../systray/java/lib/systray4j.jar" />
<pathelement location="../../../installer/lib/wrapper/win32/wrapper.jar" /> <!-- we dont care if we're not on win32 -->
@ -54,6 +55,7 @@
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<pathelement location="../../jetty/jettylib/ant.jar" />
<pathelement location="../../systray/java/build/obj" />
<pathelement location="../../systray/java/lib/systray4j.jar" /> <!-- some javacs resolve recursively... -->
<pathelement location="../../../installer/lib/wrapper/win32/wrapper.jar" /> <!-- we dont care if we're not on win32 -->
<pathelement location="build/routerconsole.jar" />
</classpath>

View File

@ -11,6 +11,7 @@ public class ConfigClientsHandler extends FormHandler {
private String _numClients;
private String _numTunnels;
private String _numHops;
private String _numHopsOutbound;
private boolean _shouldSave;
public void ConfigNetHandler() {
@ -36,6 +37,9 @@ public class ConfigClientsHandler extends FormHandler {
public void setTunneldepth(String num) {
_numHops = (num != null ? num.trim() : null);
}
public void setTunneldepthoutbound(String num) {
_numHopsOutbound = (num != null ? num.trim() : null);
}
/**
* The user made changes to the network config and wants to save them, so
@ -63,6 +67,12 @@ public class ConfigClientsHandler extends FormHandler {
saveRequired = true;
}
if ( (_numHopsOutbound != null) && (_numHopsOutbound.length() > 0) ) {
_context.router().setConfigSetting(ClientTunnelSettings.PROP_DEPTH_OUTBOUND, _numHopsOutbound);
addFormNotice("Updating default outbound tunnel length to " + _numHopsOutbound);
saveRequired = true;
}
if (saveRequired) {
boolean saved = _context.router().saveConfig();
if (saved)

View File

@ -114,4 +114,31 @@ public class ConfigClientsHelper {
buf.append("</select>\n");
return buf.toString();
}
public String getTunnelDepthOutboundSelectBox() {
int count = ClientTunnelSettings.DEFAULT_DEPTH_OUTBOUND;
String val = _context.router().getConfigSetting(ClientTunnelSettings.PROP_DEPTH_OUTBOUND);
if (val != null) {
try {
count = Integer.parseInt(val);
} catch (NumberFormatException nfe) {
// ignore, use default from above
}
}
StringBuffer buf = new StringBuffer(1024);
buf.append("<select name=\"tunneldepthoutbound\">\n");
for (int i = 0; i < 4; i++) {
buf.append("<option value=\"").append(i).append("\" ");
if (count == i)
buf.append("selected=\"true\" ");
buf.append(">").append(i).append("</option>\n");
}
if (count >= 4) {
buf.append("<option value=\"").append(count);
buf.append("\" selected>").append(count);
buf.append("</option>\n");
}
buf.append("</select>\n");
return buf.toString();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,6 +34,8 @@
<jsp:getProperty name="clientshelper" property="tunnelCountSelectBox" /><br />
<b>Default number of hops per tunnel:</b>
<jsp:getProperty name="clientshelper" property="tunnelDepthSelectBox" /><br />
<b>Hops per outbound tunnel:</b>
<jsp:getProperty name="clientshelper" property="tunnelDepthOutboundSelectBox" /><br />
<hr />
<input type="submit" name="shouldsave" value="Save changes" /> <input type="reset" value="Cancel" />
</form>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -33,11 +33,13 @@ 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,
which other client applications (such as aum's <a href="http://stasher.i2p/">stasher</a>) can use. For
which other client applications (such the <a href="http://duck.i2p/i2p-bt/">bittorrent port</a>) can use.
There is also an optimized library for doing large number calculations - jbigi - which in turn uses the
LGPL licensed <a href="http://swox.com/gmp/">GMP</a> library, tuned for various PC architectures. For
details on other applications available, as well as their licenses, please see the
<a href="http://www.i2p.net/licenses">license policy</a>. Source for the I2P code and most bundled
client applications can be found on our <a href="http://www.i2p.net/download">download page</a>, and is
@ -47,7 +49,14 @@ in <a href="http://www.i2p.net/cvs">cvs</a>.</p>
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="contenthelper" scope="request" />
<jsp:setProperty name="contenthelper" property="page" value="history.txt" />
<jsp:setProperty name="contenthelper" property="maxLines" value="500" />
<jsp:setProperty name="contenthelper" property="startAtBeginning" value="true" />
<jsp:getProperty name="contenthelper" property="textContent" />
<p>
A more complete list of updates can be found
<a href="http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/history.txt?rev=HEAD">online</a>
(<a href="http://dev.i2p/cgi-bin/cvsweb.cgi/i2p/history.txt?rev=HEAD">anonymously</a>)
</p>
</div>
</body>

View File

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

View File

@ -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,7 +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
* Improve build system
Anyone can help with these things:

View File

@ -1,7 +1,12 @@
/* 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
* Added exit values for i2p-ping from xolo
v1.25 2004-07-31
* Created I2P-Ping, a new example program (it's a clone of I2Ping). Works

View File

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

@ -0,0 +1,60 @@
#!/bin/sh
# Copyright (c) 2004, Matthew P. Cashdollar <mpc@innographx.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author nor the names of any contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# This script I use to ping all the hosts in the I2P hosts.txt file. You should
# edit your hosts.txt and remove all the comments and blanks lines before
# running this.
#
# You can set this up to run daily via cron. It takes a very long time to run
# so I recommend starting it in the middle of the night.
OUTPUT=/tmp/index.html
FINALOUT=$HOME/www/index.html
echo "<html>" > $OUTPUT
echo "<head>" >> $OUTPUT
echo "<title>I2P Weather Report</title>" >> $OUTPUT
echo "</head>" >> $OUTPUT
echo "<body>" >> $OUTPUT
echo "<h1>I2P Weather Report</h1>" >> $OUTPUT
echo "Date: " >> $OUTPUT
date -u >> $OUTPUT
echo "(refreshed daily)" >> $OUTPUT
echo "<p>" >> $OUTPUT
echo "If your site isn't listed, that means the ping failed (I2P error)" >> $OUTPUT
echo "<p>" >> $OUTPUT
echo "<pre>" >> $OUTPUT
cut -d"=" -f1 $HOME/i2p/hosts.txt | tr "\n" " " | xargs $HOME/bin/i2p-ping -q -c 1 | grep -v "I2P error" >> $OUTPUT
echo "</pre>" >> $OUTPUT
echo "<p>" >> $OUTPUT
echo "<i>Disclaimer: This only indicates accessibility from my router, not the network in general.</i>" >> $OUTPUT
echo "</body>" >> $OUTPUT
echo "</html>" >> $OUTPUT
cp $OUTPUT $FINALOUT

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,18 +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 <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 */
@ -48,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 */
@ -81,10 +81,10 @@ 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 */
} sam_sess_t; /* a SAM session */
@ -101,59 +101,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
*
@ -85,8 +98,12 @@ void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
bool sam_close(sam_sess_t *session)
{
assert(session != NULL);
if (!session->connected)
if (!session->connected) {
#if SAM_WIRETAP
SAMLOGS("Connection already closed - sam_close() skipped");
#endif
return true;
}
#ifdef WINSOCK
if (closesocket(session->sock) == SOCKET_ERROR) {
@ -95,13 +112,19 @@ bool sam_close(sam_sess_t *session)
return false;
}
session->connected = false;
if (sam_winsock_cleanup() == SAM_OK)
if (sam_winsock_cleanup() == SAM_OK) {
#if SAM_WIRETAP
SAMLOGS("Connection closed safely");
#endif
return true;
else
} else
return false;
#else
if (close(session->sock) == 0) {
session->connected = false;
#if SAM_WIRETAP
SAMLOGS("Connection closed safely");
#endif
return true;
} else {
SAMLOG("Failed closing the SAM connection (%s)", strerror(errno));
@ -122,11 +145,10 @@ bool sam_close(sam_sess_t *session)
* tunneldepth - length of the I2P tunnels created by this program (longer is
* more anonymous, but slower)
*
* Returns: True on success, false on failure. If true, `session' will be ready
* for use.
* 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;
@ -146,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;
@ -188,7 +214,7 @@ samerr_t sam_connect(sam_sess_t *session, const char *samhost, uint16_t samport,
* data - the data we're sending
* size - the size of the data
*
* Returns: true on success, false on failure
* Returns: SAM_OK on success
*/
samerr_t sam_dgram_send(sam_sess_t *session, const sam_pubkey_t dest,
const void *data, size_t size)
@ -286,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"
@ -296,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;
@ -356,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) {
@ -364,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) {
@ -372,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;
@ -509,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
*
@ -589,7 +656,7 @@ static ssize_t sam_read1(sam_sess_t *session, char *buf, size_t n)
if (*p == '\n') { /* end of SAM response */
*p = '\0';
#if SAM_WIRETAP
printf("<<<< %s\n", buf);
printf("*RR* %s\n", buf);
#endif
return n - nleft;
}
@ -654,7 +721,16 @@ static ssize_t sam_read2(sam_sess_t *session, void *buf, size_t n)
p += nread;
}
#if SAM_WIRETAP
printf("<<<< (read2() %d bytes)\n", n);
p = buf;
printf("*RR* ");
for (size_t x = 0; x < n; x++) {
if (isprint(((byte*)p)[x]))
printf("%c,", ((byte*)p)[x]);
else
printf("%03d,", ((byte*)p)[x]);
}
printf("\n");
printf("*RR* (read2() read %d bytes)\n", n);
#endif
assert(nleft == 0);/* <---\ */
return n - nleft; /* should be equal to initial n */
@ -680,7 +756,7 @@ static bool sam_readable(sam_sess_t *session)
FD_ZERO(&rset);
FD_SET(session->sock, &rset);
tv.tv_sec = 0;
tv.tv_usec = 10;
tv.tv_usec = 0;
rc = select(session->sock + 1, &rset, NULL, NULL, &tv);
if (rc == 0)
return false;
@ -791,39 +867,6 @@ void sam_sendq_flush(sam_sess_t *session, sam_sid_t stream_id,
return;
}
/*
* Allocates memory for the session and sets its default values
*
* session - pointer to a previously allocated sam_sess_t to initalise, or NULL
* if you want memory to be allocated by this function
*/
sam_sess_t *sam_session_init(sam_sess_t *session)
{
if (session == NULL) {
session = malloc(sizeof(sam_sess_t));
if (session == NULL) {
SAMLOGS("Out of memory");
abort();
}
}
session->connected = false;
session->prev_id = 0;
return session;
}
/*
* Frees memory used by the session and sets the pointer to NULL
*
* session - pointer to a pointer to a sam_sess_t
*/
void sam_session_free(sam_sess_t **session)
{
assert(*session != NULL);
free(*session);
*session = NULL;
}
/*
* Sends the second SAM handshake command and checks the reply
*
@ -835,7 +878,7 @@ void sam_session_free(sam_sess_t **session)
* 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"
@ -880,6 +923,39 @@ static samerr_t sam_session_create(sam_sess_t *session, const char *destname,
return SAM_UNKNOWN;
}
/*
* Allocates memory for the session and sets its default values
*
* session - pointer to a previously allocated sam_sess_t to initalise, or NULL
* if you want memory to be allocated by this function
*/
sam_sess_t *sam_session_init(sam_sess_t *session)
{
if (session == NULL) {
session = malloc(sizeof(sam_sess_t));
if (session == NULL) {
SAMLOGS("Out of memory");
abort();
}
}
session->connected = false;
session->prev_id = 0;
return session;
}
/*
* Frees memory used by the session and sets the pointer to NULL
*
* session - pointer to a pointer to a sam_sess_t
*/
void sam_session_free(sam_sess_t **session)
{
assert(*session != NULL);
free(*session);
*session = NULL;
}
/*
* Connects to a remote host and returns a connected descriptor
*
@ -888,7 +964,7 @@ static samerr_t sam_session_create(sam_sess_t *session, const char *destname,
*
* 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;
@ -1005,11 +1081,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;
@ -1028,13 +1100,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;
@ -1066,15 +1133,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));
@ -1120,7 +1181,7 @@ const char *sam_strerror(samerr_t code)
case SAM_BAD_STYLE: /* Style must be stream, datagram, or raw */
return "Bad connection style";
case SAM_BAD_VERSION: /* sam_hello() had an unexpected reply */
return "Bad SAM version";
return "Not a SAM port, or bad SAM version";
case SAM_CALLBACKS_UNSET: /* Some callbacks are still set to NULL */
return "Callbacks unset";
case SAM_SOCKET_ERROR: /* TCP/IP connection to the SAM host:port
@ -1324,7 +1385,8 @@ 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]))
printf("%c,", cp[x]);
@ -1356,7 +1418,7 @@ static ssize_t sam_write(sam_sess_t *session, const void *buf, size_t n)
p += nwritten;
}
#if SAM_WIRETAP
printf(">>>> (write() %d bytes)\n", n);
printf("*WW* (write() wrote %d bytes)\n", n);
#endif
return n;

View File

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

@ -46,7 +46,7 @@ public class SAMBridge implements Runnable {
* app designated destination name to the base64 of the I2P formatted
* destination keys (Destination+PrivateKey+SigningPrivateKey)
*/
private Map nameToPrivKeys = Collections.synchronizedMap(new HashMap(8));
private Map nameToPrivKeys;
private boolean acceptConnections = true;
@ -65,6 +65,7 @@ public class SAMBridge implements Runnable {
*/
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) {
persistFilename = persistFile;
nameToPrivKeys = new HashMap(8);
loadKeys();
try {
if ( (listenHost != null) && !("0.0.0.0".equals(listenHost)) ) {
@ -93,16 +94,18 @@ public class SAMBridge implements Runnable {
* @return null if the name does not exist, or if it is improperly formatted
*/
public Destination getDestination(String name) {
String val = (String)nameToPrivKeys.get(name);
if (val == null) return null;
try {
Destination d = new Destination();
d.fromBase64(val);
return d;
} catch (DataFormatException dfe) {
_log.error("Error retrieving the destination from " + name, dfe);
nameToPrivKeys.remove(name);
return null;
synchronized (nameToPrivKeys) {
String val = (String)nameToPrivKeys.get(name);
if (val == null) return null;
try {
Destination d = new Destination();
d.fromBase64(val);
return d;
} catch (DataFormatException dfe) {
_log.error("Error retrieving the destination from " + name, dfe);
nameToPrivKeys.remove(name);
return null;
}
}
}
@ -114,9 +117,11 @@ public class SAMBridge implements Runnable {
* @return null if the name does not exist, else the stream
*/
public String getKeystream(String name) {
String val = (String)nameToPrivKeys.get(name);
if (val == null) return null;
return val;
synchronized (nameToPrivKeys) {
String val = (String)nameToPrivKeys.get(name);
if (val == null) return null;
return val;
}
}
/**
@ -124,7 +129,9 @@ public class SAMBridge implements Runnable {
*
*/
public void addKeystream(String name, String stream) {
nameToPrivKeys.put(name, stream);
synchronized (nameToPrivKeys) {
nameToPrivKeys.put(name, stream);
}
storeKeys();
}
@ -132,49 +139,52 @@ public class SAMBridge implements Runnable {
* Load up the keys from the persistFilename
*
*/
private synchronized void loadKeys() {
Map keys = new HashMap(16);
FileInputStream in = null;
try {
in = new FileInputStream(persistFilename);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line = null;
while ( (line = reader.readLine()) != null) {
int eq = line.indexOf('=');
String name = line.substring(0, eq);
String privKeys = line.substring(eq+1);
keys.put(name, privKeys);
private void loadKeys() {
synchronized (nameToPrivKeys) {
nameToPrivKeys.clear();
FileInputStream in = null;
try {
in = new FileInputStream(persistFilename);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line = null;
while ( (line = reader.readLine()) != null) {
int eq = line.indexOf('=');
String name = line.substring(0, eq);
String privKeys = line.substring(eq+1);
nameToPrivKeys.put(name, privKeys);
}
} catch (FileNotFoundException fnfe) {
_log.warn("Key file does not exist at " + persistFilename);
} catch (IOException ioe) {
_log.error("Unable to read the keys from " + persistFilename, ioe);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
} catch (FileNotFoundException fnfe) {
_log.warn("Key file does not exist at " + persistFilename);
} catch (IOException ioe) {
_log.error("Unable to read the keys from " + persistFilename, ioe);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
nameToPrivKeys = Collections.synchronizedMap(keys);
}
/**
* Store the current keys to disk in the location specified on creation
*
*/
private synchronized void storeKeys() {
FileOutputStream out = null;
try {
out = new FileOutputStream(persistFilename);
for (Iterator iter = nameToPrivKeys.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String privKeys = (String)nameToPrivKeys.get(name);
out.write(name.getBytes());
out.write('=');
out.write(privKeys.getBytes());
out.write('\n');
private void storeKeys() {
synchronized (nameToPrivKeys) {
FileOutputStream out = null;
try {
out = new FileOutputStream(persistFilename);
for (Iterator iter = nameToPrivKeys.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String privKeys = (String)nameToPrivKeys.get(name);
out.write(name.getBytes());
out.write('=');
out.write(privKeys.getBytes());
out.write('\n');
}
} catch (IOException ioe) {
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
} catch (IOException ioe) {
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
}
@ -210,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();
}
@ -240,6 +259,7 @@ public class SAMBridge implements Runnable {
}
public void run() {
if (serverSocket == null) return;
try {
while (acceptConnections) {
Socket s = serverSocket.accept();
@ -270,8 +290,11 @@ public class SAMBridge implements Runnable {
if (_log.shouldLog(Log.ERROR))
_log.error("SAM Error sending error reply", ioe);
}
s.close();
}
try { s.close(); } catch (IOException ioe) {}
} catch (Exception ee) {
try { s.close(); } catch (IOException ioe) {}
_log.log(Log.CRIT, "Unexpected error handling SAM connection", ee);
}
}
} catch (Exception e) {
if (_log.shouldLog(Log.ERROR))
@ -280,7 +303,8 @@ public class SAMBridge implements Runnable {
try {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Shutting down, closing server socket");
serverSocket.close();
if (serverSocket != null)
serverSocket.close();
} catch (IOException e) {}
}
}

View File

@ -100,6 +100,14 @@ public abstract class SAMHandler implements Runnable {
socketOS.flush();
}
}
/**
* If you're crazy enough to write to the raw socket, grab the write lock
* with getWriteLock(), synchronize against it, and write to the getOut()
*
*/
protected Object getWriteLock() { return socketWLock; }
protected OutputStream getOut() { return socketOS; }
/**
* Write a string to the handler's socket. This method must
@ -111,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;
@ -152,6 +150,8 @@ public class SAMHandlerFactory {
/* Get the major protocol version from a string */
private static int getMajor(String ver) {
if ( (ver == null) || (ver.indexOf('.') < 0) )
return -1;
try {
String major = ver.substring(0, ver.indexOf("."));
return Integer.parseInt(major);
@ -164,6 +164,8 @@ public class SAMHandlerFactory {
/* Get the minor protocol version from a string */
private static int getMinor(String ver) {
if ( (ver == null) || (ver.indexOf('.') < 0) )
return -1;
try {
String major = ver.substring(ver.indexOf(".") + 1);
return Integer.parseInt(major);

View File

@ -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;
@ -180,9 +195,9 @@ public class SAMStreamSession {
Destination d = new Destination();
d.fromBase64(dest);
// FIXME: we should config I2PSocketOptions here
I2PSocketOptions opts = new I2PSocketOptions();
opts.setConnectTimeout(60 * 1000);
I2PSocketOptions opts = socketMgr.buildOptions(props);
if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
opts.setConnectTimeout(60 * 1000);
_log.debug("Connecting new I2PSocket...");
I2PSocket i2ps = socketMgr.connect(d, opts);
@ -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

@ -111,9 +111,10 @@ public class SAMUtils {
*/
public static Properties parseParams(StringTokenizer tok) throws SAMException {
int pos, nprops = 0, ntoks = tok.countTokens();
String token, param, value;
String token, param;
Properties props = new Properties();
StringBuffer value = new StringBuffer();
for (int i = 0; i < ntoks; ++i) {
token = tok.nextToken();
@ -123,9 +124,16 @@ public class SAMUtils {
throw new SAMException("Bad formatting for param [" + token + "]");
}
param = token.substring(0, pos);
value = token.substring(pos + 1);
value.append(token.substring(pos+1));
if (value.charAt(0) == '"') {
while ( (i < ntoks) && (value.lastIndexOf("\"") <= 0) ) {
value.append(' ').append(tok.nextToken());
i++;
}
}
props.setProperty(param, value);
props.setProperty(param, value.toString());
value.setLength(0);
nprops += 1;
}
@ -157,4 +165,19 @@ public class SAMUtils {
return msg;
}
public static void main(String args[]) {
try {
test("a=b c=d e=\"f g h\"");
test("a=\"b c d\" e=\"f g h\" i=\"j\"");
test("a=\"b c d\" e=f i=\"j\"");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void test(String props) throws Exception {
StringTokenizer tok = new StringTokenizer(props);
Properties p = parseParams(tok);
System.out.println(p);
}
}

View File

@ -14,6 +14,7 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
@ -25,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;
@ -43,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
@ -67,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)) {
@ -75,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 {
@ -94,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);
_log.debug("New message received: [" + msg + "]");
}
buf.reset();
tok = new StringTokenizer(msg, " ");
if (tok.countTokens() < 2) {
@ -149,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 {
@ -549,13 +546,8 @@ 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)) {
_log.error("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;
@ -563,11 +555,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;
}
@ -685,9 +677,12 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
ByteArrayOutputStream msg = new ByteArrayOutputStream();
msg.write(("RAW RECEIVED SIZE=" + data.length
+ "\n").getBytes("ISO-8859-1"));
String msgText = "RAW RECEIVED SIZE=" + data.length + "\n";
msg.write(msgText.getBytes("ISO-8859-1"));
msg.write(data);
if (_log.shouldLog(Log.DEBUG))
_log.debug("sending to client: " + msgText);
writeBytes(msg.toByteArray());
}
@ -716,9 +711,12 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
ByteArrayOutputStream msg = new ByteArrayOutputStream();
msg.write(("DATAGRAM RECEIVED DESTINATION=" + sender.toBase64()
+ " SIZE=" + data.length
+ "\n").getBytes("ISO-8859-1"));
String msgText = "DATAGRAM RECEIVED DESTINATION=" + sender.toBase64()
+ " SIZE=" + data.length + "\n";
msg.write(msgText.getBytes("ISO-8859-1"));
if (_log.shouldLog(Log.DEBUG))
_log.debug("sending to client: " + msgText);
msg.write(data);
writeBytes(msg.toByteArray());
@ -759,13 +757,24 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
throw new NullPointerException("BUG! STREAM session is null!");
}
ByteArrayOutputStream msg = new ByteArrayOutputStream();
msg.write(("STREAM RECEIVED ID=" + id
+" SIZE=" + len + "\n").getBytes("ISO-8859-1"));
msg.write(data, 0, len);
writeBytes(msg.toByteArray());
String msgText = "STREAM RECEIVED ID=" + id +" SIZE=" + len + "\n";
if (_log.shouldLog(Log.DEBUG))
_log.debug("sending to client: " + msgText);
byte prefix[] = msgText.getBytes("ISO-8859-1");
// dont waste so much memory
//ByteArrayOutputStream msg = new ByteArrayOutputStream();
//msg.write(msgText.getBytes("ISO-8859-1"));
//msg.write(data, 0, len);
// writeBytes(msg.toByteArray());
Object writeLock = getWriteLock();
OutputStream out = getOut();
synchronized (writeLock) {
out.write(prefix);
out.write(data, 0, len);
out.flush();
}
}
public void notifyStreamDisconnection(int id, String result, String msg) throws IOException {
@ -783,7 +792,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!");

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

@ -33,7 +33,11 @@ public class TestStreamTransfer {
private static void runTest(String samHost, int samPort, String conOptions) {
startAlice(samHost, samPort, conOptions);
testBob(samHost, samPort, conOptions);
for (int i = 0; i < 20; i++) {
testBob("bob" + i, samHost, samPort, conOptions);
if (i % 2 == 1)
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
private static void startAlice(String host, int port, String conOptions) {
@ -95,11 +99,13 @@ public class TestStreamTransfer {
try { _out.close(); } catch (IOException ioe) {}
try { _s.close(); } catch (IOException ioe) {}
_streams.clear();
_dead = true;
}
}
}
private void doRun() throws IOException, SAMException {
String line = _reader.readLine();
_log.debug("Read: " + line);
StringTokenizer tok = new StringTokenizer(line);
String maj = tok.nextToken();
String min = tok.nextToken();
@ -144,14 +150,17 @@ 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
String reply = "STREAM SEND ID=" + id +
" SIZE=" + payloadSize +
"\n" + payload;
"\n" + new String(payload);
_out.write(reply.getBytes());
_out.flush();
_log.info("Reply sent back [" + new String(reply.getBytes()) + "]");
*/
} else {
_log.error("Received unsupported type [" + maj + "/"+ min + "]");
return;
@ -159,8 +168,27 @@ public class TestStreamTransfer {
}
}
private static void testBob(String host, int port, String conOptions) {
_log.info("\n\nTesting Bob\n\n\n");
private static void testBob(String sessionName, String host, int port, String conOptions) {
I2PThread t = new I2PThread(new TestBob(sessionName, host, port, conOptions), sessionName);
t.start();
}
private static class TestBob implements Runnable {
private String _sessionName;
private String _host;
private int _port;
private String _opts;
public TestBob(String name, String host, int port, String opts) {
_sessionName = name;
_host = host;
_port = port;
_opts = opts;
}
public void run() {
doTestBob(_sessionName, _host, _port, _opts);
}
}
private static void doTestBob(String sessionName, String host, int port, String conOptions) {
_log.info("\n\nTesting " + sessionName + "\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
@ -168,32 +196,42 @@ public class TestStreamTransfer {
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.debug("line read for valid version: " + line);
String req = "SESSION CREATE STYLE=STREAM DESTINATION=Bob " + conOptions + "\n";
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + sessionName + " " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination Bob: " + line);
_log.info("Response to creating the session with destination "+ sessionName+": " + line);
req = "STREAM CONNECT ID=42 DESTINATION=" + _alice + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to the stream connect from Bob to Alice: " + line);
_log.info("Response to the stream connect from "+sessionName+" to Alice: " + line);
StringTokenizer tok = new StringTokenizer(line);
String maj = tok.nextToken();
String min = tok.nextToken();
Properties props = SAMUtils.parseParams(tok);
_log.info("props = " + props);
String result = props.getProperty("RESULT");
if (!("OK".equals(result))) {
_log.error("Unable to connect!");
_dead = true;
//_dead = true;
return;
}
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
_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) {}
_log.info("Sending close");
req = "STREAM CLOSE ID=42\n";
out.write(req.getBytes());
try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
_dead = true;
out.flush();
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
//_dead = true;
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
@ -203,7 +241,7 @@ public class TestStreamTransfer {
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=10001 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
@ -215,8 +253,8 @@ public class TestStreamTransfer {
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
//try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
//System.exit(0);
}
}

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

@ -33,9 +33,8 @@ def urlopen(url, eepaddr=eepaddr):
eepaddr = eepaddr.rstrip('/')
proxy = urllib2.ProxyHandler( \
{'http': 'http://anonymous:passwd@' + eepaddr})
opener = urllib2.build_opener(proxy, \
urllib2.HTTPBasicAuthHandler(), urllib2.HTTPHandler)
{'http': 'http://' + eepaddr})
opener = urllib2.build_opener(proxy, urllib2.HTTPHandler)
return opener.open(url)
def urlget(url, eepaddr=eepaddr):

View File

@ -40,7 +40,7 @@ class Poll:
del self.fds[self._hash(fd)]
def poll(self, timeout=None):
readlist, writelist, errlist = [], [], []
for F, mask in self.fds:
for F, mask in self.fds.values():
if mask & POLLIN: readlist += [F]
if mask & POLLOUT: writelist += [F]
if mask & POLLERR: errlist += [F]

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="streaming">
<target name="all" depends="clean, build" />
<target name="build" depends="builddep, jar" />
<target name="builddep">
<ant dir="../../ministreaming/java/" target="build" />
<!-- ministreaming will build core -->
</target>
<target name="compile">
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac
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" />
</target>
<target name="jar" depends="builddep, compile">
<jar destfile="./build/streaming.jar" basedir="./build/obj" includes="**/*.class" />
</target>
<target name="javadoc">
<mkdir dir="./build" />
<mkdir dir="./build/javadoc" />
<javadoc
sourcepath="./src:../../../core/java/src:../../ministreaming/java/src" destdir="./build/javadoc"
packagenames="*"
use="true"
splitindex="true"
windowtitle="Streaming" />
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
<!-- ministreaming will clean core -->
<ant dir="../../ministreaming/java/" target="distclean" />
</target>
<target name="distclean" depends="clean">
<!-- ministreaming will clean core -->
<ant dir="../../ministreaming/java/" target="distclean" />
</target>
</project>

View File

@ -0,0 +1,823 @@
package net.i2p.client.streaming;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession;
import net.i2p.data.Base64;
import net.i2p.data.Destination;
import net.i2p.data.SessionTag;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
* Maintain the state controlling a streaming connection between two
* destinations.
*
*/
public class Connection {
private I2PAppContext _context;
private Log _log;
private ConnectionManager _connectionManager;
private Destination _remotePeer;
private byte _sendStreamId[];
private byte _receiveStreamId[];
private long _lastSendTime;
private long _lastSendId;
private boolean _resetReceived;
private boolean _connected;
private MessageInputStream _inputStream;
private MessageOutputStream _outputStream;
private SchedulerChooser _chooser;
private long _nextSendTime;
private long _ackedPackets;
private long _createdOn;
private long _closeSentOn;
private long _closeReceivedOn;
private int _unackedPacketsReceived;
private long _congestionWindowEnd;
private long _highestAckedThrough;
/** Packet ID (Long) to PacketLocal for sent but unacked packets */
private Map _outboundPackets;
private PacketQueue _outboundQueue;
private ConnectionPacketHandler _handler;
private ConnectionOptions _options;
private ConnectionDataReceiver _receiver;
private I2PSocketFull _socket;
/** set to an error cause if the connection could not be established */
private String _connectionError;
private boolean _disconnectScheduled;
private long _lastReceivedOn;
private ActivityTimer _activityTimer;
/** window size when we last saw congestion */
private int _lastCongestionSeenAt;
private boolean _ackSinceCongestion;
/** Notify this on connection (or connection failure) */
private Object _connectLock;
/** how many messages have been resent and not yet ACKed? */
private int _activeResends;
private long _lifetimeBytesSent;
private long _lifetimeBytesReceived;
private long _lifetimeDupMessageSent;
private long _lifetimeDupMessageReceived;
public static final long MAX_RESEND_DELAY = 60*1000;
public static final long MIN_RESEND_DELAY = 40*1000;
/** wait up to 5 minutes after disconnection so we can ack/close packets */
public static int DISCONNECT_TIMEOUT = 5*60*1000;
/** lets be sane- no more than 32 packets in the air in each dir */
public static final int MAX_WINDOW_SIZE = 32;
public Connection(I2PAppContext ctx, ConnectionManager manager, SchedulerChooser chooser, PacketQueue queue, ConnectionPacketHandler handler) {
this(ctx, manager, chooser, queue, handler, null);
}
public Connection(I2PAppContext ctx, ConnectionManager manager, SchedulerChooser chooser, PacketQueue queue, ConnectionPacketHandler handler, ConnectionOptions opts) {
_context = ctx;
_log = ctx.logManager().getLog(Connection.class);
_receiver = new ConnectionDataReceiver(ctx, this);
_inputStream = new MessageInputStream(ctx);
_outputStream = new MessageOutputStream(ctx, _receiver);
_chooser = chooser;
_outboundPackets = new TreeMap();
_outboundQueue = queue;
_handler = handler;
_options = (opts != null ? opts : new ConnectionOptions());
_lastSendId = -1;
_nextSendTime = -1;
_ackedPackets = 0;
_createdOn = ctx.clock().now();
_closeSentOn = -1;
_closeReceivedOn = -1;
_unackedPacketsReceived = 0;
_congestionWindowEnd = 0;
_highestAckedThrough = -1;
_lastCongestionSeenAt = MAX_WINDOW_SIZE;
_connectionManager = manager;
_resetReceived = false;
_connected = true;
_disconnectScheduled = false;
_lastReceivedOn = -1;
_activityTimer = new ActivityTimer();
_ackSinceCongestion = true;
_connectLock = new Object();
_activeResends = 0;
_context.statManager().createRateStat("stream.con.windowSizeAtCongestion", "How large was our send window when we send a dup?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_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() {
synchronized (this) {
return ++_lastSendId;
}
}
void closeReceived() {
setCloseReceivedOn(_context.clock().now());
_inputStream.closeReceived();
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
/**
* Block until there is an open outbound packet slot or the write timeout
* expires.
*
* @return true if the packet should be sent
*/
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) {
_log.error("Outbound window is full of " + _outboundPackets.size()
+ " and we've waited too long (" + writeExpire + "ms)");
return false;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Outbound window is full (" + _outboundPackets.size() + "/" + _options.getWindowSize() + "), waiting " + timeLeft);
try { _outboundPackets.wait(timeLeft); } catch (InterruptedException ie) {}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Outbound window is full (" + _outboundPackets.size() + "), waiting indefinitely");
try { _outboundPackets.wait(); } catch (InterruptedException ie) {}
}
} else {
_context.statManager().addRateData("stream.chokeSizeEnd", _outboundPackets.size(), _context.clock().now() - start);
return true;
}
}
}
}
void ackImmediately() {
_receiver.send(null, 0, 0);
}
/**
* Flush any data that we can
*/
void sendAvailable() {
// this grabs the data, builds a packet, and queues it up via sendPacket
try {
_outputStream.flushAvailable(_receiver, false);
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error flushing available", ioe);
}
}
void sendPacket(PacketLocal packet) {
if (packet == null) return;
setNextSendTime(-1);
_unackedPacketsReceived = 0;
if (_options.getRequireFullySigned()) {
packet.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
packet.setFlag(Packet.FLAG_SIGNATURE_REQUESTED);
}
boolean ackOnly = false;
if ( (packet.getSequenceNum() == 0) && (!packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) ) {
ackOnly = true;
if (_log.shouldLog(Log.DEBUG))
_log.debug("No resend for " + packet);
} else {
int remaining = 0;
synchronized (_outboundPackets) {
_outboundPackets.put(new Long(packet.getSequenceNum()), packet);
remaining = _options.getWindowSize() - _outboundPackets.size() ;
_outboundPackets.notifyAll();
}
if (remaining < 0)
remaining = 0;
if (packet.isFlagSet(Packet.FLAG_CLOSE) || (remaining < 2)) {
packet.setOptionalDelay(0);
} else {
int delay = _options.getRTT() / 2;
packet.setOptionalDelay(delay);
_log.debug("Requesting ack delay of " + delay + "ms for packet " + packet);
}
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
long timeout = (_options.getRTT() < MIN_RESEND_DELAY ? MIN_RESEND_DELAY : _options.getRTT());
if (timeout > MAX_RESEND_DELAY)
timeout = MAX_RESEND_DELAY;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Resend in " + timeout + " for " + packet);
SimpleTimer.getInstance().addEvent(new ResendPacketEvent(packet), timeout);
}
_lastSendTime = _context.clock().now();
_outboundQueue.enqueue(packet);
resetActivityTimer();
if (ackOnly) {
// ACK only, don't schedule this packet for retries
// however, if we are running low on sessionTags we want to send
// something that will get a reply so that we can deliver some new tags -
// ACKs don't get ACKed, but pings do.
if ( (packet.getTagsSent() != null) && (packet.getTagsSent().size() > 0) ) {
_log.warn("Sending a ping since the ACK we just sent has " + packet.getTagsSent().size() + " tags");
_connectionManager.ping(_remotePeer, _options.getRTT()*2, false, packet.getKeyUsed(), packet.getTagsSent(), new PingNotifier());
}
}
}
private class PingNotifier implements ConnectionManager.PingNotifier {
private long _startedPingOn;
public PingNotifier() {
_startedPingOn = _context.clock().now();
}
public void pingComplete(boolean ok) {
long time = _context.clock().now()-_startedPingOn;
if (ok)
_options.updateRTT((int)time);
else
_options.updateRTT((int)time*2);
}
}
List ackPackets(long ackThrough, long nacks[]) {
if (nacks == null) {
_highestAckedThrough = ackThrough;
} else {
long lowest = -1;
for (int i = 0; i < nacks.length; i++) {
if ( (lowest < 0) || (nacks[i] < lowest) )
lowest = nacks[i];
}
if (lowest - 1 > _highestAckedThrough)
_highestAckedThrough = lowest - 1;
}
List acked = null;
synchronized (_outboundPackets) {
for (Iterator iter = _outboundPackets.keySet().iterator(); iter.hasNext(); ) {
Long id = (Long)iter.next();
if (id.longValue() <= ackThrough) {
boolean nacked = false;
if (nacks != null) {
// linear search since its probably really tiny
for (int i = 0; i < nacks.length; i++) {
if (nacks[i] == id.longValue()) {
nacked = true;
break; // NACKed
}
}
}
if (!nacked) { // aka ACKed
if (acked == null)
acked = new ArrayList(1);
PacketLocal ackedPacket = (PacketLocal)_outboundPackets.get(id);
ackedPacket.ackReceived();
acked.add(ackedPacket);
}
} else {
break; // _outboundPackets is ordered
}
}
if (acked != null) {
for (int i = 0; i < acked.size(); i++) {
PacketLocal p = (PacketLocal)acked.get(i);
_outboundPackets.remove(new Long(p.getSequenceNum()));
_ackedPackets++;
if (p.getNumSends() > 1) {
_activeResends--;
if (_log.shouldLog(Log.WARN))
_log.warn("Active resend of " + p + " successful, # active left: " + _activeResends);
}
}
}
if ( (_outboundPackets.size() <= 0) && (_activeResends != 0) ) {
if (_log.shouldLog(Log.WARN))
_log.warn("All outbound packets acked, clearing " + _activeResends);
_activeResends = 0;
}
_outboundPackets.notifyAll();
}
if ((acked != null) && (acked.size() > 0) )
_ackSinceCongestion = true;
return acked;
}
private long _occurredTime;
private long _occurredEventCount;
void eventOccurred() {
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;
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; }
void disconnect(boolean cleanDisconnect) {
disconnect(cleanDisconnect, true);
}
void disconnect(boolean cleanDisconnect, boolean removeFromConMgr) {
synchronized (_connectLock) { _connectLock.notifyAll(); }
if (_log.shouldLog(Log.DEBUG))
_log.debug("Disconnecting " + toString(), new Exception("discon"));
if (cleanDisconnect && _connected) {
// send close packets and schedule stuff...
_outputStream.closeInternal();
_inputStream.close();
} else {
if (_connected)
doClose();
killOutstandingPackets();
}
if (removeFromConMgr) {
if (!_disconnectScheduled) {
_disconnectScheduled = true;
SimpleTimer.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
}
}
_connected = false;
}
void disconnectComplete() {
_connected = false;
if (_socket != null)
_socket.destroy();
_socket = null;
if (_outputStream != null)
_outputStream.destroy();
if (_receiver != null)
_receiver.destroy();
if (_activityTimer != null)
SimpleTimer.getInstance().removeEvent(_activityTimer);
_activityTimer = null;
if (!_disconnectScheduled) {
_disconnectScheduled = true;
if (_log.shouldLog(Log.INFO))
_log.info("Connection disconnect complete from dead, drop the con "
+ toString());
_connectionManager.removeConnection(this);
}
killOutstandingPackets();
}
private void killOutstandingPackets() {
boolean tagsCancelled = false;
synchronized (_outboundPackets) {
for (Iterator iter = _outboundPackets.values().iterator(); iter.hasNext(); ) {
PacketLocal pl = (PacketLocal)iter.next();
if ( (pl.getTagsSent() != null) && (pl.getTagsSent().size() > 0) )
tagsCancelled = true;
pl.cancelled();
}
_outboundPackets.clear();
_outboundPackets.notifyAll();
}
if (tagsCancelled)
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
}
private class DisconnectEvent implements SimpleTimer.TimedEvent {
public DisconnectEvent() {
if (_log.shouldLog(Log.INFO))
_log.info("Connection disconnect timer initiated: 5 minutes to drop "
+ Connection.this.toString());
}
public void timeReached() {
killOutstandingPackets();
if (_log.shouldLog(Log.INFO))
_log.info("Connection disconnect timer complete, drop the con "
+ Connection.this.toString());
_connectionManager.removeConnection(Connection.this);
}
}
private void doClose() {
_outputStream.streamErrorOccurred(new IOException("Hard disconnect"));
_inputStream.closeReceived();
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
/** who are we talking with */
public Destination getRemotePeer() { return _remotePeer; }
public void setRemotePeer(Destination peer) { _remotePeer = peer; }
/** what stream do we send data to the peer on? */
public byte[] getSendStreamId() { return _sendStreamId; }
public void setSendStreamId(byte[] id) { _sendStreamId = id; }
/** stream the peer sends data to us on. (may be null) */
public byte[] getReceiveStreamId() { return _receiveStreamId; }
public void setReceiveStreamId(byte[] id) {
_receiveStreamId = id;
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
/** when did we last send anything to the peer? */
public long getLastSendTime() { return _lastSendTime; }
public void setLastSendTime(long when) { _lastSendTime = when; }
/** what was the last packet Id sent to the peer? */
public long getLastSendId() { return _lastSendId; }
public void setLastSendId(long id) { _lastSendId = id; }
public ConnectionOptions getOptions() { return _options; }
public void setOptions(ConnectionOptions opts) { _options = opts; }
public I2PSession getSession() { return _connectionManager.getSession(); }
public I2PSocketFull getSocket() { return _socket; }
public void setSocket(I2PSocketFull socket) { _socket = socket; }
public String getConnectionError() { return _connectionError; }
public void setConnectionError(String err) { _connectionError = err; }
public long getLifetime() {
if (_closeSentOn <= 0)
return _context.clock().now() - _createdOn;
else
return _closeSentOn - _createdOn;
}
public ConnectionPacketHandler getPacketHandler() { return _handler; }
public long getLifetimeBytesSent() { return _lifetimeBytesSent; }
public long getLifetimeBytesReceived() { return _lifetimeBytesReceived; }
public long getLifetimeDupMessagesSent() { return _lifetimeDupMessageSent; }
public long getLifetimeDupMessagesReceived() { return _lifetimeDupMessageReceived; }
public void incrementBytesSent(int bytes) { _lifetimeBytesSent += bytes; }
public void incrementDupMessagesSent(int msgs) { _lifetimeDupMessageSent += msgs; }
public void incrementBytesReceived(int bytes) { _lifetimeBytesReceived += bytes; }
public void incrementDupMessagesReceived(int msgs) { _lifetimeDupMessageReceived += msgs; }
/**
* Time when the scheduler next want to send a packet, or -1 if
* never. This should be set when we want to send on timeout, for
* instance, or want to delay an ACK.
*/
public long getNextSendTime() { return _nextSendTime; }
public void setNextSendTime(long when) {
if (_nextSendTime >= 0) {
if (when < _nextSendTime)
_nextSendTime = when;
} else {
_nextSendTime = when;
}
if (_nextSendTime >= 0) {
long max = _context.clock().now() + _options.getSendAckDelay();
if (max < _nextSendTime)
_nextSendTime = max;
}
if (_log.shouldLog(Log.DEBUG) && false) {
if (_nextSendTime <= 0)
_log.debug("set next send time to an unknown time", new Exception(toString()));
else
_log.debug("set next send time to " + (_nextSendTime-_context.clock().now()) + "ms from now", new Exception(toString()));
}
}
public long getAckedPackets() { return _ackedPackets; }
public long getCreatedOn() { return _createdOn; }
public long getCloseSentOn() { return _closeSentOn; }
public void setCloseSentOn(long when) { _closeSentOn = when; }
public long getCloseReceivedOn() { return _closeReceivedOn; }
public void setCloseReceivedOn(long when) { _closeReceivedOn = when; }
public void incrementUnackedPacketsReceived() { _unackedPacketsReceived++; }
public int getUnackedPacketsReceived() { return _unackedPacketsReceived; }
public int getUnackedPacketsSent() {
synchronized (_outboundPackets) {
return _outboundPackets.size();
}
}
public long getCongestionWindowEnd() { return _congestionWindowEnd; }
public void setCongestionWindowEnd(long endMsg) { _congestionWindowEnd = endMsg; }
public long getHighestAckedThrough() { return _highestAckedThrough; }
public void setHighestAckedThrough(long msgNum) { _highestAckedThrough = msgNum; }
public long getLastActivityOn() {
return (_lastSendTime > _lastReceivedOn ? _lastSendTime : _lastReceivedOn);
}
public int getLastCongestionSeenAt() { return _lastCongestionSeenAt; }
void congestionOccurred() {
// if we hit congestion and e.g. 5 packets are resent,
// dont set the size to (winSize >> 4). only set the
if (_ackSinceCongestion) {
_lastCongestionSeenAt = _options.getWindowSize();
_ackSinceCongestion = false;
}
}
void packetReceived() {
_lastReceivedOn = _context.clock().now();
resetActivityTimer();
synchronized (_connectLock) { _connectLock.notifyAll(); }
}
/**
* wait until a connection is made or the connection fails within the
* timeout period, setting the error accordingly.
*/
void waitForConnect() {
long expiration = _context.clock().now() + _options.getConnectTimeout();
while (true) {
if (_connected && (_receiveStreamId != null) && (_sendStreamId != null) ) {
// w00t
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): Connected and we have stream IDs");
return;
}
if (_connectionError != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): connection error found: " + _connectionError);
return;
}
if (!_connected) {
_connectionError = "Connection failed";
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): not connected");
return;
}
long timeLeft = expiration - _context.clock().now();
if ( (timeLeft <= 0) && (_options.getConnectTimeout() > 0) ) {
if (_connectionError == null) {
_connectionError = "Connection timed out";
disconnect(false);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): timed out: " + _connectionError);
return;
}
if (timeLeft > 60*1000)
timeLeft = 60*1000;
if (_options.getConnectTimeout() <= 0)
timeLeft = 60*1000;
if (_log.shouldLog(Log.DEBUG))
_log.debug("waitForConnect(): wait " + timeLeft);
try {
synchronized (_connectLock) {
_connectLock.wait(timeLeft);
}
} catch (InterruptedException ie) {}
}
}
private void resetActivityTimer() {
if (_options.getInactivityTimeout() <= 0) return;
if (_activityTimer == null) return;
long howLong = _activityTimer.getTimeLeft();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Resetting the inactivity timer to " + howLong);
// this will get rescheduled, and rescheduled, and rescheduled...
SimpleTimer.getInstance().addEvent(_activityTimer, howLong);
}
private class ActivityTimer implements SimpleTimer.TimedEvent {
public void timeReached() {
// uh, nothing more to do...
if (!_connected) return;
// we got rescheduled already
if (getTimeLeft() > 0) return;
// these are either going to time out or cause further rescheduling
if (getUnackedPacketsSent() > 0) return;
// wtf, this shouldn't have been scheduled
if (_options.getInactivityTimeout() <= 0) return;
// if one of us can't talk...
if ( (_closeSentOn > 0) || (_closeReceivedOn > 0) ) return;
// bugger it, might as well do the hard work now
switch (_options.getInactivityAction()) {
case ConnectionOptions.INACTIVITY_ACTION_SEND:
if (_log.shouldLog(Log.WARN))
_log.warn("Sending some data due to inactivity");
_receiver.send(null, 0, 0, true);
break;
case ConnectionOptions.INACTIVITY_ACTION_NOOP:
if (_log.shouldLog(Log.WARN))
_log.warn("Inactivity timer expired, but we aint doin' shit");
break;
case ConnectionOptions.INACTIVITY_ACTION_DISCONNECT:
// fall through
default:
if (_log.shouldLog(Log.WARN))
_log.warn("Closing connection due to inactivity");
disconnect(true);
break;
}
}
public final long getTimeLeft() {
if (getLastActivityOn() > 0)
return getLastActivityOn() + _options.getInactivityTimeout() - _context.clock().now();
else
return _createdOn + _options.getInactivityTimeout() - _context.clock().now();
}
}
/** stream that the local peer receives data on */
public MessageInputStream getInputStream() { return _inputStream; }
/** stream that the local peer sends data to the remote peer on */
public MessageOutputStream getOutputStream() { return _outputStream; }
public String toString() {
StringBuffer buf = new StringBuffer(128);
buf.append("[Connection ");
if (_receiveStreamId != null)
buf.append(Base64.encode(_receiveStreamId));
else
buf.append("unknown");
buf.append("<-->");
if (_sendStreamId != null)
buf.append(Base64.encode(_sendStreamId));
else
buf.append("unknown");
buf.append(" wsize: ").append(_options.getWindowSize());
buf.append(" cwin: ").append(_congestionWindowEnd - _highestAckedThrough);
buf.append(" rtt: ").append(_options.getRTT());
buf.append(" unacked outbound: ");
synchronized (_outboundPackets) {
buf.append(_outboundPackets.size()).append(" [");
for (Iterator iter = _outboundPackets.keySet().iterator(); iter.hasNext(); ) {
buf.append(((Long)iter.next()).longValue()).append(" ");
}
buf.append("] ");
}
buf.append("unacked inbound? ").append(getUnackedPacketsReceived());
if (_inputStream != null) {
buf.append(" [high ");
buf.append(_inputStream.getHighestBlockId());
long nacks[] = _inputStream.getNacks();
if (nacks != null)
for (int i = 0; i < nacks.length; i++)
buf.append(" ").append(nacks[i]);
buf.append("]");
}
buf.append("]");
return buf.toString();
}
/**
* Coordinate the resends of a given packet
*/
private class ResendPacketEvent implements SimpleTimer.TimedEvent {
private PacketLocal _packet;
private boolean _currentIsActiveResend;
public ResendPacketEvent(PacketLocal packet) {
_packet = packet;
_currentIsActiveResend = false;
packet.setResendPacketEvent(ResendPacketEvent.this);
}
public void timeReached() {
if (_packet.getAckTime() > 0)
return;
if (!_connected) {
_packet.cancelled();
return;
}
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Resend period reached for " + _packet);
boolean resend = false;
synchronized (_outboundPackets) {
if (_outboundPackets.containsKey(new Long(_packet.getSequenceNum())))
resend = true;
}
if ( (resend) && (_packet.getAckTime() < 0) ) {
if ( (_activeResends > 0) && (!_currentIsActiveResend) ) {
// we want to resend this packet, but there are already active
// resends in the air and we dont want to make a bad situation
// worse. wait another second
if (_log.shouldLog(Log.WARN))
_log.warn("Delaying resend of " + _packet + " as there are "
+ _activeResends + " active resends already in play");
SimpleTimer.getInstance().addEvent(ResendPacketEvent.this, 1000);
return;
}
// revamp various fields, in case we need to ack more, etc
_inputStream.updateAcks(_packet);
_packet.setOptionalDelay(getOptions().getChoke());
_packet.setOptionalMaxSize(getOptions().getMaxMessageSize());
_packet.setResendDelay(getOptions().getResendDelay());
_packet.setReceiveStreamId(_receiveStreamId);
_packet.setSendStreamId(_sendStreamId);
// shrink the window
int newWindowSize = getOptions().getWindowSize();
congestionOccurred();
_context.statManager().addRateData("stream.con.windowSizeAtCongestion", newWindowSize, _packet.getLifetime());
newWindowSize /= 2;
if (newWindowSize <= 0)
newWindowSize = 1;
getOptions().setWindowSize(newWindowSize);
int numSends = _packet.getNumSends() + 1;
if (numSends == 2) {
// first resend for this packet
_activeResends++;
_currentIsActiveResend = true;
}
// in case things really suck, the other side may have lost thier
// session tags (e.g. they restarted), so jump back to ElGamal.
int failTagsAt = _options.getMaxResends() - 1;
if ( (newWindowSize == 1) && (numSends == failTagsAt) ) {
if (_log.shouldLog(Log.WARN))
_log.warn("Optimistically failing tags at resend " + numSends);
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
}
if (_log.shouldLog(Log.WARN))
_log.warn("Resend packet " + _packet + " time " + numSends +
" activeResends: " + _activeResends +
" (wsize "
+ newWindowSize + " lifetime "
+ (_context.clock().now() - _packet.getCreatedOn()) + "ms)");
_outboundQueue.enqueue(_packet);
if (numSends > _options.getMaxResends()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Too many resends");
_packet.cancelled();
disconnect(false);
} else {
//long timeout = _options.getResendDelay() << numSends;
long rtt = _options.getRTT();
if (rtt < MIN_RESEND_DELAY)
rtt = MIN_RESEND_DELAY;
long timeout = rtt << (numSends-1);
if ( (timeout > MAX_RESEND_DELAY) || (timeout <= 0) )
timeout = MAX_RESEND_DELAY;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Scheduling resend in " + timeout + "ms for " + _packet);
SimpleTimer.getInstance().addEvent(ResendPacketEvent.this, timeout);
}
} else {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Packet acked before resend (resend="+ resend + "): "
// + _packet + " on " + Connection.this);
}
}
}
}

View File

@ -0,0 +1,185 @@
package net.i2p.client.streaming;
import java.io.InterruptedIOException;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* Receive data from the MessageOutputStream, build a packet,
* and send it through a connection. The write calls on this
* do NOT block, but they also do not necessary imply immediate
* delivery, or even the generation of a new packet. This class
* is the only one that builds useful outbound Packet objects.
*
*/
class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
private I2PAppContext _context;
private Log _log;
private Connection _connection;
private MessageOutputStream.WriteStatus _dummyStatus;
public ConnectionDataReceiver(I2PAppContext ctx, Connection con) {
_context = ctx;
_log = ctx.logManager().getLog(ConnectionDataReceiver.class);
_connection = con;
_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
* if there's nothing new to send.
*
* @param buf data to be sent - may be null
* @param off offset into the buffer to start writing from
* @param size how many bytes of the buffer to write (may be 0)
* @return an object to allow optional blocking for data acceptance or
* delivery.
*/
public MessageOutputStream.WriteStatus writeData(byte[] buf, int off, int size) {
boolean doSend = true;
if ( (size <= 0) && (_connection.getLastSendId() >= 0) ) {
if (_connection.getOutputStream().getClosed()) {
if (_connection.getCloseSentOn() < 0) {
doSend = true;
} else {
// closed, no new data, and we've already sent a close packet
doSend = false;
}
} else {
// no new data, not closed, already synchronized
doSend = false;
}
}
if (_connection.getUnackedPacketsReceived() > 0)
doSend = true;
if (_log.shouldLog(Log.INFO) && !doSend)
_log.info("writeData called: size="+size + " doSend=" + doSend
+ " unackedReceived: " + _connection.getUnackedPacketsReceived()
+ " con: " + _connection, new Exception("write called by"));
if (doSend) {
PacketLocal packet = send(buf, off, size);
//dont wait for non-acks
if ( (packet.getSequenceNum() > 0) || (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) )
return packet;
else
return _dummyStatus;
} else {
return _dummyStatus;
}
}
/**
* Send some data through the connection, attaching any appropriate flags
* onto the packet.
*
* @param buf data to be sent - may be null
* @param off offset into the buffer to start writing from
* @param size how many bytes of the buffer to write (may be 0)
* @return the packet sent
*/
public PacketLocal send(byte buf[], int off, int size) {
return send(buf, off, size, false);
}
/**
* @param buf data to be sent - may be null
* @param off offset into the buffer to start writing from
* @param size how many bytes of the buffer to write (may be 0)
* @param forceIncrement even if the buffer is empty, increment the packetId
* so we get an ACK back
* @return the packet sent
*/
public PacketLocal send(byte buf[], int off, int size, boolean forceIncrement) {
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;
}
private boolean isAckOnly(int size) {
boolean ackOnly = ( (size <= 0) && // no data
(_connection.getLastSendId() >= 0) && // not a SYN
( (!_connection.getOutputStream().getClosed()) || // not a CLOSE
(_connection.getOutputStream().getClosed() &&
_connection.getCloseSentOn() > 0) )); // or it is a dup CLOSE
return ackOnly;
}
private PacketLocal buildPacket(byte buf[], int off, int size, boolean forceIncrement) {
boolean ackOnly = isAckOnly(size);
PacketLocal packet = new PacketLocal(_context, _connection.getRemotePeer(), _connection);
byte data[] = new byte[size];
if (size > 0)
System.arraycopy(buf, off, data, 0, size);
packet.setPayload(data);
if (ackOnly && !forceIncrement)
packet.setSequenceNum(0);
else
packet.setSequenceNum(_connection.getNextOutboundPacketNum());
packet.setSendStreamId(_connection.getSendStreamId());
packet.setReceiveStreamId(_connection.getReceiveStreamId());
_connection.getInputStream().updateAcks(packet);
packet.setOptionalDelay(_connection.getOptions().getChoke());
packet.setOptionalMaxSize(_connection.getOptions().getMaxMessageSize());
packet.setResendDelay(_connection.getOptions().getResendDelay());
if (_connection.getOptions().getProfile() == ConnectionOptions.PROFILE_INTERACTIVE)
packet.setFlag(Packet.FLAG_PROFILE_INTERACTIVE, true);
else
packet.setFlag(Packet.FLAG_PROFILE_INTERACTIVE, false);
packet.setFlag(Packet.FLAG_SIGNATURE_REQUESTED, _connection.getOptions().getRequireFullySigned());
if ( (!ackOnly) && (packet.getSequenceNum() <= 0) ) {
packet.setFlag(Packet.FLAG_SYNCHRONIZE);
packet.setOptionalFrom(_connection.getSession().getMyDestination());
}
// don't set the closed flag if this is a plain ACK and there are outstanding
// packets sent, otherwise the other side could receive the CLOSE prematurely,
// since this ACK could arrive before the unacked payload message.
if (_connection.getOutputStream().getClosed() &&
( (size > 0) || (_connection.getUnackedPacketsSent() <= 0) ) ) {
packet.setFlag(Packet.FLAG_CLOSE);
_connection.setCloseSentOn(_context.clock().now());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Closed is set for a new packet on " + _connection + ": " + packet);
} else {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Closed is not set for a new packet on " + _connection + ": " + packet);
}
return packet;
}
/**
* Used if no new packet was sent.
*/
private static final class DummyStatus implements MessageOutputStream.WriteStatus {
public final void waitForAccept(int maxWaitMs) { return; }
public final void waitForCompletion(int maxWaitMs) { return; }
public final boolean writeAccepted() { return true; }
public final boolean writeFailed() { return false; }
public final boolean writeSuccessful() { return true; }
}
void destroy() {
_connection = null;
}
}

View File

@ -0,0 +1,158 @@
package net.i2p.client.streaming;
import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
* Receive new connection attempts
*/
class ConnectionHandler {
private I2PAppContext _context;
private Log _log;
private ConnectionManager _manager;
private List _synQueue;
private boolean _active;
private int _acceptTimeout;
/** max time after receiveNewSyn() and before the matched accept() */
private static final int DEFAULT_ACCEPT_TIMEOUT = 3*1000;
/** Creates a new instance of ConnectionHandler */
public ConnectionHandler(I2PAppContext context, ConnectionManager mgr) {
_context = context;
_log = context.logManager().getLog(ConnectionHandler.class);
_manager = mgr;
_synQueue = new ArrayList(5);
_active = false;
_acceptTimeout = DEFAULT_ACCEPT_TIMEOUT;
}
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) {
if (!_active) {
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping new SYN request, as we're not listening");
sendReset(packet);
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive new SYN: " + packet + ": timeout in " + _acceptTimeout);
SimpleTimer.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout);
synchronized (_synQueue) {
_synQueue.add(packet);
_synQueue.notifyAll();
}
}
/**
* Receive an incoming connection (built from a received SYN)
*
* @param timeoutMs max amount of time to wait for a connection (if less
* than 1ms, wait indefinitely)
* @return connection received, or null if there was a timeout or the
* handler was shut down
*/
public Connection accept(long timeoutMs) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Accept("+ timeoutMs+") called");
long expiration = timeoutMs + _context.clock().now();
while (true) {
if ( (timeoutMs > 0) && (expiration < _context.clock().now()) )
return null;
if (!_active) {
// fail all the ones we had queued up
synchronized (_synQueue) {
for (int i = 0; i < _synQueue.size(); i++) {
Packet packet = (Packet)_synQueue.get(i);
sendReset(packet);
}
_synQueue.clear();
}
return null;
}
Packet syn = null;
synchronized (_synQueue) {
while ( _active && (_synQueue.size() <= 0) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Accept("+ timeoutMs+"): active=" + _active + " queue: "
+ _synQueue.size());
if (timeoutMs <= 0) {
try { _synQueue.wait(); } catch (InterruptedException ie) {}
} else {
long remaining = expiration - _context.clock().now();
if (remaining < 0)
break;
try { _synQueue.wait(remaining); } catch (InterruptedException ie) {}
}
}
if (_active && _synQueue.size() > 0) {
syn = (Packet)_synQueue.remove(0);
}
}
if (syn != null) {
// deal with forged / invalid syn packets
Connection con = _manager.receiveConnection(syn);
if (con != null)
return con;
}
// keep looping...
}
}
private void sendReset(Packet packet) {
boolean ok = packet.verifySignature(_context, packet.getOptionalFrom(), null);
if (!ok) {
if (_log.shouldLog(Log.WARN))
_log.warn("Received a spoofed SYN packet: they said they were " + packet.getOptionalFrom());
return;
}
PacketLocal reply = new PacketLocal(_context, packet.getOptionalFrom());
reply.setFlag(Packet.FLAG_RESET);
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
reply.setAckThrough(packet.getSequenceNum());
reply.setSendStreamId(packet.getReceiveStreamId());
reply.setReceiveStreamId(null);
reply.setOptionalFrom(_manager.getSession().getMyDestination());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending RST: " + reply + " because of " + packet);
// this just sends the packet - no retries or whatnot
_manager.getPacketQueue().enqueue(reply);
}
private class TimeoutSyn implements SimpleTimer.TimedEvent {
private Packet _synPacket;
public TimeoutSyn(Packet packet) {
_synPacket = packet;
}
public void timeReached() {
boolean removed = false;
synchronized (_synQueue) {
removed = _synQueue.remove(_synPacket);
}
if (removed) {
// timeout - send RST
sendReset(_synPacket);
} else {
// handled. noop
}
}
}
}

View File

@ -0,0 +1,407 @@
package net.i2p.client.streaming;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PSession;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.SessionKey;
import net.i2p.util.SimpleTimer;
import net.i2p.util.Log;
/**
* Coordinate all of the connections for a single local destination.
*
*
*/
public class ConnectionManager {
private I2PAppContext _context;
private Log _log;
private I2PSession _session;
private MessageHandler _messageHandler;
private PacketHandler _packetHandler;
private ConnectionHandler _connectionHandler;
private PacketQueue _outboundQueue;
private SchedulerChooser _schedulerChooser;
private ConnectionPacketHandler _conPacketHandler;
/** Inbound stream ID (ByteArray) to Connection map */
private Map _connectionByInboundId;
/** Ping ID (ByteArray) to PingRequest */
private Map _pendingPings;
private boolean _allowIncoming;
private int _maxConcurrentStreams;
private volatile int _numWaiting;
private Object _connectionLock;
public ConnectionManager(I2PAppContext context, I2PSession session, int maxConcurrent) {
_context = context;
_log = context.logManager().getLog(ConnectionManager.class);
_connectionByInboundId = new HashMap(32);
_pendingPings = new HashMap(4);
_connectionLock = new Object();
_messageHandler = new MessageHandler(context, this);
_packetHandler = new PacketHandler(context, this);
_connectionHandler = new ConnectionHandler(context, this);
_schedulerChooser = new SchedulerChooser(context);
_conPacketHandler = new ConnectionPacketHandler(context);
_session = session;
session.setSessionListener(_messageHandler);
_outboundQueue = new PacketQueue(context, session, this);
_allowIncoming = false;
_maxConcurrentStreams = maxConcurrent;
_numWaiting = 0;
_context.statManager().createRateStat("stream.con.lifetimeMessagesSent", "How many messages do we send on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeMessagesReceived", "How many messages do we receive on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeBytesSent", "How many bytes do we send on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeBytesReceived", "How many bytes do we receive on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeDupMessagesSent", "How many duplicate messages do we send on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeDupMessagesReceived", "How many duplicate messages do we receive on a stream?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeRTT", "What is the final RTT when a stream closes?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeCongestionSeenAt", "When was the last congestion seen at when a stream closes?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("stream.con.lifetimeSendWindowSize", "What is the final send window size when a stream closes?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
}
Connection getConnectionByInboundId(byte[] id) {
synchronized (_connectionLock) {
return (Connection)_connectionByInboundId.get(new ByteArray(id));
}
}
/**
* not guaranteed to be unique, but in case we receive more than one packet
* on an inbound connection that we havent ack'ed yet...
*/
Connection getConnectionByOutboundId(byte[] id) {
synchronized (_connectionLock) {
for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) {
Connection con = (Connection)iter.next();
if (DataHelper.eq(con.getSendStreamId(), id))
return con;
}
}
return null;
}
public void setAllowIncomingConnections(boolean allow) {
_connectionHandler.setActive(allow);
}
/** should we acceot connections, or just reject everyone? */
public boolean getAllowIncomingConnections() {
return _connectionHandler.getActive();
}
/**
* Create a new connection based on the SYN packet we received.
*
* @return created Connection with the packet's data already delivered to
* it, or null if the syn's streamId was already taken
*/
public Connection receiveConnection(Packet synPacket) {
Connection con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler);
byte receiveId[] = new byte[4];
_context.random().nextBytes(receiveId);
boolean reject = false;
synchronized (_connectionLock) {
if (locked_tooManyStreams()) {
reject = true;
} else {
while (true) {
ByteArray ba = new ByteArray(receiveId);
Connection oldCon = (Connection)_connectionByInboundId.put(ba, con);
if (oldCon == null) {
break;
} else {
_connectionByInboundId.put(ba, oldCon);
// receiveId already taken, try another
_context.random().nextBytes(receiveId);
}
}
}
}
if (reject) {
if (_log.shouldLog(Log.WARN))
_log.warn("Refusing connection since we have exceeded our max of "
+ _maxConcurrentStreams + " connections");
PacketLocal reply = new PacketLocal(_context, synPacket.getOptionalFrom());
reply.setFlag(Packet.FLAG_RESET);
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
reply.setAckThrough(synPacket.getSequenceNum());
reply.setSendStreamId(synPacket.getReceiveStreamId());
reply.setReceiveStreamId(null);
reply.setOptionalFrom(_session.getMyDestination());
// this just sends the packet - no retries or whatnot
_outboundQueue.enqueue(reply);
return null;
}
con.setReceiveStreamId(receiveId);
try {
con.getPacketHandler().receivePacket(synPacket, con);
} catch (I2PException ie) {
synchronized (_connectionLock) {
_connectionByInboundId.remove(new ByteArray(receiveId));
}
return null;
}
_context.statManager().addRateData("stream.connectionReceived", 1, 0);
return con;
}
private static final long DEFAULT_STREAM_DELAY_MAX = 10*1000;
/**
* Build a new connection to the given peer. This blocks if there is no
* connection delay, otherwise it returns immediately.
*
* @return new connection, or null if we have exceeded our limit
*/
public Connection connect(Destination peer, ConnectionOptions opts) {
Connection con = null;
byte receiveId[] = new byte[4];
_context.random().nextBytes(receiveId);
long expiration = _context.clock().now() + opts.getConnectTimeout();
if (opts.getConnectTimeout() <= 0)
expiration = _context.clock().now() + DEFAULT_STREAM_DELAY_MAX;
_numWaiting++;
while (true) {
long remaining = expiration - _context.clock().now();
if (remaining <= 0) {
if (_log.shouldLog(Log.WARN))
_log.warn("Refusing to connect since we have exceeded our max of "
+ _maxConcurrentStreams + " connections");
_numWaiting--;
return null;
}
boolean reject = false;
synchronized (_connectionLock) {
if (locked_tooManyStreams()) {
// allow a full buffer of pending/waiting streams
if (_numWaiting > _maxConcurrentStreams) {
if (_log.shouldLog(Log.WARN))
_log.warn("Refusing connection since we have exceeded our max of "
+ _maxConcurrentStreams + " and there are " + _numWaiting
+ " waiting already");
_numWaiting--;
return null;
}
// no remaining streams, lets wait a bit
try { _connectionLock.wait(remaining); } catch (InterruptedException ie) {}
} else {
con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler, opts);
con.setRemotePeer(peer);
ByteArray ba = new ByteArray(receiveId);
while (_connectionByInboundId.containsKey(ba)) {
_context.random().nextBytes(receiveId);
}
_connectionByInboundId.put(ba, con);
break; // stop looping as a psuedo-wait
}
}
}
// ok we're in...
con.setReceiveStreamId(receiveId);
con.eventOccurred();
_log.debug("Connect() conDelay = " + opts.getConnectDelay());
if (opts.getConnectDelay() <= 0) {
con.waitForConnect();
}
if (_numWaiting > 0)
_numWaiting--;
_context.statManager().addRateData("stream.connectionCreated", 1, 0);
return con;
}
private boolean locked_tooManyStreams() {
int active = 0;
for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) {
Connection con = (Connection)iter.next();
if (con.getIsConnected())
active++;
}
if ( (_connectionByInboundId.size() > 100) && (_log.shouldLog(Log.INFO)) )
_log.info("More than 100 connections! " + active
+ " total: " + _connectionByInboundId.size());
if (_maxConcurrentStreams <= 0) return false;
if (_connectionByInboundId.size() < _maxConcurrentStreams) return false;
return (active >= _maxConcurrentStreams);
}
public MessageHandler getMessageHandler() { return _messageHandler; }
public PacketHandler getPacketHandler() { return _packetHandler; }
public ConnectionHandler getConnectionHandler() { return _connectionHandler; }
public I2PSession getSession() { return _session; }
public PacketQueue getPacketQueue() { return _outboundQueue; }
/**
* Something b0rked hard, so kill all of our connections without mercy.
* Don't bother sending close packets.
*
*/
public void disconnectAllHard() {
synchronized (_connectionLock) {
for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) {
Connection con = (Connection)iter.next();
con.disconnect(false, false);
}
_connectionByInboundId.clear();
_connectionLock.notifyAll();
}
}
/**
* Drop the (already closed) connection on the floor.
*
*/
public void removeConnection(Connection con) {
boolean removed = false;
synchronized (_connectionLock) {
Object o = _connectionByInboundId.remove(new ByteArray(con.getReceiveStreamId()));
removed = (o == con);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Connection removed? " + removed + " remaining: "
+ _connectionByInboundId.size() + ": " + con);
if (!removed && _log.shouldLog(Log.DEBUG))
_log.debug("Failed to remove " + con +"\n" + _connectionByInboundId.values());
_connectionLock.notifyAll();
}
if (removed) {
_context.statManager().addRateData("stream.con.lifetimeMessagesSent", con.getLastSendId(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeMessagesReceived", con.getHighestAckedThrough(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeBytesSent", con.getLifetimeBytesSent(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeBytesReceived", con.getLifetimeBytesReceived(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeDupMessagesSent", con.getLifetimeDupMessagesSent(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeDupMessagesReceived", con.getLifetimeDupMessagesReceived(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeRTT", con.getOptions().getRTT(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeCongestionSeenAt", con.getLastCongestionSeenAt(), con.getLifetime());
_context.statManager().addRateData("stream.con.lifetimeSendWindowSize", con.getOptions().getWindowSize(), con.getLifetime());
}
}
/** return a set of Connection objects */
public Set listConnections() {
synchronized (_connectionLock) {
return new HashSet(_connectionByInboundId.values());
}
}
public boolean ping(Destination peer, long timeoutMs) {
return ping(peer, timeoutMs, true);
}
public boolean ping(Destination peer, long timeoutMs, boolean blocking) {
return ping(peer, timeoutMs, blocking, null, null, null);
}
public boolean ping(Destination peer, long timeoutMs, boolean blocking, SessionKey keyToUse, Set tagsToSend, PingNotifier notifier) {
byte id[] = new byte[4];
_context.random().nextBytes(id);
ByteArray ba = new ByteArray(id);
PacketLocal packet = new PacketLocal(_context, peer);
packet.setSendStreamId(id);
packet.setFlag(Packet.FLAG_ECHO);
packet.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
packet.setOptionalFrom(_session.getMyDestination());
if ( (keyToUse != null) && (tagsToSend != null) ) {
packet.setKeyUsed(keyToUse);
packet.setTagsSent(tagsToSend);
}
PingRequest req = new PingRequest(peer, packet, notifier);
synchronized (_pendingPings) {
_pendingPings.put(ba, req);
}
_outboundQueue.enqueue(packet);
if (blocking) {
synchronized (req) {
if (!req.pongReceived())
try { req.wait(timeoutMs); } catch (InterruptedException ie) {}
}
synchronized (_pendingPings) {
_pendingPings.remove(ba);
}
} else {
SimpleTimer.getInstance().addEvent(new PingFailed(ba, notifier), timeoutMs);
}
boolean ok = req.pongReceived();
return ok;
}
interface PingNotifier {
public void pingComplete(boolean ok);
}
private class PingFailed implements SimpleTimer.TimedEvent {
private ByteArray _ba;
private PingNotifier _notifier;
public PingFailed(ByteArray ba, PingNotifier notifier) {
_ba = ba;
_notifier = notifier;
}
public void timeReached() {
boolean removed = false;
synchronized (_pendingPings) {
Object o = _pendingPings.remove(_ba);
if (o != null)
removed = true;
}
if (removed) {
if (_notifier != null)
_notifier.pingComplete(false);
_log.error("Ping failed");
}
}
}
private class PingRequest {
private boolean _ponged;
private Destination _peer;
private PacketLocal _packet;
private PingNotifier _notifier;
public PingRequest(Destination peer, PacketLocal packet, PingNotifier notifier) {
_ponged = false;
_peer = peer;
_packet = packet;
_notifier = notifier;
}
public void pong() {
_log.debug("Ping successful");
_context.sessionKeyManager().tagsDelivered(_peer.getPublicKey(), _packet.getKeyUsed(), _packet.getTagsSent());
synchronized (ConnectionManager.PingRequest.this) {
_ponged = true;
ConnectionManager.PingRequest.this.notifyAll();
}
if (_notifier != null)
_notifier.pingComplete(true);
}
public boolean pongReceived() { return _ponged; }
}
void receivePong(byte pingId[]) {
ByteArray ba = new ByteArray(pingId);
PingRequest req = null;
synchronized (_pendingPings) {
req = (PingRequest)_pendingPings.remove(ba);
}
if (req != null)
req.pong();
}
}

View File

@ -0,0 +1,233 @@
package net.i2p.client.streaming;
import java.util.Properties;
/**
* Define the current options for the con (and allow custom tweaking midstream)
*
*/
public class ConnectionOptions extends I2PSocketOptionsImpl {
private int _connectDelay;
private boolean _fullySigned;
private int _windowSize;
private int _receiveWindow;
private int _profile;
private int _rtt;
private int _resendDelay;
private int _sendAckDelay;
private int _maxMessageSize;
private int _choke;
private int _maxResends;
private int _inactivityTimeout;
private int _inactivityAction;
private int _inboundBufferSize;
public static final int PROFILE_BULK = 1;
public static final int PROFILE_INTERACTIVE = 2;
/** on inactivity timeout, do nothing */
public static final int INACTIVITY_ACTION_NOOP = 0;
/** on inactivity timeout, close the connection */
public static final int INACTIVITY_ACTION_DISCONNECT = 1;
/** on inactivity timeout, send a payload message */
public static final int INACTIVITY_ACTION_SEND = 2;
public static final String PROP_CONNECT_DELAY = "i2p.streaming.connectDelay";
public static final String PROP_PROFILE = "i2p.streaming.profile";
public static final String PROP_MAX_MESSAGE_SIZE = "i2p.streaming.maxMessageSize";
public static final String PROP_MAX_RESENDS = "i2p.streaming.maxResends";
public static final String PROP_INITIAL_RTT = "i2p.streaming.initialRTT";
public static final String PROP_INITIAL_RESEND_DELAY = "i2p.streaming.initialResendDelay";
public static final String PROP_INITIAL_ACK_DELAY = "i2p.streaming.initialAckDelay";
public static final String PROP_INITIAL_WINDOW_SIZE = "i2p.streaming.initialWindowSize";
public static final String PROP_INITIAL_RECEIVE_WINDOW = "i2p.streaming.initialReceiveWindow";
public static final String PROP_INACTIVITY_TIMEOUT = "i2p.streaming.inactivityTimeout";
public static final String PROP_INACTIVITY_ACTION = "i2p.streaming.inactivityAction";
public ConnectionOptions() {
super();
}
public ConnectionOptions(Properties opts) {
super(opts);
}
public ConnectionOptions(I2PSocketOptions opts) {
super(opts);
}
public ConnectionOptions(ConnectionOptions opts) {
super(opts);
if (opts != null) {
setConnectDelay(opts.getConnectDelay());
setProfile(opts.getProfile());
setRTT(opts.getRTT());
setRequireFullySigned(opts.getRequireFullySigned());
setWindowSize(opts.getWindowSize());
setResendDelay(opts.getResendDelay());
setMaxMessageSize(opts.getMaxMessageSize());
setChoke(opts.getChoke());
setMaxResends(opts.getMaxResends());
setInactivityTimeout(opts.getInactivityTimeout());
setInactivityAction(opts.getInactivityAction());
setInboundBufferSize(opts.getInboundBufferSize());
}
}
protected void init(Properties opts) {
super.init(opts);
setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1));
setProfile(getInt(opts, PROP_PROFILE, PROFILE_BULK));
setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, Packet.MAX_PAYLOAD_SIZE));
setRTT(getInt(opts, PROP_INITIAL_RTT, 30*1000));
setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 5*1000));
setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, 2*1000));
setWindowSize(getInt(opts, PROP_INITIAL_WINDOW_SIZE, 1));
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 5));
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_DISCONNECT));
setInboundBufferSize((getMaxMessageSize() + 2) * Connection.MAX_WINDOW_SIZE);
}
/**
* how long will we wait after instantiating a new con
* before actually attempting to connect. If this is
* set to 0, connect ASAP. If it is greater than 0, wait
* until the output stream is flushed, the buffer fills,
* or that many milliseconds pass.
*
*/
public int getConnectDelay() { return _connectDelay; }
public void setConnectDelay(int delayMs) { _connectDelay = delayMs; }
/**
* Do we want all packets in both directions to be signed,
* or can we deal with signatures on the SYN and FIN packets
* only?
*
*/
public boolean getRequireFullySigned() { return _fullySigned; }
public void setRequireFullySigned(boolean sign) { _fullySigned = sign; }
/**
* How many messages will we send before waiting for an ACK?
*
*/
public int getWindowSize() { return _windowSize; }
public void setWindowSize(int numMsgs) {
if (numMsgs > Connection.MAX_WINDOW_SIZE)
numMsgs = Connection.MAX_WINDOW_SIZE;
_windowSize = numMsgs;
}
/** after how many consecutive messages should we ack? */
public int getReceiveWindow() { return _receiveWindow; }
public void setReceiveWindow(int numMsgs) { _receiveWindow = numMsgs; }
/**
* What to set the round trip time estimate to (in milliseconds)
*/
public int getRTT() { return _rtt; }
public void setRTT(int ms) {
_rtt = ms;
if (_rtt > 60*1000)
_rtt = 60*1000;
}
/** rtt = rtt*RTT_DAMPENING + (1-RTT_DAMPENING)*currentPacketRTT */
private static final double RTT_DAMPENING = 0.9;
public void updateRTT(int measuredValue) {
setRTT((int)(RTT_DAMPENING*_rtt + (1-RTT_DAMPENING)*measuredValue));
}
/** How long after sending a packet will we wait before resending? */
public int getResendDelay() { return _resendDelay; }
public void setResendDelay(int ms) { _resendDelay = ms; }
/**
* if there are packets we haven't ACKed yet and we don't
* receive _receiveWindow messages before
* (_lastSendTime+_sendAckDelay), send an ACK of what
* we have received so far.
*
*/
public int getSendAckDelay() { return _sendAckDelay; }
public void setSendAckDelay(int delayMs) { _sendAckDelay = delayMs; }
/** What is the largest message we want to send or receive? */
public int getMaxMessageSize() { return _maxMessageSize; }
public void setMaxMessageSize(int bytes) { _maxMessageSize = bytes; }
/**
* how long we want to wait before any data is transferred on the
* connection in either direction
*
*/
public int getChoke() { return _choke; }
public void setChoke(int ms) { _choke = ms; }
/**
* What profile do we want to use for this connection?
*
*/
public int getProfile() { return _profile; }
public void setProfile(int profile) {
if (profile != PROFILE_BULK)
throw new IllegalArgumentException("Only bulk is supported so far");
_profile = profile;
}
/**
* How many times will we try to send a message before giving up?
*
*/
public int getMaxResends() { return _maxResends; }
public void setMaxResends(int numSends) { _maxResends = numSends; }
/**
* What period of inactivity qualifies as "too long"?
*
*/
public int getInactivityTimeout() { return _inactivityTimeout; }
public void setInactivityTimeout(int timeout) { _inactivityTimeout = timeout; }
public int getInactivityAction() { return _inactivityAction; }
public void setInactivityAction(int action) { _inactivityAction = action; }
/**
* how much data are we willing to accept in our buffer?
*
*/
public int getInboundBufferSize() { return _inboundBufferSize; }
public void setInboundBufferSize(int bytes) { _inboundBufferSize = bytes; }
public String toString() {
StringBuffer buf = new StringBuffer(128);
buf.append("conDelay=").append(_connectDelay);
buf.append(" maxSize=").append(_maxMessageSize);
buf.append(" rtt=").append(_rtt);
buf.append(" rwin=").append(_receiveWindow);
buf.append(" resendDelay=").append(_resendDelay);
buf.append(" ackDelay=").append(_sendAckDelay);
buf.append(" cwin=").append(_windowSize);
buf.append(" maxResends=").append(_maxResends);
buf.append(" writeTimeout=").append(getWriteTimeout());
buf.append(" inactivityTimeout=").append(_inactivityTimeout);
buf.append(" inboundBuffer=").append(_inboundBufferSize);
return buf.toString();
}
public static void main(String args[]) {
Properties p = new Properties();
p.setProperty(PROP_CONNECT_DELAY, "1000");
ConnectionOptions c = new ConnectionOptions(p);
System.out.println("opts: " + c);
c = new ConnectionOptions(new I2PSocketOptionsImpl(p));
System.out.println("opts: " + c);
}
}

View File

@ -0,0 +1,318 @@
package net.i2p.client.streaming;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
* Receive a packet for a particular connection - placing the data onto the
* queue, marking packets as acked, updating various fields, etc.
*
*/
public class ConnectionPacketHandler {
private I2PAppContext _context;
private Log _log;
public ConnectionPacketHandler(I2PAppContext context) {
_context = context;
_log = context.logManager().getLog(ConnectionPacketHandler.class);
_context.statManager().createRateStat("stream.con.receiveMessageSize", "Size of a message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.con.receiveDuplicateSize", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.con.packetsAckedPerMessageReceived", "Size of a duplicate message received on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("stream.sendsBeforeAck", "How many times a message was sent before it was ACKed?", "Stream", new long[] { 10*60*1000, 60*60*1000 });
}
/** distribute a packet to the connection specified */
void receivePacket(Packet packet, Connection con) throws I2PException {
boolean ok = verifyPacket(packet, con);
if (!ok) {
if ( (!packet.isFlagSet(Packet.FLAG_RESET)) && (_log.shouldLog(Log.ERROR)) )
_log.error("Packet does NOT verify: " + packet);
return;
}
con.packetReceived();
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 + " ("
+ ready + "/"+ (ready+allowedBlocks) + "/" + available
+ ": dropping " + packet);
ack(con, packet.getAckThrough(), packet.getNacks(), null, false);
con.getOptions().setChoke(5*1000);
return;
}
con.getOptions().setChoke(0);
_context.statManager().addRateData("stream.con.receiveMessageSize", packet.getPayloadSize(), 0);
boolean isNew = con.getInputStream().messageReceived(packet.getSequenceNum(), packet.getPayload());
if ( (packet.getSequenceNum() == 0) && (packet.getPayloadSize() > 0) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("seq=0 && size=" + packet.getPayloadSize() + ": isNew? " + isNew
+ " packet: " + packet + " con: " + con);
}
// close *after* receiving the data, as well as after verifying the signatures / etc
if (packet.isFlagSet(Packet.FLAG_CLOSE) && packet.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED))
con.closeReceived();
if (isNew) {
con.incrementUnackedPacketsReceived();
con.incrementBytesReceived(packet.getPayloadSize());
if (packet.isFlagSet(Packet.FLAG_DELAY_REQUESTED) && (packet.getOptionalDelay() <= 0) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Scheduling immediate ack for " + packet);
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
} else {
int delay = con.getOptions().getSendAckDelay();
if (packet.isFlagSet(Packet.FLAG_DELAY_REQUESTED)) // delayed ACK requested
delay += packet.getOptionalDelay();
con.setNextSendTime(delay + _context.clock().now());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Scheduling ack in " + delay + "ms for received packet " + packet);
}
} else {
if ( (packet.getSequenceNum() > 0) || (packet.getPayloadSize() > 0) ) {
_context.statManager().addRateData("stream.con.receiveDuplicateSize", packet.getPayloadSize(), 0);
con.incrementDupMessagesReceived(1);
// take note of congestion
//con.getOptions().setResendDelay(con.getOptions().getResendDelay()*2);
//con.getOptions().setWindowSize(con.getOptions().getWindowSize()/2);
if (_log.shouldLog(Log.WARN))
_log.warn("congestion.. dup " + packet);
SimpleTimer.getInstance().addEvent(new AckDup(con), con.getOptions().getSendAckDelay());
//con.incrementUnackedPacketsReceived();
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
} else {
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
//con.incrementUnackedPacketsReceived();
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("ACK only packet received: " + packet);
}
}
}
boolean fastAck = ack(con, packet.getAckThrough(), packet.getNacks(), packet, isNew);
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();
}
}
}
private boolean ack(Connection con, long ackThrough, long nacks[], Packet packet, boolean isNew) {
int numResends = 0;
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);
// use the highest RTT, since these would likely be bunched together,
// and the highest rtt lets us set our resend delay properly
int highestRTT = -1;
for (int i = 0; i < acked.size(); i++) {
PacketLocal p = (PacketLocal)acked.get(i);
if (p.getAckTime() > highestRTT) {
//if (p.getNumSends() <= 1)
highestRTT = p.getAckTime();
}
_context.statManager().addRateData("stream.sendsBeforeAck", p.getNumSends(), p.getAckTime());
if (p.getNumSends() > 1)
numResends++;
// ACK the tags we delivered so we can use them
if ( (p.getKeyUsed() != null) && (p.getTagsSent() != null)
&& (p.getTagsSent().size() > 0) ) {
_context.sessionKeyManager().tagsDelivered(p.getTo().getPublicKey(),
p.getKeyUsed(),
p.getTagsSent());
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet acked after " + p.getAckTime() + "ms: " + p);
}
if (highestRTT > 0) {
con.getOptions().updateRTT(highestRTT);
}
_context.statManager().addRateData("stream.con.packetsAckedPerMessageReceived", acked.size(), highestRTT);
}
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();
con.congestionOccurred();
oldSize >>>= 1;
if (oldSize <= 0)
oldSize = 1;
con.getOptions().setWindowSize(oldSize);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Congestion occurred - new windowSize " + oldSize + " congestionSeenAt: "
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
+ ") for " + con);
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;
} else {
// slow start
newWindowSize += 1;
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("New window size " + newWindowSize + " congestionSeenAt: "
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
+ ") for " + con);
con.getOptions().setWindowSize(newWindowSize);
con.setCongestionWindowEnd(newWindowSize + lowest);
}
return congested;
}
/**
* Make sure this packet is ok and that we can continue processing its data.
*
* @return true if the packet is ok for this connection, false if we shouldn't
* continue processing.
*/
private boolean verifyPacket(Packet packet, Connection con) throws I2PException {
if (packet.isFlagSet(Packet.FLAG_RESET)) {
verifyReset(packet, con);
return false;
} else {
verifySignature(packet, con);
if (con.getSendStreamId() == null) {
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
con.setSendStreamId(packet.getReceiveStreamId());
con.setRemotePeer(packet.getOptionalFrom());
return true;
} else {
// neither RST nor SYN and we dont have the stream id yet? nuh uh
if (_log.shouldLog(Log.WARN))
_log.warn("Packet without RST or SYN where we dont know stream ID: "
+ packet);
return false;
}
} else {
if (!DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Packet received with the wrong reply stream id: "
+ con + " / " + packet);
return false;
} else {
return true;
}
}
}
}
/**
* Make sure this RST packet is valid, and if it is, act on it.
*/
private void verifyReset(Packet packet, Connection con) {
if (DataHelper.eq(con.getReceiveStreamId(), packet.getSendStreamId())) {
boolean ok = packet.verifySignature(_context, packet.getOptionalFrom(), null);
if (!ok) {
if (_log.shouldLog(Log.WARN))
_log.warn("Received unsigned / forged RST on " + con);
return;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Reset received");
// ok, valid RST
con.resetReceived();
con.eventOccurred();
// no further processing
return;
}
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Received a packet for the wrong connection? wtf: "
+ con + " / " + packet);
return;
}
}
/**
* Verify the signature if necessary.
*
* @throws I2PException if the signature was necessary and it was invalid
*/
private void verifySignature(Packet packet, Connection con) throws I2PException {
// verify the signature if necessary
if (con.getOptions().getRequireFullySigned() ||
packet.isFlagSet(Packet.FLAG_SYNCHRONIZE) ||
packet.isFlagSet(Packet.FLAG_CLOSE) ) {
// we need a valid signature
Destination from = con.getRemotePeer();
if (from == null)
from = packet.getOptionalFrom();
boolean sigOk = packet.verifySignature(_context, from, null);
if (!sigOk) {
throw new I2PException("Received unsigned / forged packet: " + packet);
}
}
}
private class AckDup implements SimpleTimer.TimedEvent {
private long _created;
private Connection _con;
public AckDup(Connection con) {
_created = _context.clock().now();
_con = con;
}
public void timeReached() {
if (_con.getLastSendTime() <= _created) {
if (!_con.getIsConnected()) return;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Last sent was a while ago, and we want to ack a dup");
// we haven't done anything since receiving the dup, send an
// ack now
_con.ackImmediately();
}
}
}
}

View File

@ -0,0 +1,24 @@
package net.i2p.client.streaming;
import java.net.ConnectException;
import net.i2p.I2PException;
/**
* Bridge to allow accepting new connections
*
*/
public class I2PServerSocketFull implements I2PServerSocket {
private I2PSocketManagerFull _socketManager;
public I2PServerSocketFull(I2PSocketManagerFull mgr) {
_socketManager = mgr;
}
public I2PSocket accept() throws I2PException {
return _socketManager.receiveSocket();
}
public void close() { _socketManager.getConnectionManager().setAllowIncomingConnections(false); }
public I2PSocketManager getManager() { return _socketManager; }
}

View File

@ -0,0 +1,78 @@
package net.i2p.client.streaming;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.data.Destination;
/**
* Bridge between the full streaming lib and the I2PSocket API
*
*/
public class I2PSocketFull implements I2PSocket {
private Connection _connection;
private I2PSocket.SocketErrorListener _listener;
public I2PSocketFull(Connection con) {
_connection = con;
}
public void close() throws IOException {
Connection c = _connection;
if (c == null) return;
if (c.getIsConnected()) {
OutputStream out = c.getOutputStream();
if (out != null)
out.close();
c.disconnect(true);
} else {
//throw new IOException("Not connected");
}
destroy();
}
public InputStream getInputStream() {
return _connection.getInputStream();
}
public I2PSocketOptions getOptions() {
return _connection.getOptions();
}
public OutputStream getOutputStream() throws IOException {
return _connection.getOutputStream();
}
public Destination getPeerDestination() {
return _connection.getRemotePeer();
}
public long getReadTimeout() {
return _connection.getOptions().getReadTimeout();
}
public Destination getThisDestination() {
return _connection.getSession().getMyDestination();
}
public void setOptions(I2PSocketOptions options) {
if (options instanceof ConnectionOptions)
_connection.setOptions((ConnectionOptions)options);
else
_connection.setOptions(new ConnectionOptions(options));
}
public void setReadTimeout(long ms) {
_connection.getOptions().setReadTimeout(ms);
}
public void setSocketErrorListener(I2PSocket.SocketErrorListener lsnr) {
_listener = lsnr;
}
void destroy() {
_connection = null;
_listener = null;
}
}

View File

@ -0,0 +1,228 @@
package net.i2p.client.streaming;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Centralize the coordination and multiplexing of the local client's streaming.
* There should be one I2PSocketManager for each I2PSession, and if an application
* is sending and receiving data through the streaming library using an
* I2PSocketManager, it should not attempt to call I2PSession's setSessionListener
* or receive any messages with its .receiveMessage
*
*/
public class I2PSocketManagerFull implements I2PSocketManager {
private I2PAppContext _context;
private Log _log;
private I2PSession _session;
private I2PServerSocketFull _serverSocket;
private ConnectionOptions _defaultOptions;
private long _acceptTimeout;
private String _name;
private int _maxStreams;
private static int __managerId = 0;
private ConnectionManager _connectionManager;
/**
* How long to wait for the client app to accept() before sending back CLOSE?
* This includes the time waiting in the queue. Currently set to 5 seconds.
*/
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
public I2PSocketManagerFull() {
_context = null;
_session = null;
}
public I2PSocketManagerFull(I2PAppContext context, I2PSession session, Properties opts, String name) {
this();
init(context, session, opts, name);
}
/** how many streams will we allow at once? */
public static final String PROP_MAX_STREAMS = "i2p.streaming.maxConcurrentStreams";
/**
*
*/
public void init(I2PAppContext context, I2PSession session, Properties opts, String name) {
_context = context;
_session = session;
_log = _context.logManager().getLog(I2PSocketManagerFull.class);
_maxStreams = -1;
try {
String num = (opts != null ? opts.getProperty(PROP_MAX_STREAMS, "-1") : "-1");
_maxStreams = Integer.parseInt(num);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid max # of concurrent streams, defaulting to unlimited", nfe);
_maxStreams = -1;
}
_connectionManager = new ConnectionManager(_context, _session, _maxStreams);
_name = name + " " + (++__managerId);
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
_defaultOptions = new ConnectionOptions(opts);
_serverSocket = new I2PServerSocketFull(this);
if (_log.shouldLog(Log.INFO)) {
_log.info("Socket manager created. \ndefault options: " + _defaultOptions
+ "\noriginal properties: " + opts);
}
}
public I2PSocketOptions buildOptions() { return buildOptions(null); }
public I2PSocketOptions buildOptions(Properties opts) {
return new ConnectionOptions(opts);
}
public I2PSession getSession() {
return _session;
}
public ConnectionManager getConnectionManager() {
return _connectionManager;
}
public I2PSocket receiveSocket() throws I2PException {
if (_session.isClosed()) throw new I2PException("Session closed");
Connection con = _connectionManager.getConnectionHandler().accept(-1);
if (_log.shouldLog(Log.DEBUG))
_log.debug("receiveSocket() called: " + con);
if (con != null) {
I2PSocketFull sock = new I2PSocketFull(con);
con.setSocket(sock);
return sock;
} else {
return null;
}
}
/**
* Ping the specified peer, returning true if they replied to the ping within
* the timeout specified, false otherwise. This call blocks.
*
*/
public boolean ping(Destination peer, long timeoutMs) {
return _connectionManager.ping(peer, timeoutMs);
}
/**
* How long should we wait for the client to .accept() a socket before
* sending back a NACK/Close?
*
* @param ms milliseconds to wait, maximum
*/
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
public long getAcceptTimeout() { return _acceptTimeout; }
public void setDefaultOptions(I2PSocketOptions options) {
_defaultOptions = new ConnectionOptions(options);
}
public I2PSocketOptions getDefaultOptions() {
return _defaultOptions;
}
public I2PServerSocket getServerSocket() {
_connectionManager.setAllowIncomingConnections(true);
return _serverSocket;
}
/**
* Create a new connected socket (block until the socket is created)
*
* @param peer Destination to connect to
* @param options I2P socket options to be used for connecting
*
* @throws NoRouteToHostException if the peer is not found or not reachable
* @throws I2PException if there is some other I2P-related problem
*/
public I2PSocket connect(Destination peer, I2PSocketOptions options)
throws I2PException, NoRouteToHostException {
if (_connectionManager.getSession().isClosed())
throw new I2PException("Session is closed");
if (options == null)
options = _defaultOptions;
ConnectionOptions opts = null;
if (options instanceof ConnectionOptions)
opts = (ConnectionOptions)options;
else
opts = new ConnectionOptions(options);
Connection con = _connectionManager.connect(peer, opts);
if (con == null)
throw new TooManyStreamsException("Too many streams (max " + _maxStreams + ")");
I2PSocketFull socket = new I2PSocketFull(con);
con.setSocket(socket);
if (con.getConnectionError() != null) {
con.disconnect(false);
throw new NoRouteToHostException(con.getConnectionError());
}
return socket;
}
/**
* Create a new connected socket (block until the socket is created)
*
* @param peer Destination to connect to
*
* @throws NoRouteToHostException if the peer is not found or not reachable
* @throws I2PException if there is some other I2P-related problem
*/
public I2PSocket connect(Destination peer) throws I2PException, NoRouteToHostException {
return connect(peer, _defaultOptions);
}
/**
* Destroy the socket manager, freeing all the associated resources. This
* method will block untill all the managed sockets are closed.
*
*/
public void destroySocketManager() {
_connectionManager.disconnectAllHard();
_connectionManager.setAllowIncomingConnections(false);
// should we destroy the _session too?
// yes, since the old lib did (and SAM wants it to, and i dont know why not)
if ( (_session != null) && (!_session.isClosed()) ) {
try {
_session.destroySession();
} catch (I2PSessionException ise) {
_log.warn("Unable to destroy the session", ise);
}
}
}
/**
* Retrieve a set of currently connected I2PSockets, either initiated locally or remotely.
*
*/
public Set listSockets() {
Set connections = _connectionManager.listConnections();
Set rv = new HashSet(connections.size());
for (Iterator iter = connections.iterator(); iter.hasNext(); ) {
Connection con = (Connection)iter.next();
if (con.getSocket() != null)
rv.add(con.getSocket());
}
return rv;
}
public String getName() { return _name; }
public void setName(String name) { _name = name; }
}

View File

@ -0,0 +1,83 @@
package net.i2p.client.streaming;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionListener;
import net.i2p.client.I2PSessionException;
import net.i2p.util.Log;
/**
* Receive raw information from the I2PSession and turn it into
* Packets, if we can.
*
*/
public class MessageHandler implements I2PSessionListener {
private ConnectionManager _manager;
private I2PAppContext _context;
private Log _log;
public MessageHandler(I2PAppContext ctx, ConnectionManager mgr) {
_manager = mgr;
_context = ctx;
_log = ctx.logManager().getLog(MessageHandler.class);
_context.statManager().createRateStat("stream.packetReceiveFailure", "When do we fail to decrypt or otherwise receive a packet sent to us?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 });
}
/** Instruct the client that the given session has received a message with
* size # of bytes.
* @param session session to notify
* @param msgId message number available
* @param size size of the message
*/
public void messageAvailable(I2PSession session, int msgId, long size) {
byte data[] = null;
try {
data = session.receiveMessage(msgId);
} catch (I2PSessionException ise) {
_context.statManager().addRateData("stream.packetReceiveFailure", 1, 0);
if (_log.shouldLog(Log.WARN))
_log.warn("Error receiving the message", ise);
return;
}
Packet packet = new Packet();
try {
packet.readPacket(data, 0, data.length);
_manager.getPacketHandler().receivePacket(packet);
} catch (IllegalArgumentException iae) {
_context.statManager().addRateData("stream.packetReceiveFailure", 1, 0);
if (_log.shouldLog(Log.WARN))
_log.warn("Received an invalid packet", iae);
}
}
/** Instruct the client that the session specified seems to be under attack
* and that the client may wish to move its destination to another router.
* @param session session to report abuse to
* @param severity how bad the abuse is
*/
public void reportAbuse(I2PSession session, int severity) {
if (_log.shouldLog(Log.ERROR))
_log.error("Abuse reported with severity " + severity);
_manager.disconnectAllHard();
}
/**
* Notify the client that the session has been terminated
*
*/
public void disconnected(I2PSession session) {
if (_log.shouldLog(Log.ERROR))
_log.error("I2PSession disconnected");
_manager.disconnectAllHard();
}
/**
* Notify the client that some error occurred
*
*/
public void errorOccurred(I2PSession session, String message, Throwable error) {
if (_log.shouldLog(Log.ERROR))
_log.error("error occurred: " + message, error);
//_manager.disconnectAllHard();
}
}

View File

@ -11,7 +11,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.util.Log;
/**
* Stream that can be given messages out of order
@ -19,6 +21,8 @@ import net.i2p.data.ByteArray;
*
*/
public class MessageInputStream extends InputStream {
private I2PAppContext _context;
private Log _log;
/**
* List of ByteArray objects of data ready to be read,
* with the first ByteArray at index 0, and the next
@ -29,7 +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 volatile long _highestBlockId;
/**
* Message ID (Long) to ByteArray for blocks received
* out of order when there are lower IDs not yet
@ -44,14 +50,22 @@ public class MessageInputStream extends InputStream {
/** if we don't want any more data, ignore the data */
private boolean _locallyClosed;
private int _readTimeout;
private IOException _streamError;
private long _readTotal;
private byte[] _oneByte = new byte[1];
private Object _dataLock;
public MessageInputStream() {
public MessageInputStream(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(MessageInputStream.class);
_readyDataBlocks = new ArrayList(4);
_readyDataBlockIndex = 0;
_highestReadyBlockId = -1;
_highestBlockId = -1;
_readTimeout = -1;
_readTotal = 0;
_notYetReadyBlocks = new HashMap(4);
_dataLock = new Object();
_closeReceived = false;
@ -60,8 +74,52 @@ public class MessageInputStream extends InputStream {
/** What is the highest block ID we've completely received through? */
public long getHighestReadyBockId() {
// not synchronized as it doesnt hurt to read a too-low value
return _highestReadyBlockId;
}
public long getHighestBlockId() {
// not synchronized as it doesnt hurt to read a too-low value
return _highestBlockId;
}
/**
* Retrieve the message IDs that are holes in our sequence - ones
* past the highest ready ID and below the highest received message
* ID. This may return null if there are no such IDs.
*
*/
public long[] getNacks() {
synchronized (_dataLock) {
return _highestReadyBlockId;
return locked_getNacks();
}
}
private long[] locked_getNacks() {
List ids = null;
for (long i = _highestReadyBlockId + 1; i < _highestBlockId; i++) {
Long l = new Long(i);
if (_notYetReadyBlocks.containsKey(l)) {
// ACK
} else {
if (ids == null)
ids = new ArrayList(4);
ids.add(l);
}
}
if (ids != null) {
long rv[] = new long[ids.size()];
for (int i = 0; i < rv.length; i++)
rv[i] = ((Long)ids.get(i)).longValue();
return rv;
} else {
return null;
}
}
public void updateAcks(PacketLocal packet) {
synchronized (_dataLock) {
packet.setAckThrough(_highestBlockId);
packet.setNacks(locked_getNacks());
}
}
@ -101,70 +159,201 @@ public class MessageInputStream extends InputStream {
public int getReadTimeout() { return _readTimeout; }
public void setReadTimeout(int timeout) { _readTimeout = timeout; }
public void closeReceived() {
synchronized (_dataLock) {
if (_log.shouldLog(Log.DEBUG)) {
StringBuffer buf = new StringBuffer(128);
buf.append("Close received, ready bytes: ");
long available = 0;
for (int i = 0; i < _readyDataBlocks.size(); i++)
available += ((ByteArray)_readyDataBlocks.get(i)).getData().length;
available -= _readyDataBlockIndex;
buf.append(available);
buf.append(" blocks: ").append(_readyDataBlocks.size());
buf.append(" not ready blocks: ");
long notAvailable = 0;
for (Iterator iter = _notYetReadyBlocks.keySet().iterator(); iter.hasNext(); ) {
Long id = (Long)iter.next();
ByteArray ba = (ByteArray)_notYetReadyBlocks.get(id);
buf.append(id).append(" ");
if (ba.getData() != null)
notAvailable += ba.getData().length;
}
buf.append("not ready bytes: ").append(notAvailable);
buf.append(" highest ready block: ").append(_highestReadyBlockId);
_log.debug(buf.toString(), new Exception("closed"));
}
_closeReceived = true;
_dataLock.notifyAll();
}
}
/**
* A new message has arrived - toss it on the appropriate queue (moving
* previously pending messages to the ready queue if it fills the gap, etc)
* previously pending messages to the ready queue if it fills the gap, etc).
*
* @return true if this is a new packet, false if it is a dup
*/
public void messageReceived(long messageId, byte payload[]) {
public boolean messageReceived(long messageId, byte payload[]) {
synchronized (_dataLock) {
if (messageId <= _highestReadyBlockId) return; // already received
if (_log.shouldLog(Log.DEBUG))
_log.debug("received " + messageId + " with " + payload.length);
if (messageId <= _highestReadyBlockId) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("ignoring dup message " + messageId);
_dataLock.notifyAll();
return false; // already received
}
if (messageId > _highestBlockId)
_highestBlockId = messageId;
if (_highestReadyBlockId + 1 == messageId) {
if (!_locallyClosed)
if (!_locallyClosed && payload.length > 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("accepting bytes as ready: " + payload.length);
_readyDataBlocks.add(new ByteArray(payload));
}
_highestReadyBlockId = messageId;
long cur = _highestReadyBlockId + 1;
// now pull in any previously pending blocks
while (_notYetReadyBlocks.containsKey(new Long(_highestReadyBlockId + 1))) {
_readyDataBlocks.add(_notYetReadyBlocks.get(new Long(_highestReadyBlockId + 1)));
while (_notYetReadyBlocks.containsKey(new Long(cur))) {
ByteArray ba = (ByteArray)_notYetReadyBlocks.remove(new Long(cur));
if ( (ba != null) && (ba.getData() != null) && (ba.getData().length > 0) ) {
_readyDataBlocks.add(ba);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("making ready the block " + cur);
cur++;
_highestReadyBlockId++;
}
_dataLock.notifyAll();
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("message is out of order: " + messageId);
if (_locallyClosed) // dont need the payload, just the msgId in order
_notYetReadyBlocks.put(new Long(messageId), new ByteArray(null));
else
_notYetReadyBlocks.put(new Long(messageId), new ByteArray(payload));
_dataLock.notifyAll();
}
}
return true;
}
public int read() throws IOException {
int read = read(_oneByte, 0, 1);
if (read < 0)
return -1;
else
return _oneByte[0];
}
public int read(byte target[]) throws IOException {
return read(target, 0, target.length);
}
public int read(byte target[], int offset, int length) throws IOException {
if (_locallyClosed) throw new IOException("Already locally closed");
throwAnyError();
long expiration = -1;
if (_readTimeout > 0)
expiration = _readTimeout + System.currentTimeMillis();
synchronized (_dataLock) {
if (_readyDataBlocks.size() <= 0) {
if ( (_notYetReadyBlocks.size() <= 0) && (_closeReceived) ) {
return -1;
} else {
if (_readTimeout < 0) {
try { _dataLock.wait(); } catch (InterruptedException ie) { }
} else if (_readTimeout > 0) {
try { _dataLock.wait(_readTimeout); } catch (InterruptedException ie) { }
} else { // readTimeout == 0
// noop, don't block
for (int i = 0; i < length; i++) {
if ( (_readyDataBlocks.size() <= 0) && (i == 0) ) {
// ok, we havent found anything, so lets block until we get
// at least one byte
while (_readyDataBlocks.size() <= 0) {
if (_locallyClosed)
throw new IOException("Already closed, you wanker");
if ( (_notYetReadyBlocks.size() <= 0) && (_closeReceived) ) {
if (_log.shouldLog(Log.INFO))
_log.info("read(...," + offset + ", " + length + ")[" + i
+ "] got EOF after " + _readTotal + " " + toString());
return -1;
} else {
if (_readTimeout < 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("read(...," + offset+", " + length+ ")[" + i
+ ") with no timeout: " + toString());
try { _dataLock.wait(); } catch (InterruptedException ie) { }
if (_log.shouldLog(Log.DEBUG))
_log.debug("read(...," + offset+", " + length+ ")[" + i
+ ") with no timeout complete: " + toString());
throwAnyError();
} else if (_readTimeout > 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("read(...," + offset+", " + length+ ")[" + i
+ ") with timeout: " + _readTimeout + ": " + toString());
try { _dataLock.wait(_readTimeout); } catch (InterruptedException ie) { }
if (_log.shouldLog(Log.DEBUG))
_log.debug("read(...," + offset+", " + length+ ")[" + i
+ ") with timeout complete: " + _readTimeout + ": " + toString());
throwAnyError();
} else { // readTimeout == 0
// noop, don't block
if (_log.shouldLog(Log.INFO))
_log.info("read(...," + offset+", " + length+ ")[" + i
+ ") with nonblocking setup: " + toString());
return i;
}
if (_readyDataBlocks.size() <= 0) {
if ( (_readTimeout > 0) && (expiration < System.currentTimeMillis()) ) {
if (_log.shouldLog(Log.INFO))
_log.info("read(...," + offset+", " + length+ ")[" + i
+ ") expired: " + toString());
return i;
}
}
}
}
if (_readyDataBlocks.size() <= 0) {
throw new InterruptedIOException("Timeout reading");
// we looped a few times then got data, so this pass doesnt count
i--;
} else if (_readyDataBlocks.size() <= 0) {
if (_log.shouldLog(Log.INFO))
_log.info("read(...," + offset+", " + length+ ")[" + i
+ "] no more ready blocks, returning");
return i;
} else {
// either was already ready, or we wait()ed and it arrived
ByteArray cur = (ByteArray)_readyDataBlocks.get(0);
byte rv = cur.getData()[_readyDataBlockIndex];
_readyDataBlockIndex++;
if (cur.getData().length <= _readyDataBlockIndex) {
_readyDataBlockIndex = 0;
_readyDataBlocks.remove(0);
}
_readTotal++;
target[offset + i] = rv; // rv < 0 ? rv + 256 : rv
if ( (_readyDataBlockIndex <= 3) || (_readyDataBlockIndex >= cur.getData().length - 5) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("read(...," + offset+", " + length+ ")[" + i
+ "] after ready data: readyDataBlockIndex=" + _readyDataBlockIndex
+ " readyBlocks=" + _readyDataBlocks.size()
+ " readTotal=" + _readTotal);
}
}
}
// either was already ready, or we wait()ed and it arrived
ByteArray cur = (ByteArray)_readyDataBlocks.get(0);
byte rv = cur.getData()[_readyDataBlockIndex++];
if (cur.getData().length <= _readyDataBlockIndex) {
_readyDataBlockIndex = 0;
_readyDataBlocks.remove(0);
}
return (rv < 0 ? rv + 256 : rv);
}
} // for (int i = 0; i < length; i++) {
} // synchronized (_dataLock)
if (_log.shouldLog(Log.DEBUG))
_log.info("read(...," + offset+", " + length+ ") read fully total read: " +_readTotal);
return length;
}
public int available() throws IOException {
if (_locallyClosed) throw new IOException("Already closed, you wanker");
throwAnyError();
int numBytes = 0;
synchronized (_dataLock) {
if (_readyDataBlocks.size() <= 0)
return 0;
int numBytes = 0;
for (int i = 0; i < _readyDataBlocks.size(); i++) {
ByteArray cur = (ByteArray)_readyDataBlocks.get(i);
if (i == 0)
@ -172,8 +361,11 @@ public class MessageInputStream extends InputStream {
else
numBytes += cur.getData().length;
}
return numBytes;
}
if (_log.shouldLog(Log.DEBUG))
_log.info("available(): " + numBytes + " " + toString());
return numBytes;
}
/**
@ -200,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();
@ -211,6 +418,26 @@ public class MessageInputStream extends InputStream {
ba.setData(null);
}
_locallyClosed = true;
_dataLock.notifyAll();
}
}
/**
* Stream b0rked, die with the given error
*
*/
void streamErrorOccurred(IOException ioe) {
_streamError = ioe;
synchronized (_dataLock) {
_dataLock.notifyAll();
}
}
private void throwAnyError() throws IOException {
if (_streamError != null) {
IOException ioe = _streamError;
_streamError = null;
throw ioe;
}
}
}

View File

@ -1,54 +1,131 @@
package net.i2p.client.streaming;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
*
* A stream that we can shove data into that fires off those bytes
* on flush or when the buffer is full. It also blocks according
* to the data receiver's needs.
*/
public class MessageOutputStream extends OutputStream {
private I2PAppContext _context;
private Log _log;
private byte _buf[];
private int _valid;
private Object _dataLock;
private DataReceiver _dataReceiver;
private IOException _streamError;
private boolean _closed;
private long _written;
private int _writeTimeout;
private ByteCache _dataCache;
private Flusher _flusher;
private long _lastFlushed;
private long _lastBuffered;
/** if we enqueue data but don't flush it in this period, flush it passively */
private int _passiveFlushDelay;
public MessageOutputStream(DataReceiver receiver) {
this(receiver, 64*1024);
public MessageOutputStream(I2PAppContext ctx, DataReceiver receiver) {
this(ctx, receiver, Packet.MAX_PAYLOAD_SIZE);
}
public MessageOutputStream(DataReceiver receiver, int bufSize) {
public MessageOutputStream(I2PAppContext ctx, DataReceiver receiver, int bufSize) {
super();
_buf = new byte[bufSize];
_dataCache = ByteCache.getInstance(128, bufSize);
_context = ctx;
_log = ctx.logManager().getLog(MessageOutputStream.class);
_buf = _dataCache.acquire().getData(); // new byte[bufSize];
_dataReceiver = receiver;
_dataLock = new Object();
_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; }
public int getWriteTimeout() { return _writeTimeout; }
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException {
synchronized (_dataLock) {
int remaining = len;
while (remaining > 0) {
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
// want to allow other threads to flushAvailable() whenever they want.
// this is the only method that *adds* to the _buf, and all
// code that reads from it is synchronized
synchronized (_dataLock) {
if (_buf == null) throw new IOException("closed (buffer went away)");
if (_valid + remaining < _buf.length) {
// simply buffer the data, no flush
System.arraycopy(b, off, _buf, _valid, remaining);
System.arraycopy(b, cur, _buf, _valid, remaining);
_valid += remaining;
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
// data through
int toWrite = _buf.length - _valid;
System.arraycopy(b, off, _buf, _valid, toWrite);
System.arraycopy(b, cur, _buf, _valid, toWrite);
remaining -= toWrite;
cur += toWrite;
_valid = _buf.length;
_dataReceiver.writeData(_buf, 0, _valid);
_valid = 0;
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: " + ws);
else
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();
}
@ -57,14 +134,156 @@ public class MessageOutputStream extends OutputStream {
throwAnyError();
}
public void flush() throws IOException {
synchronized (_dataLock) {
_dataReceiver.writeData(_buf, 0, _valid);
_valid = 0;
/**
* 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.
*
* @throws IOException if the write fails
* @throws InterruptedIOException if the write times out
*/
public void flush() throws IOException {
long begin = _context.clock().now();
WriteStatus ws = null;
synchronized (_dataLock) {
if (_buf == null) throw new IOException("closed (buffer went away)");
if (_dataReceiver == null) {
throwAnyError();
return;
}
ws = _dataReceiver.writeData(_buf, 0, _valid);
_written += _valid;
_valid = 0;
_lastFlushed = _context.clock().now();
_dataLock.notifyAll();
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("before waiting " + _writeTimeout + "ms for completion of " + ws);
if (_closed &&
( (_writeTimeout > Connection.DISCONNECT_TIMEOUT) ||
(_writeTimeout <= 0) ) )
ws.waitForCompletion(Connection.DISCONNECT_TIMEOUT);
else if ( (_writeTimeout <= 0) || (_writeTimeout > Connection.DISCONNECT_TIMEOUT) )
ws.waitForCompletion(Connection.DISCONNECT_TIMEOUT);
else
ws.waitForCompletion(_writeTimeout);
if (_log.shouldLog(Log.DEBUG))
_log.debug("after waiting " + _writeTimeout + "ms for completion of " + ws);
if (ws.writeFailed() && (_writeTimeout > 0) )
throw new InterruptedIOException("Timed out during write");
else if (ws.writeFailed())
throw new IOException("Write failed");
long elapsed = _context.clock().now() - begin;
if ( (elapsed > 10*1000) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("wtf, took " + elapsed + "ms to flush the stream?\n" + ws, new Exception("bar"));
throwAnyError();
}
public void close() throws IOException {
if (_closed) return;
_closed = true;
flush();
_log.debug("Output stream closed after writing " + _written);
ByteArray ba = null;
synchronized (_dataLock) {
if (_buf != null) {
ba = new ByteArray(_buf);
_buf = null;
_valid = 0;
}
}
if (ba != null) {
_dataCache.release(ba);
}
}
/** nonblocking close */
public void closeInternal() {
_closed = true;
_streamError = new IOException("Closed internally");
ByteArray ba = null;
synchronized (_dataLock) {
// flush any data, but don't wait for it
if (_dataReceiver != null)
_dataReceiver.writeData(_buf, 0, _valid);
_written += _valid;
_valid = 0;
if (_buf != null) {
ba = new ByteArray(_buf);
_buf = null;
_valid = 0;
}
_lastFlushed = _context.clock().now();
_dataLock.notifyAll();
}
if (ba != null) {
_dataCache.release(ba);
}
}
public boolean getClosed() { return _closed; }
private void throwAnyError() throws IOException {
if (_streamError != null) {
IOException ioe = _streamError;
@ -81,15 +300,67 @@ public class MessageOutputStream extends OutputStream {
* called whenever the engine wants to push more data to the
* peer
*
* @return true if the data was flushed
*/
void flushAvailable(DataReceiver target) {
void flushAvailable(DataReceiver target) throws IOException {
flushAvailable(target, true);
}
void flushAvailable(DataReceiver target, boolean blocking) throws IOException {
WriteStatus ws = null;
long before = System.currentTimeMillis();
synchronized (_dataLock) {
target.writeData(_buf, 0, _valid);
// _buf may be null, but the data receiver can handle that just fine,
// deciding whether or not to send a packet
ws = target.writeData(_buf, 0, _valid);
_written += _valid;
_valid = 0;
_dataLock.notifyAll();
_lastFlushed = _context.clock().now();
}
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())
throw new IOException("Flush available failed");
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;
}
void destroy() {
_dataReceiver = null;
}
/** Define a component to receive data flushed from this stream */
public interface DataReceiver {
public void writeData(byte buf[], int off, int size);
/**
* Nonblocking write
*/
public WriteStatus writeData(byte buf[], int off, int size);
public boolean writeInProcess();
}
/** Define a way to detect the status of a write */
public interface WriteStatus {
/** wait until the data written either fails or succeeds */
public void waitForCompletion(int maxWaitMs);
/**
* wait until the data written is accepted into the outbound pool,
* which we throttle rather than accept arbitrary data and queue
*/
public void waitForAccept(int maxWaitMs);
/** the write was accepted. aka did the socket not close? */
public boolean writeAccepted();
/** did the write fail? */
public boolean writeFailed();
/** did the write succeed? */
public boolean writeSuccessful();
}
}

View File

@ -2,6 +2,8 @@ package net.i2p.client.streaming;
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Signature;
@ -10,13 +12,13 @@ import net.i2p.data.SigningPrivateKey;
/**
* Contain a single packet transferred as part of a streaming connection.
* The data format is as follows:<ul>
* <li>{@see #getSendStreamId sendStreamId} [4 byte value]</li>
* <li>{@see #getReceiveStreamId receiveStreamId} [4 byte value]</li>
* <li>{@see #getSequenceNum sequenceNum} [4 byte unsigned integer]</li>
* <li>{@see #getAckThrough ackThrough} [4 byte unsigned integer]</li>
* <li>{@link #getSendStreamId sendStreamId} [4 byte value]</li>
* <li>{@link #getReceiveStreamId receiveStreamId} [4 byte value]</li>
* <li>{@link #getSequenceNum sequenceNum} [4 byte unsigned integer]</li>
* <li>{@link #getAckThrough ackThrough} [4 byte unsigned integer]</li>
* <li>number of NACKs [1 byte unsigned integer]</li>
* <li>that many {@see #getNacks NACKs}</li>
* <li>{@see #getResendDelay resendDelay} [1 byte integer]</li>
* <li>that many {@link #getNacks NACKs}</li>
* <li>{@link #getResendDelay resendDelay} [1 byte integer]</li>
* <li>flags [2 byte value]</li>
* <li>option data size [2 byte integer]</li>
* <li>option data specified by those flags [0 or more bytes]</li>
@ -27,21 +29,24 @@ import net.i2p.data.SigningPrivateKey;
* turn may require certain additional data to be included. The flags are
* as follows (with any data structures specified added to the options area
* in the given order):</p><ol>
* <li>{@see #FLAG_SYNCHRONIZE}: no option data</li>
* <li>{@see #FLAG_CLOSE}: no option data</li>
* <li>{@see #FLAG_RESET}: no option data</li>
* <li>{@see #FLAG_SIGNATURE_INCLUDED}: {@see net.i2p.data.Signature}</li>
* <li>{@see #FLAG_SIGNATURE_REQUESTED}: no option data</li>
* <li>{@see #FLAG_FROM_INCLUDED}: {@see net.i2p.data.Destination}</li>
* <li>{@see #FLAG_DELAY_REQUESTED}: 1 byte integer</li>
* <li>{@see #FLAG_MAX_PACKET_SIZE_INCLUDED}: 2 byte integer</li>
* <li>{@see #FLAG_PROFILE_INTERACTIVE}: no option data</li>
* <li>{@link #FLAG_SYNCHRONIZE}: no option data</li>
* <li>{@link #FLAG_CLOSE}: no option data</li>
* <li>{@link #FLAG_RESET}: no option data</li>
* <li>{@link #FLAG_SIGNATURE_INCLUDED}: {@link net.i2p.data.Signature}</li>
* <li>{@link #FLAG_SIGNATURE_REQUESTED}: no option data</li>
* <li>{@link #FLAG_FROM_INCLUDED}: {@link net.i2p.data.Destination}</li>
* <li>{@link #FLAG_DELAY_REQUESTED}: 1 byte integer</li>
* <li>{@link #FLAG_MAX_PACKET_SIZE_INCLUDED}: 2 byte integer</li>
* <li>{@link #FLAG_PROFILE_INTERACTIVE}: no option data</li>
* </ol>
*
* <p>If the signature is included, it uses the Destination's DSA key
* to sign the entire header and payload with the space in the options
* for the signature being set to all zeroes.</p>
*
* <p>If the sequenceNum is 0 and the SYN is not set, this is a plain ACK
* packet that should not be ACKed</p>
*
*/
public class Packet {
private byte _sendStreamId[];
@ -64,11 +69,11 @@ public class Packet {
* synchronize packet)
*
*/
public static final byte RECEIVE_STREAM_ID_UNKNOWN[] = new byte[] { 0x00, 0x00, 0x00, 0x00 };
public static final byte STREAM_ID_UNKNOWN[] = new byte[] { 0x00, 0x00, 0x00, 0x00 };
/**
* This packet is creating a new socket connection (if the receiveStreamId
* is RECEIVE_STREAM_ID_UNKNOWN) or it is acknowledging a request to
* is STREAM_ID_UNKNOWN) or it is acknowledging a request to
* create a connection and in turn is accepting the socket.
*
*/
@ -122,27 +127,53 @@ public class Packet {
*
*/
public static final int FLAG_PROFILE_INTERACTIVE = (1 << 8);
/**
* If set, this packet is a ping (if sendStreamId is set) or a
* ping reply (if receiveStreamId is set).
*/
public static final int FLAG_ECHO = (1 << 9);
public static final int DEFAULT_MAX_SIZE = 32*1024;
private static final int MAX_DELAY_REQUEST = 65535;
/** what stream is this packet a part of? */
public byte[] getSendStreamId() { return _sendStreamId; }
public void setSendStreamId(byte[] id) { _sendStreamId = id; }
public byte[] getSendStreamId() {
if ( (_sendStreamId == null) || (DataHelper.eq(_sendStreamId, STREAM_ID_UNKNOWN)) )
return null;
else
return _sendStreamId;
}
public void setSendStreamId(byte[] id) {
_sendStreamId = id;
if ( (id != null) && (DataHelper.eq(id, STREAM_ID_UNKNOWN)) )
_sendStreamId = null;
}
/**
* what is the stream replies should be sent on? if the
* Stream that replies should be sent on. if the
* connection is still being built, this should be
* {@see #RECEIVE_STREAM_ID_UNKNOWN}.
* null.
*
*/
public byte[] getReceiveStreamId() { return _receiveStreamId; }
public void setReceiveStreamId(byte[] id) { _receiveStreamId = id; }
public byte[] getReceiveStreamId() {
if ( (_receiveStreamId == null) || (DataHelper.eq(_receiveStreamId, STREAM_ID_UNKNOWN)) )
return null;
else
return _receiveStreamId;
}
public void setReceiveStreamId(byte[] id) {
_receiveStreamId = id;
if ( (id != null) && (DataHelper.eq(id, STREAM_ID_UNKNOWN)) )
_receiveStreamId = null;
}
/** 0-indexed sequence number for this Packet in the sendStream */
public long getSequenceNum() { return _sequenceNum; }
public void setSequenceNum(long num) { _sequenceNum = num; }
/**
* what is the highest packet sequence number that received
* on the receiveStreamId? This field is ignored on the initial
* The highest packet sequence number that received
* on the receiveStreamId. This field is ignored on the initial
* connection packet (where receiveStreamId is the unknown id).
*
*/
@ -150,8 +181,8 @@ public class Packet {
public void setAckThrough(long id) { _ackThrough = id; }
/**
* What packet sequence numbers below the getAckThrough() value
* have not been received? this may be null.
* List of packet sequence numbers below the getAckThrough() value
* have not been received. this may be null.
*
*/
public long[] getNacks() { return _nacks; }
@ -166,21 +197,43 @@ public class Packet {
public int getResendDelay() { return _resendDelay; }
public void setResendDelay(int numSeconds) { _resendDelay = numSeconds; }
public static final int MAX_PAYLOAD_SIZE = 32*1024;
/** get the actual payload of the message. may be null */
public byte[] getPayload() { return _payload; }
public void setPayload(byte payload[]) { _payload = payload; }
public void setPayload(byte payload[]) {
_payload = payload;
if ( (payload != null) && (payload.length > MAX_PAYLOAD_SIZE) )
throw new IllegalArgumentException("Too large payload: " + payload.length);
}
public int getPayloadSize() {
return (_payload == null ? 0 : _payload.length);
}
/** is a particular flag set on this packet? */
public boolean isFlagSet(int flag) { return 0 != (_flags & flag); }
public void setFlag(int flag) { _flags |= flag; }
public void setFlag(int flag, boolean set) {
if (set)
_flags |= flag;
else
_flags &= ~flag;
}
/** the signature on the packet (only included if the flag for it is set) */
public Signature getOptionalSignature() { return _optionSignature; }
public void setOptionalSignature(Signature sig) { _optionSignature = sig; }
public void setOptionalSignature(Signature sig) {
setFlag(FLAG_SIGNATURE_INCLUDED, sig != null);
_optionSignature = sig;
}
/** the sender of the packet (only included if the flag for it is set) */
public Destination getOptionalFrom() { return _optionFrom; }
public void setOptionalFrom(Destination from) { _optionFrom = from; }
public void setOptionalFrom(Destination from) {
setFlag(FLAG_FROM_INCLUDED, from != null);
if (from == null) throw new RuntimeException("from is null!?");
_optionFrom = from;
}
/**
* How many milliseconds the sender of this packet wants the recipient
@ -188,14 +241,25 @@ public class Packet {
* set)
*/
public int getOptionalDelay() { return _optionDelay; }
public void setOptionalDelay(int delayMs) { _optionDelay = delayMs; }
public void setOptionalDelay(int delayMs) {
setFlag(FLAG_DELAY_REQUESTED, delayMs > 0);
if (delayMs > MAX_DELAY_REQUEST)
_optionDelay = MAX_DELAY_REQUEST;
else if (delayMs < 0)
_optionDelay = 0;
else
_optionDelay = delayMs;
}
/**
/**
* What is the largest payload the sender of this packet wants to receive?
*
*/
public int getOptionalMaxSize() { return _optionMaxSize; }
public void setOptionalMaxSize(int numBytes) { _optionMaxSize = numBytes; }
public void setOptionalMaxSize(int numBytes) {
setFlag(FLAG_MAX_PACKET_SIZE_INCLUDED, numBytes > 0);
_optionMaxSize = numBytes;
}
/**
* Write the packet to the buffer (starting at the offset) and return
@ -212,13 +276,19 @@ public class Packet {
*/
private int writePacket(byte buffer[], int offset, boolean includeSig) throws IllegalStateException {
int cur = offset;
System.arraycopy(_sendStreamId, 0, buffer, cur, _sendStreamId.length);
cur += _sendStreamId.length;
System.arraycopy(_receiveStreamId, 0, buffer, cur, _receiveStreamId.length);
cur += _receiveStreamId.length;
DataHelper.toLong(buffer, cur, 4, _sequenceNum);
if ( (_sendStreamId != null) && (_sendStreamId.length == 4) )
System.arraycopy(_sendStreamId, 0, buffer, cur, _sendStreamId.length);
else
System.arraycopy(STREAM_ID_UNKNOWN, 0, buffer, cur, STREAM_ID_UNKNOWN.length);
cur += 4;
DataHelper.toLong(buffer, cur, 4, _ackThrough);
if ( (_receiveStreamId != null) && (_receiveStreamId.length == 4) )
System.arraycopy(_receiveStreamId, 0, buffer, cur, _receiveStreamId.length);
else
System.arraycopy(STREAM_ID_UNKNOWN, 0, buffer, cur, STREAM_ID_UNKNOWN.length);
cur += 4;
DataHelper.toLong(buffer, cur, 4, _sequenceNum > 0 ? _sequenceNum : 0);
cur += 4;
DataHelper.toLong(buffer, cur, 4, _ackThrough > 0 ? _ackThrough : 0);
cur += 4;
if (_nacks != null) {
DataHelper.toLong(buffer, cur, 1, _nacks.length);
@ -231,14 +301,14 @@ public class Packet {
DataHelper.toLong(buffer, cur, 1, 0);
cur++;
}
DataHelper.toLong(buffer, cur, 1, _resendDelay);
DataHelper.toLong(buffer, cur, 1, _resendDelay > 0 ? _resendDelay : 0);
cur++;
DataHelper.toLong(buffer, cur, 2, _flags);
cur += 2;
int optionSize = 0;
if (isFlagSet(FLAG_DELAY_REQUESTED))
optionSize += 1;
optionSize += 2;
if (isFlagSet(FLAG_FROM_INCLUDED))
optionSize += _optionFrom.size();
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED))
@ -250,32 +320,73 @@ public class Packet {
cur += 2;
if (isFlagSet(FLAG_DELAY_REQUESTED)) {
DataHelper.toLong(buffer, cur, 1, _optionDelay);
cur++;
DataHelper.toLong(buffer, cur, 2, _optionDelay > 0 ? _optionDelay : 0);
cur += 2;
}
if (isFlagSet(FLAG_FROM_INCLUDED)) {
cur += _optionFrom.writeBytes(buffer, cur);
}
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) {
DataHelper.toLong(buffer, cur, 2, _optionMaxSize);
DataHelper.toLong(buffer, cur, 2, _optionMaxSize > 0 ? _optionMaxSize : DEFAULT_MAX_SIZE);
cur += 2;
}
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
if (includeSig)
System.arraycopy(_optionSignature.getData(), 0, buffer, cur, Signature.SIGNATURE_BYTES);
else // we're signing (or validating)
Arrays.fill(buffer, cur, Signature.SIGNATURE_BYTES, (byte)0x0);
Arrays.fill(buffer, cur, cur+Signature.SIGNATURE_BYTES, (byte)0x0);
cur += Signature.SIGNATURE_BYTES;
}
if (_payload != null) {
System.arraycopy(_payload, 0, buffer, cur, _payload.length);
try {
System.arraycopy(_payload, 0, buffer, cur, _payload.length);
} catch (ArrayIndexOutOfBoundsException aioobe) {
System.err.println("payload.length: " + _payload.length + " buffer.length: " + buffer.length + " cur: " + cur);
throw aioobe;
}
cur += _payload.length;
}
return cur - offset;
}
/**
* how large would this packet be if we wrote it
*/
public int writtenSize() throws IllegalStateException {
int size = 0;
size += 4; // _sendStreamId.length;
size += 4; // _receiveStreamId.length;
size += 4; // sequenceNum
size += 4; // ackThrough
if (_nacks != null) {
size++; // nacks length
size += 4 * _nacks.length;
} else {
size++; // nacks length
}
size++; // resendDelay
size += 2; // flags
if (isFlagSet(FLAG_DELAY_REQUESTED))
size += 2;
if (isFlagSet(FLAG_FROM_INCLUDED))
size += _optionFrom.size();
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED))
size += 2;
if (isFlagSet(FLAG_SIGNATURE_INCLUDED))
size += Signature.SIGNATURE_BYTES;
size += 2; // option size
if (_payload != null) {
size += _payload.length;
}
return size;
}
/**
* Read the packet from the buffer (starting at the offset) and return
* the number of bytes read.
@ -288,6 +399,10 @@ public class Packet {
* @throws IllegalArgumentException if the data is b0rked
*/
public void readPacket(byte buffer[], int offset, int length) throws IllegalArgumentException {
if (buffer.length - offset < length)
throw new IllegalArgumentException("len=" + buffer.length + " off=" + offset + " length=" + length);
if (length < 22) // min header size
throw new IllegalArgumentException("Too small: len=" + buffer.length);
int cur = offset;
_sendStreamId = new byte[4];
System.arraycopy(buffer, cur, _sendStreamId, 0, 4);
@ -301,6 +416,8 @@ public class Packet {
cur += 4;
int numNacks = (int)DataHelper.fromLong(buffer, cur, 1);
cur++;
if (length < 22 + numNacks*4)
throw new IllegalArgumentException("Too small with " + numNacks + " nacks: " + length);
if (numNacks > 0) {
_nacks = new long[numNacks];
for (int i = 0; i < numNacks; i++) {
@ -317,30 +434,42 @@ public class Packet {
int optionSize = (int)DataHelper.fromLong(buffer, cur, 2);
cur += 2;
if (length < 22 + numNacks*4 + optionSize)
throw new IllegalArgumentException("Too small with " + numNacks + " nacks and "
+ optionSize + " options: " + length);
int payloadBegin = cur + optionSize;
int payloadSize = length - payloadBegin;
if ( (payloadSize < 0) || (payloadSize > MAX_PAYLOAD_SIZE) )
throw new IllegalArgumentException("length: " + length + " offset: " + offset + " begin: " + payloadBegin);
// skip ahead to the payload
_payload = new byte[offset + length - payloadBegin];
System.arraycopy(buffer, payloadBegin, _payload, 0, _payload.length);
_payload = new byte[payloadSize];
System.arraycopy(buffer, payloadBegin, _payload, 0, payloadSize);
// ok now lets go back and deal with the options
if (isFlagSet(FLAG_DELAY_REQUESTED)) {
_optionDelay = (int)DataHelper.fromLong(buffer, cur, 1);
cur++;
_optionDelay = (int)DataHelper.fromLong(buffer, cur, 2);
cur += 2;
}
if (isFlagSet(FLAG_FROM_INCLUDED)) {
_optionFrom = new Destination();
cur += _optionFrom.readBytes(buffer, cur);
try {
cur += _optionFrom.readBytes(buffer, cur);
} catch (DataFormatException dfe) {
throw new IllegalArgumentException("Bad from field: " + dfe.getMessage());
}
}
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) {
_optionMaxSize = (int)DataHelper.fromLong(buffer, cur, 2);
cur += 2;
}
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
Signature sig = new Signature();
_optionSignature = new Signature();
byte buf[] = new byte[Signature.SIGNATURE_BYTES];
System.arraycopy(buffer, cur, buf, 0, Signature.SIGNATURE_BYTES);
sig.setData(buf);
_optionSignature.setData(buf);
cur += Signature.SIGNATURE_BYTES;
}
}
@ -355,8 +484,20 @@ public class Packet {
if (!isFlagSet(FLAG_SIGNATURE_INCLUDED)) return false;
if (_optionSignature == null) return false;
int size = writePacket(buffer, 0, false);
return ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, from.getSigningPublicKey());
int size = writtenSize();
if (buffer == null)
buffer = new byte[size];
int written = writePacket(buffer, 0, false);
if (written != size) {
ctx.logManager().getLog(Packet.class).error("Written " + written + " size " + size + " for " + toString(), new Exception("moo"));
return false;
}
boolean ok = ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, from.getSigningPublicKey());
if (!ok) {
ctx.logManager().getLog(Packet.class).error("Signature failed on " + toString(), new Exception("moo"));
}
return ok;
}
/**
@ -380,10 +521,54 @@ public class Packet {
+ 1 // resendDelay
+ 2 // flags
+ 2 // optionSize
+ (isFlagSet(FLAG_DELAY_REQUESTED) ? 1 : 0)
+ (isFlagSet(FLAG_DELAY_REQUESTED) ? 2 : 0)
+ (isFlagSet(FLAG_FROM_INCLUDED) ? _optionFrom.size() : 0)
+ (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED) ? 2 : 0);
System.arraycopy(_optionSignature.getData(), 0, buffer, signatureOffset, Signature.SIGNATURE_BYTES);
return size;
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append(toId(_sendStreamId));
//buf.append("<-->");
buf.append(toId(_receiveStreamId)).append(": #").append(_sequenceNum);
if (_sequenceNum < 10)
buf.append(" \t"); // so the tab lines up right
else
buf.append('\t');
buf.append(toFlagString());
buf.append(" ACK ").append(_ackThrough);
if (_nacks != null) {
buf.append(" NACK");
for (int i = 0; i < _nacks.length; i++) {
buf.append(" ").append(_nacks[i]);
}
}
if ( (_payload != null) && (_payload.length > 0) )
buf.append(" data: ").append(_payload.length);
return buf.toString();
}
private static final String toId(byte id[]) {
if (id == null)
return Base64.encode(STREAM_ID_UNKNOWN);
else
return Base64.encode(id);
}
private final String toFlagString() {
StringBuffer buf = new StringBuffer(32);
if (isFlagSet(FLAG_CLOSE)) buf.append(" CLOSE");
if (isFlagSet(FLAG_DELAY_REQUESTED)) buf.append(" DELAY ").append(_optionDelay);
if (isFlagSet(FLAG_ECHO)) buf.append(" ECHO");
if (isFlagSet(FLAG_FROM_INCLUDED)) buf.append(" FROM");
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) buf.append(" MS");
if (isFlagSet(FLAG_PROFILE_INTERACTIVE)) buf.append(" INTERACTIVE");
if (isFlagSet(FLAG_RESET)) buf.append(" RESET");
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) buf.append(" SIG");
if (isFlagSet(FLAG_SIGNATURE_REQUESTED)) buf.append(" SIGREQ");
if (isFlagSet(FLAG_SYNCHRONIZE)) buf.append(" SYN");
return buf.toString();
}
}

View File

@ -0,0 +1,259 @@
package net.i2p.client.streaming;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import java.text.SimpleDateFormat;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
* receive a packet and dispatch it correctly to the connection specified,
* the server socket, or queue a reply RST packet.
*
*/
public class PacketHandler {
private ConnectionManager _manager;
private I2PAppContext _context;
private Log _log;
private int _lastDelay;
public PacketHandler(I2PAppContext ctx, ConnectionManager mgr) {
_manager = mgr;
_context = ctx;
_log = ctx.logManager().getLog(PacketHandler.class);
_lastDelay = _context.random().nextInt(30*1000);
}
private boolean choke(Packet packet) {
if (false) {
// artificial choke: 2% random drop and a 0-30s
// random tiered delay from 0-30s
if (_context.random().nextInt(100) >= 95) {
displayPacket(packet, "DROP");
return false;
} else {
// if (true) return true; // no lag, just drop
/*
int delay = _context.random().nextInt(5*1000);
*/
int delay = _context.random().nextInt(6*1000);
int delayFactor = _context.random().nextInt(100);
if (delayFactor > 80) {
if (delayFactor > 98)
delay *= 5;
else if (delayFactor > 95)
delay *= 4;
else if (delayFactor > 90)
delay *= 3;
else
delay *= 2;
}
if (_context.random().nextInt(100) >= 20)
delay = _lastDelay;
_lastDelay = delay;
SimpleTimer.getInstance().addEvent(new Reinject(packet, delay), delay);
return false;
}
} else {
return true;
}
}
private class Reinject implements SimpleTimer.TimedEvent {
private Packet _packet;
private int _delay;
public Reinject(Packet packet, int delay) {
_packet = packet;
_delay = delay;
}
public void timeReached() {
_log.debug("Reinjecting after " + _delay + ": " + _packet);
receivePacketDirect(_packet);
}
}
void receivePacket(Packet packet) {
boolean ok = choke(packet);
if (ok)
receivePacketDirect(packet);
}
private void receivePacketDirect(Packet packet) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("packet received: " + packet);
byte sendId[] = packet.getSendStreamId();
if (!isNonZero(sendId))
sendId = null;
Connection con = (sendId != null ? _manager.getConnectionByInboundId(sendId) : null);
if (con != null) {
receiveKnownCon(con, packet);
displayPacket(packet, "RECV");
} else {
receiveUnknownCon(packet, sendId);
displayPacket(packet, "UNKN");
}
}
private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss.SSS");
void displayPacket(Packet packet, String prefix) {
String msg = null;
synchronized (_fmt) {
msg = _fmt.format(new Date()) + ": " + prefix + " " + packet.toString();
}
if (_log.shouldLog(Log.DEBUG))
System.out.println(msg);
}
private void receiveKnownCon(Connection con, Packet packet) {
// the packet is pointed at a stream ID we're receiving on
if (isValidMatch(con.getSendStreamId(), packet.getReceiveStreamId())) {
// the packet's receive stream ID also matches what we expect
if (_log.shouldLog(Log.DEBUG))
_log.debug("receive valid: " + packet);
try {
con.getPacketHandler().receivePacket(packet, con);
} catch (I2PException ie) {
if (_log.shouldLog(Log.WARN))
_log.warn("Received forged packet for " + con, ie);
}
} else {
if (packet.isFlagSet(Packet.FLAG_RESET)) {
// refused
if (_log.shouldLog(Log.DEBUG))
_log.debug("receive reset: " + packet);
try {
con.getPacketHandler().receivePacket(packet, con);
} catch (I2PException ie) {
if (_log.shouldLog(Log.WARN))
_log.warn("Received forged reset for " + con, ie);
}
} else if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
if ( (con.getSendStreamId() == null) ||
(DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) ) {
byte oldId[] =con.getSendStreamId();
// con fully established, w00t
con.setSendStreamId(packet.getReceiveStreamId());
try {
con.getPacketHandler().receivePacket(packet, con);
} catch (I2PException ie) {
if (_log.shouldLog(Log.WARN))
_log.warn("Received forged syn for " + con, ie);
con.setSendStreamId(oldId);
}
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Receive a syn packet with the wrong IDs: " + 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);
}
}
}
private void receiveUnknownCon(Packet packet, byte sendId[]) {
if (packet.isFlagSet(Packet.FLAG_ECHO)) {
if (packet.getSendStreamId() != null) {
receivePing(packet);
} else if (packet.getReceiveStreamId() != null) {
receivePong(packet);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Echo packet received with no stream IDs: " + packet);
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received on an unknown stream (and not an ECHO): " + packet);
if (sendId == null) {
Connection con = _manager.getConnectionByOutboundId(packet.getReceiveStreamId());
if (con != null) {
if (con.getAckedPackets() <= 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received additional packets before the syn on " + con + ": " + packet);
receiveKnownCon(con, packet);
return;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("hrmph, received while ack of syn was in flight on " + con + ": " + packet + " acked: " + con.getAckedPackets());
receiveKnownCon(con, packet);
return;
}
}
}
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
_manager.getConnectionHandler().receiveNewSyn(packet);
} else {
if (_log.shouldLog(Log.WARN)) {
StringBuffer buf = new StringBuffer(128);
Set cons = _manager.listConnections();
for (Iterator iter = cons.iterator(); iter.hasNext(); ) {
Connection con = (Connection)iter.next();
buf.append(con.toString()).append(" ");
}
_log.warn("Packet belongs to no other cons: " + packet + " connections: "
+ buf.toString() + " sendId: "
+ (sendId != null ? Base64.encode(sendId) : " unknown"));
}
}
}
}
private void receivePing(Packet packet) {
boolean ok = packet.verifySignature(_context, packet.getOptionalFrom(), null);
if (!ok) {
if (_log.shouldLog(Log.WARN)) {
if (packet.getOptionalFrom() == null)
_log.warn("Ping with no from (flagged? " + packet.isFlagSet(Packet.FLAG_FROM_INCLUDED) + ")");
else if (packet.getOptionalSignature() == null)
_log.warn("Ping with no signature (flagged? " + packet.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED) + ")");
else
_log.warn("Forged ping, discard (from=" + packet.getOptionalFrom().calculateHash().toBase64()
+ " sig=" + packet.getOptionalSignature().toBase64() + ")");
}
} else {
PacketLocal pong = new PacketLocal(_context, packet.getOptionalFrom());
pong.setFlag(Packet.FLAG_ECHO, true);
pong.setFlag(Packet.FLAG_SIGNATURE_INCLUDED, false);
pong.setReceiveStreamId(packet.getSendStreamId());
_manager.getPacketQueue().enqueue(pong);
}
}
private void receivePong(Packet packet) {
_manager.receivePong(packet.getReceiveStreamId());
}
private static final boolean isValidMatch(byte conStreamId[], byte packetStreamId[]) {
if ( (conStreamId == null) || (packetStreamId == null) ||
(conStreamId.length != packetStreamId.length) )
return false;
boolean nonZeroFound = false;
for (int i = 0; i < conStreamId.length; i++) {
if (conStreamId[i] != packetStreamId[i]) return false;
if (conStreamId[i] != 0x0) nonZeroFound = true;
}
return nonZeroFound;
}
private static final boolean isNonZero(byte[] b) {
boolean nonZeroFound = false;
for (int i = 0; b != null && i < b.length; i++) {
if (b[i] != 0x0)
nonZeroFound = true;
}
return nonZeroFound;
}
}

View File

@ -0,0 +1,164 @@
package net.i2p.client.streaming;
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
* retries, etc.
*/
public class PacketLocal extends Packet implements MessageOutputStream.WriteStatus {
private I2PAppContext _context;
private Log _log;
private Connection _connection;
private Destination _to;
private SessionKey _keyUsed;
private Set _tagsSent;
private long _createdOn;
private int _numSends;
private long _lastSend;
private long _acceptedOn;
private long _ackOn;
private long _cancelledOn;
private SimpleTimer.TimedEvent _resendEvent;
public PacketLocal(I2PAppContext ctx, Destination to) {
this(ctx, to, null);
}
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;
_cancelledOn = -1;
}
public Destination getTo() { return _to; }
public void setTo(Destination to) { _to = to; }
public SessionKey getKeyUsed() { return _keyUsed; }
public void setKeyUsed(SessionKey key) { _keyUsed = key; }
public Set getTagsSent() { return _tagsSent; }
public void setTagsSent(Set tags) {
if ( (_tagsSent != null) && (_tagsSent.size() > 0) && (tags.size() > 0) ) {
//int old = _tagsSent.size();
//_tagsSent.addAll(tags);
if (!_tagsSent.equals(tags))
System.out.println("ERROR: dup tags: old=" + _tagsSent.size() + " new=" + tags.size() + " packet: " + toString());
} else {
_tagsSent = tags;
}
}
public boolean shouldSign() {
return isFlagSet(FLAG_SIGNATURE_INCLUDED) ||
isFlagSet(FLAG_SYNCHRONIZE) ||
isFlagSet(FLAG_CLOSE) ||
isFlagSet(FLAG_ECHO);
}
/** last minute update of ack fields, just before write/sign */
public void prepare() {
if (_connection != null)
_connection.getInputStream().updateAcks(this);
}
public long getCreatedOn() { return _createdOn; }
public long getLifetime() { return _context.clock().now() - _createdOn; }
public void incrementSends() {
_numSends++;
_lastSend = _context.clock().now();
}
public void ackReceived() {
synchronized (this) {
if (_ackOn <= 0)
_ackOn = _context.clock().now();
notifyAll();
}
SimpleTimer.getInstance().removeEvent(_resendEvent);
}
public void cancelled() {
synchronized (this) {
_cancelledOn = _context.clock().now();
notifyAll();
}
SimpleTimer.getInstance().removeEvent(_resendEvent);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Cancelled! " + toString(), new Exception("cancelled"));
}
/** how long after packet creation was it acked? */
public int getAckTime() {
if (_ackOn <= 0)
return -1;
else
return (int)(_ackOn - _createdOn);
}
public int getNumSends() { return _numSends; }
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)
return str + " ack after " + getAckTime();
else
return str;
}
public void waitForAccept(int maxWaitMs) {
if (_connection == null)
throw new IllegalStateException("Cannot wait for accept with no connection");
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 = after;
else
_acceptedOn = -1;
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) {
long expiration = _context.clock().now()+maxWaitMs;
while (true) {
long timeRemaining = expiration - _context.clock().now();
if ( (timeRemaining <= 0) && (maxWaitMs > 0) ) return;
try {
synchronized (this) {
if (_ackOn > 0) return;
if (_cancelledOn > 0) return;
if (timeRemaining > 60*1000)
timeRemaining = 60*1000;
else if (timeRemaining <= 0)
timeRemaining = 10*1000;
wait(timeRemaining);
}
} catch (InterruptedException ie) {}
}
}
public boolean writeAccepted() { return _acceptedOn > 0 && _cancelledOn <= 0; }
public boolean writeFailed() { return _cancelledOn > 0; }
public boolean writeSuccessful() { return _ackOn > 0 && _cancelledOn <= 0; }
}

View File

@ -0,0 +1,125 @@
package net.i2p.client.streaming;
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.ByteArray;
import net.i2p.data.SessionKey;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**
* Queue out packets to be sent through the session.
* Well, thats the theory at least... in practice we just
* send them immediately with no blocking, since the
* mode=bestEffort doesnt block in the SDK.
*
*/
class PacketQueue {
private I2PAppContext _context;
private Log _log;
private I2PSession _session;
private ConnectionManager _connectionManager;
private ByteCache _cache = ByteCache.getInstance(64, 36*1024);
public PacketQueue(I2PAppContext context, I2PSession session, ConnectionManager mgr) {
_context = context;
_session = session;
_connectionManager = mgr;
_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 });
}
/**
* Add a new packet to be sent out ASAP
*/
public void enqueue(PacketLocal packet) {
packet.prepare();
SessionKey keyUsed = packet.getKeyUsed();
if (keyUsed == null)
keyUsed = new SessionKey();
Set tagsSent = packet.getTagsSent();
if (tagsSent == null)
tagsSent = new HashSet(0);
// cache this from before sendMessage
String conStr = (packet.getConnection() != null ? packet.getConnection().toString() : "");
if (packet.getAckTime() > 0) {
_log.debug("Not resending " + packet);
return;
} else {
_log.debug("Sending... " + packet);
}
ByteArray ba = _cache.acquire();
byte buf[] = ba.getData();
long begin = 0;
long end = 0;
boolean sent = false;
try {
int size = 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();
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);
if (packet.getNumSends() > 1)
con.incrementDupMessagesSent(1);
}
} catch (I2PSessionException ise) {
if (_log.shouldLog(Log.WARN))
_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);
Connection c = packet.getConnection();
if (c != null) // handle race on b0rk
c.disconnect(false);
} else {
packet.setKeyUsed(keyUsed);
packet.setTagsSent(tagsSent);
packet.incrementSends();
if (_log.shouldLog(Log.DEBUG)) {
String msg = "SEND " + packet + (tagsSent.size() > 0
? " with " + tagsSent.size() + " tags"
: "")
+ " send # " + packet.getNumSends()
+ " sendTime: " + (end-begin)
+ " con: " + conStr;
_log.debug(msg);
}
_connectionManager.getPacketHandler().displayPacket(packet, "SEND");
}
}
}

View File

@ -0,0 +1,62 @@
package net.i2p.client.streaming;
import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* Examine a connection's state and pick the right scheduler for it.
*
*/
class SchedulerChooser {
private I2PAppContext _context;
private Log _log;
private TaskScheduler _nullScheduler;
/** list of TaskScheduler objects */
private List _schedulers;
public SchedulerChooser(I2PAppContext context) {
_context = context;
_log = context.logManager().getLog(SchedulerChooser.class);
_schedulers = createSchedulers();
_nullScheduler = new NullScheduler();
}
public TaskScheduler getScheduler(Connection con) {
for (int i = 0; i < _schedulers.size(); i++) {
TaskScheduler scheduler = (TaskScheduler)_schedulers.get(i);
if (scheduler.accept(con)) {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Scheduling for " + con + " with " + scheduler.getClass().getName());
return scheduler;
}
}
return _nullScheduler;
}
private List createSchedulers() {
List rv = new ArrayList(8);
rv.add(new SchedulerPreconnect(_context));
rv.add(new SchedulerConnecting(_context));
rv.add(new SchedulerReceived(_context));
rv.add(new SchedulerConnectedBulk(_context));
rv.add(new SchedulerClosing(_context));
rv.add(new SchedulerClosed(_context));
rv.add(new SchedulerDead(_context));
return rv;
}
private class NullScheduler implements TaskScheduler {
private Log _log;
public NullScheduler() {
_log = _context.logManager().getLog(NullScheduler.class);
}
public void eventOccurred(Connection con) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Event occurred on " + con, new Exception("source"));
}
public boolean accept(Connection con) { return true; }
};
}

View File

@ -0,0 +1,54 @@
package net.i2p.client.streaming;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* <p>Scheduler used for after both sides have had their close packets
* ACKed, but the final timeout hasn't passed.</p>
*
* <h2>Entry conditions:</h2><ul>
* <li>Both sides have closed and ACKed.</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 SchedulerClosed extends SchedulerImpl {
private Log _log;
public SchedulerClosed(I2PAppContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(SchedulerClosed.class);
}
public boolean accept(Connection con) {
if (con == null) return false;
long timeSinceClose = _context.clock().now() - con.getCloseSentOn();
boolean ok = (con.getCloseSentOn() > 0) &&
(con.getCloseReceivedOn() > 0) &&
(con.getUnackedPacketsReceived() <= 0) &&
(con.getUnackedPacketsSent() <= 0) &&
(!con.getResetReceived()) &&
(timeSinceClose < Connection.DISCONNECT_TIMEOUT);
boolean conTimeout = (con.getOptions().getConnectTimeout() < con.getLifetime()) &&
con.getSendStreamId() == null &&
con.getLifetime() < Connection.DISCONNECT_TIMEOUT;
return (ok || conTimeout);
}
public void eventOccurred(Connection con) {
// noop. we do the timeout through the simpleTimer anyway
//long timeLeft = con.getCloseSentOn() + Connection.DISCONNECT_TIMEOUT - _context.clock().now();
//reschedule(timeLeft, con);
}
}

View File

@ -0,0 +1,64 @@
package net.i2p.client.streaming;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* <p>Scheduler used for after both SYNs have been ACKed and both sides
* have closed the stream, but either we haven't ACKed their close or
* they haven't ACKed ours.</p>
*
* <h2>Entry conditions:</h2><ul>
* <li>Both sides have closed.</li>
* <li>At least one direction has not ACKed the close.</li>
* </ul>
*
* <h2>Events:</h2><ul>
* <li>Packets received (which may or may not ACK the ones sent)</li>
* <li>RESET received</li>
* <li>Message sending fails (error talking to the session)</li>
* <li>Message sending fails (too many resends)</li>
* </ul>
*
* <h2>Next states:</h2>
* <li>{@link SchedulerClosed closed} - after both sending and receiving ACKs on the CLOSE</li>
* <li>{@link SchedulerDead dead} - after sending or receiving a RESET</li>
* </ul>
*
*/
class SchedulerClosing extends SchedulerImpl {
private Log _log;
public SchedulerClosing(I2PAppContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(SchedulerClosing.class);
}
public boolean accept(Connection con) {
boolean ok = (con != null) &&
(con.getCloseSentOn() > 0) &&
(con.getCloseReceivedOn() > 0) &&
( (con.getUnackedPacketsReceived() > 0) || (con.getUnackedPacketsSent() > 0) );
return ok;
}
public void eventOccurred(Connection con) {
if (con.getNextSendTime() <= 0)
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
long remaining = con.getNextSendTime() - _context.clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Event occurred w/ remaining: " + remaining + " on " + con);
if (remaining <= 0) {
if (con.getCloseSentOn() <= 0) {
con.sendAvailable();
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
} else {
con.ackImmediately();
}
} else {
//if (remaining < 5*1000)
// remaining = 5*1000;
//con.setNextSendTime(when
reschedule(remaining, con);
}
}
}

View File

@ -0,0 +1,66 @@
package net.i2p.client.streaming;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* <p>Scheduler used for after our SYN has been sent and ACKed but one
* (or more) sides haven't closed the stream yet. In addition, the
* stream must be using the BULK profile, rather than the INTERACTIVE
* profile.</p>
*
* <h2>Entry conditions:</h2><ul>
* <li>Packets sent and ACKs received.</li>
* <li>At least one direction is not closed</li>
* </ul>
*
* <h2>Events:</h2><ul>
* <li>Packets received (which may or may not ACK the ones sent)</li>
* <li>Message flush (explicitly, through a full buffer, or stream closure)</li>
* <li>RESET received</li>
* <li>Message sending fails (error talking to the session)</li>
* <li>Message sending fails (too many resends)</li>
* </ul>
*
* <h2>Next states:</h2>
* <li>{@link SchedulerClosing closing} - after both sending and receiving a CLOSE</li>
* <li>{@link SchedulerClosed closed} - after both sending and receiving ACKs on the CLOSE</li>
* <li>{@link SchedulerDead dead} - after sending or receiving a RESET</li>
* </ul>
*
*/
class SchedulerConnectedBulk extends SchedulerImpl {
private Log _log;
public SchedulerConnectedBulk(I2PAppContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(SchedulerConnectedBulk.class);
}
public boolean accept(Connection con) {
boolean ok = (con != null) &&
(con.getAckedPackets() > 0) &&
(con.getOptions().getProfile() == ConnectionOptions.PROFILE_BULK) &&
(!con.getResetReceived()) &&
( (con.getCloseSentOn() <= 0) || (con.getCloseReceivedOn() <= 0) );
if (!ok) {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("con: " + con + " closeSentOn: " + con.getCloseSentOn()
// + " closeReceivedOn: " + con.getCloseReceivedOn());
}
return ok;
}
public void eventOccurred(Connection con) {
if (con.getNextSendTime() <= 0)
return;
long timeTillSend = con.getNextSendTime() - _context.clock().now();
if (timeTillSend <= 0) {
con.setNextSendTime(-1);
con.sendAvailable();
} else {
reschedule(timeTillSend, con);
}
}
}

View File

@ -0,0 +1,77 @@
package net.i2p.client.streaming;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* <p>Scheduler used once we've sent our SYN but it hasn't been ACKed yet.
* This connection may or may not be locally created.</p>
*
* <h2>Entry conditions:</h2><ul>
* <li>Packets sent but none ACKed</li>
* </ul>
*
* <h2>Events:</h2><ul>
* <li>Packets received (which may or may not ACK the ones sent)</li>
* <li>Message flush (explicitly, through a full buffer, or stream closure)</li>
* <li>Connection establishment timeout</li>
* <li>RESET received</li>
* </ul>
*
* <h2>Next states:</h2>
* <li>{@link SchedulerConnectedBulk connected} - after receiving an ACK</li>
* <li>{@link SchedulerClosing closing} - after both sending and receiving a CLOSE</li>
* <li>{@link SchedulerClosed closed} - after both sending and receiving ACKs on the CLOSE</li>
* <li>{@link SchedulerDead dead} - after sending or receiving a RESET</li>
* </ul>
*
*/
class SchedulerConnecting extends SchedulerImpl {
private Log _log;
public SchedulerConnecting(I2PAppContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(SchedulerConnecting.class);
}
public boolean accept(Connection con) {
if (con == null) return false;
boolean notYetConnected = (con.getIsConnected()) &&
(con.getSendStreamId() == null) &&
(con.getLastSendId() >= 0) &&
(con.getAckedPackets() <= 0) &&
(!con.getResetReceived());
return notYetConnected;
}
public void eventOccurred(Connection con) {
long waited = _context.clock().now() - con.getCreatedOn();
if ( (con.getOptions().getConnectTimeout() > 0) &&
(con.getOptions().getConnectTimeout() <= waited) ) {
con.setConnectionError("Timeout waiting for ack (waited " + waited + "ms)");
con.disconnect(false);
reschedule(0, con);
if (_log.shouldLog(Log.DEBUG))
_log.debug("waited too long: " + waited);
return;
} else {
if (con.getOptions().getConnectTimeout() > 0)
reschedule(con.getOptions().getConnectTimeout(), con);
}
/*
long timeTillSend = con.getNextSendTime() - _context.clock().now();
if ( (timeTillSend <= 0) && (con.getNextSendTime() > 0) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("send next on " + con);
con.sendAvailable();
con.setNextSendTime(-1);
} else {
if (con.getNextSendTime() > 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("time till send: " + timeTillSend + " on " + con);
reschedule(timeTillSend, con);
}
}
*/
}
}

View File

@ -0,0 +1,50 @@
package net.i2p.client.streaming;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* <p>Scheduler used for after the final timeout has passed or the
* connection was reset.</p>
*
* <h2>Entry conditions:</h2><ul>
* <li>Both sides have closed and ACKed and the timeout has passed. <br />
* <b>or</b></li>
* <li>A RESET was received</li>
* </ul>
*
* <h2>Events:</h2><ul>
* <li>None</li>
* </ul>
*
* <h2>Next states:</h2>
* <li>None</li>
* </ul>
*
*
*/
class SchedulerDead extends SchedulerImpl {
private Log _log;
public SchedulerDead(I2PAppContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(SchedulerDead.class);
}
public boolean accept(Connection con) {
if (con == null) return false;
long timeSinceClose = _context.clock().now() - con.getCloseSentOn();
boolean nothingLeftToDo = (con.getCloseSentOn() > 0) &&
(con.getCloseReceivedOn() > 0) &&
(con.getUnackedPacketsReceived() <= 0) &&
(con.getUnackedPacketsSent() <= 0) &&
(timeSinceClose >= Connection.DISCONNECT_TIMEOUT);
boolean timedOut = (con.getOptions().getConnectTimeout() < con.getLifetime()) &&
con.getSendStreamId() == null &&
con.getLifetime() >= Connection.DISCONNECT_TIMEOUT;
return con.getResetReceived() || nothingLeftToDo || timedOut;
}
public void eventOccurred(Connection con) {
con.disconnectComplete();
}
}

View File

@ -0,0 +1,37 @@
package net.i2p.client.streaming;
import net.i2p.I2PAppContext;
import net.i2p.util.SimpleTimer;
import net.i2p.util.Log;
/**
* Base scheduler
*/
abstract class SchedulerImpl implements TaskScheduler {
protected I2PAppContext _context;
private Log _log;
public SchedulerImpl(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(SchedulerImpl.class);
}
protected void reschedule(long msToWait, Connection con) {
SimpleTimer.getInstance().addEvent(new ConEvent(con), msToWait);
}
private class ConEvent implements SimpleTimer.TimedEvent {
private Connection _connection;
private Exception _addedBy;
public ConEvent(Connection con) {
_connection = con;
//_addedBy = new Exception("added by");
}
public void timeReached() {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("firing event on " + _connection, _addedBy);
_connection.eventOccurred();
}
public String toString() { return "event on " + _connection; }
}
}

View File

@ -0,0 +1,54 @@
package net.i2p.client.streaming;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* <p>Scheduler used for locally created connections where we have not yet
* sent the initial SYN packet.</p>
*
* <h2>Entry conditions:</h2><ul>
* <li>Locally created</li>
* <li>No packets sent or received</li>
* </ul>
*
* <h2>Events:</h2><ul>
* <li>Message flush (explicitly, through a full buffer, or stream closure)</li>
* <li>Initial delay timeout (causing implicit flush of any data available)</li>
* </ul>
*
* <h2>Next states:</h2>
* <li>{@link SchedulerConnecting connecting} - after sending a packet</li>
* </ul>
*/
class SchedulerPreconnect extends SchedulerImpl {
private Log _log;
public SchedulerPreconnect(I2PAppContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(SchedulerPreconnect.class);
}
public boolean accept(Connection con) {
return (con != null) &&
(con.getSendStreamId() == null) &&
(con.getLastSendId() < 0);
}
public void eventOccurred(Connection con) {
if (con.getNextSendTime() < 0)
con.setNextSendTime(_context.clock().now() + con.getOptions().getConnectDelay());
long timeTillSend = con.getNextSendTime() - _context.clock().now();
if (timeTillSend <= 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send available for the SYN on " + con);
con.sendAvailable();
con.setNextSendTime(-1);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Wait " + timeTillSend + " before sending the SYN on " + con);
reschedule(timeTillSend, con);
}
}
}

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