Compare commits

..

300 Commits

Author SHA1 Message Date
d07342e3e3 * 2005-01-12 0.6.1.9 released 2006-01-12 21:18:38 +00:00
205d8de281 2005-01-12 jrandom
* Only create the loadtest.log if requested to do so (thanks zzz!)
    * Make sure we cleanly take into consideration the appropriate data
      points when filtering out duplicate messages in the message validator,
      and report the right bloom filter false positives rate (not used for
      anything except debugging)
2006-01-12 19:35:54 +00:00
a638301b5c 2005-01-12 cervantes
* Syndie CSS tweaks to removed some redundant declarations, improve font
      scaling and layout robustness. Improved cross browser compatibility
      (in other words "kicked IE"). Tightened the look of the blog template
      a little.
2006-01-12 09:59:55 +00:00
4f51ad492b 2005-01-11 Complication
* CSS comment fixes
2006-01-11 23:19:38 +00:00
zzz
c3a9f72d41 (zzz) 1-10-06 update 2006-01-11 23:12:31 +00:00
79476d3609 2005-01-11 jrandom
* Include the attachments/blogs/etc for comments on the blog view
    * Syndie HTML fixes (thanks cervantes!)
    * Make sure we fully reset the objects going into our cache before we
      reuse them (thanks zzz!)
2006-01-11 20:32:34 +00:00
zzz
97a206fcda (zzz) add step-by-step eepsite announce instructions 2006-01-11 05:31:05 +00:00
f783e65a4c added decadence.i2p, freedomarchives.i2p, closedshop.i2p 2006-01-10 20:53:10 +00:00
dbd1f65864 2005-01-10 jrandom
* Added the per-post list of attachments/blogs/etc to the blog view in
      Syndie (though this does not yet include comments or some further
      refinements)
    * Have the I2P shortcut launch i2p.exe instead of i2psvc.exe on windows,
      removing the dox box (though also removes the restart functionality...)
    * Give the i2p.exe the correct java.library.path to support the systray
      dll (thanks Bobcat, Sugadude, anon!)
2006-01-10 06:59:07 +00:00
5c78d8108f 2005-01-09 jrandom
* Removed a longstanding bug that had caused unnecessary router identity
      churn due to clock skew
    * Temporarily sanity check within the streaming lib for long pending
      writes
    * Added support for a blog-wide logo to Syndie, and automated the pushing
      of updated extended blog info data along side the metadata.
2006-01-10 02:12:54 +00:00
1b273bdf43 2005-01-09 jrandom
* Removed a longstanding bug that had caused unnecessary router identity
      churn due to clock skew
    * Temporarily sanity check within the streaming lib for long pending
      writes
    * Added support for a blog-wide logo to Syndie, and automated the pushing
      of updated extended blog info data along side the metadata.
2006-01-09 23:18:15 +00:00
934f4082f1 2005-01-09 jrandom
* Removed a longstanding bug that had caused unnecessary router identity
      churn due to clock skew
    * Temporarily sanity check within the streaming lib for long pending
      writes
    * Added support for a blog-wide logo to Syndie, and automated the pushing
      of updated extended blog info data along side the metadata.
2006-01-09 22:22:41 +00:00
002aed145f 2005-01-09 jrandom
* Bugfix for a rare SSU error (thanks cervantes!)
    * More progress on the blog interface, allowing customizable blog-wide
      links.
2006-01-09 07:02:01 +00:00
1ca27ffd39 2005-01-09 jrandom
* Bugfix for a rare SSU error (thanks cervantes!)
    * More progress on the blog interface, allowing customizable blog-wide
      links.
2006-01-09 06:33:29 +00:00
66e6dbec33 2006-01-08 jrandom
* First pass of the new blog interface, though without much of the useful
      customization features (coming soon)
2006-01-08 21:50:54 +00:00
894caaa63c 2006-01-08 jrandom
* First pass of the new blog interface, though without much of the useful
      customization features (coming soon)
2006-01-08 20:54:36 +00:00
zzz
97dae94b46 (zzz) update 2006-01-05 06:29:55 +00:00
c00488afeb 2006-01-04 jrandom
* Rather than profile individual tunnels for throughput over their
      lifetime, do so at 1 minute intervals (allowing less frequently active
      tunnels to be more fairly measured).
    * Run the live tunnel load test across two tunnels at a time, by default.
      The load test runs for a random period from 90s to the tunnel lifetime,
      self paced.  This should help gathering data for profiling peers that
      are in exploratory tunnels.
2006-01-03  jrandom
    * Calculate the overall peer throughput across the 3 fastest one minute
      tunnel throughput values, rather than the single fastest throughput.
    * Degrade the profiled throughput data over time (cutting the profiled
      peaks in half once a day, on average)
    * Enable yet another new speed calculation for profiling peers, using the
      peak throughput from individual tunnels that a peer is participating in,
      rather than across all tunnels they are participating in.  This helps
      gather a fairer peer throughput measurement, since it won't allow a slow
      high capacity peer seem to have a higher throughput (pushing a little
      data across many tunnels at once, as opposed to lots of data across a
      single tunnel).  This degrades over time like the other.
    * Add basic OS/2 support to the jbigi code (though we do not bundle a
      precompiled OS/2 library)
2006-01-05 02:48:13 +00:00
23723b56ca 2006-01-01 jrandom
* Disable multifile torrent creation in I2PSnark's web UI for the moment
      (though it can still seed and participate in multifile swarms)
    * Enable a new speed calculation for profiling peers, using their peak
      1 minute average tunnel throughput as their speed.
2006-01-01 17:23:26 +00:00
76f89ac93c 2005-12-31 jrandom
* Include a simple torrent creator in the I2PSnark web UI
    * Further streaming lib closing improvements
    * Refactored the load test components to run off live tunnels (though,
      still not safe for normal/anonymous load testing)
2005-12-31 23:40:20 +00:00
0f8611e465 2005-12-30 jrandom
* Close streams more gracefully
2005-12-30 23:33:52 +00:00
8e87ae08fb 2005-12-30 jrandom
* Small streaming lib bugfixes for the modified timeouts
    * Minor Syndie/Sucker RSS html fix
    * Small synchronization fix in I2PSnark (thanks fsm!)
2005-12-30 20:57:53 +00:00
5b1a6391f3 2005-12-30 jrandom
* Replaced the bundled linux jcpuid (written in C++) with scintilla's
      jcpuid (written in C), removing the libg++.so.5 dependency that has bit
      some distros (e.g. mandriva)
2005-12-30 18:16:46 +00:00
728f177473 2005-12-29 jrandom
* Minor fix to the new ERR-ClockSkew to deal with people whose clocks are
      actually correct
2005-12-29 13:07:22 +00:00
1d0d0d9c69 2005-12-27 jrandom
* Add a new Status: line on the router console - "ERR-ClockSkew", in case
      the clock is too skewed to do anything useful (check the year and month,
      not just the hour and minute).
    * Fixed the read/write timeouts in the streaming lib (so that it actually
      honors them now)
    * Minor I2PSnark cleanups (no read timeout, more careful shutdown and
      torrent closing)
    * Handle an oddball tunnel creation failure (thanks Xunk)
2005-12-27 13:20:50 +00:00
9b7e5d1817 2005-12-26 Complication
* Fix some integer typecasting in I2PSnark (caused >2GB torrents to fail)
    * HTML readability cosmetics on "Peers" page
2005-12-27 04:20:29 +00:00
dc0485b526 fix ugliness in release history of help.jsp
[yes, i am still alive *g*]
2005-12-23 04:36:31 +00:00
784d465d17 * 2005-12-22 0.6.1.8 released
2005-12-22  jrandom
    * Bundle the standalone I2PSnark launcher in the installer and update
      process (launch as "java -jar launch-i2psnark.jar", viewing the
      interface on http://localhost:8002/)
    * Don't autostart swarming torrents by default so that you can run a
      standalone I2PSnark from the I2P install dir and not have the embedded
      I2PSnark autolaunch the torrents that the standalone instance is running
    * Fixed a rare streaming lib bug that could let a blocking call wait
      forever.
2005-12-22 12:49:09 +00:00
327089c9d1 4matting 2005-12-22 10:10:39 +00:00
148dd99c86 2005-12-22 jrandom
* Cleaned up some buffer synchronization issues in I2PSnark that could
       cause blockage.
2005-12-22 10:04:12 +00:00
98277d3b64 2005-12-21 jrandom
* Adjusted I2PSnark's usage of the streaming lib (tweaking it for BT's
      behavior)
    * Fixed the I2PSnark bug that would lose track of live peers
2005-12-21 12:04:54 +00:00
702e5a5eab 2005-12-19 cervantes
* Silly RDF syntax
    * Oops...
2005-12-21 04:15:36 +00:00
54c91731c0 rdf updates for easier fire2pe/xul handling 2005-12-20 10:22:24 +00:00
3bfc109476 (cough. not often a problem though) 2005-12-20 02:29:09 +00:00
3989638f2d 2005-12-19 jrandom
* Fix for old Syndie blog bookmarks (thanks Complication!)
    * Fix for I2PSnark to accept incoming connections again (oops)
    * Randomize the order that peers from the tracker are contacted
2005-12-20 02:01:37 +00:00
4a65fd4f46 2005-12-19 jrandom
* I2PSnark logging, disconnect old inactive peers rather than new ones,
      memory usage reduction, better OOM handling, and a shared connection
      acceptor.
    * Cleaned up the Syndie blog page and the resulting filters (viewing a
      blog from the blog page shows threads started by the selected author,
      not those that they merely participate in)
2005-12-19 13:34:52 +00:00
d525c49d45 2005-12-18 jrandom
* Added a standalone runner for the I2PSnark web ui (build with the
      command "ant i2psnark", unzip i2psnark-standalone.zip somewhere, run
      with "java -jar launch-i2psnark.jar", and go to http://localhost:8002/).
    * Further I2PSnark error handling
2005-12-17  jrandom
    * Let multiuser accounts authorize themselves to access the remote
      functionality again (thanks Ch0Hag!)
    * Adjust the JVM heap size to 128MB for new installs (existing users can
      accomplish this by editing wrapper.config, adding the line
      "wrapper.java.maxmemory=128", and then doing a full shutdown and startup
      of the router).  This is relevent for heavy usage of I2PSnark in the
      router console.
2005-12-18 05:52:19 +00:00
c287bace0f 2005-12-18 jrandom
* Added a standalone runner for the I2PSnark web ui (build with the
      command "ant i2psnark", unzip i2psnark-standalone.zip somewhere, run
      with "java -jar launch-i2psnark.jar", and go to http://localhost:8002/).
    * Further I2PSnark error handling
2005-12-18 05:39:52 +00:00
ee0951b5b2 2005-12-17 jrandom
* Use our faster SHA1, rather than the JVM's within I2PSnark, and let
      'piece' sizes grow larger than before.
2005-12-17 09:22:07 +00:00
1eb3ae5e1b 2005-12-16 jrandom
* Added some I2PSnark sanity checks, an OOMListener when running
      standalone, and a guard against keeping memory tied up indefinitely.
    * Sanity check on the watchdog (thanks zzz!)
    * Handle invalid HTTP requests in I2PTunnel a little better
2005-12-17 03:47:02 +00:00
7d234b1978 2005-12-16 jrandom
* Moved I2PSnark from using Threads to I2PThreads, so we handle OOMs
      properly (thanks Complication!)
    * More guards in I2PSnark for zany behavior (I2PSession recon w/ skew,
      b0rking in the DirMonitor, etc)
2005-12-16 23:18:56 +00:00
6f424fa751 2005-12-16 jrandom
* Try to run a torrent in readonly mode if we can't write to the file, and
      handle failures a little more gracefully (thanks polecat!)
2005-12-16 11:01:20 +00:00
7726bd1a5c 2005-12-16 jrandom
* Refuse torrents with too many files (128), avoiding ulimit errors.
    * Remove an fd leak in I2PSnark
    * Further I2PSnark web UI cleanup
2005-12-16 08:24:21 +00:00
2a922098d6 refresh link (good idea Complication) 2005-12-16 04:01:05 +00:00
3ec92c8b62 2005-12-15 jrandom
* Added a first pass to the I2PSnark web UI (see /i2psnark/)
2005-12-16 03:00:48 +00:00
b37bb9372e 2005-12-15 jrandom
* Added multitorrent support to I2PSnark, accessible currently by running
      "i2psnark.jar --config i2psnark.config" (which may or may not exist).
      It then joins the swarm for any torrents in ./i2psnark/*.torrent, saving
      their data in that directory as well.  Removing the .torrent file stops
      participation, and it is currently set to seed indefinitely.  Completion
      is logged to the logger and standard output, with further UI interaction
      left to the (work in progress) web UI.
2005-12-15 08:58:30 +00:00
369b6930e5 2005-12-14 jrandom
* Fix to drop peer references when we shitlist people again (thanks zzz!)
    * Further I2PSnark fixes to deal with arbitrary torrent info attributes
      (thanks Complication!)
2005-12-14 09:32:50 +00:00
5033a22a9b 2005-12-13 zzz
* Don't test tunnels expiring within 90 seconds
    * Defer Test Tunnel jobs if job lag too large
    * Use JobQueue.getMaxLag() rather than the jobQueue.jobLag stat to measure
      job lag for tunnel build backoff, allowing for more agile handling
      (since the stat is only updated once a minute)
    * Use tunnel length override if all tunnels are expiring within one
      minute.
2005-12-13 21:56:41 +00:00
6e5114a4c2 multihop load tests (testing everyone with a fixed set of fast other peers).
not for general purpose use, so you can probably safely ignore this code, but its
useful for testing
2005-12-13 21:32:50 +00:00
77c818a0b1 toolbar.html isn't in cvs yet (polecat - please check this diff :) 2005-12-13 09:43:41 +00:00
7ac673cea4 2005-12-13 jrandom
* Fixed I2PSnark's handling of some torrent files to deal with those
      created by Azureus and I2PRufus (it didn't know how to deal with
      additional meta info, such as path.utf-8 or name.utf-8).
2005-12-13 09:38:51 +00:00
e5fa7e0ae4 Navbar is now customizeable via docs/toolbar.html. There is a default should that file not be there. And... wtf, didn't my syndie thumbnail patch take? Well, no conflicts reported so, here it goes again. 2005-12-13 08:18:59 +00:00
ab4f3008cb 2005-12-09 zzz
* Create different strategies for exploratory tunnels (which are difficult
      to create) and client tunnels (which are much easier)
    * Gradually increase number of parallel build attempts as tunnel expiry
      nears.
    * Temporarily shorten attempted build tunnel length if builds using
      configured tunnel length are unsuccessful
    * React more aggressively to tunnel failure than routine tunnel
      replacement
    * Make tunnel creation times randomized - there is existing code to
      randomize the tunnels but it isn't effective due to the tunnel creation
      strategy. Currently, most tunnels get built all at once, at about 2 1/2
      to 3 minutes before expiration. The patch fixes this by fixing the
      randomization, and by changing the overlap time (with old tunnels) to a
      range of 2 to 4 minutes.
    * Reduce number of excess tunnels. Lots of excess tunnels get created due
      to overlapping calls. Just about anything generated a call which could
      build many tunnels all at once, even if tunnel building was already in
      process.
    * Miscellaneous router console enhancements
2005-12-09 08:05:44 +00:00
f738a02760 link to the filtered blog, not to the current viewBlogs page 2005-12-08 21:01:04 +00:00
7d64ecb628 2005-12-08 jrandom
* Minor bugfix in SSU for dealing with corrupt packets
    * Added some hooks for load testing
2005-12-08 20:53:41 +00:00
7beacff028 2005-12-07 jrandom
* Added a first pass at a blog view in Syndie
2005-12-08 00:50:32 +00:00
952bcc696a 2005-12-07 jrandom
* Expand the thread we're viewing to its leaf
    * Bugfix on intraday ordering (children are always newer than parents)
2005-12-07 20:19:42 +00:00
19bba048f2 2005-12-05 jrandom
* Added an RDF and XML thread export to Syndie, reachable at
      .../threadnav/rdf or .../threadnav/xml, accepting the parameters
      count=$numThreads and offset=$threadIndex.  If the $numThreads is -1, it
      displays all threads.
2005-12-05 06:14:15 +00:00
5966fcf5ff 2005-12-04 TLorD
* Patch for the C SAM library to null terminate strings on copy (thanks!)
2005-12-04 20:12:19 +00:00
fbd7feee61 2005-12-04 jrandom
* Bugfix in Syndie for a problem in the threaded indexer (thanks CofE!)
    * Always include ourselves in the favorite authors (since we don't
      bookmark ourselves)
2005-12-04 20:02:24 +00:00
5faca98176 I am such a useless bitch. 2005-12-04 17:04:10 +00:00
99951bf815 Adding a schema for [link] to handle if you want to display links directly to your attachments within the context of the blog itself. Some redundant code here (3 files modified with cut & paste) so we may want to further abstract the External links: HTML generation code. 2005-12-04 13:55:27 +00:00
024b1a04e2 no message 2005-12-04 03:30:32 +00:00
a4cc18df76 2005-12-03 jrandom
* Use newgroup-like tags by default in Syndie's interface
2005-12-04 03:18:08 +00:00
fef578973a no message 2005-12-03 22:09:01 +00:00
35b75a7300 2005-12-03 jrandom
* Added support for a 'most recent posts' view that CofE requested, which
      includes the ability to filter by age (e.g. posts by your favorite
      authors in the last 5 days).
2005-12-03 19:03:44 +00:00
1c6c397913 2005-12-03 jrandom
* Adjusted Syndie to use the threaded view that cervantes suggested, which
      displays a a single thread path at a time - from root to leaf - rather
      than a depth first traversal.
2005-12-03 17:33:35 +00:00
c96965d364 2005-12-03 jrandom
* Package up a standalone Syndie install into a "syndie-standalone.zip",
      buildable with "ant syndie".  It extracts into ./syndie/, launches with
      "java -jar launchsyndie.jar" (or javaw, on windows, to avoid a dos box),
      running a single user Syndie instance (by default).  It also creates a
      default subscription to syndiemedia without any anonymity (using no
      proxy).  Upgrades can be done by just replacing the syndie.war with the
      one from I2P.
2005-12-03 05:41:25 +00:00
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
580 changed files with 49400 additions and 5065 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

@ -36,6 +36,7 @@ import java.io.File;
*/
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
@ -56,7 +57,7 @@ public class Daemon {
* @param log
* The log to write changes and conflicts to.
*/
public static void update(AddressBook master, AddressBook router,
public void update(AddressBook master, AddressBook router,
File published, SubscriptionList subscriptions, Log log) {
router.merge(master, true, null);
Iterator iter = subscriptions.iterator();
@ -77,7 +78,7 @@ public class Daemon {
* @param home
* The directory containing addressbook's configuration files.
*/
public static void update(Map settings, String home) {
public void update(Map settings, String home) {
File masterFile = new File(home, (String) settings
.get("master_addressbook"));
File routerFile = new File(home, (String) settings
@ -104,7 +105,7 @@ public class Daemon {
.get("proxy_host"), Integer.parseInt((String) settings.get("proxy_port")));
Log log = new Log(logFile);
Daemon.update(master, router, published, subscriptions, log);
update(master, router, published, subscriptions, log);
}
/**
@ -118,6 +119,10 @@ public class Daemon {
* 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;
@ -138,7 +143,7 @@ public class Daemon {
defaultSettings.put("subscriptions", "subscriptions.txt");
defaultSettings.put("etags", "etags");
defaultSettings.put("last_modified", "last_modified");
defaultSettings.put("update_delay", "1");
defaultSettings.put("update_delay", "12");
File homeFile = new File(home);
if (!homeFile.exists()) {
@ -151,19 +156,36 @@ public class Daemon {
File settingsFile = new File(homeFile, settingsLocation);
settings = ConfigParser.parse(settingsFile, defaultSettings);
// wait
try {
Thread.currentThread().sleep(5*60*1000);
} catch (InterruptedException ie) {}
while (true) {
settings = ConfigParser.parse(settingsFile, defaultSettings);
long delay = Long.parseLong((String) settings.get("update_delay"));
if (delay < 1) {
delay = 1;
}
Daemon.update(settings, home);
update(settings, home);
try {
Thread.sleep(delay * 60 * 60 * 1000);
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

@ -44,10 +44,10 @@ public class DaemonThread extends Thread {
* @see java.lang.Runnable#run()
*/
public void run() {
try {
Thread.sleep(5 * 60 * 1000);
} catch (InterruptedException exp) {
}
//try {
// Thread.sleep(5 * 60 * 1000);
//} catch (InterruptedException exp) {
//}
Daemon.main(this.args);
}
}

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,93 @@
<?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, war" />
<target name="builddep">
<ant dir="../../jetty/" target="build" />
<ant dir="../../streaming/java/" target="build" />
<!-- streaming will build ministreaming and 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:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
</target>
<target name="jar" depends="builddep, compile">
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/*Servlet.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="war" depends="jar">
<war destfile="../i2psnark.war" webxml="../web.xml">
<classes dir="./build/obj" includes="**/*" />
</war>
</target>
<target name="standalone" depends="standalone_prep">
<zip destfile="i2psnark-standalone.zip">
<zipfileset dir="./dist/" prefix="i2psnark/" />
</zip>
</target>
<target name="standalone_prep" depends="war">
<javac debug="true" deprecation="on" source="1.3" target="1.3"
destdir="./build" srcdir="src/" includes="org/klomp/snark/web/RunStandalone.java" >
<classpath>
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
<pathelement location="../../jetty/jettylib/commons-el.jar" />
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<pathelement location="../../../core/java/build/i2p.jar" />
</classpath>
</javac>
<jar destfile="./build/launch-i2psnark.jar" basedir="./build/" includes="org/klomp/snark/web/RunStandalone.class">
<manifest>
<attribute name="Main-Class" value="org.klomp.snark.web.RunStandalone" />
<attribute name="Class-Path" value="lib/i2p.jar lib/mstreaming.jar lib/streaming.jar lib/commons-el.jar lib/commons-logging.jar lib/jasper-compiler.jar lib/jasper-runtime.jar lib/javax.servlet.jar lib/org.mortbay.jetty.jar" />
</manifest>
</jar>
<delete dir="./dist" />
<mkdir dir="./dist" />
<copy file="./build/launch-i2psnark.jar" tofile="./dist/launch-i2psnark.jar" />
<mkdir dir="./dist/webapps" />
<copy file="../i2psnark.war" tofile="./dist/webapps/i2psnark.war" />
<mkdir dir="./dist/lib" />
<copy file="../../../core/java/build/i2p.jar" tofile="./dist/lib/i2p.jar" />
<copy file="../../jetty/jettylib/commons-el.jar" tofile="./dist/lib/commons-el.jar" />
<copy file="../../jetty/jettylib/commons-logging.jar" tofile="./dist/lib/commons-logging.jar" />
<copy file="../../jetty/jettylib/javax.servlet.jar" tofile="./dist/lib/javax.servlet.jar" />
<copy file="../../jetty/jettylib/org.mortbay.jetty.jar" tofile="./dist/lib/org.mortbay.jetty.jar" />
<copy file="../../jetty/jettylib/jasper-compiler.jar" tofile="./dist/lib/jasper-compiler.jar" />
<copy file="../../jetty/jettylib/jasper-runtime.jar" tofile="./dist/lib/jasper-runtime.jar" />
<copy file="../../ministreaming/java/build/mstreaming.jar" tofile="./dist/lib/mstreaming.jar" />
<copy file="../../streaming/java/build/streaming.jar" tofile="./dist/lib/streaming.jar" />
<copy file="../jetty-i2psnark.xml" tofile="./dist/jetty-i2psnark.xml" />
<copy file="../readme-standalone.txt" tofile="./dist/readme.txt" />
<mkdir dir="./dist/work" />
<mkdir dir="./dist/logs" />
<zip destfile="i2psnark-standalone.zip">
<zipfileset dir="./dist/" prefix="i2psnark/" />
</zip>
</target>
<target name="clean">
<delete dir="./build" />
<delete file="../i2psnark.war" />
<delete file="./i2psnark-standalone.zip" />
</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,132 @@
/* 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(");
sb.append(size).append(")[");
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,182 @@
/* 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;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Accepts connections on a TCP port and routes them to sub-acceptors.
*/
public class ConnectionAcceptor implements Runnable
{
private static final ConnectionAcceptor _instance = new ConnectionAcceptor();
public static final ConnectionAcceptor instance() { return _instance; }
private Log _log = new Log(ConnectionAcceptor.class);
private I2PServerSocket serverSocket;
private PeerAcceptor peeracceptor;
private Thread thread;
private boolean stop;
private boolean socketChanged;
private ConnectionAcceptor() {}
public synchronized void startAccepting(PeerCoordinatorSet set, I2PServerSocket socket) {
if (serverSocket != socket) {
if ( (peeracceptor == null) || (peeracceptor.coordinators != set) )
peeracceptor = new PeerAcceptor(set);
serverSocket = socket;
stop = false;
socketChanged = true;
if (thread == null) {
thread = new I2PThread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
}
}
}
public ConnectionAcceptor(I2PServerSocket serverSocket,
PeerAcceptor peeracceptor)
{
this.serverSocket = serverSocket;
this.peeracceptor = peeracceptor;
socketChanged = false;
stop = false;
thread = new I2PThread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
}
public void halt()
{
if (true) throw new RuntimeException("wtf");
stop = true;
I2PServerSocket ss = serverSocket;
if (ss != null)
try
{
ss.close();
}
catch(I2PException ioe) { }
Thread t = thread;
if (t != null)
t.interrupt();
}
public void restart() {
serverSocket = I2PSnarkUtil.instance().getServerSocket();
socketChanged = true;
Thread t = thread;
if (t != null)
t.interrupt();
}
public int getPort()
{
return 6881; // serverSocket.getLocalPort();
}
public void run()
{
while(!stop)
{
if (socketChanged) {
// ok, already updated
socketChanged = false;
}
if (serverSocket == null) {
Snark.debug("Server socket went away.. boo hiss", Snark.ERROR);
stop = true;
return;
}
try
{
I2PSocket socket = serverSocket.accept();
if (socket == null) {
if (socketChanged) {
continue;
} else {
Snark.debug("Null socket accepted, but socket wasn't changed?", Snark.ERROR);
}
} else {
Thread t = new I2PThread(new Handler(socket), "Connection-" + socket);
t.start();
}
}
catch (I2PException ioe)
{
if (!socketChanged) {
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) { }
throw new RuntimeException("wtf");
}
private class Handler implements Runnable {
private I2PSocket _socket;
public Handler(I2PSocket socket) {
_socket = socket;
}
public void run() {
try {
InputStream in = _socket.getInputStream();
OutputStream out = _socket.getOutputStream();
if (true) {
in = new BufferedInputStream(in);
//out = new BufferedOutputStream(out);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handling socket from " + _socket.getPeerDestination().calculateHash().toBase64());
peeracceptor.connection(_socket, in, out);
} catch (IOException ioe) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error handling connection from " + _socket.getPeerDestination().calculateHash().toBase64(), ioe);
try { _socket.close(); } catch (IOException 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,231 @@
package org.klomp.snark;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.util.EepGet;
import net.i2p.client.I2PSession;
import net.i2p.data.*;
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.*;
/**
* 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 Map _opts;
private I2PSocketManager _manager;
private boolean _configured;
private I2PSnarkUtil() {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(Snark.class);
_opts = new HashMap();
setProxy("127.0.0.1", 4444);
setI2CPConfig("127.0.0.1", 7654, null);
_configured = false;
}
/**
* 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;
}
_configured = true;
}
public boolean configured() { return _configured; }
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
if (opts != null)
_opts.putAll(opts);
_configured = true;
}
public String getI2CPHost() { return _i2cpHost; }
public int getI2CPPort() { return _i2cpPort; }
public Map getI2CPOptions() { return _opts; }
public String getEepProxyHost() { return _proxyHost; }
public int getEepProxyPort() { return _proxyPort; }
public boolean getEepProxySet() { return _shouldProxy; }
/**
* Connect to the router, if we aren't already
*/
public boolean connect() {
if (_manager == null) {
Properties opts = new Properties();
if (_opts != null) {
for (Iterator iter = _opts.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
opts.setProperty(key, _opts.get(key).toString());
}
}
if (opts.getProperty("inbound.nickname") == null)
opts.setProperty("inbound.nickname", "I2PSnark");
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
opts.setProperty("i2p.streaming.inactivityTimeout", "90000");
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
opts.setProperty("i2p.streaming.inactivityAction", "1");
if (opts.getProperty("i2p.streaming.writeTimeout") == null)
opts.setProperty("i2p.streaming.writeTimeout", "90000");
//if (opts.getProperty("i2p.streaming.readTimeout") == null)
// opts.setProperty("i2p.streaming.readTimeout", "120000");
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
}
return (_manager != null);
}
public boolean connected() { return _manager != null; }
/**
* Destroy the destination itself
*/
public void disconnect() {
I2PSocketManager mgr = _manager;
_manager = null;
mgr.destroySocketManager();
}
/** 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
*/
public File get(String url) { return get(url, true); }
public File get(String url, boolean rewrite) {
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
File out = null;
try {
out = File.createTempFile("i2psnark", "url");
} catch (IOException ioe) {
ioe.printStackTrace();
out.delete();
return null;
}
String fetchURL = url;
if (rewrite)
fetchURL = rewriteAnnounce(url);
//_log.debug("Rewritten url [" + fetchURL + "]");
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, 1, out.getAbsolutePath(), fetchURL);
if (get.fetch()) {
_log.debug("Fetch successful [" + url + "]: size=" + out.length());
return out;
} else {
_log.warn("Fetch failed [" + url + "]");
out.delete();
return null;
}
}
public I2PServerSocket getServerSocket() {
return _manager.getServerSocket();
}
String getOurIPString() {
I2PSession sess = _manager.getSession();
if (sess != null) {
Destination dest = sess.getMyDestination();
if (dest != null)
return dest.toBase64();
}
return "unknown";
}
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);
String rv = "http://i2p/" + origAnnounce.substring(destStart, destEnd) + origAnnounce.substring(pathStart);
//_log.debug("Rewriting [" + origAnnounce + "] as [" + rv + "]");
return rv;
}
/** hook between snark's logger and an i2p log */
void debug(String msg, int snarkDebugLevel, Throwable t) {
if (t instanceof OutOfMemoryError) {
try { Thread.sleep(100); } catch (InterruptedException ie) {}
try {
t.printStackTrace();
} catch (Throwable tt) {}
try {
System.out.println("OOM thread: " + Thread.currentThread().getName());
} catch (Throwable tt) {}
}
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,141 @@
/* 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;
import net.i2p.util.SimpleTimer;
// 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;
SimpleTimer.TimedEvent expireEvent;
/** 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,461 @@
/* 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.*;
import net.i2p.data.Base64;
import net.i2p.util.Log;
import net.i2p.crypto.SHA1;
/**
* Note: this class is buggy, as it doesn't propogate custom meta fields into the bencoded
* info data, and from there to the info_hash. At the moment, though, it seems to work with
* torrents created by I2P-BT, I2PRufus and Azureus.
*
*/
public class MetaInfo
{
private static final Log _log = new Log(MetaInfo.class);
private final String announce;
private final byte[] info_hash;
private final String name;
private final String name_utf8;
private final List files;
private final List files_utf8;
private final List lengths;
private final int piece_length;
private final byte[] piece_hashes;
private final long length;
private final Map infoMap;
private byte[] torrentdata;
MetaInfo(String announce, String name, String name_utf8, List files, List lengths,
int piece_length, byte[] piece_hashes, long length)
{
this.announce = announce;
this.name = name;
this.name_utf8 = name_utf8;
this.files = files;
this.files_utf8 = null;
this.lengths = lengths;
this.piece_length = piece_length;
this.piece_hashes = piece_hashes;
this.length = length;
this.info_hash = calculateInfoHash();
infoMap = null;
}
/**
* 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
{
_log.debug("Creating a metaInfo: " + m, new Exception("source"));
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();
infoMap = info;
val = (BEValue)info.get("name");
if (val == null)
throw new InvalidBEncodingException("Missing name string");
name = val.getString();
val = (BEValue)info.get("name.utf-8");
if (val != null)
name_utf8 = val.getString();
else
name_utf8 = null;
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;
files_utf8 = 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);
files_utf8 = 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);
val = (BEValue)desc.get("path.utf-8");
if (val != null) {
path_list = val.getList();
path_length = path_list.size();
if (path_length > 0) {
file = new ArrayList(path_length);
it = path_list.iterator();
while (it.hasNext())
file.add(((BEValue)it.next()).getString());
files_utf8.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)
{
if (true)
return fast_checkPiece(piece, bs, off, length);
else
return orig_checkPiece(piece, bs, off, length);
}
private boolean orig_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;
}
private boolean fast_checkPiece(int piece, byte[] bs, int off, int length) {
SHA1 sha1 = new SHA1();
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, name_utf8, 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();
if (infoMap != null) {
info.putAll(infoMap);
return info;
}
info.put("name", name);
if (name_utf8 != null)
info.put("name.utf-8", name_utf8);
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));
if ( (files_utf8 != null) && (files_utf8.size() > i) )
file.put("path.utf-8", files_utf8.get(i));
file.put("length", lengths.get(i));
l.add(file);
}
info.put("files", l);
}
return info;
}
private byte[] calculateInfoHash()
{
Map info = createInfoMap();
StringBuffer buf = new StringBuffer(128);
buf.append("info: ");
for (Iterator iter = info.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
Object val = info.get(key);
buf.append(key).append('=');
if (val instanceof byte[])
buf.append(Base64.encode((byte[])val, true));
else
buf.append(val.toString());
}
_log.debug(buf.toString());
byte[] infoBytes = BEncoder.bencode(info);
//_log.debug("info bencoded: [" + Base64.encode(infoBytes, true) + "]");
try
{
MessageDigest digest = MessageDigest.getInstance("SHA");
byte hash[] = digest.digest(infoBytes);
_log.debug("info hash: [" + net.i2p.data.Base64.encode(hash) + "]");
return hash;
}
catch(NoSuchAlgorithmException nsa)
{
throw new InternalError(nsa.toString());
}
}
}

View File

@ -0,0 +1,461 @@
/* 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;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
public class Peer implements Comparable
{
private Log _log = new Log(Peer.class);
// Identifying property, the peer id of the other side.
private final PeerID peerID;
private final byte[] my_id;
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 I2PSocket sock;
private boolean deregister = true;
private static long __id;
private long _id;
/**
* 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;
_id = ++__id;
//_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating"));
}
/**
* 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, InputStream in, OutputStream out, byte[] my_id, MetaInfo metainfo)
throws IOException
{
this.my_id = my_id;
this.metainfo = metainfo;
this.sock = sock;
byte[] id = handshake(in, out);
this.peerID = new PeerID(id, sock.getPeerDestination());
_id = ++__id;
_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating " + _id));
}
/**
* Returns the id of the peer.
*/
public PeerID getPeerID()
{
return peerID;
}
/**
* Returns the String representation of the peerID.
*/
public String toString()
{
if (peerID != null)
return peerID.toString() + ' ' + _id;
else
return "[unknown id] " + _id;
}
/**
* The hash code of a Peer is the hash code of the peerID.
*/
public int hashCode()
{
return peerID.hashCode() ^ (2 << _id);
}
/**
* 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 _id == p._id && peerID.equals(p.peerID);
}
else
return false;
}
/**
* Compares the PeerIDs.
*/
public int compareTo(Object o)
{
Peer p = (Peer)o;
int rv = peerID.compareTo(p.peerID);
if (rv == 0) {
if (_id > p._id) return 1;
else if (_id < p._id) return -1;
else return 0;
} else {
return rv;
}
}
/**
* 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");
_log.debug("Running connection to " + peerID.getAddress().calculateHash().toBase64(), new Exception("connecting"));
try
{
// Do we need to handshake?
if (din == null)
{
sock = I2PSnarkUtil.instance().connect(peerID);
_log.debug("Connected to " + peerID + ": " + sock);
if ((sock == null) || (sock.isClosed())) {
throw new IOException("Unable to reach " + peerID);
}
InputStream in = sock.getInputStream();
OutputStream out = sock.getOutputStream(); //new BufferedOutputStream(sock.getOutputStream());
if (true) {
// buffered output streams are internally synchronized, so we can't get through to the underlying
// I2PSocket's MessageOutputStream to close() it if we are blocking on a write(...). Oh, and the
// buffer is unnecessary anyway, as unbuffered access lets the streaming lib do the 'right thing'.
//out = new BufferedOutputStream(out);
in = new BufferedInputStream(sock.getInputStream());
}
//BufferedInputStream bis
// = new BufferedInputStream(sock.getInputStream());
//BufferedOutputStream bos
// = new BufferedOutputStream(sock.getOutputStream());
byte [] id = handshake(in, out); //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) + "'");
_log.debug("Handshake got matching IDs with " + toString());
} else {
_log.debug("Already have din [" + sock + "] with " + toString());
}
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);
_log.debug("Start running the reader with " + toString());
// Use this thread for running the incomming connection.
// The outgoing connection creates its own Thread.
out.startup();
Thread.currentThread().setName("Snark reader from " + peerID);
s.in.run();
}
catch(IOException eofe)
{
// Ignore, probably just the other side closing the connection.
// Or refusing the connection, timing out, etc.
if (_log.shouldLog(Log.DEBUG))
_log.debug(this.toString(), eofe);
}
catch(Throwable t)
{
_log.error(this + ": " + t.getMessage(), t);
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
finally
{
if (deregister) listener.disconnected(this);
disconnect();
}
}
/**
* Sets DataIn/OutputStreams, does the handshake and returns the id
* reported by the other side.
*/
private byte[] handshake(InputStream in, OutputStream out) //BufferedInputStream bis, BufferedOutputStream bos)
throws IOException
{
din = new DataInputStream(in);
dout = new DataOutputStream(out);
// 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();
_log.debug("Wrote my shared hash and ID to " + toString());
// Handshake read - header
byte b = din.readByte();
if (b != 19)
throw new IOException("Handshake failure, expected 19, got "
+ (b & 0xff) + " on " + sock);
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);
_log.debug("Read the remote side's hash and peerID fully from " + toString());
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();
PeerListener pl = s.listener;
if (pl != null)
pl.disconnected(this);
}
I2PSocket csock = sock;
sock = null;
if ( (csock != null) && (!csock.isClosed()) ) {
try {
csock.close();
} catch (IOException ioe) {
_log.warn("Error disconnecting " + toString(), ioe);
}
}
}
/**
* 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;
}
}
public long getInactiveTime() {
PeerState s = state;
if (s != null) {
PeerConnectionOut out = s.out;
if (out != null)
return System.currentTimeMillis() - out.lastSent;
else
return -1; //"state, no out";
} else {
return -1; //"no state";
}
}
}

View File

@ -0,0 +1,145 @@
/* 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 java.util.Iterator;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* 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 static final Log _log = new Log(PeerAcceptor.class);
private final PeerCoordinator coordinator;
final PeerCoordinatorSet coordinators;
public PeerAcceptor(PeerCoordinator coordinator)
{
this.coordinator = coordinator;
this.coordinators = null;
}
public PeerAcceptor(PeerCoordinatorSet coordinators)
{
this.coordinators = coordinators;
this.coordinator = null;
}
public void connection(I2PSocket socket,
InputStream in, OutputStream out)
throws IOException
{
// 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, but because of how the protocol works, we can get away with just reading
// ahead the first $LOOKAHEAD_SIZE bytes to figure out which infohash they want to
// talk about, and we can just look for that in our list of active torrents.
byte peerInfoHash[] = null;
if (in instanceof BufferedInputStream) {
in.mark(LOOKAHEAD_SIZE);
peerInfoHash = readHash(in);
in.reset();
} else {
// is this working right?
try {
peerInfoHash = readHash(in);
_log.info("infohash read from " + socket.getPeerDestination().calculateHash().toBase64()
+ ": " + Base64.encode(peerInfoHash));
} catch (IOException ioe) {
_log.info("Unable to read the infohash from " + socket.getPeerDestination().calculateHash().toBase64());
throw ioe;
}
in = new SequenceInputStream(new ByteArrayInputStream(peerInfoHash), in);
}
if (coordinator != null) {
// single torrent capability
MetaInfo meta = coordinator.getMetaInfo();
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
if (coordinator.needPeers())
{
Peer peer = new Peer(socket, in, out, coordinator.getID(),
coordinator.getMetaInfo());
coordinator.addPeer(peer);
}
else
socket.close();
} else {
// its for another infohash, but we are only single torrent capable. b0rk.
throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
+ ") while we only support (" + Base64.encode(meta.getInfoHash()) + ")");
}
} else {
// multitorrent capable, so lets see what we can handle
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
PeerCoordinator cur = (PeerCoordinator)iter.next();
MetaInfo meta = cur.getMetaInfo();
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
if (cur.needPeers())
{
Peer peer = new Peer(socket, in, out, cur.getID(),
cur.getMetaInfo());
cur.addPeer(peer);
return;
}
else
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Rejecting new peer for " + cur.snark.torrent);
socket.close();
return;
}
}
}
// this is only reached if none of the coordinators match the infohash
throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
+ ") while we don't support that hash");
}
}
private static final int LOOKAHEAD_SIZE = "19".length() +
"BitTorrent protocol".length() +
8 + // blank, reserved
20; // infohash
/**
* Read ahead to the infohash, throwing an exception if there isn't enough data
*/
private byte[] readHash(InputStream in) throws IOException {
byte buf[] = new byte[LOOKAHEAD_SIZE];
int read = DataHelper.read(in, buf);
if (read != buf.length)
throw new IOException("Unable to read the hash (read " + read + ")");
byte rv[] = new byte[20];
System.arraycopy(buf, buf.length-rv.length-1, rv, 0, rv.length);
return rv;
}
}

View File

@ -0,0 +1,204 @@
/* 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);
coordinator.peerCount = coordinator.peers.size();
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);
coordinator.peerCount = coordinator.peers.size();
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);
coordinator.peerCount = coordinator.peers.size();
}
if (coordinator.halted()) {
cancel();
}
}
}

View File

@ -0,0 +1,186 @@
/* 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.*;
import net.i2p.util.Log;
class PeerConnectionIn implements Runnable
{
private Log _log = new Log(PeerConnectionIn.class);
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();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received keepalive from " + peer + " on " + peer.metainfo.getName());
continue;
}
byte b = din.readByte();
Message m = new Message();
m.type = b;
switch (b)
{
case 0:
ps.chokeMessage(true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received choke from " + peer + " on " + peer.metainfo.getName());
break;
case 1:
ps.chokeMessage(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received unchoke from " + peer + " on " + peer.metainfo.getName());
break;
case 2:
ps.interestedMessage(true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received interested from " + peer + " on " + peer.metainfo.getName());
break;
case 3:
ps.interestedMessage(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received not interested from " + peer + " on " + peer.metainfo.getName());
break;
case 4:
piece = din.readInt();
ps.haveMessage(piece);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received havePiece(" + piece + ") from " + peer + " on " + peer.metainfo.getName());
break;
case 5:
byte[] bitmap = new byte[i-1];
din.readFully(bitmap);
ps.bitfieldMessage(bitmap);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName() + ": size=" + (i-1) + ": " + ps.bitfield);
break;
case 6:
piece = din.readInt();
begin = din.readInt();
len = din.readInt();
ps.requestMessage(piece, begin, len);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received request(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
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);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
}
else
{
// XXX - Consume but throw away afterwards.
piece_bytes = new byte[len];
din.readFully(piece_bytes);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
}
break;
case 8:
piece = din.readInt();
begin = din.readInt();
len = din.readInt();
ps.cancelMessage(piece, begin, len);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
break;
default:
byte[] bs = new byte[i-1];
din.readFully(bs);
ps.unknownMessage(b, bs);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received unknown message from " + peer + " on " + peer.metainfo.getName());
}
}
}
catch (IOException ioe)
{
// Ignore, probably the other side closed connection.
if (_log.shouldLog(Log.INFO))
_log.info("IOError talking with " + peer, ioe);
}
catch (Throwable t)
{
_log.error("Error talking with " + peer, t);
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
finally
{
peer.disconnect();
}
}
}

View File

@ -0,0 +1,399 @@
/* 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.*;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
class PeerConnectionOut implements Runnable
{
private Log _log = new Log(PeerConnectionOut.class);
private final Peer peer;
private final DataOutputStream dout;
private Thread thread;
private boolean quit;
// Contains Messages.
private List sendQueue = new ArrayList();
private static long __id = 0;
private long _id;
long lastSent;
public PeerConnectionOut(Peer peer, DataOutputStream dout)
{
this.peer = peer;
this.dout = dout;
_id = ++__id;
lastSent = System.currentTimeMillis();
quit = false;
}
public void startup() {
thread = new I2PThread(this, "Snark sender " + _id + ": " + peer);
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 && peer.isConnected())
{
Message m = null;
PeerState state = null;
synchronized(sendQueue)
{
while (!quit && peer.isConnected() && sendQueue.isEmpty())
{
try
{
// Make sure everything will reach the other side.
dout.flush();
// Wait till more data arrives.
sendQueue.wait(60*1000);
}
catch (InterruptedException ie)
{
/* ignored */
}
}
state = peer.state;
if (!quit && state != null && peer.isConnected())
{
// 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.
// this should get fixed for starvation
Iterator it = sendQueue.iterator();
while (m == null && it.hasNext())
{
Message nm = (Message)it.next();
if (nm.type == Message.PIECE)
{
if (state.choking) {
it.remove();
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
}
nm = null;
}
else if (nm.type == Message.REQUEST && state.choked)
{
it.remove();
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
nm = null;
}
if (m == null && nm != null)
{
m = nm;
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
it.remove();
}
}
if (m == null && sendQueue.size() > 0) {
m = (Message)sendQueue.remove(0);
SimpleTimer.getInstance().removeEvent(m.expireEvent);
}
}
}
if (m != null)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send " + peer + ": " + m + " on " + peer.metainfo.getName());
m.sendMessage(dout);
lastSent = System.currentTimeMillis();
// 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.
if (_log.shouldLog(Log.INFO))
_log.info("IOError sending to " + peer, ioe);
}
catch (Throwable t)
{
_log.error("Error sending to " + peer, t);
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
finally
{
quit = true;
peer.disconnect();
}
}
public void disconnect()
{
synchronized(sendQueue)
{
//if (quit == true)
// return;
quit = true;
if (thread != null)
thread.interrupt();
sendQueue.clear();
sendQueue.notify();
}
if (dout != null) {
try {
dout.close();
} catch (IOException ioe) {
_log.warn("Error closing the stream to " + peer, ioe);
}
}
}
/**
* Adds a message to the sendQueue and notifies the method waiting
* on the sendQueue to change.
*/
private void addMessage(Message m)
{
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
synchronized(sendQueue)
{
sendQueue.add(m);
sendQueue.notifyAll();
}
}
/** remove messages not sent in 3m */
private static final int SEND_TIMEOUT = 3*60*1000;
private class RemoveTooSlow implements SimpleTimer.TimedEvent {
private Message _m;
public RemoveTooSlow(Message m) {
_m = m;
m.expireEvent = RemoveTooSlow.this;
}
public void timeReached() {
boolean removed = false;
synchronized (sendQueue) {
removed = sendQueue.remove(_m);
sendQueue.notifyAll();
}
if (removed)
_log.info("Took too long to send " + _m + " to " + peer);
}
}
/**
* 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;
}
}
sendQueue.notifyAll();
}
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,585 @@
/* 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;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Coordinates what peer does what.
*/
public class PeerCoordinator implements PeerListener
{
private final Log _log = new Log(PeerCoordinator.class);
final MetaInfo metainfo;
final Storage storage;
final Snark snark;
// 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();
/** estimate of the peers, without requiring any synchronization */
volatile int peerCount;
/** 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 String trackerProblems = null;
public int trackerSeenPeers = 0;
public PeerCoordinator(byte[] id, MetaInfo metainfo, Storage storage,
CoordinatorListener listener, Snark torrent)
{
this.id = id;
this.metainfo = metainfo;
this.storage = storage;
this.listener = listener;
this.snark = torrent;
// 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 Storage getStorage() { return storage; }
public CoordinatorListener getListener() { return listener; }
public byte[] getID()
{
return id;
}
public boolean completed()
{
return storage.complete();
}
public int getPeerCount() { return peerCount; }
public int getPeers()
{
synchronized(peers)
{
int rv = peers.size();
peerCount = rv;
return rv;
}
}
/**
* 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 boolean halted() { return halted; }
public void halt()
{
halted = true;
List removed = new ArrayList();
synchronized(peers)
{
// Stop peer checker task.
timer.cancel();
// Stop peers.
removed.addAll(peers);
peers.clear();
peerCount = 0;
}
while (removed.size() > 0) {
Peer peer = (Peer)removed.remove(0);
peer.disconnect();
removePeerFromPieces(peer);
}
}
public void connected(Peer peer)
{
if (halted)
{
peer.disconnect(false);
return;
}
Peer toDisconnect = null;
synchronized(peers)
{
Peer old = peerIDInList(peer.getPeerID(), peers);
if ( (old != null) && (old.getInactiveTime() > 2*60*1000) ) {
// idle for 2 minutes, kill the old con
peers.remove(old);
toDisconnect = old;
old = null;
}
if (old != null)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Already connected to: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
peer.disconnect(false); // Don't deregister this connection/peer.
}
else
{
if (_log.shouldLog(Log.INFO))
_log.info("New connection to peer: " + peer + " for " + metainfo.getName());
// Add it to the beginning of the list.
// And try to optimistically make it a uploader.
peers.add(0, peer);
peerCount = peers.size();
unchokePeer();
if (listener != null)
listener.peerChange(this, peer);
}
}
if (toDisconnect != null) {
toDisconnect.disconnect(false);
removePeerFromPieces(toDisconnect);
}
}
private static Peer peerIDInList(PeerID pid, List peers)
{
Iterator it = peers.iterator();
while (it.hasNext()) {
Peer cur = (Peer)it.next();
if (pid.sameID(cur.getPeerID()))
return cur;
}
return null;
}
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)
{
_log.debug("Adding a peer " + peer.getPeerID().getAddress().calculateHash().toBase64() + " for " + metainfo.getName(), new Exception("add/run"));
// 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 I2PThread(r, threadName).start();
}
else
if (_log.shouldLog(Log.DEBUG)) {
if (peer.isConnected())
_log.info("Add peer already connected: " + peer);
else
_log.info("MAX_CONNECTIONS = " + MAX_CONNECTIONS
+ " not accepting extra peer: " + peer);
}
}
// (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();
synchronized (peers) {
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 (_log.shouldLog(Log.DEBUG))
_log.debug("Unchoke: " + peer);
peer.setChoking(false);
uploaders++;
// Put peer back at the end of the list.
peers.remove(peer);
peers.add(peer);
peerCount = peers.size();
}
}
}
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) {
if (_log.shouldLog(Log.WARN))
_log.warn("We don't want anything from the peer, as we are halted! peer=" + peer);
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) {
if (_log.shouldLog(Log.WARN))
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
+ " wanted = " + wantedPieces + " peerHas = " + havePieces);
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.stopTorrent();
_log.error("Error reading the storage for " + metainfo.getName(), ioe);
throw new RuntimeException("B0rked");
}
}
/**
* 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) {
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
return true; // We don't actually care anymore.
}
synchronized(wantedPieces)
{
Piece p = new Piece(piece);
if (!wantedPieces.contains(p))
{
_log.info("Got unwanted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
// 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))
{
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
}
else
{
// Oops. We didn't actually download this then... :(
downloaded -= metainfo.getPieceLength(piece);
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
return false; // No need to announce BAD piece to peers.
}
}
catch (IOException ioe)
{
snark.stopTorrent();
_log.error("Error writing storage for " + metainfo.getName(), ioe);
throw new RuntimeException("B0rked");
}
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 (_log.shouldLog(Log.INFO))
_log.info("Got choke(" + choke + "): " + peer);
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 (_log.shouldLog(Log.INFO))
_log.info("Unchoke: " + peer);
}
}
}
}
if (listener != null)
listener.peerChange(this, peer);
}
public void disconnected(Peer peer)
{
if (_log.shouldLog(Log.INFO))
_log.info("Disconnected " + peer, new Exception("Disconnected by"));
synchronized(peers)
{
// Make sure it is no longer in our lists
if (peers.remove(peer))
{
// Unchoke some random other peer
unchokePeer();
removePeerFromPieces(peer);
}
peerCount = peers.size();
}
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,36 @@
package org.klomp.snark;
import java.util.*;
/**
* Hmm, any guesses as to what this is? Used by the multitorrent functionality
* in the PeerAcceptor to pick the right PeerCoordinator to accept the con for.
* Each PeerCoordinator is added to the set from within the Snark (and removed
* from it there too)
*/
public class PeerCoordinatorSet {
private static final PeerCoordinatorSet _instance = new PeerCoordinatorSet();
public static final PeerCoordinatorSet instance() { return _instance; }
private Set _coordinators;
private PeerCoordinatorSet() {
_coordinators = new HashSet();
}
public Iterator iterator() {
synchronized (_coordinators) {
return new ArrayList(_coordinators).iterator();
}
}
public void add(PeerCoordinator coordinator) {
synchronized (_coordinators) {
_coordinators.add(coordinator);
}
}
public void remove(PeerCoordinator coordinator) {
synchronized (_coordinators) {
_coordinators.remove(coordinator);
}
}
}

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,526 @@
/* 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;
import net.i2p.util.Log;
class PeerState
{
private Log _log = new Log(PeerState.class);
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 = 1;
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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv alive");
/* XXX - ignored */
}
void chokeMessage(boolean choke)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv " + (choke ? "" : "un") + "choked");
choked = choke;
if (choked)
resend = true;
listener.gotChoke(peer, choke);
if (!choked && interesting)
request();
}
void interestedMessage(boolean interest)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv " + (interest ? "" : "un")
+ "interested");
interested = interest;
listener.gotInterest(peer, interest);
}
void haveMessage(int piece)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv have(" + piece + ")");
// Sanity check
if (piece < 0 || piece >= metainfo.getPieces())
{
// XXX disconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got strange 'have: " + piece + "' message from " + peer);
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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv bitfield");
if (bitfield != null)
{
// XXX - Be liberal in what you except?
if (_log.shouldLog(Log.WARN))
_log.warn("Got unexpected bitfield message from " + peer);
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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv request("
+ piece + ", " + begin + ", " + length + ") ");
if (choking)
{
if (_log.shouldLog(Log.INFO))
_log.info("Request received, but choking " + peer);
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 (_log.shouldLog(Log.WARN))
_log.warn("Got strange 'request: " + piece
+ ", " + begin
+ ", " + length
+ "' message from " + peer);
return;
}
byte[] pieceBytes = listener.gotRequest(peer, piece);
if (pieceBytes == null)
{
// XXX - Protocol error-> diconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got request for unknown piece: " + piece);
return;
}
// More sanity checks
if (begin >= pieceBytes.length || begin + length > pieceBytes.length)
{
// XXX - Protocol error-> disconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got out of range 'request: " + piece
+ ", " + begin
+ ", " + length
+ "' message from " + peer);
return;
}
if (_log.shouldLog(Log.INFO))
_log.info("Sending (" + piece + ", " + begin + ", "
+ length + ")" + " to " + peer);
out.sendPiece(piece, begin, length, pieceBytes);
}
/**
* 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 (_log.shouldLog(Log.DEBUG))
_log.debug("Got " + req.piece + ": " + peer);
}
else
{
if (_log.shouldLog(Log.WARN))
_log.warn("Got BAD " + req.piece + " from " + peer);
// 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 (_log.shouldLog(Log.DEBUG))
_log.debug("getChunk("
+ piece + "," + begin + "," + length + ") "
+ peer);
int r = getFirstOutstandingRequest(piece);
// Unrequested piece number?
if (r == -1)
{
if (_log.shouldLog(Log.INFO))
_log.info("Unrequested 'piece: " + piece + ", "
+ begin + ", " + length + "' received from "
+ peer);
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 (_log.shouldLog(Log.INFO))
_log.info("Unrequested or unneeded 'piece: "
+ piece + ", "
+ begin + ", "
+ length + "' received from "
+ peer);
downloaded = 0; // XXX - punishment?
return null;
}
// Report missing requests.
if (r != 0)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Some requests dropped, got " + req
+ ", wanted for peer: " + peer);
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 (_log.shouldLog(Log.WARN))
_log.warn("dropped " + dropReq + " with peer " + 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 (_log.shouldLog(Log.DEBUG))
_log.debug("Got cancel message ("
+ piece + ", " + begin + ", " + length + ")");
out.cancelRequest(piece, begin, length);
}
void unknownMessage(int type, byte[] bs)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Warning: Ignoring unknown message type: " + type
+ " length: " + bs.length);
}
void havePiece(int piece)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Tell " + peer + " havePiece(" + piece + ")");
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)
{
synchronized (this) {
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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " requests " + outstandingRequests);
}
// 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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " want piece " + nextPiece);
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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " setInteresting(" + interest + ")");
if (interest != interesting)
{
interesting = interest;
out.sendInterest(interest);
if (interesting && !choked)
request();
}
}
synchronized void setChoking(boolean choke)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " setChoking(" + choke + ")");
if (choking != choke)
{
choking = choke;
out.sendChoke(choke);
}
}
}

View File

@ -0,0 +1,42 @@
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; }
public String toString() {
return String.valueOf(id);
}
}

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,711 @@
/* 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.data.Destination;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.util.I2PThread;
/**
* 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
String activity = "Not started";
private static class OOMListener implements I2PThread.OOMEventListener {
public void outOfMemory(OutOfMemoryError err) {
try {
err.printStackTrace();
I2PSnarkUtil.instance().debug("OOM in the snark", Snark.ERROR, err);
} catch (Throwable t) {
System.out.println("OOM in the OOM");
}
System.exit(0);
}
}
public static void main(String[] args)
{
System.out.println(copyright);
System.out.println();
if ( (args.length > 0) && ("--config".equals(args[0])) ) {
I2PThread.addOOMEventListener(new OOMListener());
SnarkManager sm = SnarkManager.instance();
if (args.length > 1)
sm.loadConfig(args[1]);
System.out.println("Running in multitorrent mode");
while (true) {
try {
synchronized (sm) {
sm.wait();
}
} catch (InterruptedException ie) {}
}
}
// 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(snark.coordinator.peers)
{
System.out.println(snark.coordinator.peers.size()
+ " peers -"
+ " (i)nterested,"
+ " (I)nteresting,"
+ " (c)hoking,"
+ " (C)hoked:");
Iterator it = snark.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: " + snark.meta.getName());
System.out.println("Torrent: " + snark.torrent);
System.out.println("Tracker: " + snark.meta.getAnnounce());
List files = snark.meta.getFiles();
System.out.println("Files: "
+ ((files == null) ? 1 : files.size()));
System.out.println("Pieces: " + snark.meta.getPieces());
System.out.println("Piece size: "
+ snark.meta.getPieceLength(0) / 1024
+ " KB");
System.out.println("Total size: "
+ snark.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();
}
}
public String torrent;
public MetaInfo meta;
public Storage storage;
public PeerCoordinator coordinator;
public ConnectionAcceptor acceptor;
public TrackerClient trackerclient;
public String rootDataDir = ".";
public CompleteListener completeListener;
public boolean stopped;
Snark(String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener) {
this(torrent, ip, user_port, slistener, clistener, true, ".");
}
Snark(String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener, boolean start, String rootDir)
{
if (slistener == null)
slistener = this;
if (clistener == null)
clistener = this;
this.torrent = torrent;
this.rootDataDir = rootDir;
stopped = true;
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 {
Destination d = serversocket.getManager().getSession().getMyDestination();
debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().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(rootDataDir);
}
catch (IOException ioe)
{
try { storage.close(); } catch (IOException ioee) {
ioee.printStackTrace();
}
fatal("Could not create storage", ioe);
}
}
activity = "Collecting pieces";
coordinator = new PeerCoordinator(id, meta, storage, clistener, this);
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
set.add(coordinator);
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
acceptor.startAccepting(set, serversocket);
trackerclient = new TrackerClient(meta, coordinator);
if (start)
startTorrent();
}
/**
* Start up contacting peers and querying the tracker
*/
public void startTorrent() {
stopped = false;
boolean coordinatorChanged = false;
if (coordinator.halted()) {
// ok, we have already started and stopped, but the coordinator seems a bit annoying to
// restart safely, so lets build a new one to replace the old
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
set.remove(coordinator);
PeerCoordinator newCoord = new PeerCoordinator(coordinator.getID(), coordinator.getMetaInfo(),
coordinator.getStorage(), coordinator.getListener(), this);
set.add(newCoord);
coordinator = newCoord;
coordinatorChanged = true;
}
if (!trackerclient.started() && !coordinatorChanged) {
trackerclient.start();
} else if (trackerclient.halted() || coordinatorChanged) {
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
if (!trackerclient.halted())
trackerclient.halt();
trackerclient = newClient;
trackerclient.start();
}
}
/**
* Stop contacting the tracker and talking with peers
*/
public void stopTorrent() {
stopped = true;
TrackerClient tc = trackerclient;
if (tc != null)
tc.halt();
PeerCoordinator pc = coordinator;
if (pc != null)
pc.halt();
Storage st = storage;
if (st != null) {
try {
storage.close();
} catch (IOException ioe) {
System.out.println("Error closing " + torrent);
ioe.printStackTrace();
}
}
if (pc != null)
PeerCoordinatorSet.instance().remove(pc);
}
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;
boolean configured = I2PSnarkUtil.instance().configured();
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];
if (!configured)
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));
}
}
}
}
if (!configured)
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 void fatal(String s)
{
fatal(s, null);
}
/**
* Aborts program abnormally.
*/
public 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();
stopTorrent();
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 storageCompleted(Storage storage)
{
Snark.debug("Completely received " + torrent, Snark.INFO);
//storage.close();
System.out.println("Completely received: " + torrent);
if (completeListener != null)
completeListener.torrentComplete(this);
}
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);
}
public interface CompleteListener {
public void torrentComplete(Snark snark);
}
}

View File

@ -0,0 +1,505 @@
package org.klomp.snark;
import java.io.*;
import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Manage multiple snarks
*/
public class SnarkManager implements Snark.CompleteListener {
private static SnarkManager _instance = new SnarkManager();
public static SnarkManager instance() { return _instance; }
/** map of (canonical) filename to Snark instance (unsynchronized) */
private Map _snarks;
private String _configFile;
private Properties _config;
private I2PAppContext _context;
private Log _log;
private List _messages;
public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
public static final String PROP_I2CP_OPTS = "i2psnark.i2cpOptions";
public static final String PROP_EEP_HOST = "i2psnark.eepHost";
public static final String PROP_EEP_PORT = "i2psnark.eepPort";
public static final String PROP_DIR = "i2psnark.dir";
public static final String PROP_AUTO_START = "i2snark.autoStart";
public static final String DEFAULT_AUTO_START = "false";
private SnarkManager() {
_snarks = new HashMap();
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(SnarkManager.class);
_messages = new ArrayList(16);
loadConfig("i2psnark.config");
int minutes = getStartupDelayMinutes();
_messages.add("Starting up torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
monitor.setDaemon(true);
monitor.start();
}
private static final int MAX_MESSAGES = 5;
public void addMessage(String message) {
synchronized (_messages) {
_messages.add(message);
while (_messages.size() > MAX_MESSAGES)
_messages.remove(0);
}
_log.info("MSG: " + message);
}
/** newest last */
public List getMessages() {
synchronized (_messages) {
return new ArrayList(_messages);
}
}
public boolean shouldAutoStart() {
return Boolean.valueOf(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START+"")).booleanValue();
}
private int getStartupDelayMinutes() { return 1; }
public File getDataDir() {
String dir = _config.getProperty(PROP_DIR);
if ( (dir == null) || (dir.trim().length() <= 0) )
dir = "i2psnark";
return new File(dir);
}
public void loadConfig(String filename) {
_configFile = filename;
if (_config == null)
_config = new Properties();
File cfg = new File(filename);
if (cfg.exists()) {
try {
DataHelper.loadProps(_config, cfg);
} catch (IOException ioe) {
_log.error("Error loading I2PSnark config '" + filename + "'", ioe);
}
}
// now add sane defaults
if (!_config.containsKey(PROP_I2CP_HOST))
_config.setProperty(PROP_I2CP_HOST, "localhost");
if (!_config.containsKey(PROP_I2CP_PORT))
_config.setProperty(PROP_I2CP_PORT, "7654");
if (!_config.containsKey(PROP_EEP_HOST))
_config.setProperty(PROP_EEP_HOST, "localhost");
if (!_config.containsKey(PROP_EEP_PORT))
_config.setProperty(PROP_EEP_PORT, "4444");
if (!_config.containsKey(PROP_DIR))
_config.setProperty(PROP_DIR, "i2psnark");
if (!_config.containsKey(PROP_AUTO_START))
_config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START);
updateConfig();
}
private void updateConfig() {
String i2cpHost = _config.getProperty(PROP_I2CP_HOST);
int i2cpPort = getInt(PROP_I2CP_PORT, 7654);
String opts = _config.getProperty(PROP_I2CP_OPTS);
Map i2cpOpts = new HashMap();
if (opts != null) {
StringTokenizer tok = new StringTokenizer(opts, " ");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split > 0)
i2cpOpts.put(pair.substring(0, split), pair.substring(split+1));
}
}
if (i2cpHost != null) {
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, i2cpPort, i2cpOpts);
_log.debug("Configuring with I2CP options " + i2cpOpts);
}
//I2PSnarkUtil.instance().setI2CPConfig("66.111.51.110", 7654, new Properties());
String eepHost = _config.getProperty(PROP_EEP_HOST);
int eepPort = getInt(PROP_EEP_PORT, 4444);
if (eepHost != null)
I2PSnarkUtil.instance().setProxy(eepHost, eepPort);
getDataDir().mkdirs();
}
private int getInt(String prop, int defaultVal) {
String p = _config.getProperty(prop);
try {
if ( (p != null) && (p.trim().length() > 0) )
return Integer.parseInt(p.trim());
} catch (NumberFormatException nfe) {
// ignore
}
return defaultVal;
}
public void updateConfig(String dataDir, boolean autoStart, String seedPct, String eepHost,
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts) {
boolean changed = false;
if (eepHost != null) {
int port = I2PSnarkUtil.instance().getEepProxyPort();
try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {}
String host = I2PSnarkUtil.instance().getEepProxyHost();
if ( (eepHost.trim().length() > 0) && (port > 0) &&
((!host.equals(eepHost) || (port != I2PSnarkUtil.instance().getEepProxyPort()) )) ) {
I2PSnarkUtil.instance().setProxy(eepHost, port);
changed = true;
_config.setProperty(PROP_EEP_HOST, eepHost);
_config.setProperty(PROP_EEP_PORT, eepPort+"");
addMessage("EepProxy location changed to " + eepHost + ":" + port);
}
}
if (i2cpHost != null) {
int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
int port = oldI2CPPort;
try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
String host = oldI2CPHost;
Map opts = new HashMap();
if (i2cpOpts == null) i2cpOpts = "";
StringTokenizer tok = new StringTokenizer(i2cpOpts, " \t\n");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split > 0)
opts.put(pair.substring(0, split), pair.substring(split+1));
}
Map oldOpts = new HashMap();
String oldI2CPOpts = _config.getProperty(PROP_I2CP_OPTS);
if (oldI2CPOpts == null) oldI2CPOpts = "";
tok = new StringTokenizer(oldI2CPOpts, " \t\n");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split > 0)
oldOpts.put(pair.substring(0, split), pair.substring(split+1));
}
if ( (i2cpHost.trim().length() > 0) && (port > 0) &&
((!host.equals(i2cpHost) ||
(port != I2PSnarkUtil.instance().getI2CPPort()) ||
(!oldOpts.equals(opts)))) ) {
boolean snarksActive = false;
Set names = listTorrentFiles();
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
Snark snark = getTorrent((String)iter.next());
if ( (snark != null) && (!snark.stopped) ) {
snarksActive = true;
break;
}
}
if (snarksActive) {
addMessage("Cannot change the I2CP settings while torrents are active");
_log.debug("i2cp host [" + i2cpHost + "] i2cp port " + port + " opts [" + opts
+ "] oldOpts [" + oldOpts + "]");
} else {
if (I2PSnarkUtil.instance().connected()) {
I2PSnarkUtil.instance().disconnect();
addMessage("Disconnecting old I2CP destination");
}
Properties p = new Properties();
p.putAll(opts);
addMessage("I2CP settings changed to " + i2cpHost + ":" + port + " (" + i2cpOpts.trim() + ")");
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, port, p);
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) {
addMessage("Unable to connect with the new settings, reverting to the old I2CP settings");
I2PSnarkUtil.instance().setI2CPConfig(oldI2CPHost, oldI2CPPort, oldOpts);
ok = I2PSnarkUtil.instance().connect();
if (!ok)
addMessage("Unable to reconnect with the old settings!");
} else {
addMessage("Reconnected on the new I2CP destination");
_config.setProperty(PROP_I2CP_HOST, i2cpHost.trim());
_config.setProperty(PROP_I2CP_PORT, "" + port);
_config.setProperty(PROP_I2CP_OPTS, i2cpOpts.trim());
changed = true;
// no PeerAcceptors/I2PServerSockets to deal with, since all snarks are inactive
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = getTorrent(name);
if ( (snark != null) && (snark.acceptor != null) ) {
snark.acceptor.restart();
addMessage("I2CP listener restarted for " + snark.meta.getName());
}
}
}
}
changed = true;
}
}
if (shouldAutoStart() != autoStart) {
_config.setProperty(PROP_AUTO_START, autoStart + "");
addMessage("Adjusted autostart to " + autoStart);
changed = true;
}
if (changed) {
saveConfig();
} else {
addMessage("Configuration unchanged");
}
}
public void saveConfig() {
try {
DataHelper.storeProps(_config, new File(_configFile));
} catch (IOException ioe) {
addMessage("Unable to save the config to '" + _configFile + "'");
}
}
public Properties getConfig() { return _config; }
/** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */
private static final int MAX_FILES_PER_TORRENT = 128;
/** set of filenames that we are dealing with */
public Set listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } }
/**
* Grab the torrent given the (canonical) filename
*/
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
public void addTorrent(String filename) { addTorrent(filename, false); }
public void addTorrent(String filename, boolean dontAutoStart) {
if (!I2PSnarkUtil.instance().connected()) {
addMessage("Connecting to I2P");
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) {
addMessage("Error connecting to I2P - check your I2CP settings");
return;
}
}
File sfile = new File(filename);
try {
filename = sfile.getCanonicalPath();
} catch (IOException ioe) {
_log.error("Unable to add the torrent " + filename, ioe);
addMessage("ERR: Could not add the torrent '" + filename + "': " + ioe.getMessage());
return;
}
File dataDir = getDataDir();
Snark torrent = null;
synchronized (_snarks) {
torrent = (Snark)_snarks.get(filename);
if (torrent == null) {
FileInputStream fis = null;
try {
fis = new FileInputStream(sfile);
MetaInfo info = new MetaInfo(fis);
fis.close();
fis = null;
String rejectMessage = locked_validateTorrent(info);
if (rejectMessage != null) {
sfile.delete();
addMessage(rejectMessage);
return;
} else {
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
torrent.completeListener = this;
_snarks.put(filename, torrent);
}
} catch (IOException ioe) {
addMessage("Torrent in " + sfile.getName() + " is invalid: " + ioe.getMessage());
if (sfile.exists())
sfile.delete();
return;
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
} else {
return;
}
}
// ok, snark created, now lets start it up or configure it further
File f = new File(filename);
if (!dontAutoStart && shouldAutoStart()) {
torrent.startTorrent();
addMessage("Torrent added and started: '" + f.getName() + "'");
} else {
addMessage("Torrent added: '" + f.getName() + "'");
}
}
private String locked_validateTorrent(MetaInfo info) throws IOException {
List files = info.getFiles();
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
return "Too many files in " + info.getName() + " (" + files.size() + "), deleting it";
} else if (info.getPieces() <= 0) {
return "No pieces in " + info.getName() + "? deleting it";
} else if (info.getPieceLength(0) > 10*1024*1024) {
return "Pieces are too large in " + info.getName() + " (" + info.getPieceLength(0)/1024 + "KB, deleting it";
} else if (info.getTotalLength() > 10*1024*1024*1024l) {
System.out.println("torrent info: " + info.toString());
List lengths = info.getLengths();
if (lengths != null)
for (int i = 0; i < lengths.size(); i++)
System.out.println("File " + i + " is " + lengths.get(i) + " long");
return "Torrents larger than 10GB are not supported yet (because we're paranoid): " + info.getName() + ", deleting it";
} else {
// ok
return null;
}
}
/**
* Stop the torrent, leaving it on the list of torrents unless told to remove it
*/
public Snark stopTorrent(String filename, boolean shouldRemove) {
File sfile = new File(filename);
try {
filename = sfile.getCanonicalPath();
} catch (IOException ioe) {
_log.error("Unable to remove the torrent " + filename, ioe);
addMessage("ERR: Could not remove the torrent '" + filename + "': " + ioe.getMessage());
return null;
}
int remaining = 0;
Snark torrent = null;
synchronized (_snarks) {
if (shouldRemove)
torrent = (Snark)_snarks.remove(filename);
else
torrent = (Snark)_snarks.get(filename);
remaining = _snarks.size();
}
if (torrent != null) {
boolean wasStopped = torrent.stopped;
torrent.stopTorrent();
if (remaining == 0) {
// should we disconnect/reconnect here (taking care to deal with the other thread's
// I2PServerSocket.accept() call properly?)
////I2PSnarkUtil.instance().
}
if (!wasStopped)
addMessage("Torrent stopped: '" + sfile.getName() + "'");
}
return torrent;
}
/**
* Stop the torrent and delete the torrent file itself, but leaving the data
* behind.
*/
public void removeTorrent(String filename) {
Snark torrent = stopTorrent(filename, true);
if (torrent != null) {
File torrentFile = new File(filename);
torrentFile.delete();
addMessage("Torrent removed: '" + torrentFile.getName() + "'");
}
}
private class DirMonitor implements Runnable {
public void run() {
try { Thread.sleep(60*1000*getStartupDelayMinutes()); } catch (InterruptedException ie) {}
// the first message was a "We are starting up in 1m"
synchronized (_messages) {
if (_messages.size() == 1)
_messages.remove(0);
}
while (true) {
File dir = getDataDir();
_log.debug("Directory Monitor loop over " + dir.getAbsolutePath());
try {
monitorTorrents(dir);
} catch (Exception e) {
_log.error("Error in the DirectoryMonitor", e);
}
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
}
}
}
public void torrentComplete(Snark snark) {
File f = new File(snark.torrent);
long len = snark.meta.getTotalLength();
addMessage("Download complete of " + f.getName()
+ (len < 5*1024*1024 ? " (size: " + (len/1024) + "KB)" : " (size: " + (len/(1024*1024l)) + "MB)"));
}
private void monitorTorrents(File dir) {
String fileNames[] = dir.list(TorrentFilenameFilter.instance());
List foundNames = new ArrayList(0);
if (fileNames != null) {
for (int i = 0; i < fileNames.length; i++) {
try {
foundNames.add(new File(dir, fileNames[i]).getCanonicalPath());
} catch (IOException ioe) {
_log.error("Error resolving '" + fileNames[i] + "' in '" + dir, ioe);
}
}
}
Set existingNames = listTorrentFiles();
// lets find new ones first...
for (int i = 0; i < foundNames.size(); i++) {
if (existingNames.contains(foundNames.get(i))) {
// already known. noop
} else {
if (I2PSnarkUtil.instance().connect())
addTorrent((String)foundNames.get(i));
else
addMessage("Unable to connect to I2P");
}
}
// now lets see which ones have been removed...
for (Iterator iter = existingNames.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
if (foundNames.contains(name)) {
// known and still there. noop
} else {
// known, but removed. drop it
stopTorrent(name, true);
}
}
}
private static final String DEFAULT_TRACKERS[] = {
"Postman's tracker", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php"
, "Orion's tracker", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt"
// , "The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php"
};
/** comma delimited list of name=announceURL for the trackers to be displayed */
public static final String PROP_TRACKERS = "i2psnark.trackers";
/** unordered map of announceURL to name */
public Map getTrackers() {
HashMap rv = new HashMap();
String trackers = _config.getProperty(PROP_TRACKERS);
if ( (trackers == null) || (trackers.trim().length() <= 0) )
trackers = _context.getProperty(PROP_TRACKERS);
if ( (trackers == null) || (trackers.trim().length() <= 0) ) {
for (int i = 0; i < DEFAULT_TRACKERS.length; i += 2)
rv.put(DEFAULT_TRACKERS[i+1], DEFAULT_TRACKERS[i]);
} else {
StringTokenizer tok = new StringTokenizer(trackers, ",");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split <= 0)
continue;
String name = pair.substring(0, split).trim();
String url = pair.substring(split+1).trim();
if ( (name.length() > 0) && (url.length() > 0) )
rv.put(url, name);
}
}
return rv;
}
private static class TorrentFilenameFilter implements FilenameFilter {
private static final TorrentFilenameFilter _filter = new TorrentFilenameFilter();
public static TorrentFilenameFilter instance() { return _filter; }
public boolean accept(File dir, String name) {
return (name != null) && (name.endsWith(".torrent"));
}
}
}

View File

@ -0,0 +1,92 @@
/* 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;
import net.i2p.util.I2PThread;
/**
* Makes sure everything ends correctly when shutting down.
*/
public class SnarkShutdown extends I2PThread
{
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)
{
I2PSnarkUtil.instance().debug("Couldn't properly close storage", Snark.ERROR, ioe);
throw new RuntimeException("b0rking");
}
}
// 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,588 @@
/* 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;
import net.i2p.crypto.SHA1;
/**
* 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; // BitField to represent the pieces
private int needed; // Number of pieces 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(), null, files,
lengthsList, piece_size, piece_hashes, total);
}
// Creates piece hases for a new storage.
public void create() throws IOException
{
if (true) {
fast_digestCreate();
} else {
orig_digestCreate();
}
}
private void orig_digestCreate() 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 fast_digestCreate() throws IOException {
// Calculate piece_hashes
SHA1 digest = new SHA1();
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(String rootDir) throws IOException
{
File base = new File(rootDir, 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();
if (base.exists() && !base.canWrite()) // hope we can get away with this, if we are only seeding...
rafs[0] = new RandomAccessFile(base, "r");
else
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];
if (f.exists() && !f.canWrite()) // see above re: only seeding
rafs[i] = new RandomAccessFile(f, "r");
else
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);
if (needed <= 0)
listener.storageCompleted(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
{
if (rafs == null) return;
for (int i = 0; i < rafs.length; i++)
{
try {
synchronized(rafs[i])
{
rafs[i].close();
}
} catch (IOException ioe) {
I2PSnarkUtil.instance().debug("Error closing " + rafs[i], Snark.ERROR, ioe);
// gobble gobble
}
}
}
/**
* 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;
}
}
// Early typecast, avoid possibly overflowing a temp integer
long start = (long) piece * (long) 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;
}
}
if (complete) {
listener.storageCompleted(this);
// do we also need to close all of the files and reopen
// them readonly?
}
return true;
}
private int getUncheckedPiece(int piece, byte[] bs, int off)
throws IOException
{
// XXX - copy/paste code from putPiece().
// Early typecast, avoid possibly overflowing a temp integer
long start = (long) piece * (long) 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,58 @@
/* 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);
/**
* Called the one time when the data is completely received and checked.
*
*/
void storageCompleted(Storage storage);
}

View File

@ -0,0 +1,317 @@
/* 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.*;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Informs metainfo tracker of events and gets new peers for peer
* coordinator.
*
* @author Mark Wielaard (mark@klomp.org)
*/
public class TrackerClient extends I2PThread
{
private static final Log _log = new Log(TrackerClient.class);
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 boolean started;
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;
started = false;
}
public void start() {
if (stop) throw new RuntimeException("Dont rerun me, create a copy");
super.start();
started = true;
}
public boolean halted() { return stop; }
public boolean started() { return started; }
/**
* Interrupts this Thread to stop it.
*/
public void halt()
{
stop = true;
this.interrupt();
}
private boolean verifyConnected() {
while (!stop && !I2PSnarkUtil.instance().connected()) {
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) {
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
}
}
return !stop && I2PSnarkUtil.instance().connected();
}
public void run()
{
// XXX - Support other IPs
String announce = meta.getAnnounce(); //I2PSnarkUtil.instance().rewriteAnnounce(meta.getAnnounce());
String infoHash = urlencode(meta.getInfoHash());
String peerID = urlencode(coordinator.getID());
_log.debug("Announce: [" + meta.getAnnounce() + "] infoHash: " + infoHash
+ " xmitAnnounce: [" + announce + "]");
long uploaded = coordinator.getUploaded();
long downloaded = coordinator.getDownloaded();
long left = coordinator.getLeft();
boolean completed = (left == 0);
try
{
if (!verifyConnected()) return;
boolean started = false;
while (!started)
{
try
{
// Send start.
TrackerInfo info = doRequest(announce, infoHash, peerID,
uploaded, downloaded, left,
STARTED_EVENT);
Set peers = info.getPeers();
coordinator.trackerSeenPeers = peers.size();
if (!completed) {
Iterator it = peers.iterator();
while (it.hasNext()) {
Peer cur = (Peer)it.next();
coordinator.addPeer(cur);
int delay = 3000;
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
}
}
started = true;
coordinator.trackerProblems = null;
}
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);
coordinator.trackerProblems = ioe.getMessage();
}
if (!started && !stop)
{
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
try
{
// Sleep one minutes...
Thread.sleep(60*1000);
}
catch(InterruptedException interrupt)
{
// ignore
}
}
}
Random r = new Random();
while(!stop)
{
try
{
// Sleep some minutes...
int delay = SLEEP*60*1000 + r.nextInt(120*1000);
Thread.sleep(delay);
}
catch(InterruptedException interrupt)
{
// ignore
}
if (stop)
break;
if (!verifyConnected()) return;
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);
Set peers = info.getPeers();
coordinator.trackerSeenPeers = peers.size();
if ( (left > 0) && (!completed) ) {
// we only want to talk to new people if we need things
// from them (duh)
List ordered = new ArrayList(peers);
Collections.shuffle(ordered);
Iterator it = ordered.iterator();
while (it.hasNext()) {
Peer cur = (Peer)it.next();
coordinator.addPeer(cur);
int delay = 3000;
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
}
}
}
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)
{
I2PSnarkUtil.instance().debug("TrackerClient: " + t, Snark.ERROR, t);
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
finally
{
try
{
if (!verifyConnected()) return;
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() + ".i2p"
+ "&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);
}
InputStream in = null;
try {
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;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
fetched.delete();
}
}
/**
* 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,193 @@
/* 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());
}
}
/** return the untyped value */
public Object getValue() { return value; }
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,193 @@
/* 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 if (o instanceof BEValue)
bencode(((BEValue)o).getValue(), 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);
}
}

View File

@ -0,0 +1,638 @@
package org.klomp.snark.web;
import java.io.*;
import java.util.*;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServlet;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import org.klomp.snark.*;
/**
*
*/
public class I2PSnarkServlet extends HttpServlet {
private I2PAppContext _context;
private Log _log;
private SnarkManager _manager;
private static long _nonce;
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
public void init(ServletConfig cfg) throws ServletException {
super.init(cfg);
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(I2PSnarkServlet.class);
_nonce = _context.random().nextLong();
_manager = SnarkManager.instance();
String configFile = _context.getProperty(PROP_CONFIG_FILE);
if ( (configFile == null) || (configFile.trim().length() <= 0) )
configFile = "i2psnark.config";
_manager.loadConfig(configFile);
}
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html; charset=UTF-8");
String nonce = req.getParameter("nonce");
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
processRequest(req);
PrintWriter out = resp.getWriter();
out.write(HEADER_BEGIN);
// we want it to go to the base URI so we don't refresh with some funky action= value
out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + "\">\n");
out.write(HEADER);
out.write("<table border=\"0\" width=\"100%\">\n");
out.write("<tr><td width=\"5%\" class=\"snarkTitle\" valign=\"top\" align=\"left\">");
out.write("I2PSnark<br />\n");
out.write("<a href=\"" + req.getRequestURI() + "\" class=\"snarkRefresh\">Refresh</a>\n");
out.write("</td><td width=\"95%\" class=\"snarkMessages\" valign=\"top\" align=\"left\"><pre>");
List msgs = _manager.getMessages();
for (int i = msgs.size()-1; i >= 0; i--) {
String msg = (String)msgs.get(i);
out.write(msg + "\n");
}
out.write("</pre></td></tr></table>\n");
out.write(TABLE_HEADER);
List snarks = getSortedSnarks(req);
String uri = req.getRequestURI();
for (int i = 0; i < snarks.size(); i++) {
Snark snark = (Snark)snarks.get(i);
displaySnark(out, snark, uri, i);
}
if (snarks.size() <= 0) {
out.write(TABLE_EMPTY);
}
out.write(TABLE_FOOTER);
writeAddForm(out, req);
if (true) // seeding needs to register the torrent first, so we can't start it automatically (boo, hiss)
writeSeedForm(out, req);
writeConfigForm(out, req);
out.write(FOOTER);
}
/**
* Do what they ask, adding messages to _manager.addMessage as necessary
*/
private void processRequest(HttpServletRequest req) {
String action = req.getParameter("action");
if (action == null) {
// noop
} else if ("Add torrent".equals(action)) {
String newFile = req.getParameter("newFile");
String newURL = req.getParameter("newURL");
File f = null;
if ( (newFile != null) && (newFile.trim().length() > 0) )
f = new File(newFile.trim());
if ( (f != null) && (!f.exists()) ) {
_manager.addMessage("Torrent file " + newFile +" does not exist");
}
if ( (f != null) && (f.exists()) ) {
File local = new File(_manager.getDataDir(), f.getName());
String canonical = null;
try {
canonical = local.getCanonicalPath();
if (local.exists()) {
if (_manager.getTorrent(canonical) != null)
_manager.addMessage("Torrent already running: " + newFile);
else
_manager.addMessage("Torrent already in the queue: " + newFile);
} else {
boolean ok = FileUtil.copy(f.getAbsolutePath(), local.getAbsolutePath(), true);
if (ok) {
_manager.addMessage("Copying torrent to " + local.getAbsolutePath());
_manager.addTorrent(canonical);
} else {
_manager.addMessage("Unable to copy the torrent to " + local.getAbsolutePath() + " from " + f.getAbsolutePath());
}
}
} catch (IOException ioe) {
_log.warn("hrm: " + local, ioe);
}
} else if ( (newURL != null) && (newURL.trim().length() > "http://.i2p/".length()) ) {
_manager.addMessage("Fetching " + newURL);
I2PThread fetch = new I2PThread(new FetchAndAdd(newURL), "Fetch and add");
fetch.start();
} else {
// no file or URL specified
}
} else if ("Stop".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
_manager.stopTorrent(name, false);
break;
}
}
}
}
} else if ("Start".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
snark.startTorrent();
_manager.addMessage("Starting up torrent " + name);
break;
}
}
}
}
} else if ("Remove".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
_manager.stopTorrent(name, true);
// should we delete the torrent file?
// yeah, need to, otherwise it'll get autoadded again (at the moment
File f = new File(name);
f.delete();
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
break;
}
}
}
}
} else if ("Delete".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
_manager.stopTorrent(name, true);
File f = new File(name);
f.delete();
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
List files = snark.meta.getFiles();
String dataFile = snark.meta.getName();
for (int i = 0; files != null && i < files.size(); i++) {
// multifile torrents have the getFiles() return lists of lists of filenames, but
// each of those lists just contain a single file afaict...
File df = new File(_manager.getDataDir(), files.get(i).toString());
boolean deleted = FileUtil.rmdir(df, false);
if (deleted)
_manager.addMessage("Data dir deleted: " + df.getAbsolutePath());
else
_manager.addMessage("Data dir could not be deleted: " + df.getAbsolutePath());
}
if (dataFile != null) {
f = new File(_manager.getDataDir(), dataFile);
boolean deleted = f.delete();
if (deleted)
_manager.addMessage("Data file deleted: " + f.getAbsolutePath());
else
_manager.addMessage("Data file could not be deleted: " + f.getAbsolutePath());
}
break;
}
}
}
}
} else if ("Save configuration".equals(action)) {
String dataDir = req.getParameter("dataDir");
boolean autoStart = req.getParameter("autoStart") != null;
String seedPct = req.getParameter("seedPct");
String eepHost = req.getParameter("eepHost");
String eepPort = req.getParameter("eepPort");
String i2cpHost = req.getParameter("i2cpHost");
String i2cpPort = req.getParameter("i2cpPort");
String i2cpOpts = req.getParameter("i2cpOpts");
_manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts);
} else if ("Create torrent".equals(action)) {
String baseData = req.getParameter("baseFile");
if (baseData != null) {
File baseFile = new File(_manager.getDataDir(), baseData);
String announceURL = req.getParameter("announceURL");
String announceURLOther = req.getParameter("announceURLOther");
if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
announceURL = announceURLOther;
if (baseFile.exists() && baseFile.isFile()) {
try {
Storage s = new Storage(baseFile, announceURL, null);
s.create();
MetaInfo info = s.getMetaInfo();
File torrentFile = new File(baseFile.getParent(), baseFile.getName() + ".torrent");
if (torrentFile.exists())
throw new IOException("Cannot overwrite an existing .torrent file: " + torrentFile.getPath());
FileOutputStream out = new FileOutputStream(torrentFile);
out.write(info.getTorrentData());
out.close();
_manager.addMessage("Torrent created for " + baseFile.getName() + ": " + torrentFile.getAbsolutePath());
// now fire it up, but don't automatically seed it
_manager.addTorrent(torrentFile.getCanonicalPath(), false);
_manager.addMessage("Many I2P trackers require you to register new torrents before seeding - please do so before starting " + baseFile.getName());
} catch (IOException ioe) {
_manager.addMessage("Error creating a torrent for " + baseFile.getAbsolutePath() + ": " + ioe.getMessage());
}
} else if (baseFile.exists()) {
_manager.addMessage("I2PSnark doesn't yet support creating multifile torrents");
} else {
_manager.addMessage("Cannot create a torrent for the nonexistant data: " + baseFile.getAbsolutePath());
}
}
}
}
private class FetchAndAdd implements Runnable {
private String _url;
public FetchAndAdd(String url) {
_url = url;
}
public void run() {
_url = _url.trim();
File file = I2PSnarkUtil.instance().get(_url, false);
try {
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
_manager.addMessage("Torrent fetched from " + _url);
FileInputStream in = null;
try {
in = new FileInputStream(file);
MetaInfo info = new MetaInfo(in);
String name = info.getName();
name = name.replace('/', '_');
name = name.replace('\\', '_');
name = name.replace('&', '+');
name = name.replace('\'', '_');
name = name.replace('"', '_');
name = name.replace('`', '_');
name = name + ".torrent";
File torrentFile = new File(_manager.getDataDir(), name);
String canonical = torrentFile.getCanonicalPath();
if (torrentFile.exists()) {
if (_manager.getTorrent(canonical) != null)
_manager.addMessage("Torrent already running: " + name);
else
_manager.addMessage("Torrent already in the queue: " + name);
} else {
FileUtil.copy(file.getAbsolutePath(), canonical, true);
_manager.addTorrent(canonical);
}
} catch (IOException ioe) {
_manager.addMessage("Torrent at " + _url + " was not valid: " + ioe.getMessage());
} finally {
try { in.close(); } catch (IOException ioe) {}
}
} else {
_manager.addMessage("Torrent was not retrieved from " + _url);
}
} finally {
if (file != null) file.delete();
}
}
}
private List getSortedSnarks(HttpServletRequest req) {
Set files = _manager.listTorrentFiles();
TreeSet fileNames = new TreeSet(files); // sorts it alphabetically
ArrayList rv = new ArrayList(fileNames.size());
for (Iterator iter = fileNames.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if (snark != null)
rv.add(snark);
}
return rv;
}
private static final int MAX_DISPLAYED_FILENAME_LENGTH = 60;
private void displaySnark(PrintWriter out, Snark snark, String uri, int row) throws IOException {
String filename = snark.torrent;
File f = new File(filename);
filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH)
filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "...";
long total = snark.meta.getTotalLength();
// Early typecast, avoid possibly overflowing a temp integer
long remaining = (long) snark.storage.needed() * (long) snark.meta.getPieceLength(0);
if (remaining > total)
remaining = total;
int totalBps = 4096; // should probably grab this from the snark...
long remainingSeconds = remaining / totalBps;
long uploaded = snark.coordinator.getUploaded();
boolean isRunning = !snark.stopped;
boolean isValid = snark.meta != null;
boolean singleFile = snark.meta.getFiles() == null;
String err = snark.coordinator.trackerProblems;
int curPeers = snark.coordinator.getPeerCount();
int knownPeers = snark.coordinator.trackerSeenPeers;
String statusString = "Unknown";
if (err != null) {
if (isRunning)
statusString = "TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
else
statusString = "TrackerErr (" + err + ")";
} else if (remaining <= 0) {
if (isRunning)
statusString = "Seeding (" + curPeers + "/" + knownPeers + " peers)";
else
statusString = "Complete";
} else {
if (isRunning)
statusString = "OK (" + curPeers + "/" + knownPeers + " peers)";
else
statusString = "Stopped";
}
String rowClass = (row % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd");
out.write("<tr class=\"" + rowClass + "\">");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentStatus " + rowClass + "\">");
out.write(statusString + "</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentName " + rowClass + "\">");
if (remaining == 0)
out.write("<a href=\"file:///" + _manager.getDataDir().getAbsolutePath() + File.separatorChar + snark.meta.getName()
+ "\" title=\"Download the completed file\">");
out.write(filename);
if (remaining == 0)
out.write("</a>");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
if (remaining > 0) {
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
// lets hold off on the ETA until we have rates sorted...
//out.write(" (eta " + DataHelper.formatDuration(remainingSeconds*1000) + ")"); // (eta 6h)
} else {
out.write(formatSize(total)); // 3GB
}
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentUploaded " + rowClass
+ "\">" + formatSize(uploaded) + "</td>\n\t");
//out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentRate\">");
//out.write("n/a"); //2KBps/12KBps/4KBps
//out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentAction " + rowClass + "\">");
if (isRunning) {
out.write("<a href=\"" + uri + "?action=Stop&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
+ "\" title=\"Stop the torrent\">Stop</a>");
} else {
if (isValid)
out.write("<a href=\"" + uri + "?action=Start&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
+ "\" title=\"Start the torrent\">Start</a> ");
out.write("<a href=\"" + uri + "?action=Remove&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
+ "\" title=\"Remove the torrent from the active list, deleting the .torrent file\">Remove</a><br />");
out.write("<a href=\"" + uri + "?action=Delete&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
+ "\" title=\"Delete the .torrent file and the associated data file(s)\">Delete</a> ");
}
out.write("</td>\n</tr>\n");
}
private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
String uri = req.getRequestURI();
String newURL = req.getParameter("newURL");
if ( (newURL == null) || (newURL.trim().length() <= 0) ) newURL = "http://";
String newFile = req.getParameter("newFile");
if ( (newFile == null) || (newFile.trim().length() <= 0) ) newFile = "";
out.write("<span class=\"snarkNewTorrent\">\n");
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
out.write("From URL&nbsp;: <input type=\"text\" name=\"newURL\" size=\"50\" value=\"" + newURL + "\" /> \n");
// not supporting from file at the moment, since the file name passed isn't always absolute (so it may not resolve)
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
out.write("<input type=\"submit\" value=\"Add torrent\" name=\"action\" /><br />\n");
out.write("<span class=\"snarkAddInfo\">Alternately, you can copy .torrent files to " + _manager.getDataDir().getAbsolutePath() + "<br />\n");
out.write("Removing that .torrent file will cause the torrent to stop.<br /></span>\n");
out.write("</form>\n</span>\n");
}
private void writeSeedForm(PrintWriter out, HttpServletRequest req) throws IOException {
String uri = req.getRequestURI();
String baseFile = req.getParameter("baseFile");
if (baseFile == null)
baseFile = "";
out.write("<span class=\"snarkNewTorrent\">\n");
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
out.write("Data to seed: " + _manager.getDataDir().getAbsolutePath() + File.separatorChar
+ "<input type=\"text\" name=\"baseFile\" size=\"20\" value=\"" + baseFile
+ "\" title=\"File to seed (must be within the specified path)\" /><br />\n");
out.write("Tracker: <select name=\"announceURL\"><option value=\"\">Select a tracker</option>\n");
Map trackers = sort(_manager.getTrackers());
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String announceURL = (String)trackers.get(name);
// we inject whitespace in sort(...) to guarantee uniqueness, but we can strip it off here
out.write("\t<option value=\"" + announceURL + "\">" + name.trim() + "</option>\n");
}
out.write("</select>\n");
out.write("or <input type=\"text\" name=\"announceURLOther\" size=\"50\" value=\"http://\" " +
"title=\"Custom tracker URL\" /> ");
out.write("<input type=\"submit\" value=\"Create torrent\" name=\"action\" />\n");
out.write("</form>\n</span>\n");
}
private Map sort(Map trackers) {
TreeMap rv = new TreeMap();
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
String url = (String)iter.next();
String name = (String)trackers.get(url);
while (rv.containsKey(name))
name = name + " ";
rv.put(name, url);
}
return rv;
}
private void writeConfigForm(PrintWriter out, HttpServletRequest req) throws IOException {
String uri = req.getRequestURI();
String dataDir = _manager.getDataDir().getAbsolutePath();
boolean autoStart = _manager.shouldAutoStart();
int seedPct = 0;
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
out.write("<span class=\"snarkConfig\"><hr />\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
out.write("<span class=\"snarkConfigTitle\">Configuration:</span><br />\n");
out.write("Data directory: <input type=\"text\" size=\"40\" name=\"dataDir\" value=\"" + dataDir + "\" ");
out.write("title=\"Directory to store torrents and data\" disabled=\"true\" /><br />\n");
out.write("Auto start: <input type=\"checkbox\" name=\"autoStart\" value=\"true\" "
+ (autoStart ? "checked " : "")
+ "title=\"If true, automatically start torrents that are added\" />");
//Auto add: <input type="checkbox" name="autoAdd" value="true" title="If true, automatically add torrents that are found in the data directory" />
//Auto stop: <input type="checkbox" name="autoStop" value="true" title="If true, automatically stop torrents that are removed from the data directory" />
//out.write("<br />\n");
out.write("Seed percentage: <select name=\"seedPct\" disabled=\"true\" >\n\t");
if (seedPct <= 0)
out.write("<option value=\"0\" selected=\"true\">Unlimited</option>\n\t");
else
out.write("<option value=\"0\">Unlimited</option>\n\t");
if (seedPct == 100)
out.write("<option value=\"100\" selected=\"true\">100%</option>\n\t");
else
out.write("<option value=\"100\">100%</option>\n\t");
if (seedPct == 150)
out.write("<option value=\"150\" selected=\"true\">150%</option>\n\t");
else
out.write("<option value=\"150\">150%</option>\n\t");
out.write("</select><br />\n");
//out.write("<hr />\n");
out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
+ I2PSnarkUtil.instance().getEepProxyHost() + "\" size=\"15\" /> ");
out.write("port: <input type=\"text\" name=\"eepPort\" value=\""
+ I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" /><br />\n");
out.write("I2CP host: <input type=\"text\" name=\"i2cpHost\" value=\""
+ I2PSnarkUtil.instance().getI2CPHost() + "\" size=\"15\" /> ");
out.write("port: <input type=\"text\" name=\"i2cpPort\" value=\"" +
+ I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" /> <br />\n");
StringBuffer opts = new StringBuffer(64);
Map options = new TreeMap(I2PSnarkUtil.instance().getI2CPOptions());
for (Iterator iter = options.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = (String)options.get(key);
opts.append(key).append('=').append(val).append(' ');
}
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"40\" value=\""
+ opts.toString() + "\" /><br />\n");
out.write("<input type=\"submit\" value=\"Save configuration\" name=\"action\" />\n");
out.write("</span>\n");
out.write("</form>\n");
}
private String formatSize(long bytes) {
if (bytes < 5*1024)
return bytes + "B";
else if (bytes < 5*1024*1024)
return (bytes/1024) + "KB";
else if (bytes < 5*1024*1024*1024l)
return (bytes/(1024*1024)) + "MB";
else
return (bytes/(1024*1024*1024)) + "GB";
}
private static final String HEADER_BEGIN = "<html>\n" +
"<head>\n" +
"<title>I2PSnark - anonymous bittorrent</title>\n";
private static final String HEADER = "<style>\n" +
"body {\n" +
" background-color: #C7CFB4;\n" +
"}\n" +
".snarkTitle {\n" +
" text-align: left;\n" +
" float: left;\n" +
" margin: 0px 0px 5px 5px;\n" +
" display: inline;\n" +
" font-size: 16pt;\n" +
" font-weight: bold;\n" +
"}\n" +
".snarkRefresh {\n" +
" font-size: 10pt;\n" +
"}\n" +
".snarkMessages {\n" +
" border: none;\n" +
" background-color: #CECFC6;\n" +
" font-family: monospace;\n" +
" font-size: 10pt;\n" +
" font-weight: 100;\n" +
" width: 100%;\n" +
" text-align: left;\n" +
" margin: 0px 0px 0px 0px;\n" +
" border: 0px;\n" +
" padding: 5px;\n" +
" border-width: 0px;\n" +
" border-spacing: 0px;\n" +
"}\n" +
"table {\n" +
" margin: 0px 0px 0px 0px;\n" +
" border: 0px;\n" +
" padding: 0px;\n" +
" border-width: 0px;\n" +
" border-spacing: 0px;\n" +
"}\n" +
"th {\n" +
" background-color: #C7D5D5;\n" +
" margin: 0px 0px 0px 0px;\n" +
"}\n" +
".snarkTorrentEven {\n" +
" background-color: #E7E7E7;\n" +
"}\n" +
".snarkTorrentOdd {\n" +
" background-color: #DDDDCC;\n" +
"}\n" +
".snarkNewTorrent {\n" +
" font-size: 10pt;\n" +
" font-family: monospace;\n" +
" background-color: #ADAE9;\n" +
"}\n" +
".snarkAddInfo {\n" +
" font-size: 10pt;\n" +
"}\n" +
".snarkConfigTitle {\n" +
" font-size: 12pt;\n" +
" font-weight: bold;\n" +
"}\n" +
".snarkConfig {\n" +
" font-size: 10pt;\n" +
"}\n" +
"</style>\n" +
"</head>\n" +
"<body>\n";
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\">\n" +
"<thead>\n" +
"<tr><th align=\"left\" valign=\"top\">Status</th>\n" +
" <th align=\"left\" valign=\"top\">Torrent</th>\n" +
" <th align=\"left\" valign=\"top\">Downloaded</th>\n" +
" <th align=\"left\" valign=\"top\">Uploaded</th>\n" +
//" <th align=\"left\" valign=\"top\">Rate</th>\n" +
" <th>&nbsp;</th></tr>\n" +
"</thead>\n";
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
"<td class=\"snarkTorrentEven\" align=\"left\"" +
" valign=\"top\" colspan=\"5\">No torrents</td></tr>\n";
private static final String TABLE_FOOTER = "</table>\n";
private static final String FOOTER = "</body></html>";
}

View File

@ -0,0 +1,47 @@
package org.klomp.snark.web;
import java.io.File;
import net.i2p.util.FileUtil;
import org.mortbay.jetty.Server;
public class RunStandalone {
private Server _server;
static {
System.setProperty("org.mortbay.http.Version.paranoid", "true");
System.setProperty("org.mortbay.xml.XmlParser.NotValidating", "true");
}
private RunStandalone(String args[]) {}
public static void main(String args[]) {
RunStandalone runner = new RunStandalone(args);
runner.start();
}
public void start() {
File workDir = new File("work");
boolean workDirRemoved = FileUtil.rmdir(workDir, false);
if (!workDirRemoved)
System.err.println("ERROR: Unable to remove Jetty temporary work directory");
boolean workDirCreated = workDir.mkdirs();
if (!workDirCreated)
System.err.println("ERROR: Unable to create Jetty temporary work directory");
try {
_server = new Server("jetty-i2psnark.xml");
_server.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void stop() {
try {
_server.stop();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure 1.2//EN" "http://jetty.mortbay.org/configure_1_2.dtd">
<!-- =============================================================== -->
<!-- Configure the Jetty Server -->
<!-- =============================================================== -->
<Configure class="org.mortbay.jetty.Server">
<!-- =============================================================== -->
<!-- Configure the Request Listeners -->
<!-- =============================================================== -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add and configure a HTTP listener to port 8080 -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Call name="addListener">
<Arg>
<New class="org.mortbay.http.SocketListener">
<Arg>
<New class="org.mortbay.util.InetAddrPort">
<Set name="host">127.0.0.1</Set>
<Set name="port">8002</Set>
</New>
</Arg>
<Set name="MinThreads">3</Set>
<Set name="MaxThreads">10</Set>
<Set name="MaxIdleTimeMs">30000</Set>
<Set name="LowResourcePersistTimeMs">1000</Set>
<Set name="ConfidentialPort">8443</Set>
<Set name="IntegralPort">8443</Set>
<Set name="PoolName">main</Set>
</New>
</Arg>
</Call>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add a HTTPS SSL listener on port 8443 -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- UNCOMMENT TO ACTIVATE
<Call name="addListener">
<Arg>
<New class="org.mortbay.http.SunJsseListener">
<Set name="Port">8443</Set>
<Set name="PoolName">main</Set>
<Set name="Keystore"><SystemProperty name="jetty.home" default="."/>/etc/demokeystore</Set>
<Set name="Password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
<Set name="KeyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
<Set name="NonPersistentUserAgent">MSIE 5</Set>
</New>
</Arg>
</Call>
-->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add a AJP13 listener on port 8009 -->
<!-- This protocol can be used with mod_jk in apache, IIS etc. -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!--
<Call name="addListener">
<Arg>
<New class="org.mortbay.http.ajp.AJP13Listener">
<Set name="PoolName">ajp</Set>
<Set name="Port">8009</Set>
<Set name="MinThreads">3</Set>
<Set name="MaxThreads">20</Set>
<Set name="MaxIdleTimeMs">0</Set>
<Set name="confidentialPort">443</Set>
</New>
</Arg>
</Call>
-->
<!-- =============================================================== -->
<!-- Configure the Contexts -->
<!-- =============================================================== -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add a all web application within the webapps directory. -->
<!-- + No virtual host specified -->
<!-- + Look in the webapps directory relative to jetty.home or . -->
<!-- + Use the default webdefault.xml in jetty's install -->
<!-- + Upack the war file -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Set name="rootWebApp">i2psnark</Set>
<Call name="addWebApplication">
<Arg>/</Arg>
<Arg>webapps/i2psnark.war</Arg>
</Call>
<!-- =============================================================== -->
<!-- Configure the Request Log -->
<!-- =============================================================== -->
<Set name="RequestLog">
<New class="org.mortbay.http.NCSARequestLog">
<Arg>./logs/yyyy_mm_dd.i2psnark-request.log</Arg>
<Set name="retainDays">90</Set>
<Set name="append">true</Set>
<Set name="extended">false</Set>
<Set name="buffered">false</Set>
<Set name="LogTimeZone">GMT</Set>
</New>
</Set>
<!-- =============================================================== -->
<!-- Configure the Other Server Options -->
<!-- =============================================================== -->
<Set name="requestsPerGC">2000</Set>
<Set name="statsOn">false</Set>
</Configure>

View File

@ -0,0 +1,6 @@
To run I2PSnark from the command line, run "java -jar lib/i2psnark.jar", but
to run it with the web UI, run "java -jar launch-i2psnark.jar". I2PSnark is
GPL'ed software, based on Snark (http://www.klomp.org/) to run on top of I2P
(http://www.i2p.net/) within a webserver (such as the bundled Jetty from
http://jetty.mortbay.org/). For more information about I2PSnark, get in touch
with the folks at http://forum.i2p.net/

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>

25
apps/i2psnark/web.xml Normal file
View File

@ -0,0 +1,25 @@
<?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">
<web-app>
<servlet>
<servlet-name>org.klomp.snark.web.I2PSnarkServlet</servlet-name>
<servlet-class>org.klomp.snark.web.I2PSnarkServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- precompiled servlets -->
<servlet-mapping>
<servlet-name>org.klomp.snark.web.I2PSnarkServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>

View File

@ -8,36 +8,50 @@ package net.i2p.i2ptunnel;
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.FilterOutputStream;
import java.io.OutputStream;
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.
* 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 static final Log _log = new Log(HTTPResponseOutputStream.class);
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];
}
@ -51,6 +65,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
public void write(byte buf[], int off, int len) throws IOException {
if (_headerWritten) {
out.write(buf, off, len);
_dataWritten += len;
//out.flush();
return;
}
@ -62,8 +78,12 @@ class HTTPResponseOutputStream extends FilterOutputStream {
if (headerReceived()) {
writeHeader();
_headerWritten = true;
if (i + 1 < len) // write out the remaining
if (i + 1 < len) {
// write out the remaining
out.write(buf, off+i+1, len-i-1);
_dataWritten += len-i-1;
//out.flush();
}
return;
}
}
@ -128,7 +148,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
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);
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());
@ -136,6 +159,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
} 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());
}
@ -152,13 +177,131 @@ class HTTPResponseOutputStream extends FilterOutputStream {
if (!proxyConnectionSent)
out.write("Proxy-Connection: close\n".getBytes());
out.write("\n".getBytes()); // end of the headers
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[]) {

View File

@ -240,6 +240,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
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)) {
@ -291,6 +293,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
l.log("gentextkeys");
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");
@ -596,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
*

View File

@ -17,6 +17,7 @@ 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.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
@ -31,6 +32,7 @@ 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;
static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
@ -101,6 +103,12 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
this.l = l;
this.handlerName = handlerName + _clientId;
_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");
@ -362,7 +370,10 @@ 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 on " + localPort, ex);
@ -422,14 +433,20 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
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))
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);
}
}
}
@ -496,8 +513,12 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
} catch (InterruptedException ie) {}
if (s != null)
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

@ -96,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;
@ -179,6 +195,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
return opts;
}
private static final boolean DEFAULT_GZIP = true;
private static long __requestId = 0;
protected void clientConnectionRun(Socket s) {
OutputStream out = null;
@ -243,50 +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 = 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 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);
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));
}
} else {
// append each fragment unless it's the address helper
if ("".equals(urlEncoding)) {
urlEncoding = "?" + fragment;
} else {
urlEncoding = urlEncoding + "&" + fragment;
}
}
}
// reconstruct the request minus the i2paddresshelper GET var
request = uriPath + urlEncoding + " " + protocolVersion;
}
String addressHelper = (String) addressHelpers.get(destination);
if (addressHelper != null) {
destination = addressHelper;
host = getHostName(destination);
ahelper = 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;
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
@ -316,6 +386,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
} else {
request = request.substring(pos + 1);
pos = request.indexOf("/");
if (pos < 0) {
l.log("Invalid request url [" + request + "]");
if (out != null) {
out.write(ERR_REQUEST_DENIED);
out.write("<p /><i>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
}
s.close();
return;
}
destination = request.substring(0, pos);
line = method + " " + request.substring(pos);
}
@ -369,6 +451,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
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;
@ -408,27 +502,30 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
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/dnf-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);
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination, showAddrHelper);
s.close();
return;
}
String remoteID;
Properties opts = new Properties();
opts.setProperty("i2p.streaming.inactivityTimeout", ""+120*1000);
//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);
//opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions(opts));
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
@ -487,7 +584,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
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) {
@ -499,6 +596,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
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("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
@ -524,7 +628,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
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

@ -3,16 +3,13 @@
*/
package net.i2p.i2ptunnel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.FilterOutputStream;
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;
@ -30,12 +27,38 @@ import net.i2p.util.Log;
*
*/
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

@ -3,14 +3,14 @@
*/
package net.i2p.i2ptunnel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
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;
@ -24,7 +24,9 @@ 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.
* 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 {
@ -62,14 +64,54 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
try {
// give them 5 seconds to send in the HTTP request
socket.setReadTimeout(5*1000);
String modifiedHeader = getModifiedHeader(socket);
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();
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
// 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), Thread.currentThread().getName()+".hc");
req.start();
} else {
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
}
} catch (SocketException ex) {
try {
socket.close();
@ -88,17 +130,145 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
if ( (timeToHandle > 1000) && (_log.shouldLog(Log.WARN)) )
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
}
private String getModifiedHeader(I2PSocket handleSocket) throws IOException {
InputStream in = handleSocket.getInputStream();
StringBuffer command = new StringBuffer(128);
Properties headers = readHeaders(in, command);
headers.setProperty("Host", _spoofHost);
headers.setProperty("Connection", "close");
return formatHeaders(headers, command);
}
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"), Thread.currentThread().getName() + "hcs");
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');
@ -125,7 +295,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
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') ) ) {
if ( (buf.length() == 0) ||
((buf.charAt(0) == '\n') || (buf.charAt(0) == '\r')) ) {
// end of headers reached
return headers;
} else {
@ -133,6 +304,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
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

@ -153,11 +153,8 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
onTimeout.run();
}
// now one connection is dead - kill the other as well.
s.close();
i2ps.close();
t1.join(30*1000);
t2.join(30*1000);
// 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) {
if (_log.shouldLog(Log.ERROR))
_log.error("Interrupted", ex);
@ -188,6 +185,27 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
}
}
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;

View File

@ -135,6 +135,8 @@ 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)) {
@ -162,6 +164,18 @@ public class TunnelController implements Logging {
_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;
}
/**
* Note the fact that we are using some sessions, so that they dont get
* closed by some other tunnels

View File

@ -30,7 +30,9 @@ public class EditBean extends IndexBean {
if (controllers.size() > tunnel) {
TunnelController cur = (TunnelController)controllers.get(tunnel);
if (cur == null) return false;
return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
return ( ("client".equals(cur.getType())) ||
("httpclient".equals(cur.getType()))||
("ircclient".equals(cur.getType())));
} else {
return false;
}
@ -137,23 +139,63 @@ public class EditBean extends IndexBean {
}
}
public int getTunnelCount(int tunnel, int defaultCount) {
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 defaultCount;
if (len == null) return defaultQuantity;
try {
return Integer.parseInt(len);
} catch (NumberFormatException nfe) {
return defaultCount;
return defaultQuantity;
}
} else {
return defaultCount;
return defaultQuantity;
}
} else {
return defaultCount;
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;
}
}
@ -185,6 +227,10 @@ public class EditBean extends IndexBean {
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;
@ -222,4 +268,4 @@ public class EditBean extends IndexBean {
}
return props;
}
}
}

View File

@ -38,7 +38,9 @@ public class IndexBean {
private String _i2cpHost;
private String _i2cpPort;
private String _tunnelDepth;
private String _tunnelCount;
private String _tunnelQuantity;
private String _tunnelVariance;
private String _tunnelBackupQuantity;
private boolean _connectDelay;
private String _customOptions;
private String _proxyList;
@ -64,6 +66,10 @@ public class IndexBean {
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);
@ -121,21 +127,23 @@ public class IndexBean {
return "";
if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) )
return "Invalid nonce, are you being spoofed?";
if ("Stop all tunnels".equals(_action))
if ("Stop all".equals(_action))
return stopAll();
else if ("Start all tunnels".equals(_action))
else if ("Start all".equals(_action))
return startAll();
else if ("Restart all".equals(_action))
return restartAll();
else if ("Reload config".equals(_action))
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))
else if ("Save changes".equals(_action) || // IE workaround:
(_action.toLowerCase().indexOf("s</span>ave") >= 0))
return saveChanges();
else if ("Delete this proxy".equals(_action))
else if ("Delete this proxy".equals(_action) || // IE workaround:
(_action.toLowerCase().indexOf("d</span>elete") >= 0))
return deleteTunnel();
else
return "Action " + _action + " unknown";
@ -198,7 +206,9 @@ public class IndexBean {
cur.setConfig(config, "");
}
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
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();
@ -206,16 +216,27 @@ public class IndexBean {
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()) || "client".equals(c.getType())) && "true".equalsIgnoreCase(c.getSharedClient())) {
if (("httpclient".equals(c.getType()) ||
"ircclient".equals(c.getType())||
"client".equals(c.getType())
) && "true".equalsIgnoreCase(c.getSharedClient())) {
Properties cOpt = c.getConfig("");
if (_tunnelCount != null) {
cOpt.setProperty("option.inbound.quantity", _tunnelCount);
cOpt.setProperty("option.outbound.quantity", _tunnelCount);
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);
@ -270,6 +291,24 @@ public class IndexBean {
// 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();
@ -278,7 +317,9 @@ public class IndexBean {
public boolean isClient(int tunnelNum) {
TunnelController cur = getController(tunnelNum);
if (cur == null) return false;
return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
return ( ("client".equals(cur.getType())) ||
("httpclient".equals(cur.getType())) ||
("ircclient".equals(cur.getType())));
}
public String getTunnelName(int tunnel) {
@ -306,9 +347,10 @@ public class IndexBean {
}
public String getTypeName(String internalType) {
if ("client".equals(internalType)) return "Client proxy";
else if ("httpclient".equals(internalType)) return "HTTP proxy";
else if ("server".equals(internalType)) return "Server";
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;
}
@ -356,7 +398,7 @@ public class IndexBean {
public String getClientDestination(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun == null) return "";
if ("client".equals(tun.getType())) return tun.getTargetDestination();
if ("client".equals(tun.getType())||"ircclient".equals(tun.getType())) return tun.getTargetDestination();
else return tun.getProxyList();
}
@ -386,7 +428,7 @@ public class IndexBean {
///
/**
* What type of tunnel (httpclient, client, or server). This is
* What type of tunnel (httpclient, ircclient, client, or server). This is
* required when adding a new tunnel.
*
*/
@ -416,8 +458,16 @@ public class IndexBean {
_tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
}
/** how many parallel inbound tunnels to use */
public void setTunnelCount(String tunnelCount) {
_tunnelCount = (tunnelCount != null ? tunnelCount.trim() : null);
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) {
@ -427,12 +477,12 @@ public class IndexBean {
public void setProxyList(String proxyList) {
_proxyList = (proxyList != null ? proxyList.trim() : null);
}
/** what port should this client/httpclient listen on */
/** 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 listen on (unless
* what interface should this client/httpclient/ircclient listen on (unless
* overridden by the setReachableByOther() field)
*/
public void setReachableBy(String reachableBy) {
@ -440,7 +490,7 @@ public class IndexBean {
}
/**
* If specified, defines the exact IP interface to listen for requests
* on (in the case of client/httpclient tunnels)
* on (in the case of client/httpclient/ircclient tunnels)
*/
public void setReachableByOther(String reachableByOther) {
_reachableByOther = (reachableByOther != null ? reachableByOther.trim() : null);
@ -479,9 +529,12 @@ public class IndexBean {
public void setStartOnLoad(String moo) {
_startOnLoad = true;
}
public void setSharedClient(String moo) {
public void setShared(String moo) {
_sharedClient=true;
}
public void setShared(boolean val) {
_sharedClient=val;
}
public void setConnectDelay(String moo) {
_connectDelay = true;
}
@ -517,6 +570,24 @@ public class IndexBean {
}
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);
@ -553,6 +624,7 @@ public class IndexBean {
} else {
return null;
}
return config;
}
@ -582,6 +654,10 @@ public class IndexBean {
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;
@ -592,20 +668,28 @@ public class IndexBean {
config.setProperty("startOnLoad", _startOnLoad + "");
if (_tunnelCount != null) {
config.setProperty("option.inbound.quantity", _tunnelCount);
config.setProperty("option.outbound.quantity", _tunnelCount);
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))) || (!_sharedClient) ) {
if ( ((!"client".equals(_type)) && (!"httpclient".equals(_type))&& (!"ircclient".equals(_type))) || (!_sharedClient) ) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
} else {
@ -644,4 +728,4 @@ public class IndexBean {
buf.append((String)msgs.get(i)).append("\n");
}
}
}
}

View File

@ -1,26 +1,25 @@
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<% 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)) {
%><jsp:include page="editClient.jsp" /><%
} else if ("server".equals(type) || "httpserver".equals(type)) {
%><jsp:include page="editServer.jsp" /><%
<%@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 {
%>Invalid tunnel type<%
%><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

@ -1,4 +1,5 @@
<%@page contentType="text/html" %>
<%@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;
@ -10,284 +11,277 @@
}
}
%>
<html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>I2PTunnel Webmanager</title>
<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>
<form action="index.jsp">
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td colspan="2" align="center">
<% if (curTunnel >= 0) { %>
<b>Edit proxy settings</b>
<% } else { %>
<b>New proxy settings</b>
<% } %>
</td>
</tr>
<tr>
<td><b>Name: </b>
</td>
<td>
<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b>Type: </b>
<td><%
if (curTunnel >= 0) {
%><%=editBean.getTunnelType(curTunnel)%>
<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
<%
} else {
%><%=editBean.getTypeName(request.getParameter("type"))%>
<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
<%
}
%></td>
</tr>
<tr>
<td><b>Description: </b>
</td>
<td>
<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b>Start automatically?:</b>
</td>
<td>
<% if (editBean.startAutomatically(curTunnel)) { %>
<input value="1" type="checkbox" name="startOnLoad" checked="true" />
<% } else { %>
<input value="1" type="checkbox" name="startOnLoad" />
<% } %>
<i>(Check the Box for 'YES')</i>
</td>
</tr>
<tr>
<td> <b>Listening Port:</b>
</td>
<td>
<input type="text" size="6" maxlength="5" name="port" value="<%=editBean.getClientPort(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b> Accessable by:</b>
</td>
<td>
<select name="reachableBy">
<% String clientInterface = editBean.getClientInterface(curTunnel); %>
<% if (("127.0.0.1".equals(clientInterface)) || (clientInterface == null) || (clientInterface.trim().length() <= 0)) { %>
<option value="127.0.0.1" selected="true">Locally (127.0.0.1)</option>
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
<option value="other">LAN Hosts (Please specify your LAN address)</option>
<body id="tunnelEditPage">
<div id="pageHeader">
</div>
</select>
&nbsp;&nbsp;
<b>others:</b>
<input type="text" name="reachableByOther" size="20" />
<% } else if ("0.0.0.0".equals(clientInterface)) { %>
<option value="127.0.0.1">Locally (127.0.0.1)</option>
<option value="0.0.0.0" selected="true">Everyone (0.0.0.0)</option>
<option value="other">LAN Hosts (Please specify your LAN address)</option>
<form method="post" action="index.jsp">
</select>
&nbsp;&nbsp;
<b>others:</b>
<input type="text" name="reachableByOther" size="20" />
<% } else { %>
<option value="127.0.0.1">Locally (127.0.0.1)</option>
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
<option value="other" selected="true">LAN Hosts (Please specify your LAN address)</option>
<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>
</select>
&nbsp;&nbsp;
<b>others:</b>
<input type="text" name="reachableByOther" size="20" value="<%=clientInterface%>" />
<% } %>
<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>
</td>
</tr>
<tr>
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
<td><b>Outproxies:</b>
<% } else { %>
<td><b>Target:</b>
<% } %>
</td>
<td>
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
<input type="text" name="proxyList" value="<%=editBean.getClientDestination(curTunnel)%>" />
<% } else { %>
<input type="text" name="targetDestination" value="<%=editBean.getClientDestination(curTunnel)%>" />
<% } %>
<i>(name or destination)</i>
</td>
</tr>
<tr>
<td>
<b>Delayed connect?</b>
</td>
<td>
<% if (editBean.shouldDelay(curTunnel)) { %>
<input type="checkbox" value="1000" name="connectDelay" checked="true" />
<% } else { %>
<input type="checkbox" value="1000" name="connectDelay" />
<% } %>
<i>(for request/response connections)</i>
</td>
</tr>
<tr>
<td><b>Profile:</b>
</td>
<td>
<select name="profile">
<% if (editBean.isInteractive(curTunnel)) { %>
<option value="interactive" selected="true">interactive connection </option>
<option value="bulk">bulk connection (downloads/websites/BT) </option>
<% } else { %>
<option value="interactive">interactive connection </option>
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
<% } %>
</select>
</td>
</tr>
<tr>
<td>
<b>Shared Client</b>
</td>
<td>
<% if (editBean.isSharedClient(curTunnel)) { %>
<input type="checkbox" value="true" name="sharedClient" checked="true" />
<% } else { %>
<input type="checkbox" value="true" name="sharedClient" />
<% } %>
<i>(Share tunnels with other clients and httpclients? Change requires restart of client proxy)</i>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<b><hr size="1">
Advanced networking options<br />
<span style="color:#dd0000;">(NOTE: when this client proxy is configured to share tunnels, then these options are for all the shared proxy clients!)</span></b>
</td>
</tr>
<tr>
<td>
<b>Tunnel depth:</b>
</td>
<td><select name="tunnelDepth">
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
switch (tunnelDepth) {
case 0: %>
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<% break;
case 1: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<% break;
case 2: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
<% break;
default: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
<% } %>
</select>
</td>
</tr>
<tr>
<td><b>Tunnel count:</b>
</td>
<td>
<select name="tunnelCount">
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
switch (tunnelCount) {
case 1: %>
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
case 2: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
case 3: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
default: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
<% } %>
</select>
</td>
<tr>
<td><b>I2CP host:</b>
</td>
<td>
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b>I2CP port:</b>
</td>
<td>
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
</td>
</tr>
<tr>
<td><b>Custom options:</b>
</td>
<td>
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
</td>
</tr>
<tr>
<td colspan="2">
<hr size="1">
</td>
</tr>
<tr>
<td>
<b>Save:</b>
</td>
<td>
<input type="submit" name="action" value="Save changes" />
</td>
</tr>
<tr>
<td><b>Delete?</b>
</td>
<td>
<input type="submit" name="action" value="Delete this proxy" /> &nbsp;&nbsp;
<b><span style="color:#dd0000;">confirm delete:</span></b>
<input type="checkbox" value="true" name="removeConfirm" />
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>
<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

@ -1,4 +1,5 @@
<%@page contentType="text/html" %>
<%@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;
@ -10,224 +11,249 @@
}
}
%>
<html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>I2PTunnel Webmanager</title>
<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>
<form action="index.jsp">
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td colspan="2" align="center">
<% if (curTunnel >= 0) { %>
<b>Edit server settings</b>
<% } else { %>
<b>New server settings</b>
<% } %>
</td>
</tr>
<tr>
<td><b>Name: </b>
</td>
<td>
<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b>Type: </b>
<td><%
if (curTunnel >= 0) {
%><%=editBean.getTunnelType(curTunnel)%>
<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
<%
} else {
%><%=editBean.getTypeName(request.getParameter("type"))%>
<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
<%
}
%></td>
</tr>
<tr>
<td><b>Description: </b>
</td>
<td>
<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b>Start automatically?:</b>
</td>
<td>
<% if (editBean.startAutomatically(curTunnel)) { %>
<input value="1" type="checkbox" name="startOnLoad" checked="true" />
<% } else { %>
<input value="1" type="checkbox" name="startOnLoad" />
<% } %>
<i>(Check the Box for 'YES')</i>
</td>
</tr>
<tr>
<td> <b>Target:</b>
</td>
<td>
Host: <input type="text" size="20" name="targetHost" value="<%=editBean.getTargetHost(curTunnel)%>" />
Port: <input type="text" size="6" maxlength="5" name="targetPort" value="<%=editBean.getTargetPort(curTunnel)%>" />
</td>
</tr>
<% String curType = editBean.getInternalType(curTunnel);
if ( (curType == null) || (curType.trim().length() <= 0) )
curType = request.getParameter("type");
if ("httpserver".equals(curType)) { %>
<tr>
<td><b>Website name:</b></td>
<td><input type="text" size="20" name="spoofedHost" value="<%=editBean.getSpoofedHost(curTunnel)%>" />
</td></tr>
<% } %>
<tr>
<td><b>Private key file:</b>
</td>
<td><input type="text" size="30" name="privKeyFile" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" /></td>
</tr>
<tr>
<td><b>Profile:</b>
</td>
<td>
<select name="profile">
<% if (editBean.isInteractive(curTunnel)) { %>
<option value="interactive" selected="true">interactive connection </option>
<option value="bulk">bulk connection (downloads/websites/BT) </option>
<% } else { %>
<option value="interactive">interactive connection </option>
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
<% } %>
</select>
</td>
</tr>
<tr>
<td valign="top" align="left"><b>Local destination:</b><br /><i>(if known)</i></td>
<td valign="top" align="left"><input type="text" size="60" value="<%=editBean.getDestinationBase64(curTunnel)%>" /></td>
</tr>
<tr>
<td colspan="2" align="center">
<b><hr size="1">
Advanced networking options<br />
</b>
</td>
</tr>
<tr>
<td>
<b>Tunnel depth:</b>
</td>
<td><select name="tunnelDepth">
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
switch (tunnelDepth) {
case 0: %>
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<% break;
case 1: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<% break;
case 2: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
<% break;
default: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
<% } %>
</select>
</td>
</tr>
<tr>
<td><b>Tunnel count:</b>
</td>
<td>
<select name="tunnelCount">
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
switch (tunnelCount) {
case 1: %>
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
case 2: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
case 3: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
default: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
<% } %>
</select>
</td>
<tr>
<td><b>I2CP host:</b>
</td>
<td>
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b>I2CP port:</b>
</td>
<td>
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
</td>
</tr>
<tr>
<td><b>Custom options:</b>
</td>
<td>
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
</td>
</tr>
<tr>
<td colspan="2">
<hr size="1">
</td>
</tr>
<tr>
<td>
<b>Save:</b>
</td>
<td>
<input type="submit" name="action" value="Save changes" />
</td>
</tr>
<tr>
<td><b>Delete?</b>
</td>
<td>
<input type="submit" name="action" value="Delete this proxy" /> &nbsp;&nbsp;
<b><span style="color:#dd0000;">confirm delete:</span></b>
<input type="checkbox" value="true" name="removeConfirm" />
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
<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>
</html>

View File

@ -1,184 +1,259 @@
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean" %>
<!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>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>I2PTunnel Webmanager</title>
<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 style="font-family: Verdana, Tahoma, Helvetica, sans-serif;font-size:12px;">
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td nowrap="true"><b>New Messages: </b><br />
<a href="index.jsp">refresh</a>
</td>
<td>
<textarea rows="3" cols="60" readonly="true"><jsp:getProperty name="indexBean" property="messages" /></textarea>
</td>
</tr>
</table>
</td>
</tr>
</table>
<br />
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<body id="tunnelListPage">
<div id="pageHeader">
</div>
<div id="statusMessagePanel" class="panel">
<div class="header">
<h4>Status Messages</h4>
</div>
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td colspan="7" align="center" valign="middle" style="font-size:14px;">
<b>Your Client Tunnels:</b><br />
<hr size="1" />
</td>
</tr>
<tr>
<td width="15%"><b>Name:</b></td>
<td><b>Port:</b></td>
<td><b>Type:</b></td>
<td><b>Interface:</b></td>
<td><b>Status:</b></td>
</tr>
<% for (int curClient = 0; curClient < indexBean.getTunnelCount(); curClient++) {
if (!indexBean.isClient(curClient)) continue; %>
<tr>
<td valign="top" align="left">
<b><a href="edit.jsp?tunnel=<%=curClient%>"><%=indexBean.getTunnelName(curClient) %></a></b></td>
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientPort(curClient) %></td>
<td valign="top" align="left" nowrap="true"><%=indexBean.getTunnelType(curClient) %></td>
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientInterface(curClient) %></td>
<td valign="top" align="left" nowrap="true"><%
switch (indexBean.getTunnelStatus(curClient)) {
case IndexBean.STARTING:
%><b><span style="color:#339933">Starting...</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
break;
case IndexBean.RUNNING:
%><b><span style="color:#00dd00">Running</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
break;
case IndexBean.NOT_RUNNING:
%><b><span style="color:#dd0000">Not Running</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curClient%>">[START]</a><%
break;
}
%></td>
</tr>
<tr><td align="right" valign="top">Destination:</td>
<td colspan="4"><input align="left" size="40" valign="top" style="overflow: hidden" readonly="true" value="<%=indexBean.getClientDestination(curClient) %>" /></td></tr>
<tr>
<td valign="top" align="right">Description:</td>
<td valign="top" align="left" colspan="4"><%=indexBean.getTunnelDescription(curClient) %></td>
</tr>
<% } %>
</table>
</td>
</tr>
</table>
<br />
<div class="separator">
<hr />
</div>
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td colspan="5" align="center" valign="middle" style="font-size:14px;">
<b>Your Server Tunnels:</b><br />
<hr size="1" />
</td>
</tr>
<tr>
<td width="15%"><b>Name: </b>
</td>
<td>
<b>Points at:</b>
</td>
<td>
<b>Status:</b>
</td>
</tr>
<textarea id="statusMessages" rows="3" cols="60" readonly="readonly"><jsp:getProperty name="indexBean" property="messages" /></textarea>
<% for (int curServer = 0; curServer < indexBean.getTunnelCount(); curServer++) {
if (indexBean.isClient(curServer)) continue; %>
<div class="separator">
<hr />
</div>
<tr>
<td valign="top">
<b><a href="edit.jsp?tunnel=<%=curServer%>"><%=indexBean.getTunnelName(curServer)%></a></b>
</td>
<td valign="top"><%=indexBean.getServerTarget(curServer)%></td>
<td valign="top" nowrap="true"><%
switch (indexBean.getTunnelStatus(curServer)) {
case IndexBean.RUNNING:
%><b><span style="color:#00dd00">Running</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
if ("httpserver".equals(indexBean.getInternalType(curServer))) {
%> (<a href="http://<%=(new java.util.Random()).nextLong()%>.i2p/?i2paddresshelper=<%=indexBean.getDestinationBase64(curServer)%>">preview</a>)<%
}
break;
case IndexBean.NOT_RUNNING:
%><b><span style="color:#dd0000">Not Running</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curServer%>">[START]</a><%
break;
case IndexBean.STARTING:
%>
<b><span style="color:#339933">Starting...</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
break;
}
%>
</td>
</tr>
<tr><td valign="top" align="right">Description:</td>
<td valign="top" align="left" colspan="2"><%=indexBean.getTunnelDescription(curServer)%></td></tr>
<% } %>
<div class="footer">
<div class="toolbox">
<a class="control" href="index.jsp">Refresh</a>
</div>
</div>
</div>
</table>
</td>
</tr>
</table>
<br />
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td colspan="2" align="center" valign="middle">
<b>Operations Menu - Please chose from below!</b><br /><br />
</td>
</tr>
<tr>
<form action="index.jsp" method="GET">
<td >
<input type="hidden" name="nonce" value="<%=indexBean.getNextNonce()%>" />
<input type="submit" name="action" value="Stop all tunnels" />
<input type="submit" name="action" value="Start all tunnels" />
<input type="submit" name="action" value="Restart all" />
<input type="submit" name="action" value="Reload config" />
</td>
</form>
<form action="edit.jsp">
<td >
<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>
<option value="httpserver">HTTP server tunnel</option>
</select> <input type="submit" value="Create" />
</td>
</form>
</tr>
</table>
</td>
</tr>
</table>
<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>

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,30 +3,29 @@
<target name="all" depends="build" />
<target name="fetchJettylib" >
<available property="jetty.available" file="jetty-5.1.2.zip" />
<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 5.1.2" />
<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://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.2.zip" verbose="true" dest="jetty-5.1.2.zip" />
<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.2.zip" dest="." />
<unzip src="jetty-5.1.6.zip" dest="." />
<mkdir dir="jettylib" />
<copy todir="jettylib">
<fileset dir="jetty-5.1.2/lib">
<fileset dir="jetty-5.1.6/lib">
<include name="*.jar" />
</fileset>
</copy>
<copy todir="jettylib">
<fileset dir="jetty-5.1.2/ext">
<fileset dir="jetty-5.1.6/ext">
<include name="ant.jar" />
<include name="commons-el.jar" />
<include name="commons-logging.jar" />
<include name="jasper-compiler.jar" />
<include name="jasper-runtime.jar" />
<include name="javax.servlet.jar" />
@ -34,7 +33,9 @@
<include name="xercesImpl.jar" />
</fileset>
</copy>
<delete dir="jetty-5.1.2" />
<!-- 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

@ -13,7 +13,7 @@ class I2PSocketOptionsImpl implements I2PSocketOptions {
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_WRITE_TIMEOUT = -1;
public static final int DEFAULT_CONNECT_TIMEOUT = 60*1000;
public I2PSocketOptionsImpl() {

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

@ -0,0 +1 @@
This is ROME 0.7 from http://rome.dev.java.net/, released under a BSD license

BIN
apps/rome/rome-0.7.jar Normal file

Binary file not shown.

View File

@ -59,6 +59,8 @@ public class ConfigLoggingHelper {
buf.append(prefix).append('=').append(level).append('\n');
}
buf.append("</textarea><br />\n");
buf.append("<i>Add additional logging statements above. Example: net.i2p.router.tunnel=WARN</i><br>");
buf.append("<i>Or put entries in the logger.config file. Example: logger.record.net.i2p.router.tunnel=WARN</i><br>");
buf.append("<i>Valid levels are DEBUG, INFO, WARN, ERROR, CRIT</i>\n");
return buf.toString();
}

View File

@ -16,6 +16,11 @@ import java.util.Iterator;
import java.util.Set;
import net.i2p.time.Timestamper;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.router.Router;
import net.i2p.data.RouterInfo;
import net.i2p.router.web.ConfigServiceHandler.UpdateWrapperManagerTask;
import net.i2p.router.web.ConfigServiceHandler.UpdateWrapperManagerAndRekeyTask;
/**
* Handler to deal with form submissions from the main config form and act
@ -29,11 +34,16 @@ public class ConfigNetHandler extends FormHandler {
private boolean _saveRequested;
private boolean _recheckReachabilityRequested;
private boolean _timeSyncEnabled;
private boolean _requireIntroductions;
private boolean _hiddenMode;
private boolean _dynamicKeys;
private String _tcpPort;
private String _udpPort;
private String _inboundRate;
private String _inboundBurstRate;
private String _inboundBurst;
private String _outboundRate;
private String _outboundBurstRate;
private String _outboundBurst;
private String _reseedFrom;
private String _sharePct;
@ -57,6 +67,9 @@ public class ConfigNetHandler extends FormHandler {
public void setSave(String moo) { _saveRequested = true; }
public void setEnabletimesync(String moo) { _timeSyncEnabled = true; }
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
public void setRequireIntroductions(String moo) { _requireIntroductions = true; }
public void setHiddenMode(String moo) { _hiddenMode = true; }
public void setDynamicKeys(String moo) { _dynamicKeys = true; }
public void setHostname(String hostname) {
_hostname = (hostname != null ? hostname.trim() : null);
@ -70,12 +83,18 @@ public class ConfigNetHandler extends FormHandler {
public void setInboundrate(String rate) {
_inboundRate = (rate != null ? rate.trim() : null);
}
public void setInboundburstrate(String rate) {
_inboundBurstRate = (rate != null ? rate.trim() : null);
}
public void setInboundburstfactor(String factor) {
_inboundBurst = (factor != null ? factor.trim() : null);
}
public void setOutboundrate(String rate) {
_outboundRate = (rate != null ? rate.trim() : null);
}
public void setOutboundburstrate(String rate) {
_outboundBurstRate = (rate != null ? rate.trim() : null);
}
public void setOutboundburstfactor(String factor) {
_outboundBurst = (factor != null ? factor.trim() : null);
}
@ -252,8 +271,37 @@ public class ConfigNetHandler extends FormHandler {
addFormNotice("Updating bandwidth share percentage");
}
}
// If hidden mode value changes, restart is required
if (_hiddenMode && "false".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) {
_context.router().setConfigSetting(Router.PROP_HIDDEN, "true");
_context.router().getRouterInfo().addCapability(RouterInfo.CAPABILITY_HIDDEN);
addFormNotice("Gracefully restarting into Hidden Router Mode. Make sure you have no 0-1 length "
+ "<a href=\"configtunnels.jsp\">tunnels!</a>");
hiddenSwitch();
}
if (!_hiddenMode && "true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) {
_context.router().removeConfigSetting(Router.PROP_HIDDEN);
_context.router().getRouterInfo().delCapability(RouterInfo.CAPABILITY_HIDDEN);
addFormNotice("Gracefully restarting to exit Hidden Router Mode");
hiddenSwitch();
}
if (_dynamicKeys) {
_context.router().setConfigSetting(Router.PROP_DYNAMIC_KEYS, "true");
} else {
_context.router().removeConfigSetting(Router.PROP_DYNAMIC_KEYS);
}
if (_timeSyncEnabled) {
if (_requireIntroductions) {
_context.router().setConfigSetting(UDPTransport.PROP_FORCE_INTRODUCERS, "true");
addFormNotice("Requiring SSU introduers");
} else {
_context.router().removeConfigSetting(UDPTransport.PROP_FORCE_INTRODUCERS);
}
if (true || _timeSyncEnabled) {
// Time sync enable, means NOT disabled
_context.router().setConfigSetting(Timestamper.PROP_DISABLED, "false");
} else {
@ -272,6 +320,12 @@ public class ConfigNetHandler extends FormHandler {
addFormNotice("Soft restart complete");
}
}
private void hiddenSwitch() {
// Full restart required to generate new keys
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
}
private void updateRates() {
boolean updated = false;
@ -283,14 +337,22 @@ public class ConfigNetHandler extends FormHandler {
_context.router().setConfigSetting(ConfigNetHelper.PROP_OUTBOUND_KBPS, _outboundRate);
updated = true;
}
if ( (_inboundBurstRate != null) && (_inboundBurstRate.length() > 0) ) {
_context.router().setConfigSetting(ConfigNetHelper.PROP_INBOUND_BURST_KBPS, _inboundBurstRate);
updated = true;
}
if ( (_outboundBurstRate != null) && (_outboundBurstRate.length() > 0) ) {
_context.router().setConfigSetting(ConfigNetHelper.PROP_OUTBOUND_BURST_KBPS, _outboundBurstRate);
updated = true;
}
String inRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_INBOUND_KBPS);
String inBurstRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_INBOUND_BURST_KBPS);
if (_inboundBurst != null) {
int rateKBps = 0;
int burstSeconds = 0;
try {
rateKBps = Integer.parseInt(inRate);
rateKBps = Integer.parseInt(inBurstRate);
burstSeconds = Integer.parseInt(_inboundBurst);
} catch (NumberFormatException nfe) {
// ignore
@ -302,13 +364,13 @@ public class ConfigNetHandler extends FormHandler {
}
}
String outRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_OUTBOUND_KBPS);
String outBurstRate = _context.router().getConfigSetting(ConfigNetHelper.PROP_OUTBOUND_BURST_KBPS);
if (_outboundBurst != null) {
int rateKBps = 0;
int burstSeconds = 0;
try {
rateKBps = Integer.parseInt(outRate);
rateKBps = Integer.parseInt(outBurstRate);
burstSeconds = Integer.parseInt(_outboundBurst);
} catch (NumberFormatException nfe) {
// ignore

View File

@ -2,6 +2,11 @@ package net.i2p.router.web;
import net.i2p.time.Timestamper;
import net.i2p.router.RouterContext;
import net.i2p.router.CommSystemFacade;
import net.i2p.data.RouterAddress;
import net.i2p.router.transport.udp.UDPAddress;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.router.Router;
public class ConfigNetHelper {
private RouterContext _context;
@ -43,19 +48,12 @@ public class ConfigNetHelper {
return "" + port;
}
public String getUdpPort() {
int port = 8887;
String val = _context.getProperty(PROP_I2NP_UDP_PORT);
if (val == null)
val = _context.getProperty(PROP_I2NP_INTERNAL_UDP_PORT);
if (val != null) {
try {
port = Integer.parseInt(val);
} catch (NumberFormatException nfe) {
// ignore, use default from above
}
}
return "" + port;
public String getUdpAddress() {
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
if (addr == null)
return "unknown";
UDPAddress ua = new UDPAddress(addr);
return ua.toString();
}
public String getEnableTimeSyncChecked() {
@ -66,8 +64,45 @@ public class ConfigNetHelper {
return " checked ";
}
public String getHiddenModeChecked() {
String enabled = _context.getProperty(Router.PROP_HIDDEN, "false");
if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) )
return " checked ";
else
return "";
}
public String getDynamicKeysChecked() {
String enabled = _context.getProperty(Router.PROP_DYNAMIC_KEYS, "false");
if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) )
return " checked ";
else
return "";
}
public String getRequireIntroductionsChecked() {
short status = _context.commSystem().getReachabilityStatus();
switch (status) {
case CommSystemFacade.STATUS_OK:
if ("true".equalsIgnoreCase(_context.getProperty(UDPTransport.PROP_FORCE_INTRODUCERS, "false")))
return "checked=\"true\"";
return "";
case CommSystemFacade.STATUS_DIFFERENT:
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
return "checked=\"true\"";
case CommSystemFacade.STATUS_UNKNOWN:
if ("true".equalsIgnoreCase(_context.getProperty(UDPTransport.PROP_FORCE_INTRODUCERS, "false")))
return "checked=\"true\"";
return "";
default:
return "checked=\"true\"";
}
}
public static final String PROP_INBOUND_KBPS = "i2np.bandwidth.inboundKBytesPerSecond";
public static final String PROP_OUTBOUND_KBPS = "i2np.bandwidth.outboundKBytesPerSecond";
public static final String PROP_INBOUND_BURST_KBPS = "i2np.bandwidth.inboundBurstKBytesPerSecond";
public static final String PROP_OUTBOUND_BURST_KBPS = "i2np.bandwidth.outboundBurstKBytesPerSecond";
public static final String PROP_INBOUND_BURST = "i2np.bandwidth.inboundBurstKBytes";
public static final String PROP_OUTBOUND_BURST = "i2np.bandwidth.outboundBurstKBytes";
public static final String PROP_SHARE_PERCENTAGE = "router.sharePercentage";
@ -78,14 +113,28 @@ public class ConfigNetHelper {
if (rate != null)
return rate;
else
return "-1";
return "16";
}
public String getOutboundRate() {
String rate = _context.getProperty(PROP_OUTBOUND_KBPS);
if (rate != null)
return rate;
else
return "-1";
return "16";
}
public String getInboundBurstRate() {
String rate = _context.getProperty(PROP_INBOUND_BURST_KBPS);
if (rate != null)
return rate;
else
return "32";
}
public String getOutboundBurstRate() {
String rate = _context.getProperty(PROP_OUTBOUND_BURST_KBPS);
if (rate != null)
return rate;
else
return "32";
}
public String getInboundBurstFactorBox() {
String rate = _context.getProperty(PROP_INBOUND_KBPS);

View File

@ -33,6 +33,21 @@ public class ConfigServiceHandler extends FormHandler {
}
}
}
public static class UpdateWrapperManagerAndRekeyTask implements Runnable {
private int _exitCode;
public UpdateWrapperManagerAndRekeyTask(int exitCode) {
_exitCode = exitCode;
}
public void run() {
try {
Router.killKeys();
WrapperManager.signalStopped(_exitCode);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
protected void processForm() {
if (_action == null) return;
@ -56,6 +71,14 @@ public class ConfigServiceHandler extends FormHandler {
_context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
_context.router().shutdown(Router.EXIT_HARD_RESTART);
addFormNotice("Hard restart requested");
} else if ("Rekey and Restart".equals(_action)) {
addFormNotice("Rekeying after graceful restart");
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
} else if ("Rekey and Shutdown".equals(_action)) {
addFormNotice("Rekeying after graceful shutdown");
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL);
} else if ("Run I2P on startup".equals(_action)) {
installService();
} else if ("Don't run I2P on startup".equals(_action)) {
@ -195,4 +218,4 @@ public class ConfigServiceHandler extends FormHandler {
addFormError("Error updating the client config");
}
}
}
}

View File

@ -63,6 +63,8 @@ public class ConfigStatsHandler extends FormHandler {
if (_explicitFilter) {
_stats.clear();
if (_explicitFilterValue == null)
_explicitFilterValue = "";
if (_explicitFilterValue.indexOf(',') != -1) {
StringTokenizer tok = new StringTokenizer(_explicitFilterValue, ",");

View File

@ -135,7 +135,7 @@ public class ConfigTunnelsHandler extends FormHandler {
if (saveRequired) {
boolean saved = _context.router().saveConfig();
if (saved)
addFormNotice("Configuration saved successfully");
addFormNotice("Exploratory tunnel configuration saved successfully");
else
addFormNotice("Error saving the configuration (applied but not saved) - please see the error logs");
}

View File

@ -51,7 +51,19 @@ public class ContentHelper {
String str = FileUtil.readTextFile(_page, _maxLines, _startAtBeginning);
if (str == null)
return "";
else
return "<pre>" + str + "</pre>";
else {
StringBuffer sb = new StringBuffer(str.length()+11);
sb.append("<pre>");
for (int i=0; i < str.length(); i++) {
char c = str.charAt(i);
switch (str.charAt(i)) {
case '<': sb.append("&lt;"); break;
case '>': sb.append("&gt;"); break;
case '&': sb.append("&amp;"); break;
default: sb.append(c); break;
}
}
return sb.append("</pre>").toString();
}
}
}

View File

@ -0,0 +1,46 @@
package net.i2p.router.web;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import net.i2p.router.RouterContext;
public class JobQueueHelper {
private RouterContext _context;
private Writer _out;
/**
* Configure this bean to query a particular router context
*
* @param contextId begging few characters of the routerHash, or null to pick
* the first one we come across.
*/
public void setContextId(String contextId) {
try {
_context = ContextHelper.getContext(contextId);
} catch (Throwable t) {
t.printStackTrace();
}
}
public JobQueueHelper() {}
public void setWriter(Writer writer) { _out = writer; }
public String getJobQueueSummary() {
try {
if (_out != null) {
_context.jobQueue().renderStatusHTML(_out);
return "";
} else {
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
_context.jobQueue().renderStatusHTML(new OutputStreamWriter(baos));
return new String(baos.toByteArray());
}
} catch (IOException ioe) {
ioe.printStackTrace();
return "";
}
}
}

View File

@ -30,6 +30,7 @@ public class LogsHelper {
buf.append("<code>\n");
for (int i = msgs.size(); i > 0; i--) {
String msg = (String)msgs.get(i - 1);
msg = msg.replaceAll("<","&lt;");
buf.append("<li>");
buf.append(msg);
buf.append("</li>\n");
@ -46,6 +47,7 @@ public class LogsHelper {
buf.append("<code>\n");
for (int i = msgs.size(); i > 0; i--) {
String msg = (String)msgs.get(i - 1);
msg = msg.replaceAll("<","&lt;");
buf.append("<li>");
buf.append(msg);
buf.append("</li>\n");
@ -59,8 +61,10 @@ public class LogsHelper {
String str = FileUtil.readTextFile("wrapper.log", 500, false);
if (str == null)
return "";
else
else {
str = str.replaceAll("<","&lt;");
return "<pre>" + str + "</pre>";
}
}
public String getConnectionLogs() {

View File

@ -198,7 +198,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
// ignore
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
if (_log.shouldLog(Log.INFO))
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
@ -224,4 +224,5 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
File temp = new File(TEMP_NEWS_FILE);
temp.delete();
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
}

View File

@ -29,6 +29,7 @@ public class PeerHelper {
public String getPeerSummary() {
try {
_context.commSystem().renderStatusHTML(_out);
_context.bandwidthLimiter().renderStatusHTML(_out);
} catch (IOException ioe) {
ioe.printStackTrace();
}

View File

@ -28,14 +28,19 @@ public class ReseedHandler {
if (nonce == null) return;
if (nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.nonce")) ||
nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.noncePrev"))) {
synchronized (_reseedRunner) {
if (_reseedRunner.isRunning()) {
return;
} else {
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
reseed.start();
}
requestReseed();
}
}
public static void requestReseed() {
synchronized (_reseedRunner) {
if (_reseedRunner.isRunning()) {
return;
} else {
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
System.out.println("Reseeding");
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
reseed.start();
}
}
}
@ -46,7 +51,8 @@ public class ReseedHandler {
public boolean isRunning() { return _isRunning; }
public void run() {
_isRunning = true;
reseed();
reseed(false);
System.out.println("Reseeding complete");
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false");
_isRunning = false;
}
@ -59,7 +65,7 @@ public class ReseedHandler {
* save them into this router's netDb dir.
*
*/
private static void reseed() {
private static void reseed(boolean echoStatus) {
String seedURL = System.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
seedURL = DEFAULT_SEED_URL;
@ -85,10 +91,16 @@ public class ReseedHandler {
try {
fetchSeed(seedURL, (String)iter.next());
fetched++;
if (echoStatus) {
System.out.print(".");
if (fetched % 60 == 0)
System.out.println();
}
} catch (Exception e) {
errors++;
}
}
if (echoStatus) System.out.println();
} catch (Throwable t) {
I2PAppContext.getGlobalContext().logManager().getLog(ReseedHandler.class).error("Error reseeding", t);
}
@ -172,7 +184,11 @@ public class ReseedHandler {
}
public static void main(String args[]) {
reseed();
//System.out.println("Done reseeding");
if ( (args != null) && (args.length == 1) && (!Boolean.valueOf(args[0]).booleanValue()) ) {
System.out.println("Not reseeding, as requested");
return; // not reseeding on request
}
System.out.println("Reseeding");
reseed(true);
}
}

View File

@ -72,6 +72,24 @@ public class RouterConsoleRunner {
} catch (Throwable t) {
t.printStackTrace();
}
// we check the i2p installation directory (.) for a flag telling us not to reseed,
// but also check the home directory for that flag too, since new users installing i2p
// don't have an installation directory that they can put the flag in yet.
File noReseedFile = new File(new File(System.getProperty("user.home")), ".i2pnoreseed");
File noReseedFileAlt1 = new File(new File(System.getProperty("user.home")), "noreseed.i2p");
File noReseedFileAlt2 = new File(".i2pnoreseed");
File noReseedFileAlt3 = new File("noreseed.i2p");
if (!noReseedFile.exists() && !noReseedFileAlt1.exists() && !noReseedFileAlt2.exists() && !noReseedFileAlt3.exists()) {
File netDb = new File("netDb");
// sure, some of them could be "my.info" or various leaseSet- files, but chances are,
// if someone has those files, they've already been seeded (at least enough to let them
// get i2p started - they can reseed later in the web console)
String names[] = (netDb.exists() ? netDb.list() : null);
if ( (names == null) || (names.length < 15) ) {
ReseedHandler.requestReseed();
}
}
NewsFetcher fetcher = NewsFetcher.getInstance(I2PAppContext.getGlobalContext());
I2PThread t = new I2PThread(fetcher, "NewsFetcher");

View File

@ -83,24 +83,36 @@ public class SummaryHelper {
long ms = _context.clock().getOffset();
if (ms < 60 * 1000) {
return now + " (" + (ms / 1000) + "s)";
} else if (ms < 60 * 60 * 1000) {
return now + " (" + (ms / (60 * 1000)) + "m)";
} else if (ms < 24 * 60 * 60 * 1000) {
return now + " (" + (ms / (60 * 60 * 1000)) + "h)";
long diff = ms;
if (diff < 0)
diff = 0 - diff;
if (diff == 0) {
return now + " (no skew)";
} else if (diff < 1000) {
return now + " (" + ms + "ms skew)";
} else if (diff < 5 * 1000) {
return now + " (" + (ms / 1000) + "s skew)";
} else if (diff < 60 * 1000) {
return now + " <b>(" + (ms / 1000) + "s skew)</b>";
} else if (diff < 60 * 60 * 1000) {
return now + " <b>(" + (ms / (60 * 1000)) + "m skew)</b>";
} else if (diff < 24 * 60 * 60 * 1000) {
return now + " <b>(" + (ms / (60 * 60 * 1000)) + "h skew)</b>";
} else {
return now + " (" + (ms / (24 * 60 * 60 * 1000)) + "d)";
return now + " <b>(" + (ms / (24 * 60 * 60 * 1000)) + "d skew)</b>";
}
}
public boolean allowReseed() {
return (_context.netDb().getKnownRouters() < 10);
return (_context.netDb().getKnownRouters() < 30);
}
public int getAllPeers() { return _context.netDb().getKnownRouters(); }
public String getReachability() {
if (!_context.clock().getUpdatedSuccessfully())
return "ERR-ClockSkew";
int status = _context.commSystem().getReachabilityStatus();
switch (status) {
case CommSystemFacade.STATUS_OK:
@ -108,7 +120,7 @@ public class SummaryHelper {
case CommSystemFacade.STATUS_DIFFERENT:
return "ERR-SymmetricNAT";
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
return "ERR-Reject";
return "OK (NAT)";
case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
default:
return "Unknown";
@ -205,15 +217,9 @@ public class SummaryHelper {
public String getInboundMinuteKBps() {
if (_context == null)
return "0.0";
RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize");
if (receiveRate == null) return "0.0";
Rate rate = receiveRate.getRate(60*1000);
double bytes = rate.getLastTotalValue();
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(bps);
double kbps = _context.bandwidthLimiter().getReceiveBps()/1024d;
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(kbps);
}
/**
* How fast we have been sending data over the last minute (pretty printed
@ -223,15 +229,9 @@ public class SummaryHelper {
public String getOutboundMinuteKBps() {
if (_context == null)
return "0.0";
RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
if (receiveRate == null) return "0.0";
Rate rate = receiveRate.getRate(60*1000);
double bytes = rate.getLastTotalValue();
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(bps);
double kbps = _context.bandwidthLimiter().getSendBps()/1024d;
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(kbps);
}
/**
@ -243,14 +243,12 @@ public class SummaryHelper {
if (_context == null)
return "0.0";
RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize");
RateStat receiveRate = _context.statManager().getRate("bw.recvRate");
if (receiveRate == null) return "0.0";
Rate rate = receiveRate.getRate(5*60*1000);
double bytes = rate.getLastTotalValue();
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(bps);
double kbps = rate.getAverageValue()/1024;
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(kbps);
}
/**
@ -262,14 +260,12 @@ public class SummaryHelper {
if (_context == null)
return "0.0";
RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
RateStat receiveRate = _context.statManager().getRate("bw.sendRate");
if (receiveRate == null) return "0.0";
Rate rate = receiveRate.getRate(5*60*1000);
double bytes = rate.getLastTotalValue();
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(bps);
double kbps = rate.getAverageValue()/1024;
DecimalFormat fmt = new DecimalFormat("##0.00");
return fmt.format(kbps);
}
/**
@ -281,20 +277,11 @@ public class SummaryHelper {
if (_context == null)
return "0.0";
long received = _context.bandwidthLimiter().getTotalAllocatedInboundBytes();
RateStat receiveRate = _context.statManager().getRate("bw.recvRate");
if (receiveRate == null) return "0.0";
double kbps = receiveRate.getLifetimeAverageValue()/1024;
DecimalFormat fmt = new DecimalFormat("##0.00");
// we use the unadjusted time, since thats what getWhenStarted is based off
long lifetime = _context.clock().now()-_context.clock().getOffset()
- _context.router().getWhenStarted();
lifetime /= 1000;
if (received > 0) {
double receivedKBps = received / (lifetime*1024.0);
return fmt.format(receivedKBps);
} else {
return "0.0";
}
return fmt.format(kbps);
}
/**
@ -306,20 +293,11 @@ public class SummaryHelper {
if (_context == null)
return "0.0";
long sent = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes();
RateStat sendRate = _context.statManager().getRate("bw.sendRate");
if (sendRate == null) return "0.0";
double kbps = sendRate.getLifetimeAverageValue()/1024;
DecimalFormat fmt = new DecimalFormat("##0.00");
// we use the unadjusted time, since thats what getWhenStarted is based off
long lifetime = _context.clock().now()-_context.clock().getOffset()
- _context.router().getWhenStarted();
lifetime /= 1000;
if (sent > 0) {
double sendKBps = sent / (lifetime*1024.0);
return fmt.format(sendKBps);
} else {
return "0.0";
}
return fmt.format(kbps);
}
/**
@ -442,6 +420,28 @@ public class SummaryHelper {
return _context.tunnelManager().getOutboundTunnelCount();
}
/**
* How many inbound client tunnels we have.
*
*/
public int getInboundClientTunnels() {
if (_context == null)
return 0;
else
return _context.tunnelManager().getInboundClientTunnelCount();
}
/**
* How many active outbound client tunnels we have.
*
*/
public int getOutboundClientTunnels() {
if (_context == null)
return 0;
else
return _context.tunnelManager().getOutboundClientTunnelCount();
}
/**
* How many tunnels we are participating in.
*
@ -493,4 +493,4 @@ public class SummaryHelper {
public boolean updateAvailable() {
return NewsFetcher.getInstance(_context).updateAvailable();
}
}
}

View File

@ -3,6 +3,7 @@ package net.i2p.router.web;
import java.io.File;
import java.text.DecimalFormat;
import net.i2p.I2PAppContext;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
@ -30,7 +31,9 @@ public class UpdateHandler {
private static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
public UpdateHandler() {}
public UpdateHandler() {
this(ContextHelper.getContext(null));
}
public UpdateHandler(RouterContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(UpdateHandler.class);
@ -93,7 +96,7 @@ public class UpdateHandler {
public void run() {
_isRunning = true;
update();
System.setProperty("net.i2p.router.web.ReseedHandler.updateInProgress", "false");
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
_isRunning = false;
}
private void update() {
@ -143,7 +146,7 @@ public class UpdateHandler {
buf.append(" transferred<br />");
_status = buf.toString();
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
_status = "<b>Update downloaded</b><br />";
TrustedUpdate up = new TrustedUpdate(_context);
boolean ok = up.migrateVerified(RouterVersion.VERSION, SIGNED_UPDATE_FILE, "i2pupdate.zip");
@ -165,6 +168,7 @@ public class UpdateHandler {
_status = "<b>Transfer failed</b><br />";
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
}
private void restart() {

View File

@ -2,7 +2,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - logs</title>
<title>I2P Router Console - config networking</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>
@ -28,60 +28,55 @@
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigNetHandler.nonce")%>" />
<input type="hidden" name="action" value="blah" />
UDP port: <i><jsp:getProperty name="nethelper" property="udpPort" /></i><br />
<!-- <input name="udpPort" type="text" size="5" value="<jsp:getProperty name="nethelper" property="udpPort" />" /><br /> -->
<b>You must poke a hole in your firewall or NAT (if applicable) to receive new inbound UDP packets on
this port from arbitrary peers (this requirement will be removed in i2p 0.6.1, but is necessary now)</b><br />
TCP port: <input name="tcpPort" type="text" size="5" value="<jsp:getProperty name="nethelper" property="tcpPort" />" /> <br />
<b>You must poke a hole in your firewall or NAT (if applicable) so that you can receive inbound TCP
connections on it (this requirement will be removed in i2p 0.6.1, but is necessary now)</b>
<br />
<b>External UDP address:</b> <i><jsp:getProperty name="nethelper" property="udpAddress" /></i><br />
<b>Require SSU introductions? </b>
<input type="checkbox" name="requireIntroductions" value="true" <jsp:getProperty name="nethelper" property="requireIntroductionsChecked" /> /><br />
<p>If you can, please poke a hole in your NAT or firewall to allow unsolicited UDP packets to reach
you on your external UDP address. If you can't, I2P now includes supports UDP hole punching
with "SSU introductions" - peers who will relay a request from someone you don't know to your
router for your router so that you can make an outbound connection to them. I2P will use these
introductions automatically if it detects that the port is not forwarded (as shown by
the <i>Status: OK (NAT)</i> line), or you can manually require them here.
Users behind symmetric NATs, such as OpenBSD's pf, are not currently supported.</p>
<input type="submit" name="recheckReachability" value="Check network reachability..." />
<hr />
<b>Bandwidth limiter</b><br />
Inbound rate:
<input name="inboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundRate" />" /> KBytes per second
<input name="inboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundRate" />" /> KBps
bursting up to
<input name="inboundburstrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="inboundBurstRate" />" /> KBps for
<jsp:getProperty name="nethelper" property="inboundBurstFactorBox" /><br />
Outbound rate:
<input name="outboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundRate" />" /> KBytes per second
<input name="outboundrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundRate" />" /> KBps
bursting up to
<input name="outboundburstrate" type="text" size="2" value="<jsp:getProperty name="nethelper" property="outboundBurstRate" />" /> KBps for
<jsp:getProperty name="nethelper" property="outboundBurstFactorBox" /><br />
<i>A negative rate means there is no limit</i><br />
<i>KBps = kilobytes per second = 1024 bytes per second.<br />
A negative rate means a default limit of 16KBytes per second.</i><br />
Bandwidth share percentage:
<jsp:getProperty name="nethelper" property="sharePercentageBox" /><br />
Sharing a higher percentage will improve your anonymity and help the network
<hr />
Enable internal time synchronization? <input type="checkbox" <jsp:getProperty name="nethelper" property="enableTimeSyncChecked" /> name="enabletimesync" /><br />
<i>If disabled, your machine <b>must</b> be NTP synchronized - your clock must always
be within a few seconds of "correct". You will need to be able to send outbound UDP
packets on port 123 to one of the pool.ntp.org machines (or some other SNTP server).</i>
<b>Dynamic Router Keys: </b>
<input type="checkbox" name="dynamicKeys" value="true" <jsp:getProperty name="nethelper" property="dynamicKeysChecked" /> /><br />
<p>
This setting causes your router identity to be regenerated every time your IP address
changes. If you have a dynamic IP this option can speed up your reintegration into
the network (since people will have shitlisted your old router identity), and, for
very weak adversaries, help frustrate trivial
<a href="http://www.i2p.net/how_threatmodel#intersection">intersection
attacks</a> against the NetDB. Your different router identities would only be
'hidden' among other I2P users at your ISP, and further analysis would link
the router identities further.</p>
<p>Note that when I2P detects an IP address change, it will automatically
initiate a restart in order to rekey and to disconnect from peers before they
update their profiles - any long lasting client connections will be disconnected,
though such would likely already be the case anyway, since the IP address changed.
</p>
<hr />
<input type="submit" name="save" value="Save changes" /> <input type="reset" value="Cancel" /><br />
<i>Changing the TCP or UDP port will force a 'soft restart' - dropping your connections and clients as
if the router was stopped and restarted. <b>Please be patient</b> - it may take
a few seconds to complete.</i>
</form>
<hr />
<b>Advanced network config:</b>
<p>
One advanced network option has to do with reseeding - you should never need to
reseed your router as long as you can find at least one other peer on the network. However,
when you do need to reseed, a link will show up on the left hand side which will
fetch all of the routerInfo-* files from http://dev.i2p.net/i2pdb/. That URL is just an
apache folder pointing at the netDb/ directory of a router - anyone can run one, and you can
configure your router to seed off an alternate URL by adding the java environmental property
"i2p.reseedURL=someURL" (e.g. java -Di2p.reseedURL=http://dev.i2p.net/i2pdb/ ...). You can
also do it manually by getting routerInfo-*.dat files from someone (a friend, someone on IRC,
whatever) and saving them to your netDb/ directory.</p>
<p>
With the SSU transport, the internal UDP port may be different from the external
UDP port (in case of a firewall/NAT) - the UDP port field above specifies the
external one and assumes they are the same, but if you want to set the internal
port to something else, you can add "i2np.udp.internalPort=1234" to the
<a href="configadvanced.jsp">advanced</a> config and restart the router.
</p>
</div>
</body>

View File

@ -3,7 +3,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - config clients</title>
<title>I2P Router Console - config logging</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>
<jsp:useBean class="net.i2p.router.web.ConfigLoggingHelper" id="logginghelper" scope="request" />

View File

@ -3,7 +3,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - config clients</title>
<title>I2P Router Console - config service</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>

View File

@ -33,6 +33,9 @@
<input type="hidden" name="action" value="blah" />
<jsp:getProperty name="tunnelshelper" property="form" />
<hr />
<i>Note - Exploratory tunnel setting changes are stored in the router.config file.</i></br>
<i>Client tunnel changes are temporary and are not saved.</i><br>
<i>To make permanent client tunnel changes see the </i><a href="i2ptunnel/index.jsp">i2ptunnel page</a>.<br>
<input type="submit" name="shouldsave" value="Save changes" /> <input type="reset" value="Cancel" />
</form>
</div>

View File

@ -25,6 +25,10 @@ div.logo {
text-align: left;
}
div.toolbar {
font-weight: bold
}
div.routersummary {
/* width: 8em; */
/* height: 5em; */

View File

@ -45,7 +45,7 @@ more information).</p>
<p>The router by default also includes human's public domain <a href="http://www.i2p.net/sam">SAM</a> bridge,
which other client applications (such the <a href="http://duck.i2p/i2p-bt/">bittorrent port</a>) can use.
There is also an optimized library for doing large number calculations - jbigi - which in turn uses the
LGPL licensed <a href="http://swox.com/gmp/">GMP</a> library, tuned for various PC architectures. For
LGPL licensed <a href="http://swox.com/gmp/">GMP</a> library, tuned for various PC architectures. Launchers for windows users are built with <a href="http://launch4j.sourceforge.net/">Launch4J</a>, and the installer is built with <a href="http://www.izforge.com/izpack/">IzPack</a>. For
details on other applications available, as well as their licenses, please see the
<a href="http://www.i2p.net/licenses">license policy</a>. Source for the I2P code and most bundled
client applications can be found on our <a href="http://www.i2p.net/download">download page</a>, and is

View File

@ -0,0 +1,21 @@
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - job queue</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>
<%@include file="nav.jsp" %>
<%@include file="summary.jsp" %>
<div class="main" id="main">
<jsp:useBean class="net.i2p.router.web.JobQueueHelper" id="jobQueueHelper" scope="request" />
<jsp:setProperty name="jobQueueHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<jsp:setProperty name="jobQueueHelper" property="writer" value="<%=out%>" />
<jsp:getProperty name="jobQueueHelper" property="jobQueueSummary" />
</div>
</body>
</html>

View File

@ -1,3 +1,4 @@
<%@page import="java.io.File" %>
<% response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
@ -13,20 +14,32 @@
<a href="index.jsp"><img src="i2plogo.png" alt="Router Console" width="187" height="35" /></a><br />
[<a href="config.jsp">configuration</a> | <a href="help.jsp">help</a>]
</div>
<h4>
<div class="toolbar">
<% if (new File("docs/toolbar.html").exists()) { %>
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="toolbarhelper" scope="request" />
<jsp:setProperty name="toolbarhelper" property="page" value="docs/toolbar.html" />
<jsp:setProperty name="toolbarhelper" property="maxLines" value="300" />
<jsp:getProperty name="toolbarhelper" property="content" />
<% } else { %>
<!-- Could not find docs/toolbar.html! -->
<a href="susimail/susimail">Susimail</a> |
<a href="susidns/index.jsp">SusiDNS</a> |
<a href="syndie/">Syndie</a> |
<a href="i2psnark/">I2PSnark</a> |
<a href="http://localhost:7658/">My Eepsite</a> <br>
<a href="i2ptunnel/index.jsp">I2PTunnel</a> |
<a href="tunnels.jsp">Tunnels</a> |
<a href="profiles.jsp">Profiles</a> |
<a href="netdb.jsp">NetDB</a> |
<a href="logs.jsp">Logs</a> |
<a href="oldconsole.jsp">Internals</a> |
<a href="jobs.jsp">Jobs</a> |
<a href="oldstats.jsp">Stats</a> |
<a href="i2ptunnel/" target="_blank">I2PTunnel</a> |
<a href="susimail/susimail" target="_blank">Susimail</a>
<a href="oldconsole.jsp">Internals</a>
<% } %>
<jsp:useBean class="net.i2p.router.web.NavHelper" id="navhelper" scope="request" />
<jsp:setProperty name="navhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<jsp:getProperty name="navhelper" property="clientAppLinks" />
</h4>
</div>
<jsp:useBean class="net.i2p.router.web.NoticeHelper" id="noticehelper" scope="request" />
<jsp:setProperty name="noticehelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />

View File

@ -3,7 +3,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - home</title>
<title>I2P Router Console - internals</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>

View File

@ -3,7 +3,7 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - home</title>
<title>I2P Router Console - statistics</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>

View File

@ -40,7 +40,7 @@
<b>High capacity:</b> <jsp:getProperty name="helper" property="highCapacityPeers" /><br />
<b>Well integrated:</b> <jsp:getProperty name="helper" property="wellIntegratedPeers" /><br />
<b>Failing:</b> <jsp:getProperty name="helper" property="failingPeers" /><br />
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br />
<!-- <b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br /> -->
<b>Known:</b> <jsp:getProperty name="helper" property="allPeers" /><br /><%
if (helper.getActivePeers() <= 0) {
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
@ -64,7 +64,7 @@
%><hr />
<u><b>Bandwidth in/out</b></u><br />
<b>1m:</b> <jsp:getProperty name="helper" property="inboundMinuteKBps" />/<jsp:getProperty name="helper" property="outboundMinuteKBps" />KBps<br />
<b>1s:</b> <jsp:getProperty name="helper" property="inboundMinuteKBps" />/<jsp:getProperty name="helper" property="outboundMinuteKBps" />KBps<br />
<b>5m:</b> <jsp:getProperty name="helper" property="inboundFiveMinuteKBps" />/<jsp:getProperty name="helper" property="outboundFiveMinuteKBps" />KBps<br />
<b>Total:</b> <jsp:getProperty name="helper" property="inboundLifetimeKBps" />/<jsp:getProperty name="helper" property="outboundLifetimeKBps" />KBps<br />
<b>Used:</b> <jsp:getProperty name="helper" property="inboundTransferred" />/<jsp:getProperty name="helper" property="outboundTransferred" /><br />
@ -72,9 +72,9 @@
<jsp:getProperty name="helper" property="destinations" />
<u><b>Tunnels</b></u><br />
<b>Inbound:</b> <jsp:getProperty name="helper" property="inboundTunnels" /><br />
<b>Outbound:</b> <jsp:getProperty name="helper" property="outboundTunnels" /><br />
<u><b>Tunnels in/out</b></u><br />
<b>Exploratory:</b> <jsp:getProperty name="helper" property="inboundTunnels" />/<jsp:getProperty name="helper" property="outboundTunnels" /><br />
<b>Client:</b> <jsp:getProperty name="helper" property="inboundClientTunnels" />/<jsp:getProperty name="helper" property="outboundClientTunnels" /><br />
<b>Participating:</b> <jsp:getProperty name="helper" property="participatingTunnels" /><br />
<hr />

View File

@ -0,0 +1,14 @@
<%
String uri = request.getRequestURI();
if (uri.endsWith(".css")) {
response.setContentType("text/css");
} else if (uri.endsWith(".png")) {
response.setContentType("image/png");
} else if (uri.endsWith(".gif")) {
response.setContentType("image/gif");
} else if (uri.endsWith(".jpg")) {
response.setContentType("image/jpeg");
}
net.i2p.util.FileUtil.readFile(uri, "./docs", response.getOutputStream());
%>

View File

@ -5,6 +5,13 @@
<web-app>
<!-- precompiled servlets -->
<!-- yeah, i'm lazy, using a jsp instead of a servlet.. -->
<servlet-mapping>
<servlet-name>net.i2p.router.web.jsp.viewtheme_jsp</servlet-name>
<url-pattern>/themes/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30

View File

@ -90,6 +90,7 @@ void string_copy(string_t src,string_t dest) {
void string_copy_raw(string_t src, void* dest,size_t size) {
size = min(src->size,size);
memcpy(dest,src->data,size);
((char*)dest)[size] = '\0';
}
const char* string_data(string_t self) {

View File

@ -1,321 +0,0 @@
#!/usr/bin/perl
## Copyright 2004 Brian Ristuccia. This program is Free Software;
## You can redistribute it and/or modify it under the same terms as
## Perl itself.
package Net::SAM;
@ISA = ( "IO::Socket::INET" );
use strict;
use POSIX;
use Switch;
use IO::Socket;
use IO::Select;
#use Net::SAM::StreamSession;
#use Net::SAM::DatagramSession;
#use Net::SAM::RawSession;
sub new {
my ($class) = shift;
my $type = ref($class) || $class;
my $self = $type->SUPER::new("127.0.0.1:7656");
${*$self}->{incomingraw} = [];
# Connect us to the local SAM proxy.
# my $samsock = IO::Socket::INET->new('127.0.0.1:7657');
#$self->{samsock}=$samsock;
# Say hello, read response.
$self->SUPER::send("HELLO VERSION MIN=1.0 MAX=1.0\n");
while (! ${*$self}->{greeted}) {
$self->readprocess();
}
print "Created SAM object\n";
return $self;
}
sub lookup {
my $self = shift;
my $name= shift;
$self->SUPER::send("NAMING LOOKUP NAME=$name\n");
undef ${*$self}->{RESULT};
while (! ${*$self}->{RESULT}) {
$self->readprocess();
}
if ( ${*$self}->{RESULT} == "OK" ) {
return ${*$self}->{VALUE};
} else {
return undef;
}
}
#sub createsession {
# my ($self) = shift;
# my ($sesstype) = shift;
# print $self->{samsock} "SESSION CREATE STYLE=$SESSTYPE DESTINATION=$DEST, DIRECTION=
#}
#sub waitfor {
# my ($self) = shift;
# my ($prefix) = shift;
# my ($response) = <$samsock>;#
# if $response =~
#}
sub readprocesswrite {
my $self = shift;
$self->readprocess();
$self->dowrite();
}
sub doread {
my $self = shift;
my $rv;
my $data;
$rv = $self->recv($data, $POSIX::BUFSIZE, 0);
if ( defined($rv) && ( length($data) >= 1 ) ) {
# We received some data. Put it in our buffer.
${*$self}->{inbuffer} += $data;
} else {
# No data. Either we're on a non-blocking socket, or there
# was an error or EOF
if ( $!{EAGAIN} ) {
return 1;
} else {
# I suppose caller can look at $! for details
return undef;
}
}
}
sub dowrite {
my $self = shift;
my $rv;
my $data;
$rv = $self->send(${*$self}->{outbuffer}, 0);
if ( ! defined($rv) ) {
warn "SAM::dowrite - Couldn't write for no apparent reason.\n";
return undef;
}
if ( $rv == length(${*$self}->{outbuffer}) || $!{EWOULDBLOCK} ) {
substr(${*$self}->{outbuffer},0, $rv) = ''; # Remove from buffer
# Nuke buffer if empty
delete ${*$self}->{outbuffer} unless length(${*$self}->{outbuffer});
} else {
# Socket closed on us or something?
return undef;
}
}
sub messages {
my $self = shift;
return @{ ${*$self}->{messages} };
}
sub queuemessage {
my $self = shift;
my $message = shift;
push @{ ${*$self}->{messages} } , $message;
}
sub unqueuemessage {
my $self = shift;
return unshift(@{ ${*$self}->{messages} } );
}
sub readprocess {
my $self = shift;
$self->doread();
$self->process();
}
sub process {
my $self = shift;
my %tvhash;
my $payload;
# Before we can read any new messages, if an existing message has payload
# we must read it in. Otherwise we'll create garbage messages containing
# the payload of previous messages.
if ( ${*$self}->{payloadrequired} >= 1 ) {
if ( length( ${*$self}->{inbuffer} ) >= ${*$self}->{payloadrequired} ) {
# Scarf payload from inbuffer into $payload
$payload = substr(${*$self}->{inbuffer}, 0,
${*$self}->{payloadrequired});
# Nuke payload from inbuffer
substr(${*$self}->{inbuffer}, 0,
${*$self}->{payloadrequired} ) = '';
# Put message with payload into spool
push @{ ${*$self}->{messages} } ,
${*$self}->{messagerequiringpayload}.$payload;
# Delete the saved message requiring payload
delete ${*$self}->{messagerequiringpayload};
} else {
# Insufficient payload in inbuffer. Try again later.
return 1;
}
}
if ( ${*$self}->{inbuffer} =~ s/(.*\n)// ) {
%tvhash = $self->_hashtv($1); # Returns a tag/value hash
if ( $tvhash{SIZE} ) {
# We've got a message with payload on our hands. :(
${*$self}->{payloadrequired} = $tvhash{SIZE};
${*$self}->{messagerequiringpayload} = $1;
return 1; # Could call ourself here, but we'll get called again.
} else {
push @{ ${*$self}->{messages} } , $1;
}
}
return 1;
}
# sub junk {
# print "readprocess: " . $self->connected() . "\n";
# # May block if the SAM bridge gets hosed
# my $response = <$self>;
# print "readprocess: $!" . $self->connected() . "\n";
# chomp $response;
# my ($primative, $more, $extra) = split (' ', $response, 3);
# $primative = uc($primative);
# print "readprocess: " . $self->connected() . " -- $primative -- $more -- $extra\n";
# switch ($primative) {
# case "HELLO" {
# if ($more !~ m/REPLY/ ) { die ("Bogus HELLO response") }
# if ($extra =~ m/NOVERSION/ ) {
# die("SAM Bridge Doesn't support my version") ;
# }
# $self->_hashtv($extra);
# ${*$self}->{greeted} = 1;
# };
# case "SESSION" {
# if ( $more !~ m/STATUS/ ) {
# die("Bogus SESSION response");
# }
# $self->_hashtv($extra);
# }
# case "STREAM" {};
# case "DATAGRAM" {
# if ( $more !~ m/RECEIVE/ ) {
# die("Bogus DATAGRAM response.");
# }
# $self->_hashtv($extra);
# push @{ ${*$self}->{incomingdatagram } },
# [ ${*$self}->{DESTINATION},
# $self->_readblock(${*$self}->{SIZE}) ];
# };
# case "RAW" {
# if ( $more !~ m/RECEIVE/ ) {
# die("Bogus RAW response.");
# }
# $self->_hashtv($extra);
# push @{ $self->{incomingraw} }, $self->_readblock($self->{SIZE});
# };
# case "NAMING" {
# if ( $more !~ m/REPLY/ ) {
# die("Bogus NAMING response");
# }
# $self->_hashtv($extra);
# };
# case "DEST" {};
# }
# return 1;
# }
sub getfh {
# Return the FH of the SAM socket so apps can select() or poll() on it
my $self = shift;
return $self->{samsock};
}
sub _readblock {
my $self = shift;
my $size = shift;
my $chunk;
my $payload;
while ( $size > 1 ) {
# XXX: May block. No error checking.
print "readblock: $size\n";
$size -= $self->SUPER::recv($chunk, $size);
$payload .= $chunk;
}
return $payload;
}
sub _hashtv {
my $self = shift;
my $tvstring = shift;
my $tvhash;
while ( $tvstring =~ m/(\S+)=(\S+)/sg ) {
$tvhash->{$1}=$2;
print "hashtv: $1=$2\n"
}
return $tvhash;
}
sub DESTROY {
# Do nothing yet.
}
#sub StreamSession {
# my $self = shift;
# return Net::SAM::StreamSession->new($self);
#}
#sub DatagramSession {
# return Net::SAM::DatagramSession->new($self);
#}
#sub RawSession {
# return Net::SAM::RawSession->new($self);
#}
1;

View File

@ -1,48 +0,0 @@
#!/usr/bin/perl
package Net::SAM::DatagramSession;
use Net::SAM;
@ISA = ("Net::SAM");
sub new {
my ($class) = shift;
my ($dest , $direction, $options) = shift;
my $self = $class->SUPER::new(@_);
$self->SUPER::send("SESSION CREATE STYLE=DATAGRAM DESTINATION=$dest DIRECTION=$direction $options\n");
undef ${*$self}->{RESULT};
while ( ! ${*$self}->{RESULT} ) {
$self->readprocess() || return undef;
}
if ( ${*$self}->{RESULT} == "OK" ) {
return $self;
} else {
return undef; # sorry.
}
}
sub send {
my $self = shift;
my $destination = shift;
my $message = shift;
my $size = length($message);
$self->SUPER::send("DATAGRAM SEND DESTINATION=$destination SIZE=$size\n$message");
}
sub receive {
my $self = shift;
# Shift one off the fifo array. Returns undef if none wait.
return shift @{ $self->{incomingdatagram} };
}
1;

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