Compare commits

...

855 Commits

Author SHA1 Message Date
12900ca709 * 2005-12-01 0.6.1.7 released
2005-12-01  jrandom
    * Add a new criteria to the tunnel join throttle, backing off people if we
      are failing to talk to our peers more than usual.
2005-12-01 17:16:53 +00:00
f5b829a124 2005-11-30 jrandom
* Cleaned up the build process to deal with Jetty 5.1.6 and rename the
      new commons-logging-api.jar to commons-logging.jar, which it replaces.
      Jetty 5.1.6 is pushed with all updates.  Also, no need to push a
      separate jdom or rome, as they're inside syndie.war.
2005-12-01 01:13:44 +00:00
f62a6d3ce6 2005-11-30 jrandom
* Don't let the TCP transport alone shitlist a peer, since other
      transports may be working.  Also display whether TCP connections are
      inbound or outbound on the peers page.
    * Fixed some substantial bugs in the SSU introducers where we wouldn't
      talk to anyone who didn't expose an IP (even if they had introducers),
      among other goofy things.
    * When dealing with SSU introducers, send them all a packet at 3s/6s/9s,
      rather than sending one a packet at 3s, then another a packet at 6s,
      and a third a packet at 9s.
    * Fixed Syndie attachments (oops)
2005-11-30 20:48:25 +00:00
3d18bf870b 2005-11-29 zzz
* Added a link to orion's jump page on the 'key not found' error page.
2005-11-29 17:56:02 +00:00
d8071296eb 2005-11-29 jrandom
* Further Syndie UI cleanup
    * Bundled our patched MultiPartRequest code from jetty (APL2 licensed),
      since it hasn't been applied to the jetty CVS yet [1].  Its packaged
      into syndie.jar and renamed to net.i2p.syndie.web.MultiPartRequest, but
      will be removed as soon as its integrated into Jetty.  This patch allows
      posting content in various character sets.
      [1] http://article.gmane.org/gmane.comp.java.jetty.general/6031
    * Upgraded new installs to the latest stable jetty (5.1.6), though this
      isn't pushed as part of the update yet, as there aren't any critical
      bugs.
2005-11-29 16:58:01 +00:00
c66e3256aa 2005-11-29 jrandom
* Added back in the OSX jbigi, which was accidentally removed a few revs
      back (thanks for the bug report stoerte!)  New installs will get the
      full jbigi, or you can pull the jbigi.jar from CVS by going to
      http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/installer/lib/jbigi/jbigi.jar
      and clicking on the first "download" link, saving that jbigi.jar to
      lib/jbigi.jar in your I2P installation directory.  After restarting your
      router, it should load up fine.
2005-11-29 12:46:34 +00:00
686742a67b 2005-11-27 jrandom
* Inlined the Syndie CSS to reduce the number of HTTP requests (and
      because firefox [and others?] delay rendering until they fetch the css).
    * Make sure we fire the shutdown tasks when regenerating a new identity
      (thanks picsou!)
    * Cleaned up some of the things I b0rked in the 'dynamic keys' mode
    * Don't drop SSU sessions if they're still transmitting data successfully,
      even if there are transmission failures
    * Adjusted the time summarization to display hours after 119m, not 90m
    * Further EepGet cleanup (grr)
2005-11-28 16:02:38 +00:00
cdf94295f3 (1.185) added TheBreton.i2p adab.i2p awup.i2p china.i2p davidkra.i2p
comwiz.i2p dust.i2p eepsites.i2p jmg.i2p kuroneko.i2p
               mywastedlife.i2p site.games.i2p squid2.i2p striker.i2p
               tracker.awup.i2p zzz.i2p
2005-11-26 18:32:22 +00:00
fbf1705c4e * 2005-11-26 0.6.1.6 released 2005-11-26 18:26:22 +00:00
453ecc4208 Further improvements, and works fine for ubergeeks, but not yet for normal
geeks (aka no router console)
2005-11-26 17:19:29 +00:00
d1f2b447ac 2005-11-26 jrandom
* Update the sorting in Syndie to consider children 'newer' than parents,
      even if they have the same message ID (duh)
    * Cleaned up some nav links in Syndie (good idea gloin, spaetz!)
    * Added a bunch of tooltips to Syndie's fields (thanks polecat!)
    * Force support for nonvalidating XML in Jetty (so we can handle GCJ/etc
      better)
2005-11-26 16:51:16 +00:00
70c4560f02 2005-11-26 jrandom
* Be more explicit about what messages we will handle through a client
      tunnel, and how we will handle them.  This cuts off a set of attacks
      that an active adversary could mount, though they're probably nonobvious
      and would require at least some sophistication.
2005-11-26 11:39:32 +00:00
9089fdd2d5 2005-11-26 Raccoon23
* Added support for 'dynamic keys' mode, where the router creates a new
      router identity whenever it detects a substantial change in its public
      address (read: SSU IP or port).  This only offers minimal additional
      protection against trivial attackers, but should provide functional
      improvement for people who have periodic IP changes, since their new
      router address would not be shitlisted while their old one would be.
    * Added further infrastructure for restricted route operation, but its use
      is not recommended.
2005-11-26 09:16:11 +00:00
ef82cc4f20 2005-11-25 jrandom
* Further Syndie UI cleanups
    * Logging cleanup
    * Fixed link to fproxy.tino.i2p (thanks zzz!)
2005-11-26 05:05:52 +00:00
f2c2a5b386 2005-11-25 jrandom
* Don't publish stats for periods we haven't reached yet (thanks zzz!)
    * Cleaned up the syndie threaded display to show the last updated date for
      a subthread, and to highlight threads updated in the last two days.
2005-11-25 11:06:00 +00:00
fc858bc950 replaced the old nonfunctional perl SAM lib with postman's new
implementation (thanks postman!)
2005-11-24 09:41:01 +00:00
dbb4b3d0c2 2005-11-24 jrandom
* Fix to save syndication settings in Syndie (thanks spaetz!)
2005-11-24 08:45:54 +00:00
2b841ad667 2005-11-23 jrandom
* Removed spurious streaming lib RTO increase (it wasn't helpful)
    * Streamlined the tunnel batching to schedule batch transmissions more
      appropriately.
    * Default tunnel pool variance to 2 +0-1 hops
2005-11-23 16:04:52 +00:00
5e094b43b3 2005-11-21 jrandom
* IE doesn't strip SPAN from <button> form fields, so add in a workaround
      within I2PTunnel.
    * Increase the maximum SSU retransmission timeout to accomodate slower or
      more congested links (though SSU's RTO calculation will usually use a
      much lower timeout)
    * Moved the streaming lib timed events off the main timer queues and onto
      a streaming lib specific set of timer queues.  Streaming lib timed
      events are more likely to have lock contention on the I2CP socket while
      other timed events in the router are (largely) independent.
    * Fixed a case sensitive lookup bug (thanks tino!)
    * Syndie cleanup - new edit form on the preview page, and fixed some blog
      links (thanks tino!)
2005-11-21 14:45:04 +00:00
33d57dd545 2005-11-21 jrandom
* IE doesn't strip SPAN from <button> form fields, so add in a workaround
      within I2PTunnel.
    * Increase the maximum SSU retransmission timeout to accomodate slower or
      more congested links (though SSU's RTO calculation will usually use a
      much lower timeout)
    * Moved the streaming lib timed events off the main timer queues and onto
      a streaming lib specific set of timer queues.  Streaming lib timed
      events are more likely to have lock contention on the I2CP socket while
      other timed events in the router are (largely) independent.
    * Fixed a case sensitive lookup bug (thanks tino!)
    * Syndie cleanup - new edit form on the preview page, and fixed some blog
      links (thanks tino!)
2005-11-21 14:37:06 +00:00
61f75b5f09 2005-11-19 jrandom
* Implemented a trivial pure java PMTU backoff strategy, switching between
      a 608 byte MTU and a 1350 byte MTU, depending upon retransmission rates.
    * Fixed new user registration in Syndie (thanks Complication!)
2005-11-20 04:42:16 +00:00
3f65e53592 2005-11-17 jrandom
* More cautious file handling in Syndie
2005-11-17 08:23:45 +00:00
99ae3ee459 2005-11-16 jrandom
* More aggressive I2PTunnel content encoding munging to work around some
      rare HTTP behavior (ignoring q values on Accept-encoding, using gzip
      even when only identity is specified, etc).  I2PTunnelHTTPServer now
      sends "Accept-encoding: \r\n" plus "X-Accept-encoding: x-i2p-gzip\r\n",
      and I2PTunnelHTTPServer handles x-i2p-gzip in either the Accept-encoding
      or X-Accept-encoding headers.  Eepsite operators who do not know to
      check for X-Accept-encoding will simply use the identity encoding.
2005-11-16 11:50:56 +00:00
f7236d7d58 * 2005-11-15 0.6.1.5 released 2005-11-16 03:20:20 +00:00
5182008b38 more stuff 2005-11-16 03:12:04 +00:00
9d030327e6 put rome and jdom in the syndie.war, and fix some deprecation warnings 2005-11-15 12:46:54 +00:00
a15c90d2cc add in a basic limit on the attachment size (trivially circumvented, but for now it'll do)
fixed some links
2005-11-15 11:17:04 +00:00
84c2a713e1 update the post 2005-11-15 10:23:29 +00:00
7db9ce6e5b update the default syndie post, and add in some html hooks for it
only allow 40 chars in the subject within the thread tree
2005-11-15 10:22:57 +00:00
da42f5717b logging cleanup 2005-11-15 06:38:00 +00:00
5241953e5d logging 2005-11-15 06:37:20 +00:00
b1a5f61ba2 cleanup and logging 2005-11-15 06:34:04 +00:00
024a5a1ad4 deal with spaces in filenames cleanly 2005-11-15 06:29:51 +00:00
b031de5404 more cleanup 2005-11-15 06:24:17 +00:00
dceac73951 2005-11-14 jrandom
* Migrate to the new Syndie interface
2005-11-15 00:45:36 +00:00
8f95143488 more syndie cleanup 2005-11-15 00:24:35 +00:00
a8f3043aae * move over the rssimport, and default it to the proxy @ localhost:4444 2005-11-14 05:40:18 +00:00
6c91b2d4a9 migrate to the new system 2005-11-14 02:09:23 +00:00
ddd438de35 protect against spoofing in syndie with a per-jvm-instance nonce (which should prevent the spurious "are you being spoofed" things)
fixed the syndie previewing
2005-11-13 14:15:26 +00:00
16fd46db2b move the admin page over to the new system 2005-11-13 11:04:17 +00:00
1159c155a4 * migrate the post/preview over to the new system
* honor the "force new thread" and "refuse replies (from everyone but me)" functionality
2005-11-13 07:55:38 +00:00
30b4e2aa2a change one's password 2005-11-13 04:47:15 +00:00
ae46fa2e6d allow the user to change their password 2005-11-13 04:46:28 +00:00
9fc34895c9 more careful ops 2005-11-13 03:46:20 +00:00
08be4c7b3d migrated the syndie addressbook 2005-11-13 03:42:52 +00:00
7443457af4 * Ignore <ul>. Add <strong>, <p> and <li>.
* change to make [ and ] use their &#n;.
2005-11-12 17:10:56 +00:00
979a4cfb69 migrate the switchuser and register 2005-11-12 13:01:32 +00:00
ed285871bf migrate the profile page to the new format, and refactor the new format's servlets 2005-11-12 12:14:23 +00:00
8c70b8b32a ircclient too :) 2005-11-12 05:06:57 +00:00
14134694d7 2005-11-11 jrandom
* Add filtering threads by author to Syndie, populated with authors in the
      user's addressbook
    * When creating the default user, add
      "http://syndiemedia.i2p/archive/archive.txt" to their addressbook,
      configured to automatically pull updates.  (what other archives should
      be included?)
    * Tiny servlet to help dole out the new routerconsole themes, and bundle
      the installer/resources/themes/** into ./docs/themes/** on both install
      and update.
2005-11-12 05:03:51 +00:00
807d2d3509 2005-11-11 cervantes
* Initial pass of the routerconsole revamp, starting with I2PTunnel and
	  being progressively rolled out to other sections at later dates.
	  Featuring abstracted W3C strict XHTML1.0 markup, with CSS providing
	  layout and styling.
	* Implemented console themes. Users can create their own themes by
	  creating css files in: {i2pdir}/docs/themes/console/{themename}/
	  and activating it using the routerconsole.theme={themename} advanced
	  config property. Look at the example incomplete "defCon1" theme.
	  Note: This is very much a work in progress. Folks might want to hold-off
	  creating their own skins until the markup has solidified.
	* Added "routerconsole.javascript.disabled=true" to disable console
	  client-side scripting and "routerconsole.css.disabled=true" to remove
	  css styling (only rolled out in the i2ptunnel interface currently)
	* Fixed long standing bug with i2ptunnel client and server edit screens
	  where tunnel count and depth properties would fail to save. Added
	  backup quantity and variance configuration options.
	* Added basic accessibility support (key shortcuts, linear markup, alt and
	  title information and form labels).
	* So far only tested on IE6, Firefox 1.0.6, Opera 8 and lynx.
2005-11-12 02:52:40 +00:00
b222cd43f4 2005-11-11 cervantes
* Initial pass of the routerconsole revamp, starting with I2PTunnel and
	  being progressively rolled out to other sections at later dates.
	  Featuring abstracted W3C strict XHTML1.0 markup, with CSS providing
	  layout and styling.
	* Implemented console themes. Users can create their own themes by
	  creating css files in: {i2pdir}/docs/themes/console/{themename}/
	  and activating it using the routerconsole.theme={themename} advanced
	  config property. Look at the example incomplete "defCon1" theme.
	  Note: This is very much a work in progress. Folks might want to hold-off
	  creating their own skins until the markup has solidified.
	* Added "routerconsole.javascript.disabled=true" to disable console
	  client-side scripting and "routerconsole.css.disabled=true" to remove
	  css styling (only rolled out in the i2ptunnel interface currently)
	* Fixed long standing bug with i2ptunnel client and server edit screens
	  where tunnel count and depth properties would fail to save. Added
	  backup quantity and variance configuration options.
	* Added basic accessibility support (key shortcuts, linear markup, alt and
	  title information and form labels).
	* So far only tested on IE6, Firefox 1.0.6, Opera 8 and lynx.
2005-11-12 02:38:55 +00:00
7f6aa327f2 allow the user to override what login/pass is used for the default user in single user mode 2005-11-11 12:26:17 +00:00
49564a3878 2005-11-11 jrandom
* Default Syndie to single user mode, and automatically log into a default
      user account (additional accounts can be logged into with the 'switch'
      or login pages, and new accounts can be created with the register page).
    * Disable the 'automated' column on the Syndie addressbook unless the user
      is appropriately authorized (good idea Polecat!)
2005-11-11 11:29:15 +00:00
12ddaff0ce .. 2005-11-11 10:09:04 +00:00
a8ea239dcc *cough* 2005-11-11 09:58:03 +00:00
ca391097a9 onwards, christian soldiers 2005-11-11 09:39:48 +00:00
4297edc88f wait until we build the tree before sorting the threads, so we can have the most recently updated thread first 2005-11-11 05:10:38 +00:00
6de4673e9e 2005-11-10 jrandom
* First pass to a new threaded Syndie interface, which isn't enabled by
      default, as its not done yet.
2005-11-11 03:46:36 +00:00
f6979c811f 2005-11-10 jrandom
* First pass to a new threaded Syndie interface, which isn't enabled by
      default, as its not done yet.
2005-11-11 03:41:16 +00:00
bd86483204 2005-11-06 jrandom
* Include SSU establishment failure in the peer profile as a commError,
      as we do for TCP establishment failures.
    * Don't throttle the initial transmission of a message because of ongoing
      retransmissions to a peer, since the initial transmission of a message
      is more valuable than a retransmission (since it has less latency).
    * Cleaned up links to SusiDNS and I2PTunnel (thanks zzz!)
2005-11-06 22:25:17 +00:00
53cf03cec6 no need for ant's IgnoreBlank (and its not in the classpath anyway) 2005-11-05 22:50:14 +00:00
e284a8878b 2005-11-05 jrandom
* Include the most recent ACKs with packets, rather than only sending an
      ack exactly once.  SSU differs from TCP in this regard, as TCP has ever
      increasing sequence numbers, while each message ID in SSU is random, so
      we don't get the benefit of later ACKs implicitly ACKing earlier
      messages.
    * Reduced the max retransmission timeout for SSU
    * Don't try to send messages queued up for a long time waiting for
      establishment.
2005-11-05 21:19:05 +00:00
14cd469c6d 2005-11-05 jrandom
* Include the most recent ACKs with packets, rather than only sending an
      ack exactly once.  SSU differs from TCP in this regard, as TCP has ever
      increasing sequence numbers, while each message ID in SSU is random, so
      we don't get the benefit of later ACKs implicitly ACKing earlier
      messages.
    * Reduced the max retransmission timeout for SSU
    * Don't try to send messages queued up for a long time waiting for
      establishment.
2005-11-05 21:12:57 +00:00
9050d7c218 2005-11-05 dust
* Fix sucker to delete its temporary files.
    * Improve sucker's sml output some.
    * Fix Exception in SMLParser for weird sml.
2005-11-05 11:01:57 +00:00
0ad18cd0ba 2005-10-31 jrandom
* Fix for some syndie reply scenarios (thanks identiguy and CofE!)
    * Removed a potentially infinitely recursive call (oops)
(forgot to commit this file before.  oops)
2005-11-04 03:08:00 +00:00
ca0af146b7 2005-11-03 zzz
* Added a new error page to the eepproxy to differentiate the full 60
      second timeout from the immediate "I don't know this base64" failure.
2005-11-04 01:20:17 +00:00
a2d2b031f4 2005-11-01 jrandom
* Added a few more css elements (thanks identiguy!)
2005-11-02 00:35:21 +00:00
2f36912ac0 2005-10-31 jrandom
* Fix for some syndie reply scenarios (thanks identiguy and CofE!)
    * Removed a potentially infinitely recursive call (oops)
2005-10-31 20:03:11 +00:00
53e32c8e64 *** empty log message *** 2005-10-30 07:38:42 +00:00
10dde610dc 2005-10-30 dust
* Merge sucker into syndie with a rssimport.jsp page.
    * Add getContentType() to EepGet.
    * Make chunked transfer work (better) with EepGet.
    * Do replaceAll("<","&lt;") for logs.
2005-10-30 05:47:55 +00:00
f7c2ae9a3b adjust the exe build filter to only run on linux or windows 2005-10-30 01:07:09 +00:00
ac3b88b9e9 0.6.1.4 2005-10-29 23:22:11 +00:00
60124cdcdc * 2005-10-29 0.6.1.4 released 2005-10-29 23:20:04 +00:00
e7d2281772 2005-10-29 jrandom
* Improved the bandwidth throtting on tunnel participation, especially for
      low bandwidth peers.
    * Improved failure handling in SSU with proactive reestablishment of
      failing idle peers, and rather than shitlisting a peer who failed too
      much, drop the SSU session and allow a new attempt (which, if it fails,
      will cause a shitlisting)
    * Clarify the cause of the shitlist on the profiles page, and include
      bandwidth limiter info at the bottom of the peers page.
2005-10-29 21:43:41 +00:00
52ace2d695 2005-10-29 jrandom
* Improved the bandwidth throtting on tunnel participation, especially for
      low bandwidth peers.
    * Improved failure handling in SSU with proactive reestablishment of
      failing idle peers, and rather than shitlisting a peer who failed too
      much, drop the SSU session and allow a new attempt (which, if it fails,
      will cause a shitlisting)
    * Clarify the cause of the shitlist on the profiles page, and include
      bandwidth limiter info at the bottom of the peers page.
2005-10-29 21:35:24 +00:00
b5a25801b4 2005-10-26 jrandom
* In Syndie, propogate the subject and tags in a reply, and show the parent
      post on the edit page for easy quoting.  (thanks identiguy and CofE!)
    * Streamline some netDb query handling to run outside the jobqueue -
      which means they'll run on the particular SSU thread that handles the
      message.  This should help out heavily loaded netDb peers.
2005-10-28 22:26:47 +00:00
3fc0558810 added ttc.i2p 2005-10-28 01:13:10 +00:00
bd9c6ff463 oops (allow cwin=1 for interactive streams) 2005-10-25 20:19:49 +00:00
a0c822af96 2005-10-25 jrandom
* Defer netDb searches for newly referenced peers until we actually want
      them
    * Ignore netDb references to peers on our shitlist
    * Set the timeout for end to end client messages to the max delay after
      finding the leaseSet, so we don't have as many expired messages floating
      around.
    * Add a floor to the streaming lib window size
    * When we need to send a streaming lib ACK, try to retransmit one of the
      unacked packets instead (with updated ACK/NACK fields, of course).  The
      bandwidth cost of an unnecessary retransmission should be minor as
      compared to both an ACK packet (rounded up to 1KB in the tunnels) and
      the probability of a necessary retransmission.
    * Adjust the streaming lib cwin algorithm to allow growth after a full
      cwin messages if the rtt is trending downwards.  If it is not, use the
      existing algorithm.
    * Increased the maximum rto size in the streaming lib.
    * Load balancing bugfix on end to end messages to distribute across
      tunnels more evenly.
2005-10-25 19:23:17 +00:00
4de302101d 2005-10-24 jrandom
* Defer netDb searches for newly referenced peers until we actually want
      them
    * Ignore netDb references to peers on our shitlist
    * Set the timeout for end to end client messages to the max delay after
      finding the leaseSet, so we don't have as many expired messages floating
      around.
    * Add a floor to the streaming lib window size
    * When we need to send a streaming lib ACK, try to retransmit one of the
      unacked packets instead (with updated ACK/NACK fields, of course).  The
      bandwidth cost of an unnecessary retransmission should be minor as
      compared to both an ACK packet (rounded up to 1KB in the tunnels) and
      the probability of a necessary retransmission.
    * Adjust the streaming lib cwin algorithm to allow growth after a full
      cwin messages if the rtt is trending downwards.  If it is not, use the
      existing algorithm.
    * Increased the maximum rto size in the streaming lib.
    * Load balancing bugfix on end to end messages to distribute across
      tunnels more evenly.
2005-10-25 19:13:53 +00:00
84383c3dab * Enforce delay >= 1 in BlogManager.getUpdateDelay() 2005-10-25 01:24:32 +00:00
a94abb13a4 * Factor archive fetching into a seperate method from the update loop. 2005-10-25 01:03:55 +00:00
ad59ab691b (sun.* is sun specific) 2005-10-24 09:45:00 +00:00
ee9ac31c8b * Instantiate new RemoteArchiveBean for each archive fetched by the updater, to prevent weirdness if the index fetch for archive n+1 fails.
* Add a blocking fetch to EepGetScheduler and RemoteArchiveBean and use them from the updater, to prevent race conditions with multiple archive fetches.
2005-10-24 06:27:56 +00:00
788998307a * Fixed bugs preventing the use of fetchSelectedBulk in the updater 2005-10-24 05:27:22 +00:00
2f8a2879bb no more -Di2p.weakPRNG :) 2005-10-22 18:12:09 +00:00
c7b9525d2c 2005-10-22 jrandom
* Integrated GNU-Crypto's Fortuna PRNG, seeding it off /dev/urandom and
      ./prngseed.rnd (if they exist), and reseeding it with data out of
      various crypto operations (unused bits in a DH exchange, intermediary
      bits in a DSA signature generation, extra bits in an ElGamal decrypt).
      The Fortuna implementation under gnu.crypto.prng has been modified to
      use BouncyCastle's SHA256 and Cryptix's AES (since those are the ones
      I2P uses), and the resulting gnu.crypto.prng.* are therefor available
      under GPL+Classpath's linking exception (~= LGPL).  I2P's SecureRandom
      wrapper around it is, of course, public domain.
2005-10-22 18:06:02 +00:00
3fbc6f41af first pass gcj makefile
I have no idea what I'm doing, this just shows you how to do it ;)
2005-10-21 01:30:13 +00:00
6534c84578 Cought a few places where peers could be taken off the list but not removed for the purposes of rarest-first calculations. 2005-10-21 01:09:31 +00:00
3816c79193 Clean up some possible thread safety issues. 2005-10-21 00:10:13 +00:00
8458e4e0af Don't count peers we can't connect to for rarest-first calculations. 2005-10-20 23:28:32 +00:00
0b9e4967a0 The rarest-first sort is stable, so randomize the wantedPieces list to ensure a healthy swarm when you have mostly seeders. 2005-10-20 22:31:28 +00:00
05e2da7c22 Request pieces in rarest-first order, instead of randomly. 2005-10-20 22:05:51 +00:00
13bda1f6d7 2005-10-20 dust
* Fix bug in ircclient that prevented it to use its own dest (i.e. was
      always shared. (thx for info Ragnarok)
    * Fix crash in Sucker with some bad html.
2005-10-20 19:42:13 +00:00
ea22c73a73 2005-10-20 jrandom
* Workaround a bug in GCJ's Calendar implementation
    * Propery throw an exception in the streaming lib if we try to write to a
      closed stream.  This will hopefully help clear some I2Phex bugs (thanks
      GregorK!)
2005-10-20 08:56:39 +00:00
aa5f1cb18d 2005-10-19 jrandom
* Ported the snark bittorrent client to I2P such that it is compatible
      with i2p-bt and azneti2p.  For usage information, grab an update and run
      "java -jar lib/i2psnark.jar".  It isn't currently multitorrent capable,
      but adding in support would be fairly easy (see PeerAcceptor.java:49)
    * Don't allow leaseSets expiring too far in the future (thanks postman)
2005-10-19 23:23:19 +00:00
ab9c6d59cf remove jbigi.jar from the classpath so we don't mistakenly extract & overwrite the
libjbigi.so.  Yeah, this means that i2psnark uses pure java modPow, but it doesn't do
any real heavy lifting anyway, except a DSA signature every 5-10 minutes.  whoop de do.
2005-10-19 23:18:29 +00:00
76655d01d0 2005-10-19 jrandom
* Ported the snark bittorrent client to I2P such that it is compatible
      with i2p-bt and azneti2p.  For usage information, grab an update and run
      "java -jar lib/i2psnark.jar".  It isn't currently multitorrent capable,
      but adding in support would be fairly easy (see PeerAcceptor.java:49)
    * Don't allow leaseSets expiring too far in the future (thanks postman)
2005-10-19 22:38:45 +00:00
138f7d3b8d This is an I2P port of snark [http://klomp.org/snark], a GPL'ed bittorrent client
The build in tracker has been removed for simplicity.

Example usage:
  java -jar lib/i2psnark.jar myFile.torrent

or, a more verbose setting:
  java -jar lib/i2psnark.jar --eepproxy 127.0.0.1 4444 \
       --i2cp 127.0.0.1 7654 "inbound.length=2 outbound.length=2" \
       --debug 6 myFile.torrent
2005-10-19 22:02:37 +00:00
df4b998a6a 2005-10-19 jrandom
* Bugfix for the auto-update code to handle different usage patterns
    * Decreased the addressbook recheck frequency to once every 12 hours
      instead of hourly.
    * Handle dynamically changing the HMAC size (again, unless your nym is
      toad or jrandom, ignore this ;)
    * Cleaned up some synchronization/locking code
2005-10-19 05:15:12 +00:00
2d70103f88 2005-10-17 dust
* Exchange the remaining URL with EepGet in Sucker.
    * Allow /TOPIC irc command.
2005-10-18 03:14:01 +00:00
731e26e7d6 2005-10-17 jrandom
* Allow an env prop to configure whether we want to use the backwards
      compatible (but not standards compliant) HMAC-MD5, or whether we want
      to use the not-backwards compatible (but standards compliant) one.  No
      one should touch this setting, unless your name is toad or jrandom ;)
    * Added some new dummy facades
    * Be more aggressive on loading up the router.config before building the
      router context
    * Added new hooks for apps to deal with previously undefined I2NP message
      types without having to modify any code.
    * Demo code for using a castrated router for SSU comm (SSUDemo.java)
2005-10-18 00:39:46 +00:00
f9d3b157f0 Emergency patch to I2PTunnelIRCClient, not sure why zero length strings are getting passed to it. Oh, and my first patch\! 2005-10-15 22:55:14 +00:00
12775c416d * Don't filter IRC "MAP" messages (not critical, but it doesn't hurt) 2005-10-14 16:26:31 +00:00
2ca4e63216 * Fixed Syndie's Sucker to not explicitly reference something only found
in sun's JVM (thanks cervantes!)
2005-10-14 16:02:38 +00:00
918d8f851f 2005-10-14 jrandom
* More explicit filter for linux/PPC building (thanks anon!)
2005-10-14 15:05:26 +00:00
20ea680ff0 * 2005-10-14 0.6.1.3 released
2005-10-14  jrandom
    * Added a key explaining peers.jsp a bit (thanks tethra!)
2005-10-14 13:48:04 +00:00
cabb607211 2005-10-13 dust
* Bundled dust's Sucker for pulling RSS/Atom content into SML, which can
      then be injected into Syndie with the Syndie CLI.
    * Bundled ROME and JDOM (BSD and Apache licensed, respectively) for
      RSS/Atom parsing.
2005-10-13  jrandom
    * SSU retransmission choke bugfix (== != !=)
    * Include initial transmissions in the retransmission choke, so that
      if we are already retransmitting a message, we won't send anything
      to that peer other than that message (or ACKs, if necessary)
2005-10-14 02:15:39 +00:00
00a4761b5e 2005-10-13 jrandom
* SSU retransmission choke bugfix (== != !=)
    * Include initial transmissions in the retransmission choke, so that
      if we are already retransmitting a message, we won't send anything
      to that peer other than that message (or ACKs, if necessary)
2005-10-13 09:18:35 +00:00
3516701272 2005-10-12 jrandom
* Choke SSU retransmissions to a peer while there is already a
      retransmission in flight to them.  This currently lets other initial
      transmissions through, since packet loss is often sporadic, but maybe
      this should block initial transmissions as well?
    * Display the retransmission bytes stat on peers.jsp (thanks bar!)
    * Filter QUIT messages in the I2PTunnelIRCClient proxy
2005-10-13 03:54:54 +00:00
c4d785667a 2005-10-11 jrandom
* Piggyback the SSU partial ACKs with data packets.  This is backwards
      compatible.
    * Syndie RSS renderer bugfix, plus now include the full entry instead of
      just the blurb before the cut.
2005-10-11 21:05:14 +00:00
123e0ba589 2005-10-11 jrandom
* Piggyback the SSU explicit ACKs with data packets (partial ACKs aren't
      yet piggybacked).  This is backwards compatible.
    * SML parser cleanup in Syndie
2005-10-11 07:07:39 +00:00
197237aa32 2005-10-10 dust
* Implemented a new I2PTunnelIRCClient which locally filters inbound and
      outbound IRC commands for anonymity and security purposes, removing all
      CTCP messages except ACTION, as well as stripping the hostname from the
      USER message (while leaving the nick and 'full name').  The IRC proxy
      doesn't use this by default, but you can enable it by creating a new
      "IRC proxy" tunnel on the web interface, or by changing the tunnel type
      to "ircclient" in i2ptunnel.config.
2005-10-10  jrandom
    * I2PTunnel http client config cleanup and stats
    * Minor SSU congestion tweaks and stats
    * Reduced netDb exploration period
2005-10-10 23:05:18 +00:00
f30dc2b480 2005-10-10 dust
* Implemented a new I2PTunnelIRCClient which locally filters inbound and
      outbound IRC commands for anonymity and security purposes, removing all
      CTCP messages except ACTION, as well as stripping the hostname from the
      USER message (while leaving the nick and 'full name').  The IRC proxy
      doesn't use this by default, but you can enable it by creating a new
      "IRC proxy" tunnel on the web interface, or by changing the tunnel type
      to "ircclient" in i2ptunnel.config.
2005-10-10  jrandom
    * I2PTunnel http client config cleanup and stats
    * Minor SSU congestion tweaks and stats
    * Reduced netDb exploration period
2005-10-10 22:58:18 +00:00
d4ff34eacb * Treat petname names as being case insensitive. 2005-10-09 22:56:02 +00:00
978769a05d * Finished syndie address auto-import. If syndie.importAddresses=true in syndie.config, then new addresses will automatically be imported to the router's petname db. If importaddresses=true in a user's config file, then new addresses will automatically be imported to that users pername db, when they're logged in. 2005-10-09 20:16:30 +00:00
993c70f600 2005-10-09 jrandom
* Syndie CLI cleanup for simpler CLI posting.  Usage shown with
      java -jar lib/syndie.jar
    * Beginnings of the Syndie logging cleanup
    * Delete corrupt Syndie posts
2005-10-09 11:35:13 +00:00
5dfa9ad7f6 2005-10-09 jrandom
* Now that the streaming lib works reasonably, set the default inactivity
      event to send a 0 byte keepalive payload, rather than disconnecting the
      stream.  This should cut the irc netsplits and help out with other long
      lived streams.  The default timeout is now less than the old timeout as
      well, so the keepalive will be sent before earlier builds fire their
      fatal timeouts.
2005-10-09 05:46:57 +00:00
f282fe3854 * Fix probable NPE. 2005-10-09 04:01:29 +00:00
9297564555 * getName, getLocation -> getByName, getByLocation 2005-10-09 03:47:12 +00:00
e7ad516685 * Beautify PetNameDB API.
* Start of syndie auto address export.
2005-10-09 03:32:34 +00:00
ad574c8504 2005-10-08 jrandom
* Use the OS clock for stat timing, since it doesn't jump around (though
      still use the NTP'ed clock for display)
    * Added new DH stats
2005-10-08 22:05:46 +00:00
38617fe0a7 fixed link (thanks Jazzy) 2005-10-07 23:45:48 +00:00
cdee5b2c31 * 2005-10-07 0.6.1.2 released
2005-10-07  jrandom
    * Include the 1 second bandwidth usage on the console rather than the
      1 minute rate, as the 1 second value doesn't have the 1m/5m quantization
      issues.
2005-10-07 20:19:04 +00:00
7f6e65c76f 2005-10-07 jrandom
* Allow the I2PTunnelHTTPServer to send back the first few packets of an
      HTTP response quicker, and initialize the streaming lib's cwin more
      carefully.
    * Added a small web UI to the new Syndie scheduled updater.  If you log in
      as a user authorized to use the remote archive funtionality, you can
      request remote archives in your address book to be automatically pulled
      down by checking the "scheduled?" checkbox.
2005-10-07 10:22:59 +00:00
4dd628dbc8 2005-10-05 jrandom
* Allow the first few packets in the stream to fill in their IDs during
      handshake (thanks cervantes, Complication, et al!)  This should fix at
      least some of the intermittent HTTP POST issues.
2005-10-05 23:24:33 +00:00
3b5b48ad8a more cleanup (thanks bar) 2005-10-05 03:48:56 +00:00
c4cac3f3f1 we dont need no grammar 2005-10-05 01:45:21 +00:00
4a49e98c31 caps (thanks bar) 2005-10-05 01:11:25 +00:00
0c0e269e72 youspell (thanks bar) 2005-10-05 00:52:27 +00:00
70b6f97abe 2005-10-04 jrandom
* Syndie patch for single user remote archives (thanks nickless_head!)
    * Handle an invalid netDb store (thanks Complication!)
2005-10-04 23:43:05 +00:00
0013677b83 v2mail, not v2mail2 2005-10-04 23:34:19 +00:00
a98ceda64d postmans changes for i2pmail stuff 2005-10-04 23:33:15 +00:00
91ea1d0395 include two specific issues with freenet 2005-10-04 20:18:18 +00:00
4aa65c3bb3 2005-10-04 jrandom
* Further reduction in unnecessary streaming packets.
2005-10-04 07:36:25 +00:00
0a1f59940a 2005-10-03 jrandom
* Properly reject unroutable IP addresses *cough*
2005-10-04 02:05:52 +00:00
f540dc798b * Changed default update delay to twelve hours, and enforced a minimum
delay of one hour.
2005-10-04 00:27:34 +00:00
30f6f26a68 * Implemented a Syndie auto-updater. 2005-10-03 18:55:09 +00:00
ea3bf3ffc8 might as well commit this draft 2005-10-03 06:21:08 +00:00
831d5ac70c not used 2005-10-03 06:20:47 +00:00
1962867ad9 * Actually implement the bit that returns a 304 if archive.txt hasn't changed. 2005-10-03 02:54:34 +00:00
6019a03029 * 2005-10-01 0.6.1.1 released 2005-10-01 19:20:09 +00:00
df5736f571 * Add a notModified flag to Eepget and Eepget status listeners. 2005-10-01 00:57:32 +00:00
9a73c6defe * Support conditional get for remote archive imports. 2005-09-30 23:42:28 +00:00
9dfa87ba47 2005-09-30 jrandom
* Killed three more streaming lib bugs, one of which caused excess packets
      to be transmitted (dupacking dupacks), one that was the root of many of
      the old hung streams (shrinking highest received), and another that was
      releasing data too soon.
2005-09-30 23:12:57 +00:00
934a269753 2005-09-30 jrandom
* Only allow autodetection of our IP address if we haven't received an
      inbound connection in the last two minutes.
    * Increase the default max streaming resends to 8 from 5 (and down from
      the earlier 10)
2005-09-30 20:29:19 +00:00
1c0dfc242b * Fix history.txt formatting. 2005-09-30 17:51:32 +00:00
3bc3e5d47e * Export petnames from syndie to the router's petname db instead of userhosts.txt. 2005-09-30 07:32:46 +00:00
55869af2cc 2005-09-29 jrandom
* Support noreseed.i2p in addition to .i2pnoreseed for disabling automatic
      reseeding - useful on OSes that make it hard to create dot files.
      Thanks Complication (and anon)!
    * Fixed the installer version string (thanks Frontier!)
    * Added cleaner rejection of invalid IP addresses, shitlist those who send
      us invalid IP addresses, verify again that we are not sending invalid IP
      addresses, and log an error if it happens. (Thanks Complication, ptm,
      and adab!)
2005-09-30 07:17:56 +00:00
9f336dd05b Provide a store method on PetNameDB that takes no arguments, and writes the db back to where it was loaded from. 2005-09-30 05:28:31 +00:00
411ca5e6c3 Ignore case when checking network name. 2005-09-30 05:20:41 +00:00
c528e4db03 * 2005-09-29 0.6.1 released
2005-09-29  jrandom
    * Let syndie users modify their metadata.
    * Reseed the router on startup if there aren't enough peer references
      known locally.  This can be disabled by creating the file .i2pnoreseed
      in your home directory, and the existing detection and reseed handling
      on the web interface is unchanged.
2005-09-29 19:24:43 +00:00
848ead7683 * 2005-09-29 0.6.1 released
2005-09-29  jrandom
    * Let syndie users modify their metadata.
    * Reseed the router on startup if there aren't enough peer references
      known locally.  This can be disabled by creating the file .i2pnoreseed
      in your home directory, and the existing detection and reseed handling
      on the web interface is unchanged.
2005-09-29 19:19:22 +00:00
1b8419b9b5 added tracker-fr.i2p 2005-09-29 03:54:30 +00:00
900420719e 2005-09-28 jrandom
* Fix for at least some (all?) of the wrong stream errors in the streaming
      lib
2005-09-28 09:17:54 +00:00
ef7d1ba964 2005-09-27 jrandom
* Properly suggest filenames for attachments in Syndie (thanks all!)
    * Fixed the Syndie authorization scheme for single user vs. multiuser
2005-09-27 22:42:49 +00:00
ab1654c784 ; (1.181) added syncline.i2p, cerebrum.i2p, news.underscore.i2p,
;               onionforum.i2p, frostmirror.i2p, ptm.i2p, gloinsblog.i2p
;               underscore.i2p, mac7.i2p, wiht.i2p, jazzy.i2p, trwcln.i2p
2005-09-27 22:21:05 +00:00
24bad8e4bb 2005-09-26 jrandom
* I2PTunnel bugfix (thanks Complication!)
    * Increase the SSU cwin slower during congestion avoidance (at k/cwin^2
      instead of k/cwin)
    * Limit the number of inbound SSU sessions being built at once (using
      half of the i2np.udp.maxConcurrentEstablish config prop)
    * Don't shitlist on a message send failure alone (unless there aren't any
      common transports).
    * More careful bandwidth bursting
2005-09-27 07:17:40 +00:00
f6d8200bc8 oops (thanks Complication!) 2005-09-27 00:56:49 +00:00
aef33548b3 2005-09-26 jrandom
* Reworded the SSU introductions config section (thanks duck!)
    * Force identity content encoding for I2PTunnel httpserver requests
      (thanks redzara!)
    * Further x-i2p-gzip bugfixes for the end of streams
    * Reduce the minimum bandwidth limits to 3KBps steady and burst (though
      I2P's performance at 3KBps is another issue)
    * Cleaned up some streaming lib structures
2005-09-26 23:45:52 +00:00
56ecdcce82 2005-09-25 jrandom
* Allow reseeding on the console if the netDb knows less than 30 peers,
      rather than less than 10 (without internet connectivity, we keep the
      last 15 router references)
    * Reenable the x-i2p-gzip HTTP processing by default, flushing the stream
      more aggressively.
    * Show the status that used to be called "ERR-Reject" as "OK (NAT)"
    * Reduced the default maximum number of streaming lib resends of a packet
      (10 retransmits is a bit much with a reasonable RTO)
2005-09-25 23:52:58 +00:00
b9b59ff95f 2005-09-25 Complication
* Better i2paddresshelper handling in the I2PTunnel httpclient, plus a new
      conflict resolution page if the i2paddresshelper parameter differs from
      an existing name to destination mapping.
2005-09-25  jrandom
    * Fix a long standing streaming lib bug (in the inactivity detection code)
    * Improved handling of initial streaming lib packet retransmissions to
      kill the "lost first packet" bug (where a page shows up with the first
      few KB missing)
    * Add support for initial window sizes greater than 1 - useful for
      eepsites to transmit e.g. 4 packets full of data along with the initial
      ACK, thereby cutting down on the rtt latency.  The congestion window
      size can and does still shrink down to 1 packet though.
    * Adjusted the streaming lib retransmission calculation algorithm to be
      more TCP-like.
2005-09-25 09:28:59 +00:00
aa9dd3e5c6 mention syndie bug stuff (good idea jnymo) 2005-09-24 04:08:42 +00:00
30bd659149 2005-09-21 redzara
* Use ISO-8859-1 for the susidns xml
2005-09-21 23:01:00 +00:00
3286ca49c8 2005-09-21 susi
* Bugfix in susidns for deleting entries
2005-09-21  jrandom
    * Add support for HTTP POST to EepGet
    * Use HTTP POST for syndie bulk fetches, since there's a lot of data to
      put in that URL.
2005-09-21 06:43:04 +00:00
7700d12178 damn thee, syntax 2005-09-20 03:43:57 +00:00
557b7e3f2e only build exe files on ant dist or ant installer 2005-09-20 03:38:14 +00:00
3e1e9146e1 don't build the exe files on x86_64 or osx 2005-09-20 03:17:06 +00:00
40d8d1aac1 * Made MetaNamingService the default naming service. 2005-09-19 00:56:47 +00:00
1457b8efba include the lib64 wrapper (thanks mule) 2005-09-19 00:36:59 +00:00
3821e80ac8 2005-09-18 jrandom
* Added support for pure 64bit linux with jbigi and the java service
      wrapper (no need for jcpuid if we're on os.arch=amd64).  Thanks mule
      et al for help testing!
    * UI cleanup in Syndie (thanks gloin and bar!)
2005-09-18 23:08:16 +00:00
d40bb459ea * Get the PetNameDB for the PetNameNamingService from the router context. 2005-09-18 22:36:10 +00:00
edf04f07c9 * Implemented a MetaNamingService. 2005-09-18 08:50:56 +00:00
2bdea23986 * Updated history.txt. 2005-09-18 05:41:46 +00:00
6be0c4b694 * Moved PetName and PetNameDB to core.
* Implement PetNameNamingService.
2005-09-18 05:28:51 +00:00
2a272f465c * 2005-09-17 0.6.0.6 released
2005-09-17  jrandom
    * Clean up syndie a bit more and bundle a default introductory post with
      both new installs and updates.
    * Typo fixes on the console (thanks bar!)
2005-09-18 01:29:58 +00:00
a8ecd32b45 2005-09-17 jrandom
* Updated the bandwidth limiter to use two tiers of bandwidth - our normal
      steady state rate, plus a new limit on how fast we transfer when
      bursting.  This is different from the old "burst as fast as possible
      until we're out of tokens" policy, and should help those with congested
      networks.  See /config.jsp to manage this rate.
    * Bugfixes in Syndie to handle missing cache files (no data was lost, the
      old posts just didn't show up).
    * Log properly in EepPost
2005-09-17 23:01:44 +00:00
20c42a175d 2005-09-17 jrandom
* Bugfixes in Syndie to handle missing cache files (no data was lost, the
      old posts just didn't show up).
    * Log properly in EepPost
2005-09-17 20:08:25 +00:00
d6c3ffde87 2005-09-17 jrandom
* Added the natively compiled jbigi and patched java service wrapper for
      OS X.  Thanks Bill Dorsey for letting me use your machine!
    * Don't build i2p.exe or i2pinstall.exe when run on OS X machines, as we
      don't bundle the binutils necessary (and there'd be a naming conflict
      if we did).
    * Added 'single user' functionality to syndie - if the single user
      checkbox on the admin page is checked, all users are allowed to control
      the instance and sync up with remote syndie nodes.
    * Temporarily disable the x-i2p-gzip in i2ptunnel until it is more closely
      debugged.
2005-09-17 07:31:48 +00:00
177e0ae6a3 2005-09-16 jrandom
* Reject unroutable IPs in SSU like we do for the TCP transport (unless
      you have i2np.udp.allowLocal=true defined - useful for private nets)
2005-09-16 21:24:42 +00:00
dab1b4d256 2005-09-16 jrandom
* Adjust I2PTunnelHTTPServer so it can be used for outproxy operators
      (just specify the spoofed host as an empty string), allowing them to
      honor x-i2p-gzip encoding.
    * Let windows users build the exes too (thanks bar and redzara!)
    * Allow I2PTunnel httpserver operators to disable gzip compression on
      individual tunnels with the i2ptunnel.gzip=false client option
      (good idea susi!)
2005-09-16 18:28:26 +00:00
3aba12631b use the logger, not stdout/stderr 2005-09-16 06:58:55 +00:00
cfee6430d4 xml 2005-09-16 04:54:48 +00:00
6b96df1cec runplain.sh, not startRouter.sh 2005-09-16 04:50:28 +00:00
deecfa5047 no message 2005-09-16 04:40:07 +00:00
6ca3f01038 launch4j 2005-09-16 04:34:59 +00:00
d89f589f2b 2005-09-16 jrandom
* Added the i2p.exe and i2pinstall.exe for windows users, using launch4j.
    * Added runplain.sh for *nix/osx users having problems using the java
      service wrapper (called from the install dir as: sh runplain.sh)
    * Bundle susidns and syndie, with links on the top nav
    * Have I2PTunnelHTTPClient and I2PTunnelHTTPServer use the x-i2p-gzip
      content-encoding (if offered), reducing the payload size before it
      reaches the streaming lib.  The existing compression is at the i2cp
      level, so we've been packetizing 4KB of uncompressed data and then
      compressing those messages, rather than compressing and then packetizing
      4KB of compressed data.  This should reduce the number of round trips
      to fetch web pages substantially.
    * Adjust the startup and timing of the addressbook so that susidns always
      has config to work off, and expose a method for susidns to tell it to
      reload its config and rerun.
2005-09-16 04:12:24 +00:00
8c1895e04f imported fixed susidns 2005-09-16 04:04:40 +00:00
c3d0132a98 test cvs again... 2005-09-15 05:57:28 +00:00
d955279d17 minor news (and test cvs...) 2005-09-15 05:56:10 +00:00
76266dce0d 2005-09-15 jrandom
* Error handling for failed intro packets (thanks red.hand!)
    * More carefully verify intro addresses
2005-09-15 05:39:31 +00:00
5694206b35 2005-09-13 jrandom
* More careful error handling with introductions (thanks dust!)
    * Fix the forceIntroducers checkbox on config.jsp (thanks Complication!)
    * Hide the shitlist on the summary so it doesn't confuse new users.
2005-09-13 23:02:35 +00:00
4293a18726 2005-09-12 comwiz
* Migrated the router tests to junit
2005-09-13 09:06:07 +00:00
9865af4174 2005-09-12 jrandom
* Removed guaranteed delivery mode entirely (so existing i2phex clients
      using it can get the benefits of mode=best_effort).  Guaranteed delivery
      is offered at the streaming lib level.
    * Improve the peer selection code for peer testing, as everyone now
      supports tests.
    * Give the watchdog its fangs - if it detects obscene job lag or if
      clients have been unable to get a leaseSet for more than 5 minutes,
      restart the router.  This was disabled a year ago due to spurious
      restarts, and can be disabled by "watchdog.haltOnHang=false", but the
      cause of the spurious restarts should be gone.
2005-09-13 03:32:29 +00:00
c8c109093d 2005-09-12 jrandom
* Bugfix for skewed store which could kill a UDP thread (causing complete
      comm failure and eventual OOM)
2005-09-13 01:12:43 +00:00
b5784d6025 2005-09-12 jrandom
* More aggressively publish updated routerInfo.
    * Expose the flag to force SSU introductions on the router console
    * Don't give people the option to disable SNTP time sync, at least not
      through the router console, because there is no reason to disable it.
      No, not even if your OS is "ntp synced", because chances are, its not.
2005-09-13 00:11:56 +00:00
31bdb8909a tino.i2p and fproxy.tino.i2p 2005-09-12 22:40:17 +00:00
ee921c22ae use the low level rates (thanks bar / complication) 2005-09-12 02:58:13 +00:00
172ffd0434 use the OS time, since it doesn't skew as much (especially on startup) 2005-09-11 04:37:15 +00:00
d9b4406c09 2005-09-10 jrandom
* Test the router's reachability earlier and more aggressively
    * Use the low level bandwidth limiter's rates for the router console, and
      if the router has net.i2p.router.transport.FIFOBandwidthLimiter=INFO in
      the logger config, keep track of the 1 second transfer rates as the stat
      'bw.sendBps1s' and 'bw.recvBps1s', allowing closer monitoring of burst
      behavior.
2005-09-11 03:22:51 +00:00
8ac0e85df4 updated to hq.postman.i2p 2005-09-11 01:40:15 +00:00
249ccd5e3c now that its all implemented... 2005-09-10 23:18:41 +00:00
727d76d43e deal with posts containing no tags by using the implicit tag "[none]" (thanks ardvark!) 2005-09-10 06:07:25 +00:00
44770b7c07 2005-09-09 jrandom
* Added preliminary support for NAT hole punching through SSU introducers
    * Honor peer test results from peers that we have an SSU session with if
      those sessions are idle for 3 minutes or more.
2005-09-10 04:30:36 +00:00
b5d571c75f 2005-09-09 cervantes
* New build due to change in build number :P (thanks ugha!)
2005-09-10 01:13:49 +00:00
da56d83716 First pass at a new naming system. Probably the last as well. So sad :). 2005-09-09 19:38:43 +00:00
f777e213ce search.i2p 2005-09-08 05:08:33 +00:00
79906f5a7d added search.i2p 2005-09-08 05:01:01 +00:00
54074e76b5 2005-09-07 BarkerJr
* HTML cleanup for the router console (thanks!)
2005-09-07  jrandom
    * Lay the foundation for 'client routers' - the ability for peers to opt
      out of participating in tunnels entirely due to firewall/NAT issues.
      Individual routers have control over where those peers are used in
      tunnels - in outbound or inbound, exploratory or client tunnels, or
      none at all.  The defaults with this build are to simply act as before -
      placing everyone as potential participants in any tunnel.
    * Another part of the foundation includes the option for netDb
      participants to refuse to answer queries regarding peers who are marked
      as unreachable, though this too is disabled by default (meaning the
      routerInfo is retrievable from the netDb).
2005-09-07 22:31:11 +00:00
c2ea8db683 Look for names in privatehosts.txt as well as userhosts.txt and hosts.txt. 2005-09-07 02:07:41 +00:00
744671a518 Adjusted wording on the bandwidth limiter controls to reflect new router defaults 2005-09-07 01:13:56 +00:00
7f5b127bbc fix0rz. 2005-09-06 20:45:21 +00:00
89eff0c628 tyop 2005-09-06 20:35:28 +00:00
177aeebb1c stuff 2005-09-06 20:15:55 +00:00
e0e6bde4a5 throw css around like mad (very minimal stylesheet in place) 2005-09-06 20:05:09 +00:00
e6b145716f allow publishing to a remote archive automatically when posting (optionally) with 0 additional clicks
allow transparently attaching any 'public' pet names in your addressbook to a blog post (with a checkbox)
2005-09-06 03:03:55 +00:00
f958342704 admin page - no more editing config props manually (w3wt) 2005-09-05 20:53:25 +00:00
5a1f738505 2005-09-05 jrandom
* Expose the HTTP headers to EepGet status listeners
    * Handle DSA key failures properly (if the signature is not invertable, it
      is obviously invalid)
2005-09-05 19:29:55 +00:00
8147cdf40c 2005-09-05 jrandom
* Expose the HTTP headers to EepGet status listeners
    * Handle DSA key failures properly (if the signature is not invertable, it
      is obviously invalid)
also, syndie now properly detects whether the remote archive can send a filtered export.zip
by examining the HTTP headers for X-Syndie-Export-Capable: true.  If the remote archive
does not set that header (and neither freesites, nor apache or anything other than the ArchiveServlet will),
it uses individual HTTP requests for individual blog posts and metadata fetches.
2005-09-05 19:27:08 +00:00
6afc64ac39 deal with locations that have : in them (aka http://glog.i2p/archive/archive.txt) 2005-09-05 17:09:19 +00:00
61b8e3598b added rss2.0 support via rss.jsp
rss.jsp can in turn receive all the filters that index.jsp can - e.g. ?blog=blah or ?selector=group://foo,
and by default returns the latest 10 values (overridden with ?wanted=15).  If you want it to pull
with a user's blog's preferences (filters, groups, etc), you can specify ?login=user&password=password
2005-09-05 05:33:33 +00:00
3bb445ff40 better filtering/ignoring
ui improvements (per isamoor's suggestions)
more petname integration
2005-09-05 01:26:19 +00:00
59a8037599 allow exporting eepsite destinations from the syndie database into userhosts.txt (so the eepproxy can get it) 2005-09-05 00:00:11 +00:00
09cb5fad59 allow you to bookmark syndie archives and later recall those bookmarks on the remote page 2005-09-04 22:43:22 +00:00
ee8e45ecf7 allow web based control of who gets to access remote repositories.
if the prop "syndie.remotePassword" is set, users can enter it while viewing their metadata
2005-09-04 21:51:17 +00:00
339868838d thanks BarkerJr :) 2005-09-04 20:26:42 +00:00
c5579fa349 (the filtered blogs may be out of order) 2005-09-04 19:33:00 +00:00
d4a859547c 2005-09-04 jrandom
* Don't persist peer profiles until we are shutting down, as the
      persistence process gobbles RAM and wall time.
    * Bugfix to allow you to check/uncheck the sharedClient setting on the
      I2PTunnel web interface.
    * Be more careful when expiring a failed tunnel message fragment so we
      don't drop the data while attempting to read it.
2005-09-04 19:15:49 +00:00
779aa240d2 added glog.i2p 2005-09-03 04:07:50 +00:00
9aaad00383 0.6.0.5 2005-09-02 19:10:05 +00:00
6422f7ef78 2005-09-02 jrandom
* Don't refuse to send a netDb store if the targetted peer has failed a
      bit (the value was an arbitrary amount).
    * Logging changes
2005-09-02 18:34:14 +00:00
3e51584b3c 0.6.0.4 2005-09-01 20:27:35 +00:00
4ff8a53084 2005-09-01 jrandom
* Don't send out a netDb store of a router if it is more than a few hours
      old, even if someone asked us for it.
2005-09-01 06:55:00 +00:00
ccb73437c4 2005-08-31 jrandom
* Don't publish leaseSets to the netDb if they will never be looked for -
      namely, if they are for destinations that only establish outbound
      streams.  I2PTunnel's 'client' and 'httpclient' proxies have been
      modified to tell the router that it doesn't need to publish their
      leaseSet (by setting the I2CP config option 'i2cp.dontPublishLeaseSet'
      to 'true').
    * Don't publish the top 10 peer rankings of each router in the netdb, as
      it isn't being watched right now.
2005-09-01 00:26:20 +00:00
b43114f61b 2005-08-31 jrandom
* Don't publish leaseSets to the netDb if they will never be looked for -
      namely, if they are for destinations that only establish outbound
      streams.  I2PTunnel's 'client' and 'httpclient' proxies have been
      modified to tell the router that it doesn't need to publish their
      leaseSet (by setting the I2CP config option 'i2cp.dontPublishLeaseSet'
      to 'true').
    * Don't publish the top 10 peer rankings of each router in the netdb, as
      it isn't being watched right now.
2005-09-01 00:20:16 +00:00
9bd87ab511 make it work with any host charset or content charset 2005-08-31 09:50:23 +00:00
b6ea55f7ef more error handling (thanks frosk) 2005-08-30 02:39:37 +00:00
5f18cec97d 2005-08-29 jrandom
* Added the new test Floodfill netDb
2005-08-30 02:04:17 +00:00
3ba921ec0e 2005-08-29 jrandom
* Added the new test Floodfill netDb
2005-08-30 01:59:11 +00:00
e313da254c 2005-08-27 jrandom
* Minor logging and optimization tweaks in the router and SDK
    * Use ISO-8859-1 in the XML files (thanks redzara!)
    * The consolePassword config property can now be used to bypass the router
      console's nonce checking, allowing CLI restarts
2005-08-27 22:46:22 +00:00
8660cf0d74 2005-08-27 jrandom
* Minor logging and optimization tweaks in the router and SDK
    * Use ISO-8859-1 in the XML files (thanks redzara!)
    * The consolePassword config property can now be used to bypass the router
      console's nonce checking, allowing CLI restarts
2005-08-27 22:15:35 +00:00
e0bfdff152 TZ asap 2005-08-25 21:08:13 +00:00
c27aed3603 fix up the entryId calc 2005-08-25 21:07:18 +00:00
cdc6002f0e no message 2005-08-25 21:01:15 +00:00
4cf3d9c1a2 HTTP file upload (rfc 1867) helper 2005-08-25 21:00:09 +00:00
0473e08e21 remote w0rks 2005-08-25 20:59:46 +00:00
346faa3de2 2005-08-24 jrandom
* Catch errors with corrupt tunnel messages more gracefully (no need to
      kill the thread and cause an OOM...)
    * Don't skip shitlisted peers for netDb store messages, as they aren't
      necessarily shitlisted by other people (though they probably are).
    * Adjust the netDb store per-peer timeout based on each particular peer's
      profile (timeout = 4x their average netDb store response time)
    * Don't republish leaseSets to *failed* peers - send them to peers who
      replied but just didn't know the value.
    * Set a 5 second timeout on the I2PTunnelHTTPServer reading the client's
      HTTP headers, rather than blocking indefinitely.  HTTP headers should be
      sent entirely within the first streaming packet anyway, so this won't be
      a problem.
    * Don't use the I2PTunnel*Server handler thread pool by default, as it may
      prevent any clients from accessing the server if the handlers get
      blocked by the streaming lib or other issues.
    * Don't overwrite a known status (OK/ERR-Reject/ERR-SymmetricNAT) with
      Unknown.
2005-08-24 22:55:25 +00:00
5ec6dca64d 2005-08-23 jrandom
* Removed the concept of "no bandwidth limit" - if none is specified, its
      16KBps in/out.
    * Include ack packets in the per-peer cwin throttle (they were part of the
      bandwidth limit though).
    * Tweak the SSU cwin operation to get more accurrate estimates under
      congestions.
    * SSU improvements to resend more efficiently.
    * Added a basic scheduler to eepget to fetch multiple files sequentially.
2005-08-23 22:43:51 +00:00
1a6b49cfb8 2005-08-23 jrandom
* Removed the concept of "no bandwidth limit" - if none is specified, its
      16KBps in/out.
    * Include ack packets in the per-peer cwin throttle (they were part of the
      bandwidth limit though).
    * Tweak the SSU cwin operation to get more accurrate estimates under
      congestions.
    * SSU improvements to resend more efficiently.
    * Added a basic scheduler to eepget to fetch multiple files sequentially.
2005-08-23 21:25:49 +00:00
c7b75df390 Added announcement about the new Irc2P server at irc.freshcoffee.i2p 2005-08-22 13:03:11 +00:00
f97c09291b 0.6.0.3 2005-08-21 19:21:50 +00:00
8f2a5b403c * 2005-08-21 0.6.0.3 released
2005-08-21  jrandom
    * If we already have an established SSU session with the Charlie helping
      test us, cancel the test with the status of "unknown".
2005-08-21 18:39:05 +00:00
ea41a90eae sanity checking 2005-08-21 18:37:57 +00:00
b1dd29e64d added syndie.i2p and syndiemedia.i2p 2005-08-21 18:33:58 +00:00
46e47c47ac ewps 2005-08-21 18:19:22 +00:00
b7bf431f0d [these are not the droids you are looking for] 2005-08-21 18:08:05 +00:00
7f432122d9 added irc.freshcoffee.i2p (new IRC server on the irc2p network) 2005-08-20 01:19:51 +00:00
e7be8c6097 Added references to the new irc2p server: irc.freshcoffee.i2p 2005-08-20 01:18:38 +00:00
adf56a16e1 2005-08-17 jrandom
* Revise the SSU peer testing protocol so that Bob verifies Charlie's
      viability before agreeing to Alice's request.  This doesn't work with
      older SSU peer test builds, but is backwards compatible (older nodes
      won't ask newer nodes to participate in tests, and newer nodes won't
      ask older nodes to either).
2005-08-17 20:16:27 +00:00
11204b8a2b 2005-08-17 jrandom
* Revise the SSU peer testing protocol so that Bob verifies Charlie's
      viability before agreeing to Alice's request.  This doesn't work with
      older SSU peer test builds, but is backwards compatible (older nodes
      won't ask newer nodes to participate in tests, and newer nodes won't
      ask older nodes to either).
2005-08-17 20:05:01 +00:00
cade27dceb added surrender.adab.i2p 2005-08-17 00:42:15 +00:00
5597d28e59 Removing references to irc.duck.i2p, adding references to irc.arcturus.i2p, and replacing current ircProxy default destination string with "irc.postman.i2p,irc.arcturus.i2p" 2005-08-16 09:35:58 +00:00
0502fec432 added terror.i2p 2005-08-15 18:44:04 +00:00
a6714fc2de Adding irc.arcturus.i2p, a new server for the soon-to-be Irc2P network 2005-08-14 15:52:12 +00:00
1219dadbd5 2005-08-12 jrandom
* Keep detailed stats on the peer testing, publishing the results in the
      netDb.
    * Don't overwrite the status with 'unknown' unless we haven't had a valid
      status in a while.
    * Make sure to avoid shitlisted peers for peer testing.
    * When we get an unknown result to a peer test, try again soon afterwards.
    * When a peer tells us that our address is different from what we expect,
      if we've done a recent peer test with a result of OK, fire off a peer
      test to make sure our IP/port is still valid.  If our test is old or the
      result was not OK, accept their suggestion, but queue up a peer test for
      later.
    * Don't try to do a netDb store to a shitlisted peer, and adjust the way
      we monitor netDb store progress (to clear up the high netDb.storePeers
      stat)
2005-08-12 23:54:46 +00:00
77b995f5ed 2005-08-10 jrandom
* Deployed the peer testing implementation to be run every few minutes on
      each router, as well as any time the user requests a test manually.  The
      tests do not reconfigure the ports at the moment, merely determine under
      what conditions the local router is reachable.  The status shown in the
      top left will be "ERR-SymmetricNAT" if the user's IP and port show up
      differently for different peers, "ERR-Reject" if the router cannot
      receive unsolicited packets or the peer helping test could not find a
      collaborator, "Unknown" if the test has not been run or the test
      participants were unreachable, or "OK" if the router can receive
      unsolicited connections and those connections use the same IP and port.
2005-08-10 23:55:40 +00:00
2f53b9ff68 0.6.0.2 2005-08-09 18:55:31 +00:00
d84d045849 deal with full windows without *cough* NPEs
(how many times can I cvs rtag -F before going crazy?)
2005-08-08 21:20:08 +00:00
d8e72dfe48 foo 2005-08-08 20:49:17 +00:00
88b9f7a74c "ERROR [eive on 8887] uter.transport.udp.UDPReceiver: Dropping inbound packet with 1 queued for 1912 packet handlers: Handlers: 3 handler 0 state: 2 handler 1 state: 2 handler 2 state: 2"
state = 2 means all three handlers are blocking on udpReceiver.receive())
this can legitimately happen if the bandwidth limiter or router throttle chokes the receive for >= 1s.
2005-08-08 20:42:13 +00:00
6a19501214 2005-08-08 jrandom
* Add a configurable throttle to the number of concurrent outbound SSU
      connection negotiations (via i2np.udp.maxConcurrentEstablish=4).  This
      may help those with slow connections to get integrated at the start.
    * Further fixlets to the streaming lib
2005-08-08 20:35:50 +00:00
ba30b56c5f 2005-08-07 Complication
* Display the average clock skew for both SSU and TCP connections
2005-08-07  jrandom
    * Fixed the long standing streaming lib bug where we could lose the first
      packet on retransmission.
    * Avoid an NPE when a message expires on the SSU queue.
    * Adjust the streaming lib's window growth factor with an additional
      Vegas-esque congestion detection algorithm.
    * Removed an unnecessary SSU session drop
    * Reduced the MTU (until we get a working PMTU lib)
    * Deferr tunnel acceptance until we know how to reach the next hop,
      rejecting it if we can't find them in time.
    * If our netDb store of our leaseSet fails, give it a few seconds before
      republishing.
2005-08-07 19:31:58 +00:00
a375e4b2ce added more postman services (w3wt) 2005-08-07 19:27:22 +00:00
44fd71e17f added i2p-bt.postman.i2p 2005-08-05 21:20:30 +00:00
b41c378de9 Removed reference and link to Invisiblechat/IIP from the router console greeting page (because IIP's dead, Jim... how many times does it need to be said?) and added irc.postman.i2p. 2005-08-05 19:20:52 +00:00
4ce6b308b3 * 2005-08-03 0.6.0.1 released
2005-08-03  jrandom
    * Backed out an inadvertant change to the netDb store redundancy factor.
    * Verify tunnel participant caching.
    * Logging cleanup
2005-08-03 18:58:12 +00:00
72c6e7d1c5 2005-08-01 duck
* Update IzPack to 3.7.2 (build 2005.04.22). This fixes bug #82.
2005-08-02 03:26:51 +00:00
7ca3f22e77 2005-08-01 duck
* Update IzPack to 3.7.2 (build 2005.04.22)
      This fixes bug #82
2005-08-02 03:25:51 +00:00
59790dafef 2005-08-01 duck
* Fix an addressbook NPE when a new hostname from the master addressbook
      didn't exist in the router addressbook.
    * Fix an addressbook bug which caused subscriptions not to be parsed at
      all. (Oops!)
2005-08-01 13:35:11 +00:00
7227cae6ef 2005-08-01 duck
* Fix an addressbook NPE when a new hostname from the master addressbook
      didn't exist in the router addressbook.
    * Fix an addressbook bug which caused subscriptions not to be parsed at
      all. (Oops!)
2005-08-01 13:34:10 +00:00
03bba51c1e * Fixed some issues with the merge logic that caused addressbooks to be written to disk even when unmodified.
* Fixed a bug that could result in a downloaded remote addressbook not being deleted, halting the update process.
2005-08-01 03:32:37 +00:00
0637050cbc No real reason for eepget to retry, addressbook will try again in an hour, and it makes updates take an absurdly long time. 2005-08-01 00:10:54 +00:00
7f58a68c5a Whoops! Forgot the new build file. I broke cvs! 2005-07-31 22:49:25 +00:00
8120b0397c Move addressbook off URL and on to EepGet. Should no longer leak dns lookups, but now only supports conditional GET with HTTP 1.1. If that's a big problem, it can be fixed in future. 2005-07-31 22:19:10 +00:00
fbe42b7dce Added HTTP 1.1 conditional GET support to EepGet. 2005-07-31 22:17:10 +00:00
def24e34ad 2005-07-31 jrandom
* Adjust the netDb search and store per peer timeouts to match the average
      measured per peer success times, rather than huge fixed values.
    * Optimized and reverified the netDb peer selection / retrieval process
      within the kbuckets.
    * Drop TCP connections that don't have any useful activity in 10 minutes.
    * If i2np.udp.fixedPort=true, never change the externally published port,
      even if we are autodetecting the IP address.
(also includes most of the new peer/NAT testing, but thats not used atm)
2005-07-31 21:35:26 +00:00
593253e6a3 update compilation target 2005-07-31 01:11:12 +00:00
56dd4cb8b5 * added luckypunk.i2p to hosts.txt 2005-07-30 02:27:12 +00:00
10c6f67500 oops 2005-07-28 20:33:27 +00:00
5c1f968afa no message 2005-07-27 20:16:44 +00:00
aaaf437d62 skip properly (DataHelper.read confusion) 2005-07-27 20:15:35 +00:00
a8a866b5f6 * 2005-07-27 0.6 released
2005-07-27  jrandom
    * Enabled SSU as the default top priority transport, adjusting the
      config.jsp page accordingly.
    * Add verification fields to the SSU and TCP connection negotiation (not
      compatible with previous builds)
    * Enable the backwards incompatible tunnel crypto change as documented in
      tunnel-alt.html (have each hop encrypt the received IV before using it,
      then encrypt it again before sending it on)
    * Disable the I2CP encryption, leaving in place the end to end garlic
      encryption (another backwards incompatible change)
    * Adjust the protocol versions on the TCP and SSU transports so that they
      won't talk to older routers.
    * Fix up the config stats handling again
    * Fix a rare off-by-one in the SSU fragmentation
    * Reduce some unnecessary netDb resending by inluding the peers queried
      successfully in the store redundancy count.
2005-07-27 19:03:43 +00:00
aeb8f02269 2005-07-22 jrandom
* Use the small thread pool for I2PTunnelHTTPServer (already used for
      I2PTunnelServer)
    * Minor memory churn reduction in I2CP
    * Small stats update
2005-07-23 00:15:56 +00:00
45767360ab 2005-07-21 jrandom
* Fix in the SDK for a bug which would manifest itself as misrouted
      streaming packets when a destination has many concurrent streaming
      connections (thanks duck!)
    * No more "Graceful shutdown in -18140121441141s"
2005-07-21 22:37:14 +00:00
3563aa2e4d 2005-07-20 jrandom
* Allow the user to specify an external port # for SSU even if the external
      host isn't specified (thanks duck!)
2005-07-20 19:24:47 +00:00
843d5b625a 2005-07-19 jrandom
* Further preparation for removing I2CP crypto
    * Added some validation to the DH key agreement (thanks $anon)
    * Validate tunnel data message expirations (though not really a problem,
      since tunnels expire)
    * Minor PRNG threading cleanup
2005-07-19 21:00:25 +00:00
0f8ede85ca 2005-07-15 cervantes
* Added workaround for an odd win32 bug in the stats configuration
	  console page which meant only the first checkbox selection was saved.

2005-07-15  Romster
	* Added per group selection toggles in the stats configuration console
	  page.
2005-07-16 12:52:35 +00:00
9267d7cae2 more n3ws 2005-07-13 21:59:01 +00:00
dade5a981b 2005-07-13 jrandom
* Fixed a recently injected bug in the multitransport bidding which had
      allowed an essentially arbitrary choice of transports, rather than the
      properly ordered choice.
(getLatency() != getLatencyMs().  duh)
2005-07-13 20:07:31 +00:00
f873cba27e 2005-07-13 jrandom
* Fixed a long standing bug where we weren't properly comparing session
      tags but instead largely depending upon comparing their hashCode,
      causing intermittent decryption errors.
2005-07-13 18:20:43 +00:00
108dec53a5 * mixing a revert and some logging updates... (crosses fingers) 2005-07-12 22:30:13 +00:00
e9592ed400 2005-07-12 jrandom
* Add some data duplication to avoid a recently injected concurrency problem
      in the session tag manager (thanks redzara and romster).
2005-07-12 21:26:07 +00:00
4c230522a2 typo *ahem* 2005-07-12 03:56:42 +00:00
16bd19c6dc added bash.i2p, stats.i2p 2005-07-11 23:23:22 +00:00
b4b6d49d34 ssu testing 2005-07-11 23:16:41 +00:00
9d5f16a889 2005-07-11 jrandom
* Reduced the growth factor on the slow start and congestion avoidance for
      the streaming lib.
    * Adjusted some of the I2PTunnelServer threading to use a small pool of
      handlers, rather than launching off new threads which then immediately
      launch off an I2PTunnelRunner instance (which launches 3 more threads..)
    * Don't persist session keys / session tags (not worth it, for now)
    * Added some detection and handling code for duplicate session tags being
      delivered (root cause still not addressed)
    * Make the PRNG's buffer size configurable (via the config property
      "i2p.prng.totalBufferSizeKB=4096")
    * Disable SSU flooding by default (duh)
    * Updates to the StreamSink apps for better throttling tests.
2005-07-11 23:06:23 +00:00
51c492b842 no message 2005-07-09 23:02:19 +00:00
d3380228ac * you mean 3f != 0x3f? [duh]
* minor cleanups
2005-07-09 22:58:22 +00:00
ad47bf5da3 * moved the inbound partial messages to the PeerState itself, reducing lock contention in the InboundMessageFragments and transparently dropping failed messages when we drop old peer states 2005-07-07 22:27:44 +00:00
76e8631e31 included IV tagging info 2005-07-07 21:16:57 +00:00
f688b9112d 2005-07-05
* Use a buffered PRNG, pulling the PRNG data off a larger precalculated
      buffer, rather than the underlying PRNG's (likely small) one, which in
      turn reduces the frequency of recalcing.
    * More tuning to reduce temporary allocation churn
2005-07-05 22:08:56 +00:00
18d3f5d25d 2005-07-04 jrandom
* Within the tunnel, use xor(IV, msg[0:16]) as the flag to detect dups,
      rather than the IV by itself, preventing an attack that would let
      colluding internal adversaries tag a message to determine that they are
      in the same tunnel.  Thanks dvorak for the catch!
    * Drop long inactive profiles on startup and shutdown
    * /configstats.jsp: web interface to pick what stats to log
    * Deliver more session tags to account for wider window sizes
    * Cache some intermediate values in our HMACSHA256 and BC's HMAC
    * Track the client send rate (stream.sendBps and client.sendBpsRaw)
    * UrlLauncher: adjust the browser selection order
    * I2PAppContext: hooks for dummy HMACSHA256 and a weak PRNG
    * StreamSinkClient: add support for sending an unlimited amount of data
    * Migrate the tests out of the default build jars

2005-06-22  Comwiz
    * Migrate the core tests to junit
2005-07-04 20:44:17 +00:00
440cf2c983 2005-03-23 Comwiz
* Phase 1 of the unit test bounty completed. (The router build script was modified not to build the router
 tests because of a broken dependancy on the core tests. This should be fixed in
 phase 3 of the unit test bounty.)
2005-06-23 02:11:04 +00:00
adeb09576a util/PooledRandomSource.java 2005-06-03 20:23:32 +00:00
fd52bcf8cd added archive.i2p, www.fr.i2p, romster.i2p, marshmallow.i2p, openforums.i2p 2005-05-26 04:51:24 +00:00
c2696bba00 2005-05-25 duck
* Fixed PRNG bug (bugzilla #107)
2005-05-25 21:32:38 +00:00
fef9d57483 removed duplicate manveru.i2p 2005-05-10 05:53:18 +00:00
c250692ef0 added bittorrent.i2p - new home for brittanytracker 2005-05-04 05:52:55 +00:00
2a6024e196 end of first round of ssu testing 2005-05-03 00:53:53 +00:00
835662b3c9 2005-05-01 jrandom
* Added a substantial optimization to the AES engine by caching the
      prepared session keys (duh).
2005-05-02 02:35:16 +00:00
6b5b880ab6 * replaced explicit NACKs and numACKs with ACK bitfields for high congestion links
* increased the maximum number of fragments allowed in a message from 31 to 127,
  reducing the maximum fragment size to 8KB and moving around some bits in the fragment
  info.  This is not backwards compatible.
* removed the old (hokey) congestion control description, replacing it with the TCP-esque
  algorithm implemented
note: the code for the ACK bitfields and fragment info changes have not yet been
implemented, so the old version of this document describes whats going on in the live net.
the new bitfields / fragment info should be deployed in the next day or so (hopefully :)
2005-05-01 20:08:08 +00:00
3de23d4206 2005-05-01 jrandom
* Cleaned up the peers page a bit more.
more udp stuff:
* add new config option: i2np.udp.alwaysPreferred=true to adjust the bidding
  so that UDP is picked first, even if a TCP connection exists
* fixed the initial clock skew problem (duh)
* reduced the MTU to 576 (largest nearly-universally-safe, and allows a
  tunnel message in 2 fragments)
* handle some races @ connection establishment (thanks duck!)
* if there are more ACKs than we can send in a packet, reschedule another
  ACK immediately
2005-05-01 17:21:48 +00:00
ea82f2a8cc oops (thanks newkid!) 2005-05-01 01:35:23 +00:00
b5ad7642bc 2005-04-30 jrandom
* Added a small new page to the web console (/peers.jsp) which contains
      the peer connection information.  This will be cleaned up a lot more
      before 0.6 is out, but its a start.
2005-05-01 00:48:15 +00:00
0fbe84e9f0 2005-04-30 jrandom
* Reduced some SimpleTimer churn
* add hooks for per-peer choking in the outbound message queue - if/when a
  peer reaches their cwin, no further messages will enter the 'active' pool
  until there are more bytes available.  other messages waiting (either later
  on in the same priority queue, or in the queues for other priorities) may
  take that slot.
* when we have a message acked, release the acked size to the congestion
  window (duh), rather than waiting for the second to expire and refill the
  capacity.
* send packets in a volley explicitly, waiting until we can allocate the full
  cwin size for that message
2005-04-30 23:26:18 +00:00
8063889d23 udp updates:
* more stats. including per-peer KBps (updated every second)
* improved blocking/timeout situations on the send queue
* added drop simulation hook
* provide logical RTO limits
2005-04-30 03:14:09 +00:00
6e1ac8e173 added elf.i2p, de-ebooks.i2p, i2pchan.i2p, longhorn.i2p 2005-04-29 22:26:12 +00:00
1b0bb5ea19 2005-04-29 jrandom
* Reduce the peer profile stat coallesce overhead by inlining it with the
      reorganize.
    * Limit each transport to at most one address (any transport that requires
      multiple entry points can include those alternatives in the address).
udp stuff:
* change the UDP transport's style from "udp" to "SSUv1"
* keep track of each peer's skew
* properly handle session reestablishment over an existing session, rather
  than requiring both sides to expire first
2005-04-29 06:24:12 +00:00
4ce51261f1 2005-04-28 jrandom
* More fixes for the I2PTunnel "other" interface handling (thanks nelgin!)
    * Add back the code to handle bids from multiple transports (though there
      is still only one transport enabled by default)
    * Adjust the router's queueing of outbound client messages when under
      heavy load by running the preparatory job in the client's I2CP handler
      thread, thereby blocking additional outbound messages when the router is
      hosed.
    * No need to validate or persist a netDb entry if we already have it
And for some udp stuff:
* only bid on what we know (duh)
* reduceed the queue size in the UDPSender itself, so that ACKs go
  through more quickly, leaving the payload messages to queue up in
  the outbound fragment scheduler
* rather than /= 2 on congestion, /= 2/3 (still AIMD, but less drastic)
* adjust the fragment selector so a wsiz throttle won't force extra
  volleys
* mark congestion when it occurs, not after the message has been
  ACKed
* when doing a round robin over the active messages, move on to the
  next after a full volley, not after each packet (causing less "fair"
  performance but better latency)
* reduced the lock contention in the inboundMessageFragments by
  moving the ack and complete queues to the ACKSender and
  MessageReceiver respectively (each of which have their own
  threads)
* prefer new and existing UDP sessions to new TCP sessions, but
  prefer existing TCP sessions to new UDP sessions
2005-04-28 21:54:27 +00:00
6e34d9b73e added amobius.i2p 2005-04-28 02:11:02 +00:00
6e01637400 added google.i2p 2005-04-27 21:30:53 +00:00
9a96798f9f added mrplod.i2p 2005-04-27 03:58:00 +00:00
c9db6f87d1 2005-04-25 smeghead
* Added button to router console for manual update checks.
    * Fixed bug in configupdate.jsp that caused the proxy port to be updated
      every time the form was submitted even if it hadn't changed.
2005-04-26 02:59:23 +00:00
567ce84e1e * randomized the shitlist duration (still with exponential backoff though)
* fail UDP sessions after two consecutive failed messages in different minutes
* honor UDP reconnections
2005-04-25 16:29:48 +00:00
cde7ac7e52 2005-04-24 jrandom
* Added a pool of PRNGs using a different synchronization technique,
      hopefully sufficient to work around IBM's PRNG bugs until we get our
      own Fortuna.
    * In the streaming lib, don't jack up the RTT on NACK, and have the window
      size bound the not-yet-ready messages to the peer, not the unacked
      message count (not sure yet whether this is worthwile).
    * Many additions to the messageHistory log.
    * Handle out of order tunnel fragment delivery (not an issue on the live
      net with TCP, but critical with UDP).
2005-04-24 18:44:59 +00:00
b2f0d17e94 2005-04-24 jrandom
* Added a pool of PRNGs using a different synchronization technique,
      hopefully sufficient to work around IBM's PRNG bugs until we get our
      own Fortuna.
    * In the streaming lib, don't jack up the RTT on NACK, and have the window
      size bound the not-yet-ready messages to the peer, not the unacked
      message count (not sure yet whether this is worthwile).
    * Many additions to the messageHistory log.
    * Handle out of order tunnel fragment delivery (not an issue on the live
      net with TCP, but critical with UDP).
and for udp stuff:
* implemented tcp-esque rto code in the udp transport
* make sure we don't ACK too many messages at once
* transmit fragments in a simple (nonrandom) order so that we can more easily
  adjust timeouts/etc.
* let the active outbound pool grow dynamically if there are outbound slots to
  spare
* use a simple decaying bloom filter at the UDP level to drop duplicate resent
  packets.
2005-04-24 18:42:02 +00:00
dae6be14b7 I removed those dumb platform specific makefiles. They weren't doing what they ought anyway. If there are platform specific issues, someone please tell me and I'll provide support for it here. Or patch it yourself.
And this is the big "Fix the Parser" patch.  It turns the sam_parse function in src/parse.c into something that actually works.  Generating the argument list from an incoming SAM thingy is a bit memory churn-y; perhaps when I have time I'll replace all those strdups with structures that simply track the (start,end) indices.
Oh and also I moved i2p-ping to the new system.  Which required 0 change in code.  All I did was fix the Makefile, and add shared library libtool support.  Anyway, so enjoy folks.  It's rare I'm this productive
- polecat
2005-04-23 03:28:40 +00:00
20cec857d2 signed with the latest 2005-04-21 16:26:46 +00:00
aum
739f694cfe Node shutdown now uses halt() 2005-04-21 03:10:16 +00:00
aum
84779002fb now builds a working Q console 2005-04-20 21:35:05 +00:00
df926fb60d * 2005-04-20 0.5.0.7 released 2005-04-20 20:14:17 +00:00
a2c7c5a516 2005-04-20 jrandom
* In the SDK, we don't actually need to block when we're sending a message
      as BestEffort (and these days, we're always sending BestEffort).
    * Pass out client messages in fewer (larger) steps.
    * Have the InNetMessagePool short circuit dispatch requests.
    * Have the message validator take into account expiration to cut down on
      false positives at high transfer rates.
    * Allow configuration of the probabalistic window size growth rate in the
      streaming lib's slow start and congestion avoidance phases, and default
      them to a more conservative value (2), rather than the previous value
      (1).
    * Reduce the ack delay in the streaming lib to 500ms
    * Honor choke requests in the streaming lib (only affects those getting
      insanely high transfer rates)
    * Let the user specify an interface besides 127.0.0.1 or 0.0.0.0 on the
      I2PTunnel client page (thanks maestro^!)
(plus minor udp tweaks)
2005-04-20 19:15:25 +00:00
aum
1861379d43 needed for QConsole 2005-04-20 18:54:39 +00:00
aum
408a344aae added QConsole 2005-04-20 18:53:08 +00:00
e9c1ed70d0 added sirup.i2p 2005-04-18 23:27:31 +00:00
916dcca2b0 * build with reference to the i2p.jar/mstreaming.jar/i2ptunnel.jar inline (building as necessary)
* removed unnecessary references to i2ptunnel (though i2ptunnelxmlobject still references i2ptunnelxmlwrapper)
2005-04-18 18:47:22 +00:00
aum
31e81bab17 fixed build failures 2005-04-18 18:12:32 +00:00
aum
6a5170c341 oops, forgot to add earlier 2005-04-18 18:05:50 +00:00
aum
42bff8093c removed obsolete ref to MiniHttpRequestHandlerBase, changed to MiniHttpRequestHandler 2005-04-18 18:04:12 +00:00
aum
d1df94f284 added needed html template files 2005-04-18 18:02:07 +00:00
aum
9cf1744291 restored images in binary mode 2005-04-18 17:24:33 +00:00
aum
f0545c8c9a removed images which were not checked in as binary 2005-04-18 17:22:27 +00:00
aum
ef9ed87d30 binary mode this time 2005-04-18 17:20:10 +00:00
aum
58ffd92a34 dammit, forgot binary mode 2005-04-18 17:19:25 +00:00
aum
418facc7e0 Added apps/q - the Q distributed file store framework, by aum 2005-04-18 17:03:21 +00:00
7f3c953e14 2005-04-17 sirup
* Added the possibility for i2ptunnel client and httpclient instances to
      have their own i2p session (and hence, destination and tunnels).  By
      default, tunnels are shared, but that can be changed on the web
      interface or with the sharedClient config option in i2ptunnel.config.
2005-04-17  jrandom
    * Marked the net.i2p.i2ptunnel.TunnelManager as deprecated.  Anyone use
      this?  If not, I want to drop it (lots of tiny details with lots of
      duplicated semantics).
2005-04-18 02:07:57 +00:00
addab1fa2a 2005-04-17 zzz
* Added new user-editable eepproxy error page templates.
2005-04-17  jrandom
    * Revamp the tunnel building throttles, fixing a situation where the
      rebuild may not recover, and defaulting it to unthrottled (users with
      slow CPUs may want to set "router.tunnel.shouldThrottle=true" in their
      advanced router config)
2005-04-17 23:23:20 +00:00
39343ce957 2005-04-16 jrandom
* Migrated to Bouncycastle's SHA256 and HMAC implementations for efficiency
2005-04-17 01:04:06 +00:00
7389cec78f 2005-04-16 jrandom
* Migrated to Bouncycastle's SHA256 and HMAC implementations for efficiency
(also lots of udp fixes)
2005-04-17 00:59:48 +00:00
9e5fe7d2b6 * fixed some stupid threading issues in the packet handler (duh)
* use the new raw i2np message format (the previous corruptions were due to above)
* add a new test component (UDPFlooder) which floods all peers at the rate desired
* packet munging fix for highly fragmented messages
* include basic slow start code
* fixed the UDP peer rate refilling
* cleaned up some nextSend scheduling
2005-04-16 15:18:09 +00:00
a7dfaee5ac added connelly.i2p 2005-04-13 02:29:59 +00:00
7beb92b1cc First pass of the UDP transport. No where near ready for use, but it does
the basics (negotiate a session and send I2NP messages back and forth).  Lots,
lots more left.
2005-04-12 16:48:43 +00:00
5b56d22da9 2005-04-12 jrandom
* Make sure we don't get cached updates (thanks smeghead!)
    * Clear out the callback for the TestJob after it passes (only affects the
      job timing accounting)
2005-04-12 15:22:11 +00:00
e6b343070a removed copy/paste error 2005-04-09 23:15:53 +00:00
8496b88518 2005-04-08 smeghead
* Added NativeBigInteger benchmark to scripts/i2pbench.sh.
2005-04-09 03:16:05 +00:00
aa542b7876 for implementation simplicity, include fragment size in the SessionConfirmed packets 2005-04-08 23:20:45 +00:00
3f7d46378b * specify exactly what gets in the DSA signatures for the connection establishment
* include a new signedOnTime so that we can prepare the packet at a different moment from
  when we encrypt & send it (also allowing us to reuse that signature on resends for the same
  establishment)
2005-04-08 14:21:26 +00:00
b36def1f72 2005-04-08 smeghead
* Security improvements to TrustedUpdate: signing and verification of the
      version string along with the data payload for signed update files
      (consequently the positions of the DSA signature and version string fields
      have been swapped in the spec for the update file's header); router will
      no longer perform a trusted update if the signed update's version is lower
      than or equal to the currently running router's version.
    * Added two new CLI commands to TrustedUpdate: showversion, verifyupdate.
    * Extended TrustedUpdate public API for use by third party applications.
2005-04-08 12:39:20 +00:00
5a6a3a5e8d oops, forgot to add new eepget script to build 2005-04-08 01:56:02 +00:00
c3bd26d9b4 added wspucktracker.i2p 2005-04-07 20:40:30 +00:00
aum
967e106ee7 fixed one last javadoc err 2005-04-07 04:36:06 +00:00
aum
7c73e59482 Fixed more javadoc errors 2005-04-07 04:26:55 +00:00
aum
03dfa913d1 Removed erroneous @author tag from methods 2005-04-07 04:05:13 +00:00
348e845793 *cough* thanks cervantes 2005-04-06 16:38:38 +00:00
80827c3aad * 2005-04-06 0.5.0.6 released 2005-04-06 15:43:25 +00:00
3b4cf0a024 added 55cancri.i2p 2005-04-06 15:14:00 +00:00
941252fd80 2005-04-05 jrandom
* Retry I2PTunnel startup if we are unable to build a socketManager for a
      client or httpclient tunnel.
    * Add some basic sanity checking on the I2CP settings (thanks duck!)
2005-04-05 22:24:32 +00:00
bc626ece2d 2005-04-05 jrandom
* After a successfull netDb search for a leaseSet, republish it to all of
      the peers we have tried so far who did not give us the key (up to 10),
      rather than the old K closest (which may include peers who had given us
      the key)
    * Don't wait 5 minutes to publish a leaseSet (duh!), and rather than
      republish it every 5 minutes, republish it every 3.  In addition, always
      republish as soon as the leaseSet changes (duh^2).
    * Minor fix for oddball startup race (thanks travis_bickle!)
    * Minor AES update to allow in-place decryption.
2005-04-05 16:06:14 +00:00
400feb3ba7 clarify crypto/hmac usage for simpler implementation 2005-04-05 15:28:54 +00:00
756a4e3995 added a section for congestion control describing what I hope to implement. what
/actually/ gets implemented will be documented further once its, er, implemented
2005-04-04 17:21:30 +00:00
aum
578301240e Added constructors to PrivateKey, PublicKey, SigningPrivateKey and
SigningPublicKey, which take a single String argument and construct
the object from the Base64 data in that string (where this data is
the product of a .toBase64() call on a prior instance).
2005-04-04 06:13:50 +00:00
aum
9b8f91c7f9 Added 'toPublic()' methods to PrivateKey and SigningPrivateKey, such
that these return PublicKey and SigningPublicKey objects, respectively.
2005-04-04 06:01:13 +00:00
c7c389d4fb added eepget wrapper script for *nix 2005-04-03 13:35:52 +00:00
68f7adfa0b *** keyword substitution change *** 2005-04-03 13:33:29 +00:00
c4ac5170c7 2005-04-03 jrandom
* EepGet fix for open-ended HTTP fetches (such as the news.xml
      feeding the NewsFetcher)
2005-04-03 12:50:11 +00:00
32e0c8ac71 updated status blurb 2005-04-03 07:22:28 +00:00
c9c1eae32f 2005-04-01 jrandom
* Allow editing I2PTunnel server instances with five digit ports
      (thanks nickless_head!)
    * More NewsFetcher debugging for reported weirdness
2005-04-01 13:29:26 +00:00
33366cc291 2005-04-01 jrandom
* Fix to check for missing news file (thanks smeghead!)
    * Added destination display CLI:
      java -cp lib/i2p.jar net.i2p.data.Destination privKeyFilename
    * Added destination display to the web interface (thanks pnspns)
    * Installed CIA backdoor
2005-04-01 11:28:06 +00:00
083ac1f125 n3wz0rz 2005-03-31 02:04:18 +00:00
80c6290b89 oh five oh five 2005-03-30 03:27:55 +00:00
6492ad165a Added tracker.fr.i2p 2005-03-30 03:21:18 +00:00
f0d1b1a40e added v2mail.i2p, complication.i2p 2005-03-30 01:49:49 +00:00
17f044e6cd if using numACKs, use a 2 byte value (to handle higher transfer rates) 2005-03-30 00:20:07 +00:00
63f3a9cd7b * 2005-03-29 0.5.0.5 released
2005-03-29  jrandom
    * Decreased the initial RTT estimate to 10s to allow more retries.
    * Increased the default netDb store replication factor from 2 to 6 to take
      into consideration tunnel failures.
    * Address some statistical anonymity attacks against the netDb that could
      be mounted by an active internal adversary by only answering lookups for
      leaseSets we received through an unsolicited store.
    * Don't throttle lookup responses (we throttle enough elsewhere)
    * Fix the NewsFetcher so that it doesn't incorrectly resume midway through
      the file (thanks nickster!)
    * Updated the I2PTunnel HTML (thanks postman!)
    * Added support to the I2PTunnel pages for the URL parameter "passphrase",
      which, if matched against the router.config "i2ptunnel.passphrase" value,
      skips the nonce check.  If the config prop doesn't exist or is blank, no
      passphrase is accepted.
    * Implemented HMAC-SHA256.
    * Enable the tunnel batching with a 500ms delay by default
    * Dropped compatability with 0.5.0.3 and earlier releases
2005-03-30 00:07:36 +00:00
b8ddbf13b4 added lazyguy.i2p 2005-03-28 02:41:19 +00:00
be9bdbfe0f * simplify the MAC construct with a single HMAC (the other setup was an oracle anyway)
* split out the encryption and MAC keys
2005-03-27 22:08:16 +00:00
bc74bf1402 added confessions.i2p, rsync.thetower.i2p, redzara.i2p, gaytorrents.i2p 2005-03-27 01:03:42 +00:00
5c2a57f95a minor cleanup 2005-03-26 09:22:17 +00:00
9cd8cc692e added replay prevention blurb, minor cleanup 2005-03-26 09:19:42 +00:00
ebac4df2d3 2005-03-26 jrandom
* Added some error handling and fairly safe to cache data to the streaming
      lib (good call Tom!)
2005-03-26 07:13:38 +00:00
0626f714c6 speling (thanks cervantes) 2005-03-26 06:23:57 +00:00
21842291e9 *cough* 2005-03-26 05:56:06 +00:00
d461c295f6 first draft of secure semireliable UDP protocol 2005-03-26 05:47:40 +00:00
85b3450525 2005-03-25 jrandom
* Fixed up building dependencies for the routerconsole on some more
      aggressive compilers (thanks polecat!)
2005-03-25 04:07:05 +00:00
aum
75d7c81b7c Oops, forgot the DataFormatException 2005-03-24 08:39:04 +00:00
aum
1433e20f73 Added Destination constructor which accepts/uses a base64 string arg 2005-03-24 08:37:17 +00:00
e614a2f726 * 2005-03-24 0.5.0.4 released 2005-03-24 07:29:27 +00:00
32be7f1fd8 grr 2005-03-24 04:58:28 +00:00
66e1d95a2a *cough* oops 2005-03-24 04:49:15 +00:00
ff03be217e 2005-03-23 jrandom
* Added more intelligent version checking in news.xml, in case we have a
      version newer than the one specified.
2005-03-24 03:18:15 +00:00
a52f8b89dc 2005-03-23 jrandom
* Added support for Transfer-Encoding: chunked to the EepGet, so that the
      cvsweb.cgi doesn't puke on us.
2005-03-24 02:38:10 +00:00
21c7c043b3 Fixed Bugzilla Bug #99 2005-03-24 01:54:23 +00:00
45e6608ad3 Added 'Unit test passed' log message and made test check that Bug #99 is fixed. 2005-03-24 01:50:19 +00:00
28978e3680 Fixed Bug #99: Data pending to be sent is still sent even if STREAM CLOSE is issued. 2005-03-24 01:49:00 +00:00
904f755c8c 2005-03-23 jrandom
* Implemented the news fetch / update policy code, as configurated on
      /configupdate.jsp.  Defaults are to grab the news every 24h (or if it
      doesn't exist yet, on startup).  No action is taken however, though if
      the news.xml specifies that a new release is available, an option to
      update will be shown on the router console.
    * New initialNews.xml delivered with new installs, and moved news.xml out
      of the i2pwww module and into the i2p module so that we can bundle it
      within each update.
2005-03-24 01:19:52 +00:00
a2c309ddd3 2005-03-23 jrandom
* New /configupdate.jsp page for controlling the update / notification
      process, as well as various minor related updates.  Note that not all
      options are exposed yet, and the update detection code isn't in place
      in this commit - it currently says there is always an update available.
    * New EepGet component for reliable downloading, with a CLI exposed in
      java -cp lib/i2p.jar net.i2p.util.EepGet url
    * Added a default signing key to the TrustedUpdate component to be used
      for verifying updates.  This signing key can be authenticated via
      gpg --verify i2p/core/java/src/net/i2p/crypto/TrustedUpdate.java
    * New public domain SHA1 implementation for the DSA code so that we can
      handle signing streams of arbitrary size without excess memory usage
      (thanks P.Verdy!)
    * Added some helpers to the TrustedUpdate to work off streams and to offer
      a minimal CLI:
          TrustedUpdate keygen pubKeyFile privKeyFile
          TrustedUpdate sign origFile signedFile privKeyFile
          TrustedUpdate verify signedFile
2005-03-23 21:13:03 +00:00
aum
677eeac8f7 changed existing 'decodeToString' to public 2005-03-23 06:30:31 +00:00
aum
b232cc0f24 D'oh, .decodeToString was already there, eliminated my vers 2005-03-23 06:26:23 +00:00
aum
18bbae1d1e changed 'String decode(String raw)' to 'String decodeToString(String raw)'
to eliminate name clash.
2005-03-23 06:24:25 +00:00
aum
08ee62b52c Added convenience methods:
- String encode(String raw)
 - String decode(String raw)
2005-03-23 06:21:16 +00:00
5b83aed719 * Added basic trusted update creation/verification 2005-03-22 17:08:01 +00:00
b5875ca07b 2005-03-21 jrandom
* Fixed the tunnel fragmentation handler to deal with multiple fragments
      in a single message properly (rather than release the buffer into the
      cache after processing the first one) (duh!)
    * Added the batching preprocessor which will bundle together multiple
      small messages inside a single tunnel message by delaying their delivery
      up to .5s, or whenever the pending data will fill a full message,
      whichever comes first.  This is disabled at the moment, since without the
      above bugfix widely deployed, lots and lots of messages would fail.
    * Within each tunnel pool, stick with a randomly selected peer for up to
      .5s before randomizing and selecting again, instead of randomizing the
      pool each time a tunnel is needed.
2005-03-22 02:00:10 +00:00
3f9bf28382 2005-03-21 jrandom
* Fixed the tunnel fragmentation handler to deal with multiple fragments
      in a single message properly (rather than release the buffer into the
      cache after processing the first one) (duh!)
    * Added the batching preprocessor which will bundle together multiple
      small messages inside a single tunnel message by delaying their delivery
      up to .5s, or whenever the pending data will fill a full message,
      whichever comes first.  This is disabled at the moment, since without the
      above bugfix widely deployed, lots and lots of messages would fail.
    * Within each tunnel pool, stick with a randomly selected peer for up to
      .5s before randomizing and selecting again, instead of randomizing the
      pool each time a tunnel is needed.
2005-03-22 01:38:21 +00:00
a2bd71c75b * 2005-03-18 0.5.0.3 released
2005-03-18  jrandom
    * Minor tweak to the timestamper to help reduce small skews
    * Adjust the stats published to include only the relevent ones
    * Only show the currently used speed calculation on the profile page
    * Allow the full max # resends to be sent, rather than piggybacking the
      RESET packet along side the final resend (duh)
    * Add irc.postman.i2p to the default list of IRC servers for new installs
    * Drop support for routers running 0.5 or 0.5.0.1 while maintaining
      backwards compatability for users running 0.5.0.2.
2005-03-18 22:34:51 +00:00
89509490c5 2005-03-18 jrandom
* Eepproxy Fix for corrupted HTTP headers (thanks nickster!)
    * Fixed case sensitivity issues on the HTTP headers (thanks duck!)
2005-03-18 08:48:00 +00:00
a997a46040 2005-03-17 jrandom
* Update the old speed calculator and associated profile data points to
      use a non-tiered moving average of the tunnel test time, avoiding the
      freshness issues of the old tiered speed stats.
    * Explicitly synchronize all of the methods on the PRNG, rather than just
      the feeder methods (sun and kaffe only need the feeder, but it seems ibm
      needs all of them synchronized).
    * Properly use the tunnel tests as part of the profile stats.
    * Don't flood the jobqueue with sequential persist profile tasks, but
      instead, inject a brief scheduling delay between them.
    * Reduce the TCP connection establishment timeout to 20s (which is still
      absurdly excessive)
    * Reduced the max resend delay to 30s so we can get some resends in when
      dealing with client apps that hang up early (e.g. wget)
    * Added more alternative socketManager factories (good call aum!)
2005-03-17 22:12:51 +00:00
538dd07e7b 2005-03-16 jrandom
* Adjust the old speed calculator to include end to end RTT data in its
      estimates, and use that as the primary speed calculator again.
    * Use the mean of the high capacity speeds to determine the fast
      threshold, rather than the median.  Perhaps we should use the mean of
      all active non-failing peers?
    * Updated the profile page to sort by tier, then alphabetically.
    * Added some alternative socketManager factories (good call aum!)
2005-03-17 05:29:55 +00:00
046778404e added arkan.i2p, search.i2p, floureszination.i2p, antipiratbyran.i2p
asylum.i2p, templar.i2p
2005-03-16 02:56:01 +00:00
766f83d653 added feedspace.i2p 2005-03-16 02:46:17 +00:00
b20aee6753 2005-03-14 jrandom
* New strict speed calculator that goes off the actual number of messages
      verifiably sent through the peer by way of tunnels.  Initially, this only
      contains the successful message count on inbound tunnels, but may be
      augmented later to include verified outbound messages, peers queried in
      the netDb, etc.  The speed calculation decays quickly, but should give
      a better differential than the previous stat (both values are shown on
      the /profiles.jsp page)
2005-03-15 03:47:14 +00:00
f9aa3aef18 added wiki.fr.i2p 2005-03-14 04:31:55 +00:00
d74aa6e53d (no, this doesnt fix things yet, but its a save point along the path)
2005-03-11  jrandom
    * Rather than the fixed resend timeout floor (10s), use 10s+RTT as the
      minimum (increased on resends as before, of course).
    * Always prod the clock update listeners, even if just to tell them that
      the time hasn't changed much.
    * Added support for explicit peer selection for individual tunnel pools,
      which will be useful in debugging but not recommended for use by normal
      end users.
    * More aggressively search for the next hop's routerInfo on tunnel join.
    * Give messages received via inbound tunnels that are bound to remote
      locations sufficient time (taking into account clock skew).
    * Give alternate direct send messages sufficient time (10s min, not 5s)
    * Always give the end to end data message the explicit timeout (though the
      old default was sufficient before)
    * No need to give end to end messages an insane expiration (+2m), as we
      are already handling skew on the receiving side.
    * Don't complain too loudly about expired TunnelCreateMessages (at least,
      not until after all those 0.5 and 0.5.0.1 users upgrade ;)
    * Properly keep the sendBps stat
    * When running the router with router.keepHistory=true, log more data to
      messageHistory.txt
    * Logging updates
    * Minor formatting updates
2005-03-11 22:23:36 +00:00
ea6fbc7835 added septu.i2p 2005-03-09 20:02:14 +00:00
536e604b8e 2005-03-07 jrandom
* Fix the HTTP response header filter to allow multiple headers with the
      same name (thanks duck and spotteri!)
2005-03-08 02:45:14 +00:00
49d6f5018f * Properly expand the HTTP response header buffer (thanks shendaras!) 2005-03-07 00:40:45 +00:00
4a830e422a added music.i2p, rotten.i2p, wintermute.i2p, kaji2.i2p, aspnet.i2p, gaming.i2p, nntp.i2p 2005-03-07 00:38:19 +00:00
df6c52fe75 * 2005-03-06 0.5.0.2 released
2005-03-06  jrandom
    * Allow the I2PTunnel web interface to select streaming lib options for
      individual client tunnels, rather than sharing them across all of them,
      as we do with the session options.  This way people can (and should) set
      the irc proxy to interactive and the eepproxy to bulk.
    * Added a startRouter.sh script to new installs which simply calls
      "sh i2prouter start".  This should make it clear how people should start
      I2P.
2005-03-07 00:07:27 +00:00
01979c08b3 2005-03-04 jrandom
* Filter HTTP response headers in the eepproxy, forcing Connection: close
      so that broken (/malicious) webservers can't allow persistent
      connections.  All HTTP compliant browsers should now always close the
      socket.
    * Enabled the GZIPInputStream's cache (they were'nt cached before)
    * Make sure our first send is always a SYN (duh)
    * Workaround for some buggy compilers
2005-03-05 02:54:42 +00:00
7928ef83cc added cowsay.i2p 2005-03-04 23:37:39 +00:00
10afe0a060 2005-03-03 jrandom
* Loop while starting up the I2PTunnel instances, in case the I2CP
      listener isn't up yet (thanks detonate!)
    * Implement custom reusable GZIP streams to both reduce memory churn
      and prevent the exposure of data in the standard GZIP header (creation
      time, OS, etc).  This is RFC1952 compliant, and backwards compatible,
      though has only been tested within the confines of I2P's compression use
      (DataHelper.[de]compress).
    * Preemptively support the next protocol version, so that after the 0.5.0.2
      release, we'll be able to drop protocol=2 to get rid of 0.5 users.
2005-03-04 06:09:20 +00:00
ef230cfa3d 2005-03-02 jrandom
* Fix one substantial OOM cause (session tag manager was only dropping
      tags once the critical limit was met, rather than honoring their
      expiration) (duh)
    * Lots of small memory fixes
    * Double the allowable concurrent outstanding tunnel build tasks (20)
2005-03-03 03:36:52 +00:00
2d15a42137 big code cleanup to reduce number of compiler warnings 2005-03-01 23:25:15 +00:00
57d6a2f645 2005-03-01 jrandom
* Really disable the streaming lib packet caching
    * Synchronized a message handling point in the SDK (even though its use is
      already essentially single threaded, its better to play it safe)
    * Don't add new RepublishLeaseSetJobs on failure, just requeue up the
      existing one (duh)
    * Throttle the number of concurrent pending tunnel builds across all
      pools, in addition to simply throttling the number of new requests per
      minute for each pool individually.  This should avoid the cascading
      failure when tunnel builds take too long, as no new builds will be
      created until the previous ones are handled.
    * Factored out and extended the DataHelper's unit tests for dealing with
      long and date formatting.
    * Explicitly specify the HTTP auth realm as "i2prouter", though this
      alone doesn't address the bug where jetty asks for authentication too
      much.  (thanks orion!)
    * Updated the StreamSinkServer to ignore all read bytes, rather than write
      them to the filesystem.
2005-03-01 17:50:52 +00:00
469a0852d7 2005-02-27 jrandom
* Don't rerequest leaseSets if there are already pending requests
    * Reverted the insufficiently tested caching in the DSA/SHA1 impl, and
      temporary disabled the streaming lib packet caching.
    * Reduced the resend RTT penalty to 10s
2005-02-27 22:09:37 +00:00
7983bb1490 1.3 here too 2005-02-27 00:13:00 +00:00
2e7eac02ed 2005-02-26 jrandom
* Force 1.3-isms on the precompiled jsps too (thanks laberhost)
2005-02-27 00:03:42 +00:00
238389fc7f 2005-02-26 jrandom
* Further streaming lib caching improvements
    * Reduce the minimum RTT (used to calculate retry timeouts), but also
      increase the RTT on resends.
    * Lower the default message size to 4KB from 16KB to further reduce the
      chance of failed fragmentation.
    * Extend tunnel rebuild throttling to include fallback rebuilds
    * If there are less than 20 routers known, don't drop the last 20 (to help
      avoid dropping all peers under catastrophic failures)
    * New stats for end to end messages - "client.leaseSetFoundLocally",
      "client.leaseSetFoundRemoteTime", and "client.leaseSetFailedRemoteTime"
2005-02-26 19:16:46 +00:00
4cec9da0a6 2005-02-24 jrandom
* Throttle the number of tunnel rebuilds per minute, preventing CPU
      overload under catastrophic failures (thanks Tracker and cervantes!)
    * Block the router startup process until we've initialized the clock
2005-02-24 23:53:35 +00:00
00f27d4400 2005-02-24 jrandom
* Cache temporary memory allocation in the DSA's SHA1 impl, and the packet
      data in the streaming lib.
    * Fixed a streaming lib bug where the connection initiator would fail the
      stream if the ACK to their SYN was lost.
2005-02-24 18:05:25 +00:00
f61618e4a4 2005-02-23 jrandom
* Now that we don't get stale SAM sessions, it'd be nice if we didn't
      get stale tunnel pools, don't you think?
2005-02-23 21:44:30 +00:00
265d5e306e * 2005-02-23 0.5.0.1 released 2005-02-23 05:00:52 +00:00
10ed058c2e 2005-02-22 jrandom
* Reworked the tunnel (re)building process to remove the tokens and
      provide cleaner controls on the tunnels built.
    * Fixed situations where the timestamper wanted to test more servers than
      were provided (thanks Tracker!)
    * Get rid of the dead SAM sessions by using the streaming lib's callbacks
      (thanks Tracker!)
2005-02-23 04:20:28 +00:00
8a21f0efec 2005-02-22 jrandom
* Temporary workaround for the I2CP disconnect bug (have the streaminglib
      try to automatically reconnect on accept()/connect(..)).
    * Loop check for expired lease republishing (just in case)
2005-02-22 23:13:00 +00:00
b8291ac5a4 2005-02-22 jrandom
* Temporary workaround for the I2CP disconnect bug (have the streaminglib
      try to automatically reconnect on accept()/connect(..)).
    * Loop check for expired lease republishing (just in case)
2005-02-22 22:58:21 +00:00
c17433cb93 2005-02-22 jrandom
* Adjusted (and fixed...) the timestamper change detection
    * Deal with a rare reordering bug at the beginning of a stream (so we
      don't drop it unnecessarily)
    * Cleaned up some dropped message handling in the router
    * Reduced job queue churn when dealing with a large number of tunnels by
      sharing an expiration job
    * Keep a separate list of the most recent CRIT messages (shown on the
      logs.jsp).  This way they don't get buried among any other messages.
    * For clarity, display the tunnel variance config as "Randomization" on
      the web console.
    * If lease republishing fails (boo! hiss!) try it again
    * Actually fix the negative jobLag in the right place (this time)
    * Allow reseeding when there are less than 10 known peer references
    * Lots of logging updates.
2005-02-22 07:07:29 +00:00
35fe7f8203 2005-02-20 jrandom
* Allow the streaming lib resend frequency to drop down to 20s as the
      minimum, so that up to 2 retries can get sent on an http request.
    * Add further limits to failsafe tunnels.
    * Keep exploratory and client tunnel testing and building stats separate.
    * Only use the 60s period for throttling tunnel requests due to transient
      network overload.
    * Rebuild tunnels earlier (1-3m before expiration, by default)
    * Cache the next hop's routerInfo for participating tunnels so that the
      tunnel participation doesn't depend on the netDb.
    * Fixed a long standing bug in the streaming lib where we wouldn't always
      unchoke messages when the window size grows.
    * Make sure the window size never reaches 0 (duh)
2005-02-21 19:08:01 +00:00
21f13dba43 2005-02-20 jrandom
* Allow the streaming lib resend frequency to drop down to 20s as the
      minimum, so that up to 2 retries can get sent on an http request.
    * Add further limits to failsafe tunnels.
    * Keep exploratory and client tunnel testing and building stats separate.
    * Only use the 60s period for throttling tunnel requests due to transient
      network overload.
    * Rebuild tunnels earlier (1-3m before expiration, by default)
    * Cache the next hop's routerInfo for participating tunnels so that the
      tunnel participation doesn't depend on the netDb.
    * Fixed a long standing bug in the streaming lib where we wouldn't always
      unchoke messages when the window size grows.
    * Make sure the window size never reaches 0 (duh)
2005-02-21 18:02:14 +00:00
0db239a3fe added irc.postman.i2p 2005-02-21 03:13:40 +00:00
4745d61f9b added subrosa.i2p 2005-02-21 02:55:12 +00:00
b9a4c3ba52 *cough* 2005-02-20 11:09:05 +00:00
cbf6a70a1a 2005-02-20 jrandom
* Only build failsafe tunnels if we need them
    * Properly implement the selectNotFailingPeers so that we get a random
      selection of peers, rather than using the strictOrdering (thanks dm!)
    * Don't include too many "don't tell me about" peer references in the
      lookup message - only send the 10 peer references closest to the target.
2005-02-20 09:12:43 +00:00
7d4e093b58 2005-02-19 jrandom
* Only build new extra tunnels on failure if we don't have enough
    * Fix a fencepost in the tunnel building so that e.g. a variance of
      2 means +/- 2, not +/- 1 (thanks dm!)
    * Avoid an NPE on client disconnect
    * Never select a shitlisted peer to participate in a tunnel
    * Have netDb store messages timeout after 10s, not the full 60s (duh)
    * Keep session tags around for a little longer, just in case (grr)
    * Cleaned up some closing event issues on the streaming lib
    * Stop bundling the jetty 5.1.2 and updated wrapper.config in the update
      so that 0.4.* users will need to do a clean install, but we don't need
      to shove an additional 2MB in each update to those already on 0.5.
    * Imported the susimail css (oops, thanks susi!)
2005-02-19 23:20:56 +00:00
d27feabcb3 clear the old precompiled .java files (thanks duck!) 2005-02-18 16:56:46 +00:00
0d9efa17de bah, fuck it. we can deal with a little shitlisting 2005-02-18 16:04:51 +00:00
b125b04c8d 0.5 released 2005-02-18 15:58:20 +00:00
0539f1d794 updated for the new props 2005-02-18 15:35:02 +00:00
a4b6709f02 added moxonom.i2p 2005-02-18 15:27:46 +00:00
f2db143a6f Refuse to load 0.4 routerInfo (to help weed out the old ones) 2005-02-18 15:25:25 +00:00
b615f54d41 *cough* 2005-02-18 08:28:56 +00:00
db2328e03e * actually reseed properly
* hide the susimail deprecation warnings
* dont push hosts.txt in the update (people can subscribe if they want to)
2005-02-18 08:12:40 +00:00
e2071935ad added sex0r.i2p flock.i2p cneal.i2p www.nntp.i2p wallsgetbombed.i2p
thedarkside.i2p legion.i2p manveru.i2p books.manveru.i2p bt.i2p
2005-02-18 06:23:29 +00:00
1c40ff773f fproxy.i2p and www1.squid.i2p are back (yay!) 2005-02-18 00:52:26 +00:00
37a3645663 Default subscriptions shouldn't rely on a pre-existing hosts.txt. 2005-02-18 00:50:18 +00:00
eb8accd1e0 damn those copyright laws 2005-02-17 23:59:52 +00:00
3af97894b4 tyop 2005-02-17 23:45:50 +00:00
15a0dcf4d8 (not yet tagging this 0.5, but I don't think there's anytihng left)
2005-02-17  jrandom
    * If the clock is adjusted during a job run, don't act as if the job took
      negative time.
2005-02-17 22:57:53 +00:00
aa3a44c42a 2005-02-17 jrandom
* Included the GPL'ed susimail 0.13 by default (thanks susi23!)
2005-02-17 20:55:07 +00:00
40f4b47b87 initial vanilla import of susimail 0.13 (no build script yet) 2005-02-17 20:08:53 +00:00
dca09d96b3 logging 2005-02-17 19:49:16 +00:00
dd10747460 2005-02-17 jrandom
* Fixed the braindead tunnel testing logic
    * If a large number of tunnels are failing (within the last 5-10 minutes)
      and the current tunnel pool's configuration allows it, randomly build a
      zero hop tunnel to replace failed tunnels.
    * Enable postman's POP3 and SMTP tunnels by default
2005-02-17 17:59:27 +00:00
77176162af 2005-02-16 jrandom
* Added some error handling when the number of session tags exceeds the
      realistic capacity, dropping a random chunk of received tag sets and
      conducting some minor analysis of the remaining ones.  This is a part
      of a pretty serious error condition, and logs as CRIT (if/when people
      see "TOO MANY SESSION TAGS!", please let me know the full log line it
      puts in the wrapper.log or /logs.jsp)
    * Update the addressbook to only write to the published hosts location
      if the addressbook's config contains "should_publish=true" (by default,
      it contains "should_publish=false")
2005-02-17 04:08:34 +00:00
8b9ee4dfd7 updated to reflect what was implemented 2005-02-17 00:48:18 +00:00
6e8e77b9ec 0.5 merging 2005-02-16 22:43:00 +00:00
7ef9ce8cc6 0.5 merging 2005-02-16 22:37:24 +00:00
9646ac2911 continuing 0.5 merges 2005-02-16 22:35:12 +00:00
566a713baa 2005-02-16 jrandom
* (Merged the 0.5-pre branch back into CVS HEAD)
    * Replaced the old tunnel routing crypto with the one specified in
      router/doc/tunnel-alt.html, including updates to the web console to view
      and tweak it.
    * Provide the means for routers to reject tunnel requests with a wider
      range of responses:
        probabalistic rejection, due to approaching overload
        transient rejection, due to temporary overload
        bandwidth rejection, due to persistent bandwidth overload
        critical rejection, due to general router fault (or imminent shutdown)
      The different responses are factored into the profiles accordingly.
    * Replaced the old I2CP tunnel related options (tunnels.depthInbound, etc)
      with a series of new properties, relevent to the new tunnel routing code:
        inbound.nickname (used on the console)
        inbound.quantity (# of tunnels to use in any leaseSets)
        inbound.backupQuantity (# of tunnels to keep in the ready)
        inbound.length (# of remote peers in the tunnel)
        inbound.lengthVariance (if > 0, permute the length by adding a random #
                                up to the variance.  if < 0, permute the length
                                by adding or subtracting a random # up to the
                                variance)
        outbound.* (same as the inbound, except for the, uh, outbound tunnels
                    in that client's pool)
      There are other options, and more will be added later, but the above are
      the most relevent ones.
    * Replaced Jetty 4.2.21 with Jetty 5.1.2
    * Compress all profile data on disk.
    * Adjust the reseeding functionality to work even when the JVM's http proxy
      is set.
    * Enable a poor-man's interactive-flow in the streaming lib by choking the
      max window size.
    * Reduced the default streaming lib max message size to 16KB (though still
      configurable by the user), also doubling the default maximum window
      size.
    * Replaced the RouterIdentity in a Lease with its SHA256 hash.
    * Reduced the overall I2NP message checksum from a full 32 byte SHA256 to
      the first byte of the SHA256.
    * Added a new "netId" flag to let routers drop references to other routers
      who we won't be able to talk to.
    * Extended the timestamper to get a second (or third) opinion whenever it
      wants to actually adjust the clock offset.
    * Replaced that kludge of a timestamp I2NP message with a full blown
      DateMessage.
    * Substantial memory optimizations within the router and the SDK to reduce
      GC churn.  Client apps and the streaming libs have not been tuned,
      however.
    * More bugfixes thank you can shake a stick at.

2005-02-13  jrandom
    * Updated jbigi source to handle 64bit CPUs.  The bundled jbigi.jar still
      only contains 32bit versions, so build your own, placing libjbigi.so in
      your install dir if necessary.  (thanks mule!)
    * Added support for libjbigi-$os-athlon64 to NativeBigInteger and CPUID
      (thanks spaetz!)
2005-02-16 22:23:47 +00:00
36f7e98e90 added riaa.i2p 2005-02-16 14:03:22 +00:00
4da755816a added mpaa.i2p 2005-02-15 03:08:45 +00:00
3ef0258faf file TrivialRouterPreprocessor.java was initially added on branch i2p_0_5_pre_branch. 2005-02-14 22:15:20 +00:00
293ceaee93 2005-02-10 smeghead
* Initial check-in of Pants, a new utility to help us manage our 3rd-party
      dependencies (Fortuna, Jetty, Java Service Wrapper, etc.). Some parts of
      Pants are still non-functional at this time so don't mess with it yet
      unless you want to potentially mangle your working copy of CVS.
2005-02-11 02:44:47 +00:00
7b58d0fa0f Allow an unneeded newline in the SAM client protocol without disconnecting. 2005-02-09 19:28:29 +00:00
bd68c1e056 file configtunnels.jsp was initially added on branch i2p_0_5_pre_branch. 2005-02-09 18:15:53 +00:00
200162d973 file ConfigTunnelsHelper.java was initially added on branch i2p_0_5_pre_branch. 2005-02-09 18:15:52 +00:00
4b37a53f1c file TunnelHelper.java was initially added on branch i2p_0_5_pre_branch. 2005-02-09 13:29:02 +00:00
2d41de7ae0 Restore original method of filtering names with non .i2p tlds 2005-02-09 02:21:43 +00:00
bc5bc62c18 file CachingByteArrayOutputStream.java was initially added on branch i2p_0_5_pre_branch. 2005-02-08 22:22:15 +00:00
a0d680024e added pants.i2p 2005-02-08 21:11:24 +00:00
45013feea7 file DateMessage.java was initially added on branch i2p_0_5_pre_branch. 2005-02-08 13:28:51 +00:00
2abbe992dd file BloomFilterIVValidator.java was initially added on branch i2p_0_5_pre_branch. 2005-02-08 12:23:29 +00:00
d7081b3eeb file KeySelector.java was initially added on branch i2p_0_5_pre_branch. 2005-02-07 20:41:46 +00:00
a2f5289bd9 file DecayingBloomFilter.java was initially added on branch i2p_0_5_pre_branch. 2005-02-07 20:41:45 +00:00
b366a4b942 2005-02-07 jrandom
* Fixed a race in the streaming lib's delayed flush algorithm (thanks anon!)
2005-02-07 10:04:23 +00:00
27e92653fe 2005-02-06 Sugadude
* Added a filter to the addressbook to remove entries that dont end in ".i2p"
(thanks Sugadude!)
2005-02-06 22:14:46 +00:00
80120b7b7d added entropy feeding interface, and hooked it up to the end of the DH exchange (source=DH) as well as the end of the ElGamal/AES decrypt (source=ElG/AES). the default RandomSource ignores this data 2005-02-06 08:38:07 +00:00
af8a618826 added irc.carambar.i2p 2005-02-04 17:49:10 +00:00
af0e554562 file PooledTunnelCreatorConfig.java was initially added on branch i2p_0_5_pre_branch. 2005-02-04 07:31:42 +00:00
382cbb18db 2005-02-03 smeghead
* Added Ant buildfile in apps/fortuna for creating a custom Fortuna PRNG jar
      library from GNU Crypto's CVS HEAD sources.
2005-02-03 13:39:46 +00:00
252b523155 file TunnelPoolManager.java was initially added on branch i2p_0_5_pre_branch. 2005-02-01 13:37:30 +00:00
4303b3b716 file TunnelPoolSettings.java was initially added on branch i2p_0_5_pre_branch. 2005-02-01 13:37:29 +00:00
87715dc21a file DummyValidator.java was initially added on branch i2p_0_5_pre_branch. 2005-02-01 13:37:28 +00:00
8552494fc1 file TunnelGatewayMessage.java was initially added on branch i2p_0_5_pre_branch. 2005-02-01 13:37:25 +00:00
1c2290b613 added general.i2p 2005-01-28 22:30:47 +00:00
5f6060b801 2005-01-26 smeghead
* i2pProxy.pac, i2pbench.sh, and i2ptest.sh are now shipped with the dist
      packages and installed to $i2pinstalldir/scripts.
    * Added command line params to i2ptest.sh and i2pbench.sh: --gij to run them
      using gij + libgcj, and --sourcedir to run them from the source tree
      instead of the installation directory.
    * Fixed unreachable for() statement clause in the KBucketImpl class that was
      causing gcj to toss a compilation warning (jrandom++).
2005-01-27 04:48:41 +00:00
b39958604d added smeghead.i2p 2005-01-27 01:35:24 +00:00
22ca1491bc 2005-01-26 smeghead
* Added a couple of scripts, i2ptest.sh and i2pbench.sh, to manage the core
      tests and benchmarks.
    * Routerconsole now builds under gcj 3.4.3.
    * Corrected divide by zero error in TunnelId class under gcj (jrandom++).
2005-01-27 00:21:10 +00:00
690d7e30cf added nntp.fr.i2p (w00t) 2005-01-26 22:07:53 +00:00
4fac2f1094 2005-01-25 smeghead
* Tweaked some classes to enable gcj 3.4.3 to compile the router and
      supporting apps (except for the routerconsole which is still being
      investigated).
2005-01-26 06:29:17 +00:00
eb0935d577 added deadgod.i2p 2005-01-26 04:32:00 +00:00
425fedf55b outbound tunnels passing tests, now to start hacking on the tie-in 2005-01-25 21:42:25 +00:00
a33de09ae6 * implemented fragmentation
* added more inbound tests
* made the tunnel preprocessing header more clear and included better fragmentation support
(still left: tests for outbound tunnel processing, structures and jobs to integrate with the router,
remove that full SHA256 from each and every I2NPMessage or put a smaller one at the
transport layer, and all the rest of the tunnel pooling/building stuff)
2005-01-25 05:46:22 +00:00
5018e56103 oops, moving README and sam-sharp.build out of the source directory 2005-01-24 23:43:37 +00:00
de2c975ac2 2005-01-24 smeghead
* C#-ification of sam-sharp: interface greatly simplified using delegates
      and events; SamBaseEventHandler provides basic implementation and helper
      methods but is now optional.
    * NAnt buildfile and README added for sam-sharp.
2005-01-24 22:42:05 +00:00
d86e2c0f59 2005-01-23 smeghead
* Port the java SAM client library to mono/C# and released into the
      public domain.  The 0.1 version of this port is available in CVS as
      i2p/apps/sam/csharp/src/I2P.SAM.Client.  The other nonfunctional C#
      library has been removed.
2005-01-23 08:22:11 +00:00
14023163b3 added manveru.i2p 2005-01-23 05:09:34 +00:00
d85dc8213e 2005-01-21 Jhor
* Updated jbigi build scripts for OSX.
2005-01-21  jrandom
    * Added support for OSX to the NativeBigInteger code so that it will look
      in the classpath for libjbigi-osx-none.jnilib.  At the moment, that file
      is not bundled with the shipped jbigi.jar yet though.
2005-01-22 01:53:02 +00:00
f6a34055ac removed the tunnel.html-style tunnel encryption and implemented the new tunnel-alt.html style
still much to be done beyond this, but this stuff turned out quite trivial (w00t)
2005-01-21 07:54:56 +00:00
3beb0d9c12 added fr.i2p 2005-01-20 11:53:06 +00:00
60968fe6f1 added imhotep.i2p 2005-01-20 08:57:16 +00:00
517c3101c7 added jrandom.dev.i2p 2005-01-20 02:51:31 +00:00
998f03ba68 killed the loops and the PRNGs by having the tunnel participants themselves specify what
tunnel ID they listen on and make sure the previous peer doesn't change over time.  The
worst that a hostile peer could do is create a multiplicative work factor - they send N
messages, causing N*#hops in the loop of bandwidth usage.  This is identical to the hostile
peer simply building a pair of tunnels and sending N messages through them.
also added some discussion about the tradeoffs and variations wrt fixed size tunnel messages.
2005-01-19 23:13:10 +00:00
f3b0e0cfc7 we want to use E on the preIV, not HMAC - must be invertible (duh, thanks Connelly)
adjusted preIV size accordingly, and definitely use a delivered layerIVKey
2005-01-19 06:24:25 +00:00
a65e6c888c 2005-01-18 jrandom
* Increased the max # session tags maintained and decreased slightly the
      period over which they are gathered.
2005-01-19 00:08:13 +00:00
cd939d3379 speling mistaces 2005-01-18 16:21:12 +00:00
29e5aeff5c include the preIV in the verification hash 2005-01-18 16:01:55 +00:00
0e5cf81fca updates with new alternative crypto, including Connelly's suggestions for the IV 2005-01-18 15:55:17 +00:00
61f217c610 2005-01-17 jrandom
* Added meaningful support for adjusting the preferred message size in the
      streaming lib by setting the i2p.streaming.maxMessageSize=32768 (or
      whatever).  The other side will mimic a reduction (but never an increase).
    * Always make sure to use distinct ConnectionOption objects for each
      connection (duh)
    * Reduced the default ACK delay to 500ms on in the streaming lib
    * Only shrink the streaming window once per window
    * Don't bundle a new jetty.xml with updates
    * Catch another local routerInfo corruption issue on startup.
2005-01-17 08:15:00 +00:00
ccb1f491c7 use the first 16 bytes of the SHA256 for the columns & verification block, rather than all 32 bytes.
(AES won't let us go smaller.  oh well)
2005-01-16 06:07:06 +00:00
49fdac9b4e added ttp.i2p 2005-01-16 04:45:36 +00:00
6b6a9490f6 include blurb explaining tunnelIDs and replay prevention (thanks Connelly!) 2005-01-16 00:08:14 +00:00
2c783e9876 2005-01-15 cervantes
* Added support to the eepproxy for URLs such as
      http://localhost:4444/eepproxy/foo.i2p/bar/baz or even
      http://localhost:4444/eepproxy/foo.i2p/?i2paddresshelper=base64
2005-01-15 23:16:12 +00:00
ecd971c0e5 2005-01-15 jrandom
* Caught a series of (previously unhandled) errors caused by requeueing
      messages that had timed out on the TCP transport (thanks mae^!)
    * Reduce the barrier to dropping session tags on streaming lib resends -
      every fourth send should drop the tags, forcing ElGamal encryption.  This
      will help speed up the recovery after a disconnect, rather than the drop
      every fifth send.
2005-01-15 21:03:14 +00:00
c48875a6fb cbc, nimwit 2005-01-15 06:43:35 +00:00
a245ccb8b7 added freenet.eco.i2p, tracker.i2p, photo.i2p 2005-01-15 05:52:09 +00:00
75a18debcb forgot to update the processing xor 2005-01-15 03:53:13 +00:00
1a15d3bb55 filled in the tunnel building alternatives, throttling techniques, and mixing (meta)details 2005-01-15 00:06:40 +00:00
ffdcae47e3 add some whitening to the IV as it goes down the path 2005-01-14 22:43:43 +00:00
34a2bc8590 added hopekiller.i2p, microsoft.i2p, jhor.i2p, badtoys.i2p 2005-01-13 19:36:03 +00:00
8ae4d00ccb added mindspore.i2p 2005-01-13 19:31:06 +00:00
9ed6d5e7fb added irc.ircbnc.i2p (connect to it as an irc client, pass 'testpass' (may be changed/removed), and outproxy to irc servers) 2005-01-13 19:23:06 +00:00
c9243b241c added dvdr-core.i2p 2005-01-13 17:11:17 +00:00
9c364a64e3 more arm waiving wrt the tunnel building 2005-01-13 00:57:36 +00:00
b34306205c lets just get some visual versioning clues 2005-01-12 19:22:40 +00:00
77f778dbf9 Updated the crypto so that peer0 is the gateway (meaning max hop length is 8, not 9).
This prevents the first peer after the gateway from looking at the encrypted data received
and seeing "hey, none of the checksum blocks match the payload, they must be the gateway".
2005-01-12 19:09:00 +00:00
23fa4e4161 Made userhosts.txt the default master addressbook, and hosts.txt the default router addressbook (mostly just testing if this will commit properly) 2005-01-12 04:26:33 +00:00
5b6fd0b829 dont wannit 2005-01-12 03:49:05 +00:00
8fa8d7739f work in progress, but i want it in cvs so i dont lose it again 2005-01-09 23:01:34 +00:00
dc552c7a29 html fix (just to clarify that K[i] isn't actually *transmitted*) 2005-01-07 23:15:38 +00:00
cf84f453d3 Initial implementation of the new tunnel encryption code. Still much more work to be
done (e.g. *what* gets encrypted, modifying the tunnelCreate messages, the tunnel
building process, and the new tunnel pooling).  I seem to have lost much of the typed
up docs describing this too, so I'll be hitting that next.
2005-01-07 22:55:30 +00:00
daf32a24bc * 2005-01-06 0.4.2.6 released
2005-01-06  jrandom
    * Added a startup message to the addressbook, printing its version number
      to stdout (which is sent to wrapper.config) when it loads.
    * Updated the addressbook to reread the config file periodically
    * Added orion.i2p to the list of eepsites on the default homepage
2005-01-06 20:59:13 +00:00
34ecfd9857 added j.i2p 2005-01-06 11:56:35 +00:00
4838564460 2005-01-05 jrandom
* Handle unexpected network read errors more carefully (thanks parg!)
    * Added more methods to partially compare (DataHelper) and display
      arrays (Base64.encode).
    * Exposed the AES encryptBlock/decryptBlock on the context.aes()
    * Be more generous on the throttle when just starting up the router
    * Fix a missing scheduled event in the streaming lib (caused after reset)
    * Add a new DisconnectListener on the I2PSocketManager to allow
      notification of session destruction.
    * Make sure our own router identity is valid, and if it isn't, build a new
      one and restart the router.  Alternately, you can run the Router with
      the single command line argument "rebuild" and it will do the same.
2005-01-06 00:17:53 +00:00
3dd2f67ff3 added bl.i2p 2005-01-05 23:16:35 +00:00
eco
0ccec3dde0 Added log entry for bt1.eco.i2p and jap.eco.i2p removal. Fix typo in numbering of previous log entry. 2005-01-04 12:29:12 +00:00
eco
ad77879caa removed jap.eco.i2p and bt1.eco.i2p (obsolete) 2005-01-04 12:22:38 +00:00
27999983cc added chat.i2p 2005-01-03 02:36:32 +00:00
48b039940d added phonebooth.i2p 2005-01-01 05:18:01 +00:00
84dc7d9d82 2004-12-31 ragnarok
* Integrated latest addressbook changes (2.0.3) which include support for
      deploying as a .war file with no existing addressbook configuration.
    * Updated main build process to bundle the addressbook.war in the
      i2pinstall.jar and i2pupdate.zip.
2005-01-01 00:57:01 +00:00
70d6332bad 2004-12-31 jrandom
* Speling fxi (thanks digum!)
    * Bugfix for the I2PTunnel web interface so that it now properly launches
      newly added tunnels that are defined to be run on startup (thanks ugha!)
2004-12-31 17:18:05 +00:00
aec0b0c86a 2004-12-30 jrandom
* Revised the I2PTunnel client and httpclient connection establishment
      throttles.  There is now a pool of threads that build the I2PSocket
      connections with a default size of 5, configurable via the I2PTunnel
      client option 'i2ptunnel.numConnectionBuilders' (if set to 0, it will
      not throttle the number of concurrent builders, but will launch a thread
      per socket during establishment).  In addition, sockets accepted but
      not yet allocated to one of the connection builders will be destroyed
      after 30 seconds, configurable via 'i2ptunnel.maxWaitTime' (if set to
      0, it will wait indefinitely).
2004-12-30 22:51:16 +00:00
099f6a88c2 2004-12-29 jrandom
* Imported Ragnarok's addressbook source (2.0.2) which is built but not
      deployed in the i2pinstall.jar/i2pupdate.zip (yet).
    * Don't treat connection inactivity closure as a connection error.
2004-12-29 22:16:42 +00:00
00a5d42d3d forgot to import these... 2004-12-29 20:51:43 +00:00
28f4a2cb67 imported ragnarok's MIT licensed addressbook-2.0.2 2004-12-29 20:28:20 +00:00
1ac18ba10e 2004-12-29 jrandom
* Add in a new keepalive event on each TCP connection, proactively sending
      a (tiny) time message every minute or two, as well as killing the
      connection if no message has been fully sent within 5 minutes or so.
      This should help deal with hung connections from IP address changes.
2004-12-29 20:06:43 +00:00
1503ee2dfa 2004-12-28 jrandom
* Cleaned up the resending and choking algorithm in the streaming lib.
    * Removed the read timeout override for I2PTunnel's httpclient, allowing
      it to use the default for the streaming lib.
    * Revised ack triggers in the streaming lib.
    * Logging.
2004-12-29 15:53:28 +00:00
484b528d4f * 2004-12-21 0.4.2.5 released
2004-12-21  jrandom
    * Track a new stat for expired client leases (client.leaseSetExpired).
2004-12-21 18:23:03 +00:00
758293dc02 2004-12-21 jrandom
* Cleaned up the postinstall/startup scripts a bit more to handle winME,
      and added windows info to the headless docs. (thanks ardvark!)
    * Fixed a harmless (yet NPE inspiring) race during the final shutdown of
      a stream (thanks frosk!)
    * Add a pair of new stats for monitoring tunnel participation -
      tunnel.participatingBytesProcessed (total # bytes transferred) and
      tunnel.participatingBytesProcessedActive (total # bytes transferred for
      tunnels whose byte count exceed the 10m average).  This should help
      further monitor congestion issues.
    * Made the NamingService factory property public (thanks susi!)
2004-12-21 16:32:49 +00:00
6cb316b33e 2004-12-20 jrandom
* No longer do a blocking DNS lookup within the jobqueue (thanks mule!)
    * Set a 60s dns cache TTL, instead of 0s.  Most users who used to use
      dyndns/etc now just use IP autodetection, so the old "we need ttl=0"
      reasoning is gone.
2004-12-20 05:14:56 +00:00
1d31831e7d added up.i2p 2004-12-20 02:40:45 +00:00
ee32b07995 2004-12-19 jrandom
* Fix for a race on startup wrt the new stats (thanks susi!)
2004-12-19 18:55:09 +00:00
81f04ca692 2004-12-19 jrandom
* Added three new stats - router.activePeers, router.fastPeers, and
      router.highCapacityPeers, updated every minute
2004-12-19 16:27:10 +00:00
1756997608 2004-12-19 jrandom
* Added a new i2ptunnel type: 'httpserver', allowing you to specify what
      hostname should be sent to the webserver.  By default, new installs will
      have an httpserver pointing at their jetty instance with the spoofed
      name 'mysite.i2p' (editable on the /i2ptunnel/edit.jsp page).
2004-12-19 11:04:56 +00:00
ec11ea4ca7 * Convert native jcpuid code from C++ to C. This should alleviate build
problems experienced by some users.
2004-12-19 06:25:27 +00:00
a1ebf85e1b added dm.i2p 2004-12-18 06:31:22 +00:00
4b2a734cda * 2004-12-18 0.4.2.4 released 2004-12-18 04:07:13 +00:00
97ae8f78a0 added piespy.i2p 2004-12-18 03:11:03 +00:00
834665c3ba 2004-12-16 jrandom
* Catch another oddball case for a reset connection in the streaming lib.
    * Add a dumpprofile.jsp page, called with ?peer=base64OfPeerHash, which
      dumps the current state of that peer's profile.  Instead of the full
      base64, you can pass in however many characters you have and it will
      return the first match found.
2004-12-16 10:32:26 +00:00
d969dd2d8d 2004-12-16 jrandom
* Catch another oddball case for a reset connection in the streaming lib.
    * Add a dumpprofile.jsp page, called with ?peer=base64OfPeerHash, which
      dumps the current state of that peer's profile.  Instead of the full
      base64, you can pass in however many characters you have and it will
      return the first match found.
2004-12-16 10:21:23 +00:00
3cb727561c uugly stat dumper. call via /dumpstats.jsp?peer=routerIdentHash 2004-12-16 09:45:31 +00:00
cbc89376d3 2004-12-16 jrandom
* Remove the randomized factor in the tunnel rejection by bandwidth -
      we now accept the request if we've allocated less than our limit
      and reject it if we've allocated more.
    * Stick to the standard capacity scale on tunnel rejection, even for
      the 10m period.
    * Build the time message at the very last possible moment
2004-12-16 05:42:03 +00:00
66aa29e3d4 2004-12-15 jrandom
* Handle hard disconnects more gracefully within the streaming lib, and
      log unmonitored events more aggressively.
    * If we drop a peer after connection due to clock skew, log it to the
      /logs.jsp#connectionlogs with relevent info.  In addition, toss it in
      the stat 'tcp.disconnectAfterSkew'.
    * Fixed the formatting in the skew display
    * Added an ERROR message that is fired once after we run out of
      routerInfo files (thanks susi!)
    * Set the connect timeout equal to the streaming lib's disconnect timeout
      if not already specified (the I2PTunnel httpclient already enforces a
      60s connect timeout)
    * Fix for another connection startup problem in the streaming lib.
    * Fix for a stupid error in the probabalistic drop (rand <= P, not > P)
    * Adjust the capacity calculations so that tunnel failures alone in the
      last 10m will not trigger a 0 capacity rank.
2004-12-16 02:45:55 +00:00
5c72aca5ee added sciencebooks.i2p 2004-12-15 04:23:16 +00:00
8824815d6d 2004-12-14 jrandom
* Periodically send a message along all I2NP connections with the router's
      current time, allowing the receiving peer to determine that the clock
      has skewed too much, and hence, disconnect.  For backwards compatability
      reasons, this is being kludged into a DeliveryStatusMessage (ewww).  The
      next time we have a backwards compatability break, we can put in a proper
      message setup for it.
2004-12-14 16:42:35 +00:00
ad72e5cbdf 2004-12-14 jrandom
* Reenable the probabalistic drop on the TCP queues to deal with good old
      fashioned bandwidth limiting.  However, by default the probability is
      rigged to reserve 0% of the queue free - meaning we just aggressively
      fail messages in the queue if we're transferring too slowly.  That
      reservation factor can be increased with 'tcp.queueFreeFactor=0.25'
      (or whatever) and the drop code can be disabled with the parameter
      'tcp.dropProbabalistically=false'.
    * Still penalize a peer on tunnel failure, but don't immediately drop
      their capacity to 0.
    * More aggressively ACK duplicates
    * Randomize the timestamper period
    * Display the clock skew on the connection logs when a peer sends it.
    * Allow the timestamper to fix skews of up to 10 minutes
    * Logging
2004-12-14 12:14:41 +00:00
b2f183fc17 2004-12-14 jrandom
* Reenable the probabalistic drop on the TCP queues to deal with good old
      fashioned bandwidth limiting.  However, by default the probability is
      rigged to reserve 0% of the queue free - meaning we just aggressively
      fail messages in the queue if we're transferring too slowly.  That
      reservation factor can be increased with 'tcp.queueFreeFactor=0.25'
      (or whatever) and the drop code can be disabled with the parameter
      'tcp.dropProbabalistically=false'.
    * Still penalize a peer on tunnel failure, but don't immediately drop
      their capacity to 0.
    * More aggressively ACK duplicates
    * Randomize the timestamper period
    * Display the clock skew on the connection logs when a peer sends it.
    * Allow the timestamper to fix skews of up to 10 minutes
    * Logging
2004-12-14 11:54:39 +00:00
9e16bc203a 2004-12-13 jrandom
* Added some error checking on the new client send job (thanks duck!)
    * Implemented tunnel rejection based on bandwidth usage (rejecting tunnels
      proportional to the bytes allocated in existing tunnels vs the bytes
      allowed through the bandwidth limiter).
    * Enable a new configuration parameter for triggering a tunnel rebuild
      (tunnel.maxTunnelFailures), where that is the max allowed test failures
      before killing the tunnel (default 0).
    * Gather more data that we rank capacity by (now we monitor and balance the
      data from 10m/30m/60m/1d instead of just 10m/60m/1d).
    * Fix a truncation/type conversion problem on the long term capacity
      values (we were ignoring the daily stats outright)
2004-12-13 13:45:52 +00:00
83c6eac017 added forum.fr.i2p, fedo.i2p, and pastebin.i2p 2004-12-13 08:48:29 +00:00
d5b277a536 test to verify that a closed socket is propogated to the client 2004-12-13 01:18:24 +00:00
77ce6c33e3 2004-12-11 jrandom
* Fix the missing HTTP timeout, which was caused by the deferred syn used
      by default.  This, in turn, meant the I2PSocket creation doesn't fail
      on .connect, but is unable to transfer any data in any direction.  We now
      detect that condition for the I2PTunnelHTTPClient and throw up the right
      error page.
    * Logging
2004-12-11 09:26:23 +00:00
60f8d349cf 2004-12-11 jrandom
* Use a simpler and less memory intensive job for processing outbound
      client messages when the session is in mode=bestEffort.  We can
      immediately discard the data as soon as its sent the first time,
      rather than wait for an ack, since we will never internally resend.
    * Reduce some synchronization to avoid a rare deadlock
    * Replaced 'localhost' with 127.0.0.1 in the i2ptunnel config, and special
      case it within the tunnel controller.
    * Script cleanup for building jbigi/jcpuid
    * Logging
2004-12-11 07:05:12 +00:00
f539c3df70 added frosk.i2p 2004-12-11 05:08:14 +00:00
fe1cf1758c added theland.i2p 2004-12-11 04:16:10 +00:00
8c71c26487 added dox.i2p 2004-12-11 01:22:03 +00:00
2ce39d1fd4 added amiga.i2p 2004-12-10 22:37:29 +00:00
88a994b712 cleaned up paths 2004-12-10 13:12:07 +00:00
24c8cc1a0c reference the new gmp 4.1.4 2004-12-10 10:22:17 +00:00
caf684394c crypto unit test to allow cross-architecture testing of jbigi and the crypto code 2004-12-10 08:48:23 +00:00
4b74510450 added source instructions (thanks bens\!) 2004-12-10 00:34:53 +00:00
mpc
0ddcfc423e *** empty log message *** 2004-12-09 14:05:22 +00:00
b4ac56e204 added frooze.i2p 2004-12-09 08:27:45 +00:00
3b19ac3942 use the standard I2CP port (7654) not jrandom's local I2CP port (duh) 2004-12-09 00:29:29 +00:00
af52cad4ea * 2004-12-08 0.4.2.3 released 2004-12-08 21:08:10 +00:00
d88396c1e2 2004-12-08 jrandom
* Revised the buffering when reading from the SAM client and writing
      to the stream.  Also added a thread (sigh) so we don't block the
      SAM client from giving us more messages for abnormally long periods
      of time.
    * Display the router version in the logs on startup (oft requested)
    * Fix a race during the closing of a messageOutputStream
2004-12-08 17:16:16 +00:00
4c5f7b9451 aliased gott.i2p as jrandom.i2p (ed. note: no, i am not gott) 2004-12-08 07:45:09 +00:00
e601cedbb8 2004-12-06 jrandom
* Don't do a 'passive flush' while there are already outbound messages
      unacked.
    * Show the reseed link if up to 10 peers profiles are active (thanks
      dburton!)
2004-12-07 01:35:53 +00:00
fa12dc867f 2004-12-06 jrandom
* Don't do a 'passive flush' while there are already outbound messages
      unacked.
    * Show the reseed link if up to 10 peers profiles are active (thanks
      dburton!)
2004-12-07 01:09:16 +00:00
acfb6c4578 Added sonax.i2p 2004-12-06 22:13:41 +00:00
e52d637092 2004-12-06 jrandom
* Don't propogate streaming connection failures out to the SAM bridge as
      fatal errors.
    * Dont barf on repeated I2CP closure.
2004-12-06 05:03:57 +00:00
2fba055696 2004-12-05 jrandom
* Explicitly use "127.0.0.1" to bind the I2CP listener, not the JVM's
      getLocalhost call
2004-12-06 02:08:02 +00:00
88bb176f3b 2004-12-05 jrandom
* Default the I2CP listener to localhost only, unless overridden by
      i2cp.tcp.bindAllInterfaces=true (thanks dm!)
    * More SAM fixes for things recently broken (whee)
2004-12-06 00:54:07 +00:00
499eeb275b added 1.fcp.freenet.i2p and copied fcp.i2p to 2.fcp.freenet.i2p 2004-12-05 22:18:57 +00:00
61a8d679bb 2004-12-05 jrandom
* Fix the recently broken SAM bridge (duh)
    * Add a new pair of SAM apps - net.i2p.sam.client.SAMStreamSink and
      net.i2p.sam.client.SAMStreamSend, mirroring the streaming lib's
      StreamSink and StreamSend apps for transferring files.
    * Make the passive flush timer fire more frequently.
2004-12-05 15:32:32 +00:00
2bbde91625 2004-12-05 jrandom
* Fixed some links in the console (thanks ugha!) and the javadoc
      (thanks dinoman!)
    * Fix the stream's passive flush timer (oh, its supposed to work?)
2004-12-05 10:22:57 +00:00
9ce098ee06 added asciiwhite.i2p 2004-12-05 08:47:02 +00:00
927ae57d24 added fcp.i2p 2004-12-05 03:16:30 +00:00
mpc
d65c2d3539 added installation instructions 2004-12-05 01:57:12 +00:00
2d9d8f32dc 2004-12-03 jrandom
* Toss in a small pool of threads (3) to execute the events queued up with
      the SimpleTimer, as we do currently see the occational event
      notification spiking up to a second or so.
    * Implement a SAM client API in java, useful for event based streaming (or
      for testing the SAM bridge)
    * Added support to shut down the SAM bridge on OOM (useful if the SAM
      bridge is being run outside of the router).
    * Include the SAM test code in the sam.jar
    * Remove an irrelevent warning message from SAM, which was caused by
      perfectly normal operation due to a session being closed.
    * Removed some unnecessary synchronization in the streaming lib's
      PacketQueue
    * More quickly clean up the memory used by the streaming lib by
      immediately killing each packet's resend job as soon as it is ACKed (or
      cancelled), so that there are no longer any valid pointers to the
      (potentially 32KB) packet.
    * Fixed the timestamps dumped to stdout when debugging the PacketHandler.
    * Drop packets that would expand our inbound window beyond our maximum
      buffer size (default 32 messages)
    * Always read the ACK/NACK data from the verified packets received, even
      if we are going to drop them
    * Always adjust the window when there are messages ACKed, though do not
      change its size except as before.
    * Streamlined some synchronization in the router's I2CP handling
    * Streamlined some memory allocation in the SAM bridge
    * Default the streaming lib to disconnect on inactivity, rather than send
      an empty message.
2004-12-04 23:43:07 +00:00
1a30cd5f4a 2004-12-03 jrandom
* Toss in a small pool of threads (3) to execute the events queued up with
      the SimpleTimer, as we do currently see the occational event
      notification spiking up to a second or so.
    * Implement a SAM client API in java, useful for event based streaming (or
      for testing the SAM bridge)
    * Added support to shut down the SAM bridge on OOM (useful if the SAM
      bridge is being run outside of the router).
    * Include the SAM test code in the sam.jar
    * Remove an irrelevent warning message from SAM, which was caused by
      perfectly normal operation due to a session being closed.
    * Removed some unnecessary synchronization in the streaming lib's
      PacketQueue
    * More quickly clean up the memory used by the streaming lib by
      immediately killing each packet's resend job as soon as it is ACKed (or
      cancelled), so that there are no longer any valid pointers to the
      (potentially 32KB) packet.
    * Fixed the timestamps dumped to stdout when debugging the PacketHandler.
    * Drop packets that would expand our inbound window beyond our maximum
      buffer size (default 32 messages)
    * Always read the ACK/NACK data from the verified packets received, even
      if we are going to drop them
    * Always adjust the window when there are messages ACKed, though do not
      change its size except as before.
    * Streamlined some synchronization in the router's I2CP handling
    * Streamlined some memory allocation in the SAM bridge
    * Default the streaming lib to disconnect on inactivity, rather than send
      an empty message.
this still doesnt get the BT to where it needs to be, or fix the timeout problem,
but i dont like having so many commits outstanding and these updates are sound
2004-12-04 23:40:50 +00:00
f54687f398 added greenflog.i2p 2004-12-03 20:54:01 +00:00
mpc
9f4b4c5de1 Still trying to get this to compile under VS.NET 2004-12-03 05:59:25 +00:00
mpc
33bfa94229 Added session to naming callback 2004-12-02 23:05:30 +00:00
mpc
61e5f190a6 Updated examples 2004-12-02 22:54:22 +00:00
mpc
a4946272d0 get rid of stdint.h stuff because it confuses the microsoft compiler 2004-12-02 22:16:27 +00:00
8abd99d134 2004-12-01 jrandom
* Fix for a race in the streaming lib as caused by some odd SAM activity
2004-12-02 03:20:03 +00:00
97e8ab7c5b * 2004-12-01 0.4.2.2 released
2004-12-01  jrandom
    * Fixed a stupid typo that inadvertantly allowed persistent HTTP
      connections to work (thanks duck!)
    * Make sure we override the inactivity timeout too
2004-12-02 00:35:17 +00:00
cb930a7ab5 * 2004-12-01 0.4.2.2 released
2004-12-01  jrandom
    * Fixed a stupid typo that inadvertantly allowed persistent HTTP
      connections to work (thanks duck!)
    * Make sure we override the inactivity timeout too
2004-12-02 00:27:27 +00:00
610f1f7dd4 * 2004-12-01 0.4.2.1 released
2004-12-01  jrandom
    * Strip out any of the Accept-* HTTP header lines, and always make sure to
      include the forged User-agent header.
    * Adjust the default read timeout on the eepproxy to 60s, unless
      overridden.
    * Minor tweak on stream shutdown.
2004-12-01 22:31:55 +00:00
516d0b4db8 2004-11-30 jrandom
* Render the burst rate fields on /config.jsp properly (thanks ugha!)
    * Build in a simple timeout to flush data queued into the I2PSocket but
      not yet flushed.
    * Don't explicitly flush after each SAM stream write, but leave it up to
      the [nonblocking] passive flush.
    * Don't whine about 10-99 connection events occurring in a second
    * Don't wait for completion of packets that will not be ACKed (duh)
    * Adjust the congestion window, even if the packet was resent (duh)
    * Make sure to wake up any blocking read()'s when the MessageInputStream
      is close()ed (duh)
    * Never wait more than the disconnect timeout for a write to complete
2004-11-30 23:41:51 +00:00
df61ae5c6f duh. thanks clayboy :) 2004-11-30 00:10:20 +00:00
9f6584b55e 2004-11-29 jrandom
* Minor fixes to avoid unnecessary errors on shutdown (thanks susi!)
2004-11-29 23:24:49 +00:00
e4b41f5bb0 2004-11-29 jrandom
* Reduced contention for local client delivery
    * Drop the new code that munges the wrapper.config.  Instead, updates that
      need to change it will include their own wrapper.config in the
      i2pupdate.zip, overwriting the existing file.  If the file
      "wrapper.config.updated" is included, it is deleted at first opportunity
      and the router shut down, displaying a notice that the router must be
      started again cleanly to allow the changes to the wrapper.config to take
      effect.
    * Properly stop accept()ing I2PSocket connections if we close down the
      session (duh).
    * Make sure we cancel any outstanding Packets in flight when a connection
      is terminated (thanks susi!)
    * Split up the I2PTunnel closing a little further.
2004-11-29 22:27:39 +00:00
8d0cea93e9 2004-11-29 jrandom
* Reduced contention for local client delivery
    * Drop the new code that munges the wrapper.config.  Instead, updates that
      need to change it will include their own wrapper.config in the
      i2pupdate.zip, overwriting the existing file.  If the file
      "wrapper.config.updated" is included, it is deleted at first opportunity
      and the router shut down, displaying a notice that the router must be
      started again cleanly to allow the changes to the wrapper.config to take
      effect.
    * Properly stop accept()ing I2PSocket connections if we close down the
      session (duh).
    * Make sure we cancel any outstanding Packets in flight when a connection
      is terminated (thanks susi!)
    * Split up the I2PTunnel closing a little further.
2004-11-29 21:57:14 +00:00
d294d07919 added bdl.i2p 2004-11-29 20:55:38 +00:00
153eea2bd5 you mean i'm supposed to *test* it? 2004-11-29 03:35:39 +00:00
571e3c5c13 2004-11-28 jrandom
* Accept IP address detection changes with a 2-out-of-3 minimum.
    * As long as the router is up, keep retrying to bind the I2CP listener.
    * Decrease the java service wrapper ping frequency to once every 10
      minutes, rather than once every 5 seconds.
2004-11-29 02:09:27 +00:00
a2d268f3d6 2004-11-28 jrandom
* Accept IP address detection changes with a 2-out-of-3 minimum.
    * As long as the router is up, keep retrying to bind the I2CP listener.
    * Decrease the java service wrapper ping frequency to once every 10
      minutes, rather than once every 5 seconds.
2004-11-29 01:58:38 +00:00
02d456d7a0 added bacardi.i2p and guttersnipe.i2p 2004-11-28 22:47:01 +00:00
mpc
b3626ad86f should've tested it first 2004-11-28 05:11:39 +00:00
mpc
9b6eab451f partial raw handling 2004-11-28 05:10:29 +00:00
72be9b5f04 2004-11-27 jrandom
* Some cleanup and bugfixes for the IP address detection code where we
      only consider connections that have actually sent and received messages
      recently as active, rather than the mere presence of a TCP socket as
      activity.
2004-11-27 21:02:06 +00:00
35e94a7f65 added evil.i2p 2004-11-27 06:56:29 +00:00
8e02586cc9 2004-11-27 jrandom
* Removed the I2PTunnel inactivity timeout thread, since the new streaming
      lib can do that (without an additional per-connection thread).
    * Close the I2PTunnel forwarder threads more aggressively
2004-11-27 05:17:06 +00:00
0b5a640896 2004-11-27 jrandom
* Fix for a fast loop caused by a race in the new streaming library (thanks
      DrWoo, frontier, pwk_, and thetower!)
    * Minor updates to the SimpleTimer and Connection to help track down a
      high CPU usage problem (dumping debug info to stdout/wrapper.log if too
      many events/tasks fire in a second)
    * Minor fixes for races on client disconnects (causing NPEs)
2004-11-27 03:54:17 +00:00
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
a95a968fa8 * 2004-10-18 0.4.1.3 released
2004-10-18  jrandom
    * Allow sending messages with a section of a byte array.
    * Reduced stats published.
2004-10-18 19:07:59 +00:00
6c08941d8b updated curiosity.i2p 2004-10-18 18:23:13 +00:00
e13a5b3865 added blueheron.i2p 2004-10-17 21:39:06 +00:00
9011d5604a 2004-10-17 jrandom
* Don't b0rk on whitespace in the router address.
2004-10-17 21:03:05 +00:00
78aa4ca137 updated files.i2p 2004-10-17 08:45:16 +00:00
93111842df clarify history - we reduce the capacity calc, not the 'isFailing'.
* More aggressively reduce the capacity of peers if their tunnels are
      failing so that we move off them quicker.
2004-10-17 04:00:21 +00:00
88693f8adc 2004-10-16 jrandom
* More aggressively fail peers if their tunnels are failing so that we
      move off them quicker.
    * Simplify some data structure serialization for reuse in the streaming
      lib, as well as add support for signing and verifying partial byte
      arrays.
    * Logging updates
2004-10-17 03:58:08 +00:00
f904b012e9 initial impl for the new streaming lib (saying this isn't done should be obvious, but the
packet spec is at a save point)
2004-10-17 03:47:03 +00:00
cebe0a151f added utansans.i2p 2004-10-16 21:45:55 +00:00
8fffad0891 2004-10-16 jrandom
* Increased the default minimum tunnel test time to 5 seconds, since we
      still see the occational message processing time spike to 2 seconds.
    * Update the SimpleTimer to allow rescheduling a task thats already
      queued (useful for the new streaming lib).
2004-10-16 18:00:47 +00:00
fb1263dad7 2004-10-15 jrandom
* Replaced old minimum tunnel test timeout of 1s with a configurable
      value (router.config property "router.tunnelTestMinimum", with the
      default of 2s).
2004-10-15 17:39:18 +00:00
28c5d6c10d 2004-10-14 jrandom
* Tunnel rejection is no longer a sign of an overwhelmingly loaded
      peer, so don't use it as a key point of the IsFailing calculator.
      We still use it as a key point of the Capacity calculator, however.
2004-10-15 01:20:12 +00:00
e7a6f6836e added irc.orz.i2p 2004-10-14 22:35:11 +00:00
f8ffe016d1 2004-10-14 jrandom
* Allow for a configurable tunnel "growth factor", rather than trying
      to achieve a steady state.  This will let us grow gradually when
      the router is needed more, rather than blindly accepting the request
      or arbitrarily choking it at an averaged value.  Configure this with
      "router.tunnelGrowthFactor" in the router.config (default "1.5").
    * Adjust the tunnel test timeouts dynamically - rather than the old
      flat 30s (!!!) timeout, we set the timeout to 2x the average tunnel
      test time (the deviation factor can be adjusted by setting
      "router.tunnelTestDeviation" to "3.0" or whatever).  This should help
      find the 'good' tunnels.
    * Added some crazy debugging to try and track down an intermittent hang.
2004-10-14 20:02:45 +00:00
ec322f0966 added nano.i2p 2004-10-13 20:42:35 +00:00
0674709fc6 added ragnarok.i2p 2004-10-13 20:33:19 +00:00
d91ac7ef21 2004-10-13 jrandom
* Fix the probabalistic tunnel reject (we always accepted everything,
      since the docs on java.util.Random.nextDouble() are wrong..)
    * Fixed a race on startup (thanks Quadn!)
2004-10-13 19:40:47 +00:00
2f0c3c7baf added marcos.i2p 2004-10-13 16:29:27 +00:00
be68407707 duh (oops) 2004-10-12 21:50:17 +00:00
f799a25aeb 2004-10-12 jrandom
* Disable the probabalistic drop by default (enable via the router config
      property "tcp.dropProbabalistically=true")
    * Disable the actual watchdog shutdown by default, but keep track of more
      variables and log a lot more when it occurs (enable via the router
      config property "watchdog.haltOnHang=true")
    * Implement some tunnel participation smoothing by refusing requests
      probabalistically as our participating tunnel count exceeds the previous
      hour's, or when the 10 minute average tunnel test time exceeds the 60
      minute average tunnel test time.  The probabilities in both cases are
      oldAverage / #current, so if you're suddenly flooded with 200 tunnels
      and you had previously only participated in 50, you'll have a 25% chance
      of accepting a subsequent request.
2004-10-12 21:29:41 +00:00
8329d045f1 confirm removal 2004-10-11 00:23:26 +00:00
503b289240 * 2004-10-10 0.4.1.2 released 2004-10-10 19:33:08 +00:00
35e3bbb862 2004-10-10 cervantes
* Update the I2PTunnel HTTP proxy to strip out the i2paddresshelper from
      the request.
2004-10-10 14:57:15 +00:00
8dc261da79 2004-10-09 jrandom
* Added a watchdog timer to do some baseline liveliness checking to help
      debug some odd errors.
    * Added a pair of summary stats for bandwidth usage, allowing easy export
      with the other stats ("bw.sendBps" and "bw.receiveBps")
    * Trimmed another memory allocation on message reception.
2004-10-10 00:03:25 +00:00
65676f8988 2004-10-08 jrandom
* Revamp the AESInputStream so it doesn't allocate any temporary objects
      during its operation.
2004-10-08 22:53:03 +00:00
730da3aa27 2004-10-08 jrandom
* Don't kill the establisher threads during a soft restart.
    * Attempt to validate the peer's routerInfo earlier during handshaking.
    * Revamp the AESOutputStream so it doesn't allocate any temporary objects
      during its operation.
2004-10-08 18:38:48 +00:00
ff8674bca9 2004-10-07 jrandom
* Reimplement the I2NP reading with less temporary memory allocation.
      There is still significant GC churn, especially under load, but this
      should help.
    * Catch some oddball errors in the transport (message timeout while
      establishing).
2004-10-08 02:08:10 +00:00
c7cfef3b61 2004-10-07 jrandom
* Expire queued messages even when the writer is blocked.
    * Reimplement most of the I2NP writing with less temporary memory
      allocations (I2NP reading still gobbles memory).
2004-10-07 19:19:51 +00:00
32188b1cc0 expose some direct byte formatting methods
allow SHA256 to be run against a partial array
append to the stats.log instead of overwriting it
2004-10-07 16:48:46 +00:00
37479d8c0d logging 2004-10-07 16:45:11 +00:00
f5c7d6576d no need to double b0rk 2004-10-07 16:42:55 +00:00
38c422bbc0 2004-10-06 jrandom
* Implement an active queue management scheme on the TCP transports,
      dropping messages probabalistically as the queue fills up.  The
      estimated queue capacity is determined by the rate at which messages
      have been sent to the peer (averaged at 1, 5, and 60m periods).  As
      we exceed 1/2 of the estimated capacity, we drop messages throughout
      the queue probabalistically with regards to their size.  This is based
      on RFC 2309's RED, with the minimum threshold set to 1/2 the
      estimated connection capacity.  We may want to consider using a send
      rate and queue size measured across all connections, to deal with our
      own local bandwidth saturation, but we'll try the per-con metrics first.
2004-10-06 21:03:51 +00:00
39d4e5ea81 list the shutdown time w/ the clock fudge factor included 2004-10-06 16:47:36 +00:00
4191ad1cbf 2004-10-06 jrandom
* Enable explicit disabling of the systray entirely for windows machines
      with strange configurations: add -Dsystray.disable=true to the java
      command line.  (thanks mihi!)
2004-10-06 13:23:38 +00:00
29287da37c 2004-10-05 jrandom
* Allow peers on the same LAN to communicate with each other safely even
      when they cannot talk to each other through the external address.
2004-10-06 01:12:03 +00:00
98c780415b 2004-10-05 jrandom
* Display how much time is left before the graceful shutdown is complete.
    * Debug some improperly failed messages on timeout or disconnection.
2004-10-05 19:21:47 +00:00
756af9c699 oops 2004-10-05 18:21:44 +00:00
7f9076bb1d updated beyond.i2p (after verification) 2004-10-05 16:04:16 +00:00
2404f1ab9a added b.i2p 2004-10-05 15:57:08 +00:00
64bcfd09ec 2004-10-05 jrandom
* Don't go into a fast busy if an I2PTunnel 'server' is explicitly killed
      (thanks mule!)
    * Handle some more error conditions regarding abruptly closing sockets
      (thanks Jonva!)
2004-10-05 15:38:37 +00:00
6251d22c6e added tinyurl.i2p 2004-10-05 15:26:20 +00:00
de1b4937a1 2004-10-04 jrandom
* Update the shitlist to reject a peer for an exponentially increasing
      period of time (with an upper bounds of an hour).
    * Various minor stat and debugging fixes
2004-10-04 17:30:22 +00:00
d092dd79ba get rid of the really really frequent temporary object creation 2004-10-04 13:53:10 +00:00
a3ba968386 24h time 2004-10-04 01:17:01 +00:00
5ca2b97128 24h time 2004-10-04 01:00:38 +00:00
c9daad1cfd added detonate.i2p 2004-10-04 00:16:13 +00:00
0526d5b53a cli to splot the stat log 2004-10-03 23:53:16 +00:00
34163fb8e4 dont overwrite index.html anymore (0.4.1.2 wont) 2004-10-03 21:06:17 +00:00
98d2d661a8 2004-10-03 jrandom
* Add a new stat logging component to optionally dump the raw stats to
      disk as they are generated, rather than rely upon the summarized data.
      By default, this is off, but the router property "stat.logFilters" can
      be set to a comma delimited list of stats (e.g. "client.sendAckTime")
      which will be written to the file "stats.log" (or whatever the property
      "stat.logFile" is set to).  This can also log profile related stats,
      such as "dbResponseTime" or "tunnelTestResponseTime".
2004-10-03 20:48:43 +00:00
d9f0a0fd74 dont list an explicit webdefault.xml (use the default) 2004-10-03 14:04:21 +00:00
d20d043e0f 2004-10-02 jrandom
* Assure that we quickly fail messages bound for shitlisted peers.
    * Address a race on startup where the first peer contacted could hang the
      router (thanks Romster!)
    * Only whine about an intermittent inability to query the time server once
2004-10-02 19:05:24 +00:00
ce186e1872 2004-10-02 jrandom
* Command line utility to verify a peer's reachability - simply run
      net.i2p.router.transport.tcp.ConnectionHandler hostname port# and it
      will print out whether that peer is reachable or not (using a simple
      verification handshake).
2004-10-02 12:31:15 +00:00
a14da92e1d * 2004-10-01 0.4.1.1 released 2004-10-01 17:23:00 +00:00
2b54d850ea logging 2004-10-01 17:19:37 +00:00
a63c1b19fc 2004-10-01 jrandom
* Handle partial reseeds, caused by seeds going away before the download
      completes (thanks Sugadude!)
2004-10-01 14:35:49 +00:00
34f74cd6ef 2004-10-01 jrandom
* Explicitly refuse IPv6 addresses, since only some peers support
      them and we want fully reachable peers.
2004-10-01 11:49:02 +00:00
c0b8e62135 2004-10-01 jrandom
* Additional error handling for a variety of transport layer errors.
2004-10-01 09:39:14 +00:00
ea24166b8e added identiguy.i2p 2004-10-01 09:32:46 +00:00
178b229d66 *cough* (oops) 2004-09-30 16:44:46 +00:00
276493da65 * 2004-09-30 0.4.1 released (not backwards compatible)
2004-09-30  jrandom
    * Bundle the configuration necessary to run an eepsite out of the box
      with Jetty - simply edit ./eepsite/docroot/index.html and give people
      the key listed on the I2PTunnel configuration page, and its up.
    * Router console cleanup, and some (off by default) tunnels -
      smtp.postman.i2p (port 7659), pop.postman.i2p (port 7660), and
      irc.baffled.i2p (port 7661)
2004-09-30 15:58:54 +00:00
e85dadfef2 added jabber-2.i2p 2004-09-30 13:25:31 +00:00
1c70efb350 added eepsite info 2004-09-30 13:23:01 +00:00
6804a0c564 * always flush the console output (duh)
* deal with some degenerate situations with identities changing and auto IP detection
* some catch-alls for cleaning up the registry on degenerate tunnels
* lots of logging
2004-09-30 13:10:02 +00:00
6eb7ecc2d4 2004-09-30 jrandom
* Bundle the configuration necessary to run an eepsite out of the box
      with Jetty - simply edit ./eepsite/docroot/index.html and give people
      the key listed on the I2PTunnel configuration page, and its up.
plus minor bugfixes / refactoring / logging
2004-09-30 06:57:22 +00:00
f4956b06b6 be more careful on startup 2004-09-30 06:51:28 +00:00
9a2f7c2660 include the timeouts due to inability to find the leaseSet 2004-09-30 00:51:35 +00:00
b6017c558a * updated stats:
- sendsPerFailure: how many partial sends we make when they all fail
- timeoutCongestionInbound: describes how much faster than our average speed we were receiving data when each partial send timed out (in Bps)
- timeoutCongestionMessage: our send processing time when each partial send timed out (in ms)
- timeoutCongestionTunnel: our tunnel test time when each partial send timed out (in ms)
- participatingMessagesProcessedActive: # of messages more than the (most recent) average that a tunnel we were participating in transmitted (for tunnels with more than the average)
* updated to use Writer for rendering the console, so we can do partial writes (and hopefully help debug some kooky threading bugs on kaffe)
2004-09-29 22:54:38 +00:00
62ed6c6a58 * updated stats:
- sendsPerFailure: how many partial sends we make when they all fail
- timeoutCongestionInbound: describes how much faster than our average speed we were receiving data when each partial send timed out (in Bps)
- timeoutCongestionMessage: our send processing time when each partial send timed out (in ms)
- timeoutCongestionTunnel: our tunnel test time when each partial send timed out (in ms)
- participatingMessagesProcessedActive: # of messages more than the (most recent) average that a tunnel we were participating in transmitted (for tunnels with more than the average)
* updated to use Writer for rendering the console, so we can do partial writes (and hopefully help debug some kooky threading bugs on kaffe)
2004-09-29 22:49:19 +00:00
24966c812f javad0c 2004-09-29 20:12:26 +00:00
ea8dc2e0af *nix daemon scripts are gone 2004-09-29 19:38:15 +00:00
010b285e67 2004-09-29 jrandom
* Always wipe the Jetty work directory on startup, so that web updates
      are reflected immediately (Jetty does not honor the cache across
      multiple executions)
in addition, refactor various file ops out of the DataHelper into FileUtil
2004-09-29 19:34:02 +00:00
774231f347 * started reducing the temporary buffers created within various crypto methods , as we've
got some pretty heavy GC churn when under load.  rough estimate is we allocate 5-8x as
much data as we need, copying it all over the place before forwarding it (or processing it).
this should cut down a few of those copies, but not enough yet.  it'd be great to get that
down to 2x.
* lots of logging
2004-09-28 20:33:23 +00:00
ff1dfd8f25 let the algorithm handle writing the output to various places in the output array (so we can cut down on temporary memory allocation) 2004-09-28 16:38:24 +00:00
2741ac195d * protocol doc & impl cleanup
* more defensive programming
* more javadoc updates
2004-09-28 08:34:48 +00:00
cf780e296e bugfixes for autodetection/update of IP address 2004-09-27 18:05:28 +00:00
0361246db0 2004-09-27 jrandom
* Limit the number of connection tags saved to 10,000.  This is a huge
      limit, but consumes no more than 1MB of RAM.  For now, we drop them
      randomly after reaching that size, forcing those dropped peers to use
      a full DH negotiation.
    * HTML cleanup in the console.
2004-09-27 07:57:43 +00:00
63355ecd5b more tcp transport updates (getting closer to the old functionality)
* avoid bad peers
* shitlist appropriately
* include the bandwidth limiter
* add socket timeouts
* deal with *cough* closing connections
* javadocs
2004-09-26 18:11:39 +00:00
0f54ba59fb die phttp die 2004-09-26 15:32:24 +00:00
b67b243ebd the following isn't the end of the 0.4.1 updates, as there are still more things left to clean
up and debug in the new tcp transport, but it all works, and i dont like having big changes
sitting on my local machine (and there's no real need for branching atm)
2004-09-26  jrandom
    * Complete rewrite of the TCP transport with IP autodetection and
      low CPU overhead reconnections.  More concise connectivity errors
      are listed on the /oldconsole.jsp as well.  The IP autodetection works
      by listening to the first person who tells you what your IP address is
      when you have not defined one yourself and you have no other TCP
      connections.
    * Update to the I2NP message format to add transparent verification at
      the I2NP level (beyond standard TCP verification).
    * Remove a potential weakness in our AESEngine's safeEncrypt and safeDecrypt
      implementation (rather than verifying with E(H(key)), we now verify with
      E(H(iv))).
    * The above changes are NOT BACKWARDS COMPATIBLE.
    * Removed all of the old unused PHTTP code.
    * Refactor various methods and clean up some javadoc.
2004-09-26 15:16:44 +00:00
4c29c20613 javadoc fix 2004-09-26 14:50:49 +00:00
mpc
4c2619d948 minor changes 2004-09-24 21:08:00 +00:00
ea5662a4a2 another minor semantic jikes warning 2004-09-23 01:21:33 +00:00
7c1ce777a1 cleanup per jikes' whining (overloaded var names, val overflow, etc) 2004-09-23 01:13:22 +00:00
3bb85f2d61 add some logical exit values (useful for scripting).thanks xolo! 2004-09-23 01:05:40 +00:00
93e36b3113 added jake.i2p 2004-09-23 00:08:51 +00:00
932fb670e3 added anonymnet.i2p sasquotch.i2p orz.i2p microbleu.i2p www/smtp/pop3.postman.i2p 2004-09-22 23:58:37 +00:00
54dce61a95 2004-09-21 jrandom
* Have two tiers of hosts.txt files - the standard "hosts.txt" and
      the new "userhosts.txt".  Updates to I2P will only overwrite the former,
      but values stored in the later take precedence.  Both are queried on
      lookup.
2004-09-22 00:10:26 +00:00
e686c0e0a2 added curiosity.i2p 2004-09-17 19:47:58 +00:00
05acf32f39 2004-09-16 jrandom
* Refactor the TCP transport to deal with changing identities gracefully,
      and to prevent some wasted effort by keeping track of what host+port
      combinations we are connected to (rather than just the identities).  Also
      catch a few configuration errors earlier.
    * Removed no longer relevent methods from the Transport API that were
      exposing ideas that probably shouldn't be exposed.
    * Removed the 0.4.0.1 specific files from i2pupdate.zip (relating to script
      updates)
2004-09-16 23:55:12 +00:00
mpc
67064012c9 added freeciv.nightblade.i2p 2004-09-16 20:50:26 +00:00
10e93c3b1b rename (wtf was i thinking "Calculator"...) 2004-09-16 02:04:40 +00:00
5b2ec1cbb5 moved udp transport to 0.4.4 instead of 1.1 2004-09-14 19:52:46 +00:00
972f701c5c ganttproject.sf.net based plan 2004-09-14 02:19:33 +00:00
51285efbc3 2004-09-13 jrandom
* Update for the SDK reconnection to deal with overflow.
    * Web improvements (@ not # on the /logs.jsp [thanks ugha!] and fixed the
      rounding on lifetime bandwidth used [thanks gott!]).
2004-09-13 03:08:16 +00:00
e2635705f9 added xolotl.i2p 2004-09-11 01:25:24 +00:00
7762107543 added modulus.i2p 2004-09-10 04:11:15 +00:00
9123ad89c8 added links to www.i2p and dev.i2p (thanks pseudonym) 2004-09-09 04:10:25 +00:00
1196 changed files with 136354 additions and 22790 deletions

100
Makefile.gcj Normal file
View File

@ -0,0 +1,100 @@
# Makefile for building native I2P binaries and libraries with GCJ
#
# WARNING: Do not use this yet, as it may explode (etc).
#
GCJ=gcj #/usr/local/gcc-4.0.2/bin/gcj
EXTRA_LD_PATH= #/usr/local/gcc-4.0.2/lib
ANT=ant #/opt/apache-ant-1.6.5/bin/ant
ANT_TARGET=buildclean
NATIVE_DIR=native
##
# Define what jar files get into libi2p.so. The current setup is
# *incredibly* lazy, throwing everything in the .so, rather than
# give each .jar file its own .so.
# i2p.jar: base SDK
# mstreaming.jar: streaming API
# streaming.jar: full streaming lib implementation
# i2ptunnel.jar: I2PTunnel proxy
# sam.jar: SAM bridge and API
# i2psnark.jar: bittorrent client
# router.jar: full I2P router
# jbigi.jar: collection of native optimized GMP routines for crypto
JAR_BASE=i2p.jar mstreaming.jar streaming.jar
JAR_CLIENTS=i2ptunnel.jar sam.jar i2psnark.jar
JAR_ROUTER=router.jar
JAR_JBIGI=jbigi.jar
JAR_XML=xml-apis.jar resolver.jar xercesImpl.jar
JAR_CONSOLE=\
javax.servlet.jar \
commons-el.jar \
commons-logging.jar \
jasper-runtime.jar \
ant-apache-bcel.jar \
ant.jar \
jasper-compiler.jar \
org.mortbay.jetty.jar \
routerconsole.jar
JAR_SUCKER=jdom.jar rome-0.7.jar sucker.jar
LIBI2P_JARS=${JAR_BASE} ${JAR_CLIENTS} ${JAR_ROUTER} ${JAR_JBIGI}
# unfortunately, its not quite ready for most end users, as the
# ${JAR_CONSOLE} fails to compile with:
# org/apache/commons/logging/impl/LogKitLogger.java: In class 'org.apache.commons.logging.impl.LogKitLogger':
# .../LogKitLogger.java: In constructor '(java.lang.String)':
# .../LogKitLogger.java:91: error: cannot find file for class org.apache.log.Hierarchy
# .../LogKitLogger.java:91: error: cannot find file for class org.apache.log.Hierarchy
# .../LogKitLogger.java:104: error: cannot find file for class org.apache.log.Hierarchy
# .../LogKitLogger.java:104: confused by earlier errors, bailing out
#${JAR_CONSOLE}\
#${JAR_XML} \
#${JAR_SUCKER}
#${JAR_CONSOLE}
SYSTEM_PROPS=-DloggerFilenameOverride=logs/log-router-@.txt \
-Dorg.mortbay.http.Version.paranoid=true \
-Dorg.mortbay.util.FileResource.checkAliases=false \
-Dorg.mortbay.xml.XmlParser.NotValidating=true
#SYSTEM_PROPS=-Di2p.weakPRNG=true
OPTIMIZE=-O2
#OPTIMIZE=-O3
LD_LIBRARY_PATH=${EXTRA_LD_PATH}:.
all: jars native
@echo "* Build complete"
jars:
@${ANT} ${ANT_TARGET}
clean: native_clean
native: native_clean native_shared
@echo "* Native code build in ${NATIVE}"
native_clean:
@rm -rf ${NATIVE_DIR}
@mkdir ${NATIVE_DIR}
native_shared: libi2p.so
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2p_dsa --main=net.i2p.crypto.DSAEngine
@echo "* i2p_dsa is a simple test app with the DSA engine and Fortuna PRNG to make sure crypto is working"
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/prng --main=gnu.crypto.prng.Fortuna
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnel --main=net.i2p.i2ptunnel.I2PTunnel
@echo "* i2ptunnel is mihi's I2PTunnel CLI"
@echo " run it as ./i2ptunnel -cli to avoid awt complaints"
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnelctl --main=net.i2p.i2ptunnel.TunnelControllerGroup
@echo "* i2ptunnelctl is a controller for I2PTunnel, reading i2ptunnel.config"
@echo " and launching the appropriate proxies"
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2psnark --main=org.klomp.snark.Snark
@echo "* i2psnark is an anonymous bittorrent client"
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2prouter --main=net.i2p.router.Router
@echo "* i2prouter is the main I2P router"
@echo " it can be used, and while the router console won't load,"
@echo " i2ptunnel will, so it will start all the proxies defined in i2ptunnel.config"
libi2p.so:
@echo "* Building libi2p.so"
@(cd build ; ${GCJ} ${OPTIMIZE} -fPIC -fjni -shared -o ../${NATIVE_DIR}/libi2p.so ${LIBI2P_JARS} ; cd .. )
@ls -l ${NATIVE_DIR}/libi2p.so
@echo "* libi2p.so built"

View File

@ -0,0 +1,43 @@
addressbook v2.0.2 - A simple name resolution mechanism for I2P
addressbook is a simple implementation of subscribable address books for I2P.
Addresses are stored in userhosts.txt and a second copy of the address book is
placed on your eepsite as hosts.txt.
subscriptions.txt contains a list of urls to check for new addresses.
Since the urls are checked in order, and conflicting addresses are not added,
addressbook.subscriptions can be considered to be ranked in order of trust.
The system created by addressbook is similar to the early days of DNS,
when everyone ran a local name server. The major difference is the lack of
authority. Name cannot be guaranteed to be globally unique, but in practise
they probably will be, for a variety of social reasons.
Requirements
************
i2p with a running http proxy
Installation and Usage
**********************
1. Unzip addressbook-%ver.zip into your i2p directory.
2. Restart your router.
The addressbook daemon will automatically run while the router is up.
Aside from the daemon itself, the other elements of the addressbook interface
are the config.txt, myhosts.txt, and subscriptions.txt files found in the addressbook
directory.
config.txt is the configuration file for addressbook.
myhosts.txt is the addressbook master address book. Addresses placed in this file
take precidence over those in the router address book and in remote address books.
If changes are made to this file, they will be reflected in the router address book
and published address book after the next update. Do not make changes directly to the
router address book, as they could be lost during an update.
subscriptions.txt is the subscription list for addressbook. Each entry is an absolute
url to a file in hosts.txt format. Since the list is checked in order, url's should be
listed in order of trust.

View File

@ -0,0 +1,51 @@
<?xml version="1.0"?>
<project name="addressbook" default="war" basedir=".">
<property name="src" value="java/src/addressbook"/>
<property name="build" value="build"/>
<property name="dist" location="dist"/>
<property name="jar" value="addressbook.jar"/>
<property name="war" value="addressbook.war"/>
<target name="init">
<mkdir dir="${build}"/>
<mkdir dir="${dist}"/>
</target>
<target name="clean">
<delete dir="${build}"/>
<delete dir="${dist}"/>
</target>
<target name="distclean" depends="clean" />
<target name="compile" depends="init">
<javac debug="true" deprecation="on" source="1.3" target="1.3"
srcdir="${src}" destdir="${build}">
<classpath>
<pathelement location="../../core/java/build/i2p.jar" />
<pathelement location="../jetty/jettylib/javax.servlet.jar" />
</classpath>
</javac>
</target>
<target name="jar" depends="compile">
<jar basedir="${build}" destfile="${dist}/${jar}">
<manifest>
<attribute name="Main-Class" value="addressbook.Daemon"/>
</manifest>
</jar>
</target>
<target name="war" depends="compile">
<mkdir dir="${dist}/tmp"/>
<mkdir dir="${dist}/tmp/WEB-INF"/>
<mkdir dir="${dist}/tmp/WEB-INF/classes"/>
<copy todir="${dist}/tmp/WEB-INF/classes">
<fileset dir="${build}"/>
</copy>
<war basedir="${dist}/tmp" webxml="web.xml" destfile="${dist}/${war}"/>
<delete dir="${dist}/tmp"/>
</target>
</project>

View File

@ -0,0 +1,43 @@
# This is the configuration file for addressbook.
#
# Options
# *******
# All paths are realitive to i2p/addressbook. Default value for
# each option is given in parentheses.
#
# proxy_host The hostname of your I2P http proxy.
# (localhost)
#
# proxy_port The port of your I2P http proxy. (4444)
#
# master_addressbook The path to your master address book, used for local
# changes only. (myhosts.txt)
#
# router_addressbook The path to the address book used by the router.
# Contains the addresses from your master address book
# and your subscribed address books. (../userhosts.txt)
#
# published_addressbook The path to the copy of your address book made
# available on i2p. (../eepsite/docroot/hosts.txt)
#
# log The path to your addressbook log. (log.txt)
#
# subscriptions The path to your subscription file. (subscriptions.txt)
#
# etags The path to the etags header storage file. (etags)
#
# last_modified The path to the last-modified header storage file.
# (last_modified)
#
# update_delay The time (in hours) between each update. (1)
proxy_host=localhost
proxy_port=4444
master_addressbook=myhosts.txt
router_addressbook=../userhosts.txt
published_addressbook=../eepsite/docroot/hosts.txt
log=log.txt
subscriptions=subscriptions.txt
etags=etags
last_modified=last_modified
update_delay=1

View File

@ -0,0 +1,218 @@
/*
* Copyright (c) 2004 Ragnarok
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package addressbook;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.io.File;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.util.EepGet;
/**
* An address book for storing human readable names mapped to base64 i2p
* destinations. AddressBooks can be created from local and remote files, merged
* together, and written out to local files.
*
* @author Ragnarok
*
*/
public class AddressBook {
private String location;
private Map addresses;
private boolean modified;
/**
* Construct an AddressBook from the contents of the Map addresses.
*
* @param addresses
* A Map containing human readable addresses as keys, mapped to
* base64 i2p destinations.
*/
public AddressBook(Map addresses) {
this.addresses = addresses;
}
/**
* Construct an AddressBook from the contents of the file at url. If the
* remote file cannot be read, construct an empty AddressBook
*
* @param url
* A URL pointing at a file with lines in the format "key=value",
* where key is a human readable name, and value is a base64 i2p
* destination.
*/
public AddressBook(String url, String proxyHost, int proxyPort) {
this.location = url;
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
proxyHost, proxyPort, 0, "addressbook.tmp", url, true,
null);
get.fetch();
try {
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
} catch (IOException exp) {
this.addresses = new HashMap();
}
new File("addressbook.tmp").delete();
}
/**
* Construct an AddressBook from the Subscription subscription. If the
* address book at subscription has not changed since the last time it was
* read or cannot be read, return an empty AddressBook.
*
* @param subscription
* A Subscription instance pointing at a remote address book.
*/
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
this.location = subscription.getLocation();
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
proxyHost, proxyPort, 0, "addressbook.tmp",
subscription.getLocation(), true, subscription.getEtag());
get.fetch();
subscription.setEtag(get.getETag());
try {
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
} catch (IOException exp) {
this.addresses = new HashMap();
}
new File("addressbook.tmp").delete();
}
/**
* Construct an AddressBook from the contents of the file at file. If the
* file cannot be read, construct an empty AddressBook
*
* @param file
* A File pointing at a file with lines in the format
* "key=value", where key is a human readable name, and value is
* a base64 i2p destination.
*/
public AddressBook(File file) {
this.location = file.toString();
try {
this.addresses = ConfigParser.parse(file);
} catch (IOException exp) {
this.addresses = new HashMap();
}
}
/**
* Return a Map containing the addresses in the AddressBook.
*
* @return A Map containing the addresses in the AddressBook, where the key
* is a human readable name, and the value is a base64 i2p
* destination.
*/
public Map getAddresses() {
return this.addresses;
}
/**
* Return the location of the file this AddressBook was constructed from.
*
* @return A String representing either an abstract path, or a url,
* depending on how the instance was constructed.
*/
public String getLocation() {
return this.location;
}
/**
* Return a string representation of the contents of the AddressBook.
*
* @return A String representing the contents of the AddressBook.
*/
public String toString() {
return this.addresses.toString();
}
/**
* Merge this AddressBook with AddressBook other, writing messages about new
* addresses or conflicts to log. Addresses in AddressBook other that are
* not in this AddressBook are added to this AddressBook. In case of a
* conflict, addresses in this AddressBook take precedence
*
* @param other
* An AddressBook to merge with.
* @param log
* The log to write messages about new addresses or conflicts to.
*/
public void merge(AddressBook other, boolean overwrite, Log log) {
Iterator otherIter = other.addresses.keySet().iterator();
while (otherIter.hasNext()) {
String otherKey = (String) otherIter.next();
String otherValue = (String) other.addresses.get(otherKey);
if (otherKey.endsWith(".i2p") && otherValue.length() >= 516) {
if (this.addresses.containsKey(otherKey) && !overwrite) {
if (!this.addresses.get(otherKey).equals(otherValue)
&& log != null) {
log.append("Conflict for " + otherKey + " from "
+ other.location
+ ". Destination in remote address book is "
+ otherValue);
}
} else if (!this.addresses.containsKey(otherKey)
|| !this.addresses.get(otherKey).equals(otherValue)) {
this.addresses.put(otherKey, otherValue);
this.modified = true;
if (log != null) {
log.append("New address " + otherKey
+ " added to address book.");
}
}
}
}
}
/**
* Write the contents of this AddressBook out to the File file. If the file
* cannot be writen to, this method will silently fail.
*
* @param file
* The file to write the contents of this AddressBook too.
*/
public void write(File file) {
if (this.modified) {
try {
ConfigParser.write(this.addresses, file);
} catch (IOException exp) {
}
}
}
/**
* Write this AddressBook out to the file it was read from. Requires that
* AddressBook was constructed from a file on the local filesystem. If the
* file cannot be writen to, this method will silently fail.
*/
public void write() {
this.write(new File(this.location));
}
}

View File

@ -0,0 +1,304 @@
/*
* Copyright (c) 2004 Ragnarok
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package addressbook;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import java.io.*;
/**
* Utility class providing methods to parse and write files in config file
* format, and subscription file format.
*
* @author Ragnarok
*/
public class ConfigParser {
/**
* Strip the comments from a String. Lines that begin with '#' and ';' are
* considered comments, as well as any part of a line after a '#'.
*
* @param inputLine
* A String to strip comments from.
* @return A String without comments, but otherwise identical to inputLine.
*/
public static String stripComments(String inputLine) {
if (inputLine.startsWith(";")) {
return "";
}
if (inputLine.split("#").length > 0) {
return inputLine.split("#")[0];
} else {
return "";
}
}
/**
* Return a Map using the contents of BufferedReader input. input must have
* a single key, value pair on each line, in the format: key=value. Lines
* starting with '#' or ';' are considered comments, and ignored. Lines that
* are obviously not in the format key=value are also ignored.
*
* @param input
* A BufferedReader with lines in key=value format to parse into
* a Map.
* @return A Map containing the key, value pairs from input.
* @throws IOException
* if the BufferedReader cannot be read.
*
*/
public static Map parse(BufferedReader input) throws IOException {
Map result = new HashMap();
String inputLine;
inputLine = input.readLine();
while (inputLine != null) {
inputLine = ConfigParser.stripComments(inputLine);
String[] splitLine = inputLine.split("=");
if (splitLine.length == 2) {
result.put(splitLine[0].trim(), splitLine[1].trim());
}
inputLine = input.readLine();
}
input.close();
return result;
}
/**
* Return a Map using the contents of the File file. See parseBufferedReader
* for details of the input format.
*
* @param file
* A File to parse.
* @return A Map containing the key, value pairs from file.
* @throws IOException
* if file cannot be read.
*/
public static Map parse(File file) throws IOException {
FileInputStream fileStream = new FileInputStream(file);
BufferedReader input = new BufferedReader(new InputStreamReader(
fileStream));
return ConfigParser.parse(input);
}
/**
* Return a Map using the contents of the String string. See
* parseBufferedReader for details of the input format.
*
* @param string
* A String to parse.
* @return A Map containing the key, value pairs from string.
* @throws IOException
* if file cannot be read.
*/
public static Map parse(String string) throws IOException {
StringReader stringReader = new StringReader(string);
BufferedReader input = new BufferedReader(stringReader);
return ConfigParser.parse(input);
}
/**
* Return a Map using the contents of the File file. If file cannot be read,
* use map instead, and write the result to where file should have been.
*
* @param file
* A File to attempt to parse.
* @param map
* A Map to use as the default, if file fails.
* @return A Map containing the key, value pairs from file, or if file
* cannot be read, map.
*/
public static Map parse(File file, Map map) {
Map result = new HashMap();
try {
result = ConfigParser.parse(file);
} catch (IOException exp) {
result = map;
try {
ConfigParser.write(result, file);
} catch (IOException exp2) {
}
}
return result;
}
/**
* Return a List where each element is a line from the BufferedReader input.
*
* @param input
* A BufferedReader to parse.
* @return A List consisting of one element for each line in input.
* @throws IOException
* if input cannot be read.
*/
public static List parseSubscriptions(BufferedReader input)
throws IOException {
List result = new LinkedList();
String inputLine = input.readLine();
while (inputLine != null) {
inputLine = ConfigParser.stripComments(inputLine).trim();
if (inputLine.length() > 0) {
result.add(inputLine);
}
inputLine = input.readLine();
}
input.close();
return result;
}
/**
* Return a List where each element is a line from the File file.
*
* @param file
* A File to parse.
* @return A List consisting of one element for each line in file.
* @throws IOException
* if file cannot be read.
*/
public static List parseSubscriptions(File file) throws IOException {
FileInputStream fileStream = new FileInputStream(file);
BufferedReader input = new BufferedReader(new InputStreamReader(
fileStream));
return ConfigParser.parseSubscriptions(input);
}
/**
* Return a List where each element is a line from the String string.
*
* @param string
* A String to parse.
* @return A List consisting of one element for each line in string.
* @throws IOException
* if string cannot be read.
*/
public static List parseSubscriptions(String string) throws IOException {
StringReader stringReader = new StringReader(string);
BufferedReader input = new BufferedReader(stringReader);
return ConfigParser.parseSubscriptions(input);
}
/**
* Return a List using the contents of the File file. If file cannot be
* read, use list instead, and write the result to where file should have
* been.
*
* @param file
* A File to attempt to parse.
* @param string
* A List to use as the default, if file fails.
* @return A List consisting of one element for each line in file, or if
* file cannot be read, list.
*/
public static List parseSubscriptions(File file, List list) {
List result = new LinkedList();
try {
result = ConfigParser.parseSubscriptions(file);
} catch (IOException exp) {
result = list;
try {
ConfigParser.writeSubscriptions(result, file);
} catch (IOException exp2) {
}
}
return result;
}
/**
* Write contents of Map map to BufferedWriter output. Output is written
* with one key, value pair on each line, in the format: key=value.
*
* @param map
* A Map to write to output.
* @param output
* A BufferedWriter to write the Map to.
* @throws IOException
* if the BufferedWriter cannot be written to.
*/
public static void write(Map map, BufferedWriter output) throws IOException {
Iterator keyIter = map.keySet().iterator();
while (keyIter.hasNext()) {
String key = (String) keyIter.next();
output.write(key + "=" + (String) map.get(key));
output.newLine();
}
output.close();
}
/**
* Write contents of Map map to the File file. Output is written
* with one key, value pair on each line, in the format: key=value.
*
* @param map
* A Map to write to file.
* @param file
* A File to write the Map to.
* @throws IOException
* if file cannot be written to.
*/
public static void write(Map map, File file) throws IOException {
ConfigParser
.write(map, new BufferedWriter(new FileWriter(file, false)));
}
/**
* Write contents of List list to BufferedReader output. Output is written
* with each element of list on a new line.
*
* @param list
* A List to write to file.
* @param output
* A BufferedReader to write list to.
* @throws IOException
* if output cannot be written to.
*/
public static void writeSubscriptions(List list, BufferedWriter output)
throws IOException {
Iterator iter = list.iterator();
while (iter.hasNext()) {
output.write((String) iter.next());
output.newLine();
}
output.close();
}
/**
* Write contents of List list to File file. Output is written with each
* element of list on a new line.
*
* @param list
* A List to write to file.
* @param file
* A File to write list to.
* @throws IOException
* if output cannot be written to.
*/
public static void writeSubscriptions(List list, File file)
throws IOException {
ConfigParser.writeSubscriptions(list, new BufferedWriter(
new FileWriter(file, false)));
}
}

View File

@ -0,0 +1,191 @@
/*
* Copyright (c) 2004 Ragnarok
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package addressbook;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;
import java.io.File;
/**
* Main class of addressbook. Performs updates, and runs the main loop.
*
* @author Ragnarok
*
*/
public class Daemon {
public static final String VERSION = "2.0.3";
private static final Daemon _instance = new Daemon();
/**
* Update the router and published address books using remote data from the
* subscribed address books listed in subscriptions.
*
* @param master
* The master AddressBook. This address book is never
* overwritten, so it is safe for the user to write to.
* @param router
* The router AddressBook. This is the address book read by
* client applications.
* @param published
* The published AddressBook. This address book is published on
* the user's eepsite so that others may subscribe to it.
* @param subscriptions
* A SubscriptionList listing the remote address books to update
* from.
* @param log
* The log to write changes and conflicts to.
*/
public void update(AddressBook master, AddressBook router,
File published, SubscriptionList subscriptions, Log log) {
router.merge(master, true, null);
Iterator iter = subscriptions.iterator();
while (iter.hasNext()) {
router.merge((AddressBook) iter.next(), false, log);
}
router.write();
if (published != null)
router.write(published);
subscriptions.write();
}
/**
* Run an update, using the Map settings to provide the parameters.
*
* @param settings
* A Map containg the parameters needed by update.
* @param home
* The directory containing addressbook's configuration files.
*/
public void update(Map settings, String home) {
File masterFile = new File(home, (String) settings
.get("master_addressbook"));
File routerFile = new File(home, (String) settings
.get("router_addressbook"));
File published = null;
if ("true".equals(settings.get("should_publish")))
published = new File(home, (String) settings
.get("published_addressbook"));
File subscriptionFile = new File(home, (String) settings
.get("subscriptions"));
File logFile = new File(home, (String) settings.get("log"));
File etagsFile = new File(home, (String) settings.get("etags"));
File lastModifiedFile = new File(home, (String) settings
.get("last_modified"));
AddressBook master = new AddressBook(masterFile);
AddressBook router = new AddressBook(routerFile);
List defaultSubs = new LinkedList();
defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
etagsFile, lastModifiedFile, defaultSubs, (String) settings
.get("proxy_host"), Integer.parseInt((String) settings.get("proxy_port")));
Log log = new Log(logFile);
update(master, router, published, subscriptions, log);
}
/**
* Load the settings, set the proxy, then enter into the main loop. The main
* loop performs an immediate update, and then an update every number of
* hours, as configured in the settings file.
*
* @param args
* Command line arguments. If there are any arguments provided,
* the first is taken as addressbook's home directory, and the
* others are ignored.
*/
public static void main(String[] args) {
_instance.run(args);
}
public void run(String[] args) {
String settingsLocation = "config.txt";
Map settings = new HashMap();
String home;
if (args.length > 0) {
home = args[0];
} else {
home = ".";
}
Map defaultSettings = new HashMap();
defaultSettings.put("proxy_host", "localhost");
defaultSettings.put("proxy_port", "4444");
defaultSettings.put("master_addressbook", "../userhosts.txt");
defaultSettings.put("router_addressbook", "../hosts.txt");
defaultSettings.put("published_addressbook", "../eepsite/docroot/hosts.txt");
defaultSettings.put("should_publish", "false");
defaultSettings.put("log", "log.txt");
defaultSettings.put("subscriptions", "subscriptions.txt");
defaultSettings.put("etags", "etags");
defaultSettings.put("last_modified", "last_modified");
defaultSettings.put("update_delay", "12");
File homeFile = new File(home);
if (!homeFile.exists()) {
boolean created = homeFile.mkdirs();
if (created)
System.out.println("INFO: Addressbook directory " + homeFile.getName() + " created");
else
System.out.println("ERROR: Addressbook directory " + homeFile.getName() + " could not be created");
}
File settingsFile = new File(homeFile, settingsLocation);
settings = ConfigParser.parse(settingsFile, defaultSettings);
// wait
try {
Thread.currentThread().sleep(5*60*1000);
} catch (InterruptedException ie) {}
while (true) {
long delay = Long.parseLong((String) settings.get("update_delay"));
if (delay < 1) {
delay = 1;
}
update(settings, home);
try {
synchronized (this) {
wait(delay * 60 * 60 * 1000);
}
} catch (InterruptedException exp) {
}
settings = ConfigParser.parse(settingsFile, defaultSettings);
}
}
/**
* Call this to get the addressbook to reread its config and
* refetch its subscriptions.
*/
public static void wakeup() {
synchronized (_instance) {
_instance.notifyAll();
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2004 Ragnarok
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package addressbook;
/**
* A thread that waits five minutes, then runs the addressbook daemon.
*
* @author Ragnarok
*
*/
public class DaemonThread extends Thread {
private String[] args;
/**
* Construct a DaemonThread with the command line arguments args.
* @param args
* A String array to pass to Daemon.main().
*/
public DaemonThread(String[] args) {
this.args = args;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
//try {
// Thread.sleep(5 * 60 * 1000);
//} catch (InterruptedException exp) {
//}
Daemon.main(this.args);
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2004 Ragnarok
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package addressbook;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
/**
* A simple log with automatic time stamping.
*
* @author Ragnarok
*
*/
public class Log {
private File file;
/**
* Construct a Log instance that writes to the File file.
*
* @param file
* A File for the log to write to.
*/
public Log(File file) {
this.file = file;
}
/**
* Write entry to a new line in the log, with appropriate time stamp.
*
* @param entry
* A String containing a message to append to the log.
*/
public void append(String entry) {
try {
BufferedWriter bw = new BufferedWriter(new FileWriter(this.file,
true));
String timestamp = new Date().toString();
bw.write(timestamp + " -- " + entry);
bw.newLine();
bw.close();
} catch (IOException exp) {
}
}
/**
* Return the File that the Log is writing to.
*
* @return The File that the log is writing to.
*/
public File getFile() {
return this.file;
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2004 Ragnarok
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package addressbook;
import javax.servlet.GenericServlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
/**
* A wrapper for addressbook to allow it to be started as a web application.
*
* @author Ragnarok
*
*/
public class Servlet extends GenericServlet {
/* (non-Javadoc)
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
public void service(ServletRequest request, ServletResponse response) {
}
/* (non-Javadoc)
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
*/
public void init(ServletConfig config) {
try {
super.init(config);
} catch (ServletException exp) {
}
String[] args = new String[1];
args[0] = config.getInitParameter("home");
DaemonThread thread = new DaemonThread(args);
thread.setDaemon(true);
thread.start();
System.out.println("INFO: Starting Addressbook " + Daemon.VERSION);
System.out.println("INFO: config root under " + args[0]);
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2004 Ragnarok
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package addressbook;
/**
* A subscription to a remote address book.
*
* @author Ragnarok
*
*/
public class Subscription {
private String location;
private String etag;
private String lastModified;
/**
* Construct a Subscription pointing to the address book at location, that
* was last read at the time represented by etag and lastModified.
*
* @param location
* A String representing a url to a remote address book.
* @param etag
* The etag header that we recieved the last time we read this
* subscription.
* @param lastModified
* the last-modified header we recieved the last time we read
* this subscription.
*/
public Subscription(String location, String etag, String lastModified) {
this.location = location;
this.etag = etag;
this.lastModified = lastModified;
}
/**
* Return the location this Subscription points at.
*
* @return A String representing a url to a remote address book.
*/
public String getLocation() {
return this.location;
}
/**
* Return the etag header that we recieved the last time we read this
* subscription.
*
* @return A String containing the etag header.
*/
public String getEtag() {
return this.etag;
}
/**
* Set the etag header.
*
* @param etag
* A String containing the etag header.
*/
public void setEtag(String etag) {
this.etag = etag;
}
/**
* Return the last-modified header that we recieved the last time we read
* this subscription.
*
* @return A String containing the last-modified header.
*/
public String getLastModified() {
return this.lastModified;
}
/**
* Set the last-modified header.
*
* @param lastModified
* A String containing the last-modified header.
*/
public void setLastModified(String lastModified) {
this.lastModified = lastModified;
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2004 Ragnarok
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package addressbook;
import java.util.Iterator;
import java.util.List;
/**
* An iterator over the subscriptions in a SubscriptionList. Note that this iterator
* returns AddressBook objects, and not Subscription objects.
*
* @author Ragnarok
*/
public class SubscriptionIterator implements Iterator {
private Iterator subIterator;
private String proxyHost;
private int proxyPort;
/**
* Construct a SubscriptionIterator using the Subscriprions in List subscriptions.
*
* @param subscriptions
* List of Subscription objects that represent address books.
*/
public SubscriptionIterator(List subscriptions, String proxyHost, int proxyPort) {
this.subIterator = subscriptions.iterator();
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
}
/* (non-Javadoc)
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return this.subIterator.hasNext();
}
/* (non-Javadoc)
* @see java.util.Iterator#next()
*/
public Object next() {
Subscription sub = (Subscription) this.subIterator.next();
return new AddressBook(sub, this.proxyHost, this.proxyPort);
}
/* (non-Javadoc)
* @see java.util.Iterator#remove()
*/
public void remove() {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright (c) 2004 Ragnarok
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package addressbook;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.io.File;
import java.io.IOException;
/**
* A list of Subscriptions loaded from a file.
*
* @author Ragnarok
*
*/
public class SubscriptionList {
private List subscriptions;
private File etagsFile;
private File lastModifiedFile;
private String proxyHost;
private int proxyPort;
/**
* Construct a SubscriptionList using the urls from locationsFile and, if
* available, the etags and last-modified headers loaded from etagsFile and
* lastModifiedFile.
*
* @param locationsFile
* A file containing one url on each line.
* @param etagsFile
* A file containg the etag headers used for conditional GET. The
* file is in the format "url=etag".
* @param lastModifiedFile
* A file containg the last-modified headers used for conditional
* GET. The file is in the format "url=leastmodified".
*/
public SubscriptionList(File locationsFile, File etagsFile,
File lastModifiedFile, List defaultSubs, String proxyHost,
int proxyPort) {
this.subscriptions = new LinkedList();
this.etagsFile = etagsFile;
this.lastModifiedFile = lastModifiedFile;
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
Map etags;
Map lastModified;
String location;
List locations = ConfigParser.parseSubscriptions(locationsFile,
defaultSubs);
try {
etags = ConfigParser.parse(etagsFile);
} catch (IOException exp) {
etags = new HashMap();
}
try {
lastModified = ConfigParser.parse(lastModifiedFile);
} catch (IOException exp) {
lastModified = new HashMap();
}
Iterator iter = locations.iterator();
while (iter.hasNext()) {
location = (String) iter.next();
this.subscriptions.add(new Subscription(location, (String) etags
.get(location), (String) lastModified.get(location)));
}
}
/**
* Return an iterator over the AddressBooks represented by the Subscriptions
* in this SubscriptionList.
*
* @return A SubscriptionIterator.
*/
public SubscriptionIterator iterator() {
return new SubscriptionIterator(this.subscriptions, this.proxyHost,
this.proxyPort);
}
/**
* Write the etag and last-modified headers for each Subscription to files.
*/
public void write() {
Iterator iter = this.subscriptions.iterator();
Subscription sub;
Map etags = new HashMap();
Map lastModified = new HashMap();
while (iter.hasNext()) {
sub = (Subscription) iter.next();
if (sub.getEtag() != null) {
etags.put(sub.getLocation(), sub.getEtag());
}
if (sub.getLastModified() != null) {
lastModified.put(sub.getLocation(), sub.getLastModified());
}
}
try {
ConfigParser.write(etags, this.etagsFile);
ConfigParser.write(lastModified, this.lastModifiedFile);
} catch (IOException exp) {
}
}
}

View File

@ -0,0 +1,10 @@
# addressbook master address book. Addresses placed in this file take precidence
# over those in the router address book and in remote address books. If changes
# are made to this file, they will be reflected in the router address book and
# published address book after the next update.
#
# Do not make changes directly to the router address book, as they could be lost
# during an update.
#
# This file takes addresses in the hosts.txt format, i.e.
# example.i2p=somereallylongbase64thingAAAA

View File

@ -0,0 +1,7 @@
# Subscription list for addressbook
#
# Each entry is an absolute url to a file in hosts.txt format.
# Since the list is checked in order, url's should be listed in order of trust.
#
http://dev.i2p/i2p/hosts.txt
http://duck.i2p/hosts.txt

16
apps/addressbook/web.xml Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
<servlet>
<servlet-name>addressbook</servlet-name>
<servlet-class>addressbook.Servlet</servlet-class>
<init-param>
<param-name>home</param-name>
<param-value>./addressbook</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
</web-app>

View File

@ -111,12 +111,12 @@ public class Bogobot extends PircBot {
_botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh");
_ircChannel = config.getProperty("ircChannel", "#i2p-chat");
_ircServer = config.getProperty("ircServer", "irc.duck.i2p");
_ircServer = config.getProperty("ircServer", "irc.postman.i2p");
_ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668"));
_isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue();
_loggedHostnamePattern = config.getProperty("loggedHostnamePattern", "");
_logFilePrefix = config.getProperty("logFilePrefix", "irc.duck.i2p.i2p-chat");
_logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat");
_logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY);
_isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue();

106
apps/fortuna/build.xml Normal file
View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="fortuna">
<property name="cvs.base.dir" value="java/gnu-crypto" />
<property name="cvs.etc.dir" value="${cvs.base.dir}/etc" />
<property name="cvs.lib.dir" value="${cvs.base.dir}/lib" />
<property name="cvs.object.dir" value="${cvs.base.dir}/classes" />
<property name="cvs.base.crypto.object.dir" value="${cvs.object.dir}/gnu/crypto" />
<property name="cvs.cipher.object.dir" value="${cvs.base.crypto.object.dir}/cipher" />
<property name="cvs.hash.object.dir" value="${cvs.base.crypto.object.dir}/hash" />
<property name="cvs.prng.object.dir" value="${cvs.base.crypto.object.dir}/prng" />
<patternset id="fortuna.files">
<include name="${cvs.base.crypto.object.dir}/Registry.class"/>
<include name="${cvs.prng.object.dir}/Fortuna*.class"/>
<include name="${cvs.prng.object.dir}/BasePRNG.class"/>
<include name="${cvs.prng.object.dir}/RandomEventListener.class"/>
<include name="${cvs.prng.object.dir}/IRandom.class"/>
<include name="${cvs.cipher.object.dir}/CipherFactory.class"/>
<include name="${cvs.cipher.object.dir}/IBlockCipher.class"/>
<include name="${cvs.hash.object.dir}/HashFactory.class"/>
<include name="${cvs.hash.object.dir}/IMessageDigest.class"/>
</patternset>
<target name="all" depends="build,jar"
description="Create and test the custom Fortuna library" />
<target name="build" depends="-init,checkout"
description="Build the source and tests">
<ant dir="${cvs.base.dir}" target="jar" />
</target>
<target name="builddep" />
<target name="checkout" depends="-init" unless="cvs.source.available"
description="Check out GNU Crypto sources from CVS HEAD">
<cvs cvsRoot=":ext:anoncvs@savannah.gnu.org:/cvsroot/gnu-crypto"
cvsRsh="ssh"
dest="java"
package="gnu-crypto" />
</target>
<target name="clean"
description="Remove generated tests and object files">
<ant dir="${cvs.base.dir}" target="clean" />
</target>
<target name="cleandep" />
<target name="compile" />
<target name="distclean" depends="clean"
description="Remove all generated files">
<delete dir="build" />
<delete dir="jartemp" />
<!--
Annoyingly the GNU Crypto distclean task called here doesn't clean
*all* derived files from java/gnu-crypto/lib like it should.....
-->
<ant dir="${cvs.base.dir}" target="distclean" />
<!--
.....and so we mop up the rest ourselves.
-->
<delete dir="${cvs.lib.dir}" />
</target>
<target name="-init">
<available property="cvs.source.available" file="${cvs.base.dir}" />
</target>
<target name="jar" depends="build"
description="Create the custom Fortuna jar library">
<delete dir="build" />
<delete dir="jartemp" />
<mkdir dir="build" />
<mkdir dir="jartemp/${cvs.object.dir}" />
<copy todir="jartemp">
<fileset dir=".">
<patternset refid="fortuna.files" />
</fileset>
</copy>
<jar basedir="jartemp/${cvs.object.dir}" jarfile="build/fortuna.jar">
<manifest>
<section name="fortuna">
<attribute name="Implementation-Title" value="I2P Custom GNU Crypto Fortuna Library" />
<attribute name="Implementation-Version" value="CVS HEAD" />
<attribute name="Implementation-Vendor" value="Free Software Foundation" />
<attribute name="Implementation-Vendor-Id" value="FSF" />
<attribute name="Implementation-URL" value="http://www.gnu.org/software/gnu-crypto" />
</section>
</manifest>
</jar>
<delete dir="jartemp" />
</target>
<target name="test" depends="jar"
description="Perform crypto tests on custom Fortuna jar library" />
<!--
Add this when Fortuna tests are added to GNU Crypto, else write some
-->
<target name="update" depends="checkout"
description="Update GNU Crypto sources to latest CVS HEAD">
<cvs command="update -d" cvsRsh="ssh" dest="java/gnu-crypto" />
</target>
</project>

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;
}
}
}
}

340
apps/i2psnark/COPYING Normal file
View File

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

24
apps/i2psnark/TODO Normal file
View File

@ -0,0 +1,24 @@
- I2PSnark:
- add multitorrent support by checking the metainfo hash in the
PeerAcceptor and feeding it off to the appropriate coordinator
- add a web interface
- BEncode
- Byte array length indicator can overflow.
- Support really big BigNums (only 256 chars allowed now)
- Better BEValue toString(). Uses stupid heuristic now for debugging.
- Implemented bencoding.
- Remove application level hack to calculate sha1 hash for metainfo
(But can it be done as efficiently?)
- Storage
- Check file name filter.
- TrackerClient
- Support undocumented &numwant= request.
- PeerCoordinator
- Disconnect from other seeds as soon as you are a seed yourself.
- Text UI
- Make it completely silent.

View File

@ -0,0 +1 @@
Mark Wielaard <mark@klomp.org>

View File

@ -0,0 +1,487 @@
2003-06-27 14:24 Mark Wielaard <mark@klomp.org>
* README: Update version number and explain new features.
2003-06-27 13:51 Mark Wielaard <mark@klomp.org>
* Makefile, org/klomp/snark/GnomeInfoWindow.java,
org/klomp/snark/GnomePeerList.java,
org/klomp/snark/PeerCoordinator.java,
org/klomp/snark/SnarkGnome.java: Add GnomeInfoWindow.
2003-06-27 00:37 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Snark.java: Implement 'info' and 'list' commands.
2003-06-27 00:05 Mark Wielaard <mark@klomp.org>
* Makefile, org/klomp/snark/GnomePeerList.java,
org/klomp/snark/SnarkGnome.java: Add GnomePeerList to show state of
connected peers.
2003-06-27 00:04 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: Peer.java, PeerID.java: Make Comparable.
2003-06-23 23:32 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerMonitorTask.java: Correctly update
lastDownloaded and lastUploaded.
2003-06-23 23:20 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Snark.java: When checking storage use the
MetaInfo from the storage.
2003-06-23 21:47 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Storage.java: Fill piece hashes, not info hashes.
2003-06-23 21:42 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/MetaInfo.java: New package private
getPieceHashes() method.
2003-06-22 19:49 Mark Wielaard <mark@klomp.org>
* README, TODO, org/klomp/snark/Snark.java: Add new command line
switch --no-commands. Don't read interactive commands or show
usage info.
2003-06-22 19:26 Mark Wielaard <mark@klomp.org>
* Makefile, org/klomp/snark/PeerCheckerTask.java,
org/klomp/snark/PeerMonitorTask.java, org/klomp/snark/Snark.java:
Split peer statistic reporting from PeerCheckerTask into
PeerMonitorTask. Use new task in Snark text ui.
2003-06-22 18:32 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Snark.java: Only print peer id when debug level
is INFO or higher.
2003-06-22 18:00 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/ShutdownListener.java: Add new ShutdownListener
interface.
2003-06-22 17:18 Mark Wielaard <mark@klomp.org>
* TODO: Text UI item to not read from stdin.
2003-06-22 17:18 Mark Wielaard <mark@klomp.org>
* snark-gnome.sh: kaffe java-gnome support (but crashes hard at the
moment).
2003-06-22 14:04 Mark Wielaard <mark@klomp.org>
* Makefile, org/klomp/snark/CoordinatorListener.java,
org/klomp/snark/PeerCoordinator.java,
org/klomp/snark/ProgressListener.java, org/klomp/snark/Snark.java,
org/klomp/snark/SnarkGnome.java,
org/klomp/snark/SnarkShutdown.java, org/klomp/snark/Storage.java,
org/klomp/snark/StorageListener.java: Split ProgressListener into
Storage, Coordinator and Shutdown listener.
2003-06-20 19:06 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: PeerCoordinator.java, Snark.java,
SnarkGnome.java, Storage.java: Progress listeners for both Storage
and PeerCoordinator.
2003-06-20 14:50 Mark Wielaard <mark@klomp.org>
* Makefile, org/klomp/snark/PeerCoordinator.java,
org/klomp/snark/ProgressListener.java,
org/klomp/snark/SnarkGnome.java: Add ProgressListener.
2003-06-20 13:22 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/SnarkGnome.java: Add Pieces collected field.
2003-06-20 12:26 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: PeerCoordinator.java, PeerListener.java,
PeerState.java: Add PeerListener.downloaded() which gets called on
chunk updates. Keep PeerCoordinator.downloaded up to date using
this remove adjusting in gotPiece() except when we receive a bad
piece.
2003-06-16 00:27 Mark Wielaard <mark@klomp.org>
* Makefile, snark-gnome.sh, org/klomp/snark/Snark.java,
org/klomp/snark/SnarkGnome.java: Start of a Gnome GUI.
2003-06-05 13:19 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerCoordinator.java: Don't remove a BAD piece
from the wantedPieces list. Revert to synchronizing on
wantedPieces for all relevant sections.
2003-06-03 21:09 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Snark.java: Only call readLine() when !quit.
Always print exception when fatal() is called.
2003-06-01 23:12 Mark Wielaard <mark@klomp.org>
* README: Set release version to 0.4.
2003-06-01 22:59 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerConnectionIn.java: Handle negative length
prefixes (terminates connection).
2003-06-01 21:34 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: Snark.java, SnarkShutdown.java: Implement
correct shutdown and read commands from stdin.
2003-06-01 21:34 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/TrackerInfo.java: Check that interval and peers
list actually exist.
2003-06-01 21:33 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Storage.java: Implement close().
2003-06-01 21:05 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Fix debug logging.
2003-06-01 20:55 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerCoordinator.java: Implement halt().
2003-06-01 20:55 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/ConnectionAcceptor.java: Rename stop() to halt().
2003-06-01 17:35 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Drop lock on this when calling
addRequest() from havePiece().
2003-06-01 14:46 Mark Wielaard <mark@klomp.org>
* README, org/klomp/snark/ConnectionAcceptor.java,
org/klomp/snark/HttpAcceptor.java, org/klomp/snark/Peer.java,
org/klomp/snark/PeerCheckerTask.java,
org/klomp/snark/PeerConnectionIn.java,
org/klomp/snark/PeerConnectionOut.java,
org/klomp/snark/PeerCoordinator.java,
org/klomp/snark/PeerState.java, org/klomp/snark/Snark.java,
org/klomp/snark/SnarkShutdown.java, org/klomp/snark/Storage.java,
org/klomp/snark/Tracker.java, org/klomp/snark/TrackerClient.java:
Add debug/log level.
2003-05-31 23:04 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java: Use
just one lock (peers) for all synchronization (even for
wantedPieces). Let PeerChecker handle real disconnect and keep
count of uploaders.
2003-05-31 22:29 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: Peer.java, PeerConnectionIn.java: Set state to
null on first disconnect() call. So always check whether it might
already be null. Helps disconnect check.
2003-05-31 22:27 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerConnectionOut.java: Don't explicitly close
the DataOutputStream (if another thread is using it libgcj seems to
not like it very much).
2003-05-30 21:33 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerConnectionOut.java: Cancel
(un)interested/(un)choke when (inverse) is still in send queue.
Remove pieces from send queue when choke message is actaully send.
2003-05-30 19:32 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Make sure listener.wantPiece(int)
is never called while lock on this is held.
2003-05-30 19:00 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerConnectionOut.java: Indentation cleanup.
2003-05-30 17:50 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Storage.java: Only synchronize on bitfield as
long as necessary.
2003-05-30 17:43 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Tracker.java: Identing cleanup.
2003-05-30 16:32 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Better error message.
2003-05-30 15:11 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Make sure not to hold the lock on
this when calling the listener to prevent deadlocks. Implement
handling and sending of cancel messages.
2003-05-30 14:50 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerCoordinator.java: First check if we still
want a piece before trying to add it to the Storage.
2003-05-30 14:49 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerConnectionOut.java: Implement
sendCancel(Request). Add cancelRequest(int, int, int).
2003-05-30 14:46 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Request.java: Add hashCode() and equals(Object)
methods.
2003-05-30 14:45 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Peer.java: Fix wheter -> whether javadoc
comments. Mark state null immediatly after calling
listener.disconnected(). Call PeerState.havePiece() not
PeerConnectionOut.sendHave() directly.
2003-05-25 19:23 Mark Wielaard <mark@klomp.org>
* TODO: Add PeerCoordinator TODO for connecting to seeds.
2003-05-23 12:12 Mark Wielaard <mark@klomp.org>
* Makefile: Create class files with jikes again.
2003-05-18 22:01 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java:
Prefer to (optimistically) unchoke first those peers that unchoked
us. And make sure to not unchoke a peer that we just choked.
2003-05-18 21:48 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Peer.java: Fix isChoked() to not always return
true.
2003-05-18 14:46 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: Peer.java, PeerCheckerTask.java,
PeerCoordinator.java, PeerState.java: Remove separate Peer
downloading/uploading states. Keep choke and interest always up to
date. Uploading is now just when we are not choking the peer.
Downloading is now defined as being unchoked and interesting.
CHECK_PERIOD is now 20 seconds. MAX_CONNECTIONS is now 24.
MAX_DOWNLOADERS doesn't exists anymore. We download whenever we can
from peers.
2003-05-18 13:57 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerConnectionOut.java: Remove piece messages
from queue when we are choking. (They will have to be rerequested
when we unchoke the peer again.)
2003-05-15 00:08 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Ignore missed chunk requests,
don't requeue them.
2003-05-15 00:06 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Request.java: Add sanity check
2003-05-10 15:47 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Snark.java: Add extra '(' to usage message.
2003-05-10 15:22 Mark Wielaard <mark@klomp.org>
* README: Set version to 0.3 (The Bakers Tale).
2003-05-10 15:17 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Mention received piece in warning
message.
2003-05-10 03:20 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: PeerConnectionIn.java, PeerState.java,
Request.java: Remove currentRequest and handle all piece messages
from the lastRequested list.
2003-05-09 20:02 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Fix nothing requested warning
message.
2003-05-09 19:59 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerConnectionOut.java: Piece messages are big.
So if there are other (control) messages make sure they are send
first. Also remove request messages from the queue if we are
currently being choked to prevent them from being send even if we
get unchoked a little later. (Since we will resent them anyway in
that case.)
2003-05-09 18:33 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: Peer.java, PeerCheckerTask.java,
PeerCoordinator.java, PeerID.java: New definition of PeerID.equals
(port + address + id) and new method PeerID.sameID (only id). These
are used to really see if we already have a connection to a certain
peer (active setup vs passive setup).
2003-05-08 03:05 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Use Snark.debug() not
System.out.println().
2003-05-06 20:29 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: s/noting/nothing/
2003-05-06 20:28 Mark Wielaard <mark@klomp.org>
* Makefile: s/lagacy/legacy/
2003-05-05 23:17 Mark Wielaard <mark@klomp.org>
* README: Set version to 0.2, explain new functionality and add
examples.
2003-05-05 22:42 Mark Wielaard <mark@klomp.org>
* .cvsignore, Makefile, org/klomp/snark/StaticSnark.java: Enable
-static binary creation.
2003-05-05 22:42 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Tracker.java: Disable --ip support.
2003-05-05 21:02 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: HttpAcceptor.java, PeerCheckerTask.java,
PeerCoordinator.java, TrackerClient.java: Use Snark.debug() not
System.out.println().
2003-05-05 21:01 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerConnectionIn.java: Be prepared to handle the
case where currentRequest is null.
2003-05-05 21:00 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Snark.java: Improve argument parsing errors.
2003-05-05 21:00 Mark Wielaard <mark@klomp.org>
* Makefile: Use gcj -C again for creating the class files.
2003-05-05 09:24 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Just clear outstandingRequests,
never make it null.
2003-05-05 02:55 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/TrackerClient.java: Always retry both first
started event and every other event as long the TrackerClient is
not stopped.
2003-05-05 02:54 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Snark.java: Remove double assignment port.
2003-05-05 02:54 Mark Wielaard <mark@klomp.org>
* TODO: Add Tracker TODO item.
2003-05-04 23:38 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: ConnectionAcceptor.java, MetaInfo.java,
Snark.java, Storage.java, Tracker.java: Add info hash calcultation
to MetaInfo. Add torrent creation to Storage. Add ip parameter
handling to Tracker. Make ConnectionAcceptor handle
null/non-existing HttpAcceptors. Add debug output, --ip handling
and all the above to Snark.
2003-05-04 23:36 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/TrackerClient.java: Handle all failing requests
the same (print a warning).
2003-05-03 15:46 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: Peer.java, PeerID.java, TrackerInfo.java: Split
Peer and PeerID a little more.
2003-05-03 15:44 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/MetaInfo.java: Add reannounce() and
getTorrentData().
2003-05-03 15:38 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java:
More concise verbose/debug output. Always use addUpDownloader() to
set peers upload or download state to true.
2003-05-03 13:38 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/TrackerClient.java: Compile fixes.
2003-05-03 13:32 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/TrackerClient.java: Only generate fatal() call on
first Tracker access. Otherwise just print a warning error message.
2003-05-03 03:10 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerState.java: Better handle resending
outstanding pieces and try to recover better from unrequested
pieces.
2003-05-02 21:33 Mark Wielaard <mark@klomp.org>
* Makefile, org/klomp/snark/HttpAcceptor.java,
org/klomp/snark/MetaInfo.java, org/klomp/snark/PeerID.java,
org/klomp/snark/Snark.java, org/klomp/snark/Tracker.java,
org/klomp/snark/TrackerClient.java,
org/klomp/snark/bencode/BEncoder.java: Add Tracker, PeerID and
BEncoder.
2003-05-01 20:17 Mark Wielaard <mark@klomp.org>
* Makefile, org/klomp/snark/ConnectionAcceptor.java,
org/klomp/snark/HttpAcceptor.java, org/klomp/snark/Peer.java,
org/klomp/snark/PeerAcceptor.java, org/klomp/snark/Snark.java: Add
ConnectionAcceptor that handles both PeerAcceptor and HttpAcceptor.
2003-05-01 18:39 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/PeerCoordinator.java: connected() synchronize on
peers.
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/SnarkShutdown.java: Wait some time before
returning...
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
* TODO: More items.
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
* org/klomp/snark/Snark.java: Calculate real random ID.
2003-04-27 Mark Wielaard <mark@klomp.org>
* snark: Initial (0.1) version.

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="i2psnark">
<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"
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/i2psnark.jar" basedir="./build/obj" includes="**/*.class">
<manifest>
<attribute name="Main-Class" value="org.klomp.snark.Snark" />
<attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
</manifest>
</jar>
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
<ant dir="../../ministreaming/java/" target="distclean" />
</target>
<target name="distclean" depends="clean">
<ant dir="../../ministreaming/java/" target="distclean" />
</target>
</project>

View File

@ -0,0 +1,131 @@
/* BitField - Container of a byte array representing set and unset bits.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.util.Iterator;
import java.util.Set;
import java.util.HashSet;
/**
* Container of a byte array representing set and unset bits.
*/
public class BitField
{
private final byte[] bitfield;
private final int size;
/**
* Creates a new BitField that represents <code>size</code> unset bits.
*/
public BitField(int size)
{
this.size = size;
int arraysize = ((size-1)/8)+1;
bitfield = new byte[arraysize];
}
/**
* Creates a new BitField that represents <code>size</code> bits
* as set by the given byte array. This will make a copy of the array.
* Extra bytes will be ignored.
*
* @exception ArrayOutOfBoundsException if give byte array is not large
* enough.
*/
public BitField(byte[] bitfield, int size)
{
this.size = size;
int arraysize = ((size-1)/8)+1;
this.bitfield = new byte[arraysize];
// XXX - More correct would be to check that unused bits are
// cleared or clear them explicitly ourselves.
System.arraycopy(bitfield, 0, this.bitfield, 0, arraysize);
}
/**
* This returns the actual byte array used. Changes to this array
* effect this BitField. Note that some bits at the end of the byte
* array are supposed to be always unset if they represent bits
* bigger then the size of the bitfield.
*/
public byte[] getFieldBytes()
{
return bitfield;
}
/**
* Return the size of the BitField. The returned value is one bigger
* then the last valid bit number (since bit numbers are counted
* from zero).
*/
public int size()
{
return size;
}
/**
* Sets the given bit to true.
*
* @exception IndexOutOfBoundsException if bit is smaller then zero
* bigger then size (inclusive).
*/
public void set(int bit)
{
if (bit < 0 || bit >= size)
throw new IndexOutOfBoundsException(Integer.toString(bit));
int index = bit/8;
int mask = 128 >> (bit % 8);
bitfield[index] |= mask;
}
/**
* Return true if the bit is set or false if it is not.
*
* @exception IndexOutOfBoundsException if bit is smaller then zero
* bigger then size (inclusive).
*/
public boolean get(int bit)
{
if (bit < 0 || bit >= size)
throw new IndexOutOfBoundsException(Integer.toString(bit));
int index = bit/8;
int mask = 128 >> (bit % 8);
return (bitfield[index] & mask) != 0;
}
public String toString()
{
// Not very efficient
StringBuffer sb = new StringBuffer("BitField[");
for (int i = 0; i < size; i++)
if (get(i))
{
sb.append(' ');
sb.append(i);
}
sb.append(" ]");
return sb.toString();
}
}

View File

@ -0,0 +1,143 @@
/* ConnectionAcceptor - Accepts connections and routes them to sub-acceptors.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.*;
import java.net.*;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
/**
* Accepts connections on a TCP port and routes them to sub-acceptors.
*/
public class ConnectionAcceptor implements Runnable
{
private final I2PServerSocket serverSocket;
private final PeerAcceptor peeracceptor;
private Thread thread;
private boolean stop;
public ConnectionAcceptor(I2PServerSocket serverSocket,
PeerAcceptor peeracceptor)
{
this.serverSocket = serverSocket;
this.peeracceptor = peeracceptor;
stop = false;
thread = new Thread(this);
thread.start();
}
public void halt()
{
stop = true;
I2PServerSocket ss = serverSocket;
if (ss != null)
try
{
ss.close();
}
catch(I2PException ioe) { }
Thread t = thread;
if (t != null)
t.interrupt();
}
public int getPort()
{
return 6881; // serverSocket.getLocalPort();
}
public void run()
{
while(!stop)
{
try
{
final I2PSocket socket = serverSocket.accept();
Thread t = new Thread("Connection-" + socket)
{
public void run()
{
try
{
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
BufferedInputStream bis = new BufferedInputStream(in);
BufferedOutputStream bos = new BufferedOutputStream(out);
// See what kind of connection it is.
/*
if (httpacceptor != null)
{
byte[] scratch = new byte[4];
bis.mark(4);
int len = bis.read(scratch);
if (len != 4)
throw new IOException("Need at least 4 bytes");
bis.reset();
if (scratch[0] == 19 && scratch[1] == 'B'
&& scratch[2] == 'i' && scratch[3] == 't')
peeracceptor.connection(socket, bis, bos);
else if (scratch[0] == 'G' && scratch[1] == 'E'
&& scratch[2] == 'T' && scratch[3] == ' ')
httpacceptor.connection(socket, bis, bos);
}
else
*/
peeracceptor.connection(socket, bis, bos);
}
catch (IOException ioe)
{
try
{
socket.close();
}
catch (IOException ignored) { }
}
}
};
t.start();
}
catch (I2PException ioe)
{
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
stop = true;
}
catch (IOException ioe)
{
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
stop = true;
}
}
try
{
serverSocket.close();
}
catch (I2PException ignored) { }
}
}

View File

@ -0,0 +1,33 @@
/* CoordinatorListener.java - Callback when a peer changes state
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
/**
* Callback used when some peer changes state.
*/
public interface CoordinatorListener
{
/**
* Called when the PeerCoordinator notices a change in the state of a peer.
*/
void peerChange(PeerCoordinator coordinator, Peer peer);
}

View File

@ -0,0 +1,165 @@
package org.klomp.snark;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.util.EepGet;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.util.Log;
import java.io.*;
import java.util.Properties;
/**
* I2P specific helpers for I2PSnark
*/
public class I2PSnarkUtil {
private I2PAppContext _context;
private Log _log;
private static I2PSnarkUtil _instance = new I2PSnarkUtil();
public static I2PSnarkUtil instance() { return _instance; }
private boolean _shouldProxy;
private String _proxyHost;
private int _proxyPort;
private String _i2cpHost;
private int _i2cpPort;
private Properties _opts;
private I2PSocketManager _manager;
private I2PSnarkUtil() {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(Snark.class);
setProxy("127.0.0.1", 4444);
setI2CPConfig("127.0.0.1", 7654, null);
}
/**
* Specify what HTTP proxy tracker requests should go through (specify a null
* host for no proxying)
*
*/
public void setProxy(String host, int port) {
if ( (host != null) && (port > 0) ) {
_shouldProxy = true;
_proxyHost = host;
_proxyPort = port;
} else {
_shouldProxy = false;
_proxyHost = null;
_proxyPort = -1;
}
}
public void setI2CPConfig(String i2cpHost, int i2cpPort, Properties opts) {
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
if (opts != null)
_opts = opts;
}
/**
* Connect to the router, if we aren't already
*/
boolean connect() {
if (_manager == null) {
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, _opts);
}
return (_manager != null);
}
/** connect to the given destination */
I2PSocket connect(PeerID peer) throws IOException {
try {
return _manager.connect(peer.getAddress());
} catch (I2PException ie) {
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
}
}
/**
* fetch the given URL, returning the file it is stored in, or null on error
*/
File get(String url) {
File out = null;
try {
out = File.createTempFile("i2psnark", "url");
} catch (IOException ioe) {
ioe.printStackTrace();
return null;
}
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, 1, out.getAbsolutePath(), url);
if (get.fetch()) {
return out;
} else {
out.delete();
return null;
}
}
I2PServerSocket getServerSocket() {
return _manager.getServerSocket();
}
String getOurIPString() {
return _manager.getSession().getMyDestination().toBase64();
}
Destination getDestination(String ip) {
if (ip == null) return null;
if (ip.endsWith(".i2p")) {
Destination dest = _context.namingService().lookup(ip);
if (dest != null) {
return dest;
} else {
try {
return new Destination(ip.substring(0, ip.length()-4)); // sans .i2p
} catch (DataFormatException dfe) {
return null;
}
}
} else {
try {
return new Destination(ip);
} catch (DataFormatException dfe) {
return null;
}
}
}
/**
* Given http://blah.i2p/foo/announce turn it into http://i2p/blah/foo/announce
*/
String rewriteAnnounce(String origAnnounce) {
int destStart = "http://".length();
int destEnd = origAnnounce.indexOf(".i2p");
int pathStart = origAnnounce.indexOf('/', destEnd);
return "http://i2p/" + origAnnounce.substring(destStart, destEnd) + origAnnounce.substring(pathStart);
}
/** hook between snark's logger and an i2p log */
void debug(String msg, int snarkDebugLevel, Throwable t) {
switch (snarkDebugLevel) {
case 0:
case 1:
_log.error(msg, t);
break;
case 2:
_log.warn(msg, t);
break;
case 3:
case 4:
_log.info(msg, t);
break;
case 5:
case 6:
default:
_log.debug(msg, t);
break;
}
}
}

View File

@ -0,0 +1,137 @@
/* Message - A protocol message which can be send through a DataOutputStream.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.DataOutputStream;
import java.io.IOException;
// Used to queue outgoing connections
// sendMessage() should be used to translate them to wire format.
class Message
{
final static byte KEEP_ALIVE = -1;
final static byte CHOKE = 0;
final static byte UNCHOKE = 1;
final static byte INTERESTED = 2;
final static byte UNINTERESTED = 3;
final static byte HAVE = 4;
final static byte BITFIELD = 5;
final static byte REQUEST = 6;
final static byte PIECE = 7;
final static byte CANCEL = 8;
// Not all fields are used for every message.
// KEEP_ALIVE doesn't have a real wire representation
byte type;
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
int piece;
// Used for REQUEST, PIECE and CANCEL messages.
int begin;
int length;
// Used for PIECE and BITFIELD messages
byte[] data;
int off;
int len;
/** Utility method for sending a message through a DataStream. */
void sendMessage(DataOutputStream dos) throws IOException
{
// KEEP_ALIVE is special.
if (type == KEEP_ALIVE)
{
dos.writeInt(0);
return;
}
// Calculate the total length in bytes
// Type is one byte.
int datalen = 1;
// piece is 4 bytes.
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
datalen += 4;
// begin/offset is 4 bytes
if (type == REQUEST || type == PIECE || type == CANCEL)
datalen += 4;
// length is 4 bytes
if (type == REQUEST || type == CANCEL)
datalen += 4;
// add length of data for piece or bitfield array.
if (type == BITFIELD || type == PIECE)
datalen += len;
// Send length
dos.writeInt(datalen);
dos.writeByte(type & 0xFF);
// Send additional info (piece number)
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
dos.writeInt(piece);
// Send additional info (begin/offset)
if (type == REQUEST || type == PIECE || type == CANCEL)
dos.writeInt(begin);
// Send additional info (length); for PIECE this is implicit.
if (type == REQUEST || type == CANCEL)
dos.writeInt(length);
// Send actual data
if (type == BITFIELD || type == PIECE)
dos.write(data, off, len);
}
public String toString()
{
switch (type)
{
case KEEP_ALIVE:
return "KEEP_ALIVE";
case CHOKE:
return "CHOKE";
case UNCHOKE:
return "UNCHOKE";
case INTERESTED:
return "INTERESTED";
case UNINTERESTED:
return "UNINTERESTED";
case HAVE:
return "HAVE(" + piece + ")";
case BITFIELD:
return "BITFIELD";
case REQUEST:
return "REQUEST(" + piece + "," + begin + "," + length + ")";
case PIECE:
return "PIECE(" + piece + "," + begin + "," + length + ")";
case CANCEL:
return "CANCEL(" + piece + "," + begin + "," + length + ")";
default:
return "<UNKNOWN>";
}
}
}

View File

@ -0,0 +1,382 @@
/* MetaInfo - Holds all information gotten from a torrent file.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.IOException;
import java.io.InputStream;
import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import org.klomp.snark.bencode.*;
public class MetaInfo
{
private final String announce;
private final byte[] info_hash;
private final String name;
private final List files;
private final List lengths;
private final int piece_length;
private final byte[] piece_hashes;
private final long length;
private byte[] torrentdata;
MetaInfo(String announce, String name, List files, List lengths,
int piece_length, byte[] piece_hashes, long length)
{
this.announce = announce;
this.name = name;
this.files = files;
this.lengths = lengths;
this.piece_length = piece_length;
this.piece_hashes = piece_hashes;
this.length = length;
this.info_hash = calculateInfoHash();
}
/**
* Creates a new MetaInfo from the given InputStream. The
* InputStream must start with a correctly bencoded dictonary
* describing the torrent.
*/
public MetaInfo(InputStream in) throws IOException
{
this(new BDecoder(in));
}
/**
* Creates a new MetaInfo from the given BDecoder. The BDecoder
* must have a complete dictionary describing the torrent.
*/
public MetaInfo(BDecoder be) throws IOException
{
// Note that evaluation order matters here...
this(be.bdecodeMap().getMap());
}
/**
* Creates a new MetaInfo from a Map of BEValues and the SHA1 over
* the original bencoded info dictonary (this is a hack, we could
* reconstruct the bencoded stream and recalculate the hash). Will
* throw a InvalidBEncodingException if the given map does not
* contain a valid announce string or info dictonary.
*/
public MetaInfo(Map m) throws InvalidBEncodingException
{
BEValue val = (BEValue)m.get("announce");
if (val == null)
throw new InvalidBEncodingException("Missing announce string");
this.announce = val.getString();
val = (BEValue)m.get("info");
if (val == null)
throw new InvalidBEncodingException("Missing info map");
Map info = val.getMap();
val = (BEValue)info.get("name");
if (val == null)
throw new InvalidBEncodingException("Missing name string");
name = val.getString();
val = (BEValue)info.get("piece length");
if (val == null)
throw new InvalidBEncodingException("Missing piece length number");
piece_length = val.getInt();
val = (BEValue)info.get("pieces");
if (val == null)
throw new InvalidBEncodingException("Missing piece bytes");
piece_hashes = val.getBytes();
val = (BEValue)info.get("length");
if (val != null)
{
// Single file case.
length = val.getLong();
files = null;
lengths = null;
}
else
{
// Multi file case.
val = (BEValue)info.get("files");
if (val == null)
throw new InvalidBEncodingException
("Missing length number and/or files list");
List list = val.getList();
int size = list.size();
if (size == 0)
throw new InvalidBEncodingException("zero size files list");
files = new ArrayList(size);
lengths = new ArrayList(size);
long l = 0;
for (int i = 0; i < list.size(); i++)
{
Map desc = ((BEValue)list.get(i)).getMap();
val = (BEValue)desc.get("length");
if (val == null)
throw new InvalidBEncodingException("Missing length number");
long len = val.getLong();
lengths.add(new Long(len));
l += len;
val = (BEValue)desc.get("path");
if (val == null)
throw new InvalidBEncodingException("Missing path list");
List path_list = val.getList();
int path_length = path_list.size();
if (path_length == 0)
throw new InvalidBEncodingException("zero size file path list");
List file = new ArrayList(path_length);
Iterator it = path_list.iterator();
while (it.hasNext())
file.add(((BEValue)it.next()).getString());
files.add(file);
}
length = l;
}
info_hash = calculateInfoHash();
}
/**
* Returns the string representing the URL of the tracker for this torrent.
*/
public String getAnnounce()
{
return announce;
}
/**
* Returns the original 20 byte SHA1 hash over the bencoded info map.
*/
public byte[] getInfoHash()
{
// XXX - Should we return a clone, just to be sure?
return info_hash;
}
/**
* Returns the piece hashes. Only used by storage so package local.
*/
byte[] getPieceHashes()
{
return piece_hashes;
}
/**
* Returns the requested name for the file or toplevel directory.
* If it is a toplevel directory name getFiles() will return a
* non-null List of file name hierarchy name.
*/
public String getName()
{
return name;
}
/**
* Returns a list of lists of file name hierarchies or null if it is
* a single name. It has the same size as the list returned by
* getLengths().
*/
public List getFiles()
{
// XXX - Immutable?
return files;
}
/**
* Returns a list of Longs indication the size of the individual
* files, or null if it is a single file. It has the same size as
* the list returned by getFiles().
*/
public List getLengths()
{
// XXX - Immutable?
return lengths;
}
/**
* Returns the number of pieces.
*/
public int getPieces()
{
return piece_hashes.length/20;
}
/**
* Return the length of a piece. All pieces are of equal length
* except for the last one (<code>getPieces()-1</code>).
*
* @exception IndexOutOfBoundsException when piece is equal to or
* greater then the number of pieces in the torrent.
*/
public int getPieceLength(int piece)
{
int pieces = getPieces();
if (piece >= 0 && piece < pieces -1)
return piece_length;
else if (piece == pieces -1)
return (int)(length - piece * piece_length);
else
throw new IndexOutOfBoundsException("no piece: " + piece);
}
/**
* Checks that the given piece has the same SHA1 hash as the given
* byte array. Returns random results or IndexOutOfBoundsExceptions
* when the piece number is unknown.
*/
public boolean checkPiece(int piece, byte[] bs, int off, int length)
{
// Check digest
MessageDigest sha1;
try
{
sha1 = MessageDigest.getInstance("SHA");
}
catch (NoSuchAlgorithmException nsae)
{
throw new InternalError("No SHA digest available: " + nsae);
}
sha1.update(bs, off, length);
byte[] hash = sha1.digest();
for (int i = 0; i < 20; i++)
if (hash[i] != piece_hashes[20 * piece + i])
return false;
return true;
}
/**
* Returns the total length of the torrent in bytes.
*/
public long getTotalLength()
{
return length;
}
public String toString()
{
return "MetaInfo[info_hash='" + hexencode(info_hash)
+ "', announce='" + announce
+ "', name='" + name
+ "', files=" + files
+ ", #pieces='" + piece_hashes.length/20
+ "', piece_length='" + piece_length
+ "', length='" + length
+ "']";
}
/**
* Encode a byte array as a hex encoded string.
*/
private static String hexencode(byte[] bs)
{
StringBuffer sb = new StringBuffer(bs.length*2);
for (int i = 0; i < bs.length; i++)
{
int c = bs[i] & 0xFF;
if (c < 16)
sb.append('0');
sb.append(Integer.toHexString(c));
}
return sb.toString();
}
/**
* Creates a copy of this MetaInfo that shares everything except the
* announce URL.
*/
public MetaInfo reannounce(String announce)
{
return new MetaInfo(announce, name, files,
lengths, piece_length,
piece_hashes, length);
}
public byte[] getTorrentData()
{
if (torrentdata == null)
{
Map m = new HashMap();
m.put("announce", announce);
Map info = createInfoMap();
m.put("info", info);
torrentdata = BEncoder.bencode(m);
}
return torrentdata;
}
private Map createInfoMap()
{
Map info = new HashMap();
info.put("name", name);
info.put("piece length", new Integer(piece_length));
info.put("pieces", piece_hashes);
if (files == null)
info.put("length", new Long(length));
else
{
List l = new ArrayList();
for (int i = 0; i < files.size(); i++)
{
Map file = new HashMap();
file.put("path", files.get(i));
file.put("length", lengths.get(i));
l.add(file);
}
info.put("files", l);
}
return info;
}
private byte[] calculateInfoHash()
{
Map info = createInfoMap();
byte[] infoBytes = BEncoder.bencode(info);
try
{
MessageDigest digest = MessageDigest.getInstance("SHA");
return digest.digest(infoBytes);
}
catch(NoSuchAlgorithmException nsa)
{
throw new InternalError(nsa.toString());
}
}
}

View File

@ -0,0 +1,388 @@
/* Peer - All public information concerning a peer.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.*;
import java.net.*;
import java.util.Arrays;
import java.util.Map;
import org.klomp.snark.bencode.*;
import net.i2p.client.streaming.I2PSocket;
public class Peer implements Comparable
{
// Identifying property, the peer id of the other side.
private final PeerID peerID;
private final byte[] my_id;
private final MetaInfo metainfo;
// The data in/output streams set during the handshake and used by
// the actual connections.
private DataInputStream din;
private DataOutputStream dout;
// Keeps state for in/out connections. Non-null when the handshake
// was successful, the connection setup and runs
PeerState state;
private boolean deregister = true;
/**
* Creates a disconnected peer given a PeerID, your own id and the
* relevant MetaInfo.
*/
public Peer(PeerID peerID, byte[] my_id, MetaInfo metainfo)
throws IOException
{
this.peerID = peerID;
this.my_id = my_id;
this.metainfo = metainfo;
}
/**
* Creates a unconnected peer from the input and output stream got
* from the socket. Note that the complete handshake (which can take
* some time or block indefinitely) is done in the calling Thread to
* get the remote peer id. To completely start the connection call
* the connect() method.
*
* @exception IOException when an error occurred during the handshake.
*/
public Peer(final I2PSocket sock, BufferedInputStream bis,
BufferedOutputStream bos, byte[] my_id, MetaInfo metainfo)
throws IOException
{
this.my_id = my_id;
this.metainfo = metainfo;
byte[] id = handshake(bis, bos);
this.peerID = new PeerID(id, sock.getPeerDestination());
}
/**
* Returns the id of the peer.
*/
public PeerID getPeerID()
{
return peerID;
}
/**
* Returns the String representation of the peerID.
*/
public String toString()
{
return peerID.toString();
}
/**
* The hash code of a Peer is the hash code of the peerID.
*/
public int hashCode()
{
return peerID.hashCode();
}
/**
* Two Peers are equal when they have the same PeerID.
* All other properties are ignored.
*/
public boolean equals(Object o)
{
if (o instanceof Peer)
{
Peer p = (Peer)o;
return peerID.equals(p.peerID);
}
else
return false;
}
/**
* Compares the PeerIDs.
*/
public int compareTo(Object o)
{
Peer p = (Peer)o;
return peerID.compareTo(p.peerID);
}
/**
* Runs the connection to the other peer. This method does not
* return until the connection is terminated.
*
* When the connection is correctly started the connected() method
* of the given PeerListener is called. If the connection ends or
* the connection could not be setup correctly the disconnected()
* method is called.
*
* If the given BitField is non-null it is send to the peer as first
* message.
*/
public void runConnection(PeerListener listener, BitField bitfield)
{
if (state != null)
throw new IllegalStateException("Peer already started");
try
{
// Do we need to handshake?
if (din == null)
{
I2PSocket sock = I2PSnarkUtil.instance().connect(peerID);
BufferedInputStream bis
= new BufferedInputStream(sock.getInputStream());
BufferedOutputStream bos
= new BufferedOutputStream(sock.getOutputStream());
byte [] id = handshake(bis, bos);
byte [] expected_id = peerID.getID();
if (!Arrays.equals(expected_id, id))
throw new IOException("Unexpected peerID '"
+ PeerID.idencode(id)
+ "' expected '"
+ PeerID.idencode(expected_id) + "'");
}
PeerConnectionIn in = new PeerConnectionIn(this, din);
PeerConnectionOut out = new PeerConnectionOut(this, dout);
PeerState s = new PeerState(this, listener, metainfo, in, out);
// Send our bitmap
if (bitfield != null)
s.out.sendBitfield(bitfield);
// We are up and running!
state = s;
listener.connected(this);
// Use this thread for running the incomming connection.
// The outgoing connection has created its own Thread.
s.in.run();
}
catch(IOException eofe)
{
// Ignore, probably just the other side closing the connection.
// Or refusing the connection, timing out, etc.
}
catch(Throwable t)
{
Snark.debug(this + ": " + t, Snark.ERROR);
t.printStackTrace();
}
finally
{
if (deregister) listener.disconnected(this);
}
}
/**
* Sets DataIn/OutputStreams, does the handshake and returns the id
* reported by the other side.
*/
private byte[] handshake(BufferedInputStream bis, BufferedOutputStream bos)
throws IOException
{
din = new DataInputStream(bis);
dout = new DataOutputStream(bos);
// Handshake write - header
dout.write(19);
dout.write("BitTorrent protocol".getBytes("UTF-8"));
// Handshake write - zeros
byte[] zeros = new byte[8];
dout.write(zeros);
// Handshake write - metainfo hash
byte[] shared_hash = metainfo.getInfoHash();
dout.write(shared_hash);
// Handshake write - peer id
dout.write(my_id);
dout.flush();
// Handshake read - header
byte b = din.readByte();
if (b != 19)
throw new IOException("Handshake failure, expected 19, got "
+ (b & 0xff));
byte[] bs = new byte[19];
din.readFully(bs);
String bittorrentProtocol = new String(bs, "UTF-8");
if (!"BitTorrent protocol".equals(bittorrentProtocol))
throw new IOException("Handshake failure, expected "
+ "'Bittorrent protocol', got '"
+ bittorrentProtocol + "'");
// Handshake read - zeros
din.readFully(zeros);
// Handshake read - metainfo hash
bs = new byte[20];
din.readFully(bs);
if (!Arrays.equals(shared_hash, bs))
throw new IOException("Unexpected MetaInfo hash");
// Handshake read - peer id
din.readFully(bs);
return bs;
}
public boolean isConnected()
{
return state != null;
}
/**
* Disconnects this peer if it was connected. If deregister is
* true, PeerListener.disconnected() will be called when the
* connection is completely terminated. Otherwise the connection is
* silently terminated.
*/
public void disconnect(boolean deregister)
{
// Both in and out connection will call this.
this.deregister = deregister;
disconnect();
}
void disconnect()
{
PeerState s = state;
if (s != null)
{
state = null;
PeerConnectionIn in = s.in;
if (in != null)
in.disconnect();
PeerConnectionOut out = s.out;
if (out != null)
out.disconnect();
}
}
/**
* Tell the peer we have another piece.
*/
public void have(int piece)
{
PeerState s = state;
if (s != null)
s.havePiece(piece);
}
/**
* Whether or not the peer is interested in pieces we have. Returns
* false if not connected.
*/
public boolean isInterested()
{
PeerState s = state;
return (s != null) && s.interested;
}
/**
* Sets whether or not we are interested in pieces from this peer.
* Defaults to false. When interest is true and this peer unchokes
* us then we start downloading from it. Has no effect when not connected.
*/
public void setInteresting(boolean interest)
{
PeerState s = state;
if (s != null)
s.setInteresting(interest);
}
/**
* Whether or not the peer has pieces we want from it. Returns false
* if not connected.
*/
public boolean isInteresting()
{
PeerState s = state;
return (s != null) && s.interesting;
}
/**
* Sets whether or not we are choking the peer. Defaults to
* true. When choke is false and the peer requests some pieces we
* upload them, otherwise requests of this peer are ignored.
*/
public void setChoking(boolean choke)
{
PeerState s = state;
if (s != null)
s.setChoking(choke);
}
/**
* Whether or not we are choking the peer. Returns true when not connected.
*/
public boolean isChoking()
{
PeerState s = state;
return (s == null) || s.choking;
}
/**
* Whether or not the peer choked us. Returns true when not connected.
*/
public boolean isChoked()
{
PeerState s = state;
return (s == null) || s.choked;
}
/**
* Returns the number of bytes that have been downloaded.
* Can be reset to zero with <code>resetCounters()</code>/
*/
public long getDownloaded()
{
PeerState s = state;
return (s != null) ? s.downloaded : 0;
}
/**
* Returns the number of bytes that have been uploaded.
* Can be reset to zero with <code>resetCounters()</code>/
*/
public long getUploaded()
{
PeerState s = state;
return (s != null) ? s.uploaded : 0;
}
/**
* Resets the downloaded and uploaded counters to zero.
*/
public void resetCounters()
{
PeerState s = state;
if (s != null)
{
s.downloaded = 0;
s.uploaded = 0;
}
}
}

View File

@ -0,0 +1,62 @@
/* PeerAcceptor - Accepts incomming connections from peers.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.*;
import java.net.*;
import net.i2p.client.streaming.I2PSocket;
/**
* Accepts incomming connections from peers. The ConnectionAcceptor
* will call the connection() method when it detects an incomming BT
* protocol connection. The PeerAcceptor will then create a new peer
* if the PeerCoordinator wants more peers.
*/
public class PeerAcceptor
{
private final PeerCoordinator coordinator;
public PeerAcceptor(PeerCoordinator coordinator)
{
this.coordinator = coordinator;
}
public void connection(I2PSocket socket,
BufferedInputStream bis, BufferedOutputStream bos)
throws IOException
{
if (coordinator.needPeers())
{
// XXX: inside this Peer constructor's handshake is where you'd deal with the other
// side saying they want to communicate with another torrent - aka multitorrent
// support. you'd then want to grab the meta info /they/ want, look that up in
// our own list of active torrents, and put it on the right coordinator for it.
// this currently, however, throws an IOException if the metainfo doesn't match
// coodinator.getMetaInfo (Peer.java:242)
Peer peer = new Peer(socket, bis, bos, coordinator.getID(),
coordinator.getMetaInfo());
coordinator.addPeer(peer);
}
else
socket.close();
}
}

View File

@ -0,0 +1,198 @@
/* PeerCheckTasks - TimerTask that checks for good/bad up/downloaders.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.util.*;
/**
* TimerTask that checks for good/bad up/downloader. Works together
* with the PeerCoordinator to select which Peers get (un)choked.
*/
class PeerCheckerTask extends TimerTask
{
private final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
private final PeerCoordinator coordinator;
PeerCheckerTask(PeerCoordinator coordinator)
{
this.coordinator = coordinator;
}
public void run()
{
synchronized(coordinator.peers)
{
// Calculate total uploading and worst downloader.
long worstdownload = Long.MAX_VALUE;
Peer worstDownloader = null;
int peers = 0;
int uploaders = 0;
int downloaders = 0;
int interested = 0;
int interesting = 0;
int choking = 0;
int choked = 0;
long uploaded = 0;
long downloaded = 0;
// Keep track of peers we remove now,
// we will add them back to the end of the list.
List removed = new ArrayList();
Iterator it = coordinator.peers.iterator();
while (it.hasNext())
{
Peer peer = (Peer)it.next();
// Remove dying peers
if (!peer.isConnected())
{
it.remove();
coordinator.removePeerFromPieces(peer);
continue;
}
peers++;
if (!peer.isChoking())
uploaders++;
if (!peer.isChoked() && peer.isInteresting())
downloaders++;
if (peer.isInterested())
interested++;
if (peer.isInteresting())
interesting++;
if (peer.isChoking())
choking++;
if (peer.isChoked())
choked++;
// XXX - We should calculate the up/download rate a bit
// more intelligently
long upload = peer.getUploaded();
uploaded += upload;
long download = peer.getDownloaded();
downloaded += download;
peer.resetCounters();
if (Snark.debug >= Snark.DEBUG)
{
Snark.debug(peer + ":", Snark.DEBUG);
Snark.debug(" ul: " + upload/KILOPERSECOND
+ " dl: " + download/KILOPERSECOND
+ " i: " + peer.isInterested()
+ " I: " + peer.isInteresting()
+ " c: " + peer.isChoking()
+ " C: " + peer.isChoked(),
Snark.DEBUG);
}
// If we are at our max uploaders and we have lots of other
// interested peers try to make some room.
// (Note use of coordinator.uploaders)
if (coordinator.uploaders >= PeerCoordinator.MAX_UPLOADERS
&& interested > PeerCoordinator.MAX_UPLOADERS
&& !peer.isChoking())
{
// Check if it still wants pieces from us.
if (!peer.isInterested())
{
if (Snark.debug >= Snark.INFO)
Snark.debug("Choke uninterested peer: " + peer,
Snark.INFO);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
// Put it at the back of the list
it.remove();
removed.add(peer);
}
else if (peer.isChoked())
{
// If they are choking us make someone else a downloader
if (Snark.debug >= Snark.DEBUG)
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
// Put it at the back of the list
it.remove();
removed.add(peer);
}
else if (peer.isInteresting()
&& !peer.isChoked()
&& download == 0)
{
// We are downloading but didn't receive anything...
if (Snark.debug >= Snark.DEBUG)
Snark.debug("Choke downloader that doesn't deliver:"
+ peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
// Put it at the back of the list
it.remove();
removed.add(peer);
}
else if (!peer.isChoking() && download < worstdownload)
{
// Make sure download is good if we are uploading
worstdownload = download;
worstDownloader = peer;
}
}
}
// Resync actual uploaders value
// (can shift a bit by disconnecting peers)
coordinator.uploaders = uploaders;
// Remove the worst downloader if needed.
if (uploaders >= PeerCoordinator.MAX_UPLOADERS
&& interested > PeerCoordinator.MAX_UPLOADERS
&& worstDownloader != null)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug("Choke worst downloader: " + worstDownloader,
Snark.DEBUG);
worstDownloader.setChoking(true);
coordinator.uploaders--;
// Put it at the back of the list
coordinator.peers.remove(worstDownloader);
removed.add(worstDownloader);
}
// Optimistically unchoke a peer
coordinator.unchokePeer();
// Put peers back at the end of the list that we removed earlier.
coordinator.peers.addAll(removed);
}
}
}

View File

@ -0,0 +1,156 @@
/* PeerConnectionIn - Handles incomming messages and hands them to PeerState.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.*;
import java.net.*;
import java.util.*;
class PeerConnectionIn implements Runnable
{
private final Peer peer;
private final DataInputStream din;
private Thread thread;
private boolean quit;
public PeerConnectionIn(Peer peer, DataInputStream din)
{
this.peer = peer;
this.din = din;
quit = false;
}
void disconnect()
{
if (quit == true)
return;
quit = true;
Thread t = thread;
if (t != null)
t.interrupt();
}
public void run()
{
thread = Thread.currentThread();
try
{
PeerState ps = peer.state;
while (!quit && ps != null)
{
// Common variables used for some messages.
int piece;
int begin;
int len;
// Wait till we hear something...
// The length of a complete message in bytes.
int i = din.readInt();
if (i < 0)
throw new IOException("Unexpected length prefix: " + i);
if (i == 0)
{
ps.keepAliveMessage();
continue;
}
byte b = din.readByte();
Message m = new Message();
m.type = b;
switch (b)
{
case 0:
ps.chokeMessage(true);
break;
case 1:
ps.chokeMessage(false);
break;
case 2:
ps.interestedMessage(true);
break;
case 3:
ps.interestedMessage(false);
break;
case 4:
piece = din.readInt();
ps.haveMessage(piece);
break;
case 5:
byte[] bitmap = new byte[i-1];
din.readFully(bitmap);
ps.bitfieldMessage(bitmap);
break;
case 6:
piece = din.readInt();
begin = din.readInt();
len = din.readInt();
ps.requestMessage(piece, begin, len);
break;
case 7:
piece = din.readInt();
begin = din.readInt();
len = i-9;
Request req = ps.getOutstandingRequest(piece, begin, len);
byte[] piece_bytes;
if (req != null)
{
piece_bytes = req.bs;
din.readFully(piece_bytes, begin, len);
ps.pieceMessage(req);
}
else
{
// XXX - Consume but throw away afterwards.
piece_bytes = new byte[len];
din.readFully(piece_bytes);
}
break;
case 8:
piece = din.readInt();
begin = din.readInt();
len = din.readInt();
ps.cancelMessage(piece, begin, len);
break;
default:
byte[] bs = new byte[i-1];
din.readFully(bs);
ps.unknownMessage(b, bs);
}
}
}
catch (IOException ioe)
{
// Ignore, probably the other side closed connection.
}
catch (Throwable t)
{
Snark.debug(peer + ": " + t, Snark.ERROR);
t.printStackTrace();
}
finally
{
peer.disconnect();
}
}
}

View File

@ -0,0 +1,342 @@
/* PeerConnectionOut - Keeps a queue of outgoing messages and delivers them.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.*;
import java.net.*;
import java.util.*;
class PeerConnectionOut implements Runnable
{
private final Peer peer;
private final DataOutputStream dout;
private Thread thread;
private boolean quit;
// Contains Messages.
private List sendQueue = new ArrayList();
public PeerConnectionOut(Peer peer, DataOutputStream dout)
{
this.peer = peer;
this.dout = dout;
quit = false;
thread = new Thread(this);
thread.start();
}
/**
* Continuesly monitors for more outgoing messages that have to be send.
* Stops if quit is true of an IOException occurs.
*/
public void run()
{
try
{
while (!quit)
{
Message m = null;
PeerState state = null;
synchronized(sendQueue)
{
while (!quit && sendQueue.isEmpty())
{
try
{
// Make sure everything will reach the other side.
dout.flush();
// Wait till more data arrives.
sendQueue.wait();
}
catch (InterruptedException ie)
{
/* ignored */
}
}
state = peer.state;
if (!quit && state != null)
{
// Piece messages are big. So if there are other
// (control) messages make sure they are send first.
// Also remove request messages from the queue if
// we are currently being choked to prevent them from
// being send even if we get unchoked a little later.
// (Since we will resent them anyway in that case.)
// And remove piece messages if we are choking.
Iterator it = sendQueue.iterator();
while (m == null && it.hasNext())
{
Message nm = (Message)it.next();
if (nm.type == Message.PIECE)
{
if (state.choking)
it.remove();
nm = null;
}
else if (nm.type == Message.REQUEST && state.choked)
{
it.remove();
nm = null;
}
if (m == null && nm != null)
{
m = nm;
it.remove();
}
}
if (m == null && sendQueue.size() > 0)
m = (Message)sendQueue.remove(0);
}
}
if (m != null)
{
if (Snark.debug >= Snark.ALL)
Snark.debug("Send " + peer + ": " + m, Snark.ALL);
m.sendMessage(dout);
// Remove all piece messages after sending a choke message.
if (m.type == Message.CHOKE)
removeMessage(Message.PIECE);
// XXX - Should also register overhead...
if (m.type == Message.PIECE)
state.uploaded(m.len);
m = null;
}
}
}
catch (IOException ioe)
{
// Ignore, probably other side closed connection.
}
catch (Throwable t)
{
Snark.debug(peer + ": " + t, Snark.ERROR);
t.printStackTrace();
}
finally
{
quit = true;
peer.disconnect();
}
}
public void disconnect()
{
synchronized(sendQueue)
{
if (quit == true)
return;
quit = true;
thread.interrupt();
sendQueue.clear();
sendQueue.notify();
}
}
/**
* Adds a message to the sendQueue and notifies the method waiting
* on the sendQueue to change.
*/
private void addMessage(Message m)
{
synchronized(sendQueue)
{
sendQueue.add(m);
sendQueue.notify();
}
}
/**
* Removes a particular message type from the queue.
*
* @param type the Message type to remove.
* @returns true when a message of the given type was removed, false
* otherwise.
*/
private boolean removeMessage(int type)
{
boolean removed = false;
synchronized(sendQueue)
{
Iterator it = sendQueue.iterator();
while (it.hasNext())
{
Message m = (Message)it.next();
if (m.type == type)
{
it.remove();
removed = true;
}
}
}
return removed;
}
void sendAlive()
{
Message m = new Message();
m.type = Message.KEEP_ALIVE;
addMessage(m);
}
void sendChoke(boolean choke)
{
// We cancel the (un)choke but keep PIECE messages.
// PIECE messages are purged if a choke is actually send.
synchronized(sendQueue)
{
int inverseType = choke ? Message.UNCHOKE
: Message.CHOKE;
if (!removeMessage(inverseType))
{
Message m = new Message();
if (choke)
m.type = Message.CHOKE;
else
m.type = Message.UNCHOKE;
addMessage(m);
}
}
}
void sendInterest(boolean interest)
{
synchronized(sendQueue)
{
int inverseType = interest ? Message.UNINTERESTED
: Message.INTERESTED;
if (!removeMessage(inverseType))
{
Message m = new Message();
if (interest)
m.type = Message.INTERESTED;
else
m.type = Message.UNINTERESTED;
addMessage(m);
}
}
}
void sendHave(int piece)
{
Message m = new Message();
m.type = Message.HAVE;
m.piece = piece;
addMessage(m);
}
void sendBitfield(BitField bitfield)
{
Message m = new Message();
m.type = Message.BITFIELD;
m.data = bitfield.getFieldBytes();
m.off = 0;
m.len = m.data.length;
addMessage(m);
}
void sendRequests(List requests)
{
Iterator it = requests.iterator();
while (it.hasNext())
{
Request req = (Request)it.next();
sendRequest(req);
}
}
void sendRequest(Request req)
{
Message m = new Message();
m.type = Message.REQUEST;
m.piece = req.piece;
m.begin = req.off;
m.length = req.len;
addMessage(m);
}
void sendPiece(int piece, int begin, int length, byte[] bytes)
{
Message m = new Message();
m.type = Message.PIECE;
m.piece = piece;
m.begin = begin;
m.length = length;
m.data = bytes;
m.off = begin;
m.len = length;
addMessage(m);
}
void sendCancel(Request req)
{
// See if it is still in our send queue
synchronized(sendQueue)
{
Iterator it = sendQueue.iterator();
while (it.hasNext())
{
Message m = (Message)it.next();
if (m.type == Message.REQUEST
&& m.piece == req.piece
&& m.begin == req.off
&& m.length == req.len)
it.remove();
}
}
// Always send, just to be sure it it is really canceled.
Message m = new Message();
m.type = Message.CANCEL;
m.piece = req.piece;
m.begin = req.off;
m.length = req.len;
addMessage(m);
}
// Called by the PeerState when the other side doesn't want this
// request to be handled anymore. Removes any pending Piece Message
// from out send queue.
void cancelRequest(int piece, int begin, int length)
{
synchronized (sendQueue)
{
Iterator it = sendQueue.iterator();
while (it.hasNext())
{
Message m = (Message)it.next();
if (m.type == Message.PIECE
&& m.piece == piece
&& m.begin == begin
&& m.length == length)
it.remove();
}
}
}
}

View File

@ -0,0 +1,534 @@
/* PeerCoordinator - Coordinates which peers do what (up and downloading).
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.util.*;
import java.io.IOException;
/**
* Coordinates what peer does what.
*/
public class PeerCoordinator implements PeerListener
{
final MetaInfo metainfo;
final Storage storage;
// package local for access by CheckDownLoadersTask
final static long CHECK_PERIOD = 20*1000; // 20 seconds
final static int MAX_CONNECTIONS = 24;
final static int MAX_UPLOADERS = 12; // i2p: might as well balance it out
// Approximation of the number of current uploaders.
// Resynced by PeerChecker once in a while.
int uploaders = 0;
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
// int downloaders = 0;
private long uploaded;
private long downloaded;
// synchronize on this when changing peers or downloaders
final List peers = new ArrayList();
/** Timer to handle all periodical tasks. */
private final Timer timer = new Timer(true);
private final byte[] id;
// Some random wanted pieces
private final List wantedPieces;
private boolean halted = false;
private final CoordinatorListener listener;
public PeerCoordinator(byte[] id, MetaInfo metainfo, Storage storage,
CoordinatorListener listener)
{
this.id = id;
this.metainfo = metainfo;
this.storage = storage;
this.listener = listener;
// Make a list of pieces
wantedPieces = new ArrayList();
BitField bitfield = storage.getBitField();
for(int i = 0; i < metainfo.getPieces(); i++)
if (!bitfield.get(i))
wantedPieces.add(new Piece(i));
Collections.shuffle(wantedPieces);
// Install a timer to check the uploaders.
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
}
public byte[] getID()
{
return id;
}
public boolean completed()
{
return storage.complete();
}
public int getPeers()
{
synchronized(peers)
{
return peers.size();
}
}
/**
* Returns how many bytes are still needed to get the complete file.
*/
public long getLeft()
{
// XXX - Only an approximation.
return storage.needed() * metainfo.getPieceLength(0);
}
/**
* Returns the total number of uploaded bytes of all peers.
*/
public long getUploaded()
{
return uploaded;
}
/**
* Returns the total number of downloaded bytes of all peers.
*/
public long getDownloaded()
{
return downloaded;
}
public MetaInfo getMetaInfo()
{
return metainfo;
}
public boolean needPeers()
{
synchronized(peers)
{
return !halted && peers.size() < MAX_CONNECTIONS;
}
}
public void halt()
{
halted = true;
synchronized(peers)
{
// Stop peer checker task.
timer.cancel();
// Stop peers.
Iterator it = peers.iterator();
while(it.hasNext())
{
Peer peer = (Peer)it.next();
peer.disconnect();
it.remove();
removePeerFromPieces(peer);
}
}
}
public void connected(Peer peer)
{
if (halted)
{
peer.disconnect(false);
return;
}
synchronized(peers)
{
if (peerIDInList(peer.getPeerID(), peers))
{
if (Snark.debug >= Snark.INFO)
Snark.debug("Already connected to: " + peer, Snark.INFO);
peer.disconnect(false); // Don't deregister this connection/peer.
}
else
{
if (Snark.debug >= Snark.INFO)
Snark.debug("New connection to peer: " + peer, Snark.INFO);
// Add it to the beginning of the list.
// And try to optimistically make it a uploader.
peers.add(0, peer);
unchokePeer();
if (listener != null)
listener.peerChange(this, peer);
}
}
}
private static boolean peerIDInList(PeerID pid, List peers)
{
Iterator it = peers.iterator();
while (it.hasNext())
if (pid.sameID(((Peer)it.next()).getPeerID()))
return true;
return false;
}
public void addPeer(final Peer peer)
{
if (halted)
{
peer.disconnect(false);
return;
}
boolean need_more;
synchronized(peers)
{
need_more = !peer.isConnected() && peers.size() < MAX_CONNECTIONS;
}
if (need_more)
{
// Run the peer with us as listener and the current bitfield.
final PeerListener listener = this;
final BitField bitfield = storage.getBitField();
Runnable r = new Runnable()
{
public void run()
{
peer.runConnection(listener, bitfield);
}
};
String threadName = peer.toString();
new Thread(r, threadName).start();
}
else
if (Snark.debug >= Snark.INFO)
if (peer.isConnected())
Snark.debug("Add peer already connected: " + peer, Snark.INFO);
else
Snark.debug("MAX_CONNECTIONS = " + MAX_CONNECTIONS
+ " not accepting extra peer: " + peer, Snark.INFO);
}
// (Optimistically) unchoke. Should be called with peers synchronized
void unchokePeer()
{
// linked list will contain all interested peers that we choke.
// At the start are the peers that have us unchoked at the end the
// other peer that are interested, but are choking us.
List interested = new LinkedList();
Iterator it = peers.iterator();
while (it.hasNext())
{
Peer peer = (Peer)it.next();
boolean remove = false;
if (uploaders < MAX_UPLOADERS
&& peer.isChoking()
&& peer.isInterested())
{
if (!peer.isChoked())
interested.add(0, peer);
else
interested.add(peer);
}
}
while (uploaders < MAX_UPLOADERS && interested.size() > 0)
{
Peer peer = (Peer)interested.remove(0);
if (Snark.debug >= Snark.INFO)
Snark.debug("Unchoke: " + peer, Snark.INFO);
peer.setChoking(false);
uploaders++;
// Put peer back at the end of the list.
peers.remove(peer);
peers.add(peer);
}
}
public byte[] getBitMap()
{
return storage.getBitField().getFieldBytes();
}
/**
* Returns true if we don't have the given piece yet.
*/
public boolean gotHave(Peer peer, int piece)
{
if (listener != null)
listener.peerChange(this, peer);
synchronized(wantedPieces)
{
return wantedPieces.contains(new Piece(piece));
}
}
/**
* Returns true if the given bitfield contains at least one piece we
* are interested in.
*/
public boolean gotBitField(Peer peer, BitField bitfield)
{
if (listener != null)
listener.peerChange(this, peer);
synchronized(wantedPieces)
{
Iterator it = wantedPieces.iterator();
while (it.hasNext())
{
Piece p = (Piece)it.next();
int i = p.getId();
if (bitfield.get(i))
p.addPeer(peer);
return true;
}
}
return false;
}
/**
* Returns one of pieces in the given BitField that is still wanted or
* -1 if none of the given pieces are wanted.
*/
public int wantPiece(Peer peer, BitField havePieces)
{
if (halted)
return -1;
synchronized(wantedPieces)
{
Piece piece = null;
Collections.sort(wantedPieces); // Sort in order of rarest first.
List requested = new ArrayList();
Iterator it = wantedPieces.iterator();
while (piece == null && it.hasNext())
{
Piece p = (Piece)it.next();
if (havePieces.get(p.getId()) && !p.isRequested())
{
piece = p;
}
else if (p.isRequested())
{
requested.add(p);
}
}
//Only request a piece we've requested before if there's no other choice.
if (piece == null) {
Iterator it2 = requested.iterator();
while (piece == null && it2.hasNext())
{
Piece p = (Piece)it2.next();
if (havePieces.get(p.getId()))
{
piece = p;
}
}
if (piece == null) return -1; //If we still can't find a piece we want, so be it.
}
piece.setRequested(true);
return piece.getId();
}
}
/**
* Returns a byte array containing the requested piece or null of
* the piece is unknown.
*/
public byte[] gotRequest(Peer peer, int piece)
{
if (halted)
return null;
try
{
return storage.getPiece(piece);
}
catch (IOException ioe)
{
Snark.fatal("Error reading storage", ioe);
return null; // Never reached.
}
}
/**
* Called when a peer has uploaded some bytes of a piece.
*/
public void uploaded(Peer peer, int size)
{
uploaded += size;
if (listener != null)
listener.peerChange(this, peer);
}
/**
* Called when a peer has downloaded some bytes of a piece.
*/
public void downloaded(Peer peer, int size)
{
downloaded += size;
if (listener != null)
listener.peerChange(this, peer);
}
/**
* Returns false if the piece is no good (according to the hash).
* In that case the peer that supplied the piece should probably be
* blacklisted.
*/
public boolean gotPiece(Peer peer, int piece, byte[] bs)
{
if (halted)
return true; // We don't actually care anymore.
synchronized(wantedPieces)
{
Piece p = new Piece(piece);
if (!wantedPieces.contains(p))
{
if (Snark.debug >= Snark.INFO)
Snark.debug(peer + " piece " + piece + " no longer needed",
Snark.INFO);
// No need to announce have piece to peers.
// Assume we got a good piece, we don't really care anymore.
return true;
}
try
{
if (storage.putPiece(piece, bs))
{
if (Snark.debug >= Snark.INFO)
Snark.debug("Recv p" + piece + " " + peer, Snark.INFO);
}
else
{
// Oops. We didn't actually download this then... :(
downloaded -= metainfo.getPieceLength(piece);
if (Snark.debug >= Snark.NOTICE)
Snark.debug("Got BAD piece " + piece + " from " + peer,
Snark.NOTICE);
return false; // No need to announce BAD piece to peers.
}
}
catch (IOException ioe)
{
Snark.fatal("Error writing storage", ioe);
}
wantedPieces.remove(p);
}
// Announce to the world we have it!
synchronized(peers)
{
Iterator it = peers.iterator();
while (it.hasNext())
{
Peer p = (Peer)it.next();
if (p.isConnected())
p.have(piece);
}
}
return true;
}
public void gotChoke(Peer peer, boolean choke)
{
if (Snark.debug >= Snark.INFO)
Snark.debug("Got choke(" + choke + "): " + peer, Snark.INFO);
if (listener != null)
listener.peerChange(this, peer);
}
public void gotInterest(Peer peer, boolean interest)
{
if (interest)
{
synchronized(peers)
{
if (uploaders < MAX_UPLOADERS)
{
if(peer.isChoking())
{
uploaders++;
peer.setChoking(false);
if (Snark.debug >= Snark.INFO)
Snark.debug("Unchoke: " + peer, Snark.INFO);
}
}
}
}
if (listener != null)
listener.peerChange(this, peer);
}
public void disconnected(Peer peer)
{
if (Snark.debug >= Snark.INFO)
Snark.debug("Disconnected " + peer, Snark.INFO);
synchronized(peers)
{
// Make sure it is no longer in our lists
if (peers.remove(peer))
{
// Unchoke some random other peer
unchokePeer();
removePeerFromPieces(peer);
}
}
if (listener != null)
listener.peerChange(this, peer);
}
/** Called when a peer is removed, to prevent it from being used in
* rarest-first calculations.
*/
public void removePeerFromPieces(Peer peer) {
synchronized(wantedPieces) {
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
Piece piece = (Piece)iter.next();
piece.removePeer(peer);
}
}
}
}

View File

@ -0,0 +1,208 @@
/* PeerID - All public information concerning a peer.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import org.klomp.snark.bencode.*;
import net.i2p.data.Base64;
import net.i2p.data.Destination;
import net.i2p.data.DataFormatException;
public class PeerID implements Comparable
{
private final byte[] id;
private final Destination address;
private final int port;
private final int hash;
public PeerID(byte[] id, Destination address)
{
this.id = id;
this.address = address;
this.port = 6881;
hash = calculateHash();
}
/**
* Creates a PeerID from a BDecoder.
*/
public PeerID(BDecoder be)
throws IOException
{
this(be.bdecodeMap().getMap());
}
/**
* Creates a PeerID from a Map containing BEncoded peer id, ip and
* port.
*/
public PeerID(Map m)
throws InvalidBEncodingException, UnknownHostException
{
BEValue bevalue = (BEValue)m.get("peer id");
if (bevalue == null)
throw new InvalidBEncodingException("peer id missing");
id = bevalue.getBytes();
bevalue = (BEValue)m.get("ip");
if (bevalue == null)
throw new InvalidBEncodingException("ip missing");
address = I2PSnarkUtil.instance().getDestination(bevalue.getString());
if (address == null)
throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]");
port = 6881;
hash = calculateHash();
}
public byte[] getID()
{
return id;
}
public Destination getAddress()
{
return address;
}
public int getPort()
{
return port;
}
private int calculateHash()
{
int b = 0;
for (int i = 0; i < id.length; i++)
b ^= id[i];
return (b ^ address.hashCode()) ^ port;
}
/**
* The hash code of a PeerID is the exclusive or of all id bytes.
*/
public int hashCode()
{
return hash;
}
/**
* Returns true if and only if this peerID and the given peerID have
* the same 20 bytes as ID.
*/
public boolean sameID(PeerID pid)
{
boolean equal = true;
for (int i = 0; equal && i < id.length; i++)
equal = id[i] == pid.id[i];
return equal;
}
/**
* Two PeerIDs are equal when they have the same id, address and port.
*/
public boolean equals(Object o)
{
if (o instanceof PeerID)
{
PeerID pid = (PeerID)o;
return port == pid.port
&& address.equals(pid.address)
&& sameID(pid);
}
else
return false;
}
/**
* Compares port, address and id.
*/
public int compareTo(Object o)
{
PeerID pid = (PeerID)o;
int result = port - pid.port;
if (result != 0)
return result;
result = address.hashCode() - pid.address.hashCode();
if (result != 0)
return result;
for (int i = 0; i < id.length; i++)
{
result = id[i] - pid.id[i];
if (result != 0)
return result;
}
return 0;
}
/**
* Returns the String "id@address" where id is the base64 encoded id.
*/
public String toString()
{
int nonZero = 0;
for (int i = 0; i < id.length; i++) {
if (id[i] != 0) {
nonZero = i;
break;
}
}
return Base64.encode(id, nonZero, id.length-nonZero).substring(0,4) + "@" + address.calculateHash().toBase64().substring(0,6);
}
/**
* Encode an id as a hex encoded string and remove leading zeros.
*/
public static String idencode(byte[] bs)
{
boolean leading_zeros = true;
StringBuffer sb = new StringBuffer(bs.length*2);
for (int i = 0; i < bs.length; i++)
{
int c = bs[i] & 0xFF;
if (leading_zeros && c == 0)
continue;
else
leading_zeros = false;
if (c < 16)
sb.append('0');
sb.append(Integer.toHexString(c));
}
return sb.toString();
}
}

View File

@ -0,0 +1,145 @@
/* PeerListener - Interface for listening to peer events.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
/**
* Listener for Peer events.
*/
public interface PeerListener
{
/**
* Called when the connection to the peer has started and the
* handshake was successfull.
*
* @param peer the Peer that just got connected.
*/
void connected(Peer peer);
/**
* Called when the connection to the peer was terminated or the
* connection handshake failed.
*
* @param peer the Peer that just got disconnected.
*/
void disconnected(Peer peer);
/**
* Called when a choke message is received.
*
* @param peer the Peer that got the message.
* @param choke true when the peer got a choke message, false when
* the peer got an unchoke message.
*/
void gotChoke(Peer peer, boolean choke);
/**
* Called when an interested message is received.
*
* @param peer the Peer that got the message.
* @param interest true when the peer got a interested message, false when
* the peer got an uninterested message.
*/
void gotInterest(Peer peer, boolean interest);
/**
* Called when a have piece message is received. If the method
* returns true and the peer has not yet received a interested
* message or we indicated earlier to be not interested then an
* interested message will be send.
*
* @param peer the Peer that got the message.
* @param piece the piece number that the per just got.
*
* @return true when it is a piece that we want, false if the piece is
* already known.
*/
boolean gotHave(Peer peer, int piece);
/**
* Called when a bitmap message is received. If this method returns
* true a interested message will be send back to the peer.
*
* @param peer the Peer that got the message.
* @param bitfield a BitField containing the pieces that the other
* side has.
*
* @return true when the BitField contains pieces we want, false if
* the piece is already known.
*/
boolean gotBitField(Peer peer, BitField bitfield);
/**
* Called when a piece is received from the peer. The piece must be
* requested by Peer.request() first. If this method returns false
* that means the Peer provided a corrupted piece and the connection
* will be closed.
*
* @param peer the Peer that got the piece.
* @param piece the piece number received.
* @param bs the byte array containing the piece.
*
* @return true when the bytes represent the piece, false otherwise.
*/
boolean gotPiece(Peer peer, int piece, byte[] bs);
/**
* Called when the peer wants (part of) a piece from us. Only called
* when the peer is not choked by us (<code>peer.choke(false)</code>
* was called).
*
* @param peer the Peer that wants the piece.
* @param piece the piece number requested.
*
* @return a byte array containing the piece or null when the piece
* is not available (which is a protocol error).
*/
byte[] gotRequest(Peer peer, int piece);
/**
* Called when a (partial) piece has been downloaded from the peer.
*
* @param peer the Peer from which size bytes where downloaded.
* @param size the number of bytes that where downloaded.
*/
void downloaded(Peer peer, int size);
/**
* Called when a (partial) piece has been uploaded to the peer.
*
* @param peer the Peer to which size bytes where uploaded.
* @param size the number of bytes that where uploaded.
*/
void uploaded(Peer peer, int size);
/**
* Called when we are downloading from the peer and need to ask for
* a new piece. Might be called multiple times before
* <code>gotPiece()</code> is called.
*
* @param peer the Peer that will be asked to provide the piece.
* @param bitfield a BitField containing the pieces that the other
* side has.
*
* @return one of the pieces from the bitfield that we want or -1 if
* we are no longer interested in the peer.
*/
int wantPiece(Peer peer, BitField bitfield);
}

View File

@ -0,0 +1,128 @@
/* PeerMonitorTasks - TimerTask that monitors the peers and total up/down speed
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.util.*;
/**
* TimerTask that monitors the peers and total up/download speeds.
* Works together with the main Snark class to report periodical statistics.
*/
class PeerMonitorTask extends TimerTask
{
final static long MONITOR_PERIOD = 10 * 1000; // Ten seconds.
private final long KILOPERSECOND = 1024 * (MONITOR_PERIOD / 1000);
private final PeerCoordinator coordinator;
private long lastDownloaded = 0;
private long lastUploaded = 0;
PeerMonitorTask(PeerCoordinator coordinator)
{
this.coordinator = coordinator;
}
public void run()
{
// Get some statistics
int peers = 0;
int uploaders = 0;
int downloaders = 0;
int interested = 0;
int interesting = 0;
int choking = 0;
int choked = 0;
synchronized(coordinator.peers)
{
Iterator it = coordinator.peers.iterator();
while (it.hasNext())
{
Peer peer = (Peer)it.next();
// Don't list dying peers
if (!peer.isConnected())
continue;
peers++;
if (!peer.isChoking())
uploaders++;
if (!peer.isChoked() && peer.isInteresting())
downloaders++;
if (peer.isInterested())
interested++;
if (peer.isInteresting())
interesting++;
if (peer.isChoking())
choking++;
if (peer.isChoked())
choked++;
}
}
// Print some statistics
long downloaded = coordinator.getDownloaded();
String totalDown;
if (downloaded >= 10 * 1024 * 1024)
totalDown = (downloaded / (1024 * 1024)) + "MB";
else
totalDown = (downloaded / 1024 )+ "KB";
long uploaded = coordinator.getUploaded();
String totalUp;
if (uploaded >= 10 * 1024 * 1024)
totalUp = (uploaded / (1024 * 1024)) + "MB";
else
totalUp = (uploaded / 1024) + "KB";
int needP = coordinator.storage.needed();
long needMB
= needP * coordinator.metainfo.getPieceLength(0) / (1024 * 1024);
int totalP = coordinator.metainfo.getPieces();
long totalMB = coordinator.metainfo.getTotalLength() / (1024 * 1024);
System.out.println();
System.out.println("Down: "
+ (downloaded - lastDownloaded) / KILOPERSECOND
+ "KB/s"
+ " (" + totalDown + ")"
+ " Up: "
+ (uploaded - lastUploaded) / KILOPERSECOND
+ "KB/s"
+ " (" + totalUp + ")"
+ " Need " + needP
+ " (" + needMB + "MB)"
+ " of " + totalP
+ " (" + totalMB + "MB)"
+ " pieces");
System.out.println(peers + ": Download #" + downloaders
+ " Upload #" + uploaders
+ " Interested #" + interested
+ " Interesting #" + interesting
+ " Choking #" + choking
+ " Choked #" + choked);
System.out.println();
lastDownloaded = downloaded;
lastUploaded = uploaded;
}
}

View File

@ -0,0 +1,539 @@
/* PeerState - Keeps track of the Peer state through connection callbacks.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
class PeerState
{
final Peer peer;
final PeerListener listener;
final MetaInfo metainfo;
// Interesting and choking describes whether we are interested in or
// are choking the other side.
boolean interesting = false;
boolean choking = true;
// Interested and choked describes whether the other side is
// interested in us or choked us.
boolean interested = false;
boolean choked = true;
// Package local for use by Peer.
long downloaded;
long uploaded;
BitField bitfield;
// Package local for use by Peer.
final PeerConnectionIn in;
final PeerConnectionOut out;
// Outstanding request
private final List outstandingRequests = new ArrayList();
private Request lastRequest = null;
// If we have te resend outstanding requests (true after we got choked).
private boolean resend = false;
private final static int MAX_PIPELINE = 5;
private final static int PARTSIZE = 64*1024; // default was 16K, i2p-bt uses 64KB
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
PeerConnectionIn in, PeerConnectionOut out)
{
this.peer = peer;
this.listener = listener;
this.metainfo = metainfo;
this.in = in;
this.out = out;
}
// NOTE Methods that inspect or change the state synchronize (on this).
void keepAliveMessage()
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug(peer + " rcv alive", Snark.DEBUG);
/* XXX - ignored */
}
void chokeMessage(boolean choke)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug(peer + " rcv " + (choke ? "" : "un") + "choked",
Snark.DEBUG);
choked = choke;
if (choked)
resend = true;
listener.gotChoke(peer, choke);
if (!choked && interesting)
request();
}
void interestedMessage(boolean interest)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug(peer + " rcv " + (interest ? "" : "un")
+ "interested", Snark.DEBUG);
interested = interest;
listener.gotInterest(peer, interest);
}
void haveMessage(int piece)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug(peer + " rcv have(" + piece + ")", Snark.DEBUG);
// Sanity check
if (piece < 0 || piece >= metainfo.getPieces())
{
// XXX disconnect?
if (Snark.debug >= Snark.INFO)
Snark.debug("Got strange 'have: " + piece + "' message from " + peer,
+ Snark.INFO);
return;
}
synchronized(this)
{
// Can happen if the other side never send a bitfield message.
if (bitfield == null)
bitfield = new BitField(metainfo.getPieces());
bitfield.set(piece);
}
if (listener.gotHave(peer, piece))
setInteresting(true);
}
void bitfieldMessage(byte[] bitmap)
{
synchronized(this)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug(peer + " rcv bitfield", Snark.DEBUG);
if (bitfield != null)
{
// XXX - Be liberal in what you except?
if (Snark.debug >= Snark.INFO)
Snark.debug("Got unexpected bitfield message from " + peer,
Snark.INFO);
return;
}
// XXX - Check for weird bitfield and disconnect?
bitfield = new BitField(bitmap, metainfo.getPieces());
}
setInteresting(listener.gotBitField(peer, bitfield));
}
void requestMessage(int piece, int begin, int length)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug(peer + " rcv request("
+ piece + ", " + begin + ", " + length + ") ",
Snark.DEBUG);
if (choking)
{
if (Snark.debug >= Snark.INFO)
Snark.debug("Request received, but choking " + peer, Snark.INFO);
return;
}
// Sanity check
if (piece < 0
|| piece >= metainfo.getPieces()
|| begin < 0
|| begin > metainfo.getPieceLength(piece)
|| length <= 0
|| length > 4*PARTSIZE)
{
// XXX - Protocol error -> disconnect?
if (Snark.debug >= Snark.INFO)
Snark.debug("Got strange 'request: " + piece
+ ", " + begin
+ ", " + length
+ "' message from " + peer,
Snark.INFO);
return;
}
byte[] pieceBytes = listener.gotRequest(peer, piece);
if (pieceBytes == null)
{
// XXX - Protocol error-> diconnect?
if (Snark.debug >= Snark.INFO)
Snark.debug("Got request for unknown piece: " + piece, Snark.INFO);
return;
}
// More sanity checks
if (begin >= pieceBytes.length || begin + length > pieceBytes.length)
{
// XXX - Protocol error-> disconnect?
if (Snark.debug >= Snark.INFO)
Snark.debug("Got out of range 'request: " + piece
+ ", " + begin
+ ", " + length
+ "' message from " + peer,
Snark.INFO);
return;
}
if (Snark.debug >= Snark.DEBUG)
Snark.debug("Sending (" + piece + ", " + begin + ", "
+ length + ")" + " to " + peer, Snark.DEBUG);
out.sendPiece(piece, begin, length, pieceBytes);
// Tell about last subpiece delivery.
if (begin + length == pieceBytes.length)
if (Snark.debug >= Snark.DEBUG)
Snark.debug("Send p" + piece + " " + peer,
Snark.DEBUG);
}
/**
* Called when some bytes have left the outgoing connection.
* XXX - Should indicate whether it was a real piece or overhead.
*/
void uploaded(int size)
{
uploaded += size;
listener.uploaded(peer, size);
}
/**
* Called when a partial piece request has been handled by
* PeerConnectionIn.
*/
void pieceMessage(Request req)
{
int size = req.len;
downloaded += size;
listener.downloaded(peer, size);
// Last chunk needed for this piece?
if (getFirstOutstandingRequest(req.piece) == -1)
{
if (listener.gotPiece(peer, req.piece, req.bs))
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug("Got " + req.piece + ": " + peer, Snark.DEBUG);
}
else
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug("Got BAD " + req.piece + " from " + peer,
Snark.DEBUG);
// XXX ARGH What now !?!
downloaded = 0;
}
}
}
synchronized private int getFirstOutstandingRequest(int piece)
{
for (int i = 0; i < outstandingRequests.size(); i++)
if (((Request)outstandingRequests.get(i)).piece == piece)
return i;
return -1;
}
/**
* Called when a piece message is being processed by the incoming
* connection. Returns null when there was no such request. It also
* requeues/sends requests when it thinks that they must have been
* lost.
*/
Request getOutstandingRequest(int piece, int begin, int length)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug("getChunk("
+ piece + "," + begin + "," + length + ") "
+ peer, Snark.DEBUG);
int r = getFirstOutstandingRequest(piece);
// Unrequested piece number?
if (r == -1)
{
if (Snark.debug >= Snark.INFO)
Snark.debug("Unrequested 'piece: " + piece + ", "
+ begin + ", " + length + "' received from "
+ peer,
Snark.INFO);
downloaded = 0; // XXX - punishment?
return null;
}
// Lookup the correct piece chunk request from the list.
Request req;
synchronized(this)
{
req = (Request)outstandingRequests.get(r);
while (req.piece == piece && req.off != begin
&& r < outstandingRequests.size() - 1)
{
r++;
req = (Request)outstandingRequests.get(r);
}
// Something wrong?
if (req.piece != piece || req.off != begin || req.len != length)
{
if (Snark.debug >= Snark.INFO)
Snark.debug("Unrequested or unneeded 'piece: "
+ piece + ", "
+ begin + ", "
+ length + "' received from "
+ peer,
Snark.INFO);
downloaded = 0; // XXX - punishment?
return null;
}
// Report missing requests.
if (r != 0)
{
if (Snark.debug >= Snark.INFO)
System.err.print("Some requests dropped, got " + req
+ ", wanted:");
for (int i = 0; i < r; i++)
{
Request dropReq = (Request)outstandingRequests.remove(0);
outstandingRequests.add(dropReq);
// We used to rerequest the missing chunks but that mostly
// just confuses the other side. So now we just keep
// waiting for them. They will be rerequested when we get
// choked/unchoked again.
/*
if (!choked)
out.sendRequest(dropReq);
*/
if (Snark.debug >= Snark.INFO)
System.err.print(" " + dropReq);
}
if (Snark.debug >= Snark.INFO)
System.err.println(" " + peer);
}
outstandingRequests.remove(0);
}
// Request more if necessary to keep the pipeline filled.
addRequest();
return req;
}
void cancelMessage(int piece, int begin, int length)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug("Got cancel message ("
+ piece + ", " + begin + ", " + length + ")",
Snark.DEBUG);
out.cancelRequest(piece, begin, length);
}
void unknownMessage(int type, byte[] bs)
{
if (Snark.debug >= Snark.WARNING)
Snark.debug("Warning: Ignoring unknown message type: " + type
+ " length: " + bs.length, Snark.WARNING);
}
void havePiece(int piece)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug("Tell " + peer + " havePiece(" + piece + ")", Snark.DEBUG);
synchronized(this)
{
// Tell the other side that we are no longer interested in any of
// the outstanding requests for this piece.
if (lastRequest != null && lastRequest.piece == piece)
lastRequest = null;
Iterator it = outstandingRequests.iterator();
while (it.hasNext())
{
Request req = (Request)it.next();
if (req.piece == piece)
{
it.remove();
// Send cancel even when we are choked to make sure that it is
// really never ever send.
out.sendCancel(req);
}
}
}
// Tell the other side that we really have this piece.
out.sendHave(piece);
// Request something else if necessary.
addRequest();
synchronized(this)
{
// Is the peer still interesting?
if (lastRequest == null)
setInteresting(false);
}
}
// Starts or resumes requesting pieces.
private void request()
{
// Are there outstanding requests that have to be resend?
if (resend)
{
out.sendRequests(outstandingRequests);
resend = false;
}
// Add/Send some more requests if necessary.
addRequest();
}
/**
* Adds a new request to the outstanding requests list.
*/
private void addRequest()
{
boolean more_pieces = true;
while (more_pieces)
{
synchronized(this)
{
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
}
// We want something and we don't have outstanding requests?
if (more_pieces && lastRequest == null)
more_pieces = requestNextPiece();
else if (more_pieces) // We want something
{
int pieceLength;
boolean isLastChunk;
synchronized(this)
{
pieceLength = metainfo.getPieceLength(lastRequest.piece);
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
}
// Last part of a piece?
if (isLastChunk)
more_pieces = requestNextPiece();
else
{
synchronized(this)
{
int nextPiece = lastRequest.piece;
int nextBegin = lastRequest.off + PARTSIZE;
byte[] bs = lastRequest.bs;
int maxLength = pieceLength - nextBegin;
int nextLength = maxLength > PARTSIZE ? PARTSIZE
: maxLength;
Request req
= new Request(nextPiece, bs, nextBegin, nextLength);
outstandingRequests.add(req);
if (!choked)
out.sendRequest(req);
lastRequest = req;
}
}
}
}
if (Snark.debug >= Snark.DEBUG)
Snark.debug(peer + " requests " + outstandingRequests, Snark.DEBUG);
}
// Starts requesting first chunk of next piece. Returns true if
// something has been added to the requests, false otherwise.
private boolean requestNextPiece()
{
// Check that we already know what the other side has.
if (bitfield != null)
{
int nextPiece = listener.wantPiece(peer, bitfield);
if (Snark.debug >= Snark.DEBUG)
Snark.debug(peer + " want piece " + nextPiece, Snark.DEBUG);
synchronized(this)
{
if (nextPiece != -1
&& (lastRequest == null || lastRequest.piece != nextPiece))
{
int piece_length = metainfo.getPieceLength(nextPiece);
byte[] bs = new byte[piece_length];
int length = Math.min(piece_length, PARTSIZE);
Request req = new Request(nextPiece, bs, 0, length);
outstandingRequests.add(req);
if (!choked)
out.sendRequest(req);
lastRequest = req;
return true;
}
}
}
return false;
}
synchronized void setInteresting(boolean interest)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug(peer + " setInteresting(" + interest + ")", Snark.DEBUG);
if (interest != interesting)
{
interesting = interest;
out.sendInterest(interest);
if (interesting && !choked)
request();
}
}
synchronized void setChoking(boolean choke)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug(peer + " setChoking(" + choke + ")", Snark.DEBUG);
if (choking != choke)
{
choking = choke;
out.sendChoke(choke);
}
}
}

View File

@ -0,0 +1,38 @@
package org.klomp.snark;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
public class Piece implements Comparable {
private int id;
private Set peers;
private boolean requested;
public Piece(int id) {
this.id = id;
this.peers = Collections.synchronizedSet(new HashSet());
this.requested = false;
}
public int compareTo(Object o) throws ClassCastException {
return this.peers.size() - ((Piece)o).peers.size();
}
public boolean equals(Object o) {
if (o == null) return false;
try {
return this.id == ((Piece)o).id;
} catch (ClassCastException cce) {
return false;
}
}
public int getId() { return this.id; }
public Set getPeers() { return this.peers; }
public boolean addPeer(Peer peer) { return this.peers.add(peer.getPeerID()); }
public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); }
public boolean isRequested() { return this.requested; }
public void setRequested(boolean requested) { this.requested = requested; }
}

View File

@ -0,0 +1,73 @@
/* Request - Holds all information needed for a (partial) piece request.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
/**
* Holds all information needed for a partial piece request.
*/
class Request
{
final int piece;
final byte[] bs;
final int off;
final int len;
/**
* Creates a new Request.
*
* @param piece Piece number requested.
* @param bs byte array where response should be stored.
* @param off the offset in the array.
* @param len the number of bytes requested.
*/
Request(int piece, byte[] bs, int off, int len)
{
this.piece = piece;
this.bs = bs;
this.off = off;
this.len = len;
// Sanity check
if (piece < 0 || off < 0 || len <= 0 || off + len > bs.length)
throw new IndexOutOfBoundsException("Illegal Request " + toString());
}
public int hashCode()
{
return piece ^ off ^ len;
}
public boolean equals(Object o)
{
if (o instanceof Request)
{
Request req = (Request)o;
return req.piece == piece && req.off == off && req.len == len;
}
return false;
}
public String toString()
{
return "(" + piece + "," + off + "," + len + ")";
}
}

View File

@ -0,0 +1,34 @@
/* ShutdownListener - Callback for end of shutdown sequence
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
/**
* Callback for end of shutdown sequence.
*/
interface ShutdownListener
{
/**
* Called when the SnarkShutdown hook has finished shutting down all
* subcomponents.
*/
void shutdown();
}

View File

@ -0,0 +1,598 @@
/* Snark - Main snark program startup class.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.*;
import java.net.*;
import java.util.*;
import org.klomp.snark.bencode.*;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PServerSocket;
/**
* Main Snark program startup class.
*
* @author Mark Wielaard (mark@klomp.org)
*/
public class Snark
implements StorageListener, CoordinatorListener, ShutdownListener
{
private final static int MIN_PORT = 6881;
private final static int MAX_PORT = 6889;
// Error messages (non-fatal)
public final static int ERROR = 1;
// Warning messages
public final static int WARNING = 2;
// Notices (peer level)
public final static int NOTICE = 3;
// Info messages (protocol policy level)
public final static int INFO = 4;
// Debug info (protocol level)
public final static int DEBUG = 5;
// Very low level stuff (network level)
public final static int ALL = 6;
/**
* What level of debug info to show.
*/
public static int debug = NOTICE;
// Whether or not to ask the user for commands while sharing
private static boolean command_interpreter = true;
private static final String newline = System.getProperty("line.separator");
private static final String copyright =
"The Hunting of the Snark Project - Copyright (C) 2003 Mark J. Wielaard"
+ newline + newline
+ "Snark comes with ABSOLUTELY NO WARRANTY. This is free software, and"
+ newline
+ "you are welcome to redistribute it under certain conditions; read the"
+ newline
+ "COPYING file for details." + newline + newline
+ "This is the I2P port, allowing anonymous bittorrent (http://www.i2p.net/)" + newline
+ "It will not work with normal torrents, so don't even try ;)";
private static final String usage =
"Press return for help. Type \"quit\" and return to stop.";
private static final String help =
"Commands: 'info', 'list', 'quit'.";
// String indicating main activity
static String activity = "Not started";
public static void main(String[] args)
{
System.out.println(copyright);
System.out.println();
// Parse debug, share/ip and torrent file options.
Snark snark = parseArguments(args);
SnarkShutdown snarkhook
= new SnarkShutdown(snark.storage,
snark.coordinator,
snark.acceptor,
snark.trackerclient,
snark);
Runtime.getRuntime().addShutdownHook(snarkhook);
Timer timer = new Timer(true);
TimerTask monitor = new PeerMonitorTask(snark.coordinator);
timer.schedule(monitor,
PeerMonitorTask.MONITOR_PERIOD,
PeerMonitorTask.MONITOR_PERIOD);
// Start command interpreter
if (Snark.command_interpreter)
{
boolean quit = false;
System.out.println();
System.out.println(usage);
System.out.println();
try
{
BufferedReader br = new BufferedReader
(new InputStreamReader(System.in));
String line = br.readLine();
while(!quit && line != null)
{
line = line.toLowerCase();
if ("quit".equals(line))
quit = true;
else if ("list".equals(line))
{
synchronized(coordinator.peers)
{
System.out.println(coordinator.peers.size()
+ " peers -"
+ " (i)nterested,"
+ " (I)nteresting,"
+ " (c)hoking,"
+ " (C)hoked:");
Iterator it = coordinator.peers.iterator();
while (it.hasNext())
{
Peer peer = (Peer)it.next();
System.out.println(peer);
System.out.println("\ti: " + peer.isInterested()
+ " I: " + peer.isInteresting()
+ " c: " + peer.isChoking()
+ " C: " + peer.isChoked());
}
}
}
else if ("info".equals(line))
{
System.out.println("Name: " + meta.getName());
System.out.println("Torrent: " + torrent);
System.out.println("Tracker: " + meta.getAnnounce());
List files = meta.getFiles();
System.out.println("Files: "
+ ((files == null) ? 1 : files.size()));
System.out.println("Pieces: " + meta.getPieces());
System.out.println("Piece size: "
+ meta.getPieceLength(0) / 1024
+ " KB");
System.out.println("Total size: "
+ meta.getTotalLength() / (1024 * 1024)
+ " MB");
}
else if ("".equals(line) || "help".equals(line))
{
System.out.println(usage);
System.out.println(help);
}
else
{
System.out.println("Unknown command: " + line);
System.out.println(usage);
}
if (!quit)
{
System.out.println();
line = br.readLine();
}
}
}
catch(IOException ioe)
{
debug("ERROR while reading stdin: " + ioe, ERROR);
}
// Explicit shutdown.
Runtime.getRuntime().removeShutdownHook(snarkhook);
snarkhook.start();
}
}
static String torrent;
static MetaInfo meta;
static Storage storage;
static PeerCoordinator coordinator;
static ConnectionAcceptor acceptor;
static TrackerClient trackerclient;
private Snark(String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener)
{
if (slistener == null)
slistener = this;
if (clistener == null)
clistener = this;
this.torrent = torrent;
activity = "Network setup";
// "Taking Three as the subject to reason about--
// A convenient number to state--
// We add Seven, and Ten, and then multiply out
// By One Thousand diminished by Eight.
//
// "The result we proceed to divide, as you see,
// By Nine Hundred and Ninety Two:
// Then subtract Seventeen, and the answer must be
// Exactly and perfectly true.
// Create a new ID and fill it with something random. First nine
// zeros bytes, then three bytes filled with snark and then
// sixteen random bytes.
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
byte[] id = new byte[20];
Random random = new Random();
int i;
for (i = 0; i < 9; i++)
id[i] = 0;
id[i++] = snark;
id[i++] = snark;
id[i++] = snark;
while (i < 20)
id[i++] = (byte)random.nextInt(256);
Snark.debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
int port;
IOException lastException = null;
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) fatal("Unable to connect to I2P");
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
if (serversocket == null)
fatal("Unable to listen for I2P connections");
else
debug("Listening on I2P destination " + serversocket.getManager().getSession().getMyDestination().toBase64(), NOTICE);
// Figure out what the torrent argument represents.
meta = null;
File f = null;
try
{
InputStream in = null;
f = new File(torrent);
if (f.exists())
in = new FileInputStream(f);
else
{
activity = "Getting torrent";
File torrentFile = I2PSnarkUtil.instance().get(torrent);
if (torrentFile == null) {
fatal("Unable to fetch " + torrent);
if (false) return; // never reached - fatal(..) throws
} else {
torrentFile.deleteOnExit();
in = new FileInputStream(torrentFile);
}
}
meta = new MetaInfo(new BDecoder(in));
}
catch(IOException ioe)
{
// OK, so it wasn't a torrent metainfo file.
if (f != null && f.exists())
if (ip == null)
fatal("'" + torrent + "' exists,"
+ " but is not a valid torrent metainfo file."
+ System.getProperty("line.separator"), ioe);
else
fatal("I2PSnark does not support creating and tracking a torrent at the moment");
/*
{
// Try to create a new metainfo file
Snark.debug
("Trying to create metainfo torrent for '" + torrent + "'",
NOTICE);
try
{
activity = "Creating torrent";
storage = new Storage
(f, "http://" + ip + ":" + port + "/announce", slistener);
storage.create();
meta = storage.getMetaInfo();
}
catch (IOException ioe2)
{
fatal("Could not create torrent for '" + torrent + "'", ioe2);
}
}
*/
else
fatal("Cannot open '" + torrent + "'", ioe);
}
debug(meta.toString(), INFO);
// When the metainfo torrent was created from an existing file/dir
// it already exists.
if (storage == null)
{
try
{
activity = "Checking storage";
storage = new Storage(meta, slistener);
storage.check();
}
catch (IOException ioe)
{
fatal("Could not create storage", ioe);
}
}
activity = "Collecting pieces";
coordinator = new PeerCoordinator(id, meta, storage, clistener);
PeerAcceptor peeracceptor = new PeerAcceptor(coordinator);
ConnectionAcceptor acceptor = new ConnectionAcceptor(serversocket,
peeracceptor);
trackerclient = new TrackerClient(meta, coordinator);
trackerclient.start();
}
static Snark parseArguments(String[] args)
{
return parseArguments(args, null, null);
}
/**
* Sets debug, ip and torrent variables then creates a Snark
* instance. Calls usage(), which terminates the program, if
* non-valid argument list. The given listeners will be
* passed to all components that take one.
*/
static Snark parseArguments(String[] args,
StorageListener slistener,
CoordinatorListener clistener)
{
int user_port = -1;
String ip = null;
String torrent = null;
int i = 0;
while (i < args.length)
{
if (args[i].equals("--debug"))
{
debug = INFO;
i++;
// Try if there is an level argument.
if (i < args.length)
{
try
{
int level = Integer.parseInt(args[i]);
if (level >= 0)
{
debug = level;
i++;
}
}
catch (NumberFormatException nfe) { }
}
}
else if (args[i].equals("--port"))
{
if (args.length - 1 < i + 1)
usage("--port needs port number to listen on");
try
{
user_port = Integer.parseInt(args[i + 1]);
}
catch (NumberFormatException nfe)
{
usage("--port argument must be a number (" + nfe + ")");
}
i += 2;
}
else if (args[i].equals("--no-commands"))
{
command_interpreter = false;
i++;
}
else if (args[i].equals("--eepproxy"))
{
String proxyHost = args[i+1];
String proxyPort = args[i+2];
I2PSnarkUtil.instance().setProxy(proxyHost, Integer.parseInt(proxyPort));
i += 3;
}
else if (args[i].equals("--i2cp"))
{
String i2cpHost = args[i+1];
String i2cpPort = args[i+2];
Properties opts = null;
if (i+3 < args.length) {
if (!args[i+3].startsWith("--")) {
opts = new Properties();
StringTokenizer tok = new StringTokenizer(args[i+3], " \t");
while (tok.hasMoreTokens()) {
String str = tok.nextToken();
int split = str.indexOf('=');
if (split > 0) {
opts.setProperty(str.substring(0, split), str.substring(split+1));
}
}
}
}
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
i += 3 + (opts != null ? 1 : 0);
}
else
{
torrent = args[i];
i++;
break;
}
}
if (torrent == null || i != args.length)
if (torrent != null && torrent.startsWith("-"))
usage("Unknow option '" + torrent + "'.");
else
usage("Need exactly one <url>, <file> or <dir>.");
return new Snark(torrent, ip, user_port, slistener, clistener);
}
private static void usage(String s)
{
System.out.println("snark: " + s);
usage();
}
private static void usage()
{
System.out.println
("Usage: snark [--debug [level]] [--no-commands] [--port <port>]");
System.out.println
(" [--eepproxy hostname portnum]");
System.out.println
(" [--i2cp routerHost routerPort ['name=val name=val name=val']]");
System.out.println
(" (<url>|<file>)");
System.out.println
(" --debug\tShows some extra info and stacktraces");
System.out.println
(" level\tHow much debug details to show");
System.out.println
(" \t(defaults to "
+ NOTICE + ", with --debug to "
+ INFO + ", highest level is "
+ ALL + ").");
System.out.println
(" --no-commands\tDon't read interactive commands or show usage info.");
System.out.println
(" --port\tThe port to listen on for incomming connections");
System.out.println
(" \t(if not given defaults to first free port between "
+ MIN_PORT + "-" + MAX_PORT + ").");
System.out.println
(" --share\tStart torrent tracker on <ip> address or <host> name.");
System.out.println
(" --eepproxy\thttp proxy to use (default of 127.0.0.1 port 4444)");
System.out.println
(" --i2cp\tlocation of your I2P router (default of 127.0.0.1 port 7654)");
System.out.println
(" \toptional settings may be included, such as");
System.out.println
(" \tinbound.length=2 outbound.length=2 inbound.lengthVariance=-1 ");
System.out.println
(" <url> \tURL pointing to .torrent metainfo file to download/share.");
System.out.println
(" <file> \tEither a local .torrent metainfo file to download");
System.out.println
(" \tor (with --share) a file to share.");
System.exit(-1);
}
/**
* Aborts program abnormally.
*/
public static void fatal(String s)
{
fatal(s, null);
}
/**
* Aborts program abnormally.
*/
public static void fatal(String s, Throwable t)
{
I2PSnarkUtil.instance().debug(s, ERROR, t);
//System.err.println("snark: " + s + ((t == null) ? "" : (": " + t)));
//if (debug >= INFO && t != null)
// t.printStackTrace();
throw new RuntimeException("die bart die");
}
/**
* Show debug info if debug is true.
*/
public static void debug(String s, int level)
{
I2PSnarkUtil.instance().debug(s, level, null);
//if (debug >= level)
// System.out.println(s);
}
public void peerChange(PeerCoordinator coordinator, Peer peer)
{
// System.out.println(peer.toString());
}
boolean allocating = false;
public void storageCreateFile(Storage storage, String name, long length)
{
if (allocating)
System.out.println(); // Done with last file.
System.out.print("Creating file '" + name
+ "' of length " + length + ": ");
allocating = true;
}
// How much storage space has been allocated
private long allocated = 0;
public void storageAllocated(Storage storage, long length)
{
allocating = true;
System.out.print(".");
allocated += length;
if (allocated == meta.getTotalLength())
System.out.println(); // We have all the disk space we need.
}
boolean allChecked = false;
boolean checking = false;
boolean prechecking = true;
public void storageChecked(Storage storage, int num, boolean checked)
{
allocating = false;
if (!allChecked && !checking)
{
// Use the MetaInfo from the storage since our own might not
// yet be setup correctly.
MetaInfo meta = storage.getMetaInfo();
if (meta != null)
System.out.print("Checking existing "
+ meta.getPieces()
+ " pieces: ");
checking = true;
}
if (checking)
if (checked)
System.out.print("+");
else
System.out.print("-");
else
Snark.debug("Got " + (checked ? "" : "BAD ") + "piece: " + num,
Snark.INFO);
}
public void storageAllChecked(Storage storage)
{
if (checking)
System.out.println();
allChecked = true;
checking = false;
}
public void shutdown()
{
// Should not be necessary since all non-deamon threads should
// have died. But in reality this does not always happen.
System.exit(0);
}
}

View File

@ -0,0 +1,89 @@
/* TrackerShutdown - Makes sure everything ends correctly when shutting down.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.IOException;
/**
* Makes sure everything ends correctly when shutting down.
*/
public class SnarkShutdown extends Thread
{
private final Storage storage;
private final PeerCoordinator coordinator;
private final ConnectionAcceptor acceptor;
private final TrackerClient trackerclient;
private final ShutdownListener listener;
public SnarkShutdown(Storage storage,
PeerCoordinator coordinator,
ConnectionAcceptor acceptor,
TrackerClient trackerclient,
ShutdownListener listener)
{
this.storage = storage;
this.coordinator = coordinator;
this.acceptor = acceptor;
this.trackerclient = trackerclient;
this.listener = listener;
}
public void run()
{
Snark.debug("Shutting down...", Snark.NOTICE);
Snark.debug("Halting ConnectionAcceptor...", Snark.INFO);
if (acceptor != null)
acceptor.halt();
Snark.debug("Halting TrackerClient...", Snark.INFO);
if (trackerclient != null)
trackerclient.halt();
Snark.debug("Halting PeerCoordinator...", Snark.INFO);
if (coordinator != null)
coordinator.halt();
Snark.debug("Closing Storage...", Snark.INFO);
if (storage != null)
{
try
{
storage.close();
}
catch(IOException ioe)
{
Snark.fatal("Couldn't properly close storage", ioe);
}
}
// XXX - Should actually wait till done...
try
{
Snark.debug("Waiting 5 seconds...", Snark.INFO);
Thread.sleep(5*1000);
}
catch (InterruptedException ie) { /* ignored */ }
listener.shutdown();
}
}

View File

@ -0,0 +1,47 @@
/* StaticSnark - Main snark startup class for staticly linking with gcj.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.security.Provider;
import java.security.Security;
import org.klomp.snark.bencode.*;
/**
* Main snark startup class for staticly linking with gcj.
* It references somee necessary classes that are normally loaded through
* reflection.
*
* @author Mark Wielaard (mark@klomp.org)
*/
public class StaticSnark
{
public static void main(String[] args)
{
// The GNU security provider is needed for SHA-1 MessageDigest checking.
// So make sure it is available as a security provider.
//Provider gnu = new gnu.java.security.provider.Gnu();
//Security.addProvider(gnu);
// And finally call the normal starting point.
Snark.main(args);
}
}

View File

@ -0,0 +1,525 @@
/* Storage - Class used to store and retrieve pieces.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.*;
import java.util.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Maintains pieces on disk. Can be used to store and retrieve pieces.
*/
public class Storage
{
private MetaInfo metainfo;
private long[] lengths;
private RandomAccessFile[] rafs;
private String[] names;
private final StorageListener listener;
private final BitField bitfield;
private int needed;
// XXX - Not always set correctly
int piece_size;
int pieces;
/** The default piece size. */
private static int MIN_PIECE_SIZE = 256*1024;
/** The maximum number of pieces in a torrent. */
private static long MAX_PIECES = 100*1024/20;
/**
* Creates a new storage based on the supplied MetaInfo. This will
* try to create and/or check all needed files in the MetaInfo.
*
* @exception IOException when creating and/or checking files fails.
*/
public Storage(MetaInfo metainfo, StorageListener listener)
throws IOException
{
this.metainfo = metainfo;
this.listener = listener;
needed = metainfo.getPieces();
bitfield = new BitField(needed);
}
/**
* Creates a storage from the existing file or directory together
* with an appropriate MetaInfo file as can be announced on the
* given announce String location.
*/
public Storage(File baseFile, String announce, StorageListener listener)
throws IOException
{
this.listener = listener;
// Create names, rafs and lengths arrays.
getFiles(baseFile);
long total = 0;
ArrayList lengthsList = new ArrayList();
for (int i = 0; i < lengths.length; i++)
{
long length = lengths[i];
total += length;
lengthsList.add(new Long(length));
}
piece_size = MIN_PIECE_SIZE;
pieces = (int) ((total - 1)/piece_size) + 1;
while (pieces > MAX_PIECES)
{
piece_size = piece_size*2;
pieces = (int) ((total - 1)/piece_size) +1;
}
// Note that piece_hashes and the bitfield will be filled after
// the MetaInfo is created.
byte[] piece_hashes = new byte[20*pieces];
bitfield = new BitField(pieces);
needed = 0;
List files = new ArrayList();
for (int i = 0; i < names.length; i++)
{
List file = new ArrayList();
StringTokenizer st = new StringTokenizer(names[i], File.separator);
while (st.hasMoreTokens())
{
String part = st.nextToken();
file.add(part);
}
files.add(file);
}
String name = baseFile.getName();
if (files.size() == 1)
{
files = null;
lengthsList = null;
}
// Note that the piece_hashes are not correctly setup yet.
metainfo = new MetaInfo(announce, baseFile.getName(), files,
lengthsList, piece_size, piece_hashes, total);
}
// Creates piece hases for a new storage.
public void create() throws IOException
{
// Calculate piece_hashes
MessageDigest digest = null;
try
{
digest = MessageDigest.getInstance("SHA");
}
catch(NoSuchAlgorithmException nsa)
{
throw new InternalError(nsa.toString());
}
byte[] piece_hashes = metainfo.getPieceHashes();
byte[] piece = new byte[piece_size];
for (int i = 0; i < pieces; i++)
{
int length = getUncheckedPiece(i, piece, 0);
digest.update(piece, 0, length);
byte[] hash = digest.digest();
for (int j = 0; j < 20; j++)
piece_hashes[20 * i + j] = hash[j];
bitfield.set(i);
if (listener != null)
listener.storageChecked(this, i, true);
}
if (listener != null)
listener.storageAllChecked(this);
// Reannounce to force recalculating the info_hash.
metainfo = metainfo.reannounce(metainfo.getAnnounce());
}
private void getFiles(File base) throws IOException
{
ArrayList files = new ArrayList();
addFiles(files, base);
int size = files.size();
names = new String[size];
lengths = new long[size];
rafs = new RandomAccessFile[size];
int i = 0;
Iterator it = files.iterator();
while (it.hasNext())
{
File f = (File)it.next();
names[i] = f.getPath();
lengths[i] = f.length();
rafs[i] = new RandomAccessFile(f, "r");
i++;
}
}
private static void addFiles(List l, File f)
{
if (!f.isDirectory())
l.add(f);
else
{
File[] files = f.listFiles();
if (files == null)
{
Snark.debug("WARNING: Skipping '" + f
+ "' not a normal file.", Snark.WARNING);
return;
}
for (int i = 0; i < files.length; i++)
addFiles(l, files[i]);
}
}
/**
* Returns the MetaInfo associated with this Storage.
*/
public MetaInfo getMetaInfo()
{
return metainfo;
}
/**
* How many pieces are still missing from this storage.
*/
public int needed()
{
return needed;
}
/**
* Whether or not this storage contains all pieces if the MetaInfo.
*/
public boolean complete()
{
return needed == 0;
}
/**
* The BitField that tells which pieces this storage contains.
* Do not change this since this is the current state of the storage.
*/
public BitField getBitField()
{
return bitfield;
}
/**
* Creates (and/or checks) all files from the metainfo file list.
*/
public void check() throws IOException
{
File base = new File(filterName(metainfo.getName()));
List files = metainfo.getFiles();
if (files == null)
{
// Create base as file.
Snark.debug("Creating/Checking file: " + base, Snark.NOTICE);
if (!base.createNewFile() && !base.exists())
throw new IOException("Could not create file " + base);
lengths = new long[1];
rafs = new RandomAccessFile[1];
names = new String[1];
lengths[0] = metainfo.getTotalLength();
rafs[0] = new RandomAccessFile(base, "rw");
names[0] = base.getName();
}
else
{
// Create base as dir.
Snark.debug("Creating/Checking directory: " + base, Snark.NOTICE);
if (!base.mkdir() && !base.isDirectory())
throw new IOException("Could not create directory " + base);
List ls = metainfo.getLengths();
int size = files.size();
long total = 0;
lengths = new long[size];
rafs = new RandomAccessFile[size];
names = new String[size];
for (int i = 0; i < size; i++)
{
File f = createFileFromNames(base, (List)files.get(i));
lengths[i] = ((Long)ls.get(i)).longValue();
total += lengths[i];
rafs[i] = new RandomAccessFile(f, "rw");
names[i] = f.getName();
}
// Sanity check for metainfo file.
long metalength = metainfo.getTotalLength();
if (total != metalength)
throw new IOException("File lengths do not add up "
+ total + " != " + metalength);
}
checkCreateFiles();
}
/**
* Removes 'suspicious' characters from the give file name.
*/
private String filterName(String name)
{
// XXX - Is this enough?
return name.replace(File.separatorChar, '_');
}
private File createFileFromNames(File base, List names) throws IOException
{
File f = null;
Iterator it = names.iterator();
while (it.hasNext())
{
String name = filterName((String)it.next());
if (it.hasNext())
{
// Another dir in the hierarchy.
f = new File(base, name);
if (!f.mkdir() && !f.isDirectory())
throw new IOException("Could not create directory " + f);
base = f;
}
else
{
// The final element (file) in the hierarchy.
f = new File(base, name);
if (!f.createNewFile() && !f.exists())
throw new IOException("Could not create file " + f);
}
}
return f;
}
private void checkCreateFiles() throws IOException
{
// Whether we are resuming or not,
// if any of the files already exists we assume we are resuming.
boolean resume = false;
// Make sure all files are available and of correct length
for (int i = 0; i < rafs.length; i++)
{
long length = rafs[i].length();
if(length == lengths[i])
{
if (listener != null)
listener.storageAllocated(this, length);
resume = true; // XXX Could dynamicly check
}
else if (length == 0)
allocateFile(i);
else
throw new IOException("File '" + names[i]
+ "' exists, but has wrong length");
}
// Check which pieces match and which don't
if (resume)
{
pieces = metainfo.getPieces();
byte[] piece = new byte[metainfo.getPieceLength(0)];
for (int i = 0; i < pieces; i++)
{
int length = getUncheckedPiece(i, piece, 0);
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
if (correctHash)
{
bitfield.set(i);
needed--;
}
if (listener != null)
listener.storageChecked(this, i, correctHash);
}
}
if (listener != null)
listener.storageAllChecked(this);
}
private void allocateFile(int nr) throws IOException
{
// XXX - Is this the best way to make sure we have enough space for
// the whole file?
listener.storageCreateFile(this, names[nr], lengths[nr]);
final int ZEROBLOCKSIZE = metainfo.getPieceLength(0);
byte[] zeros = new byte[ZEROBLOCKSIZE];
int i;
for (i = 0; i < lengths[nr]/ZEROBLOCKSIZE; i++)
{
rafs[nr].write(zeros);
if (listener != null)
listener.storageAllocated(this, ZEROBLOCKSIZE);
}
int size = (int)(lengths[nr] - i*ZEROBLOCKSIZE);
rafs[nr].write(zeros, 0, size);
if (listener != null)
listener.storageAllocated(this, size);
}
/**
* Closes the Storage and makes sure that all RandomAccessFiles are
* closed. The Storage is unusable after this.
*/
public void close() throws IOException
{
for (int i = 0; i < rafs.length; i++)
{
synchronized(rafs[i])
{
rafs[i].close();
}
}
}
/**
* Returns a byte array containing the requested piece or null if
* the storage doesn't contain the piece yet.
*/
public byte[] getPiece(int piece) throws IOException
{
if (!bitfield.get(piece))
return null;
byte[] bs = new byte[metainfo.getPieceLength(piece)];
getUncheckedPiece(piece, bs, 0);
return bs;
}
/**
* Put the piece in the Storage if it is correct.
*
* @return true if the piece was correct (sha metainfo hash
* matches), otherwise false.
* @exception IOException when some storage related error occurs.
*/
public boolean putPiece(int piece, byte[] bs) throws IOException
{
// First check if the piece is correct.
// If we were paranoia we could copy the array first.
int length = bs.length;
boolean correctHash = metainfo.checkPiece(piece, bs, 0, length);
if (listener != null)
listener.storageChecked(this, piece, correctHash);
if (!correctHash)
return false;
boolean complete;
synchronized(bitfield)
{
if (bitfield.get(piece))
return true; // No need to store twice.
else
{
bitfield.set(piece);
needed--;
complete = needed == 0;
}
}
long start = piece * metainfo.getPieceLength(0);
int i = 0;
long raflen = lengths[i];
while (start > raflen)
{
i++;
start -= raflen;
raflen = lengths[i];
}
int written = 0;
int off = 0;
while (written < length)
{
int need = length - written;
int len = (start + need < raflen) ? need : (int)(raflen - start);
synchronized(rafs[i])
{
rafs[i].seek(start);
rafs[i].write(bs, off + written, len);
}
written += len;
if (need - len > 0)
{
i++;
raflen = lengths[i];
start = 0;
}
}
return true;
}
private int getUncheckedPiece(int piece, byte[] bs, int off)
throws IOException
{
// XXX - copy/paste code from putPiece().
long start = piece * metainfo.getPieceLength(0);
int length = metainfo.getPieceLength(piece);
int i = 0;
long raflen = lengths[i];
while (start > raflen)
{
i++;
start -= raflen;
raflen = lengths[i];
}
int read = 0;
while (read < length)
{
int need = length - read;
int len = (start + need < raflen) ? need : (int)(raflen - start);
synchronized(rafs[i])
{
rafs[i].seek(start);
rafs[i].readFully(bs, off + read, len);
}
read += len;
if (need - len > 0)
{
i++;
raflen = lengths[i];
start = 0;
}
}
return length;
}
}

View File

@ -0,0 +1,52 @@
/* StorageListener.java - Interface used as callback when storage changes.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
/**
* Callback used when Storage changes.
*/
public interface StorageListener
{
/**
* Called when the storage creates a new file of a given length.
*/
void storageCreateFile(Storage storage, String name, long length);
/**
* Called to indicate that length bytes have been allocated.
*/
void storageAllocated(Storage storage, long length);
/**
* Called when storage is being checked and the num piece of that
* total pieces has been checked. When the piece hash matches the
* expected piece hash checked will be true, otherwise it will be
* false.
*/
void storageChecked(Storage storage, int num, boolean checked);
/**
* Called when all pieces in the storage have been checked. Does not
* mean that the storage is complete, just that the state of the
* storage is known.
*/
void storageAllChecked(Storage storage);
}

View File

@ -0,0 +1,254 @@
/* TrackerClient - Class that informs a tracker and gets new peers.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.*;
import java.net.*;
import java.util.*;
import org.klomp.snark.bencode.*;
/**
* Informs metainfo tracker of events and gets new peers for peer
* coordinator.
*
* @author Mark Wielaard (mark@klomp.org)
*/
public class TrackerClient extends Thread
{
private static final String NO_EVENT = "";
private static final String STARTED_EVENT = "started";
private static final String COMPLETED_EVENT = "completed";
private static final String STOPPED_EVENT = "stopped";
private final static int SLEEP = 5; // 5 minutes.
private final MetaInfo meta;
private final PeerCoordinator coordinator;
private final int port;
private boolean stop;
private long interval;
private long lastRequestTime;
public TrackerClient(MetaInfo meta, PeerCoordinator coordinator)
{
// Set unique name.
super("TrackerClient-" + urlencode(coordinator.getID()));
this.meta = meta;
this.coordinator = coordinator;
this.port = 6881; //(port == -1) ? 9 : port;
stop = false;
}
/**
* Interrupts this Thread to stop it.
*/
public void halt()
{
stop = true;
this.interrupt();
}
public void run()
{
// XXX - Support other IPs
String announce = I2PSnarkUtil.instance().rewriteAnnounce(meta.getAnnounce());
String infoHash = urlencode(meta.getInfoHash());
String peerID = urlencode(coordinator.getID());
long uploaded = coordinator.getUploaded();
long downloaded = coordinator.getDownloaded();
long left = coordinator.getLeft();
boolean completed = (left == 0);
try
{
boolean started = false;
while (!started)
{
try
{
// Send start.
TrackerInfo info = doRequest(announce, infoHash, peerID,
uploaded, downloaded, left,
STARTED_EVENT);
Iterator it = info.getPeers().iterator();
while (it.hasNext())
coordinator.addPeer((Peer)it.next());
started = true;
}
catch (IOException ioe)
{
// Probably not fatal (if it doesn't last to long...)
Snark.debug
("WARNING: Could not contact tracker at '"
+ announce + "': " + ioe, Snark.WARNING);
}
if (!started && !stop)
{
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
try
{
// Sleep one minutes...
Thread.sleep(60*1000);
}
catch(InterruptedException interrupt)
{
// ignore
}
}
}
while(!stop)
{
try
{
// Sleep some minutes...
Thread.sleep(SLEEP*60*1000);
}
catch(InterruptedException interrupt)
{
// ignore
}
if (stop)
break;
uploaded = coordinator.getUploaded();
downloaded = coordinator.getDownloaded();
left = coordinator.getLeft();
// First time we got a complete download?
String event;
if (!completed && left == 0)
{
completed = true;
event = COMPLETED_EVENT;
}
else
event = NO_EVENT;
// Only do a request when necessary.
if (event == COMPLETED_EVENT
|| coordinator.needPeers()
|| System.currentTimeMillis() > lastRequestTime + interval)
{
try
{
TrackerInfo info = doRequest(announce, infoHash, peerID,
uploaded, downloaded, left,
event);
Iterator it = info.getPeers().iterator();
while (it.hasNext())
coordinator.addPeer((Peer)it.next());
}
catch (IOException ioe)
{
// Probably not fatal (if it doesn't last to long...)
Snark.debug
("WARNING: Could not contact tracker at '"
+ announce + "': " + ioe, Snark.WARNING);
}
}
}
}
catch (Throwable t)
{
Snark.debug("TrackerClient: " + t, Snark.ERROR);
t.printStackTrace();
}
finally
{
try
{
TrackerInfo info = doRequest(announce, infoHash, peerID, uploaded,
downloaded, left, STOPPED_EVENT);
}
catch(IOException ioe) { /* ignored */ }
}
}
private TrackerInfo doRequest(String announce, String infoHash,
String peerID, long uploaded,
long downloaded, long left, String event)
throws IOException
{
String s = announce
+ "?info_hash=" + infoHash
+ "&peer_id=" + peerID
+ "&port=" + port
+ "&ip=" + I2PSnarkUtil.instance().getOurIPString()
+ "&uploaded=" + uploaded
+ "&downloaded=" + downloaded
+ "&left=" + left
+ ((event != NO_EVENT) ? ("&event=" + event) : "");
if (Snark.debug >= Snark.INFO)
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
File fetched = I2PSnarkUtil.instance().get(s);
if (fetched == null) {
throw new IOException("Error fetching " + s);
}
fetched.deleteOnExit();
InputStream in = new FileInputStream(fetched);
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
coordinator.getMetaInfo());
if (Snark.debug >= Snark.INFO)
Snark.debug("TrackerClient response: " + info, Snark.INFO);
lastRequestTime = System.currentTimeMillis();
String failure = info.getFailureReason();
if (failure != null)
throw new IOException(failure);
interval = info.getInterval() * 1000;
return info;
}
/**
* Very lazy byte[] to URL encoder. Just encodes everything, even
* "normal" chars.
*/
static String urlencode(byte[] bs)
{
StringBuffer sb = new StringBuffer(bs.length*3);
for (int i = 0; i < bs.length; i++)
{
int c = bs[i] & 0xFF;
sb.append('%');
if (c < 16)
sb.append('0');
sb.append(Integer.toHexString(c));
}
return sb.toString();
}
}

View File

@ -0,0 +1,128 @@
/* TrackerInfo - Holds information returned by a tracker, mainly the peer list.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import org.klomp.snark.bencode.*;
public class TrackerInfo
{
private final String failure_reason;
private final int interval;
private final Set peers;
public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo)
throws IOException
{
this(new BDecoder(in), my_id, metainfo);
}
public TrackerInfo(BDecoder be, byte[] my_id, MetaInfo metainfo)
throws IOException
{
this(be.bdecodeMap().getMap(), my_id, metainfo);
}
public TrackerInfo(Map m, byte[] my_id, MetaInfo metainfo)
throws IOException
{
BEValue reason = (BEValue)m.get("failure reason");
if (reason != null)
{
failure_reason = reason.getString();
interval = -1;
peers = null;
}
else
{
failure_reason = null;
BEValue beInterval = (BEValue)m.get("interval");
if (beInterval == null)
throw new InvalidBEncodingException("No interval given");
else
interval = beInterval.getInt();
BEValue bePeers = (BEValue)m.get("peers");
if (bePeers == null)
throw new InvalidBEncodingException("No peer list");
else
peers = getPeers(bePeers.getList(), my_id, metainfo);
}
}
public static Set getPeers(InputStream in, byte[] my_id, MetaInfo metainfo)
throws IOException
{
return getPeers(new BDecoder(in), my_id, metainfo);
}
public static Set getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo)
throws IOException
{
return getPeers(be.bdecodeList().getList(), my_id, metainfo);
}
public static Set getPeers(List l, byte[] my_id, MetaInfo metainfo)
throws IOException
{
Set peers = new HashSet(l.size());
Iterator it = l.iterator();
while (it.hasNext())
{
PeerID peerID = new PeerID(((BEValue)it.next()).getMap());
peers.add(new Peer(peerID, my_id, metainfo));
}
return peers;
}
public Set getPeers()
{
return peers;
}
public String getFailureReason()
{
return failure_reason;
}
public int getInterval()
{
return interval;
}
public String toString()
{
if (failure_reason != null)
return "TrackerInfo[FAILED: " + failure_reason + "]";
else
return "TrackerInfo[interval=" + interval
+ ", peers=" + peers + "]";
}
}

View File

@ -0,0 +1,355 @@
/* BDecoder - Converts an InputStream to BEValues.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark.bencode;
import java.io.IOException;
import java.io.InputStream;
import java.io.EOFException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Decodes a bencoded stream to <code>BEValue</code>s.
*
* A bencoded byte stream can represent byte arrays, numbers, lists and
* maps (dictionaries).
*
* It currently contains a hack to indicate a name of a dictionary of
* which a SHA-1 digest hash should be calculated (the hash over the
* original bencoded bytes).
*
* @author Mark Wielaard (mark@klomp.org).
*/
public class BDecoder
{
// The InputStream to BDecode.
private final InputStream in;
// The last indicator read.
// Zero if unknown.
// '0'..'9' indicates a byte[].
// 'i' indicates an Number.
// 'l' indicates a List.
// 'd' indicates a Map.
// 'e' indicates end of Number, List or Map (only used internally).
// -1 indicates end of stream.
// Call getNextIndicator to get the current value (will never return zero).
private int indicator = 0;
// Used for ugly hack to get SHA hash over the metainfo info map
private String special_map = "info";
private boolean in_special_map = false;
private final MessageDigest sha_digest;
// Ugly hack. Return the SHA has over bytes that make up the special map.
public byte[] get_special_map_digest()
{
byte[] result = sha_digest.digest();
return result;
}
// Ugly hack. Name defaults to "info".
public void set_special_map_name(String name)
{
special_map = name;
}
/**
* Initalizes a new BDecoder. Nothing is read from the given
* <code>InputStream</code> yet.
*/
public BDecoder(InputStream in)
{
this.in = in;
// XXX - Used for ugly hack.
try
{
sha_digest = MessageDigest.getInstance("SHA");
}
catch(NoSuchAlgorithmException nsa)
{
throw new InternalError(nsa.toString());
}
}
/**
* Creates a new BDecoder and immediatly decodes the first value it
* sees.
*
* @return The first BEValue on the stream or null when the stream
* has ended.
*
* @exception InvalidBEncoding when the stream doesn't start with a
* bencoded value or the stream isn't a bencoded stream at all.
* @exception IOException when somthing bad happens with the stream
* to read from.
*/
public static BEValue bdecode(InputStream in) throws IOException
{
return new BDecoder(in).bdecode();
}
/**
* Returns what the next bencoded object will be on the stream or -1
* when the end of stream has been reached. Can return something
* unexpected (not '0' .. '9', 'i', 'l' or 'd') when the stream
* isn't bencoded.
*
* This might or might not read one extra byte from the stream.
*/
public int getNextIndicator() throws IOException
{
if (indicator == 0)
{
indicator = in.read();
// XXX - Used for ugly hack
if (in_special_map) sha_digest.update((byte)indicator);
}
return indicator;
}
/**
* Gets the next indicator and returns either null when the stream
* has ended or bdecodes the rest of the stream and returns the
* appropriate BEValue encoded object.
*/
public BEValue bdecode() throws IOException
{
indicator = getNextIndicator();
if (indicator == -1)
return null;
if (indicator >= '0' && indicator <= '9')
return bdecodeBytes();
else if (indicator == 'i')
return bdecodeNumber();
else if (indicator == 'l')
return bdecodeList();
else if (indicator == 'd')
return bdecodeMap();
else
throw new InvalidBEncodingException
("Unknown indicator '" + indicator + "'");
}
/**
* Returns the next bencoded value on the stream and makes sure it
* is a byte array. If it is not a bencoded byte array it will throw
* InvalidBEncodingException.
*/
public BEValue bdecodeBytes() throws IOException
{
int c = getNextIndicator();
int num = c - '0';
if (num < 0 || num > 9)
throw new InvalidBEncodingException("Number expected, not '"
+ (char)c + "'");
indicator = 0;
c = read();
int i = c - '0';
while (i >= 0 && i <= 9)
{
// XXX - This can overflow!
num = num*10 + i;
c = read();
i = c - '0';
}
if (c != ':')
throw new InvalidBEncodingException("Colon expected, not '"
+ (char)c + "'");
return new BEValue(read(num));
}
/**
* Returns the next bencoded value on the stream and makes sure it
* is a number. If it is not a number it will throw
* InvalidBEncodingException.
*/
public BEValue bdecodeNumber() throws IOException
{
int c = getNextIndicator();
if (c != 'i')
throw new InvalidBEncodingException("Expected 'i', not '"
+ (char)c + "'");
indicator = 0;
c = read();
if (c == '0')
{
c = read();
if (c == 'e')
return new BEValue(BigInteger.ZERO);
else
throw new InvalidBEncodingException("'e' expected after zero,"
+ " not '" + (char)c + "'");
}
// XXX - We don't support more the 255 char big integers
char[] chars = new char[256];
int off = 0;
if (c == '-')
{
c = read();
if (c == '0')
throw new InvalidBEncodingException("Negative zero not allowed");
chars[off] = (char)c;
off++;
}
if (c < '1' || c > '9')
throw new InvalidBEncodingException("Invalid Integer start '"
+ (char)c + "'");
chars[off] = (char)c;
off++;
c = read();
int i = c - '0';
while(i >= 0 && i <= 9)
{
chars[off] = (char)c;
off++;
c = read();
i = c - '0';
}
if (c != 'e')
throw new InvalidBEncodingException("Integer should end with 'e'");
String s = new String(chars, 0, off);
return new BEValue(new BigInteger(s));
}
/**
* Returns the next bencoded value on the stream and makes sure it
* is a list. If it is not a list it will throw
* InvalidBEncodingException.
*/
public BEValue bdecodeList() throws IOException
{
int c = getNextIndicator();
if (c != 'l')
throw new InvalidBEncodingException("Expected 'l', not '"
+ (char)c + "'");
indicator = 0;
List result = new ArrayList();
c = getNextIndicator();
while (c != 'e')
{
result.add(bdecode());
c = getNextIndicator();
}
indicator = 0;
return new BEValue(result);
}
/**
* Returns the next bencoded value on the stream and makes sure it
* is a map (dictonary). If it is not a map it will throw
* InvalidBEncodingException.
*/
public BEValue bdecodeMap() throws IOException
{
int c = getNextIndicator();
if (c != 'd')
throw new InvalidBEncodingException("Expected 'd', not '"
+ (char)c + "'");
indicator = 0;
Map result = new HashMap();
c = getNextIndicator();
while (c != 'e')
{
// Dictonary keys are always strings.
String key = bdecode().getString();
// XXX ugly hack
boolean special = special_map.equals(key);
if (special)
in_special_map = true;
BEValue value = bdecode();
result.put(key, value);
// XXX ugly hack continued
if (special)
in_special_map = false;
c = getNextIndicator();
}
indicator = 0;
return new BEValue(result);
}
/**
* Returns the next byte read from the InputStream (as int).
* Throws EOFException if InputStream.read() returned -1.
*/
private int read() throws IOException
{
int c = in.read();
if (c == -1)
throw new EOFException();
if (in_special_map) sha_digest.update((byte)c);
return c;
}
/**
* Returns a byte[] containing length valid bytes starting at offset
* zero. Throws EOFException if InputStream.read() returned -1
* before all requested bytes could be read. Note that the byte[]
* returned might be bigger then requested but will only contain
* length valid bytes. The returned byte[] will be reused when this
* method is called again.
*/
private byte[] read(int length) throws IOException
{
byte[] result = new byte[length];
int read = 0;
while (read < length)
{
int i = in.read(result, read, length - read);
if (i == -1)
throw new EOFException();
read += i;
}
if (in_special_map) sha_digest.update(result, 0, length);
return result;
}
}

View File

@ -0,0 +1,190 @@
/* BEValue - Holds different types that a bencoded byte array can represent.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark.bencode;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;
/**
* Holds different types that a bencoded byte array can represent.
* You need to call the correct get method to get the correct java
* type object. If the BEValue wasn't actually of the requested type
* you will get a InvalidBEncodingException.
*
* @author Mark Wielaard (mark@klomp.org)
*/
public class BEValue
{
// This is either a byte[], Number, List or Map.
private final Object value;
public BEValue(byte[] value)
{
this.value = value;
}
public BEValue(Number value)
{
this.value = value;
}
public BEValue(List value)
{
this.value = value;
}
public BEValue(Map value)
{
this.value = value;
}
/**
* Returns this BEValue as a String. This operation only succeeds
* when the BEValue is a byte[], otherwise it will throw a
* InvalidBEncodingException. The byte[] will be interpreted as
* UTF-8 encoded characters.
*/
public String getString() throws InvalidBEncodingException
{
try
{
return new String(getBytes(), "UTF-8");
}
catch (ClassCastException cce)
{
throw new InvalidBEncodingException(cce.toString());
}
catch (UnsupportedEncodingException uee)
{
throw new InternalError(uee.toString());
}
}
/**
* Returns this BEValue as a byte[]. This operation only succeeds
* when the BEValue is actually a byte[], otherwise it will throw a
* InvalidBEncodingException.
*/
public byte[] getBytes() throws InvalidBEncodingException
{
try
{
return (byte[])value;
}
catch (ClassCastException cce)
{
throw new InvalidBEncodingException(cce.toString());
}
}
/**
* Returns this BEValue as a Number. This operation only succeeds
* when the BEValue is actually a Number, otherwise it will throw a
* InvalidBEncodingException.
*/
public Number getNumber() throws InvalidBEncodingException
{
try
{
return (Number)value;
}
catch (ClassCastException cce)
{
throw new InvalidBEncodingException(cce.toString());
}
}
/**
* Returns this BEValue as int. This operation only succeeds when
* the BEValue is actually a Number, otherwise it will throw a
* InvalidBEncodingException. The returned int is the result of
* <code>Number.intValue()</code>.
*/
public int getInt() throws InvalidBEncodingException
{
return getNumber().intValue();
}
/**
* Returns this BEValue as long. This operation only succeeds when
* the BEValue is actually a Number, otherwise it will throw a
* InvalidBEncodingException. The returned long is the result of
* <code>Number.longValue()</code>.
*/
public long getLong() throws InvalidBEncodingException
{
return getNumber().longValue();
}
/**
* Returns this BEValue as a List of BEValues. This operation only
* succeeds when the BEValue is actually a List, otherwise it will
* throw a InvalidBEncodingException.
*/
public List getList() throws InvalidBEncodingException
{
try
{
return (List)value;
}
catch (ClassCastException cce)
{
throw new InvalidBEncodingException(cce.toString());
}
}
/**
* Returns this BEValue as a Map of BEValue keys and BEValue
* values. This operation only succeeds when the BEValue is actually
* a Map, otherwise it will throw a InvalidBEncodingException.
*/
public Map getMap() throws InvalidBEncodingException
{
try
{
return (Map)value;
}
catch (ClassCastException cce)
{
throw new InvalidBEncodingException(cce.toString());
}
}
public String toString()
{
String valueString;
if (value instanceof byte[])
{
byte[] bs = (byte[])value;
// XXX - Stupid heuristic...
if (bs.length <= 12)
valueString = new String(bs);
else
valueString = "bytes:" + bs.length;
}
else
valueString = value.toString();
return "BEValue[" + valueString + "]";
}
}

View File

@ -0,0 +1,191 @@
/* BDecoder - Converts an InputStream to BEValues.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark.bencode;
import java.io.IOException;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class BEncoder
{
public static byte[] bencode(Object o) throws IllegalArgumentException
{
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bencode(o, baos);
return baos.toByteArray();
}
catch (IOException ioe)
{
throw new InternalError(ioe.toString());
}
}
public static void bencode(Object o, OutputStream out)
throws IOException, IllegalArgumentException
{
if (o instanceof String)
bencode((String)o, out);
else if (o instanceof byte[])
bencode((byte[])o, out);
else if (o instanceof Number)
bencode((Number)o, out);
else if (o instanceof List)
bencode((List)o, out);
else if (o instanceof Map)
bencode((Map)o, out);
else
throw new IllegalArgumentException("Cannot bencode: " + o.getClass());
}
public static byte[] bencode(String s)
{
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bencode(s, baos);
return baos.toByteArray();
}
catch (IOException ioe)
{
throw new InternalError(ioe.toString());
}
}
public static void bencode(String s, OutputStream out) throws IOException
{
byte[] bs = s.getBytes("UTF-8");
bencode(bs, out);
}
public static byte[] bencode(Number n)
{
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bencode(n, baos);
return baos.toByteArray();
}
catch (IOException ioe)
{
throw new InternalError(ioe.toString());
}
}
public static void bencode(Number n, OutputStream out) throws IOException
{
out.write('i');
String s = n.toString();
out.write(s.getBytes("UTF-8"));
out.write('e');
}
public static byte[] bencode(List l)
{
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bencode(l, baos);
return baos.toByteArray();
}
catch (IOException ioe)
{
throw new InternalError(ioe.toString());
}
}
public static void bencode(List l, OutputStream out) throws IOException
{
out.write('l');
Iterator it = l.iterator();
while (it.hasNext())
bencode(it.next(), out);
out.write('e');
}
public static byte[] bencode(byte[] bs)
{
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bencode(bs, baos);
return baos.toByteArray();
}
catch (IOException ioe)
{
throw new InternalError(ioe.toString());
}
}
public static void bencode(byte[] bs, OutputStream out) throws IOException
{
String l = Integer.toString(bs.length);
out.write(l.getBytes("UTF-8"));
out.write(':');
out.write(bs);
}
public static byte[] bencode(Map m)
{
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bencode(m, baos);
return baos.toByteArray();
}
catch (IOException ioe)
{
throw new InternalError(ioe.toString());
}
}
public static void bencode(Map m, OutputStream out) throws IOException
{
out.write('d');
// Keys must be sorted. XXX - But is this the correct order?
Set s = m.keySet();
List l = new ArrayList(s);
Collections.sort(l);
Iterator it = l.iterator();
while(it.hasNext())
{
// Keys must be Strings.
String key = (String)it.next();
Object value = m.get(key);
bencode(key, out);
bencode(value, out);
}
out.write('e');
}
}

View File

@ -0,0 +1,36 @@
/* InvalidBEncodingException - Thrown when a bencoded stream is corrupted.
Copyright (C) 2003 Mark J. Wielaard
This file is part of Snark.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark.bencode;
import java.io.IOException;
/**
* Exception thrown when a bencoded stream is corrupted.
*
* @author Mark Wielaard (mark@klomp.org)
*/
public class InvalidBEncodingException extends IOException
{
public InvalidBEncodingException(String message)
{
super(message);
}
}

11
apps/i2psnark/readme.txt Normal file
View File

@ -0,0 +1,11 @@
This is an I2P port of snark [http://klomp.org/snark], a GPL'ed bittorrent client
The build in tracker has been removed for simplicity.
Example usage:
java -jar lib/i2psnark.jar myFile.torrent
or, a more verbose setting:
java -jar lib/i2psnark.jar --eepproxy 127.0.0.1 4444 \
--i2cp 127.0.0.1 7654 "inbound.length=2 outbound.length=2" \
--debug 6 myFile.torrent

View File

@ -0,0 +1,141 @@
The Hunting of the Snark Project - BitTorrent Application Suite
0.5 - The Beaver's Lesson (27 June 2003)
"It's a Snark!" was the sound that first came to their ears,
And seemed almost too good to be true.
Then followed a torrent of laughter and cheers:
Then the ominous words "It's a Boo-"
-- from The Hunting Of The Snark by Lewis Carroll
Snark is a client for downloading and sharing files distributed with
the BitTorrent protocol. It is mainly used for exploring the BitTorrent
protocol and experimenting with the the GNU Compiler for Java (gcj).
But it can also be used as a regular BitTorrent Client.
Snark can also act as a torrent creator, micro http server for delivering
metainfo.torrent files and has an integrated Tracker for making sharing of
files as easy as possible.
When you give the option --share Snark will automatically
create a .torrent file, start a very simple webserver to distribute
the metainfo.torrent file and a local tracker that other BitTorrent
clients can connect to.
Distribution
------------
Copyright (C) 2003 Mark J. Wielaard
Snark is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Requirements/Installation
-------------------------
The GNU Compiler for java (gcj) version 3.3 or later.
(Earlier versions have a faulty SHA message digest implementation.)
On Debian GNU/Linux based distributions just install the gcj-3.3 package.
Edit the GCJ variable in the Makefile if your gcj binary is not gcj-3.3.
Typing 'make' will create the native snark binary and a snark.jar file
for use with traditional java byte code interpreters.
It is possible to compile the sources with other java compilers
like jikes or kjc to produce the snark.jar file. Edit the JAVAC and
JAVAC_FLAGS variables on top of the Makefile for this. And type
'make snark.jar' to create a jar file that can be used by traditional
java bytecode interpreters like kaffe: 'kaffe -jar snark.jar'.
You will need at least version 1.1 of kaffe for all functionality to work
correctly ('--share' does not work with older versions).
When trying out the experimental Gnome frontend you also need the java-gnome
bindings. On Debian GNU/Linux systems install the package libgnome0-java.
You can try it out by typing 'make snark-gnome' and then run 'snark-gnome.sh'
like you would with the normal command line client.
Running
-------
To use the program start it with:
snark [--debug [level]] [--no-commands] [--port <port>]
[--share (<ip>|<host>)] (<url>|<file>|<dir>)
--debug Shows some extra info and stacktraces.
level How much debug details to show
(defaults to 3, with --debug to 4, highest level is 6).
--no-commands Don't read interactive commands or show usage info.
--port The port to listen on for incomming connections
(if not given defaults to first free port between 6881-6889).
--share Start torrent tracker on <ip> address or <host> name.
<url> URL pointing to .torrent metainfo file to download/share.
<file> Either a local .torrent metainfo file to download
or (with --share) a file to share.
<dir> A directory with files to share (needs --share).
Since this is an early beta release there are probably still some bugs
in the program. To help find them run the program with the --debug
option which shows more information on what it going on. You can also give
the level of debug output you want. Zero will give (almost) no output at all.
Everything above debug level 4 is probably to much (only really useful to
see what goes on on the protocol/network level).
Examples
- To simple start downloading/sharing a file.
Either download the .torrent file to disk and start snark with:
./snark somefile.torrent
Or give it the complete URL:
./snark http://somehost.example.com/cd-images/bbc-lnx.iso.torrent
- To start seeding/sharing a local file:
./snark --share my-host.example.com some-file
Snark will respond with:
Listening on port: 6881
Trying to create metainfo torrent for 'some-file'
Creating torrent piece hashes: ++++++++++
Torrent available on http://my-host.example.com:6881/metainfo.torrent
You can now point other people to the above URL so they can share
the file with their own BitTorrent client.
Commands
While the program is running in text mode you can currently give the
following commands: 'info', 'list' and 'quit'.
Interactive commands are disabled when the '--no-commands' flag is given.
This is sometimes desireable for running snark in the background.
More information
----------------
- The Evolution of Cooperation - Robert Axelrod
ISBN 0-465-02121-2
- The BitTorrent protocol description:
<http://bitconjurer.org/BitTorrent/protocol.html>
- The GNU Compiler for Java (gcj):
<http://gcc.gnu.org/java/>
- java-gnome bindings : <http://java-gnome.sourceforge.net/>
- The Hunting of the Snark - Lewis Carroll
Comments welcome
- Mark Wielaard <mark@klomp.org>

View File

@ -31,6 +31,9 @@
</war>
</target>
<target name="precompilejsp">
<delete dir="../jsp/WEB-INF/" />
<delete file="../jsp/web-fragment.xml" />
<delete file="../jsp/web-out.xml" />
<mkdir dir="../jsp/WEB-INF/" />
<mkdir dir="../jsp/WEB-INF/classes" />
<!-- there are various jspc ant tasks, but they all seem a bit flakey -->
@ -39,12 +42,13 @@
<pathelement location="../../jetty/jettylib/jasper-compiler.jar" />
<pathelement location="../../jetty/jettylib/jasper-runtime.jar" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
<pathelement location="../../jetty/jettylib/commons-el.jar" />
<pathelement location="../../jetty/jettylib/ant.jar" />
<pathelement location="build/i2ptunnel.jar" />
</classpath>
<arg value="-d" />
<arg value="../jsp/WEB-INF/classes" />
<arg value="-v9" />
<arg value="-p" />
<arg value="net.i2p.i2ptunnel.jsp" />
<arg value="-webinc" />
@ -52,10 +56,13 @@
<arg value="-webapp" />
<arg value="../jsp/" />
</java>
<javac destdir="../jsp/WEB-INF/classes/" srcdir="../jsp/WEB-INF/classes" includes="*.java">
<javac debug="true" deprecation="on" source="1.3" target="1.3"
destdir="../jsp/WEB-INF/classes/" srcdir="../jsp/WEB-INF/classes" includes="**/*.java">
<classpath>
<pathelement location="../../jetty/jettylib/jasper-runtime.jar" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
<pathelement location="../../jetty/jettylib/commons-el.jar" />
<pathelement location="build/i2ptunnel.jar" />
</classpath>
</javac>

View File

@ -0,0 +1,385 @@
package net.i2p.i2ptunnel;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2005 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.GZIPInputStream;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.util.ByteCache;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Simple stream for delivering an HTTP response to
* the client, trivially filtered to make sure "Connection: close"
* is always in the response. Perhaps add transparent handling of the
* Content-encoding: x-i2p-gzip, adjusting the headers to say Content-encoding: identity?
* Content-encoding: gzip is trivial as well, but Transfer-encoding: chunked makes it
* more work than is worthwhile at the moment.
*
*/
class HTTPResponseOutputStream extends FilterOutputStream {
private I2PAppContext _context;
private Log _log;
private ByteCache _cache;
protected ByteArray _headerBuffer;
private boolean _headerWritten;
private byte _buf1[];
protected boolean _gzip;
private long _dataWritten;
private InternalGZIPInputStream _in;
private static final int CACHE_SIZE = 8*1024;
public HTTPResponseOutputStream(OutputStream raw) {
super(raw);
_context = I2PAppContext.getGlobalContext();
_context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
_context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
_context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "i2ptunnel", new long[] { 60*1000, 30*60*1000 });
_log = _context.logManager().getLog(getClass());
_cache = ByteCache.getInstance(8, CACHE_SIZE);
_headerBuffer = _cache.acquire();
_headerWritten = false;
_gzip = false;
_dataWritten = 0;
_buf1 = new byte[1];
}
public void write(int c) throws IOException {
_buf1[0] = (byte)c;
write(_buf1, 0, 1);
}
public void write(byte buf[]) throws IOException {
write(buf, 0, buf.length);
}
public void write(byte buf[], int off, int len) throws IOException {
if (_headerWritten) {
out.write(buf, off, len);
_dataWritten += len;
//out.flush();
return;
}
for (int i = 0; i < len; i++) {
ensureCapacity();
_headerBuffer.getData()[_headerBuffer.getValid()] = buf[off+i];
_headerBuffer.setValid(_headerBuffer.getValid()+1);
if (headerReceived()) {
writeHeader();
_headerWritten = true;
if (i + 1 < len) {
// write out the remaining
out.write(buf, off+i+1, len-i-1);
_dataWritten += len-i-1;
//out.flush();
}
return;
}
}
}
/** grow (and free) the buffer as necessary */
private void ensureCapacity() {
if (_headerBuffer.getValid() + 1 >= _headerBuffer.getData().length) {
int newSize = (int)(_headerBuffer.getData().length * 1.5);
ByteArray newBuf = new ByteArray(new byte[newSize]);
System.arraycopy(_headerBuffer.getData(), 0, newBuf.getData(), 0, _headerBuffer.getValid());
newBuf.setValid(_headerBuffer.getValid());
newBuf.setOffset(0);
if (_headerBuffer.getData().length == CACHE_SIZE)
_cache.release(_headerBuffer);
_headerBuffer = newBuf;
}
}
/** are the headers finished? */
private boolean headerReceived() {
if (_headerBuffer.getValid() < 3) return false;
byte first = _headerBuffer.getData()[_headerBuffer.getValid()-3];
byte second = _headerBuffer.getData()[_headerBuffer.getValid()-2];
byte third = _headerBuffer.getData()[_headerBuffer.getValid()-1];
return (isNL(second) && isNL(third)) || // \n\n
(isNL(first) && isNL(third)); // \n\r\n
}
/**
* Tweak that first HTTP response line (HTTP 200 OK, etc)
*
*/
protected String filterResponseLine(String line) {
return line;
}
/** we ignore any potential \r, since we trim it on write anyway */
private static final byte NL = '\n';
private boolean isNL(byte b) { return (b == NL); }
/** ok, received, now munge & write it */
private void writeHeader() throws IOException {
String responseLine = null;
boolean connectionSent = false;
boolean proxyConnectionSent = false;
int lastEnd = -1;
for (int i = 0; i < _headerBuffer.getValid(); i++) {
if (isNL(_headerBuffer.getData()[i])) {
if (lastEnd == -1) {
responseLine = new String(_headerBuffer.getData(), 0, i+1); // includes NL
responseLine = filterResponseLine(responseLine);
responseLine = (responseLine.trim() + "\n");
out.write(responseLine.getBytes());
} else {
for (int j = lastEnd+1; j < i; j++) {
if (_headerBuffer.getData()[j] == ':') {
int keyLen = j-(lastEnd+1);
int valLen = i-(j+2);
if ( (keyLen <= 0) || (valLen <= 0) )
throw new IOException("Invalid header @ " + j);
String key = new String(_headerBuffer.getData(), lastEnd+1, keyLen);
String val = new String(_headerBuffer.getData(), j+2, valLen).trim();
if (_log.shouldLog(Log.INFO))
_log.info("Response header [" + key + "] = [" + val + "]");
if ("Connection".equalsIgnoreCase(key)) {
out.write("Connection: close\n".getBytes());
connectionSent = true;
} else if ("Proxy-Connection".equalsIgnoreCase(key)) {
out.write("Proxy-Connection: close\n".getBytes());
proxyConnectionSent = true;
} else if ( ("Content-encoding".equalsIgnoreCase(key)) && ("x-i2p-gzip".equalsIgnoreCase(val)) ) {
_gzip = true;
} else {
out.write((key.trim() + ": " + val.trim() + "\n").getBytes());
}
break;
}
}
}
lastEnd = i;
}
}
if (!connectionSent)
out.write("Connection: close\n".getBytes());
if (!proxyConnectionSent)
out.write("Proxy-Connection: close\n".getBytes());
finishHeaders();
boolean shouldCompress = shouldCompress();
if (_log.shouldLog(Log.INFO))
_log.info("After headers: gzip? " + _gzip + " compress? " + shouldCompress);
// done, shove off
if (_headerBuffer.getData().length == CACHE_SIZE)
_cache.release(_headerBuffer);
else
_headerBuffer = null;
if (shouldCompress) {
beginProcessing();
}
}
protected boolean shouldCompress() { return _gzip; }
protected void finishHeaders() throws IOException {
out.write("\n".getBytes()); // end of the headers
}
public void close() throws IOException {
out.close();
}
protected void beginProcessing() throws IOException {
//out.flush();
PipedInputStream pi = new PipedInputStream();
PipedOutputStream po = new PipedOutputStream(pi);
new I2PThread(new Pusher(pi, out), "HTTP decompresser").start();
out = po;
}
private class Pusher implements Runnable {
private InputStream _inRaw;
private OutputStream _out;
public Pusher(InputStream in, OutputStream out) {
_inRaw = in;
_out = out;
}
public void run() {
OutputStream to = null;
_in = null;
long start = System.currentTimeMillis();
long written = 0;
try {
_in = new InternalGZIPInputStream(_inRaw);
byte buf[] = new byte[8192];
int read = -1;
while ( (read = _in.read(buf)) != -1) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read " + read + " and writing it to the browser/streams");
_out.write(buf, 0, read);
_out.flush();
written += read;
}
if (_log.shouldLog(Log.INFO))
_log.info("Decompressed: " + written + ", " + _in.getTotalRead() + "/" + _in.getTotalExpanded());
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error decompressing: " + written + ", " + (_in != null ? _in.getTotalRead() + "/" + _in.getTotalExpanded() : ""), ioe);
} finally {
if (_log.shouldLog(Log.WARN) && (_in != null))
_log.warn("After decompression, written=" + written +
(_in != null ?
" read=" + _in.getTotalRead()
+ ", expanded=" + _in.getTotalExpanded() + ", remaining=" + _in.getRemaining()
+ ", finished=" + _in.getFinished()
: ""));
if (_out != null) try {
_out.close();
} catch (IOException ioe) {}
}
long end = System.currentTimeMillis();
double compressed = (_in != null ? _in.getTotalRead() : 0);
double expanded = (_in != null ? _in.getTotalExpanded() : 0);
double ratio = 0;
if (expanded > 0)
ratio = compressed/expanded;
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), end-start);
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, end-start);
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, end-start);
}
}
private class InternalGZIPInputStream extends GZIPInputStream {
public InternalGZIPInputStream(InputStream in) throws IOException {
super(in);
}
public long getTotalRead() {
try {
return super.inf.getTotalIn();
} catch (Exception e) {
return 0;
}
}
public long getTotalExpanded() {
try {
return super.inf.getTotalOut();
} catch (Exception e) {
return 0;
}
}
public long getRemaining() {
try {
return super.inf.getRemaining();
} catch (Exception e) {
return 0;
}
}
public boolean getFinished() {
try {
return super.inf.finished();
} catch (Exception e) {
return true;
}
}
public String toString() {
return "Read: " + getTotalRead() + " expanded: " + getTotalExpanded() + " remaining: " + getRemaining() + " finished: " + getFinished();
}
}
public String toString() {
return super.toString() + ": " + _in;
}
public static void main(String args[]) {
String simple = "HTTP/1.1 200 OK\n" +
"foo: bar\n" +
"baz: bat\n" +
"\n" +
"hi ho, this is the body";
String filtered = "HTTP/1.1 200 OK\n" +
"Connection: keep-alive\n" +
"foo: bar\n" +
"baz: bat\n" +
"\n" +
"hi ho, this is the body";
String winfilter= "HTTP/1.1 200 OK\r\n" +
"Connection: keep-alive\r\n" +
"foo: bar\r\n" +
"baz: bat\r\n" +
"\r\n" +
"hi ho, this is the body";
String minimal = "HTTP/1.1 200 OK\n" +
"\n" +
"hi ho, this is the body";
String winmin = "HTTP/1.1 200 OK\r\n" +
"\r\n" +
"hi ho, this is the body";
String invalid1 = "HTTP/1.1 200 OK\n";
String invalid2 = "HTTP/1.1 200 OK";
String invalid3 = "HTTP 200 OK\r\n";
String invalid4 = "HTTP 200 OK\r";
String invalid5 = "HTTP/1.1 200 OK\r\n" +
"I am broken, and I smell\r\n" +
"\r\n";
String invalid6 = "HTTP/1.1 200 OK\r\n" +
":I am broken, and I smell\r\n" +
"\r\n";
String invalid7 = "HTTP/1.1 200 OK\n" +
"I am broken, and I smell:\n" +
":asdf\n" +
":\n" +
"\n";
String large = "HTTP/1.1 200 OK\n" +
"Last-modified: Tue, 25 Nov 2003 12:05:38 GMT\n" +
"Expires: Tue, 25 Nov 2003 12:05:38 GMT\n" +
"Content-length: 32\n" +
"\n" +
"hi ho, this is the body";
/* */
test("Simple", simple, true);
test("Filtered", filtered, true);
test("Filtered windows", winfilter, true);
test("Minimal", minimal, true);
test("Windows", winmin, true);
test("Large", large, true);
test("Invalid (short headers)", invalid1, true);
test("Invalid (no headers)", invalid2, true);
test("Invalid (windows with short headers)", invalid3, true);
test("Invalid (windows no headers)", invalid4, true);
test("Invalid (bad headers)", invalid5, true);
test("Invalid (bad headers2)", invalid6, false);
test("Invalid (bad headers3)", invalid7, false);
/* */
}
private static void test(String name, String orig, boolean shouldPass) {
System.out.println("====Testing: " + name + "\n" + orig + "\n------------");
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
HTTPResponseOutputStream resp = new HTTPResponseOutputStream(baos);
resp.write(orig.getBytes());
resp.flush();
String received = new String(baos.toByteArray());
System.out.println(received);
} catch (Exception e) {
if (shouldPass)
e.printStackTrace();
else
System.out.println("Properly fails with " + e.getMessage());
}
}
}

View File

@ -27,6 +27,7 @@
* not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
@ -108,8 +109,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
_tunnelId = ++__tunnelId;
_log = _context.logManager().getLog(I2PTunnel.class);
_event = new EventDispatcherImpl();
_clientOptions = new Properties();
_clientOptions.putAll(System.getProperties());
Properties p = new Properties();
p.putAll(System.getProperties());
_clientOptions = p;
_sessions = new ArrayList(1);
addConnectionEventListener(lsnr);
@ -183,7 +185,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
void addSession(I2PSession session) {
if (session == null) return;
synchronized (_sessions) {
_sessions.add(session);
if (!_sessions.contains(session))
_sessions.add(session);
}
}
void removeSession(I2PSession session) {
@ -229,12 +232,16 @@ public class I2PTunnel implements Logging, EventDispatcher {
runClientOptions(args, l);
} else if ("server".equals(cmdname)) {
runServer(args, l);
} else if ("httpserver".equals(cmdname)) {
runHttpServer(args, l);
} else if ("textserver".equals(cmdname)) {
runTextServer(args, l);
} else if ("client".equals(cmdname)) {
runClient(args, l);
} else if ("httpclient".equals(cmdname)) {
runHttpClient(args, l);
} else if ("ircclient".equals(cmdname)) {
runIrcClient(args, l);
} else if ("sockstunnel".equals(cmdname)) {
runSOCKSTunnel(args, l);
} else if ("config".equals(cmdname)) {
@ -281,11 +288,13 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log("owndest yes|no");
l.log("ping <args>");
l.log("server <host> <port> <privkeyfile>");
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
l.log("textserver <host> <port> <privkey>");
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
l.log("gentextkeys");
l.log("client <port> <pubkey>|file:<pubkeyfile>");
l.log("httpclient <port>");
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("ircclient <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
l.log("lookup <name>");
l.log("quit");
l.log("close [forced] <jobnumber>|all");
@ -370,6 +379,65 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
}
/**
* Run the HTTP server pointing at the host and port specified using the private i2p
* destination loaded from the specified file, replacing the HTTP headers
* so that the Host: specified is the one spoofed. <p />
*
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
*
* @param args {hostname, portNumber, spoofedHost, privKeyFilename}
* @param l logger to receive events and output
*/
public void runHttpServer(String args[], Logging l) {
if (args.length == 4) {
InetAddress serverHost = null;
int portNum = -1;
File privKeyFile = null;
try {
serverHost = InetAddress.getByName(args[0]);
} catch (UnknownHostException uhe) {
l.log("unknown host");
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
try {
portNum = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
String spoofedHost = args[2];
privKeyFile = new File(args[3]);
if (!privKeyFile.canRead()) {
l.log("private key file does not exist");
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
I2PTunnelHTTPServer serv = new I2PTunnelHTTPServer(serverHost, portNum, privKeyFile, args[3], spoofedHost, l, (EventDispatcher) this, this);
serv.setReadTimeout(readTimeout);
serv.startRunning();
addtask(serv);
notifyEvent("serverTaskId", new Integer(serv.getId()));
return;
} else {
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>");
l.log(" creates an HTTP server that sends all incoming data\n"
+ " of its destination to host:port., filtering the HTTP\n"
+ " headers so it looks like the request is to the spoofed host.");
notifyEvent("serverTaskId", new Integer(-1));
}
}
/**
* Run the server pointing at the host and port specified using the private i2p
* destination loaded from the given base64 stream. <p />
@ -423,12 +491,16 @@ public class I2PTunnel implements Logging, EventDispatcher {
* Also sets the event "openClientResult" = "error" or "ok" (before setting the value to "ok" it also
* adds "Ready! Port #" to the logger as well). In addition, it will also set "clientLocalPort" =
* Integer port number if the client is listening
* sharedClient parameter is a String "true" or "false"
*
* @param args {portNumber, destinationBase64 or "file:filename"}
* @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient]}
* @param l logger to receive events and output
*/
public void runClient(String args[], Logging l) {
if (args.length == 2) {
boolean isShared = true;
if (args.length == 3)
isShared = Boolean.valueOf(args[2].trim()).booleanValue();
if ( (args.length == 2) || (args.length == 3) ) {
int portNum = -1;
try {
portNum = Integer.parseInt(args[0]);
@ -439,6 +511,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
return;
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this);
addtask(task);
@ -449,9 +522,12 @@ 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>[ <sharedClient>]");
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. sharedClient indicates if this client shares \n"
+ " with other clients (true of false)");
notifyEvent("clientTaskId", new Integer(-1));
}
}
@ -461,12 +537,13 @@ public class I2PTunnel implements Logging, EventDispatcher {
*
* Sets the event "httpclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
* Also sets "httpclientStatus" = "ok" or "error" after the client tunnel has started.
* parameter sharedClient is a String, either "true" or "false"
*
* @param args {portNumber and (optionally) proxy to be used for the WWW}
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
* @param l logger to receive events and output
*/
public void runHttpClient(String args[], Logging l) {
if (args.length >= 1 && args.length <= 2) {
if (args.length >= 1 && args.length <= 3) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
@ -476,12 +553,32 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("httpclientTaskId", new Integer(-1));
return;
}
String proxy = "squid.i2p";
if (args.length == 2) {
proxy = args[1];
boolean isShared = true;
if (args.length > 1) {
if ("true".equalsIgnoreCase(args[1].trim())) {
isShared = true;
if (args.length == 3)
proxy = args[2];
} else if ("false".equalsIgnoreCase(args[1].trim())) {
_log.warn("args[1] == [" + args[1] + "] and rejected explicitly");
isShared = false;
if (args.length == 3)
proxy = args[2];
} else if (args.length == 3) {
isShared = false; // not "true"
proxy = args[2];
_log.warn("args[1] == [" + args[1] + "] but rejected");
} else {
// isShared not specified, default to true
isShared = true;
proxy = args[1];
}
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelHTTPClient(port, l, ownDest, proxy, (EventDispatcher) this, this);
addtask(task);
@ -492,8 +589,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("httpclientTaskId", new Integer(-1));
}
} else {
l.log("httpclient <port> [<proxy>]");
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
l.log(" creates a client that distributes HTTP requests.");
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
l.log(" <proxy> (optional) indicates a proxy server to be used");
l.log(" when trying to access an address out of the .i2p domain");
l.log(" (the default proxy is squid.i2p).");
@ -501,6 +599,60 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
}
/**
* Run an IRC client on the given port number
*
* Sets the event "ircclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
* Also sets "ircclientStatus" = "ok" or "error" after the client tunnel has started.
* parameter sharedClient is a String, either "true" or "false"
*
* @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient]}
* @param l logger to receive events and output
*/
public void runIrcClient(String args[], Logging l) {
if (args.length >= 2 && args.length <= 3) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
notifyEvent("ircclientTaskId", new Integer(-1));
return;
}
boolean isShared = true;
if (args.length > 2) {
if ("true".equalsIgnoreCase(args[2].trim())) {
isShared = true;
} else if ("false".equalsIgnoreCase(args[2].trim())) {
_log.warn("args[2] == [" + args[2] + "] and rejected explicitly");
isShared = false;
} else {
// isShared not specified, default to true
isShared = true;
}
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelIRCClient(port, args[1],l, ownDest, (EventDispatcher) this, this);
addtask(task);
notifyEvent("ircclientTaskId", new Integer(task.getId()));
} catch (IllegalArgumentException iae) {
_log.error(getPrefix() + "Invalid I2PTunnel config to create an ircclient [" + host + ":"+ port + "]", iae);
l.log("Invalid I2PTunnel configuration [" + host + ":" + port + "]");
notifyEvent("ircclientTaskId", new Integer(-1));
}
} else {
l.log("ircclient <port> [<sharedClient>]");
l.log(" creates a client that filter IRC protocol.");
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
notifyEvent("ircclientTaskId", new Integer(-1));
}
}
/**
* Run an SOCKS tunnel on the given port number
*
@ -1052,6 +1204,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
private String getPrefix() { return '[' + _tunnelId + "]: "; }
public I2PAppContext getContext() { return _context; }
/**
* Call this whenever we lose touch with the router involuntarily (aka the router

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

@ -17,8 +17,8 @@ import java.util.List;
import java.util.Properties;
import net.i2p.I2PException;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
@ -26,20 +26,22 @@ import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.SimpleTimer;
import net.i2p.util.Log;
public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable {
private static final Log _log = new Log(I2PTunnelClientBase.class);
protected I2PAppContext _context;
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 +59,32 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private String handlerName;
private Object conLock = new Object();
/** List of Socket for those accept()ed but not yet started up */
private List _waitingSockets;
/** How many connections will we allow to be in the process of being built at once? */
private int _numConnectionBuilders;
/** How long will we allow sockets to sit in the _waitingSockets map before killing them? */
private int _maxWaitTime;
/**
* How many concurrent connections this I2PTunnel instance will allow to be
* in the process of connecting (or if less than 1, there is no limit)?
*/
public static final String PROP_NUM_CONNECTION_BUILDERS = "i2ptunnel.numConnectionBuilders";
/**
* How long will we let a socket wait after being accept()ed without getting
* pumped through a connection builder (in milliseconds). If this time is
* reached, the socket is unceremoniously closed and discarded. If the max
* wait time is less than 1, there is no limit.
*
*/
public static final String PROP_MAX_WAIT_TIME = "i2ptunnel.maxWaitTime";
private static final int DEFAULT_NUM_CONNECTION_BUILDERS = 5;
private static final int DEFAULT_MAX_WAIT_TIME = 30*1000;
//public I2PTunnelClientBase(int localPort, boolean ownDest,
// Logging l) {
// I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
@ -75,11 +103,27 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
this.l = l;
this.handlerName = handlerName + _clientId;
synchronized (sockLock) {
if (ownDest) {
sockMgr = buildSocketManager();
} else {
sockMgr = getSocketManager();
_context = tunnel.getContext();
_context.statManager().createRateStat("i2ptunnel.client.closeBacklog", "How many pending sockets remain when we close one due to backlog?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.client.closeNoBacklog", "How many pending sockets remain when it was removed prior to backlog timeout?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.client.manageTime", "How long it takes to accept a socket and fire it into an i2ptunnel runner (or queue it for the pool)?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
// no need to load the netDb with leaseSets for destinations that will never
// be looked up
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
while (sockMgr == null) {
synchronized (sockLock) {
if (ownDest) {
sockMgr = buildSocketManager();
} else {
sockMgr = getSocketManager();
}
}
if (sockMgr == null) {
_log.log(Log.CRIT, "Unable to create socket manager (our own? " + ownDest + ")");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
if (sockMgr == null) {
@ -96,7 +140,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) {
@ -105,14 +149,47 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
}
configurePool(tunnel);
if (open && listenerReady) {
l.log("Ready! Port " + getLocalPort());
notifyEvent("openBaseClientResult", "ok");
} else {
l.log("Error!");
l.log("Error listening - please see the logs!");
notifyEvent("openBaseClientResult", "error");
}
}
/**
* build and configure the pool handling accept()ed but not yet
* established connections
*
*/
private void configurePool(I2PTunnel tunnel) {
_waitingSockets = new ArrayList(4);
Properties opts = tunnel.getClientOptions();
String maxWait = opts.getProperty(PROP_MAX_WAIT_TIME, DEFAULT_MAX_WAIT_TIME+"");
try {
_maxWaitTime = Integer.parseInt(maxWait);
} catch (NumberFormatException nfe) {
_maxWaitTime = DEFAULT_MAX_WAIT_TIME;
}
String numBuild = opts.getProperty(PROP_NUM_CONNECTION_BUILDERS, DEFAULT_NUM_CONNECTION_BUILDERS+"");
try {
_numConnectionBuilders = Integer.parseInt(numBuild);
} catch (NumberFormatException nfe) {
_numConnectionBuilders = DEFAULT_NUM_CONNECTION_BUILDERS;
}
for (int i = 0; i < _numConnectionBuilders; i++) {
String name = "ClientBuilder" + _clientId + '.' + i;
I2PThread b = new I2PThread(new TunnelConnectionBuilder(), name);
b.setDaemon(true);
b.start();
}
}
private static I2PSocketManager socketManager;
@ -144,8 +221,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
props.putAll(System.getProperties());
else
props.putAll(tunnel.getClientOptions());
I2PSocketManager sockManager = I2PSocketManagerFactory.createManager(tunnel.host, Integer.parseInt(tunnel.port), props);
if (sockManager == null) return null;
int portNum = 7654;
if (tunnel.port != null) {
try {
portNum = Integer.parseInt(tunnel.port);
} catch (NumberFormatException nfe) {
_log.log(Log.CRIT, "Invalid port specified [" + tunnel.port + "], reverting to " + portNum);
}
}
I2PSocketManager sockManager = null;
while (sockManager == null) {
sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
if (sockManager == null) {
_log.log(Log.CRIT, "Unable to create socket manager");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
sockManager.setName("Client");
return sockManager;
}
@ -181,9 +274,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 +335,14 @@ 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();
}
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }
return;
}
ss = new ServerSocket(localPort, 0, addr);
// If a free port was requested, find out what we got
@ -255,11 +370,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
while (true) {
Socket s = ss.accept();
long before = System.currentTimeMillis();
manageConnection(s);
long total = System.currentTimeMillis() - before;
_context.statManager().addRateData("i2ptunnel.client.manageTime", total, total);
}
} 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();
}
}
synchronized (_waitingSockets) {
_waitingSockets.notifyAll();
}
}
@ -269,7 +397,58 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
* @param s Socket to take care of
*/
protected void manageConnection(Socket s) {
new ClientConnectionRunner(s, handlerName);
if (s == null) return;
if (_numConnectionBuilders <= 0) {
new I2PThread(new BlockingRunner(s), "Clinet run").start();
return;
}
if (_maxWaitTime > 0)
SimpleTimer.getInstance().addEvent(new CloseEvent(s), _maxWaitTime);
synchronized (_waitingSockets) {
_waitingSockets.add(s);
_waitingSockets.notifyAll();
}
}
/**
* Blocking runner, used during the connection establishment whenever we
* are not using the queued builders.
*
*/
private class BlockingRunner implements Runnable {
private Socket _s;
public BlockingRunner(Socket s) { _s = s; }
public void run() {
clientConnectionRun(_s);
}
}
/**
* Remove and close the socket from the waiting list, if it is still there.
*
*/
private class CloseEvent implements SimpleTimer.TimedEvent {
private Socket _s;
public CloseEvent(Socket s) { _s = s; }
public void timeReached() {
int remaining = 0;
boolean stillWaiting = false;
synchronized (_waitingSockets) {
stillWaiting = _waitingSockets.remove(_s);
remaining = _waitingSockets.size();
}
if (stillWaiting) {
try { _s.close(); } catch (IOException ioe) {}
if (_log.shouldLog(Log.INFO)) {
_context.statManager().addRateData("i2ptunnel.client.closeBacklog", remaining, 0);
_log.info("Closed a waiting socket because of backlog");
}
} else {
_context.statManager().addRateData("i2ptunnel.client.closeNoBacklog", remaining, 0);
}
}
}
public boolean close(boolean forced) {
@ -301,8 +480,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
l.log("Client closed.");
open = false;
return true;
}
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }
return true;
}
public static void closeSocket(Socket s) {
@ -312,20 +493,34 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
_log.error("Could not close socket", ex);
}
}
private static volatile long __runnerId = 0;
public class ClientConnectionRunner extends I2PThread {
private Socket s;
public ClientConnectionRunner(Socket s, String name) {
this.s = s;
setName(name + '.' + (++__runnerId));
start();
}
public void run() {
clientConnectionRun(s);
/**
* Pool runner pulling sockets off the waiting list and pushing them
* through clientConnectionRun. This dies when the I2PTunnel instance
* is closed.
*
*/
private class TunnelConnectionBuilder implements Runnable {
public void run() {
Socket s = null;
while (open) {
try {
synchronized (_waitingSockets) {
if (_waitingSockets.size() <= 0)
_waitingSockets.wait();
else
s = (Socket)_waitingSockets.remove(0);
}
} catch (InterruptedException ie) {}
if (s != null) {
long before = System.currentTimeMillis();
clientConnectionRun(s);
long total = System.currentTimeMillis() - before;
_context.statManager().addRateData("i2ptunnel.client.buildRunTime", total, 0);
}
s = null;
}
}
}

View File

@ -32,7 +32,7 @@ public class I2PTunnelGUI extends Frame implements ActionListener, Logging {
log.setEditable(false);
log("enter 'help' for help.");
pack();
show();
setVisible(true);
}
public void log(String s) {

View File

@ -14,15 +14,16 @@ 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;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
/**
@ -70,7 +71,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"wrong BASE64 I2P Destination or the link you are following is "+
"bad. The host (or the WWW proxy, if you're using one) could also "+
"be temporarily offline. You may want to <b>retry</b>. "+
"Could not find the following Destination:<BR><BR>")
"Could not find the following Destination:<BR><BR><div>")
.getBytes();
private final static byte[] ERR_TIMEOUT =
@ -95,6 +96,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
.getBytes();
private final static byte[] ERR_AHELPER_CONFLICT =
("HTTP/1.1 409 Conflict\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: Destination key conflict</H1>"+
"The addresshelper link you followed specifies a different destination key "+
"than a host entry in your host database. "+
"Someone could be trying to impersonate another eepsite, "+
"or people have given two eepsites identical names.<P/>"+
"You can resolve the conflict by considering which key you trust, "+
"and either discarding the addresshelper link, "+
"discarding the host entry from your host database, "+
"or naming one of them differently.<P/>")
.getBytes();
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
@ -142,6 +159,43 @@ 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 final boolean DEFAULT_GZIP = true;
private static long __requestId = 0;
protected void clientConnectionRun(Socket s) {
@ -149,20 +203,21 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
String targetRequest = null;
boolean usingWWWProxy = false;
String currentProxy = null;
InactivityTimeoutThread timeoutThread = null;
long requestId = ++__requestId;
try {
out = s.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "ISO-8859-1"));
String line, method = null, protocol = null, host = null, destination = null;
StringBuffer newRequest = new StringBuffer();
int ahelper = 0;
while ((line = br.readLine()) != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
if (line.startsWith("Connection: ") ||
line.startsWith("Keep-Alive: ") ||
line.startsWith("Proxy-Connection: "))
String lowercaseLine = line.toLowerCase();
if (lowercaseLine.startsWith("connection: ") ||
lowercaseLine.startsWith("keep-alive: ") ||
lowercaseLine.startsWith("proxy-connection: "))
continue;
if (method == null) { // first line (GET /base64/realaddr)
@ -175,7 +230,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
String request = line.substring(pos + 1);
if (request.startsWith("/") && getTunnel().getClientOptions().getProperty("i2ptunnel.noproxy") != null) {
request = "http://i2p" + request;
} else if (request.startsWith("/eepproxy/")) {
// /eepproxy/foo.i2p/bar/baz.html HTTP/1.0
String subRequest = request.substring("/eepproxy/".length());
int protopos = subRequest.indexOf(" ");
String uri = subRequest.substring(0, protopos);
if (uri.indexOf("/") == -1) {
uri = uri + "/";
}
// "http://" + "foo.i2p/bar/baz.html" + " HTTP/1.0"
request = "http://" + uri + subRequest.substring(protopos);
}
pos = request.indexOf("//");
if (pos == -1) {
method = null;
@ -195,36 +261,102 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
// Quick hack for foo.bar.i2p
if (host.toLowerCase().endsWith(".i2p")) {
// Destination gets the host name
destination = host;
// Host becomes the destination key
host = getHostName(destination);
if ( (host != null) && ("i2p".equals(host)) ) {
int pos2;
if ((pos2 = line.indexOf("?")) != -1) {
// Try to find an address helper in the fragments
String fragments = line.substring(pos2 + 1);
pos2 = fragments.indexOf(" ");
fragments = fragments.substring(0, pos2);
fragments = fragments + "&";
String fragment;
while(fragments.length() > 0) {
pos2 = fragments.indexOf("&");
fragment = fragments.substring(0, pos2);
fragments = fragments.substring(pos2 + 1);
if (fragment.startsWith("i2paddresshelper")) {
pos2 = fragment.indexOf("=");
if (pos2 >= 0) {
addressHelpers.put(destination,fragment.substring(pos2 + 1));
int pos2;
if ((pos2 = request.indexOf("?")) != -1) {
// Try to find an address helper in the fragments
// and split the request into it's component parts for rebuilding later
String ahelperKey = null;
boolean ahelperConflict = false;
String fragments = request.substring(pos2 + 1);
String uriPath = request.substring(0, pos2);
pos2 = fragments.indexOf(" ");
String protocolVersion = fragments.substring(pos2 + 1);
String urlEncoding = "";
fragments = fragments.substring(0, pos2);
String initialFragments = fragments;
fragments = fragments + "&";
String fragment;
while(fragments.length() > 0) {
pos2 = fragments.indexOf("&");
fragment = fragments.substring(0, pos2);
fragments = fragments.substring(pos2 + 1);
// Fragment looks like addresshelper key
if (fragment.startsWith("i2paddresshelper=")) {
pos2 = fragment.indexOf("=");
ahelperKey = fragment.substring(pos2 + 1);
// Key contains data, lets not ignore it
if (ahelperKey != null) {
// Host resolvable only with addresshelper
if ( (host == null) || ("i2p".equals(host)) )
{
// Cannot check, use addresshelper key
addressHelpers.put(destination,ahelperKey);
} else {
// Host resolvable from database, verify addresshelper key
// Silently bypass correct keys, otherwise alert
if (!host.equals(ahelperKey))
{
// Conflict: handle when URL reconstruction done
ahelperConflict = true;
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + host + "], specified key [" + ahelperKey + "].");
}
}
}
} else {
// Other fragments, just pass along
// Append each fragment to urlEncoding
if ("".equals(urlEncoding)) {
urlEncoding = "?" + fragment;
} else {
urlEncoding = urlEncoding + "&" + fragment;
}
}
}
// Reconstruct the request minus the i2paddresshelper GET var
request = uriPath + urlEncoding + " " + protocolVersion;
// Did addresshelper key conflict?
if (ahelperConflict)
{
String str;
byte[] header;
str = FileUtil.readTextFile("docs/ahelper-conflict-header.ht", 100, true);
if (str != null) header = str.getBytes();
else header = ERR_AHELPER_CONFLICT;
String addressHelper = (String) addressHelpers.get(destination);
if (addressHelper != null) {
destination = addressHelper;
host = getHostName(destination);
if (out != null) {
long alias = I2PAppContext.getGlobalContext().random().nextLong();
String trustedURL = protocol + uriPath + urlEncoding;
String conflictURL = protocol + alias + ".i2p/?" + initialFragments;
out.write(header);
out.write(("To visit the destination in your host database, click <a href=\"" + trustedURL + "\">here</a>. To visit the conflicting addresshelper link by temporarily giving it a random alias, click <a href=\"" + conflictURL + "\">here</a>.<P/>").getBytes());
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
}
s.close();
return;
}
}
String addressHelper = (String) addressHelpers.get(destination);
if (addressHelper != null) {
destination = addressHelper;
host = getHostName(destination);
ahelper = 1;
}
line = method + " " + request.substring(pos);
} else if (host.indexOf(".") != -1) {
// The request must be forwarded to a WWW proxy
@ -277,23 +409,49 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
} else {
if (line.startsWith("Host: ") && !usingWWWProxy) {
if (lowercaseLine.startsWith("host: ") && !usingWWWProxy) {
line = "Host: " + host;
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)";
} else if (line.startsWith("Referer: ")) {
} else if (lowercaseLine.startsWith("user-agent: ")) {
// always stripped, added back at the end
line = null;
continue;
} else if (lowercaseLine.startsWith("accept")) {
// strip the accept-blah headers, as they vary dramatically from
// browser to browser
line = null;
continue;
} else if (lowercaseLine.startsWith("referer: ")) {
// Shouldn't we be more specific, like accepting in-site referers ?
line = "Referer: i2p";
} else if (line.startsWith("Via: ")) {
line = "Via: i2p";
} else if (line.startsWith("From: ")) {
line = "From: i2p";
//line = "Referer: i2p";
line = null;
continue; // completely strip the line
} else if (lowercaseLine.startsWith("via: ")) {
//line = "Via: i2p";
line = null;
continue; // completely strip the line
} else if (lowercaseLine.startsWith("from: ")) {
//line = "From: i2p";
line = null;
continue; // completely strip the line
}
}
if (line.length() == 0) {
String ok = getTunnel().getContext().getProperty("i2ptunnel.gzip");
boolean gzip = DEFAULT_GZIP;
if (ok != null)
gzip = Boolean.valueOf(ok).booleanValue();
if (gzip) {
// according to rfc2616 s14.3, this *should* force identity, even if
// an explicit q=0 for gzip doesn't. tested against orion.i2p, and it
// seems to work.
newRequest.append("Accept-Encoding: \r\n");
newRequest.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
}
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
newRequest.append("Connection: close\r\n\r\n");
break;
} else {
@ -330,30 +488,47 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
l.log("Could not resolve " + destination + ".");
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest, usingWWWProxy, destination);
String str;
byte[] header;
boolean showAddrHelper = false;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else if(ahelper != 0)
str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
else {
str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
showAddrHelper = true;
}
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination, showAddrHelper);
s.close();
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();
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
I2PTunnelRunner runner = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
} catch (SocketException ex) {
if (timeoutThread != null) timeoutThread.disable();
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (IOException ex) {
if (timeoutThread != null) timeoutThread.disable();
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (I2PException ex) {
if (timeoutThread != null) timeoutThread.disable();
_log.info("getPrefix(requestId) + Error trying to connect", ex);
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
@ -361,91 +536,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 {
@ -457,15 +547,52 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
}
private class OnTimeout implements Runnable {
private Socket _socket;
private OutputStream _out;
private String _target;
private boolean _usingProxy;
private String _wwwProxy;
private long _requestId;
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
_socket = s;
_out = out;
_target = target;
_usingProxy = usingProxy;
_wwwProxy = wwwProxy;
_requestId = id;
}
public void run() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Timeout occured requesting " + _target);
handleHTTPClientException(new RuntimeException("Timeout"), _out,
_target, _usingProxy, _wwwProxy, _requestId);
closeSocket(_socket);
}
}
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
boolean usingWWWProxy, String wwwProxy) throws IOException {
boolean usingWWWProxy, String wwwProxy, boolean showAddrHelper) throws IOException {
if (out != null) {
out.write(errMessage);
if (targetRequest != null) {
out.write(targetRequest.getBytes());
int protopos = targetRequest.indexOf(" ");
String uri = targetRequest.substring(0, protopos);
out.write("<a href=\"http://".getBytes());
out.write(uri.getBytes());
out.write("\">http://".getBytes());
out.write(uri.getBytes());
out.write("</a>".getBytes());
if (usingWWWProxy) out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
if (showAddrHelper) {
out.write("<br><br>Click below to try an address helper link:<br><br><a href=\"http://orion.i2p/jump/".getBytes());
out.write(uri.getBytes());
out.write("\">http://orion.i2p/jump/".getBytes());
out.write(uri.getBytes());
out.write("</a>".getBytes());
}
}
out.write("<p /><i>Generated on: ".getBytes());
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
@ -479,7 +606,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
_log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex);
if (out != null) {
try {
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest, usingWWWProxy, wwwProxy);
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy, false);
} catch (IOException ioe) {
_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
}

View File

@ -0,0 +1,64 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.*;
import java.net.Socket;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**
* Override the response with a stream filtering the HTTP headers
* received. Specifically, this makes sure we get Connection: close,
* so the browser knows they really shouldn't try to use persistent
* connections. The HTTP server *should* already be setting this,
* since the HTTP headers sent by the browser specify Connection: close,
* and the server should echo it. However, both broken and malicious
* servers could ignore that, potentially confusing the user.
*
*/
public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner {
private Log _log;
public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
super(s, i2ps, slock, initialI2PData, sockList, onTimeout);
_log = I2PAppContext.getGlobalContext().logManager().getLog(I2PTunnelHTTPClientRunner.class);
}
protected OutputStream getSocketOut() throws IOException {
OutputStream raw = super.getSocketOut();
return new HTTPResponseOutputStream(raw);
}
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
try {
i2pin.close();
i2pout.close();
} catch (IOException ioe) {
// ignore
if (_log.shouldLog(Log.DEBUG))
_log.debug("Unable to close the i2p socket output stream: " + i2pout, ioe);
}
try {
in.close();
out.close();
} catch (IOException ioe) {
// ignore
if (_log.shouldLog(Log.DEBUG))
_log.debug("Unable to close the browser output stream: " + out, ioe);
}
i2ps.close();
s.close();
t1.join(30*1000);
t2.join(30*1000);
}
}

View File

@ -0,0 +1,317 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.Iterator;
import java.util.Properties;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Deflater;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataHelper;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Simple extension to the I2PTunnelServer that filters the HTTP
* headers sent from the client to the server, replacing the Host
* header with whatever this instance has been configured with, and
* if the browser set Accept-encoding: x-i2p-gzip, gzip the http
* message body and set Content-encoding: x-i2p-gzip.
*
*/
public class I2PTunnelHTTPServer extends I2PTunnelServer {
private final static Log _log = new Log(I2PTunnelHTTPServer.class);
/** what Host: should we seem to be to the webserver? */
private String _spoofHost;
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privData, l, notifyThis, tunnel);
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
}
public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
}
public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privData, privkeyname, l, notifyThis, tunnel);
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
}
/**
* Called by the thread pool of I2PSocket handlers
*
*/
protected void blockingHandle(I2PSocket socket) {
long afterAccept = getTunnel().getContext().clock().now();
long afterSocket = -1;
//local is fast, so synchronously. Does not need that many
//threads.
try {
// give them 5 seconds to send in the HTTP request
socket.setReadTimeout(5*1000);
InputStream in = socket.getInputStream();
StringBuffer command = new StringBuffer(128);
Properties headers = readHeaders(in, command);
if ( (_spoofHost != null) && (_spoofHost.trim().length() > 0) )
headers.setProperty("Host", _spoofHost);
headers.setProperty("Connection", "close");
// we keep the enc sent by the browser before clobbering it, since it may have
// been x-i2p-gzip
String enc = headers.getProperty("Accept-encoding");
String altEnc = headers.getProperty("X-Accept-encoding");
// according to rfc2616 s14.3, this *should* force identity, even if
// "identity;q=1, *;q=0" didn't.
headers.setProperty("Accept-encoding", "");
String modifiedHeader = formatHeaders(headers, command);
//String modifiedHeader = getModifiedHeader(socket);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Modified header: [" + modifiedHeader + "]");
socket.setReadTimeout(readTimeout);
Socket s = new Socket(remoteHost, remotePort);
afterSocket = getTunnel().getContext().clock().now();
// instead of i2ptunnelrunner, use something that reads the HTTP
// request from the socket, modifies the headers, sends the request to the
// server, reads the response headers, rewriting to include Content-encoding: x-i2p-gzip
// if it was one of the Accept-encoding: values, and gzip the payload
Properties opts = getTunnel().getClientOptions();
boolean allowGZIP = true;
if (opts != null) {
String val = opts.getProperty("i2ptunnel.gzip");
if ( (val != null) && (!Boolean.valueOf(val).booleanValue()) )
allowGZIP = false;
}
if (_log.shouldLog(Log.INFO))
_log.info("HTTP server encoding header: " + enc + "/" + altEnc);
boolean useGZIP = ( (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) );
if ( (!useGZIP) && (altEnc != null) && (altEnc.indexOf("x-i2p-gzip") >= 0) )
useGZIP = true;
if (allowGZIP && useGZIP) {
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), "http compressor");
req.start();
} else {
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
}
} catch (SocketException ex) {
try {
socket.close();
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error while closing the received i2p con", ex);
}
} catch (IOException ex) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new HTTP request", ex);
}
long afterHandle = getTunnel().getContext().clock().now();
long timeToHandle = afterHandle - afterAccept;
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpserver.blockingHandleTime", timeToHandle, 0);
if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
}
private class CompressedRequestor implements Runnable {
private Socket _webserver;
private I2PSocket _browser;
private String _headers;
public CompressedRequestor(Socket webserver, I2PSocket browser, String headers) {
_webserver = webserver;
_browser = browser;
_headers = headers;
}
public void run() {
if (_log.shouldLog(Log.INFO))
_log.info("Compressed requestor running");
OutputStream serverout = null;
OutputStream browserout = null;
InputStream browserin = null;
InputStream serverin = null;
try {
serverout = _webserver.getOutputStream();
if (_log.shouldLog(Log.INFO))
_log.info("request headers: " + _headers);
serverout.write(_headers.getBytes());
browserin = _browser.getInputStream();
I2PThread sender = new I2PThread(new Sender(serverout, browserin, "server: browser to server"), "http compressed sender");
sender.start();
browserout = _browser.getOutputStream();
serverin = _webserver.getInputStream();
CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout);
Sender s = new Sender(compressedOut, serverin, "server: server to browser");
if (_log.shouldLog(Log.INFO))
_log.info("Before pumping the compressed response");
s.run(); // same thread
if (_log.shouldLog(Log.INFO))
_log.info("After pumping the compressed response: " + compressedOut.getTotalRead() + "/" + compressedOut.getTotalCompressed());
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("error compressing", ioe);
} finally {
if (browserout != null) try { browserout.close(); } catch (IOException ioe) {}
if (serverout != null) try { serverout.close(); } catch (IOException ioe) {}
if (browserin != null) try { browserin.close(); } catch (IOException ioe) {}
if (serverin != null) try { serverin.close(); } catch (IOException ioe) {}
}
}
}
private class Sender implements Runnable {
private OutputStream _out;
private InputStream _in;
private String _name;
public Sender(OutputStream out, InputStream in, String name) {
_out = out;
_in = in;
_name = name;
}
public void run() {
if (_log.shouldLog(Log.INFO))
_log.info(_name + ": Begin sending");
try {
byte buf[] = new byte[16*1024];
int read = 0;
int total = 0;
while ( (read = _in.read(buf)) != -1) {
if (_log.shouldLog(Log.INFO))
_log.info(_name + ": read " + read + " and sending through the stream");
_out.write(buf, 0, read);
total += read;
}
if (_log.shouldLog(Log.INFO))
_log.info(_name + ": Done sending: " + total);
//_out.flush();
} catch (IOException ioe) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error sending", ioe);
} finally {
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
}
}
}
private class CompressedResponseOutputStream extends HTTPResponseOutputStream {
private InternalGZIPOutputStream _gzipOut;
public CompressedResponseOutputStream(OutputStream o) {
super(o);
}
protected boolean shouldCompress() { return true; }
protected void finishHeaders() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("Including x-i2p-gzip as the content encoding in the response");
out.write("Content-encoding: x-i2p-gzip\n".getBytes());
super.finishHeaders();
}
protected void beginProcessing() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("Beginning compression processing");
//out.flush();
_gzipOut = new InternalGZIPOutputStream(out);
out = _gzipOut;
}
public long getTotalRead() {
InternalGZIPOutputStream gzipOut = _gzipOut;
if (gzipOut != null)
return gzipOut.getTotalRead();
else
return 0;
}
public long getTotalCompressed() {
InternalGZIPOutputStream gzipOut = _gzipOut;
if (gzipOut != null)
return gzipOut.getTotalCompressed();
else
return 0;
}
}
private class InternalGZIPOutputStream extends GZIPOutputStream {
public InternalGZIPOutputStream(OutputStream target) throws IOException {
super(target);
}
public long getTotalRead() {
try {
return def.getTotalIn();
} catch (Exception e) {
// j2se 1.4.2_08 on linux is sometimes throwing an NPE in the getTotalIn() implementation
return 0;
}
}
public long getTotalCompressed() {
try {
return def.getTotalOut();
} catch (Exception e) {
// j2se 1.4.2_08 on linux is sometimes throwing an NPE in the getTotalOut() implementation
return 0;
}
}
}
private String formatHeaders(Properties headers, StringBuffer command) {
StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64);
buf.append(command.toString()).append('\n');
for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String val = headers.getProperty(name);
buf.append(name).append(": ").append(val).append('\n');
}
buf.append('\n');
return buf.toString();
}
private Properties readHeaders(InputStream in, StringBuffer command) throws IOException {
Properties headers = new Properties();
StringBuffer buf = new StringBuffer(128);
boolean ok = DataHelper.readLine(in, command);
if (!ok) throw new IOException("EOF reached while reading the HTTP command [" + command.toString() + "]");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read the http command [" + command.toString() + "]");
while (true) {
buf.setLength(0);
ok = DataHelper.readLine(in, buf);
if (!ok) throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
if ( (buf.length() <= 1) && ( (buf.charAt(0) == '\n') || (buf.charAt(0) == '\r') ) ) {
// end of headers reached
return headers;
} else {
int split = buf.indexOf(": ");
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
String name = buf.substring(0, split);
String value = buf.substring(split+2); // ": "
if ("Accept-encoding".equalsIgnoreCase(name))
name = "Accept-encoding";
else if ("X-Accept-encoding".equalsIgnoreCase(name))
name = "X-Accept-encoding";
headers.setProperty(name, value);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read the header [" + name + "] = [" + value + "]");
}
}
}
}

View File

@ -0,0 +1,399 @@
package net.i2p.i2ptunnel;
import java.io.*;
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.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable {
private static final Log _log = new Log(I2PTunnelIRCClient.class);
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
/** 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;
/**
* @throws IllegalArgumentException if the I2PTunnel does not contain
* valid config to contact the router
*/
public I2PTunnelIRCClient(
int localPort,
String destinations,
Logging l,
boolean ownDest,
EventDispatcher notifyThis,
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort,
ownDest,
l,
notifyThis,
"IRCHandler " + (++__clientId), tunnel);
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 + "\"");
}
}
if (dests.size() <= 0) {
l.log("No target destinations found");
notifyEvent("openClientResult", "error");
return;
}
setName(getLocalPort() + " -> IRCClient");
startRunning();
notifyEvent("openIRCClientResult", "ok");
}
protected void clientConnectionRun(Socket s) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("got a connection.");
Destination dest = pickDestination();
I2PSocket i2ps = null;
try {
i2ps = createI2PSocket(dest);
i2ps.setReadTimeout(readTimeout);
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps));
in.start();
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps));
out.start();
} catch (Exception ex) {
if (_log.shouldLog(Log.ERROR))
_log.error("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);
}
/*************************************************************************
*
*/
private class IrcInboundFilter implements Runnable {
private Socket local;
private I2PSocket remote;
IrcInboundFilter(Socket _local, I2PSocket _remote) {
local=_local;
remote=_remote;
}
public void run() {
InputStream input;
OutputStream output;
try {
input=remote.getInputStream();
output=local.getOutputStream();
} catch (IOException e) {
if (_log.shouldLog(Log.ERROR))
_log.error("IrcInboundFilter: no streams",e);
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("IrcInboundFilter: Running.");
try {
while(true)
{
try {
String inmsg = DataHelper.readLine(input);
if(inmsg==null)
break;
if(inmsg.endsWith("\r"))
inmsg=inmsg.substring(0,inmsg.length()-1);
String outmsg = inboundFilter(inmsg);
if(outmsg!=null)
{
if(!inmsg.equals(outmsg)) {
if (_log.shouldLog(Log.WARN)) {
_log.warn("inbound FILTERED: "+outmsg);
_log.warn(" - inbound was: "+inmsg);
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("inbound: "+outmsg);
}
outmsg=outmsg+"\n";
output.write(outmsg.getBytes());
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("inbound BLOCKED: "+inmsg);
}
} catch (IOException e1) {
if (_log.shouldLog(Log.WARN))
_log.warn("IrcInboundFilter: disconnected",e1);
break;
}
}
} catch (RuntimeException re) {
_log.error("Error filtering inbound data", re);
} finally {
if (local != null) try { local.close(); } catch (IOException e) {}
}
if(_log.shouldLog(Log.DEBUG))
_log.debug("IrcInboundFilter: Done.");
}
}
/*************************************************************************
*
*/
private class IrcOutboundFilter implements Runnable {
private Socket local;
private I2PSocket remote;
IrcOutboundFilter(Socket _local, I2PSocket _remote) {
local=_local;
remote=_remote;
}
public void run() {
InputStream input;
OutputStream output;
try {
input=local.getInputStream();
output=remote.getOutputStream();
} catch (IOException e) {
if (_log.shouldLog(Log.ERROR))
_log.error("IrcOutboundFilter: no streams",e);
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("IrcOutboundFilter: Running.");
try {
while(true)
{
try {
String inmsg = DataHelper.readLine(input);
if(inmsg==null)
break;
if(inmsg.endsWith("\r"))
inmsg=inmsg.substring(0,inmsg.length()-1);
String outmsg = outboundFilter(inmsg);
if(outmsg!=null)
{
if(!inmsg.equals(outmsg)) {
if (_log.shouldLog(Log.WARN)) {
_log.warn("outbound FILTERED: "+outmsg);
_log.warn(" - outbound was: "+inmsg);
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("outbound: "+outmsg);
}
outmsg=outmsg+"\n";
output.write(outmsg.getBytes());
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("outbound BLOCKED: "+"\""+inmsg+"\"");
}
} catch (IOException e1) {
if (_log.shouldLog(Log.WARN))
_log.warn("IrcOutboundFilter: disconnected",e1);
break;
}
}
} catch (RuntimeException re) {
_log.error("Error filtering outbound data", re);
} finally {
if (remote != null) try { remote.close(); } catch (IOException e) {}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("IrcOutboundFilter: Done.");
}
}
/*************************************************************************
*
*/
public static String inboundFilter(String s) {
String field[]=s.split(" ",4);
String command;
int idx=0;
final String[] allowedCommands =
{
"NOTICE",
"PING",
"PONG",
"MODE",
"JOIN",
"NICK",
"QUIT",
"PART",
"WALLOPS",
"ERROR",
"TOPIC"
};
if(field[0].charAt(0)==':')
idx++;
command = field[idx++];
idx++; //skip victim
// Allow numerical responses
try {
new Integer(command);
return s;
} catch(NumberFormatException nfe){}
// Allow all allowedCommands
for(int i=0;i<allowedCommands.length;i++) {
if(allowedCommands[i].equals(command))
return s;
}
// Allow PRIVMSG, but block CTCP.
if("PRIVMSG".equals(command))
{
String msg;
msg = field[idx++];
byte[] bytes = msg.getBytes();
if(bytes[1]==0x01)
{
// CTCP
msg=msg.substring(2);
if(msg.startsWith("ACTION ")) {
// /me says hello
return s;
}
return null; // Block all other ctcp
}
return s;
}
// Block the rest
return null;
}
public static String outboundFilter(String s) {
String field[]=s.split(" ",3);
String command;
final String[] allowedCommands =
{
"NOTICE",
"PONG",
"MODE",
"JOIN",
"NICK",
"WHO",
"WHOIS",
"LIST",
"NAMES",
"NICK",
// "QUIT", // replace with a filtered QUIT to hide client quit messages
"SILENCE",
"MAP", // seems safe enough, the ircd should protect themselves though
"PART",
"OPER",
"PING",
"KICK",
"HELPME",
"RULES",
"TOPIC"
};
if(field[0].length()==0)
return null; // W T F?
if(field[0].charAt(0)==':')
return null; // wtf
command = field[0].toUpperCase();
// Allow all allowedCommands
for(int i=0;i<allowedCommands.length;i++)
{
if(allowedCommands[i].equals(command))
return s;
}
// Allow PRIVMSG, but block CTCP (except ACTION).
if("PRIVMSG".equals(command))
{
String msg;
msg = field[2];
byte[] bytes = msg.getBytes();
if(bytes[1]==0x01)
{
// CTCP
msg=msg.substring(2);
if(msg.startsWith("ACTION ")) {
// /me says hello
return s;
}
return null; // Block all other ctcp
}
return s;
}
if("USER".equals(command)) {
int idx = field[2].lastIndexOf(":");
if(idx<0)
return "USER user hostname localhost :realname";
String realname = field[2].substring(idx+1);
String ret = "USER "+field[1]+" hostname localhost :"+realname;
return ret;
} else if ("QUIT".equals(command)) {
return "QUIT :leaving";
}
// Block the rest
return null;
}
}

View File

@ -3,7 +3,6 @@
*/
package net.i2p.i2ptunnel;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
@ -11,9 +10,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;
@ -29,7 +30,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
* Sun's impl of BufferedOutputStream), but that is the streaming
* api's job...
*/
static int MAX_PACKET_SIZE = 1024 * 32;
static int MAX_PACKET_SIZE = 1024 * 4;
static final int NETWORK_BUFFER_SIZE = MAX_PACKET_SIZE;
@ -38,23 +39,43 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
Object slock, finishLock = new Object();
boolean finished = false;
HashMap ostreams, sockets;
I2PSession session;
byte[] initialData;
byte[] initialI2PData;
byte[] initialSocketData;
/** when the last data was sent/received (or -1 if never) */
private long lastActivityOn;
/** when the runner started up */
private long startedOn;
private List sockList;
/** if we die before receiving any data, run this job */
private Runnable onTimeout;
private long totalSent;
private long totalReceived;
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData) {
private volatile long __forwarderId;
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList) {
this(s, i2ps, slock, initialI2PData, null, sockList, null);
}
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList) {
this(s, i2ps, slock, initialI2PData, initialSocketData, sockList, null);
}
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) {
this(s, i2ps, slock, initialI2PData, null, sockList, onTimeout);
}
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList, Runnable onTimeout) {
this.sockList = sockList;
this.s = s;
this.i2ps = i2ps;
this.slock = slock;
this.initialData = initialData;
this.initialI2PData = initialI2PData;
this.initialSocketData = initialSocketData;
this.onTimeout = onTimeout;
lastActivityOn = -1;
startedOn = Clock.getInstance().now();
if (_log.shouldLog(Log.INFO))
_log.info("I2PTunnelRunner started");
_runnerId = ++__runnerId;
__forwarderId = i2ps.hashCode();
setName("I2PTunnelRunner " + _runnerId);
start();
}
@ -90,51 +111,101 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
return startedOn;
}
protected InputStream getSocketIn() throws IOException { return s.getInputStream(); }
protected OutputStream getSocketOut() throws IOException { return s.getOutputStream(); }
public void run() {
try {
InputStream in = s.getInputStream();
OutputStream out = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
InputStream in = getSocketIn();
OutputStream out = getSocketOut(); // = new BufferedOutputStream(s.getOutputStream(), NETWORK_BUFFER_SIZE);
i2ps.setSocketErrorListener(this);
InputStream i2pin = i2ps.getInputStream();
OutputStream i2pout = new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
if (initialData != null) {
OutputStream i2pout = i2ps.getOutputStream(); //new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
if (initialI2PData != null) {
synchronized (slock) {
i2pout.write(initialData);
i2pout.flush();
i2pout.write(initialI2PData);
//i2pout.flush();
}
}
Thread t1 = new StreamForwarder(in, i2pout);
Thread t2 = new StreamForwarder(i2pin, out);
if (initialSocketData != null) {
out.write(initialSocketData);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Initial data " + (initialI2PData != null ? initialI2PData.length : 0)
+ " written to I2P, " + (initialSocketData != null ? initialSocketData.length : 0)
+ " written to the socket, starting forwarders");
Thread t1 = new StreamForwarder(in, i2pout, true);
Thread t2 = new StreamForwarder(i2pin, out, false);
synchronized (finishLock) {
while (!finished) {
finishLock.wait();
}
}
// now one connection is dead - kill the other as well.
s.close();
s = null;
i2ps.close();
i2ps = null;
t1.join();
t2.join();
if (_log.shouldLog(Log.DEBUG))
_log.debug("At least one forwarder completed, closing and joining");
// this task is useful for the httpclient
if (onTimeout != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("runner has a timeout job, totalReceived = " + totalReceived
+ " totalSent = " + totalSent + " job = " + onTimeout);
if ( (totalSent <= 0) && (totalReceived <= 0) )
onTimeout.run();
}
// now one connection is dead - kill the other as well, after making sure we flush
close(out, in, i2pout, i2pin, s, i2ps, t1, t2);
} 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)
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);
}
if (i2ps != null) {
try {
i2ps.close();
} catch (IOException ex) {
if (_log.shouldLog(Log.ERROR))
_log.error("Could not close I2PSocket", ex);
}
i2ps.setSocketErrorListener(null);
}
}
}
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException {
try {
out.flush();
} catch (IOException ioe) {
// ignore
}
try {
i2pout.flush();
} catch (IOException ioe) {
// ignore
}
in.close();
i2pin.close();
// ok, yeah, there's a race here in theory, if data comes in after flushing and before
// closing, but its better than before...
s.close();
i2ps.close();
t1.join(30*1000);
t2.join(30*1000);
}
public void errorOccurred() {
synchronized (finishLock) {
finished = true;
@ -142,63 +213,110 @@ 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 boolean _toI2P;
private ByteCache _cache;
private StreamForwarder(InputStream in, OutputStream out) {
private StreamForwarder(InputStream in, OutputStream out, boolean toI2P) {
this.in = in;
this.out = out;
_toI2P = toI2P;
direction = (toI2P ? "toI2P" : "fromI2P");
_cache = ByteCache.getInstance(32, 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) {
out.write(buffer, 0, len);
if (_toI2P)
totalSent += len;
else
totalReceived += len;
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 {
_cache.release(ba);
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.flush();
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn(direction + ": Error flushing to close", ioe);
}
synchronized (finishLock) {
finished = true;

View File

@ -11,6 +11,7 @@ import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.ConnectException;
import java.util.Iterator;
import java.util.Properties;
@ -31,29 +32,43 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
private final static Log _log = new Log(I2PTunnelServer.class);
private I2PSocketManager sockMgr;
private I2PServerSocket i2pss;
protected I2PSocketManager sockMgr;
protected I2PServerSocket i2pss;
private Object lock = new Object(), slock = new Object();
private Object lock = new Object();
protected Object slock = new Object();
private InetAddress remoteHost;
private int remotePort;
protected InetAddress remoteHost;
protected int remotePort;
private boolean _usePool;
private Logging l;
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
/** default timeout to 3 minutes - override if desired */
private long readTimeout = DEFAULT_READ_TIMEOUT;
protected long readTimeout = DEFAULT_READ_TIMEOUT;
private static final boolean DEFAULT_USE_POOL = false;
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host + ":" + port + " <- " + privData, notifyThis, tunnel);
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
if (usePool != null)
_usePool = "true".equalsIgnoreCase(usePool);
else
_usePool = DEFAULT_USE_POOL;
init(host, port, bais, privData, l);
}
public I2PTunnelServer(InetAddress host, int port, File privkey, String privkeyname, Logging l,
EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
if (usePool != null)
_usePool = "true".equalsIgnoreCase(usePool);
else
_usePool = DEFAULT_USE_POOL;
try {
init(host, port, new FileInputStream(privkey), privkeyname, l);
} catch (IOException ioe) {
@ -64,6 +79,11 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
String usePool = tunnel.getClientOptions().getProperty("i2ptunnel.usePool");
if (usePool != null)
_usePool = "true".equalsIgnoreCase(usePool);
else
_usePool = DEFAULT_USE_POOL;
init(host, port, privData, privkeyname, l);
}
@ -74,10 +94,25 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
I2PClient client = I2PClientFactory.createClient();
Properties props = new Properties();
props.putAll(getTunnel().getClientOptions());
synchronized (slock) {
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, Integer.parseInt(getTunnel().port),
props);
int portNum = 7654;
if (getTunnel().port != null) {
try {
portNum = Integer.parseInt(getTunnel().port);
} catch (NumberFormatException nfe) {
_log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
}
}
while (sockMgr == null) {
synchronized (slock) {
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, portNum,
props);
}
if (sockMgr == null) {
_log.log(Log.CRIT, "Unable to create socket manager");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
sockMgr.setName("Server");
getTunnel().addSession(sockMgr.getSession());
@ -143,57 +178,103 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
}
private static final String PROP_HANDLER_COUNT = "i2ptunnel.blockingHandlerCount";
private static final int DEFAULT_HANDLER_COUNT = 10;
protected int getHandlerCount() {
int rv = DEFAULT_HANDLER_COUNT;
String cnt = getTunnel().getClientOptions().getProperty(PROP_HANDLER_COUNT);
if (cnt != null) {
try {
rv = Integer.parseInt(cnt);
if (rv <= 0)
rv = DEFAULT_HANDLER_COUNT;
} catch (NumberFormatException nfe) {
rv = DEFAULT_HANDLER_COUNT;
}
}
return rv;
}
public void run() {
try {
if (shouldUsePool()) {
I2PServerSocket i2pss = sockMgr.getServerSocket();
int handlers = getHandlerCount();
for (int i = 0; i < handlers; i++) {
I2PThread handler = new I2PThread(new Handler(i2pss), "Handle Server " + i);
handler.start();
}
} else {
I2PServerSocket i2pss = sockMgr.getServerSocket();
while (true) {
I2PSocket i2ps = i2pss.accept();
I2PThread t = new I2PThread(new Handler(i2ps));
t.start();
try {
final I2PSocket i2ps = i2pss.accept();
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
new I2PThread(new Runnable() { public void run() { blockingHandle(i2ps); } }).start();
} catch (I2PException ipe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error accepting - KILLING THE TUNNEL SERVER", ipe);
return;
} catch (ConnectException ce) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error accepting", ce);
// not killing the server..
}
}
} catch (I2PException ex) {
_log.error("Error while waiting for I2PConnections", ex);
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
}
}
public boolean shouldUsePool() { return _usePool; }
/**
* Async handler to keep .accept() from blocking too long.
* todo: replace with a thread pool so we dont get overrun by threads if/when
* receiving a lot of connection requests concurrently.
* minor thread pool to pull off the accept() concurrently. there are still lots
* (and lots) of wasted threads within the I2PTunnelRunner, but its a start
*
*/
private class Handler implements Runnable {
private I2PSocket _handleSocket;
public Handler(I2PSocket socket) {
_handleSocket = socket;
private I2PServerSocket _serverSocket;
public Handler(I2PServerSocket serverSocket) {
_serverSocket = serverSocket;
}
public void run() {
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
long afterSocket = -1;
//local is fast, so synchronously. Does not need that many
//threads.
try {
_handleSocket.setReadTimeout(readTimeout);
Socket s = new Socket(remoteHost, remotePort);
afterSocket = I2PAppContext.getGlobalContext().clock().now();
new I2PTunnelRunner(s, _handleSocket, slock, null);
} catch (SocketException ex) {
while (open) {
try {
_handleSocket.close();
} catch (IOException ioe) {
_log.error("Error while closing the received i2p con", ex);
blockingHandle(_serverSocket.accept());
} catch (I2PException ex) {
_log.error("Error while waiting for I2PConnections", ex);
return;
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
return;
}
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
}
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
long timeToHandle = afterHandle - afterAccept;
if (timeToHandle > 1000)
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
}
}
protected void blockingHandle(I2PSocket socket) {
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
long afterSocket = -1;
//local is fast, so synchronously. Does not need that many
//threads.
try {
socket.setReadTimeout(readTimeout);
Socket s = new Socket(remoteHost, remotePort);
afterSocket = I2PAppContext.getGlobalContext().clock().now();
new I2PTunnelRunner(s, socket, slock, null, null);
} catch (SocketException ex) {
try {
socket.close();
} catch (IOException ioe) {
_log.error("Error while closing the received i2p con", ex);
}
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
}
long afterHandle = I2PAppContext.getGlobalContext().clock().now();
long timeToHandle = afterHandle - afterAccept;
if (timeToHandle > 1000)
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
}
}

View File

@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
@ -15,6 +16,7 @@ import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.data.Destination;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
@ -30,6 +32,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.
@ -55,10 +58,9 @@ public class TunnelController implements Logging {
setConfig(config, prefix);
_messages = new ArrayList(4);
_running = false;
if (createKey && ("server".equals(getType())) )
if (createKey && ("server".equals(getType()) || "httpserver".equals(getType())) )
createPrivateKey();
if (getStartOnLoad())
startTunnel();
_starting = getStartOnLoad();
}
private void createPrivateKey() {
@ -98,17 +100,25 @@ public class TunnelController implements Logging {
}
}
public void startTunnelBackground() {
if (_running) return;
_starting = true;
new I2PThread(new Runnable() { public void run() { startTunnel(); } }).start();
}
/**
* Start up the tunnel (if it isn't already running)
*
*/
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) {
@ -125,10 +135,14 @@ public class TunnelController implements Logging {
}
if ("httpclient".equals(type)) {
startHttpClient();
}else if("ircclient".equals(type)) {
startIrcClient();
} else if ("client".equals(type)) {
startClient();
} else if ("server".equals(type)) {
startServer();
} else if ("httpserver".equals(type)) {
startHttpServer();
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Cannot start tunnel - unknown type [" + type + "]");
@ -141,10 +155,23 @@ public class TunnelController implements Logging {
setListenOn();
String listenPort = getListenPort();
String proxyList = getProxyList();
String sharedClient = getSharedClient();
if (proxyList == null)
_tunnel.runHttpClient(new String[] { listenPort }, this);
_tunnel.runHttpClient(new String[] { listenPort, sharedClient }, this);
else
_tunnel.runHttpClient(new String[] { listenPort, proxyList }, this);
_tunnel.runHttpClient(new String[] { listenPort, sharedClient, proxyList }, this);
acquire();
_running = true;
}
private void startIrcClient() {
setI2CPOptions();
setSessionOptions();
setListenOn();
String listenPort = getListenPort();
String dest = getTargetDestination();
String sharedClient = getSharedClient();
_tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this);
acquire();
_running = true;
}
@ -187,7 +214,8 @@ public class TunnelController implements Logging {
setListenOn();
String listenPort = getListenPort();
String dest = getTargetDestination();
_tunnel.runClient(new String[] { listenPort, dest }, this);
String sharedClient = getSharedClient();
_tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
acquire();
_running = true;
}
@ -203,6 +231,18 @@ public class TunnelController implements Logging {
_running = true;
}
private void startHttpServer() {
setI2CPOptions();
setSessionOptions();
String targetHost = getTargetHost();
String targetPort = getTargetPort();
String spoofedHost = getSpoofedHost();
String privKeyFile = getPrivKeyFile();
_tunnel.runHttpServer(new String[] { targetHost, targetPort, spoofedHost, privKeyFile }, this);
acquire();
_running = true;
}
private void setListenOn() {
String listenOn = getListenOnInterface();
if ( (listenOn != null) && (listenOn.length() > 0) ) {
@ -230,9 +270,20 @@ public class TunnelController implements Logging {
String host = getI2CPHost();
if ( (host != null) && (host.length() > 0) )
_tunnel.host = host;
// woohah, special casing for people with ipv6/etc
if ("localhost".equals(_tunnel.host))
_tunnel.host = "127.0.0.1";
String port = getI2CPPort();
if ( (port != null) && (port.length() > 0) )
_tunnel.port = port;
if ( (port != null) && (port.length() > 0) ) {
try {
int portNum = Integer.parseInt(port);
_tunnel.port = String.valueOf(portNum);
} catch (NumberFormatException nfe) {
_tunnel.port = "7654";
}
} else {
_tunnel.port = "7654";
}
}
public void stopTunnel() {
@ -291,13 +342,28 @@ public class TunnelController implements Logging {
public String getListenOnInterface() { return _config.getProperty("interface"); }
public String getTargetHost() { return _config.getProperty("targetHost"); }
public String getTargetPort() { return _config.getProperty("targetPort"); }
public String getSpoofedHost() { return _config.getProperty("spoofedHost"); }
public String getPrivKeyFile() { return _config.getProperty("privKeyFile"); }
public String getListenPort() { return _config.getProperty("listenPort"); }
public String getTargetDestination() { return _config.getProperty("targetDestination"); }
public String getProxyList() { return _config.getProperty("proxyList"); }
public String getSharedClient() { return _config.getProperty("sharedClient", "true"); }
public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
public String getMyDestination() {
if (_tunnel != null) {
List sessions = _tunnel.getSessions();
for (int i = 0; i < sessions.size(); i++) {
I2PSession session = (I2PSession)sessions.get(i);
Destination dest = session.getMyDestination();
if (dest != null)
return dest.toBase64();
}
}
return null;
}
public boolean getIsRunning() { return _running; }
public boolean getIsStarting() { return _starting; }
public void getSummary(StringBuffer buf) {
String type = getType();
@ -307,6 +373,8 @@ public class TunnelController implements Logging {
getClientSummary(buf);
else if ("server".equals(type))
getServerSummary(buf);
else if ("httpserver".equals(type))
getHttpServerSummary(buf);
else
buf.append("Unknown type ").append(type);
}
@ -360,6 +428,18 @@ public class TunnelController implements Logging {
getOptionSummary(buf);
}
private void getHttpServerSummary(StringBuffer buf) {
String description = getDescription();
if ( (description != null) && (description.trim().length() > 0) )
buf.append("<i>").append(description).append("</i><br />\n");
buf.append("Server tunnel pointing at port ").append(getTargetPort());
buf.append(" on ").append(getTargetHost());
buf.append(" for the site ").append(getSpoofedHost());
buf.append("<br />\n");
buf.append("Private destination loaded from ").append(getPrivKeyFile()).append("<br />\n");
getOptionSummary(buf);
}
private void getOptionSummary(StringBuffer buf) {
String opts = getClientOptions();
if ( (opts != null) && (opts.length() > 0) )
@ -371,14 +451,20 @@ public class TunnelController implements Logging {
Destination dest = session.getMyDestination();
if (dest != null) {
buf.append("Destination hash: ").append(dest.calculateHash().toBase64()).append("<br />\n");
if ("server".equals(getType())) {
if ( ("server".equals(getType())) || ("httpserver".equals(getType())) ) {
buf.append("Full destination: ");
buf.append("<input type=\"text\" size=\"10\" onclick=\"this.select();\" ");
buf.append("value=\"").append(dest.toBase64()).append("\" />\n");
long val = new Random().nextLong();
if (val < 0) val = 0 - val;
buf.append("<br />You can <a href=\"http://temp").append(val);
buf.append(".i2p/?i2paddresshelper=").append(dest.toBase64()).append("\">view</a>");
buf.append(" it in a browser (only when you're using the eepProxy)\n");
buf.append("<br />If you are going to share this on IRC, you need to split it up:<br />\n");
String str = dest.toBase64();
buf.append(str.substring(0, str.length()/2)).append("<br />\n");
buf.append(str.substring(str.length()/2)).append("<br />\n");
buf.append("You can also post it to <a href=\"http://forum.i2p/viewforum.php?f=16\">Eepsite announcement forum</a><br />");
}
}
}

View File

@ -1,13 +1,10 @@
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
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 +17,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 +42,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 +94,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 +162,6 @@ public class TunnelControllerGroup {
controller.stopTunnel();
msgs.addAll(controller.clearMessages());
}
if (_log.shouldLog(Log.INFO))
_log.info(_controllers.size() + " controllers stopped");
return msgs;
@ -158,10 +176,10 @@ public class TunnelControllerGroup {
List msgs = new ArrayList();
for (int i = 0; i < _controllers.size(); i++) {
TunnelController controller = (TunnelController)_controllers.get(i);
controller.startTunnel();
controller.startTunnelBackground();
msgs.addAll(controller.clearMessages());
}
if (_log.shouldLog(Log.INFO))
_log.info(_controllers.size() + " controllers started");
return msgs;
@ -259,33 +277,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

@ -1,435 +0,0 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Quick and dirty socket listener to control an I2PTunnel.
* Basically run this class as TunnelManager [listenHost] [listenPort] and
* then send it commands on that port. Commands are one shot deals -
* Send a command + newline, get a response plus newline, then get disconnected.
* <p />
* <b>Implemented commands:</b>
* <pre>
* -------------------------------------------------
* lookup &lt;name&gt;\n
* --
* &lt;base64 of the destination&gt;\n
* or
* &lt;error message, usually 'Unknown host'&gt;\n
*
* Lookup the public key of a named destination (i.e. listed in hosts.txt)
* -------------------------------------------------
* genkey\n
* --
* &lt;base64 of the destination&gt;\t&lt;base64 of private data&gt;\n
*
* Generates a new public and private key pair
* -------------------------------------------------
* convertprivate &lt;base64 of privkey&gt;
* --
* &lt;base64 of destination&gt;\n
* or
* &lt;error message&gt;\n
*
* Returns the destination (pubkey) of a given private key.
* -------------------------------------------------
* listen_on &lt;ip&gt;\n
* --
* ok\n
* or
* error\n
*
* Sets the ip address clients will listen on. By default this is the
* localhost (127.0.0.1)
* -------------------------------------------------
* openclient &lt;listenPort&gt; &lt;peer&gt;\n
* --
* ok [&lt;jobId&gt;]\n
* or
* ok &lt;listenPort&gt; [&lt;jobId&gt;]\n
* or
* error\n
*
* Open a tunnel on the given &lt;listenport&gt; to the destination specified
* by &lt;peer&gt;. If &lt;listenPort&gt; is 0 a free port is picked and returned in
* the reply message. Otherwise the short reply message is used.
* Peer can be the base64 of the destination, a file with the public key
* specified as 'file:&lt;filename&gt;' or the name of a destination listed in
* hosts.txt. The &lt;jobId&gt; returned together with "ok" and &lt;listenport&gt; can
* later be used as argument for the "close" command.
* -------------------------------------------------
* openhttpclient &lt;listenPort&gt; [&lt;proxy&gt;]\n
* --
* ok [&lt;jobId&gt;]\n
* or
* ok &lt;listenPort&gt; [&lt;jobId&gt;]\n
* or
* error\n
*
* Open an HTTP proxy through the I2P on the given
* &lt;listenport&gt;. &lt;proxy&gt; (optional) specifies a
* destination to be used as an outbound proxy, to access normal WWW
* sites out of the .i2p domain. If &lt;listenPort&gt; is 0 a free
* port is picked and returned in the reply message. Otherwise the
* short reply message is used. &lt;proxy&gt; can be the base64 of the
* destination, a file with the public key specified as
* 'file:&lt;filename&gt;' or the name of a destination listed in
* hosts.txt. The &lt;jobId&gt; returned together with "ok" and
* &lt;listenport&gt; can later be used as argument for the "close"
* command.
* -------------------------------------------------
* opensockstunnel &lt;listenPort&gt;\n
* --
* ok [&lt;jobId&gt;]\n
* or
* ok &lt;listenPort&gt; [&lt;jobId&gt;]\n
* or
* error\n
*
* Open an SOCKS tunnel through the I2P on the given
* &lt;listenport&gt;. If &lt;listenPort&gt; is 0 a free port is
* picked and returned in the reply message. Otherwise the short
* reply message is used. The &lt;jobId&gt; returned together with
* "ok" and &lt;listenport&gt; can later be used as argument for the
* "close" command.
* -------------------------------------------------
* openserver &lt;serverHost&gt; &lt;serverPort&gt; &lt;serverKeys&gt;\n
* --
* ok [&lt;jobId&gt;]\n
* or
* error\n
*
* Starts receiving traffic for the destination specified by &lt;serverKeys&gt;
* and forwards it to the &lt;serverPort&gt; of &lt;serverHost&gt;.
* &lt;serverKeys&gt; is the base 64 encoded private key set of the local
* destination. The &lt;joId&gt; returned together with "ok" can later be used
* as argument for the "close" command.
* -------------------------------------------------
* close [forced] &lt;jobId&gt;\n
* or
* close [forced] all\n
* --
* ok\n
* or
* error\n
*
* Closes the job specified by &lt;jobId&gt; or all jobs. Use the list command
* for a list of running jobs.
* Normally a connection job is not closed when it still has an active
* connection. Use the optional 'forced' keyword to close connections
* regardless of their use.
* -------------------------------------------------
* list\n
* --
* Example output:
*
* [0] i2p.dnsalias.net/69.55.226.145:5555 &lt;- C:\i2pKeys\squidPriv
* [1] 8767 -&gt; HTTPClient
* [2] 7575 -&gt; file:C:\i2pKeys\squidPub
* [3] 5252 -&gt; sCcSANIO~f4AQtCNI1BvDp3ZBS~9Ag5O0k0Msm7XBWWz5eOnZWL3MQ-2rxlesucb9XnpASGhWzyYNBpWAfaIB3pux1J1xujQLOwscMIhm7T8BP76Ly5jx6BLZCYrrPj0BI0uV90XJyT~4UyQgUlC1jzFQdZ9HDgBPJDf1UI4-YjIwEHuJgdZynYlQ1oUFhgno~HhcDByXO~PDaO~1JDMDbBEfIh~v6MgmHp-Xchod1OfKFrxFrzHgcJbn7E8edTFjZA6JCi~DtFxFelQz1lSBd-QB1qJnA0g-pVL5qngNUojXJCXs4qWcQ7ICLpvIc-Fpfj-0F1gkVlGDSGkb1yLH3~8p4czYgR3W5D7OpwXzezz6clpV8kmbd~x2SotdWsXBPRhqpewO38coU4dJG3OEUbuYmdN~nJMfWbmlcM1lXzz2vBsys4sZzW6dV3hZnbvbfxNTqbdqOh-KXi1iAzXv7CVTun0ubw~CfeGpcAqutC5loRUq7Mq62ngOukyv8Z9AAAA
*
* Lists descriptions of all running jobs. The exact format of the
* description depends on the type of job.
* -------------------------------------------------
* </pre>
*/
public class TunnelManager implements Runnable {
private final static Log _log = new Log(TunnelManager.class);
private I2PTunnel _tunnel;
private ServerSocket _socket;
private boolean _keepAccepting;
public TunnelManager(int listenPort) {
this(null, listenPort);
}
public TunnelManager(String listenHost, int listenPort) {
_tunnel = new I2PTunnel();
_keepAccepting = true;
try {
if (listenHost != null) {
_socket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
_log.info("Listening for tunnel management clients on " + listenHost + ":" + listenPort);
} else {
_socket = new ServerSocket(listenPort);
_log.info("Listening for tunnel management clients on localhost:" + listenPort);
}
} catch (Exception e) {
_log.error("Error starting up tunnel management listener on " + listenPort, e);
}
}
public static void main(String args[]) {
int port = 7676;
String host = null;
if (args.length == 1) {
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
_log.error("Usage: TunnelManager [host] [port]");
return;
}
} else if (args.length == 2) {
host = args[0];
try {
port = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
_log.error("Usage: TunnelManager [host] [port]");
return;
}
}
TunnelManager mgr = new TunnelManager(host, port);
Thread t = new I2PThread(mgr, "Listener");
t.start();
}
public void run() {
if (_socket == null) {
_log.error("Unable to start listening, since the socket was not bound. Already running?");
return;
}
_log.debug("Running");
try {
while (_keepAccepting) {
Socket socket = _socket.accept();
_log.debug("Client accepted");
if (socket != null) {
Thread t = new I2PThread(new TunnelManagerClientRunner(this, socket));
t.setName("TunnelManager Client");
t.setPriority(I2PThread.MIN_PRIORITY);
t.start();
}
}
} catch (IOException ioe) {
_log.error("Error accepting connections", ioe);
} catch (Exception e) {
_log.error("Other error?!", e);
} finally {
if (_socket != null) try {
_socket.close();
} catch (IOException ioe) {
}
}
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
}
}
public void error(String msg, OutputStream out) throws IOException {
out.write(msg.getBytes());
out.write('\n');
}
public void processQuit(OutputStream out) throws IOException {
out.write("Nice try".getBytes());
out.write('\n');
}
public void processList(OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
long startCommand = Clock.getInstance().now();
_tunnel.runCommand("list", buf);
Object obj = _tunnel.waitEventValue("listDone");
long endCommand = Clock.getInstance().now();
String str = buf.getBuffer();
_log.debug("ListDone complete after " + (endCommand - startCommand) + "ms: [" + str + "]");
out.write(str.getBytes());
out.write('\n');
buf.ignoreFurtherActions();
}
public void processListenOn(String ip, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("listen_on " + ip, buf);
String status = (String) _tunnel.waitEventValue("listen_onResult");
out.write((status + "\n").getBytes());
buf.ignoreFurtherActions();
}
/**
* "lookup <name>" returns with the result in base64, else "Unknown host" [or something like that],
* then a newline.
*
*/
public void processLookup(String name, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("lookup " + name, buf);
String rv = (String) _tunnel.waitEventValue("lookupResult");
out.write(rv.getBytes());
out.write('\n');
buf.ignoreFurtherActions();
}
public void processTestDestination(String destKey, OutputStream out) throws IOException {
try {
Destination d = new Destination();
d.fromBase64(destKey);
out.write("valid\n".getBytes());
} catch (DataFormatException dfe) {
out.write("invalid\n".getBytes());
}
out.flush();
}
public void processConvertPrivate(String priv, OutputStream out) throws IOException {
try {
Destination dest = new Destination();
dest.fromBase64(priv);
String str = dest.toBase64();
out.write(str.getBytes());
out.write('\n');
} catch (DataFormatException dfe) {
_log.error("Error converting private data", dfe);
out.write("Error converting private key\n".getBytes());
}
}
public void processClose(String which, boolean forced, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand((forced ? "close forced " : "close ") + which, buf);
String str = (String) _tunnel.waitEventValue("closeResult");
out.write((str + "\n").getBytes());
buf.ignoreFurtherActions();
}
/**
* "genkey" returns with the base64 of the destination, followed by a tab, then the base64 of that
* destination's private keys, then a newline.
*
*/
public void processGenKey(OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("gentextkeys", buf);
String priv = (String) _tunnel.waitEventValue("privateKey");
String pub = (String) _tunnel.waitEventValue("publicDestination");
out.write((pub + "\t" + priv).getBytes());
out.write('\n');
buf.ignoreFurtherActions();
}
public void processOpenClient(int listenPort, String peer, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("client " + listenPort + " " + peer, buf);
Integer taskId = (Integer) _tunnel.waitEventValue("clientTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String) _tunnel.waitEventValue("openClientResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
if (listenPort != 0) {
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
return;
}
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
}
public void processOpenHTTPClient(int listenPort, String proxy, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("httpclient " + listenPort + " " + proxy, buf);
Integer taskId = (Integer) _tunnel.waitEventValue("httpclientTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String) _tunnel.waitEventValue("openHTTPClientResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
if (listenPort != 0) {
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
return;
}
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
}
public void processOpenSOCKSTunnel(int listenPort, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("sockstunnel " + listenPort, buf);
Integer taskId = (Integer) _tunnel.waitEventValue("sockstunnelTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String) _tunnel.waitEventValue("openSOCKSTunnelResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
if (listenPort != 0) {
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
return;
}
Integer port = (Integer) _tunnel.waitEventValue("clientLocalPort");
out.write((rv + " " + port.intValue() + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
}
public void processOpenServer(String serverHost, int serverPort, String privateKeys, OutputStream out)
throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("textserver " + serverHost + " " + serverPort + " " + privateKeys, buf);
Integer taskId = (Integer) _tunnel.waitEventValue("serverTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String) _tunnel.waitEventValue("openServerResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
}
/**
* Frisbee.
*
*/
public void unknownCommand(String command, OutputStream out) throws IOException {
out.write("Unknown command: ".getBytes());
out.write(command.getBytes());
out.write("\n".getBytes());
}
}

View File

@ -1,194 +0,0 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.StringTokenizer;
import net.i2p.util.Log;
/**
* Runner thread that reads commands from the socket and fires off commands to
* the TunnelManager
*
*/
class TunnelManagerClientRunner implements Runnable {
private final static Log _log = new Log(TunnelManagerClientRunner.class);
private TunnelManager _mgr;
private Socket _clientSocket;
public TunnelManagerClientRunner(TunnelManager mgr, Socket socket) {
_clientSocket = socket;
_mgr = mgr;
}
public void run() {
_log.debug("Client running");
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(_clientSocket.getInputStream()));
OutputStream out = _clientSocket.getOutputStream();
String cmd = reader.readLine();
if (cmd != null) processCommand(cmd, out);
} catch (IOException ioe) {
_log.error("Error processing client commands", ioe);
} finally {
if (_clientSocket != null) try {
_clientSocket.close();
} catch (IOException ioe) {
}
}
_log.debug("Client closed");
}
/**
* Parse the command string and fire off the appropriate tunnelManager method,
* sending the results to the output stream
*/
private void processCommand(String command, OutputStream out) throws IOException {
_log.debug("Processing [" + command + "]");
StringTokenizer tok = new StringTokenizer(command);
if (!tok.hasMoreTokens()) {
_mgr.unknownCommand(command, out);
} else {
String cmd = tok.nextToken();
if ("quit".equalsIgnoreCase(cmd)) {
_mgr.processQuit(out);
} else if ("lookup".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens())
_mgr.processLookup(tok.nextToken(), out);
else
_mgr.error("Usage: lookup <hostname>", out);
} else if ("testdestination".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens())
_mgr.processTestDestination(tok.nextToken(), out);
else
_mgr.error("Usage: testdestination <publicDestination>", out);
} else if ("convertprivate".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens())
_mgr.processConvertPrivate(tok.nextToken(), out);
else
_mgr.error("Usage: convertprivate <privateData>", out);
} else if ("close".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens()) {
String closeArg;
if ((closeArg = tok.nextToken()).equals("forced")) {
if (tok.hasMoreTokens()) {
_mgr.processClose(tok.nextToken(), true, out);
} else {
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
}
} else {
_mgr.processClose(closeArg, false, out);
}
} else {
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
}
} else if ("genkey".equalsIgnoreCase(cmd)) {
_mgr.processGenKey(out);
} else if ("list".equalsIgnoreCase(cmd)) {
_mgr.processList(out);
} else if ("listen_on".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens()) {
_mgr.processListenOn(tok.nextToken(), out);
} else {
_mgr.error("Usage: listen_on <ip>", out);
}
} else if ("openclient".equalsIgnoreCase(cmd)) {
int listenPort = 0;
String peer = null;
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openclient <listenPort> <peer>", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openclient <listenport> <peer>", out);
return;
}
peer = tok.nextToken();
_mgr.processOpenClient(listenPort, peer, out);
} else if ("openhttpclient".equalsIgnoreCase(cmd)) {
int listenPort = 0;
String proxy = "squid.i2p";
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openhttpclient <listenPort> [<proxy>]", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (tok.hasMoreTokens()) {
proxy = tok.nextToken();
}
if (tok.hasMoreTokens()) {
_mgr.error("Usage: openclient <listenport> [<proxy>]", out);
return;
}
_mgr.processOpenHTTPClient(listenPort, proxy, out);
} else if ("opensockstunnel".equalsIgnoreCase(cmd)) {
int listenPort = 0;
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: opensockstunnel <listenPort>", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (tok.hasMoreTokens()) {
_mgr.error("Usage: opensockstunnel <listenport>", out);
return;
}
_mgr.processOpenSOCKSTunnel(listenPort, out);
} else if ("openserver".equalsIgnoreCase(cmd)) {
int listenPort = 0;
String serverHost = null;
String serverKeys = null;
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
return;
}
serverHost = tok.nextToken();
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
return;
}
serverKeys = tok.nextToken();
_mgr.processOpenServer(serverHost, listenPort, serverKeys, out);
} else {
_mgr.unknownCommand(command, out);
}
}
}
}

View File

@ -1,313 +0,0 @@
package net.i2p.i2ptunnel;
import java.io.File;
import java.util.Iterator;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
/**
* Uuuugly... generate the edit/add forms for the various
* I2PTunnel types (httpclient/client/server)
*
*/
class WebEditPageFormGenerator {
private static final String SELECT_TYPE_FORM =
"<form action=\"edit.jsp\"> Type of tunnel: <select name=\"type\">" +
"<option value=\"httpclient\">HTTP proxy</option>" +
"<option value=\"client\">Client tunnel</option>" +
"<option value=\"server\">Server tunnel</option>" +
"</select> <input type=\"submit\" value=\"GO\" />" +
"</form>\n";
/**
* Retrieve the form requested
*
*/
public static String getForm(WebEditPageHelper helper) {
TunnelController controller = helper.getTunnelController();
if ( (helper.getType() == null) && (controller == null) )
return SELECT_TYPE_FORM;
String id = helper.getNum();
String type = helper.getType();
if (controller != null)
type = controller.getType();
if ("httpclient".equals(type))
return getEditHttpClientForm(controller, id);
else if ("client".equals(type))
return getEditClientForm(controller, id);
else if ("server".equals(type))
return getEditServerForm(controller, id);
else
return "WTF, unknown type [" + type + "]";
}
private static String getEditHttpClientForm(TunnelController controller, String id) {
StringBuffer buf = new StringBuffer(1024);
addGeneral(buf, controller, id);
buf.append("<b>Type:</b> <i>HTTP proxy</i><input type=\"hidden\" name=\"type\" value=\"httpclient\" /><br />\n");
addListeningOn(buf, controller, 4444);
buf.append("<b>Outproxies:</b> <input type=\"text\" name=\"proxyList\" size=\"20\" ");
if ( (controller != null) && (controller.getProxyList() != null) )
buf.append("value=\"").append(controller.getProxyList()).append("\" ");
else
buf.append("value=\"squid.i2p\" ");
buf.append("/><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");
buf.append(" <i>confirm</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
buf.append("</form>\n");
return buf.toString();
}
private static String getEditClientForm(TunnelController controller, String id) {
StringBuffer buf = new StringBuffer(1024);
addGeneral(buf, controller, id);
buf.append("<b>Type:</b> <i>Client tunnel</i><input type=\"hidden\" name=\"type\" value=\"client\" /><br />\n");
addListeningOn(buf, controller, 2025 + new Random().nextInt(1000)); // 2025 since nextInt can be negative
buf.append("<b>Target:</b> <input type=\"text\" size=\"40\" name=\"targetDestination\" ");
if ( (controller != null) && (controller.getTargetDestination() != null) )
buf.append("value=\"").append(controller.getTargetDestination()).append("\" ");
buf.append(" /> (either the hosts.txt name or the full base64 destination)<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");
buf.append(" <i>confirm</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
buf.append("</form>\n");
return buf.toString();
}
private static String getEditServerForm(TunnelController controller, String id) {
StringBuffer buf = new StringBuffer(1024);
addGeneral(buf, controller, id);
buf.append("<b>Type:</b> <i>Server tunnel</i><input type=\"hidden\" name=\"type\" value=\"server\" /><br />\n");
buf.append("<b>Target host:</b> <input type=\"text\" size=\"40\" name=\"targetHost\" ");
if ( (controller != null) && (controller.getTargetHost() != null) )
buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
else
buf.append("value=\"localhost\" ");
buf.append(" /><br />\n");
buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
if ( (controller != null) && (controller.getTargetPort() != null) )
buf.append("value=\"").append(controller.getTargetPort()).append("\" ");
else
buf.append("value=\"80\" ");
buf.append(" /><br />\n");
buf.append("<b>Private key file:</b> <input type=\"text\" name=\"privKeyFile\" value=\"");
if ( (controller != null) && (controller.getPrivKeyFile() != null) ) {
buf.append(controller.getPrivKeyFile()).append("\" /><br />");
} else {
buf.append("myServer.privKey\" /><br />");
buf.append("<input type=\"hidden\" name=\"privKeyGenerate\" value=\"true\" />");
}
addOptions(buf, controller);
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
buf.append(" <i>confirm</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
buf.append("</form>\n");
return buf.toString();
}
/**
* Start off the form and add some common fields (name, num, description)
*
* @param buf where to shove the form
* @param controller tunnel in question, or null if we're creating a new tunnel
* @param id index into the current list of tunnelControllerGroup.getControllers() list
* (or null if we are generating an 'add' form)
*/
private static void addGeneral(StringBuffer buf, TunnelController controller, String id) {
buf.append("<form action=\"edit.jsp\">");
if (id != null)
buf.append("<input type=\"hidden\" name=\"num\" value=\"").append(id).append("\" />");
buf.append("<b>Name:</b> <input type=\"text\" name=\"name\" size=\"20\" ");
if ( (controller != null) && (controller.getName() != null) )
buf.append("value=\"").append(controller.getName()).append("\" ");
buf.append("/><br />\n");
buf.append("<b>Description:</b> <input type=\"text\" name=\"description\" size=\"60\" ");
if ( (controller != null) && (controller.getDescription() != null) )
buf.append("value=\"").append(controller.getDescription()).append("\" ");
buf.append("/><br />\n");
}
/**
* Generate the fields asking for what port and interface the tunnel should
* listen on.
*
* @param buf where to shove the form
* @param controller tunnel in question, or null if we're creating a new tunnel
* @param defaultPort if we are creating a new tunnel, default the form to the given port
*/
private static void addListeningOn(StringBuffer buf, TunnelController controller, int defaultPort) {
buf.append("<b>Listening on port:</b> <input type=\"text\" name=\"port\" size=\"20\" ");
if ( (controller != null) && (controller.getListenPort() != null) )
buf.append("value=\"").append(controller.getListenPort()).append("\" ");
else
buf.append("value=\"").append(defaultPort).append("\" ");
buf.append("/><br />\n");
String selectedOn = null;
if ( (controller != null) && (controller.getListenOnInterface() != null) )
selectedOn = controller.getListenOnInterface();
buf.append("<b>Reachable by:</b> ");
buf.append("<select name=\"reachableBy\">");
buf.append("<option value=\"127.0.0.1\" ");
if ( (selectedOn != null) && ("127.0.0.1".equals(selectedOn)) )
buf.append("selected=\"true\" ");
buf.append(">Locally (127.0.0.1)</option>\n");
buf.append("<option value=\"0.0.0.0\" ");
if ( (selectedOn != null) && ("0.0.0.0".equals(selectedOn)) )
buf.append("selected=\"true\" ");
buf.append(">Everyone (0.0.0.0)</option>\n");
buf.append("</select> ");
buf.append("Other: <input type=\"text\" name=\"reachableByOther\" value=\"");
if ( (selectedOn != null) && (!"127.0.0.1".equals(selectedOn)) && (!"0.0.0.0".equals(selectedOn)) )
buf.append(selectedOn);
buf.append("\"><br />\n");
}
/**
* Add fields for customizing the I2PSession options, including helpers for
* tunnel depth and count, as well as I2CP host and port.
*
* @param buf where to shove the form
* @param controller tunnel in question, or null if we're creating a new tunnel
*/
private static void addOptions(StringBuffer buf, TunnelController controller) {
int tunnelDepth = 2;
int numTunnels = 2;
Properties opts = getOptions(controller);
if (opts != null) {
String depth = opts.getProperty("tunnels.depthInbound");
if (depth != null) {
try {
tunnelDepth = Integer.parseInt(depth);
} catch (NumberFormatException nfe) {
tunnelDepth = 2;
}
}
String num = opts.getProperty("tunnels.numInbound");
if (num != null) {
try {
numTunnels = Integer.parseInt(num);
} catch (NumberFormatException nfe) {
numTunnels = 2;
}
}
}
buf.append("<b>Tunnel depth:</b> ");
buf.append("<select name=\"tunnelDepth\">");
buf.append("<option value=\"0\" ");
if (tunnelDepth == 0) buf.append(" selected=\"true\" ");
buf.append(">0 hop tunnel (low anonymity, low latency)</option>");
buf.append("<option value=\"1\" ");
if (tunnelDepth == 1) buf.append(" selected=\"true\" ");
buf.append(">1 hop tunnel (medium anonymity, medium latency)</option>");
buf.append("<option value=\"2\" ");
if (tunnelDepth == 2) buf.append(" selected=\"true\" ");
buf.append(">2 hop tunnel (high anonymity, high latency)</option>");
if (tunnelDepth > 2) {
buf.append("<option value=\"").append(tunnelDepth).append("\" selected=\"true\" >");
buf.append(tunnelDepth);
buf.append(" hop tunnel (custom)</option>");
}
buf.append("</select><br />\n");
buf.append("<b>Tunnel count:</b> ");
buf.append("<select name=\"tunnelCount\">");
buf.append("<option value=\"1\" ");
if (numTunnels == 1) buf.append(" selected=\"true\" ");
buf.append(">1 inbound tunnel (low bandwidth usage, less reliability)</option>");
buf.append("<option value=\"2\" ");
if (numTunnels == 2) buf.append(" selected=\"true\" ");
buf.append(">2 inbound tunnels (standard bandwidth usage, standard reliability)</option>");
buf.append("<option value=\"3\" ");
if (numTunnels == 3) buf.append(" selected=\"true\" ");
buf.append(">3 inbound tunnels (higher bandwidth usage, higher reliability)</option>");
if (numTunnels > 3) {
buf.append("<option value=\"").append(numTunnels).append("\" selected=\"true\" >");
buf.append(numTunnels);
buf.append(" inbound tunnels (custom)</option>");
}
buf.append("</select><br />\n");
buf.append("<b>I2CP host:</b> ");
buf.append("<input type=\"text\" name=\"clientHost\" size=\"20\" value=\"");
if ( (controller != null) && (controller.getI2CPHost() != null) )
buf.append(controller.getI2CPHost());
else
buf.append("localhost");
buf.append("\" /><br />\n");
buf.append("<b>I2CP port:</b> ");
buf.append("<input type=\"text\" name=\"clientPort\" size=\"20\" value=\"");
if ( (controller != null) && (controller.getI2CPPort() != null) )
buf.append(controller.getI2CPPort());
else
buf.append("7654");
buf.append("\" /><br />\n");
buf.append("<b>Other custom options:</b> \n");
buf.append("<input type=\"text\" name=\"customOptions\" size=\"60\" value=\"");
if (opts != null) {
int i = 0;
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = opts.getProperty(key);
if ("tunnels.depthInbound".equals(key)) continue;
if ("tunnels.numInbound".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");
}
/**
* Retrieve the client options from the tunnel
*
* @return map of name=val to be used as I2P session options
*/
private static Properties getOptions(TunnelController controller) {
if (controller == null) return null;
String opts = controller.getClientOptions();
StringTokenizer tok = new StringTokenizer(opts);
Properties props = new Properties();
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int eq = pair.indexOf('=');
if ( (eq <= 0) || (eq >= pair.length()) )
continue;
String key = pair.substring(0, eq);
String val = pair.substring(eq+1);
props.setProperty(key, val);
}
return props;
}
}

View File

@ -1,358 +0,0 @@
package net.i2p.i2ptunnel;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* UUUUuuuuuugly glue code to handle bean interaction from the web, process
* that data, and spit out the results (or the form requested). The basic
* usage is to set any of the fields with data then query the bean via
* getActionResults() which triggers the request processing (taking all the
* provided data, doing what needs to be done) and returns the results of those
* activites. Then a subsequent call to getEditForm() generates the HTML form
* to either edit the currently selected tunnel (if specified) or add a new one.
* This functionality is delegated to the WebEditPageFormGenerator.
*
*/
public class WebEditPageHelper {
private Log _log;
private String _action;
private String _type;
private String _id;
private String _name;
private String _description;
private String _i2cpHost;
private String _i2cpPort;
private String _tunnelDepth;
private String _tunnelCount;
private String _customOptions;
private String _proxyList;
private String _port;
private String _reachableBy;
private String _reachableByOther;
private String _targetDestination;
private String _targetHost;
private String _targetPort;
private String _privKeyFile;
private boolean _startOnLoad;
private boolean _privKeyGenerate;
private boolean _removeConfirmed;
public WebEditPageHelper() {
_action = null;
_type = null;
_id = null;
_removeConfirmed = false;
_log = I2PAppContext.getGlobalContext().logManager().getLog(WebEditPageHelper.class);
}
/**
* Used for form submit - either "Save" or Remove"
*/
public void setAction(String action) {
_action = (action != null ? action.trim() : null);
}
/**
* What type of tunnel (httpclient, client, or server). This is
* required when adding a new tunnel.
*
*/
public void setType(String type) {
_type = (type != null ? type.trim() : null);
}
/**
* Which particular tunnel should be edited (index into the current
* TunnelControllerGroup's getControllers() list). This is required
* when editing a tunnel, but not when adding a new one.
*
*/
public void setNum(String id) {
_id = (id != null ? id.trim() : null);
}
String getType() { return _type; }
String getNum() { return _id; }
/** Short name of the tunnel */
public void setName(String name) {
_name = (name != null ? name.trim() : null);
}
/** one line description */
public void setDescription(String description) {
_description = (description != null ? description.trim() : null);
}
/** I2CP host the router is on */
public void setClientHost(String host) {
_i2cpHost = (host != null ? host.trim() : null);
}
/** I2CP port the router is on */
public void setClientPort(String port) {
_i2cpPort = (port != null ? port.trim() : null);
}
/** how many hops to use for inbound tunnels */
public void setTunnelDepth(String tunnelDepth) {
_tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
}
/** how many parallel inbound tunnels to use */
public void setTunnelCount(String tunnelCount) {
_tunnelCount = (tunnelCount != null ? tunnelCount.trim() : null);
}
/** what I2P session overrides should be used */
public void setCustomOptions(String customOptions) {
_customOptions = (customOptions != null ? customOptions.trim() : null);
}
/** what HTTP outproxies should be used (httpclient specific) */
public void setProxyList(String proxyList) {
_proxyList = (proxyList != null ? proxyList.trim() : null);
}
/** what port should this client/httpclient listen on */
public void setPort(String port) {
_port = (port != null ? port.trim() : null);
}
/**
* what interface should this client/httpclient listen on (unless
* overridden by the setReachableByOther() field)
*/
public void setReachableBy(String reachableBy) {
_reachableBy = (reachableBy != null ? reachableBy.trim() : null);
}
/**
* If specified, defines the exact IP interface to listen for requests
* on (in the case of client/httpclient tunnels)
*/
public void setReachableByOther(String reachableByOther) {
_reachableByOther = (reachableByOther != null ? reachableByOther.trim() : null);
}
/** What peer does this client tunnel point at */
public void setTargetDestination(String dest) {
_targetDestination = (dest != null ? dest.trim() : null);
}
/** What host does this server tunnel point at */
public void setTargetHost(String host) {
_targetHost = (host != null ? host.trim() : null);
}
/** What port does this server tunnel point at */
public void setTargetPort(String port) {
_targetPort = (port != null ? port.trim() : null);
}
/** What filename is this server tunnel's private keys stored in */
public void setPrivKeyFile(String file) {
_privKeyFile = (file != null ? file.trim() : null);
}
/**
* If called with any value, we want to generate a new destination
* for this server tunnel. This won't cause any existing private keys
* to be overwritten, however.
*/
public void setPrivKeyGenerate(String moo) {
_privKeyGenerate = true;
}
/**
* If called with any value (and the form submitted with action=Remove),
* we really do want to stop and remove the tunnel.
*/
public void setRemoveConfirm(String moo) {
_removeConfirmed = true;
}
/**
* If called with any value, we want this tunnel to start whenever it is
* loaded (aka right now and whenever the router is started up)
*/
public void setStartOnLoad(String moo) {
_startOnLoad = true;
}
/**
* Process the form and display any resulting messages
*
*/
public String getActionResults() {
try {
return processAction();
} catch (Throwable t) {
_log.log(Log.CRIT, "Internal error processing request", t);
return "Internal error - " + t.getMessage();
}
}
/**
* Generate an HTML form to edit / create a tunnel according to the
* specified fields
*/
public String getEditForm() {
try {
return WebEditPageFormGenerator.getForm(this);
} catch (Throwable t) {
_log.log(Log.CRIT, "Internal error retrieving edit form", t);
return "Internal error - " + t.getMessage();
}
}
/**
* Retrieve the tunnel pointed to by the current id
*
*/
TunnelController getTunnelController() {
if (_id == null) return null;
int id = -1;
try {
id = Integer.parseInt(_id);
List controllers = TunnelControllerGroup.getInstance().getControllers();
if ( (id < 0) || (id >= controllers.size()) )
return null;
else
return (TunnelController)controllers.get(id);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid tunnel id [" + _id + "]", nfe);
return null;
}
}
private String processAction() {
if ( (_action == null) || (_action.trim().length() <= 0) )
return "";
if ("Save".equals(_action))
return save();
else if ("Remove".equals(_action))
return remove();
else
return "Action <i>" + _action + "</i> unknown";
}
private String remove() {
if (!_removeConfirmed)
return "Please confirm removal";
TunnelController cur = getTunnelController();
if (cur == null)
return "Invalid tunnel number";
List msgs = TunnelControllerGroup.getInstance().removeController(cur);
msgs.addAll(doSave());
return getMessages(msgs);
}
private String save() {
if (_type == null)
return "<b>Invalid form submission (no type?)</b>";
Properties config = getConfig();
if (config == null)
return "<b>Invalid params</b>";
TunnelController cur = getTunnelController();
if (cur == null) {
// creating new
cur = new TunnelController(config, "", _privKeyGenerate);
TunnelControllerGroup.getInstance().addController(cur);
} else {
cur.setConfig(config, "");
}
return getMessages(doSave());
}
private List doSave() {
TunnelControllerGroup.getInstance().saveConfig();
return TunnelControllerGroup.getInstance().clearAllMessages();
}
/**
* Based on all provided data, create a set of configuration parameters
* suitable for use in a TunnelController. This will replace (not add to)
* any existing parameters, so this should return a comprehensive mapping.
*
*/
private Properties getConfig() {
Properties config = new Properties();
updateConfigGeneric(config);
if ("httpclient".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
if (_reachableByOther != null)
config.setProperty("interface", _reachableByOther);
else
config.setProperty("interface", _reachableBy);
if (_proxyList != null)
config.setProperty("proxyList", _proxyList);
} else if ("client".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
if (_reachableByOther != null)
config.setProperty("interface", _reachableByOther);
else
config.setProperty("interface", _reachableBy);
if (_targetDestination != null)
config.setProperty("targetDestination", _targetDestination);
} else if ("server".equals(_type)) {
if (_targetHost != null)
config.setProperty("targetHost", _targetHost);
if (_targetPort != null)
config.setProperty("targetPort", _targetPort);
if (_privKeyFile != null)
config.setProperty("privKeyFile", _privKeyFile);
} else {
return null;
}
return config;
}
private void updateConfigGeneric(Properties config) {
config.setProperty("type", _type);
if (_name != null)
config.setProperty("name", _name);
if (_description != null)
config.setProperty("description", _description);
if (_i2cpHost != null)
config.setProperty("i2cpHost", _i2cpHost);
if (_i2cpPort != null)
config.setProperty("i2cpPort", _i2cpPort);
if (_customOptions != null) {
StringTokenizer tok = new StringTokenizer(_customOptions);
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int eq = pair.indexOf('=');
if ( (eq <= 0) || (eq >= pair.length()) )
continue;
String key = pair.substring(0, eq);
String val = pair.substring(eq+1);
if ("tunnels.numInbound".equals(key)) continue;
if ("tunnels.depthInbound".equals(key)) continue;
config.setProperty("option." + key, val);
}
}
config.setProperty("startOnLoad", _startOnLoad + "");
if (_tunnelCount != null)
config.setProperty("option.tunnels.numInbound", _tunnelCount);
if (_tunnelDepth != null)
config.setProperty("option.tunnels.depthInbound", _tunnelDepth);
}
/**
* Pretty print the messages provided
*
*/
private String getMessages(List msgs) {
if (msgs == null) return "";
int num = msgs.size();
switch (num) {
case 0: return "";
case 1: return (String)msgs.get(0);
default:
StringBuffer buf = new StringBuffer(512);
buf.append("<ul>");
for (int i = 0; i < num; i++)
buf.append("<li>").append((String)msgs.get(i)).append("</li>\n");
buf.append("</ul>\n");
return buf.toString();
}
}
}

View File

@ -1,181 +0,0 @@
package net.i2p.i2ptunnel;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* Ugly hack to let the web interface access the list of known tunnels and
* control their operation. Any data submitted by setting properties are
* acted upon by calling getActionResults() (which returns any messages
* generated). In addition, the getSummaryList() generates the html for
* summarizing all of the tunnels known, including both their status and the
* links to edit, stop, or start them.
*
*/
public class WebStatusPageHelper {
private Log _log;
private String _action;
private int _controllerNum;
public WebStatusPageHelper() {
_action = null;
_controllerNum = -1;
_log = I2PAppContext.getGlobalContext().logManager().getLog(WebStatusPageHelper.class);
}
public void setAction(String action) {
_action = action;
}
public void setNum(String num) {
if (num != null) {
try {
_controllerNum = Integer.parseInt(num);
} catch (NumberFormatException nfe) {
_controllerNum = -1;
}
}
}
public String getActionResults() {
try {
return processAction();
} catch (Throwable t) {
_log.log(Log.CRIT, "Internal error processing web status", t);
return "Internal error processing request - " + t.getMessage();
}
}
public String getSummaryList() {
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group == null)
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
StringBuffer buf = new StringBuffer(4*1024);
buf.append("<ul>");
List tunnels = group.getControllers();
for (int i = 0; i < tunnels.size(); i++) {
buf.append("<li>\n");
getSummary(buf, i, (TunnelController)tunnels.get(i));
buf.append("</li>\n");
}
buf.append("</ul>");
return buf.toString();
}
private void getSummary(StringBuffer buf, int num, TunnelController controller) {
buf.append("<b>").append(controller.getName()).append("</b>: ");
if (controller.getIsRunning()) {
buf.append("<i>running</i> ");
buf.append("<a href=\"index.jsp?num=").append(num).append("&action=stop\">stop</a> ");
} else {
buf.append("<i>not running</i> ");
buf.append("<a href=\"index.jsp?num=").append(num).append("&action=start\">start</a> ");
}
buf.append("<a href=\"edit.jsp?num=").append(num).append("\">edit</a> ");
buf.append("<br />\n");
controller.getSummary(buf);
}
private String processAction() {
if ( (_action == null) || (_action.trim().length() <= 0) )
return getMessages();
if ("Stop all".equals(_action))
return stopAll();
else if ("Start all".equals(_action))
return startAll();
else if ("Restart all".equals(_action))
return restartAll();
else if ("Reload config".equals(_action))
return reloadConfig();
else if ("stop".equals(_action))
return stop();
else if ("start".equals(_action))
return start();
else
return "Action <i>" + _action + "</i> unknown";
}
private String stopAll() {
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group == null)
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
List msgs = group.stopAllControllers();
return getMessages(msgs);
}
private String startAll() {
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group == null)
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
List msgs = group.startAllControllers();
return getMessages(msgs);
}
private String restartAll() {
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group == null)
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
List msgs = group.restartAllControllers();
return getMessages(msgs);
}
private String reloadConfig() {
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group == null)
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
group.reloadControllers();
return "Config reloaded";
}
private String start() {
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group == null)
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
if (_controllerNum < 0) return "Invalid tunnel";
List controllers = group.getControllers();
if (_controllerNum >= controllers.size()) return "Invalid tunnel";
TunnelController controller = (TunnelController)controllers.get(_controllerNum);
controller.startTunnel();
return getMessages(controller.clearMessages());
}
private String stop() {
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group == null)
return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
if (_controllerNum < 0) return "Invalid tunnel";
List controllers = group.getControllers();
if (_controllerNum >= controllers.size()) return "Invalid tunnel";
TunnelController controller = (TunnelController)controllers.get(_controllerNum);
controller.stopTunnel();
return getMessages(controller.clearMessages());
}
private String getMessages() {
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
if (group == null)
return "";
return getMessages(group.clearAllMessages());
}
private String getMessages(List msgs) {
if (msgs == null) return "";
int num = msgs.size();
switch (num) {
case 0: return "";
case 1: return (String)msgs.get(0);
default:
StringBuffer buf = new StringBuffer(512);
buf.append("<ul>");
for (int i = 0; i < num; i++)
buf.append("<li>").append((String)msgs.get(i)).append("</li>\n");
buf.append("</ul>\n");
return buf.toString();
}
}
}

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

@ -14,7 +14,6 @@ import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.util.Log;
@ -81,7 +80,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

@ -0,0 +1,271 @@
package net.i2p.i2ptunnel.web;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2005 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.Log;
/**
* Ugly little accessor for the edit page
*/
public class EditBean extends IndexBean {
public EditBean() { super(); }
public static boolean staticIsClient(int tunnel) {
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
List controllers = group.getControllers();
if (controllers.size() > tunnel) {
TunnelController cur = (TunnelController)controllers.get(tunnel);
if (cur == null) return false;
return ( ("client".equals(cur.getType())) ||
("httpclient".equals(cur.getType()))||
("ircclient".equals(cur.getType())));
} else {
return false;
}
}
public String getTargetHost(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getTargetHost();
else
return "";
}
public String getTargetPort(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getTargetPort();
else
return "";
}
public String getSpoofedHost(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getSpoofedHost();
else
return "";
}
public String getPrivateKeyFile(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getPrivKeyFile();
else
return "";
}
public boolean startAutomatically(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getStartOnLoad();
else
return false;
}
public boolean isSharedClient(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return "true".equalsIgnoreCase(tun.getSharedClient());
else
return true;
}
public boolean shouldDelay(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String delay = opts.getProperty("i2p.streaming.connectDelay");
if ( (delay == null) || ("0".equals(delay)) )
return false;
else
return true;
} else {
return false;
}
} else {
return false;
}
}
public boolean isInteractive(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String wsiz = opts.getProperty("i2p.streaming.maxWindowSize");
if ( (wsiz == null) || (!"1".equals(wsiz)) )
return false;
else
return true;
} else {
return false;
}
} else {
return false;
}
}
public int getTunnelDepth(int tunnel, int defaultLength) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String len = opts.getProperty("inbound.length");
if (len == null) return defaultLength;
try {
return Integer.parseInt(len);
} catch (NumberFormatException nfe) {
return defaultLength;
}
} else {
return defaultLength;
}
} else {
return defaultLength;
}
}
public int getTunnelQuantity(int tunnel, int defaultQuantity) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String len = opts.getProperty("inbound.quantity");
if (len == null) return defaultQuantity;
try {
return Integer.parseInt(len);
} catch (NumberFormatException nfe) {
return defaultQuantity;
}
} else {
return defaultQuantity;
}
} else {
return defaultQuantity;
}
}
public int getTunnelBackupQuantity(int tunnel, int defaultBackupQuantity) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String len = opts.getProperty("inbound.backupQuantity");
if (len == null) return defaultBackupQuantity;
try {
return Integer.parseInt(len);
} catch (NumberFormatException nfe) {
return defaultBackupQuantity;
}
} else {
return defaultBackupQuantity;
}
} else {
return defaultBackupQuantity;
}
}
public int getTunnelVariance(int tunnel, int defaultVariance) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String len = opts.getProperty("inbound.lengthVariance");
if (len == null) return defaultVariance;
try {
return Integer.parseInt(len);
} catch (NumberFormatException nfe) {
return defaultVariance;
}
} else {
return defaultVariance;
}
} else {
return defaultVariance;
}
}
public String getI2CPHost(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getI2CPHost();
else
return "localhost";
}
public String getI2CPPort(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getI2CPPort();
else
return "7654";
}
public String getCustomOptions(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts == null) return "";
StringBuffer buf = new StringBuffer(64);
int i = 0;
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = opts.getProperty(key);
if ("inbound.length".equals(key)) continue;
if ("outbound.length".equals(key)) continue;
if ("inbound.lengthVariance".equals(key)) continue;
if ("outbound.lengthVariance".equals(key)) continue;
if ("inbound.backupQuantity".equals(key)) continue;
if ("outbound.backupQuantity".equals(key)) continue;
if ("inbound.quantity".equals(key)) continue;
if ("outbound.quantity".equals(key)) continue;
if ("inbound.nickname".equals(key)) continue;
if ("outbound.nickname".equals(key)) continue;
if ("i2p.streaming.connectDelay".equals(key)) continue;
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
if (i != 0) buf.append(' ');
buf.append(key).append('=').append(val);
i++;
}
return buf.toString();
} else {
return "";
}
}
/**
* Retrieve the client options from the tunnel
*
* @return map of name=val to be used as I2P session options
*/
private static Properties getOptions(TunnelController controller) {
if (controller == null) return null;
String opts = controller.getClientOptions();
StringTokenizer tok = new StringTokenizer(opts);
Properties props = new Properties();
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int eq = pair.indexOf('=');
if ( (eq <= 0) || (eq >= pair.length()) )
continue;
String key = pair.substring(0, eq);
String val = pair.substring(eq+1);
props.setProperty(key, val);
}
return props;
}
}

View File

@ -0,0 +1,731 @@
package net.i2p.i2ptunnel.web;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2005 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.Log;
/**
* Simple accessor for exposing tunnel info, but also an ugly form handler
*
*/
public class IndexBean {
protected I2PAppContext _context;
protected Log _log;
protected TunnelControllerGroup _group;
private String _action;
private int _tunnel;
private long _prevNonce;
private long _curNonce;
private long _nextNonce;
private String _passphrase;
private String _type;
private String _name;
private String _description;
private String _i2cpHost;
private String _i2cpPort;
private String _tunnelDepth;
private String _tunnelQuantity;
private String _tunnelVariance;
private String _tunnelBackupQuantity;
private boolean _connectDelay;
private String _customOptions;
private String _proxyList;
private String _port;
private String _reachableBy;
private String _reachableByOther;
private String _targetDestination;
private String _targetHost;
private String _targetPort;
private String _spoofedHost;
private String _privKeyFile;
private String _profile;
private boolean _startOnLoad;
private boolean _sharedClient;
private boolean _privKeyGenerate;
private boolean _removeConfirmed;
public static final int RUNNING = 1;
public static final int STARTING = 2;
public static final int NOT_RUNNING = 3;
public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase";
static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
static final String CLIENT_NICKNAME = "shared clients";
public static final String PROP_THEME_NAME = "routerconsole.theme";
public static final String PROP_CSS_DISABLED = "routerconsole.css.disabled";
public static final String PROP_JS_DISABLED = "routerconsole.javascript.disabled";
public IndexBean() {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(IndexBean.class);
_group = TunnelControllerGroup.getInstance();
_action = null;
_tunnel = -1;
_curNonce = -1;
_prevNonce = -1;
try {
String nonce = System.getProperty(PROP_NONCE);
if (nonce != null)
_prevNonce = Long.parseLong(nonce);
} catch (NumberFormatException nfe) {}
_nextNonce = _context.random().nextLong();
System.setProperty(PROP_NONCE, Long.toString(_nextNonce));
}
public long getNextNonce() { return _nextNonce; }
public void setNonce(String nonce) {
if ( (nonce == null) || (nonce.trim().length() <= 0) ) return;
try {
_curNonce = Long.parseLong(nonce);
} catch (NumberFormatException nfe) {
_curNonce = -1;
}
}
public void setPassphrase(String phrase) {
_passphrase = phrase;
}
public void setAction(String action) {
if ( (action == null) || (action.trim().length() <= 0) ) return;
_action = action;
}
public void setTunnel(String tunnel) {
if ( (tunnel == null) || (tunnel.trim().length() <= 0) ) return;
try {
_tunnel = Integer.parseInt(tunnel);
} catch (NumberFormatException nfe) {
_tunnel = -1;
}
}
private boolean validPassphrase(String proposed) {
if (proposed == null) return false;
String pass = _context.getProperty(PROP_TUNNEL_PASSPHRASE);
if ( (pass != null) && (pass.trim().length() > 0) )
return pass.trim().equals(proposed.trim());
else
return false;
}
private String processAction() {
if ( (_action == null) || (_action.trim().length() <= 0) )
return "";
if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) )
return "Invalid nonce, are you being spoofed?";
if ("Stop all".equals(_action))
return stopAll();
else if ("Start all".equals(_action))
return startAll();
else if ("Restart all".equals(_action))
return restartAll();
else if ("Reload configuration".equals(_action))
return reloadConfig();
else if ("stop".equals(_action))
return stop();
else if ("start".equals(_action))
return start();
else if ("Save changes".equals(_action) || // IE workaround:
(_action.toLowerCase().indexOf("s</span>ave") >= 0))
return saveChanges();
else if ("Delete this proxy".equals(_action) || // IE workaround:
(_action.toLowerCase().indexOf("d</span>elete") >= 0))
return deleteTunnel();
else
return "Action " + _action + " unknown";
}
private String stopAll() {
if (_group == null) return "";
List msgs = _group.stopAllControllers();
return getMessages(msgs);
}
private String startAll() {
if (_group == null) return "";
List msgs = _group.startAllControllers();
return getMessages(msgs);
}
private String restartAll() {
if (_group == null) return "";
List msgs = _group.restartAllControllers();
return getMessages(msgs);
}
private String reloadConfig() {
if (_group == null) return "";
_group.reloadControllers();
return "Config reloaded";
}
private String start() {
if (_tunnel < 0) return "Invalid tunnel";
List controllers = _group.getControllers();
if (_tunnel >= controllers.size()) return "Invalid tunnel";
TunnelController controller = (TunnelController)controllers.get(_tunnel);
controller.startTunnelBackground();
return "";
}
private String stop() {
if (_tunnel < 0) return "Invalid tunnel";
List controllers = _group.getControllers();
if (_tunnel >= controllers.size()) return "Invalid tunnel";
TunnelController controller = (TunnelController)controllers.get(_tunnel);
controller.stopTunnel();
return "";
}
private String saveChanges() {
TunnelController cur = getController(_tunnel);
Properties config = getConfig();
if (config == null)
return "Invalid params";
if (cur == null) {
// creating new
cur = new TunnelController(config, "", true);
_group.addController(cur);
if (cur.getStartOnLoad())
cur.startTunnelBackground();
} else {
cur.setConfig(config, "");
}
if ("ircclient".equals(cur.getType()) ||
"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 = _group.getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelController c = (TunnelController)controllers.get(i);
if (c == cur) continue;
//only change when they really are declared of beeing a sharedClient
if (("httpclient".equals(c.getType()) ||
"ircclient".equals(c.getType())||
"client".equals(c.getType())
) && "true".equalsIgnoreCase(c.getSharedClient())) {
Properties cOpt = c.getConfig("");
if (_tunnelQuantity != null) {
cOpt.setProperty("option.inbound.quantity", _tunnelQuantity);
cOpt.setProperty("option.outbound.quantity", _tunnelQuantity);
}
if (_tunnelDepth != null) {
cOpt.setProperty("option.inbound.length", _tunnelDepth);
cOpt.setProperty("option.outbound.length", _tunnelDepth);
}
if (_tunnelVariance != null) {
cOpt.setProperty("option.inbound.lengthVariance", _tunnelVariance);
cOpt.setProperty("option.outbound.lengthVariance", _tunnelVariance);
}
if (_tunnelBackupQuantity != null) {
cOpt.setProperty("option.inbound.backupQuantity", _tunnelBackupQuantity);
cOpt.setProperty("option.outbound.backupQuantity", _tunnelBackupQuantity);
}
cOpt.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
cOpt.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
c.setConfig(cOpt, "");
}
}
}
List msgs = doSave();
msgs.add(0, "Changes saved");
return getMessages(msgs);
}
private List doSave() {
_group.saveConfig();
return _group.clearAllMessages();
}
private String deleteTunnel() {
if (!_removeConfirmed)
return "Please confirm removal";
TunnelController cur = getController(_tunnel);
if (cur == null)
return "Invalid tunnel number";
List msgs = _group.removeController(cur);
msgs.addAll(doSave());
return getMessages(msgs);
}
/**
* Executes any action requested (start/stop/etc) and dump out the
* messages.
*
*/
public String getMessages() {
if (_group == null)
return "";
StringBuffer buf = new StringBuffer(512);
if (_action != null) {
try {
buf.append(processAction()).append("\n");
} catch (Exception e) {
_log.log(Log.CRIT, "Error processing " + _action, e);
}
}
getMessages(_group.clearAllMessages(), buf);
return buf.toString();
}
////
// The remaining methods are simple bean props for the jsp to query
////
public String getTheme() {
String theme = _context.getProperty(PROP_THEME_NAME);
if (theme != null)
return "/themes/console/" + theme + "/";
else
return "/themes/console/";
}
public boolean allowCSS() {
String css = _context.getProperty(PROP_CSS_DISABLED);
return (css == null);
}
public boolean allowJS() {
String js = _context.getProperty(PROP_JS_DISABLED);
return (js == null);
}
public int getTunnelCount() {
if (_group == null) return 0;
return _group.getControllers().size();
}
public boolean isClient(int tunnelNum) {
TunnelController cur = getController(tunnelNum);
if (cur == null) return false;
return ( ("client".equals(cur.getType())) ||
("httpclient".equals(cur.getType())) ||
("ircclient".equals(cur.getType())));
}
public String getTunnelName(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getName();
else
return "";
}
public String getClientPort(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getListenPort();
else
return "";
}
public String getTunnelType(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return getTypeName(tun.getType());
else
return "";
}
public String getTypeName(String internalType) {
if ("client".equals(internalType)) return "Standard client";
else if ("httpclient".equals(internalType)) return "HTTP client";
else if ("ircclient".equals(internalType)) return "IRC client";
else if ("server".equals(internalType)) return "Standard server";
else if ("httpserver".equals(internalType)) return "HTTP server";
else return internalType;
}
public String getInternalType(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getType();
else
return "";
}
public String getClientInterface(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getListenOnInterface();
else
return "";
}
public int getTunnelStatus(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun == null) return NOT_RUNNING;
if (tun.getIsRunning()) return RUNNING;
else if (tun.getIsStarting()) return STARTING;
else return NOT_RUNNING;
}
public String getTunnelDescription(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getDescription();
else
return "";
}
public String getSharedClient(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getSharedClient();
else
return "";
}
public String getClientDestination(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun == null) return "";
if ("client".equals(tun.getType())||"ircclient".equals(tun.getType())) return tun.getTargetDestination();
else return tun.getProxyList();
}
public String getServerTarget(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getTargetHost() + ':' + tun.getTargetPort();
else
return "";
}
public String getDestinationBase64(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
String rv = tun.getMyDestination();
if (rv != null)
return rv;
else
return "";
} else {
return "";
}
}
///
/// bean props for form submission
///
/**
* What type of tunnel (httpclient, ircclient, client, or server). This is
* required when adding a new tunnel.
*
*/
public void setType(String type) {
_type = (type != null ? type.trim() : null);
}
String getType() { return _type; }
/** Short name of the tunnel */
public void setName(String name) {
_name = (name != null ? name.trim() : null);
}
/** one line description */
public void setDescription(String description) {
_description = (description != null ? description.trim() : null);
}
/** I2CP host the router is on */
public void setClientHost(String host) {
_i2cpHost = (host != null ? host.trim() : null);
}
/** I2CP port the router is on */
public void setClientPort(String port) {
_i2cpPort = (port != null ? port.trim() : null);
}
/** how many hops to use for inbound tunnels */
public void setTunnelDepth(String tunnelDepth) {
_tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
}
/** how many parallel inbound tunnels to use */
public void setTunnelQuantity(String tunnelQuantity) {
_tunnelQuantity = (tunnelQuantity != null ? tunnelQuantity.trim() : null);
}
/** how much randomisation to apply to the depth of tunnels */
public void setTunnelVariance(String tunnelVariance) {
_tunnelVariance = (tunnelVariance != null ? tunnelVariance.trim() : null);
}
/** how many tunnels to hold in reserve to guard against failures */
public void setTunnelBackupQuantity(String tunnelBackupQuantity) {
_tunnelBackupQuantity = (tunnelBackupQuantity != null ? tunnelBackupQuantity.trim() : null);
}
/** what I2P session overrides should be used */
public void setCustomOptions(String customOptions) {
_customOptions = (customOptions != null ? customOptions.trim() : null);
}
/** what HTTP outproxies should be used (httpclient specific) */
public void setProxyList(String proxyList) {
_proxyList = (proxyList != null ? proxyList.trim() : null);
}
/** what port should this client/httpclient/ircclient listen on */
public void setPort(String port) {
_port = (port != null ? port.trim() : null);
}
/**
* what interface should this client/httpclient/ircclient listen on (unless
* overridden by the setReachableByOther() field)
*/
public void setReachableBy(String reachableBy) {
_reachableBy = (reachableBy != null ? reachableBy.trim() : null);
}
/**
* If specified, defines the exact IP interface to listen for requests
* on (in the case of client/httpclient/ircclient tunnels)
*/
public void setReachableByOther(String reachableByOther) {
_reachableByOther = (reachableByOther != null ? reachableByOther.trim() : null);
}
/** What peer does this client tunnel point at */
public void setTargetDestination(String dest) {
_targetDestination = (dest != null ? dest.trim() : null);
}
/** What host does this server tunnel point at */
public void setTargetHost(String host) {
_targetHost = (host != null ? host.trim() : null);
}
/** What port does this server tunnel point at */
public void setTargetPort(String port) {
_targetPort = (port != null ? port.trim() : null);
}
/** What host does this http server tunnel spoof */
public void setSpoofedHost(String host) {
_spoofedHost = (host != null ? host.trim() : null);
}
/** What filename is this server tunnel's private keys stored in */
public void setPrivKeyFile(String file) {
_privKeyFile = (file != null ? file.trim() : null);
}
/**
* If called with any value (and the form submitted with action=Remove),
* we really do want to stop and remove the tunnel.
*/
public void setRemoveConfirm(String moo) {
_removeConfirmed = true;
}
/**
* If called with any value, we want this tunnel to start whenever it is
* loaded (aka right now and whenever the router is started up)
*/
public void setStartOnLoad(String moo) {
_startOnLoad = true;
}
public void setShared(String moo) {
_sharedClient=true;
}
public void setShared(boolean val) {
_sharedClient=val;
}
public void setConnectDelay(String moo) {
_connectDelay = true;
}
public void setProfile(String profile) {
_profile = profile;
}
/**
* Based on all provided data, create a set of configuration parameters
* suitable for use in a TunnelController. This will replace (not add to)
* any existing parameters, so this should return a comprehensive mapping.
*
*/
private Properties getConfig() {
Properties config = new Properties();
updateConfigGeneric(config);
if ("httpclient".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
if (_reachableByOther != null)
config.setProperty("interface", _reachableByOther);
else
config.setProperty("interface", _reachableBy);
if (_proxyList != null)
config.setProperty("proxyList", _proxyList);
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
if (_name != null && !_sharedClient) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
}
config.setProperty("sharedClient", _sharedClient + "");
}else if ("ircclient".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
if (_reachableByOther != null)
config.setProperty("interface", _reachableByOther);
else
config.setProperty("interface", _reachableBy);
if (_targetDestination != null)
config.setProperty("targetDestination", _targetDestination);
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
if (_name != null && !_sharedClient) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
}
config.setProperty("sharedClient", _sharedClient + "");
} else if ("client".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
if (_reachableByOther != null)
config.setProperty("interface", _reachableByOther);
else
config.setProperty("interface", _reachableBy);
if (_targetDestination != null)
config.setProperty("targetDestination", _targetDestination);
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
if (_name != null && !_sharedClient) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
}
config.setProperty("sharedClient", _sharedClient + "");
} else if ("server".equals(_type)) {
if (_targetHost != null)
config.setProperty("targetHost", _targetHost);
if (_targetPort != null)
config.setProperty("targetPort", _targetPort);
if (_privKeyFile != null)
config.setProperty("privKeyFile", _privKeyFile);
} else if ("httpserver".equals(_type)) {
if (_targetHost != null)
config.setProperty("targetHost", _targetHost);
if (_targetPort != null)
config.setProperty("targetPort", _targetPort);
if (_privKeyFile != null)
config.setProperty("privKeyFile", _privKeyFile);
if (_spoofedHost != null)
config.setProperty("spoofedHost", _spoofedHost);
} else {
return null;
}
return config;
}
private void updateConfigGeneric(Properties config) {
config.setProperty("type", _type);
if (_name != null)
config.setProperty("name", _name);
if (_description != null)
config.setProperty("description", _description);
if (_i2cpHost != null)
config.setProperty("i2cpHost", _i2cpHost);
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) )
config.setProperty("i2cpPort", _i2cpPort);
else
config.setProperty("i2cpPort", "7654");
if (_customOptions != null) {
StringTokenizer tok = new StringTokenizer(_customOptions);
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int eq = pair.indexOf('=');
if ( (eq <= 0) || (eq >= pair.length()) )
continue;
String key = pair.substring(0, eq);
String val = pair.substring(eq+1);
if ("inbound.length".equals(key)) continue;
if ("outbound.length".equals(key)) continue;
if ("inbound.quantity".equals(key)) continue;
if ("outbound.quantity".equals(key)) continue;
if ("inbound.lengthVariance".equals(key)) continue;
if ("outbound.lengthVariance".equals(key)) continue;
if ("inbound.backupQuantity".equals(key)) continue;
if ("outbound.backupQuantity".equals(key)) continue;
if ("inbound.nickname".equals(key)) continue;
if ("outbound.nickname".equals(key)) continue;
if ("i2p.streaming.connectDelay".equals(key)) continue;
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
config.setProperty("option." + key, val);
}
}
config.setProperty("startOnLoad", _startOnLoad + "");
if (_tunnelQuantity != null) {
config.setProperty("option.inbound.quantity", _tunnelQuantity);
config.setProperty("option.outbound.quantity", _tunnelQuantity);
}
if (_tunnelDepth != null) {
config.setProperty("option.inbound.length", _tunnelDepth);
config.setProperty("option.outbound.length", _tunnelDepth);
}
if (_tunnelVariance != null) {
config.setProperty("option.inbound.lengthVariance", _tunnelVariance);
config.setProperty("option.outbound.lengthVariance", _tunnelVariance);
}
if (_tunnelBackupQuantity != null) {
config.setProperty("option.inbound.backupQuantity", _tunnelBackupQuantity);
config.setProperty("option.outbound.backupQuantity", _tunnelBackupQuantity);
}
if (_connectDelay)
config.setProperty("option.i2p.streaming.connectDelay", "1000");
else
config.setProperty("option.i2p.streaming.connectDelay", "0");
if (_name != null) {
if ( ((!"client".equals(_type)) && (!"httpclient".equals(_type))&& (!"ircclient".equals(_type))) || (!_sharedClient) ) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
} else {
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
}
}
if ("interactive".equals(_profile))
config.setProperty("option.i2p.streaming.maxWindowSize", "1");
else
config.remove("option.i2p.streaming.maxWindowSize");
}
///
///
///
protected TunnelController getController(int tunnel) {
if (tunnel < 0) return null;
if (_group == null) return null;
List controllers = _group.getControllers();
if (controllers.size() > tunnel)
return (TunnelController)controllers.get(tunnel);
else
return null;
}
private String getMessages(List msgs) {
StringBuffer buf = new StringBuffer(128);
getMessages(msgs, buf);
return buf.toString();
}
private void getMessages(List msgs, StringBuffer buf) {
if (msgs == null) return;
for (int i = 0; i < msgs.size(); i++) {
buf.append((String)msgs.get(i)).append("\n");
}
}
}

View File

@ -1,16 +1,25 @@
<%@page contentType="text/html" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2PTunnel edit</title>
</head><body>
<a href="index.jsp">Back</a>
<jsp:useBean class="net.i2p.i2ptunnel.WebEditPageHelper" id="helper" scope="request" />
<jsp:setProperty name="helper" property="*" />
<b><jsp:getProperty name="helper" property="actionResults" /></b>
<jsp:getProperty name="helper" property="editForm" />
</body>
</html>
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean" %><%
String tun = request.getParameter("tunnel");
if (tun != null) {
try {
int curTunnel = Integer.parseInt(tun);
if (EditBean.staticIsClient(curTunnel)) {
%><jsp:include page="editClient.jsp" /><%
} else {
%><jsp:include page="editServer.jsp" /><%
}
} catch (NumberFormatException nfe) {
%>Invalid tunnel parameter<%
}
} else {
String type = request.getParameter("type");
int curTunnel = -1;
if ("client".equals(type) || "httpclient".equals(type) || "ircclient".equals(type)) {
%><jsp:include page="editClient.jsp" /><%
} else if ("server".equals(type) || "httpserver".equals(type)) {
%><jsp:include page="editServer.jsp" /><%
} else {
%>Invalid tunnel type<%
}
}
%>

View File

@ -0,0 +1,287 @@
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean"%><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
<% String tun = request.getParameter("tunnel");
int curTunnel = -1;
if (tun != null) {
try {
curTunnel = Integer.parseInt(tun);
} catch (NumberFormatException nfe) {
curTunnel = -1;
}
}
%>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>I2PTunnel Webmanager - Edit</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<% if (editBean.allowCSS()) {
%><link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
<link href="<%=editBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
<link href="<%=editBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" />
<% }
%>
</head>
<body id="tunnelEditPage">
<div id="pageHeader">
</div>
<form method="post" action="index.jsp">
<div id="tunnelEditPanel" class="panel">
<div class="header">
<%
String tunnelTypeName = "";
String tunnelType = "";
if (curTunnel >= 0) {
tunnelTypeName = editBean.getTunnelType(curTunnel);
tunnelType = editBean.getInternalType(curTunnel);
%><h4>Edit proxy settings</h4><%
} else {
tunnelTypeName = editBean.getTypeName(request.getParameter("type"));
tunnelType = request.getParameter("type");
%><h4>New proxy settings</h4><%
} %>
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
<input type="hidden" name="type" value="<%=tunnelType%>" />
</div>
<div class="separator">
<hr />
</div>
<div id="nameField" class="rowItem">
<label for="name" accesskey="N">
<span class="accessKey">N</span>ame:
</label>
<input type="text" size="30" maxlength="50" name="name" id="name" title="Tunnel Name" value="<%=editBean.getTunnelName(curTunnel)%>" class="freetext" />
</div>
<div id="typeField" class="rowItem">
<label>Type:</label>
<span class="text"><%=tunnelTypeName%></span>
</div>
<div id="descriptionField" class="rowItem">
<label for="description" accesskey="e">
D<span class="accessKey">e</span>scription:
</label>
<input type="text" size="60" maxlength="80" name="description" id="description" title="Tunnel Description" value="<%=editBean.getTunnelDescription(curTunnel)%>" class="freetext" />
</div>
<div class="subdivider">
<hr />
</div>
<div id="accessField" class="rowItem">
<label>Access Point:</label>
</div>
<div id="portField" class="rowItem">
<label for="port" accesskey="P">
<span class="accessKey">P</span>ort:
</label>
<input type="text" size="6" maxlength="5" id="port" name="port" title="Access Port Number" value="<%=editBean.getClientPort(curTunnel)%>" class="freetext" />
</div>
<div id="reachField" class="rowItem">
<label for="reachableBy" accesskey="r">
<span class="accessKey">R</span>eachable by:
</label>
<select id="reachableBy" name="reachableBy" title="Valid IP for Client Access" class="selectbox">
<% String clientInterface = editBean.getClientInterface(curTunnel);
String otherInterface = "";
if (!("127.0.0.1".equals(clientInterface)) &&
!("0.0.0.0".equals(clientInterface)) &&
(clientInterface != null) &&
(clientInterface.trim().length() > 0)) {
otherInterface = clientInterface;
}
%><option value="127.0.0.1"<%=("127.0.0.1".equals(clientInterface) ? " selected=\"selected\"" : "")%>>Locally (127.0.0.1)</option>
<option value="0.0.0.0"<%=("0.0.0.0".equals(clientInterface) ? " selected=\"selected\"" : "")%>>Everyone (0.0.0.0)</option>
<option value="other"<%=(!("".equals(otherInterface)) ? " selected=\"selected\"" : "")%>>LAN Hosts (Please specify your LAN address)</option>
</select>
</div>
<div id="otherField" class="rowItem">
<label for="reachableByOther" accesskey="O">
<span class="accessKey">O</span>ther:
</label>
<input type="text" size="20" id="reachableByOther" name="reachableByOther" title="Alternative IP for Client Access" value="<%=otherInterface%>" class="freetext" />
</div>
<div class="subdivider">
<hr />
</div>
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) {
%><div id="destinationField" class="rowItem">
<label for="proxyList" accesskey="x">
Outpro<span class="accessKey">x</span>ies:
</label>
<input type="text" size="30" id="proxyList" name="proxyList" title="List of Outproxy I2P destinations" value="<%=editBean.getClientDestination(curTunnel)%>" class="freetext" />
</div>
<% } else {
%><div id="destinationField" class="rowItem">
<label for="targetDestination" accesskey="T">
<span class="accessKey">T</span>unnel Destination:
</label>
<input type="text" size="30" id="targetDestination" name="targetDestination" title="Destination of the Tunnel" value="<%=editBean.getClientDestination(curTunnel)%>" class="freetext" />
<span class="comment">(name or destination)</span>
</div>
<% }
%><div id="profileField" class="rowItem">
<label for="profile" accesskey="f">
Pro<span class="accessKey">f</span>ile:
</label>
<select id="profile" name="profile" title="Connection Profile" class="selectbox">
<% boolean interactiveProfile = editBean.isInteractive(curTunnel);
%><option <%=(interactiveProfile == true ? "selected=\"selected\" " : "")%>value="interactive">interactive connection </option>
<option <%=(interactiveProfile == false ? "selected=\"selected\" " : "")%>value="bulk">bulk connection (downloads/websites/BT) </option>
</select>
</div>
<div id="delayConnectField" class="rowItem">
<label for="connectDelay" accesskey="y">
Dela<span class="accessKey">y</span> Connect:
</label>
<input value="1000" type="checkbox" id="connectDelay" name="connectDelay" title="Delay Connection"<%=(editBean.shouldDelay(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
<span class="comment">(for request/response connections)</span>
</div>
<div id="sharedtField" class="rowItem">
<label for="shared" accesskey="h">
S<span class="accessKey">h</span>ared Client:
</label>
<input value="true" type="checkbox" id="shared" name="shared" title="Share tunnels with other clients"<%=(editBean.isSharedClient(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
<span class="comment">(Share tunnels with other clients and irc/httpclients? Change requires restart of client proxy)</span>
</div>
<div id="startupField" class="rowItem">
<label for="startOnLoad" accesskey="a">
<span class="accessKey">A</span>uto Start:
</label>
<input value="1" type="checkbox" id="startOnLoad" name="startOnLoad" title="Start Tunnel Automatically"<%=(editBean.startAutomatically(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
<span class="comment">(Check the Box for 'YES')</span>
</div>
<div class="footer">
</div>
</div>
<div id="tunnelAdvancedNetworking" class="panel">
<div class="header">
<h4>Advanced networking options</h4>
<span class="comment">(NOTE: when this client proxy is configured to share tunnels, then these options are for all the shared proxy clients!)</span>
</div>
<div class="separator">
<hr />
</div>
<div id="tunnelOptionsField" class="rowItem">
<label>Tunnel Options:</label>
</div>
<div id="depthField" class="rowItem">
<label for="tunnelDepth" accesskey="t">
Dep<span class="accessKey">t</span>h:
</label>
<select id="tunnelDepth" name="tunnelDepth" title="Depth of each Tunnel" class="selectbox">
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
%><option value="0"<%=(tunnelDepth == 0 ? " selected=\"selected\"" : "") %>>0 hop tunnel (low anonymity, low latency)</option>
<option value="1"<%=(tunnelDepth == 1 ? " selected=\"selected\"" : "") %>>1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2"<%=(tunnelDepth == 2 ? " selected=\"selected\"" : "") %>>2 hop tunnel (high anonymity, high latency)</option>
<% if (tunnelDepth > 2) {
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel</option>
<% }
%></select>
</div>
<div id="varianceField" class="rowItem">
<label for="tunnelVariance" accesskey="v">
<span class="accessKey">V</span>ariance:
</label>
<select id="tunnelVariance" name="tunnelVariance" title="Level of Randomization for Tunnel Depth" class="selectbox">
<% int tunnelVariance = editBean.getTunnelVariance(curTunnel, -1);
%><option value="0"<%=(tunnelVariance == 0 ? " selected=\"selected\"" : "") %>>0 hop variance (no randomisation, consistant performance)</option>
<option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option>
<option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (high randomisation, variable performance)</option>
<option value="1"<%=(tunnelVariance == 1 ? " selected=\"selected\"" : "") %>>+ 0-1 hop variance (medium additive randomisation, subtractive performance)</option>
<option value="2"<%=(tunnelVariance == 2 ? " selected=\"selected\"" : "") %>>+ 0-2 hop variance (high additive randomisation, subtractive performance)</option>
<% if (tunnelVariance > 2 || tunnelVariance < -2) {
%> <option value="<%=tunnelVariance%>" selected="selected"><%= (tunnelVariance > 2 ? "+ " : "+/- ") %>0-<%=tunnelVariance%> hop variance</option>
<% }
%></select>
</div>
<div id="countField" class="rowItem">
<label for="tunnelQuantity" accesskey="C">
<span class="accessKey">C</span>ount:
</label>
<select id="tunnelQuantity" name="tunnelQuantity" title="Number of Tunnels in Group" class="selectbox">
<% int tunnelQuantity = editBean.getTunnelQuantity(curTunnel, 2);
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% if (tunnelQuantity > 3) {
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> inbound tunnels</option>
<% }
%></select>
</div>
<div id="backupField" class="rowItem">
<label for="tunnelBackupQuantity" accesskey="b">
<span class="accessKey">B</span>ackup Count:
</label>
<select id="tunnelBackupQuantity" name="tunnelBackupQuantity" title="Number of Reserve Tunnels" class="selectbox">
<% int tunnelBackupQuantity = editBean.getTunnelBackupQuantity(curTunnel, 0);
%><option value="0"<%=(tunnelBackupQuantity == 0 ? " selected=\"selected\"" : "") %>>0 backup tunnels (0 redundancy, no added resource usage)</option>
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel (low redundancy, low resource usage)</option>
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels (medium redundancy, medium resource usage)</option>
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels (high redundancy, high resource usage)</option>
<% if (tunnelBackupQuantity > 3) {
%> <option value="<%=tunnelBackupQuantity%>" selected="selected"><%=tunnelBackupQuantity%> backup tunnels</option>
<% }
%></select>
</div>
<div class="subdivider">
<hr />
</div>
<div id="optionsField" class="rowItem">
<label>I2CP Options:</label>
</div>
<div id="optionsHostField" class="rowItem">
<label for="clientHost" accesskey="o">
H<span class="accessKey">o</span>st:
</label>
<input type="text" id="clientHost" name="clientHost" size="20" title="I2CP Hostname or IP" value="<%=editBean.getI2CPHost(curTunnel)%>" class="freetext" />
</div>
<div id="optionsPortField" class="rowItem">
<label for="clientPort" accesskey="r">
Po<span class="accessKey">r</span>t:
</label>
<input type="text" id="clientPort" name="clientPort" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" />
</div>
<div class="subdivider">
<hr />
</div>
<div id="customOptionsField" class="rowItem">
<label for="customOptions" accesskey="u">
C<span class="accessKey">u</span>stom options:
</label>
<input type="text" id="customOptions" name="customOptions" size="60" title="Custom Options" value="<%=editBean.getCustomOptions(curTunnel)%>" class="freetext" />
</div>
<div class="footer">
</div>
</div>
<div id="globalOperationsPanel" class="panel">
<div class="header"></div>
<div class="footer">
<div class="toolbox">
<input type="hidden" value="true" name="removeConfirm" />
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button><button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button>
</div>
</div>
</div>
</form>
<div id="pageFooter">
</div>
</body>
</html>

View File

@ -0,0 +1,259 @@
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean"%><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
<% String tun = request.getParameter("tunnel");
int curTunnel = -1;
if (tun != null) {
try {
curTunnel = Integer.parseInt(tun);
} catch (NumberFormatException nfe) {
curTunnel = -1;
}
}
%>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>I2PTunnel Webmanager - Edit</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<% if (editBean.allowCSS()) {
%><link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
<link href="<%=editBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
<link href="<%=editBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" />
<% }
%>
</head>
<body id="tunnelEditPage">
<div id="pageHeader">
</div>
<form method="post" action="index.jsp">
<div id="tunnelEditPanel" class="panel">
<div class="header">
<%
String tunnelTypeName = "";
String tunnelType = "";
if (curTunnel >= 0) {
tunnelTypeName = editBean.getTunnelType(curTunnel);
tunnelType = editBean.getInternalType(curTunnel);
%><h4>Edit server settings</h4><%
} else {
tunnelTypeName = editBean.getTypeName(request.getParameter("type"));
tunnelType = request.getParameter("type");
%><h4>New server settings</h4><%
} %>
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
<input type="hidden" name="type" value="<%=tunnelType%>" />
</div>
<div class="separator">
<hr />
</div>
<div id="nameField" class="rowItem">
<label for="name" accesskey="N">
<span class="accessKey">N</span>ame:
</label>
<input type="text" size="30" maxlength="50" name="name" id="name" title="Tunnel Name" value="<%=editBean.getTunnelName(curTunnel)%>" class="freetext" />
</div>
<div id="typeField" class="rowItem">
<label>Type:</label>
<span class="text"><%=tunnelTypeName%></span>
</div>
<div id="descriptionField" class="rowItem">
<label for="description" accesskey="e">
D<span class="accessKey">e</span>scription:
</label>
<input type="text" size="60" maxlength="80" name="description" id="description" title="Tunnel Description" value="<%=editBean.getTunnelDescription(curTunnel)%>" class="freetext" />
</div>
<div id="startupField" class="rowItem">
<label for="startOnLoad" accesskey="a">
<span class="accessKey">A</span>uto Start:
</label>
<input value="1" type="checkbox" id="startOnLoad" name="startOnLoad" title="Start Tunnel Automatically"<%=(editBean.startAutomatically(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
<span class="comment">(Check the Box for 'YES')</span>
</div>
<div class="subdivider">
<hr />
</div>
<div id="targetField" class="rowItem">
<label>Target:</label>
</div>
<div id="hostField" class="rowItem">
<label for="targetHost" accesskey="H">
<span class="accessKey">H</span>ost:
</label>
<input type="text" size="20" id="targetHost" name="targetHost" title="Target Hostname or IP" value="<%=editBean.getTargetHost(curTunnel)%>" class="freetext" />
</div>
<div id="portField" class="rowItem">
<label for="targetPort" accesskey="P">
<span class="accessKey">P</span>ort:
</label>
<input type="text" size="6" maxlength="5" id="targetPort" name="targetPort" title="Target Port Number" value="<%=editBean.getTargetPort(curTunnel)%>" class="freetext" />
</div>
<div class="subdivider">
<hr />
</div>
<% if ("httpserver".equals(tunnelType)) {
%><div id="websiteField" class="rowItem">
<label for="spoofedHost" accesskey="W">
<span class="accessKey">W</span>ebsite name:
</label>
<input type="text" size="20" id="spoofedHost" name="spoofedHost" title="Website Host Name" value="<%=editBean.getSpoofedHost(curTunnel)%>" class="freetext" />
</div>
<% }
%><div id="privKeyField" class="rowItem">
<label for="privKeyFile" accesskey="k">
Private <span class="accessKey">k</span>ey file:
</label>
<input type="text" size="30" id="privKeyFile" name="privKeyFile" title="Path to Private Key File" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" class="freetext" />
</div>
<div id="profileField" class="rowItem">
<label for="profile" accesskey="f">
Pro<span class="accessKey">f</span>ile:
</label>
<select id="profile" name="profile" title="Connection Profile" class="selectbox">
<% boolean interactiveProfile = editBean.isInteractive(curTunnel);
%><option <%=(interactiveProfile == true ? "selected=\"selected\" " : "")%>value="interactive">interactive connection </option>
<option <%=(interactiveProfile == false ? "selected=\"selected\" " : "")%>value="bulk">bulk connection (downloads/websites/BT) </option>
</select>
</div>
<div id="destinationField" class="rowItem">
<label for="localDestination" accesskey="L">
<span class="accessKey">L</span>ocal destination:
</label>
<input type="text" size="60" readonly="readonly" id="localDestination" title="Read Only: Local Destination (if known)" value="<%=editBean.getDestinationBase64(curTunnel)%>" class="freetext" />
<span class="comment">(if known)</span>
</div>
<div class="footer">
</div>
</div>
<div id="tunnelAdvancedNetworking" class="panel">
<div class="header">
<h4>Advanced networking options</h4>
</div>
<div class="separator">
<hr />
</div>
<div id="tunnelOptionsField" class="rowItem">
<label>Tunnel Options:</label>
</div>
<div id="depthField" class="rowItem">
<label for="tunnelDepth" accesskey="t">
Dep<span class="accessKey">t</span>h:
</label>
<select id="tunnelDepth" name="tunnelDepth" title="Depth of each Tunnel" class="selectbox">
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
%><option value="0"<%=(tunnelDepth == 0 ? " selected=\"selected\"" : "") %>>0 hop tunnel (low anonymity, low latency)</option>
<option value="1"<%=(tunnelDepth == 1 ? " selected=\"selected\"" : "") %>>1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2"<%=(tunnelDepth == 2 ? " selected=\"selected\"" : "") %>>2 hop tunnel (high anonymity, high latency)</option>
<% if (tunnelDepth > 2) {
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel</option>
<% }
%></select>
</div>
<div id="varianceField" class="rowItem">
<label for="tunnelVariance" accesskey="v">
<span class="accessKey">V</span>ariance:
</label>
<select id="tunnelVariance" name="tunnelVariance" title="Level of Randomization for Tunnel Depth" class="selectbox">
<% int tunnelVariance = editBean.getTunnelVariance(curTunnel, -1);
%><option value="0"<%=(tunnelVariance == 0 ? " selected=\"selected\"" : "") %>>0 hop variance (no randomisation, consistant performance)</option>
<option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option>
<option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (high randomisation, variable performance)</option>
<option value="1"<%=(tunnelVariance == 1 ? " selected=\"selected\"" : "") %>>+ 0-1 hop variance (medium additive randomisation, subtractive performance)</option>
<option value="2"<%=(tunnelVariance == 2 ? " selected=\"selected\"" : "") %>>+ 0-2 hop variance (high additive randomisation, subtractive performance)</option>
<% if (tunnelVariance > 2 || tunnelVariance < -2) {
%> <option value="<%=tunnelVariance%>" selected="selected"><%= (tunnelVariance > 2 ? "+ " : "+/- ") %>0-<%=tunnelVariance%> hop variance</option>
<% }
%></select>
</div>
<div id="countField" class="rowItem">
<label for="tunnelQuantity" accesskey="C">
<span class="accessKey">C</span>ount:
</label>
<select id="tunnelQuantity" name="tunnelQuantity" title="Number of Tunnels in Group" class="selectbox">
<% int tunnelQuantity = editBean.getTunnelQuantity(curTunnel, 2);
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% if (tunnelQuantity > 3) {
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> inbound tunnels</option>
<% }
%></select>
</div>
<div id="backupField" class="rowItem">
<label for="tunnelBackupQuantity" accesskey="b">
<span class="accessKey">B</span>ackup Count:
</label>
<select id="tunnelBackupQuantity" name="tunnelBackupQuantity" title="Number of Reserve Tunnels" class="selectbox">
<% int tunnelBackupQuantity = editBean.getTunnelBackupQuantity(curTunnel, 0);
%><option value="0"<%=(tunnelBackupQuantity == 0 ? " selected=\"selected\"" : "") %>>0 backup tunnels (0 redundancy, no added resource usage)</option>
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel (low redundancy, low resource usage)</option>
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels (medium redundancy, medium resource usage)</option>
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels (high redundancy, high resource usage)</option>
<% if (tunnelBackupQuantity > 3) {
%> <option value="<%=tunnelBackupQuantity%>" selected="selected"><%=tunnelBackupQuantity%> backup tunnels</option>
<% }
%></select>
</div>
<div class="subdivider">
<hr />
</div>
<div id="optionsField" class="rowItem">
<label>I2CP Options:</label>
</div>
<div id="optionsHostField" class="rowItem">
<label for="clientHost" accesskey="o">
H<span class="accessKey">o</span>st:
</label>
<input type="text" id="clientHost" name="clientHost" size="20" title="I2CP Hostname or IP" value="<%=editBean.getI2CPHost(curTunnel)%>" class="freetext" />
</div>
<div id="optionsPortField" class="rowItem">
<label for="clientPort" accesskey="r">
Po<span class="accessKey">r</span>t:
</label>
<input type="text" id="clientPort" name="clientPort" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" />
</div>
<div class="subdivider">
<hr />
</div>
<div id="customOptionsField" class="rowItem">
<label for="customOptions" accesskey="u">
C<span class="accessKey">u</span>stom options:
</label>
<input type="text" id="customOptions" name="customOptions" size="60" title="Custom Options" value="<%=editBean.getCustomOptions(curTunnel)%>" class="freetext" />
</div>
<div class="footer">
</div>
</div>
<div id="globalOperationsPanel" class="panel">
<div class="header"></div>
<div class="footer">
<div class="toolbox">
<input type="hidden" value="true" name="removeConfirm" />
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button><button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button>
</div>
</div>
</div>
</form>
<div id="pageFooter">
</div>
</body>
</html>

View File

@ -1,31 +1,259 @@
<%@page contentType="text/html" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean"%><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<jsp:useBean class="net.i2p.i2ptunnel.web.IndexBean" id="indexBean" scope="request" />
<jsp:setProperty name="indexBean" property="*" />
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>I2PTunnel Webmanager - List</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<% if (indexBean.allowCSS()) {
%><link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
<link href="<%=indexBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
<link href="<%=indexBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" />
<% }
%>
</head>
<body id="tunnelListPage">
<div id="pageHeader">
</div>
<div id="statusMessagePanel" class="panel">
<div class="header">
<h4>Status Messages</h4>
</div>
<html><head>
<title>I2PTunnel status</title>
</head><body>
<div class="separator">
<hr />
</div>
<jsp:useBean class="net.i2p.i2ptunnel.WebStatusPageHelper" id="helper" scope="request" />
<jsp:setProperty name="helper" property="*" />
<b><jsp:getProperty name="helper" property="actionResults" /></b>
<jsp:getProperty name="helper" property="summaryList" />
<hr />
<form action="index.jsp" method="GET">
<input type="submit" name="action" value="Stop all" />
<input type="submit" name="action" value="Start all" />
<input type="submit" name="action" value="Restart all" />
<input type="submit" name="action" value="Reload config" />
</form>
<textarea id="statusMessages" rows="3" cols="60" readonly="readonly"><jsp:getProperty name="indexBean" property="messages" /></textarea>
<form action="edit.jsp">
<b>Add new:</b>
<select name="type">
<option value="httpclient">HTTP proxy</option>
<option value="client">Client tunnel</option>
<option value="server">Server tunnel</option>
</select> <input type="submit" value="GO" />
</form>
<div class="separator">
<hr />
</div>
<div class="footer">
<div class="toolbox">
<a class="control" href="index.jsp">Refresh</a>
</div>
</div>
</div>
<div id="localClientTunnelList" class="panel">
<div class="header">
<h4>Local Client Tunnels</h4>
</div>
<div class="separator">
<hr />
</div>
<div class="nameHeaderField rowItem">
<label>Name:</label>
</div>
<div class="portHeaderField rowItem">
<label>Port:</label>
</div>
<div class="typeHeaderField rowItem">
<label>Type:</label>
</div>
<div class="interfaceHeaderField rowItem">
<label>Interface:</label>
</div>
<div class="statusHeaderField rowItem">
<label>Status:</label>
</div>
<div class="separator">
<hr />
</div>
<%
for (int curClient = 0; curClient < indexBean.getTunnelCount(); curClient++) {
if (!indexBean.isClient(curClient)) continue;
%>
<div class="nameField rowItem">
<label>Name:</label>
<span class="text"><a href="edit.jsp?tunnel=<%=curClient%>" title="Edit Tunnel Settings for <%=indexBean.getTunnelName(curClient)%>"><%=indexBean.getTunnelName(curClient)%></a></span>
</div>
<div class="portField rowItem">
<label>Port:</label>
<span class="text"><%=indexBean.getClientPort(curClient)%></span>
</div>
<div class="typeField rowItem">
<label>Type:</label>
<span class="text"><%=indexBean.getTunnelType(curClient)%></span>
</div>
<div class="interfaceField rowItem">
<label>Interface:</label>
<span class="text"><%=indexBean.getClientInterface(curClient)%></span>
</div>
<div class="statusField rowItem">
<label>Status:</label>
<%
switch (indexBean.getTunnelStatus(curClient)) {
case IndexBean.STARTING:
%><div class="statusStarting text">Starting...</div>
<a class="control" title="Stop this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&amp;action=stop&amp;tunnel=<%=curClient%>">Stop</a>
<%
break;
case IndexBean.RUNNING:
%><div class="statusRunning text">Running</div>
<a class="control" title="Stop this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&amp;action=stop&amp;tunnel=<%=curClient%>">Stop</a>
<%
break;
case IndexBean.NOT_RUNNING:
%><div class="statusNotRunning text">Stopped</div>
<a class="control" title="Start this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&amp;action=start&amp;tunnel=<%=curClient%>">Start</a>
<%
break;
}
%></div>
<div class="destinationField rowItem">
<label>Destination:</label>
<input class="freetext" size="40" readonly="readonly" value="<%=indexBean.getClientDestination(curClient)%>" />
</div>
<div class="descriptionField rowItem">
<label>Description:</label>
<div class="text"><%=indexBean.getTunnelDescription(curClient)%></div>
</div>
<div class="subdivider">
<hr />
</div>
<%
}
%>
<div class="separator">
<hr />
</div>
<div class="footer">
<form id="addNewClientTunnelForm" action="edit.jsp">
<div class="toolbox">
<label>Add new client tunnel:</label>
<select name="type">
<option value="client">Standard</option>
<option value="httpclient">HTTP</option>
<option value="ircclient">IRC</option>
</select>
<input class="control" type="submit" value="Create" />
</div>
</form>
</div>
</div>
<div id="localServerTunnelList" class="panel">
<div class="header">
<h4>Local Server Tunnels</h4>
</div>
<div class="separator">
<hr />
</div>
<div class="nameHeaderField rowItem">
<label>Name:</label>
</div>
<div class="targetHeaderField rowItem">
<label>Points at:</label>
</div>
<div class="previewHeaderField rowItem">
<label>Preview:</label>
</div>
<div class="statusHeaderField rowItem">
<label>Status:</label>
</div>
<%
for (int curServer = 0; curServer < indexBean.getTunnelCount(); curServer++) {
if (indexBean.isClient(curServer)) continue;
%>
<div class="nameField rowItem">
<label>Name:</label>
<span class="text"><a href="edit.jsp?tunnel=<%=curServer%>" title="Edit Server Tunnel Settings for <%=indexBean.getTunnelName(curServer)%>"><%=indexBean.getTunnelName(curServer)%></a></span>
</div>
<div class="targetField rowItem">
<label>Points at:</label>
<span class="text"><%=indexBean.getServerTarget(curServer)%></span>
</div>
<div class="previewField rowItem">
<%
if ("httpserver".equals(indexBean.getInternalType(curServer)) && indexBean.getTunnelStatus(curServer) == IndexBean.RUNNING) {
%><label>Preview:</label>
<a class="control" title="Preview this Tunnel" href="http://<%=(new java.util.Random()).nextLong()%>.i2p/?i2paddresshelper=<%=indexBean.getDestinationBase64(curServer)%>" target="_new">Preview</a>
<%
} else {
%><span class="comment">No Preview</span>
<%
}
%></div>
<div class="statusField rowItem">
<label>Status:</label>
<%
switch (indexBean.getTunnelStatus(curServer)) {
case IndexBean.STARTING:
%><div class="statusStarting text">Starting...</div>
<a class="control" title="Stop this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&amp;action=stop&amp;tunnel=<%=curServer%>">Stop</a>
<%
break;
case IndexBean.RUNNING:
%><div class="statusRunning text">Running</div>
<a class="control" title="Stop this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&amp;action=stop&amp;tunnel=<%=curServer%>">Stop</a>
<%
break;
case IndexBean.NOT_RUNNING:
%><div class="statusNotRunning text">Stopped</div>
<a class="control" title="Start this Tunnel" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&amp;action=start&amp;tunnel=<%=curServer%>">Start</a>
<%
break;
}
%></div>
<div class="descriptionField rowItem">
<label>Description:</label>
<div class="text"><%=indexBean.getTunnelDescription(curServer)%></div>
</div>
<div class="subdivider">
<hr />
</div>
<%
}
%>
<div class="separator">
<hr />
</div>
<div class="footer">
<form id="addNewServerTunnelForm" action="edit.jsp">
<div class="toolbox">
<label>Add new server tunnel:</label>
<select name="type">
<option value="server">Standard</option>
<option value="httpserver">HTTP</option>
</select>
<input class="control" type="submit" value="Create" />
</div>
</form>
</div>
</div>
<div id="globalOperationsPanel" class="panel">
<div class="header"></div>
<div class="footer">
<div class="toolbox">
<a class="control" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&amp;action=Stop%20all">Stop All</a><a class="control" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&amp;action=Start%20all">Start All</a><a class="control" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&amp;action=Restart%20all">Restart All</a><a class="control" href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&amp;action=Reload%20configuration">Reload Config</a>
</div>
</div>
</div>
<div id="pageFooter">
</div>
</body>
</html>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
@ -14,4 +14,4 @@
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
</web-app>

BIN
apps/jdom/jdom.jar Normal file

Binary file not shown.

1
apps/jdom/readme.txt Normal file
View File

@ -0,0 +1 @@
This is JDOM 1.0 from http://jdom.org/, released under an Apache style license

View File

@ -3,18 +3,39 @@
<target name="all" depends="build" />
<target name="fetchJettylib" >
<available property="jetty.available" file="jettylib" />
<available property="jetty.available" file="jetty-5.1.6.zip" />
<ant target="doFetchJettylib" />
</target>
<target name="doFetchJettylib" unless="jetty.available" >
<echo message="The libraries contained within the fetched file are from Jetty's 4.2.21 " />
<echo message="distribution (http://jetty.mortbay.org/) which we have copied to our website since" />
<echo message="theirs doesn't have direct HTTP access to the libs. These are not " />
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.6" />
<echo message="distribution (http://jetty.mortbay.org/). These are not " />
<echo message="necessary for using I2P, but are used by some applications on top of I2P," />
<echo message="such as the routerconsole." />
<get src="http://dev.i2p.net/jettylib.tar.bz2" verbose="true" dest="jettylib.tar.bz2" />
<untar src="jettylib.tar.bz2" compression="bzip2" dest="." />
<delete file="jettylib.tar.bz2" />
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.6.zip" verbose="true" dest="jetty-5.1.6.zip" />
<ant target="doExtract" />
</target>
<target name="doExtract">
<unzip src="jetty-5.1.6.zip" dest="." />
<mkdir dir="jettylib" />
<copy todir="jettylib">
<fileset dir="jetty-5.1.6/lib">
<include name="*.jar" />
</fileset>
</copy>
<copy todir="jettylib">
<fileset dir="jetty-5.1.6/ext">
<include name="ant.jar" />
<include name="commons-el.jar" />
<include name="jasper-compiler.jar" />
<include name="jasper-runtime.jar" />
<include name="javax.servlet.jar" />
<include name="org.mortbay.jetty.jar" />
<include name="xercesImpl.jar" />
</fileset>
</copy>
<!-- note the rename, to keep compat with old rev, since we only used the API anyway -->
<copy file="jetty-5.1.6/ext/commons-logging-api.jar" tofile="jettylib/commons-logging.jar" />
<delete dir="jetty-5.1.6" />
</target>
<target name="build" depends="fetchJettylib" />
<target name="builddep" />

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

@ -62,6 +62,8 @@ public interface I2PSocket {
*/
public void close() throws IOException;
public boolean isClosed();
public void setSocketErrorListener(SocketErrorListener lsnr);
/**
* Allow notification of underlying errors communicating across I2P without

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;
@ -32,7 +32,7 @@ class I2PSocketImpl implements I2PSocket {
private Object remoteIDWaiter = new Object();
private I2PInputStream in;
private I2POutputStream out;
private SocketErrorListener _socketErrorListener;
private I2PSocket.SocketErrorListener _socketErrorListener;
private boolean outgoing;
private long _socketId;
private static long __socketId = 0;
@ -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;
@ -233,8 +233,10 @@ class I2PSocketImpl implements I2PSocket {
in.notifyClosed();
}
public boolean isClosed() { return _closedOn > 0; }
/**
* Close the socket from the I2P side, e. g. by a close packet.
* Close the socket from the I2P side (by a close packet)
*/
protected void internalClose() {
synchronized (flagLock) {
@ -249,9 +251,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) {
@ -284,7 +286,7 @@ class I2PSocketImpl implements I2PSocket {
in.setReadTimeout(ms);
}
public void setSocketErrorListener(SocketErrorListener lsnr) {
public void setSocketErrorListener(I2PSocket.SocketErrorListener lsnr) {
_socketErrorListener = lsnr;
}
@ -614,7 +616,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 +647,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

@ -4,26 +4,16 @@
*/
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;
/**
@ -34,62 +24,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,340 +33,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.error(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.error(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");
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);
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.error(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);
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);
if (!acceptConnections) {
// The app did not instantiate an I2PServerSocket
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
boolean replySentOk = false;
synchronized (_session) {
replySentOk = _session.sendMessage(d, packet);
}
if (!replySentOk) {
_log.error(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.error(getName() + ": Disconnect packet had " + payload.length + " bytes");
if (s != null)
s.internalClose();
return;
}
} catch (Exception t) {
_log.error(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)
@ -445,86 +55,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);
}
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);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
throw new I2PException("Error sending through I2P network");
}
if (options != null)
remoteID = s.getRemoteID(true, options.getConnectTimeout());
else
remoteID = s.getRemoteID(true, getDefaultOptions().getConnectTimeout());
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.ERROR))
_log.error(getName() + ": Timeout waiting for ack from syn for id "
+ getReadableForm(lcID) + " 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) {
s.internalClose();
throw ex;
} catch (NoRouteToHostException ex) {
s.internalClose();
throw ex;
} catch (IOException ex) {
if (_log.shouldLog(Log.ERROR))
_log.error(getName() + ": Error sending syn on id " + getReadableForm(lcID) + " 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 " + getReadableForm(lcID) + " for socket " + s, ex);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw ex;
} catch (Exception e) {
s.internalClose();
_log.error(getName() + ": Unhandled error connecting", e);
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
}
}
NoRouteToHostException, InterruptedIOException;
/**
* Create a new connected socket (block until the socket is created)
@ -537,179 +68,37 @@ 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.error(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.error(getName() + ": I2PException:", ex);
return false;
}
}
public boolean ping(Destination peer, long timeoutMs);
public void removeSocket(I2PSocketImpl sock) {
synchronized (lock) {
_inSockets.remove(sock.getLocalID());
_outSockets.remove(sock.getLocalID());
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(sock.getLocalID()) + "\" [" + sock
+ ", send: " + sent + ", recv: " + recv
+ ", lifetime: " + lifetime + "ms, time since close: " + timeSinceClose + ")]",
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 void init(I2PAppContext context, I2PSession session, Properties opts, String 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;
}
public void addDisconnectListener(DisconnectListener lsnr);
public void removeDisconnectListener(DisconnectListener lsnr);
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 static interface DisconnectListener {
public void sessionDisconnected();
}
}

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).
@ -30,18 +36,27 @@ public class I2PSocketManagerFactory {
* @return the newly created socket manager, or null if there were errors
*/
public static I2PSocketManager createManager() {
String i2cpHost = System.getProperty(I2PClient.PROP_TCP_HOST, "localhost");
int i2cpPort = 7654;
String i2cpPortStr = System.getProperty(I2PClient.PROP_TCP_PORT);
if (i2cpPortStr != null) {
try {
i2cpPort = Integer.parseInt(i2cpPortStr);
} catch (NumberFormatException nfe) {
// gobble gobble
}
}
return createManager(getHost(), getPort(), System.getProperties());
}
/**
* Create a socket manager using a brand new destination connected to the
* I2CP router on the local machine on the default port (7654).
*
* @return the newly created socket manager, or null if there were errors
*/
public static I2PSocketManager createManager(Properties opts) {
return createManager(getHost(), getPort(), opts);
}
return createManager(i2cpHost, i2cpPort, System.getProperties());
/**
* Create a socket manager using a brand new destination connected to the
* I2CP router on the specified host and port
*
* @return the newly created socket manager, or null if there were errors
*/
public static I2PSocketManager createManager(String host, int port) {
return createManager(host, port, System.getProperties());
}
/**
@ -66,6 +81,26 @@ public class I2PSocketManagerFactory {
}
}
/**
* Create a socket manager using the destination loaded from the given private key
* stream and connected to the default I2CP host and port.
*
* @return the newly created socket manager, or null if there were errors
*/
public static I2PSocketManager createManager(InputStream myPrivateKeyStream) {
return createManager(myPrivateKeyStream, getHost(), getPort(), System.getProperties());
}
/**
* Create a socket manager using the destination loaded from the given private key
* stream and connected to the default I2CP host and port.
*
* @return the newly created socket manager, or null if there were errors
*/
public static I2PSocketManager createManager(InputStream myPrivateKeyStream, Properties opts) {
return createManager(myPrivateKeyStream, getHost(), getPort(), opts);
}
/**
* Create a socket manager using the destination loaded from the given private key
* stream and connected to the I2CP router on the specified machine on the given
@ -76,23 +111,92 @@ 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);
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
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);
if (i2cpPort > 0)
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");
}
}
}
private static String getHost() {
return System.getProperty(I2PClient.PROP_TCP_HOST, "localhost");
}
private static int getPort() {
int i2cpPort = 7654;
String i2cpPortStr = System.getProperty(I2PClient.PROP_TCP_PORT);
if (i2cpPortStr != null) {
try {
i2cpPort = Integer.parseInt(i2cpPortStr);
} catch (NumberFormatException nfe) {
// gobble gobble
}
}
return i2cpPort;
}
}

View File

@ -0,0 +1,787 @@
/*
* 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.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
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 List _listeners;
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;
_listeners = new ArrayList(1);
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();
List listeners = null;
synchronized (_listeners) {
listeners = new ArrayList(_listeners);
_listeners.clear();
}
for (int i = 0; i < listeners.size(); i++) {
I2PSocketManager.DisconnectListener lsnr = (I2PSocketManager.DisconnectListener)listeners.get(i);
lsnr.sessionDisconnected();
}
}
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 void addDisconnectListener(I2PSocketManager.DisconnectListener lsnr) {
synchronized (_listeners) {
_listeners.add(lsnr);
}
}
public void removeDisconnectListener(I2PSocketManager.DisconnectListener lsnr) {
synchronized (_listeners) {
_listeners.remove(lsnr);
}
}
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

@ -4,56 +4,38 @@ package net.i2p.client.streaming;
* 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 +45,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 +54,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 +62,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 +70,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,147 @@
package net.i2p.client.streaming;
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);
}
public void setProperties(Properties opts) {
if (opts == null) return;
if (opts.containsKey(PROP_BUFFER_SIZE))
_maxBufferSize = getInt(opts, PROP_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
if (opts.containsKey(PROP_CONNECT_TIMEOUT))
_connectTimeout = getInt(opts, PROP_CONNECT_TIMEOUT, DEFAULT_CONNECT_TIMEOUT);
if (opts.containsKey(PROP_READ_TIMEOUT))
_readTimeout = getInt(opts, PROP_READ_TIMEOUT, -1);
if (opts.containsKey(PROP_WRITE_TIMEOUT))
_writeTimeout = getInt(opts, PROP_WRITE_TIMEOUT, DEFAULT_WRITE_TIMEOUT);
}
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,12 +8,14 @@ import java.io.OutputStream;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.util.Properties;
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.I2PThread;
import net.i2p.util.Log;
/**
@ -26,6 +28,8 @@ public class StreamSinkClient {
private int _sendSize;
private int _writeDelay;
private String _peerDestFile;
private String _i2cpHost;
private int _i2cpPort;
/**
@ -35,6 +39,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 +55,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 {
@ -62,74 +75,114 @@ public class StreamSinkClient {
} finally {
if (fis == null) try { fis.close(); } catch (IOException ioe) {}
}
System.out.println("Send " + _sendSize + "KB to " + peer.calculateHash().toBase64());
try {
I2PSocket sock = mgr.connect(peer);
byte buf[] = new byte[32*1024];
Random rand = new Random();
OutputStream out = sock.getOutputStream();
long beforeSending = System.currentTimeMillis();
for (int i = 0; i < _sendSize; i+= 32) {
rand.nextBytes(buf);
out.write(buf);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send " + _sendSize + "KB to " + peer.calculateHash().toBase64());
while (true) {
try {
I2PSocket sock = mgr.connect(peer);
byte buf[] = new byte[Math.min(32*1024, _sendSize*1024)];
Random rand = new Random();
OutputStream out = sock.getOutputStream();
long beforeSending = System.currentTimeMillis();
for (int i = 0; (_sendSize < 0) || (i < _sendSize); i+= buf.length/1024) {
rand.nextBytes(buf);
out.write(buf);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Wrote " + ((1+i*buf.length)/1024) + "/" + _sendSize + "KB");
if (_writeDelay > 0) {
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
}
}
sock.close();
long afterSending = System.currentTimeMillis();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Wrote " + (i+32) + "/" + _sendSize + "KB");
if (_writeDelay > 0) {
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
}
}
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;
} 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;
_log.debug("Sent " + _sendSize + "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 sendSizeKB writeDelayMs serverDestFile</code> <br />
* Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile [concurrentSends]</code> <br />
* <ul>
* <li><b>sendSizeKB</b>: how many KB to send</li>
* <li><b>sendSizeKB</b>: how many KB to send, or -1 for unlimited</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>
* <li><b>concurrentSends</b>: how many concurrent threads should send to the server at once</li>
* </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;
int concurrent = 1;
switch (args.length) {
case 3: // fall through
case 4:
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;
}
if (args.length == 4) {
try { concurrent = Integer.parseInt(args[3]); } catch (NumberFormatException nfe) {}
}
client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
break;
case 5: // fall through
case 6:
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");
}
if (args.length == 6) {
try { concurrent = Integer.parseInt(args[5]); } catch (NumberFormatException nfe) {}
}
break;
default:
System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile [concurrentSends]");
}
if (client != null) {
for (int i = 0; i < concurrent; i++)
new I2PThread(new Runner(client), "Client " + i).start();
}
}
private static class Runner implements Runnable {
private StreamSinkClient _client;
public Runner(StreamSinkClient client) {
_client = client;
}
public void run() {
_client.runClient();
}
}
}

View File

@ -0,0 +1,130 @@
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 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,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
@ -23,6 +26,9 @@ public class StreamSinkServer {
private Log _log;
private String _sinkDir;
private String _destFile;
private String _i2cpHost;
private int _i2cpPort;
private int _handlers;
/**
* Create but do not start the streaming server.
@ -31,8 +37,14 @@ public class StreamSinkServer {
* @param ourDestFile filename to write our binary destination to
*/
public StreamSinkServer(String sinkDir, String ourDestFile) {
this(sinkDir, ourDestFile, null, -1, 3);
}
public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort, int handlers) {
_sinkDir = sinkDir;
_destFile = ourDestFile;
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
_handlers = handlers;
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkServer.class);
}
@ -42,9 +54,14 @@ 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());
if (_log.shouldLog(Log.INFO))
_log.info("Listening for connections on: " + dest.calculateHash().toBase64());
FileOutputStream fos = null;
try {
fos = new FileOutputStream(_destFile);
@ -60,24 +77,16 @@ public class StreamSinkServer {
}
I2PServerSocket sock = mgr.getServerSocket();
while (true) {
try {
I2PSocket curSock = sock.accept();
handle(curSock);
} catch (I2PException ie) {
_log.error("Error accepting connection", ie);
return;
} catch (ConnectException ce) {
_log.error("Connection already dropped", ce);
return;
}
}
startup(sock);
}
private void handle(I2PSocket socket) {
I2PThread t = new I2PThread(new ClientRunner(socket));
t.setName("Handle " + socket.getPeerDestination().calculateHash().toBase64().substring(0,4));
t.start();
public void startup(I2PServerSocket sock) {
for (int i = 0; i < _handlers; i++) {
I2PThread t = new I2PThread(new ClientRunner(sock));
t.setName("Handler " + i);
t.setDaemon(false);
t.start();
}
}
/**
@ -85,51 +94,102 @@ public class StreamSinkServer {
*
*/
private class ClientRunner implements Runnable {
private I2PSocket _sock;
private FileOutputStream _fos;
public ClientRunner(I2PSocket socket) {
_sock = socket;
private I2PServerSocket _socket;
public ClientRunner(I2PServerSocket socket) {
_socket = socket;
}
public void run() {
while (true) {
try {
I2PSocket socket = _socket.accept();
if (socket != null)
handle(socket);
} catch (I2PException ie) {
_log.error("Error accepting connection", ie);
return;
} catch (ConnectException ce) {
_log.error("Connection already dropped", ce);
return;
}
}
}
private void handle(I2PSocket sock) {
FileOutputStream fos = null;
try {
File sink = new File(_sinkDir);
if (!sink.exists())
sink.mkdirs();
File cur = File.createTempFile("clientSink", ".dat", sink);
_fos = new FileOutputStream(cur);
fos = new FileOutputStream(cur);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Writing to " + cur.getAbsolutePath());
} catch (IOException ioe) {
_log.error("Error creating sink", ioe);
_fos = null;
return;
}
}
public void run() {
if (_fos == null) return;
long start = System.currentTimeMillis();
try {
InputStream in = _sock.getInputStream();
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);
//_fos.write(buf, 0, read);
written += read;
if (_log.shouldLog(Log.DEBUG))
_log.debug("read and wrote " + read + " (" + written + ")");
}
fos.write(("written: [" + written + "]\n").getBytes());
long lifetime = System.currentTimeMillis() - start;
_log.info("Got EOF from client socket [written=" + written + " lifetime=" + lifetime + "]");
} catch (IOException ioe) {
_log.error("Error writing the sink", ioe);
} finally {
if (_fos != null) try { _fos.close(); } catch (IOException ioe) {}
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
if (sock != null) try { sock.close(); } catch (IOException ioe) {}
_log.debug("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 [numHandlers]</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>
* <li><b>numHandlers</b>: how many concurrent connections to handle</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", 7654, 3);
break;
case 2:
server = new StreamSinkServer(args[0], args[1]);
break;
case 4:
case 5:
int handlers = 3;
if (args.length == 5) {
try {
handlers = Integer.parseInt(args[4]);
} catch (NumberFormatException nfe) {}
}
try {
int port = Integer.parseInt(args[1]);
server = new StreamSinkServer(args[2], args[3], args[0], port, handlers);
} catch (NumberFormatException nfe) {
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [handlers]");
}
break;
default:
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile [handlers]");
}
if (server != null)
server.runServer();
}
}

View File

@ -0,0 +1,215 @@
package net.i2p.client.streaming;
import java.io.InputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PClientFactory;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
/**
* Sit around on a destination, receiving lots of data and sending lots of
* data to whomever talks to us.
*
* Usage: TestSwarm myKeyFile [peerDestFile ]*
*
*/
public class TestSwarm {
private I2PAppContext _context;
private Log _log;
private String _destFile;
private String _peerDestFiles[];
private String _conOptions;
private I2PSocketManager _manager;
private boolean _dead;
public static void main(String args[]) {
if (args.length < 1) {
System.err.println("Usage: TestSwarm myDestFile [peerDestFile ]*");
return;
}
I2PAppContext ctx = new I2PAppContext();
String files[] = new String[args.length - 1];
System.arraycopy(args, 1, files, 0, files.length);
TestSwarm swarm = new TestSwarm(ctx, args[0], files);
swarm.startup();
}
public TestSwarm(I2PAppContext ctx, String destFile, String peerDestFiles[]) {
_context = ctx;
_log = ctx.logManager().getLog(TestSwarm.class);
_dead = false;
_destFile = destFile;
_peerDestFiles = peerDestFiles;
_conOptions = "";
}
public void startup() {
_log.debug("Starting up");
File keys = new File(_destFile);
if (!keys.exists()) {
try {
I2PClientFactory.createClient().createDestination(new FileOutputStream(keys));
} catch (Exception e) {
_log.error("Error creating a new destination on " + keys, e);
return;
}
}
try {
_manager = I2PSocketManagerFactory.createManager(new FileInputStream(_destFile), null, -1, null);
} catch (Exception e) {
_log.error("Error creatign the manager", e);
return;
}
I2PThread listener = new I2PThread(new Listener(), "Listener");
listener.start();
connectWithPeers();
}
private void connectWithPeers() {
if (_peerDestFiles != null) {
for (int i = 0; i < _peerDestFiles.length; i++) {
try {
FileInputStream fin = new FileInputStream(_peerDestFiles[i]);
Destination dest = new Destination();
dest.readBytes(fin);
I2PThread flooder = new I2PThread(new Flooder(dest), "Flooder+" + dest.calculateHash().toBase64().substring(0,4));
flooder.start();
} catch (Exception e) {
_log.error("Unable to read the peer from " + _peerDestFiles[i], e);
}
}
}
}
private class Listener implements Runnable {
public void run() {
try {
I2PServerSocket ss = _manager.getServerSocket();
I2PSocket s = null;
while ( (s = ss.accept()) != null) {
I2PThread flooder = new I2PThread(new Flooder(s), "Flooder-" + s.getPeerDestination().calculateHash().toBase64().substring(0,4));
flooder.start();
}
} catch (Exception e) {
_log.error("Error listening", e);
}
}
}
private static volatile long __conId = 0;
private class Flooder implements Runnable {
private Destination _remoteDestination;
private I2PSocket _socket;
private boolean _closed;
private long _started;
private long _totalSent;
private long _totalReceived;
private long _lastReceived;
private long _lastReceivedOn;
private long _connectionId;
public Flooder(Destination dest) {
_socket = null;
_remoteDestination = dest;
_connectionId = ++__conId;
_closed = false;
_lastReceived = -1;
_lastReceivedOn = _context.clock().now();
_context.statManager().createRateStat("swarm." + _connectionId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
}
public Flooder(I2PSocket socket) {
_socket = socket;
_remoteDestination = socket.getPeerDestination();
_connectionId = ++__conId;
_closed = false;
_lastReceived = -1;
_lastReceivedOn = _context.clock().now();
_context.statManager().createRateStat("swarm." + _connectionId + ".totalReceived", "Data size received", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
_context.statManager().createRateStat("swarm." + _connectionId + ".lifetime", "How long we talk to a peer", "swarm", new long[] { 5*60*1000 });
}
public long getConnectionId() { return _connectionId; }
public Destination getDestination() { return _remoteDestination; }
public void run() {
_started = _context.clock().now();
_context.statManager().addRateData("swarm." + _connectionId + ".started", 1, 0);
byte data[] = new byte[4*1024];
_context.random().nextBytes(data);
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) {
if (shouldSend()) {
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) {}
} else {
try { Thread.sleep(5000); } 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[8*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);
}
}
}
}
private boolean shouldSend() {
return Boolean.valueOf(_context.getProperty("shouldSend", "false")).booleanValue();
}
}

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);
}
}
}

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