Compare commits

...

204 Commits

Author SHA1 Message Date
ea0d4ffd7f * 2008-02-10 0.6.1.31 released
2008-02-10 Complication
    * Update news and version numbers
2008-02-10 13:59:20 +00:00
zzz
1365d3e3b9 Remove orion reference in dnfh error page 2008-02-08 21:04:23 +00:00
zzz
40befb5a92 remove comments from hosts.txt 2008-02-08 19:16:48 +00:00
zzz
9c16eec361 checklist updates, +x some files 2008-02-08 00:15:32 +00:00
zzz
093c69637d * build.xml: Add some apps to javadoc
* checklist.txt: Add some things
    * news.xml: Minor edit
    * runplain.sh: Add some comments
2008-02-06 16:38:23 +00:00
zzz
134ec7acea * news.xml: make links relative
* wrapper.config: Add some comments
* checklist.txt: Add some things
2008-02-05 03:14:18 +00:00
0802a5ae40 2008-02-05 Complication
* Change the dates too (sorry for such forgetfulness!)
2008-02-04 23:52:11 +00:00
14ddfb360f 2008-02-04 Complication
* Also use the new key for checking, and add it into news.xml
2008-02-04 23:20:28 +00:00
78ad831028 2008-02-04 Complication
* Added my release signing key into TrustedUpdate.java
2008-02-04 21:34:52 +00:00
zzz
22f1684262 * NewsFetcher: Change fetch failed from error to warning
* installer: Fix URL and "email"
    * checklist.txt: New release checklist
2008-01-31 19:56:00 +00:00
zzz
83f51b4a66 2008-01-29 zzz
* Addressbook: Change default subscription
    * ConfigUpdateHandler: Change default news URL
    * initialNews.xml: Update version to .31
    * news.xml: More updates
    * hosts.txt: Add i2p-projekt.i2p
    * readme.html: More URL updates
    * SusiDNS: Change references to default subscription
2008-01-29 22:20:04 +00:00
zzz
9c28de0704 2008-01-28 zzz
* news.xml: Updates, still preliminary
    * ReseedHandler: Change default URL
    * i2ptunnel.config: Change default outproxies
    * readme.html: Change *.i2p.net URLs
    * help.jsp: Change *.i2p.net URLs
    * eepsite_index.html: Change stats.i2p addressbook subscription URL
    * hosts.txt: Add krabs.i2p, true.i2p, www.i2p2.i2p
2008-01-28 19:24:12 +00:00
zzz
c69fda2298 Drop unused directories 2008-01-27 14:48:50 +00:00
zzz
cabb22331b Drop unused directories 2008-01-27 14:42:04 +00:00
zzz
5b3aca29a8 replace orion.i2p with perv.i2p/stats.cgi 2008-01-09 22:40:33 +00:00
zzz
f35cbf59d8 * NewsFetcher: add last-modified support, reduce number of retries
* Error pages: add icon and logo,
        clarify 'destination not found' and 'proxy not found' pages
2008-01-09 04:11:12 +00:00
zzz
a96119d09b 2008-01-08 zzz
* addressbook: Limit size of subscribed hosts.txt,
        don't save old etag or last-modified data
    * EepGet: Add some logging,
        enforce size limits even when size not in returned header,
        don't return old etag or last-modified data,
        don't call transferFailed listener more than once
2008-01-09 02:15:43 +00:00
zzz
2711294aee (zzz) sign my update signing key 2008-01-09 01:56:38 +00:00
zzz
f838b1828b 2008-01-07 zzz
* profiles.jsp formatting cleanup
    * NTCP: Reduce max idle time from 60m to 20m
    * NTCP: Fix idle time on connections with zero messages,
      correctly drop these connections
2008-01-07 08:09:43 +00:00
zzz
fbf6282c1a 2008-01-03 zzz
* addressbook: Do basic validation of hostnames and destkeys
    * susidns: Add support for the private addressbook,
      update the text and links somewhat
2008-01-04 02:37:24 +00:00
zzz
5195a5c1fc 2008-01-02 zzz
* Add stats.i2p to the jump list
    * Impose 20MB limit on POSTs and catch OOMs in POST
    * eepsite_index.html: add stats.i2p services
    * addressbook: log source of new keys; disallow dests > 516 bytes
    * addressbook: convert hostnames to lower case to prevent duplicates
    * susidns: generalize references to orion
2008-01-02 22:08:48 +00:00
zzz
62b18b18b5 2007-12-29 zzz
* Tweak IRC inbound PONG filtering to fix xchat/BitchX lagometers
2007-12-30 03:45:09 +00:00
zzz
7c8f519b35 2007-12-29 zzz
* Allow commas in router.trustedUpdateKeys and router.updateURL again
2007-12-29 23:59:24 +00:00
zzz
d6fb979616 2007-12-29 zzz
* Change default news host from dev.i2p.net to dev.i2p
2007-12-29 22:15:11 +00:00
zzz
f568d21969 2007-12-29 zzz
* Change jetty timeout from 30 to 60 sec (thanks sponge!)
2007-12-29 21:08:29 +00:00
zzz
7fe9d590f5 2007-12-28 zzz
* Add zzz's update signing key
2007-12-28 20:27:16 +00:00
0a1240ebfd 2007-12-26 Complication
* Improve reseed handler (less repetitive code,
      avoid reporting errors when less than 10% of fetches fail)
2007-12-26 20:55:07 +00:00
4e68f2a157 2007-12-26 Complication
* Escape both CR, LF and CR LF line breaks in Router.saveConfig()
      and unescape them in DataHelper.loadProps() to support
      saving and loading config properties with line breaks
    * Change the update URLs textbox into a textarea like keys have,
      so different URLs go on different lines
    * Modify TrustedUpdate to provide a method which supplies a key list
      delimited with CR LF line breaks
    * Modify DEFAULT_UPDATE_URL to supply a default URL list
      delimited with CR LF line breaks
    * Modify selectUpdateURL() to handle URL lists
      delimited by any kind of line breaks
    * Start saving trusted update keys
    * Improve formatting on configupdate.jsp
2007-12-26 08:14:54 +00:00
zzz
e9bd6907d1 2007-12-22 zzz
* Add support for multiple update URLs
    * Change default for update to use i2p proxy,
      add several URLs as defaults
    * Enable trusted key form on configupdate.jsp
2007-12-22 23:58:46 +00:00
zzz
b20495c39f (zzz) Clarify the 'destination not found' error page 2007-12-22 20:54:44 +00:00
zzz
0ecbc4c27b (zzz) remove anonymitytracker from default tracker list, not seen in over 6 months 2007-12-17 02:33:05 +00:00
zzz
4d5b1d4c3f 2007-12-10 zzz
* Fix NPE in CLI TrustedUpdate keygen
2007-12-10 22:22:59 +00:00
17b719f3f7 2007-12-02 Complication
* Commit SAM v2 patch from mkvore (thank you!)
    * Minor reformatting to preserve consistent whitespace
      in old SAM classes (new classes unaltered)
2007-12-03 04:19:25 +00:00
979a3e98d8 2007-12-01 Complication
* Separate the checks "does Jetty .zip file need downloading"
      and "does Jetty .zip file need extracting" in the Jetty buildfile.
      First download (unless already done), then extract (unless done).
2007-12-02 03:13:15 +00:00
zzz
c6a1112f0a 2007-11-26 zzz
* i2psnark: add timeout for receive inactivity
2007-11-26 21:53:58 +00:00
zzz
4ebcc95d9f 2007-11-24 zzz
* i2psnark: increase streaming lib write timeout to 240 sec and change
      timeout action from "ping" to "disconect", as the fix in .30 to
      honor options on outbound connections led to hung outbound connections
      (bitfield never transmitted, connection never dropped)
2007-11-24 20:22:45 +00:00
zzz
7e59ce27fa (zzz) i2phost.i2p => i2host.i2p 2007-11-06 20:15:25 +00:00
zzz
22345a264e (zzz) fix new jump server typo 2007-11-06 19:53:06 +00:00
03739996da 2007-11-06 jrandom
* add i2phost.i2p to the jump list
2007-11-06 11:26:01 +00:00
zzz
819a72d4f6 2007-10-11 zzz
* IRC Proxy: Fix several possible anonymity holes:
      - Block CTCP in NOTICE messages
      - Block CTCP anywhere in PRIVMSG and NOTICE, not just at first character
      - Check for lower case commands
    (Thanks sponge!)
2007-10-11 06:03:21 +00:00
e480931e20 2007-10-07 jrandom
* back out the NTCP backlog pushback, as it could be used to mount an
      active anonymity attack.
2007-10-08 04:11:36 +00:00
zzz
3f01d0a69e (zzz) .30 details 2007-10-08 04:03:40 +00:00
f67f47f0cd 0.6.1.30 2007-10-08 03:09:35 +00:00
5ad6ee60eb 0.6.1.30 2007-10-08 03:01:47 +00:00
5accba6cdc 2007-10-07 Complication
* Fix an issue in EepGet whereby sending of "etag" and "lastModified" headers
      broke retrying.
2007-10-08 02:36:17 +00:00
zzz
313e1704df 2007-09-27 zzz
* Implement pushback of NTCP transport backlog to the outbound tunnel selection code
    * Clean up the NTCP and UDP tables on peers.jsp to be consistent,
      fix some of the sorting
2007-09-27 03:52:32 +00:00
zzz
cf4d2b17c9 2007-09-22 zzz
* Send messages for the same destination out the same outbound
      tunnel to reduce out-of-order delivery.
2007-09-23 02:44:34 +00:00
zzz
9145eedc35 2007-09-19 zzz
* i2psnark: Fix broken multifile torrent Delete;
        cleanup Storage resources in AddTorrent;
        don't autostart torrent after Create
2007-09-20 01:44:02 +00:00
zzz
b772179077 2007-09-18 zzz
* eepsite_index.html: Add links to trevorreznik address book
    * streaming lib: Fix SocketManagerFactory to honor options on outbound connections
    * streaming lib: Fix setDefaultOptions() when called with a ConnectionOptions parameter
    * i2psnark: Don't make outbound connections to already-connected peers
    * i2psnark: Debug logging cleanup
2007-09-18 19:09:19 +00:00
zzz
9054a196ce 2007-09-14 zzz
* eepget: Increase header timeout to 45s
    * HTTP proxy: Return a better error message for localhost requests
    * tunnels: Fix PooledTunnelCreatorConfig memory leak
2007-09-15 01:58:30 +00:00
zzz
d28a96ac7d 2007-09-09 zzz
* eepget: Add support for Last-Modified and If-Modified-Since
    * addressbook: Finish incomplete support for Last-Modified
2007-09-09 17:38:53 +00:00
zzz
9c73f80ac3 (zzz) Copy over SocketTimeout.java file from syndie for EepGet.java 2007-09-08 20:21:15 +00:00
20c46cff04 synced up with the eepget from the syndie source tree (allowing better
control of timeouts and transparent redirection).  the users of eepget
in this source tree don't necessarily use the timeout controls, though
they can be updated to do so
2007-09-08 02:24:01 +00:00
f332513755 added trevorreznik.i2p 2007-09-02 01:36:54 +00:00
zzz
cb69a66498 (zzz) .29 announcement 2007-08-24 02:34:40 +00:00
1c66543938 0.6.1.29 released 2007-08-24 00:33:28 +00:00
zzz
53ab3c472e 2007-08-13 zzz
* readme.html - Add inproxy.tino.i2p, replace search.i2p with eepsites.i2p,
      tweak the eepsite and troubleshooting sections
2007-08-13 19:42:59 +00:00
zzz
a4b221fa71 2007-08-11 zzz
* Add stats for individual tunnel rates (nice when graphed)
    * i2psnark: Fix outbound tunnel nickname
2007-08-11 20:48:14 +00:00
e3e1d0842d 2007-08-05 Complication
* Update the sharing calculator on config.jsp
      and explain the trade-off even more thoroughly.
2007-08-06 03:35:42 +00:00
99b5bf9609 2007-08-04 Complication
* Lower the threshold between the K and L bandwidth class,
      so that K is now < 12 KB/s, instead of <= 16 KB/s.
      Hopefully this lets people with 128 kbit/s (16 KB/s) upload lines
      participate in routing, if they keep the default share percentage.
2007-08-05 03:25:30 +00:00
zzz
da10fe0df7 (zzz) ask for bandwidth 2007-07-19 18:05:44 +00:00
zzz
9fd5ba7b2d 2007-07-16 zzz
* i2psnark: Add tooltip info for choked/uninterested
2007-07-16 21:15:51 +00:00
zzz
05b5df9d76 2007-07-16 zzz
* Make selection of graphed data configurable via configstats.jsp,
      remove most of the default graphs to save some memory
2007-07-16 20:47:57 +00:00
zzz
5c1dc79767 2007-07-15 zzz
* Add current values to graph legends
    * Fix up previous Rate fix to check for divide by zero
2007-07-15 18:34:33 +00:00
4acd2996c5 2007-07-14 Complication
* Take the post-download routerInfo size check back out of ReseedHandler,
      since it wasn't helpful, and a lower limit caused false warnings.
    * Give EepGet ability to enforce a min/max HTTP response size.
    * Enforce a maximum response size of 8 MB when ReseedHandler
      downloads into a ByteArrayOutputStream.
    * Refactor ReseedHandler/ReseedRunner from static to ordinary classes,
      change invocation from RouterConsoleRunner accordingly.
    * Add an EepGet status listener to ReseedHandler to log causes of reseed failure,
      provide status reports to indicate the progress of reseeding.
    * Enable icon for default eepsite, and the index page
      of the router console (more later).
2007-07-15 00:56:18 +00:00
zzz
16fa6a89bc 2007-07-14 zzz
* Clean up graphs.jsp - set K=1024 where appropriate,
      output image sizes in html, catch ooms, other minor tweaks
    * Fix current event count truncation which fixes graphs with low
      60-sec event counts displaying high values
      (bw.* and router.* graphs for example were 1.5x too high)
      Affects all "events per period" (non-lifetime) counts.
2007-07-14 18:44:11 +00:00
zzz
2a72e8574b 2007-07-09 zzz
* i2psnark: give a better error message for a non-i2p torrent
2007-07-10 01:20:37 +00:00
zzz
d4a1bcf28f 2007-07-07 zzz
* Add auto-detect IP/Port to NTCP. When enabled on config.jsp,
      SSU will notify/restart NTCP when the external address changes.
      Now you can enable inbound TCP without a static IP or dyndns service.
2007-07-07 20:03:50 +00:00
zzz
409b71def5 2007-07-04 zzz
* Display calculated share bandwidth and remove load testing
      on config.jsp
2007-07-04 22:58:48 +00:00
zzz
2dc5fbda02 2007-07-01 zzz
* Replace broken option i2np.udp.alwaysPreferred with
      i2np.udp.preferred and adjust UDP bids; possible settings are
      "false" (default), "true", and "always".
      Default setting results in same behavior as before
      (NTCP is preferred unless it isn't established and UDP is established).
      Use to compare NTCP and UDP transports.
2007-07-01 22:07:52 +00:00
71aaf03d09 2007-06-27 jrandom
* fix for a streaming lib bug that could leave a thread waiting
      indefinitely (thanks Complication!)
2007-06-28 01:51:16 +00:00
30c99e630b 2007-06-16 Complication
* First pass on EepGet and ReseedHandler improvements,
      please avoid use on routers which matter!
    * Give EepGet ability of downloading into an OutputStream,
      such as the ByteArrayOutputStream of ReseedHandler.
    * Detect failure to reseed better, report it persistently
      and more verbosely, provide a link to logs
      and suggest manual reseed.
2007-06-16 23:15:49 +00:00
445b39171a 2007-05-06 Complication
* spelling correction to history.txt
2007-05-06 20:02:04 +00:00
571c2d6047 2007-05-06 Complication
* Fix the build.xml file, so the preppkg build target won't try copying files
      which became deprecated with the old Syndie (thank for alerting, itsu!)
2007-05-06 19:52:39 +00:00
zzz
42ff763933 (zzz) 4-10 2007-04-11 06:01:35 +00:00
zzz
727edc3ff9 (zzz) add 204 log link 2007-04-08 04:18:18 +00:00
zzz
82a4758a0a (zzz) 3-27 and 4-3 2007-04-05 06:11:21 +00:00
zzz
915914ebb3 2007-03-31 zzz
* Add trevorreznik jump server to the http proxy error page
    * Add anonymity to the trackers supporting details links in i2psnark
2007-03-31 21:50:51 +00:00
zzz
c438b56378 (zzz) 3-20 2007-03-25 22:58:18 +00:00
zzz
307ccfb1b4 2007-03-24 zzz
* Remove Syndie from build targets and navbar
2007-03-24 07:57:37 +00:00
zzz
34e23259b4 2007-03-22 zzz
* i2psnark tracker handling tweaks:
    -   Add link to tracker details page (Postman only for now, requires bytemonsoon patch)
    -   Add Base URL to tracker list configuration
    -   Web page links built from tracker list Base URLs
    -   Only build and sort tracker list once
    -   Add anonymityWeb tracker to default list
    -   Add tooltip info for TrackerErrs
    -   Stop torrent if not registered with tracker
    -   Mark temp files as delete on exit
2007-03-22 05:21:25 +00:00
zzz
036802d66a 2007-03-18 zzz
* i2psnark: Cleanup some handling of saved partial pieces
    * i2psnark: Put bit counting in Bitfield.java for efficiency
    * i2psnark: Save torrent completion state in i2psnark.config
2007-03-18 21:57:01 +00:00
zzz
6a7dbc8e3a (zzz) i2psnark: Save torrent completion status in i2psnark.config 2007-03-18 21:43:41 +00:00
zzz
cf4a9ffc27 (zzz) i2psnark: Put bit counting in Bitfield.java for efficiency 2007-03-18 21:28:28 +00:00
zzz
6ef4adf318 (zzz) i2psnark: Remove saved partial when halted, don't save partial when halted 2007-03-18 21:25:18 +00:00
zzz
f84c9bf3b1 (zzz) .28 news 2007-03-18 02:17:16 +00:00
da0837bd58 ugh, jrandom sucks. 0.6.1.28 2007-03-18 01:47:59 +00:00
9094a62273 oops 2007-03-17 21:19:37 +00:00
026183a655 * 2007-03-17 0.6.1.2 released 2007-03-17 21:18:12 +00:00
zzz
b033c7945c (zzz) i2psnark: clarify that total uploader value is peers on UI. (thanks jadeSerpent) 2007-03-15 18:26:06 +00:00
zzz
0c2dcf0845 (zzz) 3-13 mtg 2007-03-15 08:21:35 +00:00
zzz
b6e597e5bf 2007-03-13 zzz
* i2psnark: Make max total uploaders configurable (thanks Amiga4000!)
2007-03-14 04:06:27 +00:00
ae402baa71 2007-03-12 jrandom
* dodge a race on startup (thanks zzz!)
2007-03-12 18:19:57 +00:00
zzz
d6c8a4d9eb 2007-03-10 zzz
* Streaming lib: Change initial RTT deviation from RTT to RTT/2
      to reduce early RTO values
2007-03-10 08:45:27 +00:00
zzz
8e2849b7e5 2007-03-08 zzz
* i2psnark changes to improve upload performance:
    *  Implement total uploader limit (10)
    *  Don't timeout non-piece messages out
    *  Change chunk size to 32K (was 64K)
    *  Change request limit to 64K (was 256K)
    * i2psnark: Disconnect from seeds when complete
2007-03-08 18:55:17 +00:00
zzz
0aa0cd330f (zzz) take dynamic router keys off the config page 2007-03-07 21:05:21 +00:00
zzz
0960cafaf5 2007-03-07 zzz
* Streaming lib changes to improve upstream performance during congestion:
    *   Change min window size from 12 to 1
    *   Change max timeout from 10 to 45 sec
    *   Change initial timeout from 10 to 15 sec
    *   Change intial window size for i2psnark from 12 to 1
    *   Change slow start growth rate for i2psnark from 1/2 to 1
2007-03-07 05:11:45 +00:00
zzz
2088a28053 (zzz) Mar. 04 - More tweaks to the default eepsite start page to help
people start up the tunnel which is now stopped by default
2007-03-04 23:25:18 +00:00
zzz
a5c4ba3bff 2007-03-03 zzz
* Upgrade from Jetty 5.1.6 to 5.1.12 which fixes spaces in URL
    * Add a updaterWithJetty build target
2007-03-03 20:30:52 +00:00
zzz
1bbd2cf52e 2007-03-03 zzz
* Implement priority sending for NTCP
    * Disable trimForOverload() in tunnel BuildExecutor which
      was preventing tunnel builds when outbound traffic was high
      (i.e. most of the time when running i2psnark)
2007-03-03 20:11:05 +00:00
zzz
ce50efa60c (zzz) 02-28 i2psnark file reopen cleanup 2007-03-01 02:29:11 +00:00
zzz
a3c64a9ba3 (zzz) 02-28 add peer details to i2psnark web page 2007-03-01 02:22:14 +00:00
zzz
1447164a8a (zzz) 2-20 2007-02-24 09:15:29 +00:00
zzz
bc0bf8d7ff (zzz) Fix Syndie URL typo 2007-02-22 08:51:14 +00:00
zzz
9f346f488e (zzz) add links 2007-02-17 02:07:45 +00:00
zzz
760d7d9704 (zzz) 0.6.1.27 2007-02-16 19:35:09 +00:00
77310e17d1 * 2007-02-15 0.6.1.27 released
2007-02-15  jrandom
    * Limit the whispering floodfill sends to at most 3 randomly
      chosen from the known floodfill peers
2007-02-15 23:25:04 +00:00
e54b964929 2007-02-14 jrandom
* Don't filter out KICK and H(ide oper status) IRC messages
      (thanks Takk and postman!)
2007-02-14 21:35:43 +00:00
809f3e847b 2007-02-13 jrandom
* Tell our peers about who we know in the floodfill netDb every
      6 hours or so, mitigating the situation where peers lose track
      of floodfill routers.
    * Disable the Syndie updater (people should use the new Syndie,
      not this one)
    * Disable the eepsite tunnel by default
2007-02-14 04:33:36 +00:00
zzz
f4beebe60d (zzz) 02-13 2007-02-14 03:04:11 +00:00
827e427f0b added trac.i2p 2007-02-12 10:26:21 +00:00
zzz
c02125511d (zzz) 02-06 2007-02-08 18:51:58 +00:00
zzz
1aa1069b6f (zzz) 01-30 2007-02-02 02:50:43 +00:00
zzz
91d281077d 2007-01-30 zzz
* i2psnark: Don't hold _snarks lock while checking a snark,
      so web page is responsive at startup
2007-01-30 08:58:19 +00:00
zzz
f339dec024 2007-01-29 zzz
* i2psnark: Add NickyB tracker
2007-01-30 04:05:21 +00:00
zzz
2aeef44f8d 2007-01-28 zzz
* i2psnark: Don't hold sendQueue lock while flushing output,
      to make everything run smoother
2007-01-29 04:03:36 +00:00
zzz
0fd41a9490 2007-01-27 zzz
* i2psnark: Fix orphaned Snark reader tasks leading to OOMs
2007-01-28 02:30:05 +00:00
58f10d14b2 2007-01-20 Complication
* Drop overlooked comment
2007-01-21 03:49:41 +00:00
46ca42ddf8 2007-01-20 Complication
* Modify ReseedHandler to query the "i2p.reseedURL" property from I2PAppContext
      instead of System, so setting a reseed URL in advanced configuration has effect.
    * Clean out obsolete reseed code from ConfigNetHandler.
2007-01-21 03:35:49 +00:00
zzz
e6e6d6f4ee 2007-01-20 zzz
* Improve performance by not reading in the whole
      piece from disk for each request. A huge memory savings
      on 1MB torrents with many peers.
2007-01-21 01:43:31 +00:00
zzz
8a87df605b 2007-01-20 zzz
* i2psnark: More choking rotation tweaks
    * Improve performance by not reading in the whole
      piece from disk for each request. A huge memory savings
      on 1MB torrents with many peers.
2007-01-21 00:35:09 +00:00
zzz
8ca085bceb (zzz) 1/16 2007-01-19 03:04:49 +00:00
zzz
df47587db0 * Add new HTTP Proxy error message for non-http protocols 2007-01-18 01:42:13 +00:00
zzz
d705e0ad04 2007-01-17 zzz
* Add note on Syndie index.html steering people to new Syndie
2007-01-17 05:13:27 +00:00
zzz
40d209dd7c 2007-01-17 zzz
* i2psnark: Fix crash when autostart off and
      tcrrent started manually
2007-01-16 06:20:23 +00:00
zzz
7f2a0457bf 2007-01-16 zzz
* i2psnark: Fix bug caused by last i2psnark checkin
      (ConnectionAcceptor not started)
    * Don't start PeerCoordinator, ConnectionAcceptor,
      and TrackerClient unless starting torrent
2007-01-16 04:47:58 +00:00
f4749f2483 2007-01-15 jrandom
* small guard against unnecessary streaming lib reset packets
      (thanks Complication!)
2007-01-15 06:35:59 +00:00
zzz
9c42830076 2007-01-15 zzz
* i2psnark: Add 'Stop All' link on web page
    * Add some links to trackers and forum on web page
    * Don't start tunnel if 'Autostart' unchecked
    * Fix torrent restart bug by reopening file descriptors
2007-01-15 04:36:03 +00:00
zzz
53ba6c2a64 2007-01-14 zzz
* i2psnark: Improvements for torrents with > 4 leechers:
      choke based on upload rate when seeding, and
      be smarter and fairer about rotating choked peers.
    * Handle two common i2psnark OOM situations rather
      than shutting down the whole thing.
    * Fix reporting to tracker of remaining bytes for
      torrents > 4GB (but ByteMonsoon still has a bug)
2007-01-14 19:49:33 +00:00
zzz
61b3f21f69 (zzz) 01-09 2007-01-13 01:15:04 +00:00
zzz
506fd5f889 (zzz) Jan. 2 2007-01-03 03:19:06 +00:00
zzz
d538f888b4 (zzz) 12-19,12-26 2006-12-28 09:11:30 +00:00
976c5fdd47 new syndie httpserv 2006-12-16 22:31:07 +00:00
zzz
b63f3437f2 (zzz) 12-12 mtg 2006-12-13 06:40:15 +00:00
zzz
e760f2e538 (zzz) 12/5 mtg 2006-12-06 02:52:13 +00:00
zzz
17c8fca779 (zzz) 11-28 mtg 2006-11-29 01:37:18 +00:00
zzz
87fda382c3 (zzz) 11/14 and 11/21 mtgs 2006-11-22 02:31:23 +00:00
zzz
1e404cd7ac 2006-10-29 zzz
* i2psnark: Fix and enable generation of multifile torrents,
      print error if no tracker selected at create-torrent,
      fix stopping a torrent that hasn't started successfully,
      add eBook and GayTorrents trackers to form,
      web page formatting tweaks
2006-11-10 01:44:35 +00:00
zzz
098f99d806 (zzz) 11/7 mtg 2006-11-10 01:34:54 +00:00
zzz
da93f96035 (zzz) 10/31 mtg 2006-11-02 02:39:07 +00:00
ead39cc87e 2006-10-29 Complication
* Ensure we get NTP samples from more diverse sources
      (0.pool.ntp.org, 1.pool.ntp.org, etc)
    * Discard median-based peer skew calculator as framed average works,
      and adjusting its percentage can make it behave median-like
    * Require more data points (from at least 20 peers)
      before considering a peer skew measurement reliable
2006-10-29 19:29:50 +00:00
zzz
e4e3c44459 (zzz) 10/24 2006-10-25 08:22:09 +00:00
zzz
af151e32e5 (zzz) 10/17 mtg 2006-10-21 08:22:24 +00:00
12819a2a17 added mtn.i2p 2006-10-15 02:10:36 +00:00
87eedff254 0.6.1.26 2006-10-09 05:10:22 +00:00
4c59cd7621 * 2006-10-10 0.6.1.26 released
2006-10-10  jrandom
    * Removed the status display from the console, as its more confusing
      than informative (though the content is still displayed in the HTML)
2006-10-09 01:44:47 +00:00
ef707e7956 2006-10-08 Complication
* Update comment to reflect current status
2006-10-08 23:10:58 +00:00
73cf3fb299 2006-10-08 Complication
* Add a framed average peer clock skew calculator
    * Add config property "router.clockOffsetSanityCheck" to determine
      if NTP-suggested clock offsets get sanity checked (default "true")
    * Reject NTP-suggested clock offsets if they'd increase peer clock skew
      by more than 5 seconds, or make it more than 20 seconds total
    * Decrease log level in getMedianPeerClockSkew()
2006-10-08 22:52:59 +00:00
zzz
80b0c97d72 (zzz) 10/3 status notes 2006-10-04 19:41:09 +00:00
zzz
5cf85c1d7b (zzz)
* i2psnark: Second try at synchronization fix - synch addRequest()
      completely rather than just portions of it and requestNextPiece()
2006-09-29 23:54:17 +00:00
c14e52ceb5 2006-09-27 jrandom
* added HMAC-SHA256
    * properly use CRLF with EepPost
    * suppress jbigi/jcpuid messages if jbigi.dontLog/jcpuid.dontLog is set
    * PBE session key generation (with 1000 rounds of SHA256)
    * misc SDK helper functions
2006-09-27 06:00:33 +00:00
32a579e480 2006-09-26 Complication
* Take back another inadverent logging change in NTCPConnection
2006-09-27 04:44:13 +00:00
0a240a4436 2006-09-26 Complication
* Take back an accidental log level change
2006-09-27 04:31:34 +00:00
9325b806e4 2006-09-26 Complication
* Subclass from Clock a RouterClock which can access router transports,
      with the goal of developing it to second-guess NTP results
    * Make transports report clock skew in seconds
    * Adjust renderStatusHTML() methods accordingly
    * Show average for NTCP clock skews too
    * Give transports a getClockSkews() method to report clock skews
    * Give transport manager a getClockSkews() method to aggregate results
    * Give comm system facade a getMedianPeerClockSkew() method which RouterClock calls
      (to observe results, add "net.i2p.router.transport.CommSystemFacadeImpl=WARN" to
logging)
    * Extra explicitness in NTCP classes to denote unit of time.
    * Fix some places in NTCPConnection where milliseconds and seconds were confused
2006-09-27 04:02:13 +00:00
zzz
ef2e24ea11 (zzz)
* i2psnark: Paranoid copy before writing pieces,
      recheck files on completion, redownload bad pieces
    * i2psnark: Don't contact tracker as often when seeding
2006-09-26 03:11:39 +00:00
zzz
373934c6e0 (zzz)
* i2psnark: Add some synchronization to prevent rare problem
      after restoring orphan piece
2006-09-24 18:30:22 +00:00
zzz
e8e8bac694 (zzz)
* i2psnark: Eliminate duplicate requests caused by i2p-bt's
      rapid choke/unchokes
    * i2psnark: Truncate long TrackerErr messages on web page
2006-09-20 22:39:24 +00:00
zzz
23e8a558c2 (zzz)
* i2psnark: Implement retransmission of requests. This
      eliminates one cause of complete stalls with a peer.
      This problem is common on torrents with a small number of
      active peers where there are no choke/unchokes to kickstart things.
2006-09-16 21:07:28 +00:00
46f2645834 2006-09-14 Complication
* news.xml update
2006-09-14 03:16:53 +00:00
zzz
2329439034 (zzz)
* i2psnark: Fix restoral of partial pieces broken by last patch
2006-09-14 02:37:32 +00:00
zzz
6d400368b9 (zzz) changelog date fix 2006-09-13 23:24:14 +00:00
zzz
26c13b40fe (zzz)
* i2psnark: Mark a peer's requests as unrequested on disconnect,
      preventing premature end game
    * i2psnark: Randomize selection of next piece during end game
    * i2psnark: Don't restore a partial piece to a peer that is already working on it
    * i2psnark: strip ".torrent" on web page
    * i2psnark: Limit piece size in generated torrent to 1MB max
2006-09-13 23:02:07 +00:00
zzz
9fd0e95fe8 (zzz) 9/12 status 2006-09-13 17:34:58 +00:00
zzz
7e21f2c92b (zzz)
* i2psnark: Add "Stalled" indication and stat totals on web page
2006-09-10 01:55:37 +00:00
zzz
c9d8e796c6 (zzz)
* i2psnark: Fix bug where new peers would always be set to "interested"
      regardless of actual interest
    * i2psnark: Reduce max piece size from 10MB to 1MB; larger may have severe
      memory and efficiency problems
2006-09-09 22:15:05 +00:00
zzz
e7203f5d46 (zzz) 0.6.1.25 2006-09-09 21:19:49 +00:00
22d76a1b64 * 2006-09-09 0.6.1.25 released 2006-09-09 17:46:21 +00:00
0903dc46c6 2006-09-08 jrandom
* Tweak the PRNG logging so it only displays error messages if there are
      problems
    * Disable dynamic router keys for the time being, as they don't offer
      meaningful security, may hurt the router, and makes it harder to
      determine the network health.  The code to restart on SSU IP change is
      still enabled however.
    * Disable tunnel load testing, leaning back on the tiered selection for
      the time being.
    * Spattering of bugfixes
2006-09-09 01:41:57 +00:00
zzz
0f56ec8078 (zzz) oops remove duplicate 2006-09-07 23:26:53 +00:00
zzz
70ee1df2bf (zzz)
* i2psnark: Increase output timeout from 2 min to 4 min
    * i2psnark: Orphan debug msg cleanup
    * i2psnark: More web rate report cleanup
2006-09-07 23:22:12 +00:00
zzz
61a6a29bec cCVS: ---------------------------------------------------------------------- 2006-09-07 23:03:18 +00:00
zzz
678f7d8f72 (zzz)
* i2psnark: Implement basic partial-piece saves across connections
    * i2psnark: Implement keep-alive sending. This will keep non-i2psnark clients
      from dropping us for inactivity but also renders the 2-minute transmit-inactivity
      code in i2psnark ineffective. Will have to research why there is transmit but
      not receive inactivity code. With the current connection limit of 24 peers
      we aren't in any danger of keeping out new peers by keeping inactive ones.
    * i2psnark: Increase CHECK_PERIOD from 20 to 40 since nothing happens in 20 seconds
    * i2psnark: Fix dropped chunk handling
    * i2psnark: Web rate report cleanup
2006-09-06 06:32:53 +00:00
zzz
b92ee364bc (zzz)
* i2psnark: Report cleared trackerErr immediately
    * i2psnark: Add trackerErr reporting after previous success; retry more quickly
    * i2psnark: Set up new connections more quickly
    * i2psnark: Don't delay tracker fetch when setting up lots of connections
    * i2psnark: Reduce MAX_UPLOADERS from 12 to 4
2006-09-04 08:26:21 +00:00
zzz
aef19fcd38 (zzz) i2psnark: enable pipelining, set tunnel length default to 1 + 0-1 2006-09-04 06:01:53 +00:00
zzz
3b01df1d2c (zzz) Add rate reporting to i2psnark 2006-09-03 09:12:22 +00:00
4aed23b198 2006-09-03 Complication
* Limit form size in SusiDNS to avoid exceeding a POST size limit on postback
    * Print messages about addressbook size to give better overview
    * Enable delete function in published addressbook
2006-09-03 06:37:46 +00:00
03e8875c27 2006-08-21 Complication
* Fix error reporting discrepancy (thanks for helping notice, yojoe!)
2006-08-21 05:55:33 +00:00
48921a0875 2006-08-03 Complication
* news.xml update
2006-08-04 00:49:40 +00:00
633fabb09e 2006-08-03 jrandom
* Decrease the recently modified tunnel building timeout, though keep
      the scaling on their processing
2006-08-03 22:34:24 +00:00
bc42c26d94 2006-07-31 jrandom
* Increase the tunnel building timeout
    * Avoid a rare race (thanks bar!)
    * Fix the bandwidth capacity publishing code to factor in share percentage
      and outbound throttling (oops)
2006-08-01 02:26:52 +00:00
3c09ca3359 2006-07-29 Complication
* Treat NTP responses from unexpected stratums like failures
2006-07-30 05:08:20 +00:00
zzz
1e9e7dd345 (zzz) 0.6.1.24 2006-07-29 23:02:57 +00:00
034803add7 * 2006-07-28 0.6.1.24 released 2006-07-29 18:03:14 +00:00
b25bb053bb 2006-07-28 jrandom
* Don't try to reverify too many netDb entries at once (thanks
      cervantes and Complication!)
2006-07-29 04:41:15 +00:00
9bd0c79441 2006-07-28 jrandom
* Actually fix the threading deadlock issue in the netDb (removing
      the synchronized access to individual kbuckets while validating
      individual entries) (thanks cervantes, postman, frosk, et al!)
2006-07-29 01:11:50 +00:00
06b8670410 * 2006-07-27 0.6.1.23 released 2006-07-28 03:34:59 +00:00
6577ae499f 2006-07-27 jrandom
* Cut down NTCP connection establishments once we know the peer is skewed
      (rather than wait for full establishment before verifying)
    * Removed a lock on the stats framework when accessing rates, which
      shouldn't be a problem, assuming rates are created (pretty much) all at
      once and merely updated during the lifetime of the jvm.
2006-07-27 23:40:00 +00:00
54bc5485ec oops, thanks bar! 2006-07-27 07:06:55 +00:00
84b741ac98 2006-07-27 jrandom
* Further NTCP write status cleanup
    * Handle more oddly-timed NTCP disconnections (thanks bar!)
2006-07-27 06:20:25 +00:00
c48c419d74 quick prng workaround 2006-07-27 01:34:31 +00:00
fb2e795add 2006-07-26 jrandom
* When dropping a netDb router reference, only accept newer
      references as part of the update check
    * If we have been up for a while, don't accept really old
      router references (published 2 or more days ago)
    * Drop router references once they are no longer valid, even if
      they were allowed in due to the lax restrictions on startup
2006-07-27 01:04:59 +00:00
ec215777ec 2006-07-26 jrandom
* When dropping a netDb router reference, only accept newer
      references as part of the update check
    * If we have been up for a while, don't accept really old
      router references (published 2 or more days ago)
    * Drop router references once they are no longer valid, even if
      they were allowed in due to the lax restrictions on startup
2006-07-27 00:56:49 +00:00
d4e0f27c56 2006-07-26 jrandom
* Every time we create a new router identity, add an entry to the
      new "identlog.txt" text file in the I2P install directory.  For
      debugging purposes, publish the count of how many identities the
      router has cycled through, though not the identities itself.
    * Cleaned up the way the multitransport shitlisting worked, and
      added per-transport shitlists
    * When dropping a router reference locally, first fire a netDb
      lookup for the entry
    * Take the peer selection filters into account when organizing the
      profiles (thanks Complication!)
    * Avoid some obvious configuration errors for the NTCP transport
      (invalid ports, "null" ip, etc)
    * Deal with some small NTCP bugs found in the wild (unresolveable
      hosts, strange network discons, etc)
    * Send our netDb info to peers we have direct NTCP connections to
      after each 6-12 hours of connection uptime
    * Clean up the NTCP reading and writing queue logic to avoid some
      potential delays
    * Allow people to specify the IP that the SSU transport binds on
      locally, via the advanced config "i2np.udp.bindInterface=1.2.3.4"
2006-07-26 06:36:18 +00:00
e1c686baa6 2006-07-18 Complication
* URL and date fix in news.xml
2006-07-18 23:35:54 +00:00
d57af1aef4 remove 1.5ism 2006-07-18 20:20:08 +00:00
a52dd57215 * 2006-07-18 0.6.1.22 released
2006-07-18  jrandom
    * Add a failsafe to the NTCP transport to make sure we keep
      pumping writes when we should.
    * Properly reallow 16-32KBps routers in the default config
      (thanks Complication!)
2006-07-18 20:08:00 +00:00
65138357d3 2006-07-16 Complication
* Collect tunnel build agree/reject/expire statistics
      for each bandwidth tier of peers (and peers of unknown tiers,
      even if those shouldn't exist)
2006-07-16 17:20:46 +00:00
f6320696dd 2006-07-14 jrandom
* Improve the multitransport shitlisting (thanks Complication!)
    * Allow routers with a capacity of 16-32KBps to be used in tunnels under
      the default configuration (thanks for the stats Complication!)
    * Properly allow older router references to load on startup
      (thanks bar, Complication, et al!)
    * Add a new "i2p.alwaysAllowReseed" advanced config property, though
      hopefully today's changes should make this unnecessary (thanks void!)
    * Improved NTCP buffering
    * Close NTCP connections if we are too backlogged when writing to them
2006-07-14 18:08:44 +00:00
900d8a2026 oops, test method. thanks cervantes 2006-07-07 19:04:24 +00:00
ccc9a87e8c unnecessary 2006-07-07 18:59:16 +00:00
208634e5de 2006-07-04 jrandom
* New NIO-based tcp transport (NTCP), enabled by default for outbound
      connections only.  Those who configure their NAT/firewall to allow
      inbound connections and specify the external host and port
      (dyndns/etc is ok) on /config.jsp can receive inbound connections.
      SSU is still enabled for use by default for all users as a fallback.
    * Substantial bugfix to the tunnel gateway processing to transfer
      messages sequentially instead of interleaved
    * Renamed GNU/crypto classes to avoid name clashes with kaffe and other
      GNU/Classpath based JVMs
    * Adjust the Fortuna PRNG's pooling system to reduce contention on
      refill with a background thread to refill the output buffer
    * Add per-transport support for the shitlist
    * Add a new async pumped tunnel gateway to reduce tunnel dispatcher
      contention
2006-07-04 21:17:44 +00:00
3d07205c9d 2006-07-01 Complication
* Ensure that the I2PTunnel web interface won't update tunnel settings
      for shared clients when a non-shared client is modified
      (thanks for spotting, BarkerJr!)
2006-07-01 22:44:34 +00:00
zzz
f0a424a93f (zzz) .21, 6-13 mtg 2006-06-15 22:15:09 +00:00
f9b59ee07d 2006-06-14 cervantes
* Small tweak to I2PTunnel CSS, so it looks better with desktops
      that use Bitstream Vera fonts @ 96 dpi
2006-06-14 05:24:34 +00:00
182 changed files with 11249 additions and 1704 deletions

View File

@ -17,6 +17,10 @@
# Contains the addresses from your master address book
# and your subscribed address books. (../userhosts.txt)
#
# private_addressbook The path to the private address book used by the router.
# This is used only by the router and SusiDNS.
# It is not published by addressbook. (../privatehosts.txt)
#
# published_addressbook The path to the copy of your address book made
# available on i2p. (../eepsite/docroot/hosts.txt)
#
@ -35,6 +39,7 @@ proxy_host=localhost
proxy_port=4444
master_addressbook=myhosts.txt
router_addressbook=../userhosts.txt
private_addressbook=../privatehosts.txt
published_addressbook=../eepsite/docroot/hosts.txt
log=log.txt
subscriptions=subscriptions.txt

View File

@ -66,6 +66,7 @@ public class AddressBook {
* where key is a human readable name, and value is a base64 i2p
* destination.
*/
/* unused
public AddressBook(String url, String proxyHost, int proxyPort) {
this.location = url;
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
@ -79,22 +80,26 @@ public class AddressBook {
}
new File("addressbook.tmp").delete();
}
*/
/**
* Construct an AddressBook from the Subscription subscription. If the
* address book at subscription has not changed since the last time it was
* read or cannot be read, return an empty AddressBook.
* Set a maximum size of the remote book to make it a little harder for a malicious book-sender.
*
* @param subscription
* A Subscription instance pointing at a remote address book.
*/
static final long MAX_SUB_SIZE = 3 * 1024 * 1024l; //about 5,000 hosts
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
this.location = subscription.getLocation();
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
proxyHost, proxyPort, 0, "addressbook.tmp",
subscription.getLocation(), true, subscription.getEtag());
get.fetch();
subscription.setEtag(get.getETag());
proxyHost, proxyPort, 0, -1l, MAX_SUB_SIZE, "addressbook.tmp", null,
subscription.getLocation(), true, subscription.getEtag(), subscription.getLastModified(), null);
if (get.fetch()) {
subscription.setEtag(get.getETag());
subscription.setLastModified(get.getLastModified());
}
try {
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
} catch (IOException exp) {
@ -151,6 +156,27 @@ public class AddressBook {
return this.addresses.toString();
}
/**
* Do basic validation of the hostname and dest
* hostname was already converted to lower case by ConfigParser.parse()
*/
private static boolean valid(String host, String dest) {
return
host.endsWith(".i2p") &&
host.length() > 4 &&
host.length() <= 67 && // 63 + ".i2p"
(! host.startsWith(".")) &&
(! host.startsWith("-")) &&
(! host.endsWith("-.i2p")) &&
host.indexOf("..") < 0 &&
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
dest.length() == 516 &&
dest.endsWith("AAAA") &&
dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0
;
}
/**
* Merge this AddressBook with AddressBook other, writing messages about new
* addresses or conflicts to log. Addresses in AddressBook other that are
@ -169,7 +195,7 @@ public class AddressBook {
String otherKey = (String) otherIter.next();
String otherValue = (String) other.addresses.get(otherKey);
if (otherKey.endsWith(".i2p") && otherValue.length() >= 516) {
if (valid(otherKey, otherValue)) {
if (this.addresses.containsKey(otherKey) && !overwrite) {
if (!this.addresses.get(otherKey).equals(otherValue)
&& log != null) {
@ -184,7 +210,7 @@ public class AddressBook {
this.modified = true;
if (log != null) {
log.append("New address " + otherKey
+ " added to address book.");
+ " added to address book. From: " + other.location);
}
}
}

View File

@ -60,6 +60,7 @@ public class ConfigParser {
* a single key, value pair on each line, in the format: key=value. Lines
* starting with '#' or ';' are considered comments, and ignored. Lines that
* are obviously not in the format key=value are also ignored.
* The key is converted to lower case.
*
* @param input
* A BufferedReader with lines in key=value format to parse into
@ -77,7 +78,7 @@ public class ConfigParser {
inputLine = ConfigParser.stripComments(inputLine);
String[] splitLine = inputLine.split("=");
if (splitLine.length == 2) {
result.put(splitLine[0].trim(), splitLine[1].trim());
result.put(splitLine[0].trim().toLowerCase(), splitLine[1].trim());
}
inputLine = input.readLine();
}
@ -301,4 +302,4 @@ public class ConfigParser {
new FileWriter(file, false)));
}
}
}

View File

@ -98,7 +98,8 @@ public class Daemon {
AddressBook router = new AddressBook(routerFile);
List defaultSubs = new LinkedList();
defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
// defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
defaultSubs.add("http://www.i2p2.i2p/hosts.txt");
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
etagsFile, lastModifiedFile, defaultSubs, (String) settings

View File

@ -32,6 +32,7 @@ public class BitField
private final byte[] bitfield;
private final int size;
private int count;
/**
* Creates a new BitField that represents <code>size</code> unset bits.
@ -41,6 +42,7 @@ public class BitField
this.size = size;
int arraysize = ((size-1)/8)+1;
bitfield = new byte[arraysize];
this.count = 0;
}
/**
@ -60,6 +62,11 @@ public class BitField
// 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.count = 0;
for (int i = 0; i < size; i++)
if (get(i))
this.count++;
}
/**
@ -95,7 +102,10 @@ public class BitField
throw new IndexOutOfBoundsException(Integer.toString(bit));
int index = bit/8;
int mask = 128 >> (bit % 8);
bitfield[index] |= mask;
if ((bitfield[index] & mask) == 0) {
count++;
bitfield[index] |= mask;
}
}
/**
@ -114,6 +124,22 @@ public class BitField
return (bitfield[index] & mask) != 0;
}
/**
* Return the number of set bits.
*/
public int count()
{
return count;
}
/**
* Return true if all bits are set.
*/
public boolean complete()
{
return count >= size;
}
public String toString()
{
// Not very efficient
@ -129,4 +155,5 @@ public class BitField
return sb.toString();
}
}

View File

@ -33,6 +33,7 @@ public class I2PSnarkUtil {
private I2PSocketManager _manager;
private boolean _configured;
private Set _shitlist;
private int _maxUploaders;
private I2PSnarkUtil() {
_context = I2PAppContext.getGlobalContext();
@ -42,6 +43,7 @@ public class I2PSnarkUtil {
setI2CPConfig("127.0.0.1", 7654, null);
_shitlist = new HashSet(64);
_configured = false;
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
}
/**
@ -72,12 +74,18 @@ public class I2PSnarkUtil {
_configured = true;
}
public void setMaxUploaders(int limit) {
_maxUploaders = limit;
_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; }
public int getMaxUploaders() { return _maxUploaders; }
/**
* Connect to the router, if we aren't already
@ -93,10 +101,16 @@ public class I2PSnarkUtil {
}
if (opts.getProperty("inbound.nickname") == null)
opts.setProperty("inbound.nickname", "I2PSnark");
if (opts.getProperty("outbound.nickname") == null)
opts.setProperty("outbound.nickname", "I2PSnark");
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
opts.setProperty("i2p.streaming.inactivityTimeout", "90000");
opts.setProperty("i2p.streaming.inactivityTimeout", "240000");
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
opts.setProperty("i2p.streaming.inactivityAction", "2"); // 1 == disconnect, 2 == ping
opts.setProperty("i2p.streaming.inactivityAction", "1"); // 1 == disconnect, 2 == ping
if (opts.getProperty("i2p.streaming.initialWindowSize") == null)
opts.setProperty("i2p.streaming.initialWindowSize", "1");
if (opts.getProperty("i2p.streaming.slowStartGrowthRateFactor") == null)
opts.setProperty("i2p.streaming.slowStartGrowthRateFactor", "1");
//if (opts.getProperty("i2p.streaming.writeTimeout") == null)
// opts.setProperty("i2p.streaming.writeTimeout", "90000");
//if (opts.getProperty("i2p.streaming.readTimeout") == null)

View File

@ -54,6 +54,10 @@ public class Peer implements Comparable
private boolean deregister = true;
private static long __id;
private long _id;
final static long CHECK_PERIOD = PeerCoordinator.CHECK_PERIOD; // 40 seconds
final static int RATE_DEPTH = PeerCoordinator.RATE_DEPTH; // make following arrays RATE_DEPTH long
private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
/**
* Creates a disconnected peer given a PeerID, your own id and the
@ -318,6 +322,14 @@ public class Peer implements Comparable
PeerState s = state;
if (s != null)
{
// try to save partial piece
if (this.deregister) {
PeerListener p = state.listener;
if (p != null) {
p.savePeerPartial(state);
p.markUnrequested(this);
}
}
state = null;
PeerConnectionIn in = s.in;
@ -449,13 +461,105 @@ public class Peer implements Comparable
public long getInactiveTime() {
PeerState s = state;
if (s != null) {
PeerConnectionIn in = s.in;
PeerConnectionOut out = s.out;
if (out != null)
return System.currentTimeMillis() - out.lastSent;
else
if (in != null && out != null) {
long now = System.currentTimeMillis();
return Math.max(now - out.lastSent, now - in.lastRcvd);
} else
return -1; //"state, no out";
} else {
return -1; //"no state";
}
}
/**
* Send keepalive
*/
public void keepAlive()
{
PeerState s = state;
if (s != null)
s.keepAlive();
}
/**
* Retransmit outstanding requests if necessary
*/
public void retransmitRequests()
{
PeerState s = state;
if (s != null)
s.retransmitRequests();
}
/**
* Return how much the peer has
*/
public int completed()
{
PeerState s = state;
if (s == null || s.bitfield == null)
return 0;
return s.bitfield.count();
}
/**
* Return if a peer is a seeder
*/
public boolean isCompleted()
{
PeerState s = state;
if (s == null || s.bitfield == null)
return false;
return s.bitfield.complete();
}
/**
* Push the total uploaded/downloaded onto a RATE_DEPTH deep stack
*/
public void setRateHistory(long up, long down)
{
setRate(up, uploaded_old);
setRate(down, downloaded_old);
}
private void setRate(long val, long array[])
{
synchronized(array) {
for (int i = RATE_DEPTH-1; i > 0; i--)
array[i] = array[i-1];
array[0] = val;
}
}
/**
* Returns the 4-minute-average rate in Bps
*/
public long getUploadRate()
{
return getRate(uploaded_old);
}
public long getDownloadRate()
{
return getRate(downloaded_old);
}
private long getRate(long array[])
{
long rate = 0;
int i = 0;
synchronized(array) {
for ( ; i < RATE_DEPTH; i++){
if (array[i] < 0)
break;
rate += array[i];
}
}
if (i == 0)
return 0;
return rate / (i * CHECK_PERIOD / 1000);
}
}

View File

@ -41,6 +41,17 @@ class PeerCheckerTask extends TimerTask
{
synchronized(coordinator.peers)
{
Iterator it = coordinator.peers.iterator();
if ((!it.hasNext()) || coordinator.halted()) {
coordinator.peerCount = 0;
coordinator.interestedAndChoking = 0;
coordinator.setRateHistory(0, 0);
coordinator.uploaders = 0;
if (coordinator.halted())
cancel();
return;
}
// Calculate total uploading and worst downloader.
long worstdownload = Long.MAX_VALUE;
Peer worstDownloader = null;
@ -48,10 +59,7 @@ class PeerCheckerTask extends TimerTask
int peers = 0;
int uploaders = 0;
int downloaders = 0;
int interested = 0;
int interesting = 0;
int choking = 0;
int choked = 0;
int removedCount = 0;
long uploaded = 0;
long downloaded = 0;
@ -59,8 +67,7 @@ class PeerCheckerTask extends TimerTask
// 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();
int uploadLimit = coordinator.allowedUploaders();
while (it.hasNext())
{
Peer peer = (Peer)it.next();
@ -80,48 +87,36 @@ class PeerCheckerTask extends TimerTask
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.setRateHistory(upload, 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);
}
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
if (((coordinator.uploaders == uploadLimit
&& coordinator.interestedAndChoking > 0)
|| coordinator.uploaders > uploadLimit)
&& !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);
Snark.debug("Choke uninterested peer: " + peer,
Snark.INFO);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
@ -130,14 +125,27 @@ class PeerCheckerTask extends TimerTask
it.remove();
removed.add(peer);
}
else if (peer.isChoked())
else if (peer.isInteresting() && 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);
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
removedCount++;
// Put it at the back of the list
it.remove();
removed.add(peer);
}
else if (!peer.isInteresting() && !coordinator.completed())
{
// If they aren't interesting make someone else a downloader
Snark.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
removedCount++;
// Put it at the back of the list
it.remove();
@ -148,41 +156,51 @@ class PeerCheckerTask extends TimerTask
&& 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);
Snark.debug("Choke downloader that doesn't deliver:"
+ peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
removedCount++;
// Put it at the back of the list
it.remove();
removed.add(peer);
}
else if (!peer.isChoking() && download < worstdownload)
else if (peer.isInteresting() && !peer.isChoked() &&
download < worstdownload)
{
// Make sure download is good if we are uploading
worstdownload = download;
worstDownloader = peer;
}
else if (upload < worstdownload && coordinator.completed())
{
// Make sure upload is good if we are seeding
worstdownload = upload;
worstDownloader = peer;
}
}
peer.retransmitRequests();
peer.keepAlive();
}
// 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
// Remove the worst downloader if needed. (uploader if seeding)
if (((uploaders == uploadLimit
&& coordinator.interestedAndChoking > 0)
|| uploaders > uploadLimit)
&& worstDownloader != null)
{
if (Snark.debug >= Snark.DEBUG)
Snark.debug("Choke worst downloader: " + worstDownloader,
Snark.DEBUG);
Snark.debug("Choke worst downloader: " + worstDownloader,
Snark.DEBUG);
worstDownloader.setChoking(true);
coordinator.uploaders--;
removedCount++;
// Put it at the back of the list
coordinator.peers.remove(worstDownloader);
@ -196,9 +214,11 @@ class PeerCheckerTask extends TimerTask
// Put peers back at the end of the list that we removed earlier.
coordinator.peers.addAll(removed);
coordinator.peerCount = coordinator.peers.size();
coordinator.interestedAndChoking += removedCount;
// store the rates
coordinator.setRateHistory(uploaded, downloaded);
}
if (coordinator.halted()) {
cancel();
}
}
}

View File

@ -33,12 +33,15 @@ class PeerConnectionIn implements Runnable
private final DataInputStream din;
private Thread thread;
private boolean quit;
private volatile boolean quit;
long lastRcvd;
public PeerConnectionIn(Peer peer, DataInputStream din)
{
this.peer = peer;
this.din = din;
lastRcvd = System.currentTimeMillis();
quit = false;
}
@ -51,6 +54,13 @@ class PeerConnectionIn implements Runnable
Thread t = thread;
if (t != null)
t.interrupt();
if (din != null) {
try {
din.close();
} catch (IOException ioe) {
_log.warn("Error closing the stream from " + peer, ioe);
}
}
}
public void run()
@ -69,6 +79,7 @@ class PeerConnectionIn implements Runnable
// Wait till we hear something...
// The length of a complete message in bytes.
int i = din.readInt();
lastRcvd = System.currentTimeMillis();
if (i < 0)
throw new IOException("Unexpected length prefix: " + i);

View File

@ -72,6 +72,16 @@ class PeerConnectionOut implements Runnable
{
Message m = null;
PeerState state = null;
boolean shouldFlush;
synchronized(sendQueue)
{
shouldFlush = !quit && peer.isConnected() && sendQueue.isEmpty();
}
if (shouldFlush)
// Make sure everything will reach the other side.
// flush while not holding lock, could take a long time
dout.flush();
synchronized(sendQueue)
{
while (!quit && peer.isConnected() && sendQueue.isEmpty())
@ -79,7 +89,8 @@ class PeerConnectionOut implements Runnable
try
{
// Make sure everything will reach the other side.
dout.flush();
// don't flush while holding lock, could take a long time
// dout.flush();
// Wait till more data arrives.
sendQueue.wait(60*1000);
@ -197,10 +208,12 @@ class PeerConnectionOut implements Runnable
/**
* Adds a message to the sendQueue and notifies the method waiting
* on the sendQueue to change.
* If a PIECE message only, add a timeout.
*/
private void addMessage(Message m)
{
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
if (m.type == Message.PIECE)
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
synchronized(sendQueue)
{
sendQueue.add(m);
@ -259,7 +272,13 @@ class PeerConnectionOut implements Runnable
{
Message m = new Message();
m.type = Message.KEEP_ALIVE;
addMessage(m);
// addMessage(m);
synchronized(sendQueue)
{
if(sendQueue.isEmpty())
sendQueue.add(m);
sendQueue.notifyAll();
}
}
void sendChoke(boolean choke)
@ -318,6 +337,23 @@ class PeerConnectionOut implements Runnable
addMessage(m);
}
/** reransmit requests not received in 7m */
private static final int REQ_TIMEOUT = (2 * SEND_TIMEOUT) + (60 * 1000);
void retransmitRequests(List requests)
{
long now = System.currentTimeMillis();
Iterator it = requests.iterator();
while (it.hasNext())
{
Request req = (Request)it.next();
if(now > req.sendTime + REQ_TIMEOUT) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Retransmit request " + req + " to peer " + peer);
sendRequest(req);
}
}
}
void sendRequests(List requests)
{
Iterator it = requests.iterator();
@ -330,12 +366,30 @@ class PeerConnectionOut implements Runnable
void sendRequest(Request req)
{
// Check for duplicate requests to deal with fibrillating i2p-bt
// (multiple choke/unchokes received cause duplicate requests in the 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)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Discarding duplicate request " + req + " to peer " + peer);
return;
}
}
}
Message m = new Message();
m.type = Message.REQUEST;
m.piece = req.piece;
m.begin = req.off;
m.length = req.len;
addMessage(m);
req.sendTime = System.currentTimeMillis();
}
void sendPiece(int piece, int begin, int length, byte[] bytes)
@ -346,7 +400,7 @@ class PeerConnectionOut implements Runnable
m.begin = begin;
m.length = length;
m.data = bytes;
m.off = begin;
m.off = 0;
m.len = length;
addMessage(m);
}

View File

@ -37,19 +37,23 @@ public class PeerCoordinator implements PeerListener
final Snark snark;
// package local for access by CheckDownLoadersTask
final static long CHECK_PERIOD = 20*1000; // 20 seconds
final static long CHECK_PERIOD = 40*1000; // 40 seconds
final static int MAX_CONNECTIONS = 24;
final static int MAX_UPLOADERS = 12; // i2p: might as well balance it out
final static int MAX_UPLOADERS = 4;
// Approximation of the number of current uploaders.
// Resynced by PeerChecker once in a while.
int uploaders = 0;
int interestedAndChoking = 0;
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
// int downloaders = 0;
private long uploaded;
private long downloaded;
final static int RATE_DEPTH = 6; // make following arrays RATE_DEPTH long
private long uploaded_old[] = {0,0,0,0,0,0};
private long downloaded_old[] = {0,0,0,0,0,0};
// synchronize on this when changing peers or downloaders
final List peers = new ArrayList();
@ -62,7 +66,7 @@ public class PeerCoordinator implements PeerListener
private final byte[] id;
// Some random wanted pieces
private final List wantedPieces;
private List wantedPieces;
private boolean halted = false;
@ -80,6 +84,15 @@ public class PeerCoordinator implements PeerListener
this.listener = listener;
this.snark = torrent;
setWantedPieces();
// Install a timer to check the uploaders.
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
}
// only called externally from Storage after the double-check fails
public void setWantedPieces()
{
// Make a list of pieces
wantedPieces = new ArrayList();
BitField bitfield = storage.getBitField();
@ -87,14 +100,20 @@ public class PeerCoordinator implements PeerListener
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; }
// for web page detailed stats
public List peerList()
{
synchronized(peers)
{
return new ArrayList(peers);
}
}
public byte[] getID()
{
return id;
@ -123,7 +142,7 @@ public class PeerCoordinator implements PeerListener
public long getLeft()
{
// XXX - Only an approximation.
return storage.needed() * metainfo.getPieceLength(0);
return ((long) storage.needed()) * metainfo.getPieceLength(0);
}
/**
@ -142,6 +161,47 @@ public class PeerCoordinator implements PeerListener
return downloaded;
}
/**
* Push the total uploaded/downloaded onto a RATE_DEPTH deep stack
*/
public void setRateHistory(long up, long down)
{
setRate(up, uploaded_old);
setRate(down, downloaded_old);
}
private static void setRate(long val, long array[])
{
synchronized(array) {
for (int i = RATE_DEPTH-1; i > 0; i--)
array[i] = array[i-1];
array[0] = val;
}
}
/**
* Returns the 4-minute-average rate in Bps
*/
public long getDownloadRate()
{
return getRate(downloaded_old);
}
public long getUploadRate()
{
return getRate(uploaded_old);
}
private long getRate(long array[])
{
long rate = 0;
synchronized(array) {
for (int i = 0; i < RATE_DEPTH; i++)
rate += array[i];
}
return rate / (RATE_DEPTH * CHECK_PERIOD / 1000);
}
public MetaInfo getMetaInfo()
{
return metainfo;
@ -177,6 +237,8 @@ public class PeerCoordinator implements PeerListener
peer.disconnect();
removePeerFromPieces(peer);
}
// delete any saved orphan partial piece
savedRequest = null;
}
public void connected(Peer peer)
@ -191,8 +253,10 @@ public class PeerCoordinator implements PeerListener
synchronized(peers)
{
Peer old = peerIDInList(peer.getPeerID(), peers);
if ( (old != null) && (old.getInactiveTime() > 2*60*1000) ) {
// idle for 2 minutes, kill the old con
if ( (old != null) && (old.getInactiveTime() > 8*60*1000) ) {
// idle for 8 minutes, kill the old con (32KB/8min = 68B/sec minimum for one block)
if (_log.shouldLog(Log.WARN))
_log.warn("Remomving old peer: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
peers.remove(old);
toDisconnect = old;
old = null;
@ -201,6 +265,7 @@ public class PeerCoordinator implements PeerListener
{
if (_log.shouldLog(Log.WARN))
_log.warn("Already connected to: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
// toDisconnect = peer to get out of synchronized(peers)
peer.disconnect(false); // Don't deregister this connection/peer.
}
else
@ -235,18 +300,22 @@ public class PeerCoordinator implements PeerListener
return null;
}
public void addPeer(final Peer peer)
// returns true if actual attempt to add peer occurs
public boolean addPeer(final Peer peer)
{
if (halted)
{
peer.disconnect(false);
return;
return false;
}
boolean need_more;
synchronized(peers)
{
need_more = !peer.isConnected() && peers.size() < MAX_CONNECTIONS;
// Check if we already have this peer before we build the connection
Peer old = peerIDInList(peer.getPeerID(), peers);
need_more = need_more && ((old == null) || (old.getInactiveTime() > 8*60*1000));
}
if (need_more)
@ -265,6 +334,7 @@ public class PeerCoordinator implements PeerListener
};
String threadName = peer.toString();
new I2PThread(r, threadName).start();
return true;
}
else
if (_log.shouldLog(Log.DEBUG)) {
@ -274,6 +344,7 @@ public class PeerCoordinator implements PeerListener
_log.info("MAX_CONNECTIONS = " + MAX_CONNECTIONS
+ " not accepting extra peer: " + peer);
}
return false;
}
@ -285,34 +356,40 @@ public class PeerCoordinator implements PeerListener
// other peer that are interested, but are choking us.
List interested = new LinkedList();
synchronized (peers) {
int count = 0;
int unchokedCount = 0;
int maxUploaders = allowedUploaders();
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.isChoking() && peer.isInterested())
{
if (!peer.isChoked())
interested.add(0, peer);
else
interested.add(peer);
count++;
if (uploaders < maxUploaders)
{
if (!peer.isChoked())
interested.add(unchokedCount++, peer);
else
interested.add(peer);
}
}
}
while (uploaders < MAX_UPLOADERS && interested.size() > 0)
while (uploaders < maxUploaders && interested.size() > 0)
{
Peer peer = (Peer)interested.remove(0);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Unchoke: " + peer);
peer.setChoking(false);
uploaders++;
count--;
// Put peer back at the end of the list.
peers.remove(peer);
peers.add(peer);
peerCount = peers.size();
}
interestedAndChoking = count;
}
}
@ -351,9 +428,10 @@ public class PeerCoordinator implements PeerListener
{
Piece p = (Piece)it.next();
int i = p.getId();
if (bitfield.get(i))
if (bitfield.get(i)) {
p.addPeer(peer);
return true;
}
}
}
return false;
@ -392,6 +470,8 @@ public class PeerCoordinator implements PeerListener
//Only request a piece we've requested before if there's no other choice.
if (piece == null) {
// let's not all get on the same piece
Collections.shuffle(requested);
Iterator it2 = requested.iterator();
while (piece == null && it2.hasNext())
{
@ -403,9 +483,17 @@ public class PeerCoordinator implements PeerListener
}
if (piece == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
+ " wanted = " + wantedPieces + " peerHas = " + havePieces);
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested);
// _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.
} else {
// Should be a lot smarter here - limit # of parallel attempts and
// share blocks rather than starting from 0 with each peer.
// This is where the flaws of the snark data model are really exposed.
// Could also randomize within the duplicate set rather than strict rarest-first
if (_log.shouldLog(Log.DEBUG))
_log.debug("parallel request (end game?) for " + peer + ": piece = " + piece);
}
}
piece.setRequested(true);
@ -417,14 +505,14 @@ public class PeerCoordinator implements PeerListener
* Returns a byte array containing the requested piece or null of
* the piece is unknown.
*/
public byte[] gotRequest(Peer peer, int piece)
public byte[] gotRequest(Peer peer, int piece, int off, int len)
{
if (halted)
return null;
try
{
return storage.getPiece(piece);
return storage.getPiece(piece, off, len);
}
catch (IOException ioe)
{
@ -504,14 +592,27 @@ public class PeerCoordinator implements PeerListener
}
// Announce to the world we have it!
// Disconnect from other seeders when we get the last piece
synchronized(peers)
{
List toDisconnect = new ArrayList();
Iterator it = peers.iterator();
while (it.hasNext())
{
Peer p = (Peer)it.next();
if (p.isConnected())
p.have(piece);
{
if (completed() && p.isCompleted())
toDisconnect.add(p);
else
p.have(piece);
}
}
it = toDisconnect.iterator();
while (it.hasNext())
{
Peer p = (Peer)it.next();
p.disconnect(true);
}
}
@ -533,7 +634,7 @@ public class PeerCoordinator implements PeerListener
{
synchronized(peers)
{
if (uploaders < MAX_UPLOADERS)
if (uploaders < allowedUploaders())
{
if(peer.isChoking())
{
@ -582,4 +683,141 @@ public class PeerCoordinator implements PeerListener
}
}
}
/** Simple method to save a partial piece on peer disconnection
* and hopefully restart it later.
* Only one partial piece is saved at a time.
* Replace it if a new one is bigger or the old one is too old.
* Storage method is private so we can expand to save multiple partials
* if we wish.
*/
private Request savedRequest = null;
private long savedRequestTime = 0;
public void savePeerPartial(PeerState state)
{
if (halted)
return;
Request req = state.getPartialRequest();
if (req == null)
return;
if (savedRequest == null ||
req.off > savedRequest.off ||
System.currentTimeMillis() > savedRequestTime + (15 * 60 * 1000)) {
if (savedRequest == null || (req.piece != savedRequest.piece && req.off != savedRequest.off)) {
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(" Saving orphaned partial piece " + req);
if (savedRequest != null)
_log.debug(" (Discarding previously saved orphan) " + savedRequest);
}
}
savedRequest = req;
savedRequestTime = System.currentTimeMillis();
} else {
if (req.piece != savedRequest.piece)
if (_log.shouldLog(Log.DEBUG))
_log.debug(" Discarding orphaned partial piece " + req);
}
}
/** Return partial piece if it's still wanted and peer has it.
*/
public Request getPeerPartial(BitField havePieces) {
if (savedRequest == null)
return null;
if (! havePieces.get(savedRequest.piece)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Peer doesn't have orphaned piece " + savedRequest);
return null;
}
synchronized(wantedPieces)
{
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
Piece piece = (Piece)iter.next();
if (piece.getId() == savedRequest.piece) {
Request req = savedRequest;
piece.setRequested(true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Restoring orphaned partial piece " + req);
savedRequest = null;
return req;
}
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("We no longer want orphaned piece " + savedRequest);
savedRequest = null;
return null;
}
/** Clear the requested flag for a piece if the peer
** is the only one requesting it
*/
private void markUnrequestedIfOnlyOne(Peer peer, int piece)
{
// see if anybody else is requesting
synchronized (peers)
{
Iterator it = peers.iterator();
while (it.hasNext()) {
Peer p = (Peer)it.next();
if (p.equals(peer))
continue;
if (p.state == null)
continue;
int[] arr = p.state.getRequestedPieces();
for (int i = 0; arr[i] >= 0; i++)
if(arr[i] == piece) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Another peer is requesting piece " + piece);
return;
}
}
}
// nobody is, so mark unrequested
synchronized(wantedPieces)
{
Iterator it = wantedPieces.iterator();
while (it.hasNext()) {
Piece p = (Piece)it.next();
if (p.getId() == piece) {
p.setRequested(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Removing from request list piece " + piece);
return;
}
}
}
}
/** Mark a peer's requested pieces unrequested when it is disconnected
** Once for each piece
** This is enough trouble, maybe would be easier just to regenerate
** the requested list from scratch instead.
*/
public void markUnrequested(Peer peer)
{
if (halted || peer.state == null)
return;
int[] arr = peer.state.getRequestedPieces();
for (int i = 0; arr[i] >= 0; i++)
markUnrequestedIfOnlyOne(peer, arr[i]);
}
/** Return number of allowed uploaders for this torrent.
** Check with Snark to see if we are over the total upload limit.
*/
public int allowedUploaders()
{
if (Snark.overUploadLimit(uploaders)) {
// if (_log.shouldLog(Log.DEBUG))
// _log.debug("Over limit, uploaders was: " + uploaders);
return uploaders - 1;
} else if (uploaders < MAX_UPLOADERS)
return uploaders + 1;
else
return MAX_UPLOADERS;
}
}

View File

@ -107,11 +107,13 @@ public interface PeerListener
*
* @param peer the Peer that wants the piece.
* @param piece the piece number requested.
* @param off byte offset into the piece.
* @param len length of the chunk 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);
byte[] gotRequest(Peer peer, int piece, int off, int len);
/**
* Called when a (partial) piece has been downloaded from the peer.
@ -142,4 +144,29 @@ public interface PeerListener
* we are no longer interested in the peer.
*/
int wantPiece(Peer peer, BitField bitfield);
/**
* Called when the peer has disconnected and the peer task may have a partially
* downloaded piece that the PeerCoordinator can save
*
* @param state the PeerState for the peer
*/
void savePeerPartial(PeerState state);
/**
* Called when a peer has connected and there may be a partially
* downloaded piece that the coordinatorator can give the peer task
*
* @param havePieces the have-pieces bitmask for the peer
*
* @return request (contains the partial data and valid length)
*/
Request getPeerPartial(BitField havePieces);
/** Mark a peer's requested pieces unrequested when it is disconnected
* This prevents premature end game
*
* @param peer the peer that is disconnecting
*/
void markUnrequested(Peer peer);
}

View File

@ -62,8 +62,9 @@ class PeerState
// 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
private final static int MAX_PIPELINE = 2;
private final static int PARTSIZE = 32*1024; // Snark was 16K, i2p-bt uses 64KB
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
PeerConnectionIn in, PeerConnectionOut out)
@ -173,7 +174,7 @@ class PeerState
|| begin < 0
|| begin > metainfo.getPieceLength(piece)
|| length <= 0
|| length > 4*PARTSIZE)
|| length > MAX_PARTSIZE)
{
// XXX - Protocol error -> disconnect?
if (_log.shouldLog(Log.WARN))
@ -184,7 +185,7 @@ class PeerState
return;
}
byte[] pieceBytes = listener.gotRequest(peer, piece);
byte[] pieceBytes = listener.gotRequest(peer, piece, begin, length);
if (pieceBytes == null)
{
// XXX - Protocol error-> diconnect?
@ -194,7 +195,7 @@ class PeerState
}
// More sanity checks
if (begin >= pieceBytes.length || begin + length > pieceBytes.length)
if (length != pieceBytes.length)
{
// XXX - Protocol error-> disconnect?
if (_log.shouldLog(Log.WARN))
@ -221,6 +222,10 @@ class PeerState
listener.uploaded(peer, size);
}
// This is used to flag that we have to back up from the firstOutstandingRequest
// when calculating how far we've gotten
Request pendingRequest = null;
/**
* Called when a partial piece request has been handled by
* PeerConnectionIn.
@ -231,6 +236,8 @@ class PeerState
downloaded += size;
listener.downloaded(peer, size);
pendingRequest = null;
// Last chunk needed for this piece?
if (getFirstOutstandingRequest(req.piece) == -1)
{
@ -318,14 +325,8 @@ class PeerState
{
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)
if (!choked)
out.sendRequest(dropReq);
*/
if (_log.shouldLog(Log.WARN))
_log.warn("dropped " + dropReq + " with peer " + peer);
}
@ -336,10 +337,58 @@ class PeerState
// Request more if necessary to keep the pipeline filled.
addRequest();
pendingRequest = req;
return req;
}
// get longest partial piece
Request getPartialRequest()
{
Request req = null;
for (int i = 0; i < outstandingRequests.size(); i++) {
Request r1 = (Request)outstandingRequests.get(i);
int j = getFirstOutstandingRequest(r1.piece);
if (j == -1)
continue;
Request r2 = (Request)outstandingRequests.get(j);
if (r2.off > 0 && ((req == null) || (r2.off > req.off)))
req = r2;
}
if (pendingRequest != null && req != null && pendingRequest.off < req.off) {
if (pendingRequest.off != 0)
req = pendingRequest;
else
req = null;
}
return req;
}
// return array of pieces terminated by -1
// remove most duplicates
// but still could be some duplicates, not guaranteed
int[] getRequestedPieces()
{
int size = outstandingRequests.size();
int[] arr = new int[size+2];
int pc = -1;
int pos = 0;
if (pendingRequest != null) {
pc = pendingRequest.piece;
arr[pos++] = pc;
}
Request req = null;
for (int i = 0; i < size; i++) {
Request r1 = (Request)outstandingRequests.get(i);
if (pc != r1.piece) {
pc = r1.piece;
arr[pos++] = pc;
}
}
arr[pos] = -1;
return(arr);
}
void cancelMessage(int piece, int begin, int length)
{
if (_log.shouldLog(Log.DEBUG))
@ -414,16 +463,12 @@ class PeerState
/**
* Adds a new request to the outstanding requests list.
*/
private void addRequest()
synchronized private void addRequest()
{
boolean more_pieces = true;
while (more_pieces)
{
synchronized(this)
{
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
}
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
// We want something and we don't have outstanding requests?
if (more_pieces && lastRequest == null)
more_pieces = requestNextPiece();
@ -431,19 +476,14 @@ class PeerState
{
int pieceLength;
boolean isLastChunk;
synchronized(this)
{
pieceLength = metainfo.getPieceLength(lastRequest.piece);
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
}
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;
@ -456,7 +496,6 @@ class PeerState
if (!choked)
out.sendRequest(req);
lastRequest = req;
}
}
}
}
@ -472,16 +511,41 @@ class PeerState
// Check that we already know what the other side has.
if (bitfield != null)
{
// Check for adopting an orphaned partial piece
Request r = listener.getPeerPartial(bitfield);
if (r != null) {
// Check that r not already in outstandingRequests
int[] arr = getRequestedPieces();
boolean found = false;
for (int i = 0; arr[i] >= 0; i++) {
if (arr[i] == r.piece) {
found = true;
break;
}
}
if (!found) {
outstandingRequests.add(r);
if (!choked)
out.sendRequest(r);
lastRequest = r;
return true;
}
}
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];
//Catch a common place for OOMs esp. on 1MB pieces
byte[] bs;
try {
bs = new byte[piece_length];
} catch (OutOfMemoryError oom) {
_log.warn("Out of memory, can't request piece " + nextPiece, oom);
return false;
}
int length = Math.min(piece_length, PARTSIZE);
Request req = new Request(nextPiece, bs, 0, length);
@ -491,7 +555,6 @@ class PeerState
lastRequest = req;
return true;
}
}
}
return false;
@ -523,4 +586,15 @@ class PeerState
out.sendChoke(choke);
}
}
void keepAlive()
{
out.sendAlive();
}
synchronized void retransmitRequests()
{
if (interesting && !choked)
out.retransmitRequests(outstandingRequests);
}
}

View File

@ -29,6 +29,7 @@ class Request
final byte[] bs;
final int off;
final int len;
long sendTime;
/**
* Creates a new Request.

View File

@ -63,7 +63,7 @@ public class Snark
/**
* What level of debug info to show.
*/
public static int debug = NOTICE;
//public static int debug = NOTICE;
// Whether or not to ask the user for commands while sharing
private static boolean command_interpreter = true;
@ -234,6 +234,7 @@ public class Snark
public String rootDataDir = ".";
public CompleteListener completeListener;
public boolean stopped;
byte[] id;
Snark(String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener) {
@ -268,7 +269,7 @@ public class Snark
// 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];
id = new byte[20];
Random random = new Random();
int i;
for (i = 0; i < 9; i++)
@ -283,6 +284,11 @@ public class Snark
int port;
IOException lastException = null;
/*
* Don't start a tunnel if the torrent isn't going to be started.
* If we are starting,
* startTorrent() will force a connect.
*
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) fatal("Unable to connect to I2P");
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
@ -292,6 +298,7 @@ public class Snark
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;
@ -361,6 +368,9 @@ public class Snark
activity = "Checking storage";
storage = new Storage(meta, slistener);
storage.check(rootDataDir);
// have to figure out when to reopen
// if (!start)
// storage.close();
}
catch (IOException ioe)
{
@ -371,14 +381,19 @@ public class Snark
}
}
/*
* see comment above
*
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();
}
@ -386,6 +401,26 @@ public class Snark
* Start up contacting peers and querying the tracker
*/
public void startTorrent() {
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) fatal("Unable to connect to I2P");
if (coordinator == null) {
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);
}
debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
activity = "Collecting pieces";
coordinator = new PeerCoordinator(id, meta, storage, this, this);
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
set.add(coordinator);
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
acceptor.startAccepting(set, serversocket);
trackerclient = new TrackerClient(meta, coordinator);
}
stopped = false;
boolean coordinatorChanged = false;
if (coordinator.halted()) {
@ -402,6 +437,17 @@ public class Snark
if (!trackerclient.started() && !coordinatorChanged) {
trackerclient.start();
} else if (trackerclient.halted() || coordinatorChanged) {
try
{
storage.reopen(rootDataDir);
}
catch (IOException ioe)
{
try { storage.close(); } catch (IOException ioee) {
ioee.printStackTrace();
}
fatal("Could not reopen storage", ioe);
}
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
if (!trackerclient.halted())
trackerclient.halt();
@ -422,6 +468,8 @@ public class Snark
pc.halt();
Storage st = storage;
if (st != null) {
if (storage.changed)
SnarkManager.instance().saveTorrentStatus(storage.getMetaInfo(), storage.getBitField());
try {
storage.close();
} catch (IOException ioe) {
@ -457,6 +505,7 @@ public class Snark
int i = 0;
while (i < args.length)
{
/*
if (args[i].equals("--debug"))
{
debug = INFO;
@ -477,7 +526,7 @@ public class Snark
catch (NumberFormatException nfe) { }
}
}
else if (args[i].equals("--port"))
else */ if (args[i].equals("--port"))
{
if (args.length - 1 < i + 1)
usage("--port needs port number to listen on");
@ -693,6 +742,11 @@ public class Snark
completeListener.torrentComplete(this);
}
public void setWantedPieces(Storage storage)
{
coordinator.setWantedPieces();
}
public void shutdown()
{
// Should not be necessary since all non-deamon threads should
@ -703,4 +757,23 @@ public class Snark
public interface CompleteListener {
public void torrentComplete(Snark snark);
}
/** Maintain a configurable total uploader cap
*/
final static int MIN_TOTAL_UPLOADERS = 4;
final static int MAX_TOTAL_UPLOADERS = 10;
public static boolean overUploadLimit(int uploaders) {
PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
if (coordinators == null || uploaders <= 0)
return false;
int totalUploaders = 0;
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
PeerCoordinator c = (PeerCoordinator)iter.next();
if (!c.halted())
totalUploaders += c.uploaders;
}
int limit = I2PSnarkUtil.instance().getMaxUploaders();
// Snark.debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
return totalUploaders > limit;
}
}

View File

@ -3,6 +3,7 @@ package org.klomp.snark;
import java.io.*;
import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
@ -16,6 +17,7 @@ public class SnarkManager implements Snark.CompleteListener {
/** map of (canonical) filename to Snark instance (unsynchronized) */
private Map _snarks;
private Object _addSnarkLock;
private String _configFile;
private Properties _config;
private I2PAppContext _context;
@ -27,19 +29,23 @@ public class SnarkManager implements Snark.CompleteListener {
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_UPLOADERS_TOTAL = "i2psnark.uploaders.total";
public static final String PROP_DIR = "i2psnark.dir";
public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
public static final String PROP_AUTO_START = "i2snark.autoStart";
public static final String DEFAULT_AUTO_START = "false";
private SnarkManager() {
_snarks = new HashMap();
_addSnarkLock = new Object();
_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"));
_messages.add("Adding torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
monitor.setDaemon(true);
monitor.start();
@ -91,10 +97,14 @@ public class SnarkManager implements Snark.CompleteListener {
_config.setProperty(PROP_I2CP_HOST, "localhost");
if (!_config.containsKey(PROP_I2CP_PORT))
_config.setProperty(PROP_I2CP_PORT, "7654");
if (!_config.containsKey(PROP_I2CP_OPTS))
_config.setProperty(PROP_I2CP_OPTS, "inbound.length=1 inbound.lengthVariance=1 outbound.length=1 outbound.lengthVariance=1");
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_UPLOADERS_TOTAL))
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS);
if (!_config.containsKey(PROP_DIR))
_config.setProperty(PROP_DIR, "i2psnark");
if (!_config.containsKey(PROP_AUTO_START))
@ -125,6 +135,7 @@ public class SnarkManager implements Snark.CompleteListener {
int eepPort = getInt(PROP_EEP_PORT, 4444);
if (eepHost != null)
I2PSnarkUtil.instance().setProxy(eepHost, eepPort);
I2PSnarkUtil.instance().setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS));
getDataDir().mkdirs();
}
@ -140,7 +151,8 @@ public class SnarkManager implements Snark.CompleteListener {
}
public void updateConfig(String dataDir, boolean autoStart, String seedPct, String eepHost,
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts) {
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
String upLimit) {
boolean changed = false;
if (eepHost != null) {
int port = I2PSnarkUtil.instance().getEepProxyPort();
@ -155,6 +167,20 @@ public class SnarkManager implements Snark.CompleteListener {
addMessage("EepProxy location changed to " + eepHost + ":" + port);
}
}
if (upLimit != null) {
int limit = I2PSnarkUtil.instance().getMaxUploaders();
try { limit = Integer.parseInt(upLimit); } catch (NumberFormatException nfe) {}
if ( limit != I2PSnarkUtil.instance().getEepProxyPort()) {
if ( limit >= Snark.MIN_TOTAL_UPLOADERS ) {
I2PSnarkUtil.instance().setMaxUploaders(limit);
changed = true;
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + limit);
addMessage("Total uploaders limit changed to " + limit);
} else {
addMessage("Minimum total uploaders limit is " + Snark.MIN_TOTAL_UPLOADERS);
}
}
}
if (i2cpHost != null) {
int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
@ -248,7 +274,9 @@ public class SnarkManager implements Snark.CompleteListener {
public void saveConfig() {
try {
DataHelper.storeProps(_config, new File(_configFile));
synchronized (_configFile) {
DataHelper.storeProps(_config, new File(_configFile));
}
} catch (IOException ioe) {
addMessage("Unable to save the config to '" + _configFile + "'");
}
@ -267,7 +295,7 @@ public class SnarkManager implements Snark.CompleteListener {
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()) {
if ((!dontAutoStart) && !I2PSnarkUtil.instance().connected()) {
addMessage("Connecting to I2P");
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) {
@ -287,7 +315,16 @@ public class SnarkManager implements Snark.CompleteListener {
Snark torrent = null;
synchronized (_snarks) {
torrent = (Snark)_snarks.get(filename);
if (torrent == null) {
}
// don't hold the _snarks lock while verifying the torrent
if (torrent == null) {
synchronized (_addSnarkLock) {
// double-check
synchronized (_snarks) {
if(_snarks.get(filename) != null)
return;
}
FileInputStream fis = null;
try {
fis = new FileInputStream(sfile);
@ -303,7 +340,9 @@ public class SnarkManager implements Snark.CompleteListener {
} else {
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
torrent.completeListener = this;
_snarks.put(filename, torrent);
synchronized (_snarks) {
_snarks.put(filename, torrent);
}
}
} catch (IOException ioe) {
addMessage("Torrent in " + sfile.getName() + " is invalid: " + ioe.getMessage());
@ -313,9 +352,9 @@ public class SnarkManager implements Snark.CompleteListener {
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
} else {
return;
}
} else {
return;
}
// ok, snark created, now lets start it up or configure it further
File f = new File(filename);
@ -327,13 +366,103 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
/**
* Get the timestamp for a torrent from the config file
*/
public long getSavedTorrentTime(MetaInfo metainfo) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
if (time == null)
return 0;
int comma = time.indexOf(',');
if (comma <= 0)
return 0;
time = time.substring(0, comma);
try { return Long.parseLong(time); } catch (NumberFormatException nfe) {}
return 0;
}
/**
* Get the saved bitfield for a torrent from the config file.
* Convert "." to a full bitfield.
*/
public BitField getSavedTorrentBitField(MetaInfo metainfo) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
if (bf == null)
return null;
int comma = bf.indexOf(',');
if (comma <= 0)
return null;
bf = bf.substring(comma + 1).trim();
int len = metainfo.getPieces();
if (bf.equals(".")) {
BitField bitfield = new BitField(len);
for (int i = 0; i < len; i++)
bitfield.set(i);
return bitfield;
}
byte[] bitfield = Base64.decode(bf);
if (bitfield == null)
return null;
if (bitfield.length * 8 < len)
return null;
return new BitField(bitfield, len);
}
/**
* Save the completion status of a torrent and the current time in the config file
* in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield".
* The config file property key is appended with the Base64 of the infohash,
* with the '=' changed to '$' since a key can't contain '='.
* The time is a standard long converted to string.
* The status is either a bitfield converted to Base64 or "." for a completed
* torrent to save space in the config file and in memory.
*/
public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
String now = "" + System.currentTimeMillis();
String bfs;
if (bitfield.complete()) {
bfs = ".";
} else {
byte[] bf = bitfield.getFieldBytes();
bfs = Base64.encode(bf);
}
_config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs);
saveConfig();
}
/**
* Remove the status of a torrent from the config file.
* This may help the config file from growing too big.
*/
public void removeTorrentStatus(MetaInfo metainfo) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
_config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
saveConfig();
}
private String locked_validateTorrent(MetaInfo info) throws IOException {
String announce = info.getAnnounce();
// basic validation of url
if ((!announce.startsWith("http://")) ||
(announce.indexOf(".i2p/") < 0))
return "Non-i2p tracker in " + info.getName() + ", deleting it";
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) {
} else if (info.getPieceLength(0) > 1*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());
@ -392,6 +521,8 @@ public class SnarkManager implements Snark.CompleteListener {
if (torrent != null) {
File torrentFile = new File(filename);
torrentFile.delete();
if (torrent.storage != null)
removeTorrentStatus(torrent.storage.getMetaInfo());
addMessage("Torrent removed: '" + torrentFile.getName() + "'");
}
}
@ -444,10 +575,9 @@ public class SnarkManager implements Snark.CompleteListener {
if (existingNames.contains(foundNames.get(i))) {
// already known. noop
} else {
if (I2PSnarkUtil.instance().connect())
addTorrent((String)foundNames.get(i));
else
if (shouldAutoStart() && !I2PSnarkUtil.instance().connect())
addMessage("Unable to connect to I2P");
addTorrent((String)foundNames.get(i), !shouldAutoStart());
}
}
// now lets see which ones have been removed...
@ -463,22 +593,29 @@ public class SnarkManager implements Snark.CompleteListener {
}
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/announce.php"
"Postman", "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=http://tracker.postman.i2p/"
, "eBook", "http://E71FRom6PZNEqTN2Lr8P-sr23b7HJVC32KoGnVQjaX6zJiXwhJy2HsXob36Qmj81TYFZdewFZa9mSJ533UZgGyQkXo2ahctg82JKYZfDe5uDxAn1E9YPjxZCWJaFJh0S~UwSs~9AZ7UcauSJIoNtpxrtbmRNVFLqnkEDdLZi26TeucfOmiFmIWnVblLniWv3tG1boE9Abd-6j3FmYVrRucYuepAILYt6katmVNOk6sXmno1Eynrp~~MBuFq0Ko6~jsc2E2CRVYXDhGHEMdt-j6JUz5D7S2RIVzDRqQyAZLKJ7OdQDmI31przzmne1vOqqqLC~1xUumZVIvF~yOeJUGNjJ1Vx0J8i2BQIusn1pQJ6UCB~ZtZZLQtEb8EPVCfpeRi2ri1M5CyOuxN0V5ekmPHrYIBNevuTCRC26NP7ZS5VDgx1~NaC3A-CzJAE6f1QXi0wMI9aywNG5KGzOPifcsih8eyGyytvgLtrZtV7ykzYpPCS-rDfITncpn5hliPUAAAA.i2p/pub/bt/announce.php=http://de-ebook-archiv.i2p/pub/bt/"
, "Gaytorrents", "http://uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA.i2p/announce.php=http://gaytorrents.i2p/"
, "NickyB", "http://9On6d3cZ27JjwYCtyJJbowe054d5tFnfMjv4PHsYs-EQn4Y4mk2zRixatvuAyXz2MmRfXG-NAUfhKr0KCxRNZbvHmlckYfT-WBzwwpiMAl0wDFY~Pl8cqXuhfikSG5WrqdPfDNNIBuuznS0dqaczf~OyVaoEOpvuP3qV6wKqbSSLpjOwwAaQPHjlRtNIW8-EtUZp-I0LT45HSoowp~6b7zYmpIyoATvIP~sT0g0MTrczWhbVTUZnEkZeLhOR0Duw1-IRXI2KHPbA24wLO9LdpKKUXed05RTz0QklW5ROgR6TYv7aXFufX8kC0-DaKvQ5JKG~h8lcoHvm1RCzNqVE-2aiZnO2xH08H-iCWoLNJE-Td2kT-Tsc~3QdQcnEUcL5BF-VT~QYRld2--9r0gfGl-yDrJZrlrihHGr5J7ImahelNn9PpkVp6eIyABRmJHf2iicrk3CtjeG1j9OgTSwaNmEpUpn4aN7Kx0zNLdH7z6uTgCGD9Kmh1MFYrsoNlTp4AAAA.i2p/bittorrent/announce.php=http://nickyb.i2p/bittorrent/"
, "Orion", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt/announce.php=http://orion.i2p/bt/"
// , "anonymity", "http://8EoJZIKrWgGuDrxA3nRJs1jsPfiGwmFWL91hBrf0HA7oKhEvAna4Ocx47VLUR9retVEYBAyWFK-eZTPcvhnz9XffBEiJQQ~kFSCqb1fV6IfPiV3HySqi9U5Caf6~hC46fRd~vYnxmaBLICT3N160cxBETqH3v2rdxdJpvYt8q4nMk9LUeVXq7zqCTFLLG5ig1uKgNzBGe58iNcsvTEYlnbYcE930ABmrzj8G1qQSgSwJ6wx3tUQNl1z~4wSOUMan~raZQD60lRK70GISjoX0-D0Po9WmPveN3ES3g72TIET3zc3WPdK2~lgmKGIs8GgNLES1cXTolvbPhdZK1gxddRMbJl6Y6IPFyQ9o4-6Rt3Lp-RMRWZ2TG7j2OMcNSiOmATUhKEFBDfv-~SODDyopGBmfeLw16F4NnYednvn4qP10dyMHcUASU6Zag4mfc2-WivrOqeWhD16fVAh8MoDpIIT~0r9XmwdaVFyLcjbXObabJczxCAW3fodQUnvuSkwzAAAA.i2p/anonymityTracker/announce.php=http://anonymityweb.i2p/anonymityTracker/"
// , "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 */
/** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */
public static final String PROP_TRACKERS = "i2psnark.trackers";
/** unordered map of announceURL to name */
private static Map trackerMap = null;
/** sorted map of name to announceURL=baseURL */
public Map getTrackers() {
HashMap rv = new HashMap();
if (trackerMap != null) // only do this once, can't be updated while running
return trackerMap;
Map rv = new TreeMap();
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]);
rv.put(DEFAULT_TRACKERS[i], DEFAULT_TRACKERS[i+1]);
} else {
StringTokenizer tok = new StringTokenizer(trackers, ",");
while (tok.hasMoreTokens()) {
@ -489,11 +626,12 @@ public class SnarkManager implements Snark.CompleteListener {
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);
rv.put(name, url);
}
}
return rv;
trackerMap = rv;
return trackerMap;
}
private static class TorrentFilenameFilter implements FilenameFilter {

View File

@ -39,15 +39,17 @@ public class Storage
private final StorageListener listener;
private final BitField bitfield; // BitField to represent the pieces
private BitField bitfield; // BitField to represent the pieces
private int needed; // Number of pieces needed
// XXX - Not always set correctly
int piece_size;
int pieces;
boolean changed;
/** The default piece size. */
private static int MIN_PIECE_SIZE = 256*1024;
private static int MAX_PIECE_SIZE = 1024*1024;
/** The maximum number of pieces in a torrent. */
private static long MAX_PIECES = 100*1024/20;
@ -75,7 +77,6 @@ public class Storage
throws IOException
{
this.listener = listener;
// Create names, rafs and lengths arrays.
getFiles(baseFile);
@ -90,7 +91,7 @@ public class Storage
piece_size = MIN_PIECE_SIZE;
pieces = (int) ((total - 1)/piece_size) + 1;
while (pieces > MAX_PIECES)
while (pieces > MAX_PIECES && piece_size < MAX_PIECE_SIZE)
{
piece_size = piece_size*2;
pieces = (int) ((total - 1)/piece_size) +1;
@ -116,7 +117,8 @@ public class Storage
}
String name = baseFile.getName();
if (files.size() == 1)
if (files.size() == 1) // FIXME: ...and if base file not a directory or should this be the only check?
// this makes a bad metainfo if the directory has only one file in it
{
files = null;
lengthsList = null;
@ -131,13 +133,14 @@ public class Storage
// Creates piece hases for a new storage.
public void create() throws IOException
{
if (true) {
// if (true) {
fast_digestCreate();
} else {
orig_digestCreate();
}
// } else {
// orig_digestCreate();
// }
}
/*
private void orig_digestCreate() throws IOException {
// Calculate piece_hashes
MessageDigest digest = null;
@ -155,7 +158,7 @@ public class Storage
byte[] piece = new byte[piece_size];
for (int i = 0; i < pieces; i++)
{
int length = getUncheckedPiece(i, piece, 0);
int length = getUncheckedPiece(i, piece);
digest.update(piece, 0, length);
byte[] hash = digest.digest();
for (int j = 0; j < 20; j++)
@ -173,6 +176,7 @@ public class Storage
// Reannounce to force recalculating the info_hash.
metainfo = metainfo.reannounce(metainfo.getAnnounce());
}
*/
private void fast_digestCreate() throws IOException {
// Calculate piece_hashes
@ -183,7 +187,7 @@ public class Storage
byte[] piece = new byte[piece_size];
for (int i = 0; i < pieces; i++)
{
int length = getUncheckedPiece(i, piece, 0);
int length = getUncheckedPiece(i, piece);
digest.update(piece, 0, length);
byte[] hash = digest.digest();
for (int j = 0; j < 20; j++)
@ -218,6 +222,8 @@ public class Storage
{
File f = (File)it.next();
names[i] = f.getPath();
if (base.isDirectory() && names[i].startsWith(base.getPath()))
names[i] = names[i].substring(base.getPath().length() + 1);
lengths[i] = f.length();
rafs[i] = new RandomAccessFile(f, "r");
i++;
@ -281,6 +287,10 @@ public class Storage
public void check(String rootDir) throws IOException
{
File base = new File(rootDir, filterName(metainfo.getName()));
// look for saved bitfield and timestamp in the config file
long savedTime = SnarkManager.instance().getSavedTorrentTime(metainfo);
BitField savedBitField = SnarkManager.instance().getSavedTorrentBitField(metainfo);
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
List files = metainfo.getFiles();
if (files == null)
@ -294,6 +304,11 @@ public class Storage
rafs = new RandomAccessFile[1];
names = new String[1];
lengths[0] = metainfo.getTotalLength();
if (useSavedBitField) {
long lm = base.lastModified();
if (lm <= 0 || lm > savedTime)
useSavedBitField = false;
}
if (base.exists() && !base.canWrite()) // hope we can get away with this, if we are only seeding...
rafs[0] = new RandomAccessFile(base, "r");
else
@ -318,6 +333,11 @@ public class Storage
File f = createFileFromNames(base, (List)files.get(i));
lengths[i] = ((Long)ls.get(i)).longValue();
total += lengths[i];
if (useSavedBitField) {
long lm = base.lastModified();
if (lm <= 0 || lm > savedTime)
useSavedBitField = false;
}
if (f.exists() && !f.canWrite()) // see above re: only seeding
rafs[i] = new RandomAccessFile(f, "r");
else
@ -331,13 +351,63 @@ public class Storage
throw new IOException("File lengths do not add up "
+ total + " != " + metalength);
}
checkCreateFiles();
if (useSavedBitField) {
bitfield = savedBitField;
needed = metainfo.getPieces() - bitfield.count();
Snark.debug("Found saved state and files unchanged, skipping check", Snark.NOTICE);
} else {
checkCreateFiles();
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
}
}
/**
* Reopen the file descriptors for a restart
* Do existence check but no length check or data reverification
*/
public void reopen(String rootDir) throws IOException
{
File base = new File(rootDir, filterName(metainfo.getName()));
List files = metainfo.getFiles();
if (files == null)
{
// Reopen base as file.
Snark.debug("Reopening file: " + base, Snark.NOTICE);
if (!base.exists())
throw new IOException("Could not reopen file " + base);
if (complete() || !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");
}
else
{
// Reopen base as dir.
Snark.debug("Reopening directory: " + base, Snark.NOTICE);
if (!base.isDirectory())
throw new IOException("Could not reopen directory " + base);
int size = files.size();
for (int i = 0; i < size; i++)
{
File f = getFileFromNames(base, (List)files.get(i));
if (!f.exists())
throw new IOException("Could not reopen file " + f);
if (complete() || !f.canWrite()) // see above re: only seeding
rafs[i] = new RandomAccessFile(f, "r");
else
rafs[i] = new RandomAccessFile(f, "rw");
}
}
}
/**
* Removes 'suspicious' characters from the give file name.
*/
private String filterName(String name)
private static String filterName(String name)
{
// XXX - Is this enough?
return name.replace(File.separatorChar, '_');
@ -369,6 +439,17 @@ public class Storage
return f;
}
public static File getFileFromNames(File base, List names)
{
Iterator it = names.iterator();
while (it.hasNext())
{
String name = filterName((String)it.next());
base = new File(base, name);
}
return base;
}
private void checkCreateFiles() throws IOException
{
// Whether we are resuming or not,
@ -399,7 +480,7 @@ public class Storage
byte[] piece = new byte[metainfo.getPieceLength(0)];
for (int i = 0; i < pieces; i++)
{
int length = getUncheckedPiece(i, piece, 0);
int length = getUncheckedPiece(i, piece);
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
if (correctHash)
{
@ -459,19 +540,27 @@ public class Storage
// gobble gobble
}
}
changed = false;
}
/**
* Returns a byte array containing the requested piece or null if
* Returns a byte array containing a portion of the requested piece or null if
* the storage doesn't contain the piece yet.
*/
public byte[] getPiece(int piece) throws IOException
public byte[] getPiece(int piece, int off, int len) throws IOException
{
if (!bitfield.get(piece))
return null;
byte[] bs = new byte[metainfo.getPieceLength(piece)];
getUncheckedPiece(piece, bs, 0);
//Catch a common place for OOMs esp. on 1MB pieces
byte[] bs;
try {
bs = new byte[len];
} catch (OutOfMemoryError oom) {
I2PSnarkUtil.instance().debug("Out of memory, can't honor request for piece " + piece, Snark.WARNING, oom);
return null;
}
getUncheckedPiece(piece, bs, off, len);
return bs;
}
@ -482,10 +571,11 @@ public class Storage
* matches), otherwise false.
* @exception IOException when some storage related error occurs.
*/
public boolean putPiece(int piece, byte[] bs) throws IOException
public boolean putPiece(int piece, byte[] ba) throws IOException
{
// First check if the piece is correct.
// If we were paranoia we could copy the array first.
// Copy the array first to be paranoid.
byte[] bs = (byte[]) ba.clone();
int length = bs.length;
boolean correctHash = metainfo.checkPiece(piece, bs, 0, length);
if (listener != null)
@ -504,6 +594,7 @@ public class Storage
needed--;
complete = needed == 0;
}
}
// Early typecast, avoid possibly overflowing a temp integer
@ -537,24 +628,48 @@ public class Storage
}
}
changed = true;
if (complete) {
listener.storageCompleted(this);
// listener.storageCompleted(this);
// do we also need to close all of the files and reopen
// them readonly?
// Do a complete check to be sure.
// Temporarily resets the 'needed' variable and 'bitfield', then call
// checkCreateFiles() which will set 'needed' and 'bitfield'
// and also call listener.storageCompleted() if the double-check
// was successful.
// Todo: set a listener variable so the web shows "checking" and don't
// have the user panic when completed amount goes to zero temporarily?
needed = metainfo.getPieces();
bitfield = new BitField(needed);
checkCreateFiles();
if (needed > 0) {
listener.setWantedPieces(this);
Snark.debug("WARNING: Not really done, missing " + needed
+ " pieces", Snark.WARNING);
} else {
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
}
}
return true;
}
private int getUncheckedPiece(int piece, byte[] bs, int off)
private int getUncheckedPiece(int piece, byte[] bs)
throws IOException
{
return getUncheckedPiece(piece, bs, 0, metainfo.getPieceLength(piece));
}
private int getUncheckedPiece(int piece, byte[] bs, int off, int length)
throws IOException
{
// XXX - copy/paste code from putPiece().
// Early typecast, avoid possibly overflowing a temp integer
long start = (long) piece * (long) metainfo.getPieceLength(0);
long start = ((long) piece * (long) metainfo.getPieceLength(0)) + off;
int length = metainfo.getPieceLength(piece);
int i = 0;
long raflen = lengths[i];
while (start > raflen)
@ -572,7 +687,7 @@ public class Storage
synchronized(rafs[i])
{
rafs[i].seek(start);
rafs[i].readFully(bs, off + read, len);
rafs[i].readFully(bs, read, len);
}
read += len;
if (need - len > 0)

View File

@ -55,4 +55,11 @@ public interface StorageListener
*
*/
void storageCompleted(Storage storage);
/** Reset the peer's wanted pieces table
* Call after the storage double-check fails
*
* @param peer the peer
*/
void setWantedPieces(Storage storage);
}

View File

@ -41,8 +41,11 @@ public class TrackerClient extends I2PThread
private static final String STARTED_EVENT = "started";
private static final String COMPLETED_EVENT = "completed";
private static final String STOPPED_EVENT = "stopped";
private static final String NOT_REGISTERED = "torrent not registered"; //bytemonsoon
private final static int SLEEP = 5; // 5 minutes.
private final static int DELAY_MIN = 2000; // 2 secs.
private final static int DELAY_MUL = 1500; // 1.5 secs.
private final MetaInfo meta;
private final PeerCoordinator coordinator;
@ -110,6 +113,7 @@ public class TrackerClient extends I2PThread
long left = coordinator.getLeft();
boolean completed = (left == 0);
int sleptTime = 0;
try
{
@ -117,6 +121,7 @@ public class TrackerClient extends I2PThread
boolean started = false;
while (!started)
{
sleptTime = 0;
try
{
// Send start.
@ -125,18 +130,20 @@ public class TrackerClient extends I2PThread
STARTED_EVENT);
Set peers = info.getPeers();
coordinator.trackerSeenPeers = peers.size();
coordinator.trackerProblems = null;
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) {}
int delay = DELAY_MUL;
delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
delay += DELAY_MIN;
sleptTime += delay;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
started = true;
coordinator.trackerProblems = null;
}
catch (IOException ioe)
{
@ -145,9 +152,16 @@ public class TrackerClient extends I2PThread
("WARNING: Could not contact tracker at '"
+ announce + "': " + ioe, Snark.WARNING);
coordinator.trackerProblems = ioe.getMessage();
if (coordinator.trackerProblems.toLowerCase().startsWith(NOT_REGISTERED)) {
stop = true;
coordinator.snark.stopTorrent();
}
}
if (!started && !stop)
if (stop)
break;
if (!started)
{
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
try
@ -168,8 +182,17 @@ public class TrackerClient extends I2PThread
try
{
// Sleep some minutes...
int delay = SLEEP*60*1000 + r.nextInt(120*1000);
Thread.sleep(delay);
int delay;
if(coordinator.trackerProblems != null && !completed) {
delay = 60*1000;
} else if(completed) {
delay = 3*SLEEP*60*1000 + r.nextInt(120*1000);
} else {
delay = SLEEP*60*1000 + r.nextInt(120*1000);
delay -= sleptTime;
}
if (delay > 0)
Thread.sleep(delay);
}
catch(InterruptedException interrupt)
{
@ -196,6 +219,7 @@ public class TrackerClient extends I2PThread
event = NO_EVENT;
// Only do a request when necessary.
sleptTime = 0;
if (event == COMPLETED_EVENT
|| coordinator.needPeers()
|| System.currentTimeMillis() > lastRequestTime + interval)
@ -206,6 +230,7 @@ public class TrackerClient extends I2PThread
uploaded, downloaded, left,
event);
coordinator.trackerProblems = null;
Set peers = info.getPeers();
coordinator.trackerSeenPeers = peers.size();
if ( (left > 0) && (!completed) ) {
@ -216,10 +241,14 @@ public class TrackerClient extends I2PThread
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) {}
// only delay if we actually make an attempt to add peer
if(coordinator.addPeer(cur)) {
int delay = DELAY_MUL;
delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
delay += DELAY_MIN;
sleptTime += delay;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
}
}
@ -229,6 +258,11 @@ public class TrackerClient extends I2PThread
Snark.debug
("WARNING: Could not contact tracker at '"
+ announce + "': " + ioe, Snark.WARNING);
coordinator.trackerProblems = ioe.getMessage();
if (coordinator.trackerProblems.toLowerCase().startsWith(NOT_REGISTERED)) {
stop = true;
coordinator.snark.stopTorrent();
}
}
}
}
@ -266,13 +300,13 @@ public class TrackerClient extends I2PThread
+ "&downloaded=" + downloaded
+ "&left=" + left
+ ((event != NO_EVENT) ? ("&event=" + event) : "");
if (Snark.debug >= Snark.INFO)
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
File fetched = I2PSnarkUtil.instance().get(s);
if (fetched == null) {
throw new IOException("Error fetching " + s);
}
fetched.deleteOnExit();
InputStream in = null;
try {
@ -280,8 +314,7 @@ public class TrackerClient extends I2PThread
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
coordinator.getMetaInfo());
if (Snark.debug >= Snark.INFO)
Snark.debug("TrackerClient response: " + info, Snark.INFO);
Snark.debug("TrackerClient response: " + info, Snark.INFO);
lastRequestTime = System.currentTimeMillis();
String failure = info.getFailureReason();
@ -300,7 +333,7 @@ public class TrackerClient extends I2PThread
* Very lazy byte[] to URL encoder. Just encodes everything, even
* "normal" chars.
*/
static String urlencode(byte[] bs)
public static String urlencode(byte[] bs)
{
StringBuffer sb = new StringBuffer(bs.length*3);
for (int i = 0; i < bs.length; i++)

View File

@ -43,22 +43,49 @@ public class I2PSnarkServlet extends HttpServlet {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html; charset=UTF-8");
long stats[] = {0,0,0,0};
String nonce = req.getParameter("nonce");
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
processRequest(req);
String peerParam = req.getParameter("p");
String peerString;
if (peerParam == null) {
peerString = "";
} else {
peerString = "?p=" + peerParam;
}
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("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + peerString + "\">\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("<tr><td width=\"20%\" 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>");
out.write("<table border=\"0\" width=\"100%\">\n");
out.write("<tr><td><a href=\"" + req.getRequestURI() + peerString + "\" class=\"snarkRefresh\">Refresh</a>\n");
out.write("<td><a href=\"http://forum.i2p/viewforum.php?f=21\" class=\"snarkRefresh\">Forum</a>\n");
int count = 0;
Map trackers = _manager.getTrackers();
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String baseURL = (String)trackers.get(name);
int e = baseURL.indexOf('=');
if (e < 0)
continue;
baseURL = baseURL.substring(e + 1);
if (count++ % 2 == 0)
out.write("<tr>");
out.write("<td><a href=\"" + baseURL + "\" class=\"snarkRefresh\">" + name + "</a>\n");
}
if (count % 2 == 1)
out.write("<td>&nbsp;\n");
out.write("</table>\n");
out.write("</td><td width=\"80%\" 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);
@ -66,19 +93,42 @@ public class I2PSnarkServlet extends HttpServlet {
}
out.write("</pre></td></tr></table>\n");
out.write(TABLE_HEADER);
List snarks = getSortedSnarks(req);
String uri = req.getRequestURI();
out.write(TABLE_HEADER);
if (I2PSnarkUtil.instance().connected() && snarks.size() > 0) {
if (peerParam != null)
out.write("(<a href=\"" + req.getRequestURI() + "\">Hide Peers</a>)<br />\n");
else
out.write("(<a href=\"" + req.getRequestURI() + "?p=1" + "\">Show Peers</a>)<br />\n");
}
out.write(TABLE_HEADER2);
out.write("<th align=\"left\" valign=\"top\">");
if (I2PSnarkUtil.instance().connected())
out.write("<a href=\"" + uri + "?action=StopAll&nonce=" + _nonce +
"\" title=\"Stop all torrents and the i2p tunnel\">Stop All</a>");
else
out.write("&nbsp;");
out.write("</th></tr></thead>\n");
for (int i = 0; i < snarks.size(); i++) {
Snark snark = (Snark)snarks.get(i);
displaySnark(out, snark, uri, i);
boolean showPeers = "1".equals(peerParam) || Base64.encode(snark.meta.getInfoHash()).equals(peerParam);
displaySnark(out, snark, uri, i, stats, showPeers);
}
if (snarks.size() <= 0) {
out.write(TABLE_EMPTY);
} else if (snarks.size() > 1) {
out.write(TABLE_TOTAL);
out.write(" <th align=\"right\" valign=\"top\">" + formatSize(stats[0]) + "</th>\n" +
" <th align=\"right\" valign=\"top\">" + formatSize(stats[1]) + "</th>\n" +
" <th align=\"right\" valign=\"top\">" + formatSize(stats[2]) + "ps</th>\n" +
" <th align=\"right\" valign=\"top\">" + formatSize(stats[3]) + "ps</th>\n" +
" <th>&nbsp;</th></tr>\n" +
"</tfoot>\n");
}
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);
@ -198,23 +248,31 @@ public class I2PSnarkServlet extends HttpServlet {
_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)
f = new File(_manager.getDataDir(), dataFile);
if (files == null) { // single file torrent
if (f.delete())
_manager.addMessage("Data file deleted: " + f.getAbsolutePath());
else
_manager.addMessage("Data file could not be deleted: " + f.getAbsolutePath());
break;
}
for (int i = 0; i < files.size(); i++) { // pass 1 delete files
// multifile torrents have the getFiles() return lists of lists of filenames, but
// each of those lists just contain a single file afaict...
File df = Storage.getFileFromNames(f, (List) files.get(i));
if (df.delete())
_manager.addMessage("Data file deleted: " + df.getAbsolutePath());
else
_manager.addMessage("Data file could not be deleted: " + df.getAbsolutePath());
}
for (int i = files.size() - 1; i >= 0; i--) { // pass 2 delete dirs - not foolproof,
// we could sort and do a strict bottom-up
File df = Storage.getFileFromNames(f, (List) files.get(i));
df = df.getParentFile();
if (df == null || !df.exists())
continue;
if(df.delete())
_manager.addMessage("Data dir deleted: " + df.getAbsolutePath());
}
break;
}
@ -230,7 +288,8 @@ public class I2PSnarkServlet extends HttpServlet {
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);
String upLimit = req.getParameter("upLimit");
_manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts, upLimit);
} else if ("Create torrent".equals(action)) {
String baseData = req.getParameter("baseFile");
if (baseData != null) {
@ -240,30 +299,45 @@ public class I2PSnarkServlet extends HttpServlet {
if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
announceURL = announceURLOther;
if (baseFile.exists() && baseFile.isFile()) {
if (announceURL == null || announceURL.length() <= 0)
_manager.addMessage("Error creating torrent - you must select a tracker");
else if (baseFile.exists()) {
try {
Storage s = new Storage(baseFile, announceURL, null);
s.create();
s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over
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());
_manager.saveTorrentStatus(info, s.getBitField()); // so addTorrent won't recheck
// DirMonitor could grab this first, maybe hold _snarks lock?
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.addTorrent(torrentFile.getCanonicalPath(), true);
_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());
_manager.addMessage("Cannot create a torrent for the nonexistent data: " + baseFile.getAbsolutePath());
}
}
} else if ("StopAll".equals(action)) {
_manager.addMessage("Stopping all torrents and closing the I2P tunnel");
List snarks = getSortedSnarks(req);
for (int i = 0; i < snarks.size(); i++) {
Snark snark = (Snark)snarks.get(i);
if (!snark.stopped)
_manager.stopTorrent(snark.torrent, false);
}
if (I2PSnarkUtil.instance().connected()) {
I2PSnarkUtil.instance().disconnect();
_manager.addMessage("I2P tunnel closed");
}
}
}
@ -279,12 +353,16 @@ public class I2PSnarkServlet extends HttpServlet {
}
return rv;
}
private static final int MAX_DISPLAYED_FILENAME_LENGTH = 60;
private void displaySnark(PrintWriter out, Snark snark, String uri, int row) throws IOException {
private static final int MAX_DISPLAYED_ERROR_LENGTH = 40;
private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers) 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
int i = filename.lastIndexOf(".torrent");
if (i > 0)
filename = filename.substring(0, i);
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH)
filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "...";
long total = snark.meta.getTotalLength();
@ -292,32 +370,78 @@ public class I2PSnarkServlet extends HttpServlet {
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();
long downBps = 0;
long upBps = 0;
if (snark.coordinator != null) {
downBps = snark.coordinator.getDownloadRate();
upBps = snark.coordinator.getUploadRate();
}
long remainingSeconds;
if (downBps > 0)
remainingSeconds = remaining / downBps;
else
remainingSeconds = -1;
boolean isRunning = !snark.stopped;
long uploaded = 0;
if (snark.coordinator != null) {
uploaded = snark.coordinator.getUploaded();
stats[0] += snark.coordinator.getDownloaded();
}
stats[1] += uploaded;
if (isRunning) {
stats[2] += downBps;
stats[3] += upBps;
}
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 err = null;
int curPeers = 0;
int knownPeers = 0;
if (snark.coordinator != null) {
err = snark.coordinator.trackerProblems;
curPeers = snark.coordinator.getPeerCount();
knownPeers = snark.coordinator.trackerSeenPeers;
}
String statusString = "Unknown";
if (err != null) {
if (isRunning)
statusString = "TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
else
statusString = "TrackerErr (" + err + ")";
if (isRunning && curPeers > 0 && !showPeers)
statusString = "<a title=\"" + err + "\">TrackerErr</a> (" +
curPeers + "/" + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
else if (isRunning)
statusString = "<a title=\"" + err + "\">TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
else {
if (err.length() > MAX_DISPLAYED_ERROR_LENGTH)
err = err.substring(0, MAX_DISPLAYED_ERROR_LENGTH) + "...";
statusString = "TrackerErr<br />(" + err + ")";
}
} else if (remaining <= 0) {
if (isRunning)
if (isRunning && curPeers > 0 && !showPeers)
statusString = "Seeding (" +
curPeers + "/" + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
else if (isRunning)
statusString = "Seeding (" + curPeers + "/" + knownPeers + " peers)";
else
statusString = "Complete";
} else {
if (isRunning)
if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
statusString = "OK (" +
curPeers + "/" + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
else if (isRunning && curPeers > 0 && downBps > 0)
statusString = "OK (" + curPeers + "/" + knownPeers + " peers)";
else if (isRunning && curPeers > 0 && !showPeers)
statusString = "Stalled (" +
curPeers + "/" + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
else if (isRunning && curPeers > 0)
statusString = "Stalled (" + curPeers + "/" + knownPeers + " peers)";
else if (isRunning)
statusString = "No Peers (0/" + knownPeers + ")";
else
statusString = "Stopped";
}
@ -334,40 +458,138 @@ public class I2PSnarkServlet extends HttpServlet {
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
// temporarily hardcoded for postman and anonymity, requires bytemonsoon patch for lookup by info_hash
String announce = snark.meta.getAnnounce();
if (announce.startsWith("http://YRgrgTLG") || announce.startsWith("http://8EoJZIKr")) {
Map trackers = _manager.getTrackers();
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String baseURL = (String)trackers.get(name);
if (!baseURL.startsWith(announce))
continue;
int e = baseURL.indexOf('=');
if (e < 0)
continue;
baseURL = baseURL.substring(e + 1);
out.write("&nbsp;&nbsp;&nbsp;(<a href=\"" + baseURL + "details.php?dllist=1&filelist=1&info_hash=");
out.write(TrackerClient.urlencode(snark.meta.getInfoHash()));
out.write("\" title=\"" + name + " Tracker\">Details</a>)");
break;
}
}
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentUploaded " + rowClass
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentETA " + rowClass + "\">");
if(isRunning && remainingSeconds > 0)
out.write(DataHelper.formatDuration(remainingSeconds*1000)); // (eta 6h)
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
if (remaining > 0)
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
else
out.write(formatSize(total)); // 3GB
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" 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=\"right\" class=\"snarkTorrentRate\">");
if(isRunning && remaining > 0)
out.write(formatSize(downBps) + "ps");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentRate\">");
if(isRunning)
out.write(formatSize(upBps) + "ps");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentAction " + rowClass + "\">");
String parameters = "&nonce=" + _nonce + "&torrent=" + Base64.encode(snark.meta.getInfoHash());
if (showPeers)
parameters = parameters + "&p=1";
if (isRunning) {
out.write("<a href=\"" + uri + "?action=Stop&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
out.write("<a href=\"" + uri + "?action=Stop" + parameters
+ "\" title=\"Stop the torrent\">Stop</a>");
} else {
if (isValid)
out.write("<a href=\"" + uri + "?action=Start&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
out.write("<a href=\"" + uri + "?action=Start" + parameters
+ "\" title=\"Start the torrent\">Start</a> ");
out.write("<a href=\"" + uri + "?action=Remove&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
out.write("<a href=\"" + uri + "?action=Remove" + parameters
+ "\" 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())
out.write("<a href=\"" + uri + "?action=Delete" + parameters
+ "\" title=\"Delete the .torrent file and the associated data file(s)\">Delete</a> ");
}
out.write("</td>\n</tr>\n");
if(showPeers && isRunning && curPeers > 0) {
List peers = snark.coordinator.peerList();
Iterator it = peers.iterator();
while (it.hasNext()) {
Peer peer = (Peer)it.next();
if (!peer.isConnected())
continue;
out.write("<tr class=\"" + rowClass + "\">");
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
String ch = peer.toString().substring(0, 4);
String client;
if ("AwMD".equals(ch))
client = "I2PSnark";
else if ("BFJT".equals(ch))
client = "I2PRufus";
else if ("TTMt".equals(ch))
client = "I2P-BT";
else if ("LUFa".equals(ch))
client = "Azureus";
else
client = "Unknown";
out.write("<font size=-1>" + client + "</font>&nbsp;&nbsp;<tt>" + peer.toString().substring(5, 9) + "</tt>");
out.write("</td>\n\t");
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
float pct = (float) (100.0 * (float) peer.completed() / snark.meta.getPieces());
if (pct == 100.0)
out.write("<font size=-1>Seed</font>");
else {
String ps = String.valueOf(pct);
if (ps.length() > 5)
ps = ps.substring(0, 5);
out.write("<font size=-1>" + ps + "%</font>");
}
out.write("</td>\n\t");
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
if (remaining > 0) {
if (peer.isInteresting() && !peer.isChoked()) {
out.write("<font color=#008000>");
out.write("<font size=-1>" + formatSize(peer.getDownloadRate()) + "ps</font></font>");
} else {
out.write("<font color=#a00000><font size=-1><a title=\"");
if (!peer.isInteresting())
out.write("Uninteresting\">");
else
out.write("Choked\">");
out.write(formatSize(peer.getDownloadRate()) + "ps</a></font></font>");
}
}
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
if (pct != 100.0) {
if (peer.isInterested() && !peer.isChoking()) {
out.write("<font color=#008000>");
out.write("<font size=-1>" + formatSize(peer.getUploadRate()) + "ps</font></font>");
} else {
out.write("<font color=#a00000><font size=-1><a title=\"");
if (!peer.isInterested())
out.write("Uninterested\">");
else
out.write("Choking\">");
out.write(formatSize(peer.getUploadRate()) + "ps</a></font></font>");
}
}
out.write("</td>\n\t");
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
out.write("</td></tr>\n\t");
}
}
}
private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
@ -381,7 +603,8 @@ public class I2PSnarkServlet extends HttpServlet {
// *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");
out.write("<span class=\"snarkConfigTitle\">Add Torrent:</span><br />\n");
out.write("From URL&nbsp;: <input type=\"text\" name=\"newURL\" size=\"80\" 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");
@ -396,21 +619,24 @@ public class I2PSnarkServlet extends HttpServlet {
if (baseFile == null)
baseFile = "";
out.write("<span class=\"snarkNewTorrent\">\n");
out.write("<span class=\"snarkNewTorrent\"><hr />\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("<span class=\"snarkConfigTitle\">Create Torrent:</span><br />\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());
Map trackers = _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");
int e = announceURL.indexOf('=');
if (e > 0)
announceURL = announceURL.substring(0, e);
out.write("\t<option value=\"" + announceURL + "\">" + name + "</option>\n");
}
out.write("</select>\n");
out.write("or <input type=\"text\" name=\"announceURLOther\" size=\"50\" value=\"http://\" " +
@ -419,23 +645,11 @@ public class I2PSnarkServlet extends HttpServlet {
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;
//int seedPct = 0;
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
out.write("<span class=\"snarkConfig\"><hr />\n");
@ -449,6 +663,7 @@ public class I2PSnarkServlet extends HttpServlet {
//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");
@ -463,6 +678,9 @@ public class I2PSnarkServlet extends HttpServlet {
else
out.write("<option value=\"150\">150%</option>\n\t");
out.write("</select><br />\n");
*/
out.write("Total uploader limit: <input type=\"text\" name=\"upLimit\" value=\""
+ I2PSnarkUtil.instance().getMaxUploaders() + "\" size=\"3\" /> peers<br />\n");
//out.write("<hr />\n");
out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
@ -480,22 +698,23 @@ public class I2PSnarkServlet extends HttpServlet {
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=\""
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"80\" value=\""
+ opts.toString() + "\" /><br />\n");
out.write("<input type=\"submit\" value=\"Save configuration\" name=\"action\" />\n");
out.write("</span>\n");
out.write("</form>\n");
}
// rounding makes us look faster :)
private String formatSize(long bytes) {
if (bytes < 5*1024)
return bytes + "B";
else if (bytes < 5*1024*1024)
return (bytes/1024) + "KB";
return ((bytes + 512)/1024) + "KB";
else if (bytes < 5*1024*1024*1024l)
return (bytes/(1024*1024)) + "MB";
return ((bytes + 512*1024)/(1024*1024)) + "MB";
else
return (bytes/(1024*1024*1024)) + "GB";
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + "GB";
}
private static final String HEADER_BEGIN = "<html>\n" +
@ -540,7 +759,10 @@ public class I2PSnarkServlet extends HttpServlet {
"}\n" +
"th {\n" +
" background-color: #C7D5D5;\n" +
" margin: 0px 0px 0px 0px;\n" +
" padding: 0px 7px 0px 3px;\n" +
"}\n" +
"td {\n" +
" padding: 0px 7px 0px 3px;\n" +
"}\n" +
".snarkTorrentEven {\n" +
" background-color: #E7E7E7;\n" +
@ -550,8 +772,6 @@ public class I2PSnarkServlet extends HttpServlet {
"}\n" +
".snarkNewTorrent {\n" +
" font-size: 10pt;\n" +
" font-family: monospace;\n" +
" background-color: #ADAE9;\n" +
"}\n" +
".snarkAddInfo {\n" +
" font-size: 10pt;\n" +
@ -568,19 +788,26 @@ public class I2PSnarkServlet extends HttpServlet {
"<body>\n";
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\">\n" +
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\" cellpadding=\"0 10px\">\n" +
"<thead>\n" +
"<tr><th align=\"left\" valign=\"top\">Status</th>\n" +
"<tr><th align=\"left\" valign=\"top\">Status \n";
private static final String TABLE_HEADER2 = "</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";
" <th align=\"right\" valign=\"top\">ETA</th>\n" +
" <th align=\"right\" valign=\"top\">Downloaded</th>\n" +
" <th align=\"right\" valign=\"top\">Uploaded</th>\n" +
" <th align=\"right\" valign=\"top\">Down Rate</th>\n" +
" <th align=\"right\" valign=\"top\">Up Rate</th>\n";
private static final String TABLE_TOTAL = "<tfoot>\n" +
"<tr><th align=\"left\" valign=\"top\">Totals</th>\n" +
" <th>&nbsp;</th>\n" +
" <th>&nbsp;</th>\n";
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
"<td class=\"snarkTorrentEven\" align=\"left\"" +
" valign=\"top\" colspan=\"5\">No torrents</td></tr>\n";
" valign=\"top\" colspan=\"8\">No torrents</td></tr>\n";
private static final String TABLE_FOOTER = "</table>\n";

View File

@ -112,6 +112,35 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"or naming one of them differently.<P/>")
.getBytes();
private final static byte[] ERR_BAD_PROTOCOL =
("HTTP/1.1 403 Bad Protocol\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: NON-HTTP PROTOCOL</H1>"+
"The request uses a bad protocol. "+
"The I2P HTTP Proxy supports http:// requests ONLY. Other protocols such as https:// and ftp:// are not allowed.<BR>")
.getBytes();
private final static byte[] ERR_LOCALHOST =
("HTTP/1.1 403 Access Denied\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: REQUEST DENIED</H1>"+
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
.getBytes();
private final static int MAX_POSTBYTES = 20*1024*1024; // arbitrary but huge - all in memory, no temp file
private final static byte[] ERR_MAXPOST =
("HTTP/1.1 503 Bad POST\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: REQUEST DENIED</H1>"+
"The maximum POST size is " + MAX_POSTBYTES + " bytes.<BR>")
.getBytes();
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
@ -384,6 +413,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
usingWWWProxy = true;
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Host doesnt end with .i2p and it contains a period [" + host + "]: wwwProxy!");
} else if (host.toLowerCase().startsWith("localhost:")) {
if (out != null) {
out.write(ERR_LOCALHOST);
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;
} else {
request = request.substring(pos + 1);
pos = request.indexOf("/");
@ -474,16 +513,32 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "NewRequest header: [" + newRequest.toString() + "]");
int postbytes = 0;
while (br.ready()) { // empty the buffer (POST requests)
int i = br.read();
if (i != -1) {
newRequest.append((char) i);
if (++postbytes > MAX_POSTBYTES) {
if (out != null) {
out.write(ERR_MAXPOST);
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;
}
}
}
if (method == null || destination == null) {
l.log("No HTTP method found in the request.");
if (out != null) {
out.write(ERR_REQUEST_DENIED);
if ("http://".equalsIgnoreCase(protocol))
out.write(ERR_REQUEST_DENIED);
else
out.write(ERR_BAD_PROTOCOL);
out.write("<p /><i>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
@ -546,6 +601,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (OutOfMemoryError oom) { // mainly for huge POSTs
IOException ex = new IOException("OOM (in POST?)");
_log.info("getPrefix(requestId) + Error trying to connect", ex);
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
}
}
@ -584,6 +645,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
}
private static String jumpServers[] = {
"http://i2host.i2p/cgi-bin/i2hostjump?",
"http://orion.i2p/jump/",
"http://stats.i2p/cgi-bin/jump.cgi?a=",
"http://trevorreznik.i2p/cgi-bin/jump.php?hostname="
};
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
boolean usingWWWProxy, String wwwProxy, boolean showAddrHelper) throws IOException {
if (out != null) {
@ -602,11 +669,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
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("<br><br>Click a link below to look for an address helper by using a \"jump\" service:<br>".getBytes());
for (int i = 0; i < jumpServers.length; i++) {
out.write("<br><a href=\"".getBytes());
out.write(jumpServers[i].getBytes());
out.write(uri.getBytes());
out.write("\">".getBytes());
out.write(jumpServers[i].getBytes());
out.write(uri.getBytes());
out.write("</a>".getBytes());
}
}
}
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());

View File

@ -272,7 +272,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
int idx=0;
final String[] allowedCommands =
{
"NOTICE",
// "NOTICE", // can contain CTCP
//"PING",
//"PONG",
"MODE",
@ -282,6 +282,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
"PART",
"WALLOPS",
"ERROR",
"KICK",
"H", // "hide operator status" (after kicking an op)
"TOPIC"
};
@ -304,34 +306,36 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
} catch(NumberFormatException nfe){}
if ("PING".equals(command))
if ("PING".equalsIgnoreCase(command))
return "PING 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
if ("PONG".equals(command)) {
if ("PONG".equalsIgnoreCase(command)) {
// Turn the received ":irc.freshcoffee.i2p PONG irc.freshcoffee.i2p :127.0.0.1"
// into ":127.0.0.1 PONG 127.0.0.1 " so that the caller can append the client's extra parameter
// though, does 127.0.0.1 work for irc clients connecting remotely? and for all of them? sure would
// be great if irc clients actually followed the RFCs here, but i guess thats too much to ask.
// If we haven't PINGed them, or the PING we sent isn't something we know how to filter, this
// is blank.
String pong = expectedPong.length() > 0 ? expectedPong.toString() : null;
//
// String pong = expectedPong.length() > 0 ? expectedPong.toString() : null;
// If we aren't going to rewrite it, pass it through
String pong = expectedPong.length() > 0 ? expectedPong.toString() : s;
expectedPong.setLength(0);
return pong;
}
// Allow all allowedCommands
for(int i=0;i<allowedCommands.length;i++) {
if(allowedCommands[i].equals(command))
if(allowedCommands[i].equalsIgnoreCase(command))
return s;
}
// Allow PRIVMSG, but block CTCP.
if("PRIVMSG".equals(command))
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
{
String msg;
msg = field[idx++];
byte[] bytes = msg.getBytes();
if(bytes[1]==0x01)
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
{
// CTCP
msg=msg.substring(2);
@ -354,7 +358,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
String command;
final String[] allowedCommands =
{
"NOTICE",
// "NOTICE", // can contain CTCP
"MODE",
"JOIN",
"NICK",
@ -385,7 +389,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
command = field[0].toUpperCase();
if ("PING".equals(command)) {
if ("PING".equalsIgnoreCase(command)) {
// Most clients just send a PING and are happy with any old PONG. Others,
// like BitchX, actually expect certain behavior. It sends two different pings:
// "PING :irc.freshcoffee.i2p" and "PING 1234567890 127.0.0.1" (where the IP is the proxy)
@ -401,13 +405,15 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
expectedPong.setLength(0);
if (field.length == 1) { // PING
rv = "PING";
expectedPong.append("PONG 127.0.0.1");
// If we aren't rewriting the PING don't rewrite the PONG
// expectedPong.append("PONG 127.0.0.1");
} else if (field.length == 2) { // PING nonce
rv = "PING " + field[1];
expectedPong.append("PONG ").append(field[1]);
// If we aren't rewriting the PING don't rewrite the PONG
// expectedPong.append("PONG ").append(field[1]);
} else if (field.length == 3) { // PING nonce serverLocation
rv = "PING " + field[1];
expectedPong.append("PONG ").append(field[1]);
expectedPong.append("PONG ").append(field[2]).append(" :").append(field[1]); // PONG serverLocation nonce
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")");
@ -415,28 +421,27 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
}
if (_log.shouldLog(Log.WARN))
_log.warn("sending ping " + rv + ", waiting for " + expectedPong + " orig was [" + s + "]");
_log.warn("sending ping [" + rv + "], waiting for [" + expectedPong + "] orig was [" + s + "]");
return rv;
}
if ("PONG".equals(command))
if ("PONG".equalsIgnoreCase(command))
return "PONG 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
// Allow all allowedCommands
for(int i=0;i<allowedCommands.length;i++)
{
if(allowedCommands[i].equals(command))
if(allowedCommands[i].equalsIgnoreCase(command))
return s;
}
// Allow PRIVMSG, but block CTCP (except ACTION).
if("PRIVMSG".equals(command))
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
{
String msg;
msg = field[2];
byte[] bytes = msg.getBytes();
if(bytes[1]==0x01)
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
{
// CTCP
msg=msg.substring(2);
@ -449,14 +454,14 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
return s;
}
if("USER".equals(command)) {
if("USER".equalsIgnoreCase(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)) {
} else if ("QUIT".equalsIgnoreCase(command)) {
return "QUIT :leaving";
}

View File

@ -190,6 +190,7 @@ public class IndexBean {
}
private String saveChanges() {
// Get current tunnel controller
TunnelController cur = getController(_tunnel);
Properties config = getConfig();
@ -205,21 +206,28 @@ public class IndexBean {
} else {
cur.setConfig(config, "");
}
if ("ircclient".equals(cur.getType()) ||
"httpclient".equals(cur.getType()) ||
"client".equals(cur.getType())) {
// all clients use the same I2CP session, and as such, use the same
// I2CP options
// Only modify other shared tunnels
// if the current tunnel is shared, and of supported type
if ("true".equalsIgnoreCase(cur.getSharedClient()) &&
("ircclient".equals(cur.getType()) ||
"httpclient".equals(cur.getType()) ||
"client".equals(cur.getType()))) {
// all clients use the same I2CP session, and as such, use the same I2CP options
List controllers = _group.getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelController c = (TunnelController)controllers.get(i);
// Current tunnel modified by user, skip
if (c == cur) continue;
//only change when they really are declared of beeing a sharedClient
if (("httpclient".equals(c.getType()) ||
"ircclient".equals(c.getType())||
"client".equals(c.getType())
) && "true".equalsIgnoreCase(c.getSharedClient())) {
// Only modify this non-current tunnel
// if it belongs to a shared destination, and is of supported type
if ("true".equalsIgnoreCase(c.getSharedClient()) &&
("httpclient".equals(c.getType()) ||
"ircclient".equals(c.getType()) ||
"client".equals(c.getType()))) {
Properties cOpt = c.getConfig("");
if (_tunnelQuantity != null) {
cOpt.setProperty("option.inbound.quantity", _tunnelQuantity);

View File

@ -3,29 +3,34 @@
<target name="all" depends="build" />
<target name="fetchJettylib" >
<available property="jetty.available" file="jetty-5.1.6.zip" />
<available property="jetty.zip.available" file="jetty-5.1.12.zip" type="file" />
<available property="jetty.zip.extracted" file="jettylib" type="dir" />
<ant target="doFetchJettylib" />
<ant target="doExtractJettylib" />
</target>
<target name="doFetchJettylib" unless="jetty.available" >
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.6" />
<target name="doFetchJettylib" unless="jetty.zip.available" >
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.12" />
<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.6.zip" verbose="true" dest="jetty-5.1.6.zip" />
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.12.zip" verbose="true" dest="jetty-5.1.12.zip" />
</target>
<target name="doExtractJettylib" unless="jetty.zip.extracted" >
<ant target="doExtract" />
</target>
<target name="doExtract">
<unzip src="jetty-5.1.6.zip" dest="." />
<unzip src="jetty-5.1.12.zip" dest="." />
<mkdir dir="jettylib" />
<copy todir="jettylib">
<fileset dir="jetty-5.1.6/lib">
<fileset dir="jetty-5.1.12/lib">
<include name="*.jar" />
</fileset>
</copy>
<copy todir="jettylib">
<fileset dir="jetty-5.1.6/ext">
<fileset dir="jetty-5.1.12/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" />
@ -33,9 +38,7 @@
<include name="xercesImpl.jar" />
</fileset>
</copy>
<!-- note the rename, to keep compat with old rev, since we only used the API anyway -->
<copy file="jetty-5.1.6/ext/commons-logging-api.jar" tofile="jettylib/commons-logging.jar" />
<delete dir="jetty-5.1.6" />
<delete dir="jetty-5.1.12" />
</target>
<target name="build" depends="fetchJettylib" />
<target name="builddep" />

View File

@ -138,8 +138,6 @@ public class I2PSocketManagerFactory {
I2PSession session = client.createSession(myPrivateKeyStream, opts);
session.connect();
I2PSocketManager sockMgr = createManager(session, opts, "manager");
if (sockMgr != null)
sockMgr.setDefaultOptions(sockMgr.buildOptions(opts));
return sockMgr;
} catch (I2PSessionException ise) {
_log.error("Error creating session for socket manager", ise);
@ -199,4 +197,4 @@ public class I2PSocketManagerFactory {
}
return i2cpPort;
}
}
}

View File

@ -37,8 +37,12 @@ public class ConfigNetHandler extends FormHandler {
private boolean _requireIntroductions;
private boolean _hiddenMode;
private boolean _dynamicKeys;
private String _ntcpHostname;
private String _ntcpPort;
private String _tcpPort;
private String _udpPort;
private boolean _ntcpAutoIP;
private boolean _ntcpAutoPort;
private String _inboundRate;
private String _inboundBurstRate;
private String _inboundBurst;
@ -51,9 +55,7 @@ public class ConfigNetHandler extends FormHandler {
private boolean _ratesOnly;
protected void processForm() {
if (_reseedRequested) {
reseed();
} else if (_saveRequested || ( (_action != null) && ("Save changes".equals(_action)) )) {
if (_saveRequested || ( (_action != null) && ("Save changes".equals(_action)) )) {
saveChanges();
} else if (_recheckReachabilityRequested) {
recheckReachability();
@ -62,7 +64,6 @@ public class ConfigNetHandler extends FormHandler {
}
}
public void setReseed(String moo) { _reseedRequested = true; }
public void setSave(String moo) { _saveRequested = true; }
public void setEnabletimesync(String moo) { _timeSyncEnabled = true; }
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
@ -71,6 +72,8 @@ public class ConfigNetHandler extends FormHandler {
public void setDynamicKeys(String moo) { _dynamicKeys = true; }
public void setUpdateratesonly(String moo) { _ratesOnly = true; }
public void setEnableloadtesting(String moo) { _enableLoadTesting = true; }
public void setNtcpAutoIP(String moo) { _ntcpAutoIP = true; }
public void setNtcpAutoPort(String moo) { _ntcpAutoPort = true; }
public void setHostname(String hostname) {
_hostname = (hostname != null ? hostname.trim() : null);
@ -78,6 +81,12 @@ public class ConfigNetHandler extends FormHandler {
public void setTcpPort(String port) {
_tcpPort = (port != null ? port.trim() : null);
}
public void setNtcphost(String host) {
_ntcpHostname = (host != null ? host.trim() : null);
}
public void setNtcpport(String port) {
_ntcpPort = (port != null ? port.trim() : null);
}
public void setUdpPort(String port) {
_udpPort = (port != null ? port.trim() : null);
}
@ -99,95 +108,9 @@ public class ConfigNetHandler extends FormHandler {
public void setOutboundburstfactor(String factor) {
_outboundBurst = (factor != null ? factor.trim() : null);
}
public void setReseedfrom(String url) {
_reseedFrom = (url != null ? url.trim() : null);
}
public void setSharePercentage(String pct) {
_sharePct = (pct != null ? pct.trim() : null);
}
private static final String DEFAULT_SEED_URL = ReseedHandler.DEFAULT_SEED_URL;
/**
* Reseed has been requested, so lets go ahead and do it. Fetch all of
* the routerInfo-*.dat files from the specified URL (or the default) and
* save them into this router's netDb dir.
*
*/
private void reseed() {
String seedURL = DEFAULT_SEED_URL;
if (_reseedFrom != null)
seedURL = _reseedFrom;
try {
URL dir = new URL(seedURL);
String content = new String(readURL(dir));
Set urls = new HashSet();
int cur = 0;
while (true) {
int start = content.indexOf("href=\"routerInfo-", cur);
if (start < 0)
break;
int end = content.indexOf(".dat\">", start);
String name = content.substring(start+"href=\"routerInfo-".length(), end);
urls.add(name);
cur = end + 1;
}
int fetched = 0;
int errors = 0;
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
try {
fetchSeed(seedURL, (String)iter.next());
fetched++;
} catch (Exception e) {
errors++;
}
}
addFormNotice("Reseeded with " + fetched + " peers (and " + errors + " failures)");
} catch (Throwable t) {
_context.logManager().getLog(ConfigNetHandler.class).error("Error reseeding", t);
addFormError("Error reseeding (RESEED_EXCEPTION)");
}
}
private void fetchSeed(String seedURL, String peer) throws Exception {
URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + "routerInfo-" + peer + ".dat");
byte data[] = readURL(url);
writeSeed(peer, data);
}
private byte[] readURL(URL url) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
URLConnection con = url.openConnection();
InputStream in = con.getInputStream();
byte buf[] = new byte[1024];
while (true) {
int read = in.read(buf);
if (read < 0)
break;
baos.write(buf, 0, read);
}
in.close();
return baos.toByteArray();
}
private void writeSeed(String name, byte data[]) throws Exception {
// props taken from KademliaNetworkDatabaseFacade...
String dirName = _context.getProperty("router.networkDatabase.dbDir", "netDb");
File netDbDir = new File(dirName);
if (!netDbDir.exists()) {
boolean ok = netDbDir.mkdirs();
if (ok)
addFormNotice("Network database directory created: " + dirName);
else
addFormNotice("Error creating network database directory: " + dirName);
}
FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat"));
fos.write(data);
fos.close();
}
private void recheckReachability() {
_context.commSystem().recheckReachability();
@ -222,6 +145,52 @@ public class ConfigNetHandler extends FormHandler {
restartRequired = true;
}
}
// Normalize some things to make the following code a little easier...
String oldNHost = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
if (oldNHost == null) oldNHost = "";
String oldNPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
if (oldNPort == null) oldNPort = "";
String sAutoHost = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_IP);
String sAutoPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_PORT);
boolean oldAutoHost = "true".equalsIgnoreCase(sAutoHost);
boolean oldAutoPort = "true".equalsIgnoreCase(sAutoPort);
if (_ntcpHostname == null) _ntcpHostname = "";
if (_ntcpPort == null) _ntcpPort = "";
if (oldAutoHost != _ntcpAutoIP || ! oldNHost.equalsIgnoreCase(_ntcpHostname)) {
if (_ntcpAutoIP) {
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_IP, "true");
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
addFormNotice("Updating inbound TCP address to auto");
} else if (_ntcpHostname.length() > 0) {
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME, _ntcpHostname);
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_IP);
addFormNotice("Updating inbound TCP address to " + _ntcpHostname);
} else {
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_IP);
addFormNotice("Disabling inbound TCP");
}
restartRequired = true;
}
if (oldAutoPort != _ntcpAutoPort || ! oldNPort.equals(_ntcpPort)) {
if ( _ntcpAutoPort ) {
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_PORT, "true");
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
addFormNotice("Updating inbound TCP port to auto");
} else if (_ntcpPort.length() > 0) {
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT, _ntcpPort);
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_PORT);
addFormNotice("Updating inbound TCP port to " + _ntcpPort);
} else {
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_PORT);
addFormNotice("Disabling inbound TCP");
}
restartRequired = true;
}
if ( (_udpPort != null) && (_udpPort.length() > 0) ) {
String oldPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_UDP_PORT);
if ( (oldPort == null) && (_udpPort.equals("8887")) ) {

View File

@ -48,6 +48,20 @@ public class ConfigNetHelper {
}
return "" + port;
}
public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoip";
public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoport";
public String getNtcphostname() {
String hostname = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME);
if (hostname == null) return "";
return hostname;
}
public String getNtcpport() {
String port = _context.getProperty(PROP_I2NP_NTCP_PORT);
if (port == null) return "";
return port;
}
public String getUdpAddress() {
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
@ -57,6 +71,26 @@ public class ConfigNetHelper {
return ua.toString();
}
public String getUdpIP() {
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
if (addr == null)
return "unknown";
UDPAddress ua = new UDPAddress(addr);
if (ua.getHost() == null)
return "unknown";
return ua.getHost();
}
public String getUdpPort() {
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
if (addr == null)
return "unknown";
UDPAddress ua = new UDPAddress(addr);
if (ua.getPort() <= 0)
return "unknown";
return "" + ua.getPort();
}
public String getEnableTimeSyncChecked() {
String disabled = _context.getProperty(Timestamper.PROP_DISABLED, "false");
if ( (disabled != null) && ("true".equalsIgnoreCase(disabled)) )
@ -81,6 +115,22 @@ public class ConfigNetHelper {
return "";
}
public String getTcpAutoPortChecked() {
String enabled = _context.getProperty(PROP_I2NP_NTCP_AUTO_PORT, "false");
if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) )
return " checked ";
else
return "";
}
public String getTcpAutoIPChecked() {
String enabled = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "false");
if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) )
return " checked ";
else
return "";
}
public String getRequireIntroductionsChecked() {
short status = _context.commSystem().getReachabilityStatus();
switch (status) {
@ -224,4 +274,23 @@ public class ConfigNetHelper {
buf.append("</select>\n");
return buf.toString();
}
public int getShareBandwidth() {
String irate = _context.getProperty(PROP_INBOUND_KBPS);
String orate = _context.getProperty(PROP_OUTBOUND_KBPS);
String pctStr = _context.getProperty(PROP_SHARE_PERCENTAGE);
if ( (irate != null) && (orate != null) && (pctStr != null)) {
try {
int irateKBps = Integer.parseInt(irate);
int orateKBps = Integer.parseInt(orate);
if (irateKBps < 0 || orateKBps < 0)
return 0;
int pct = Integer.parseInt(pctStr);
return (int) (((float) pct) * Math.min(irateKBps, orateKBps) / 100);
} catch (NumberFormatException nfe) {
// ignore
}
}
return 0;
}
}

View File

@ -17,6 +17,7 @@ import net.i2p.stat.StatManager;
public class ConfigStatsHandler extends FormHandler {
private String _filename;
private List _stats;
private String _graphs;
private boolean _explicitFilter;
private String _explicitFilterValue;
@ -48,6 +49,25 @@ public class ConfigStatsHandler extends FormHandler {
_log.debug("Updated stats: " + _stats);
}
public void setGraphList(String stats[]) {
if (stats != null) {
String s = "";
for (int i = 0; i < stats.length; i++) {
String cur = stats[i].trim();
if (cur.length() > 0) {
if (s.length() > 0)
s = s + ",";
s = s + cur;
}
}
_graphs = s;
} else {
_graphs = "";
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Updated graphs: " + _graphs);
}
public void setExplicitFilter(String foo) { _explicitFilter = true; }
public void setExplicitFilterValue(String filter) { _explicitFilterValue = filter; }
@ -88,11 +108,13 @@ public class ConfigStatsHandler extends FormHandler {
}
_context.router().setConfigSetting(StatManager.PROP_STAT_FILTER, stats.toString());
_context.router().setConfigSetting("stat.summaries", _graphs);
boolean ok = _context.router().saveConfig();
if (ok)
addFormNotice("Stat filter and location updated successfully to: " + stats.toString());
else
addFormError("Failed to update the stat filter and location");
addFormNotice("Graph list updated, may take up to 60s to be reflected here and on the <a href=\"graphs.jsp\">Graphs Page</a>");
}
}

View File

@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.stat.FrequencyStat;
import net.i2p.router.RouterContext;
@ -20,12 +21,15 @@ public class ConfigStatsHelper {
/** list of names of stats which are remaining, ordered by nested groups */
private List _stats;
private String _currentStatName;
private String _currentGraphName;
private String _currentStatDescription;
private String _currentGroup;
/** true if the current stat is the first in the group */
private boolean _currentIsFirstInGroup;
/** true if the stat is being logged */
private boolean _currentIsLogged;
private boolean _currentIsGraphed;
private boolean _currentCanBeGraphed;
/**
* Configure this bean to query a particular router context
@ -71,6 +75,7 @@ public class ConfigStatsHelper {
public boolean hasMoreStats() {
if (_stats.size() <= 0)
return false;
_currentIsGraphed = false;
_currentStatName = (String)_stats.remove(0);
RateStat rs = _context.statManager().getRate(_currentStatName);
if (rs != null) {
@ -82,6 +87,16 @@ public class ConfigStatsHelper {
else
_currentIsFirstInGroup = false;
_currentGroup = rs.getGroupName();
long period = rs.getPeriods()[0]; // should be the minimum
if (period <= 10*60*1000) {
Rate r = rs.getRate(period);
_currentCanBeGraphed = r != null;
if (_currentCanBeGraphed)
_currentIsGraphed = r.getSummaryListener() != null;
_currentGraphName = _currentStatName + "." + period;
} else {
_currentCanBeGraphed = false;
}
} else {
FrequencyStat fs = _context.statManager().getFrequency(_currentStatName);
if (fs != null) {
@ -93,6 +108,7 @@ public class ConfigStatsHelper {
else
_currentIsFirstInGroup = false;
_currentGroup = fs.getGroupName();
_currentCanBeGraphed = false;
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Stat does not exist?! [" + _currentStatName + "]");
@ -119,7 +135,10 @@ public class ConfigStatsHelper {
/** What group is the current stat in */
public String getCurrentGroupName() { return _currentGroup; }
public String getCurrentStatName() { return _currentStatName; }
public String getCurrentGraphName() { return _currentGraphName; }
public String getCurrentStatDescription() { return _currentStatDescription; }
public boolean getCurrentIsLogged() { return _currentIsLogged; }
public boolean getCurrentIsGraphed() { return _currentIsGraphed; }
public boolean getCurrentCanBeGraphed() { return _currentCanBeGraphed; }
public String getExplicitFilter() { return _filter; }
}

View File

@ -2,6 +2,7 @@ package net.i2p.router.web;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.router.Router;
import net.i2p.router.web.ConfigServiceHandler.UpdateWrapperManagerTask;
import net.i2p.util.Log;
@ -20,20 +21,29 @@ public class ConfigUpdateHandler extends FormHandler {
private String _trustedKeys;
public static final String PROP_NEWS_URL = "router.newsURL";
public static final String DEFAULT_NEWS_URL = "http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/news.xml?rev=HEAD";
// public static final String DEFAULT_NEWS_URL = "http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/news.xml?rev=HEAD";
public static final String DEFAULT_NEWS_URL = "http://complication.i2p/news.xml";
public static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
public static final String DEFAULT_REFRESH_FREQUENCY = 12*60*60*1000 + "";
public static final String PROP_UPDATE_URL = "router.updateURL";
public static final String DEFAULT_UPDATE_URL = "http://dev.i2p.net/i2p/i2pupdate.sud";
public static final String PROP_UPDATE_POLICY = "router.updatePolicy";
public static final String DEFAULT_UPDATE_POLICY = "notify";
public static final String PROP_SHOULD_PROXY = "router.updateThroughProxy";
public static final String DEFAULT_SHOULD_PROXY = Boolean.FALSE.toString();
public static final String DEFAULT_SHOULD_PROXY = Boolean.TRUE.toString();
public static final String PROP_PROXY_HOST = "router.updateProxyHost";
public static final String DEFAULT_PROXY_HOST = "localhost";
public static final String PROP_PROXY_PORT = "router.updateProxyPort";
public static final String DEFAULT_PROXY_PORT = "4444";
public static final String PROP_UPDATE_URL = "router.updateURL";
// public static final String DEFAULT_UPDATE_URL = "http://dev.i2p.net/i2p/i2pupdate.sud";
public static final String DEFAULT_UPDATE_URL =
"http://amiga.i2p/i2p/i2pupdate.sud\r\n" +
"http://stats.i2p/i2p/i2pupdate.sud\r\n" +
"http://complication.i2p/i2p/i2pupdate.sud";
public static final String PROP_TRUSTED_KEYS = "router.trustedUpdateKeys";
protected void processForm() {
if ("Check for update now".equals(_action)) {
NewsFetcher fetcher = NewsFetcher.getInstance(I2PAppContext.getGlobalContext());
@ -52,14 +62,6 @@ public class ConfigUpdateHandler extends FormHandler {
}
}
if ( (_updateURL != null) && (_updateURL.length() > 0) ) {
String oldURL = _context.router().getConfigSetting(PROP_UPDATE_URL);
if ( (oldURL == null) || (!_updateURL.equals(oldURL)) ) {
_context.router().setConfigSetting(PROP_UPDATE_URL, _updateURL);
addFormNotice("Updating update URL to " + _updateURL);
}
}
if ( (_proxyHost != null) && (_proxyHost.length() > 0) ) {
String oldHost = _context.router().getConfigSetting(PROP_PROXY_HOST);
if ( (oldHost == null) || (!_proxyHost.equals(oldHost)) ) {
@ -98,8 +100,22 @@ public class ConfigUpdateHandler extends FormHandler {
addFormNotice("Updating update policy to " + _updatePolicy);
}
}
// should save the keys...
if ( (_updateURL != null) && (_updateURL.length() > 0) ) {
String oldURL = _context.router().getConfigSetting(PROP_UPDATE_URL);
if ( (oldURL == null) || (!_updateURL.equals(oldURL)) ) {
_context.router().setConfigSetting(PROP_UPDATE_URL, _updateURL);
addFormNotice("Updating update URLs.");
}
}
if ( (_trustedKeys != null) && (_trustedKeys.length() > 0) ) {
String oldKeys = new TrustedUpdate(_context).getTrustedKeysString();
if ( (oldKeys == null) || (!_trustedKeys.equals(oldKeys)) ) {
_context.router().setConfigSetting(PROP_TRUSTED_KEYS, _trustedKeys);
addFormNotice("Updating trusted keys.");
}
}
_context.router().saveConfig();
}

View File

@ -1,6 +1,5 @@
package net.i2p.router.web;
import java.util.List;
import net.i2p.data.DataHelper;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.router.RouterContext;
@ -113,11 +112,6 @@ public class ConfigUpdateHelper {
}
public String getTrustedKeys() {
StringBuffer buf = new StringBuffer(1024);
TrustedUpdate up = new TrustedUpdate(_context);
List keys = up.getTrustedKeys();
for (int i = 0; i < keys.size(); i++)
buf.append((String)keys.get(i)).append('\n');
return buf.toString();
return new TrustedUpdate(_context).getTrustedKeysString();
}
}

View File

@ -55,20 +55,37 @@ public class GraphHelper {
public String getImages() {
try {
_out.write("<img src=\"viewstat.jsp?stat=bw.combined"
+ "&amp;periodCount=" + _periodCount
+ "&amp;width=" + _width
+ "&amp;height=" + _height
+ "\" title=\"Combined bandwidth graph\" />\n");
List listeners = StatSummarizer.instance().getListeners();
TreeSet ordered = new TreeSet(new AlphaComparator());
ordered.addAll(listeners);
// go to some trouble to see if we have the data for the combined bw graph
boolean hasTx = false;
boolean hasRx = false;
for (Iterator iter = ordered.iterator(); iter.hasNext(); ) {
SummaryListener lsnr = (SummaryListener)iter.next();
String title = lsnr.getRate().getRateStat().getName();
if (title.equals("bw.sendRate")) hasTx = true;
else if (title.equals("bw.recvRate")) hasRx = true;
}
if (hasTx && hasRx && !_showEvents)
_out.write("<img width=\""
+ (_width + 83) + "\" height=\"" + (_height + 92)
+ "\" src=\"viewstat.jsp?stat=bw.combined"
+ "&amp;periodCount=" + _periodCount
+ "&amp;width=" + _width
+ "&amp;height=" + (_height - 14)
+ "\" title=\"Combined bandwidth graph\" />\n");
for (Iterator iter = ordered.iterator(); iter.hasNext(); ) {
SummaryListener lsnr = (SummaryListener)iter.next();
Rate r = lsnr.getRate();
String title = r.getRateStat().getName() + " for " + DataHelper.formatDuration(_periodCount * r.getPeriod());
_out.write("<img src=\"viewstat.jsp?stat=" + r.getRateStat().getName()
_out.write("<img width=\""
+ (_width + 83) + "\" height=\"" + (_height + 92)
+ "\" src=\"viewstat.jsp?stat="
+ r.getRateStat().getName()
+ "&amp;showEvents=" + _showEvents
+ "&amp;period=" + r.getPeriod()
+ "&amp;periodCount=" + _periodCount
@ -86,6 +103,7 @@ public class GraphHelper {
}
public String getForm() {
try {
_out.write("<p /><a href=\"configstats.jsp\">Select Stats to Graph</a><p />");
_out.write("<form action=\"graphs.jsp\" method=\"GET\">");
_out.write("Periods: <input size=\"3\" type=\"text\" name=\"periodCount\" value=\"" + _periodCount + "\" /><br />\n");
_out.write("Plot averages: <input type=\"radio\" name=\"showEvents\" value=\"false\" " + (_showEvents ? "" : "checked=\"true\" ") + " /> ");
@ -119,4 +137,4 @@ class AlphaComparator implements Comparator {
String rName = r.getRate().getRateStat().getName() + "." + r.getRate().getPeriod();
return lName.compareTo(rName);
}
}
}

View File

@ -24,6 +24,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
private Log _log;
private boolean _updateAvailable;
private long _lastFetch;
private String _lastModified;
private static NewsFetcher _instance;
//public static final synchronized NewsFetcher getInstance() { return _instance; }
public static final synchronized NewsFetcher getInstance(I2PAppContext ctx) {
@ -105,11 +106,12 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
proxyPort = Integer.parseInt(port);
EepGet get = null;
if (shouldProxy)
get = new EepGet(_context, proxyHost, proxyPort, 10, TEMP_NEWS_FILE, newsURL);
get = new EepGet(_context, true, proxyHost, proxyPort, 2, TEMP_NEWS_FILE, newsURL, true, null, _lastModified);
else
get = new EepGet(_context, 10, TEMP_NEWS_FILE, newsURL);
get = new EepGet(_context, false, null, 0, 0, TEMP_NEWS_FILE, newsURL, true, null, _lastModified);
get.addStatusListener(this);
get.fetch();
if (get.fetch())
_lastModified = get.getLastModified();
} catch (Throwable t) {
_log.error("Error fetching the news", t);
}
@ -212,17 +214,18 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
_log.error("Failed to copy the news file!");
}
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Transfer complete, but no file?");
if (_log.shouldLog(Log.WARN))
_log.warn("Transfer complete, but no file? - probably 304 Not Modified");
}
checkForUpdates();
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
if (_log.shouldLog(Log.ERROR))
_log.error("Failed to fetch the news from " + url);
if (_log.shouldLog(Log.WARN))
_log.warn("Failed to fetch the news from " + url);
File temp = new File(TEMP_NEWS_FILE);
temp.delete();
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
public void attempting(String url) {}
}

View File

@ -3,7 +3,6 @@ package net.i2p.router.web;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
@ -13,17 +12,51 @@ import java.util.Iterator;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
import net.i2p.util.EepGet;
/**
* Handler to deal with reseed requests. This reseed from the URL
* http://dev.i2p.net/i2pdb2/ unless the java env property "i2p.reseedURL" is
* Handler to deal with reseed requests. This will reseed from the URL
* http://i2pdb.tin0.de/netDb/ unless the I2P configuration property "i2p.reseedURL" is
* set. It always writes to ./netDb/, so don't mess with that.
*
*/
public class ReseedHandler {
private static ReseedRunner _reseedRunner = new ReseedRunner();
private static ReseedRunner _reseedRunner;
private RouterContext _context;
private Log _log;
// Reject unreasonably big files, because we download into a ByteArrayOutputStream.
private static final long MAX_RESEED_RESPONSE_SIZE = 8 * 1024 * 1024;
private static final String DEFAULT_SEED_URL = "http://i2pdb.tin0.de/netDb/";
public ReseedHandler() {
this(ContextHelper.getContext(null));
}
public ReseedHandler(RouterContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(ReseedHandler.class);
}
/**
* 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);
_log = _context.logManager().getLog(ReseedHandler.class);
} catch (Throwable t) {
t.printStackTrace();
}
}
public void setReseedNonce(String nonce) {
if (nonce == null) return;
if (nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.nonce")) ||
@ -32,22 +65,28 @@ public class ReseedHandler {
}
}
public static void requestReseed() {
synchronized (_reseedRunner) {
public void requestReseed() {
synchronized (ReseedHandler.class) {
if (_reseedRunner == null)
_reseedRunner = new 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();
}
}
}
public static class ReseedRunner implements Runnable {
public class ReseedRunner implements Runnable, EepGet.StatusListener {
private boolean _isRunning;
public ReseedRunner() { _isRunning = false; }
public ReseedRunner() {
_isRunning = false;
System.setProperty("net.i2p.router.web.ReseedHandler.statusMessage","Reseeding.");
}
public boolean isRunning() { return _isRunning; }
public void run() {
_isRunning = true;
@ -56,141 +95,159 @@ public class ReseedHandler {
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false");
_isRunning = false;
}
}
static final String DEFAULT_SEED_URL = "http://dev.i2p.net/i2pdb2/";
/**
* Reseed has been requested, so lets go ahead and do it. Fetch all of
* the routerInfo-*.dat files from the specified URL (or the default) and
* save them into this router's netDb dir.
*
*/
private static void reseed(boolean echoStatus) {
String seedURL = System.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
seedURL = DEFAULT_SEED_URL;
try {
URL dir = new URL(seedURL);
byte contentRaw[] = readURL(dir);
if (contentRaw == null) return;
String content = new String(contentRaw);
Set urls = new HashSet();
int cur = 0;
while (true) {
int start = content.indexOf("href=\"routerInfo-", cur);
if (start < 0)
break;
int end = content.indexOf(".dat\">", start);
String name = content.substring(start+"href=\"routerInfo-".length(), end);
urls.add(name);
cur = end + 1;
}
// EepGet status listeners
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
// Since readURL() runs an EepGet with 0 retries,
// we can report errors with attemptFailed() instead of transferFailed().
// It has the benefit of providing cause of failure, which helps resolve issues.
if (_log.shouldLog(Log.ERROR)) _log.error("EepGet failed on " + url, cause);
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {}
public void headerReceived(String url, int attemptNum, String key, String val) {}
public void attempting(String url) {}
// End of EepGet status listeners
int fetched = 0;
int errors = 0;
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
try {
fetchSeed(seedURL, (String)iter.next());
fetched++;
if (echoStatus) {
System.out.print(".");
if (fetched % 60 == 0)
System.out.println();
}
} catch (Exception e) {
errors++;
/**
* Reseed has been requested, so lets go ahead and do it. Fetch all of
* the routerInfo-*.dat files from the specified URL (or the default) and
* save them into this router's netDb dir.
*
*/
private static final String RESEED_TIPS =
"Ensure that nothing blocks outbound HTTP, check <a href=logs.jsp>logs</a> " +
"and if nothing helps, read FAQ about reseeding manually.";
private void reseed(boolean echoStatus) {
String seedURL = _context.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
seedURL = DEFAULT_SEED_URL;
try {
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage","");
System.setProperty("net.i2p.router.web.ReseedHandler.statusMessage","Reseeding: fetching seed URL.");
URL dir = new URL(seedURL);
byte contentRaw[] = readURL(dir);
if (contentRaw == null) {
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage",
"Last reseed failed fully (failed reading seed URL). " +
RESEED_TIPS);
// Logging deprecated here since attemptFailed() provides better info
_log.debug("Failed reading seed URL: " + seedURL);
return;
}
}
if (echoStatus) System.out.println();
} catch (Throwable t) {
I2PAppContext.getGlobalContext().logManager().getLog(ReseedHandler.class).error("Error reseeding", t);
}
}
private static void fetchSeed(String seedURL, String peer) throws Exception {
URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + "routerInfo-" + peer + ".dat");
String content = new String(contentRaw);
Set urls = new HashSet();
int cur = 0;
while (true) {
int start = content.indexOf("href=\"routerInfo-", cur);
if (start < 0)
break;
byte data[] = readURL(url);
//System.out.println("read: " + (data != null ? data.length : -1));
writeSeed(peer, data);
}
private static byte[] readURL(URL url) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024);
String hostname = url.getHost();
int port = url.getPort();
if (port < 0)
port = 80;
Socket s = new Socket(hostname, port);
OutputStream out = s.getOutputStream();
InputStream in = s.getInputStream();
String request = getRequest(url);
//System.out.println("Sending to " + hostname +":"+ port + ": " + request);
out.write(request.getBytes());
out.flush();
// skip the HTTP response headers
// (if we were smart, we'd check for HTTP 200, content-length, etc)
int consecutiveNL = 0;
while (true) {
int cur = in.read();
switch (cur) {
case -1:
return null;
case '\n':
case '\r':
consecutiveNL++;
break;
default:
consecutiveNL = 0;
int end = content.indexOf(".dat\">", start);
String name = content.substring(start+"href=\"routerInfo-".length(), end);
urls.add(name);
cur = end + 1;
}
if (urls.size() <= 0) {
_log.error("Read " + contentRaw.length + " bytes from seed " + seedURL + ", but found no routerInfo URLs.");
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage",
"Last reseed failed fully (no routerInfo URLs at seed URL). " +
RESEED_TIPS);
return;
}
int fetched = 0;
int errors = 0;
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
try {
System.setProperty("net.i2p.router.web.ReseedHandler.statusMessage",
"Reseeding: fetching router info from seed URL (" +
fetched + " successful, " + errors + " errors, " + urls.size() + " total).");
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();
int failPercent = 100 * errors / urls.size();
// Less than 10% of failures is considered success,
// because some routerInfos will always fail.
if ((failPercent >= 10) && (failPercent < 90)) {
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage",
"Last reseed failed partly (" + failPercent + "% of " + urls.size() + "). " +
RESEED_TIPS);
}
if (failPercent >= 90) {
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage",
"Last reseed failed (" + failPercent + "% of " + urls.size() + "). " +
RESEED_TIPS);
}
} catch (Throwable t) {
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage",
"Last reseed failed fully (exception caught). " +
RESEED_TIPS);
_log.error("Error reseeding", t);
}
if (consecutiveNL == 4)
break;
}
// ok, past the headers, grab the goods
byte buf[] = new byte[1024];
while (true) {
int read = in.read(buf);
if (read < 0)
break;
baos.write(buf, 0, read);
/* Since we don't return a value, we should always throw an exception if something fails. */
private void fetchSeed(String seedURL, String peer) throws Exception {
Log log = I2PAppContext.getGlobalContext().logManager().getLog(ReseedHandler.class);
URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + "routerInfo-" + peer + ".dat");
byte data[] = readURL(url);
if (data == null) {
// Logging deprecated here since attemptFailed() provides better info
_log.debug("Failed fetching seed: " + url.toString());
throw new Exception ("Failed fetching seed.");
}
//System.out.println("read: " + (data != null ? data.length : -1));
writeSeed(peer, data);
}
in.close();
s.close();
return baos.toByteArray();
}
private static String getRequest(URL url) {
StringBuffer buf = new StringBuffer(512);
String path = url.getPath();
if ("".equals(path))
path = "/";
buf.append("GET ").append(path).append(" HTTP/1.0\n");
buf.append("Host: ").append(url.getHost());
int port = url.getPort();
if ( (port > 0) && (port != 80) )
buf.append(":").append(port);
buf.append("\nConnection: close\n\n");
return buf.toString();
}
private static void writeSeed(String name, byte data[]) throws Exception {
String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb");
File netDbDir = new File(dirName);
if (!netDbDir.exists()) {
boolean ok = netDbDir.mkdirs();
private byte[] readURL(URL url) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024);
// Do a non-proxied eepget into our ByteArrayOutputStream with 0 retries
EepGet get = new EepGet( I2PAppContext.getGlobalContext(), false, null, -1, 0, 0, MAX_RESEED_RESPONSE_SIZE,
null, baos, url.toString(), false, null, null);
get.addStatusListener(ReseedRunner.this);
if (get.fetch()) return baos.toByteArray(); else return null;
}
FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat"));
fos.write(data);
fos.close();
}
private void writeSeed(String name, byte data[]) throws Exception {
String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb");
File netDbDir = new File(dirName);
if (!netDbDir.exists()) {
boolean ok = netDbDir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat"));
fos.write(data);
fos.close();
}
}
public static void main(String args[]) {
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);
ReseedHandler reseedHandler = new ReseedHandler();
reseedHandler.requestReseed();
}
}

View File

@ -88,7 +88,8 @@ public class RouterConsoleRunner {
// 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();
ReseedHandler reseedHandler = new ReseedHandler();
reseedHandler.requestReseed();
}
}

View File

@ -45,23 +45,27 @@ public class StatSummarizer implements Runnable {
private static final String DEFAULT_DATABASES = "bw.sendRate.60000" +
",bw.recvRate.60000" +
",tunnel.testSuccessTime.60000" +
",udp.outboundActiveCount.60000" +
",udp.receivePacketSize.60000" +
",udp.receivePacketSkew.60000" +
",udp.sendConfirmTime.60000" +
",udp.sendPacketSize.60000" +
",router.activePeers.60000" +
",router.activeSendPeers.60000" +
",tunnel.acceptLoad.60000" +
",tunnel.dropLoadProactive.60000" +
",tunnel.buildExploratorySuccess.60000" +
",tunnel.buildExploratoryReject.60000" +
",tunnel.buildExploratoryExpire.60000" +
",client.sendAckTime.60000" +
",client.dispatchNoACK.60000" +
",transport.sendMessageFailureLifetime.60000" +
",transport.sendProcessingTime.60000";
// ",tunnel.testSuccessTime.60000" +
// ",udp.outboundActiveCount.60000" +
// ",udp.receivePacketSize.60000" +
// ",udp.receivePacketSkew.60000" +
// ",udp.sendConfirmTime.60000" +
// ",udp.sendPacketSize.60000" +
",router.activePeers.60000";
// ",router.activeSendPeers.60000" +
// ",tunnel.acceptLoad.60000" +
// ",tunnel.dropLoadProactive.60000" +
// ",tunnel.buildExploratorySuccess.60000" +
// ",tunnel.buildExploratoryReject.60000" +
// ",tunnel.buildExploratoryExpire.60000" +
// ",client.sendAckTime.60000" +
// ",client.dispatchNoACK.60000" +
// ",ntcp.sendTime.60000" +
// ",ntcp.transmitTime.60000" +
// ",ntcp.sendBacklogTime.60000" +
// ",ntcp.receiveTime.60000" +
// ",transport.sendMessageFailureLifetime.60000" +
// ",transport.sendProcessingTime.60000";
private String adjustDatabases(String oldSpecs) {
String spec = _context.getProperty("stat.summaries", DEFAULT_DATABASES);
@ -138,16 +142,17 @@ public class StatSummarizer implements Runnable {
}
public boolean renderRatePng(OutputStream out, int width, int height, boolean hideLegend, boolean hideGrid, boolean hideTitle, boolean showEvents, int periodCount, boolean showCredit) throws IOException {
long end = _context.clock().now();
long end = _context.clock().now() - 60*1000;
if (periodCount <= 0) periodCount = SummaryListener.PERIODS;
if (periodCount > SummaryListener.PERIODS)
periodCount = SummaryListener.PERIODS;
long period = 60*1000;
long start = end - period*periodCount;
long begin = System.currentTimeMillis();
//long begin = System.currentTimeMillis();
try {
RrdGraphDef def = new RrdGraphDef();
def.setTimePeriod(start/1000, end/1000);
def.setTimePeriod(start/1000, 0);
def.setBaseValue(1024);
String title = "Bandwidth usage";
if (!hideTitle)
def.setTitle(title);
@ -155,15 +160,15 @@ public class StatSummarizer implements Runnable {
String recvName = SummaryListener.createName(_context, "bw.recvRate.60000");
def.datasource(sendName, sendName, sendName, "AVERAGE", "MEMORY");
def.datasource(recvName, recvName, recvName, "AVERAGE", "MEMORY");
def.area(sendName, Color.BLUE, "Outbound bytes/second");
//def.line(sendName, Color.BLUE, "Outbound bytes/second", 3);
//def.line(recvName, Color.RED, "Inbound bytes/second@r", 3);
def.area(recvName, Color.RED, "Inbound bytes/second@r");
def.area(sendName, Color.BLUE, "Outbound bytes/sec");
//def.line(sendName, Color.BLUE, "Outbound bytes/sec", 3);
def.line(recvName, Color.RED, "Inbound bytes/sec@r", 3);
//def.area(recvName, Color.RED, "Inbound bytes/sec@r");
if (!hideLegend) {
def.gprint(sendName, "AVERAGE", "outbound average: @2@sbytes/second");
def.gprint(sendName, "MAX", " max: @2@sbytes/second@r");
def.gprint(recvName, "AVERAGE", "inbound average: @2bytes/second@s");
def.gprint(recvName, "MAX", " max: @2@sbytes/second@r");
def.gprint(sendName, "AVERAGE", "out average: @2@sbytes/sec");
def.gprint(sendName, "MAX", " max: @2@sbytes/sec@r");
def.gprint(recvName, "AVERAGE", "in average: @2@sbytes/sec");
def.gprint(recvName, "MAX", " max: @2@sbytes/sec@r");
}
if (!showCredit)
def.setShowSignature(false);
@ -184,7 +189,7 @@ public class StatSummarizer implements Runnable {
data = graph.getPNGBytes();
else
data = graph.getPNGBytes(width, height);
long timeToPlot = System.currentTimeMillis() - begin;
//long timeToPlot = System.currentTimeMillis() - begin;
out.write(data);
//File t = File.createTempFile("jrobinData", ".xml");
//_listener.getData().dumpXml(new FileOutputStream(t));
@ -197,6 +202,9 @@ public class StatSummarizer implements Runnable {
} catch (IOException ioe) {
_log.error("Error rendering", ioe);
throw ioe;
} catch (OutOfMemoryError oom) {
_log.error("Error rendering", oom);
throw new IOException("Error plotting: " + oom.getMessage());
}
}

View File

@ -107,7 +107,8 @@ public class SummaryHelper {
}
public boolean allowReseed() {
return (_context.netDb().getKnownRouters() < 30);
return (_context.netDb().getKnownRouters() < 30) ||
Boolean.valueOf(_context.getProperty("i2p.alwaysAllowReseed", "false")).booleanValue();
}
public int getAllPeers() { return _context.netDb().getKnownRouters(); }

View File

@ -154,7 +154,7 @@ class SummaryRenderer {
*
*/
public static synchronized void render(I2PAppContext ctx, OutputStream out, String filename) throws IOException {
long end = ctx.clock().now();
long end = ctx.clock().now() - 60*1000;
long start = end - 60*1000*SummaryListener.PERIODS;
long begin = System.currentTimeMillis();
try {
@ -174,17 +174,26 @@ class SummaryRenderer {
}
public void render(OutputStream out) throws IOException { render(out, -1, -1, false, false, false, false, -1, true); }
public void render(OutputStream out, int width, int height, boolean hideLegend, boolean hideGrid, boolean hideTitle, boolean showEvents, int periodCount, boolean showCredit) throws IOException {
long end = _listener.now();
long end = _listener.now() - 60*1000;
if (periodCount <= 0) periodCount = SummaryListener.PERIODS;
if (periodCount > SummaryListener.PERIODS)
periodCount = SummaryListener.PERIODS;
long start = end - _listener.getRate().getPeriod()*periodCount;
long begin = System.currentTimeMillis();
//long begin = System.currentTimeMillis();
try {
RrdGraphDef def = new RrdGraphDef();
def.setTimePeriod(start/1000, end/1000);
String title = _listener.getRate().getRateStat().getName() + " averaged for "
+ DataHelper.formatDuration(_listener.getRate().getPeriod());
def.setTimePeriod(start/1000, 0);
String name = _listener.getRate().getRateStat().getName();
// heuristic to set K=1024
if ((name.startsWith("bw.") || name.indexOf("Size") >= 0 || name.indexOf("Bps") >= 0)
&& !showEvents)
def.setBaseValue(1024);
String title = name;
if (showEvents)
title = title + " events in ";
else
title = title + " averaged for ";
title = title + DataHelper.formatDuration(_listener.getRate().getPeriod());
if (!hideTitle)
def.setTitle(title);
String path = _listener.getData().getPath();
@ -203,8 +212,9 @@ class SummaryRenderer {
def.datasource(plotName, path, plotName, "AVERAGE", "MEMORY");
def.area(plotName, Color.BLUE, descr + "@r");
if (!hideLegend) {
def.gprint(plotName, "AVERAGE", "average: @2@s");
def.gprint(plotName, "MAX", " max: @2@s@r");
def.gprint(plotName, "AVERAGE", "avg: @2@s");
def.gprint(plotName, "MAX", " max: @2@s");
def.gprint(plotName, "LAST", " now: @2@s@r");
}
if (!showCredit)
def.setShowSignature(false);
@ -233,7 +243,7 @@ class SummaryRenderer {
data = graph.getPNGBytes();
else
data = graph.getPNGBytes(width, height);
long timeToPlot = System.currentTimeMillis() - begin;
//long timeToPlot = System.currentTimeMillis() - begin;
out.write(data);
//File t = File.createTempFile("jrobinData", ".xml");
//_listener.getData().dumpXml(new FileOutputStream(t));
@ -245,6 +255,9 @@ class SummaryRenderer {
} catch (IOException ioe) {
_log.error("Error rendering", ioe);
throw ioe;
} catch (OutOfMemoryError oom) {
_log.error("Error rendering", oom);
throw new IOException("Error plotting: " + oom.getMessage());
}
}
}

View File

@ -2,6 +2,9 @@ package net.i2p.router.web;
import java.io.File;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.crypto.TrustedUpdate;
@ -102,7 +105,9 @@ public class UpdateHandler {
private void update() {
_startedOn = -1;
_status = "<b>Updating</b><br />";
String updateURL = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL, ConfigUpdateHandler.DEFAULT_UPDATE_URL);
String updateURL = selectUpdateURL();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Selected update URL: " + updateURL);
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
String port = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT);
@ -162,17 +167,35 @@ public class UpdateHandler {
}
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
_log.log(Log.CRIT, "Update did not download completely (" + bytesTransferred + " with "
_log.log(Log.CRIT, "Update from " + url + " did not download completely (" + bytesTransferred + " with "
+ bytesRemaining + " after " + currentAttempt + " tries)");
_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) {}
public void attempting(String url) {}
}
private void restart() {
_context.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
}
private String selectUpdateURL() {
String URLs = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL, ConfigUpdateHandler.DEFAULT_UPDATE_URL);
StringTokenizer tok = new StringTokenizer(URLs, " ,\r\n");
List URLList = new ArrayList();
while (tok.hasMoreTokens())
URLList.add(tok.nextToken().trim());
int size = URLList.size();
_log.log(Log.DEBUG, "Picking update source from " + size + " candidates.");
if (size <= 0) {
_log.log(Log.WARN, "Update list is empty - no update available");
return null;
}
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
_log.log(Log.DEBUG, "Picked update source " + index + ".");
return (String) URLList.get(index);
}
}

View File

@ -43,9 +43,21 @@
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<br />
<% int share = nethelper.getShareBandwidth();
if (share < 12) {
out.print("<b>NOTE</b>: You have configured I2P to share only " + share + "KBps. ");
out.print("I2P requires at least 12KBps to enable sharing. ");
out.print("Please enable sharing (participating in tunnels) by configuring more bandwidth. ");
out.print("It improves your anonymity by creating cover traffic, and helps the network.<br />");
} else {
out.print("You have configured I2P to share " + share + "KBps. ");
out.print("The higher the share bandwidth the more you improve your anonymity and help the network.<br />");
}
%>
<p>
<input type="submit" name="save" value="Save changes" /> <input type="reset" value="Cancel" /><br />
<hr />
<!--
<b>Enable load testing: </b>
<input type="checkbox" name="enableloadtesting" value="true" <jsp:getProperty name="nethelper" property="enableLoadTesting" /> />
<p>If enabled, your router will periodically anonymously probe some of your peers
@ -54,6 +66,7 @@
load testing is fed into the profiles as well as the
<a href="oldstats.jsp#test.rtt">test.rtt</a> and related stats.</p>
<hr />
-->
<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 />
@ -66,6 +79,28 @@
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>Inbound TCP connection configuration:</b><br />
Externally reachable hostname or IP address:
<input name ="ntcphost" type="text" size="16" value="<jsp:getProperty name="nethelper" property="ntcphostname" />" />
(dyndns and the like are fine)<br />
OR use IP address detected by SSU
(currently <jsp:getProperty name="nethelper" property="udpIP" />)?
<input type="checkbox" name="ntcpAutoIP" value="true" <jsp:getProperty name="nethelper" property="tcpAutoIPChecked" /> /><br />
<p>
Externally reachable TCP port:
<input name ="ntcpport" type="text" size="6" value="<jsp:getProperty name="nethelper" property="ntcpport" />" /><br />
OR use the same port configured for SSU
(currently <jsp:getProperty name="nethelper" property="udpPort" />)?
<input type="checkbox" name="ntcpAutoPort" value="true" <jsp:getProperty name="nethelper" property="tcpAutoPortChecked" /> /><br />
<p>You do <i>not</i> need to allow inbound TCP connections - outbound connections work with no
configuration. However, if you want to receive inbound TCP connections, you <b>must</b> poke a hole
in your NAT or firewall for unsolicited TCP connections. If you specify the wrong IP address or
hostname, or do not properly configure your NAT or firewall, your network performance will degrade
substantially. When in doubt, leave the hostname and port number blank.</p>
<p><b>Note: changing any of these settings will terminate all of your connections and effectively
restart your router.</b>
<hr />
<!--
<b>Dynamic Router Keys: </b>
<input type="checkbox" name="dynamicKeys" value="true" <jsp:getProperty name="nethelper" property="dynamicKeysChecked" /> /><br />
<p>
@ -83,6 +118,7 @@
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 />
</form>
</div>

View File

@ -78,22 +78,26 @@ function toggleAll(category)
<table>
<% while (statshelper.hasMoreStats()) {
while (statshelper.groupRequired()) { %>
<tr><td valign="top" align="left" colspan="2">
<tr><td valign="top" align="left" colspan="3">
<b><%=statshelper.getCurrentGroupName()%></b>
(<a href="javascript: void(null);" onclick="toggleAll('<%=statshelper.getCurrentGroupName()%>')">toggle all</a>)
</td></tr><%
</td></tr><tr><td>Log</td><td>Graph</td><td></td></tr><%
} // end iterating over required groups for the current stat %>
<tr><td valign="top" align="left">
<input id="<%=statshelper.getCurrentGroupName()%>" type="checkbox" name="statList" value="<%=statshelper.getCurrentStatName()%>" <%
if (statshelper.getCurrentIsLogged()) { %>checked="true" <% } %>/></td>
<td valign="top" align="left">
<% if (statshelper.getCurrentCanBeGraphed()) { %>
<input id="<%=statshelper.getCurrentGroupName()%>" type="checkbox" name="graphList" value="<%=statshelper.getCurrentGraphName()%>" <%
if (statshelper.getCurrentIsGraphed()) { %>checked="true" <% } %>/><% } %></td>
<td valign="top" align="left"><b><%=statshelper.getCurrentStatName()%>:</b><br />
<%=statshelper.getCurrentStatDescription()%></td></tr><%
} // end iterating over all stats %>
<tr><td colspan="2"><hr /></td></tr>
<tr><td colspan="3"><hr /></td></tr>
<tr><td><input type="checkbox" name="explicitFilter" /></td>
<td>Advanced filter:
<td colspan="2">Advanced filter:
<input type="text" name="explicitFilterValue" value="<%=statshelper.getExplicitFilter()%>" size="40" /></td></tr>
<tr><td colspan="2"><hr /></td></tr>
<tr><td colspan="3"><hr /></td></tr>
<tr><td><input type="submit" name="shouldsave" value="Save changes" /> </td>
<td><input type="reset" value="Cancel" /></td></tr>
</form>
@ -101,4 +105,4 @@ function toggleAll(category)
</div>
</body>
</html>
</html>

View File

@ -32,17 +32,17 @@
<input type="text" size="60" name="newsURL" value="<jsp:getProperty name="updatehelper" property="newsURL" />"><br />
Refresh frequency:
<jsp:getProperty name="updatehelper" property="refreshFrequencySelectBox" /><br />
Update URL:
<input type="text" size="60" name="updateURL" value="<jsp:getProperty name="updatehelper" property="updateURL" />"><br />
Update policy:
<jsp:getProperty name="updatehelper" property="updatePolicySelectBox" /><br />
Update through the eepProxy?
<p>Update through the eepProxy?
<jsp:getProperty name="updatehelper" property="updateThroughProxy" /><br />
eepProxy host: <input type="text" size="10" name="proxyHost" value="<jsp:getProperty name="updatehelper" property="proxyHost" />" /><br />
eepProxy port: <input type="text" size="4" name="proxyPort" value="<jsp:getProperty name="updatehelper" property="proxyPort" />" /><br />
<!-- prompt for the eepproxy -->
Trusted keys:
<textarea name="trustedKeys" disabled="true" cols="60" rows="2"><jsp:getProperty name="updatehelper" property="trustedKeys" /></textarea>
eepProxy port: <input type="text" size="4" name="proxyPort" value="<jsp:getProperty name="updatehelper" property="proxyPort" />" /></p>
<p>Update URLs:<br />
<textarea name="updateURL" cols="90" rows="4"><jsp:getProperty name="updatehelper" property="updateURL" /></textarea></p>
<p>Trusted keys:</br />
<textarea name="trustedKeys" cols="90" rows="4"><jsp:getProperty name="updatehelper" property="trustedKeys" /></textarea></p>
<br />
<input type="submit" name="action" value="Save" />
</form>
</div>

View File

@ -34,7 +34,7 @@ Their XML parser requires the Sun XML APIs (JAXP) which is included in binary fo
by their binary code license. This product includes software developed by the Apache Software Foundation
(http://www.apache.org/). </p>
<p>Another application you can see on this webpage is <a href="http://www.i2p.net/i2ptunnel">I2PTunnel</a>
<p>Another application you can see on this webpage is <a href="http://www.i2p2.i2p/i2ptunnel">I2PTunnel</a>
(your <a href="i2ptunnel/" target="_blank">web interface</a>) - a GPL'ed application written by mihi that
lets you tunnel normal TCP/IP traffic over I2P (such as the eepproxy and the irc proxy). There is also a
<a href="http://susi.i2p/">susimail</a> web based mail client <a href="susimail/susimail">available</a> on
@ -42,14 +42,14 @@ the console, which is a GPL'ed application written by susi23. The addressbook a
<a href="http://ragnarok.i2p/">Ragnarok</a> helps maintain your hosts.txt files (see ./addressbook/ for
more information).</p>
<p>The router by default also includes human's public domain <a href="http://www.i2p.net/sam">SAM</a> bridge,
<p>The router by default also includes human's public domain <a href="http://www.i2p2.i2p/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. 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
in <a href="http://www.i2p.net/cvs">cvs</a>.</p>
<a href="http://www.i2p2.i2p/licenses">license policy</a>. Source for the I2P code and most bundled
client applications can be found on our <a href="http://www.i2p2.i2p/download">download page</a>.
.</p>
<h2>Release history</h2>
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="contenthelper" scope="request" />
@ -59,9 +59,8 @@ in <a href="http://www.i2p.net/cvs">cvs</a>.</p>
<jsp:getProperty name="contenthelper" property="textContent" />
<p>
A more complete list of updates can be found
<a href="http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/history.txt?rev=HEAD">online</a>
(<a href="http://dev.i2p/cgi-bin/cvsweb.cgi/i2p/history.txt?rev=HEAD">anonymously</a>)
A more complete list of changes can be found
in the history.txt file in your i2p directory.
</p>
</div>

View File

@ -5,6 +5,7 @@
<html><head>
<title>I2P Router Console - home</title>
<link rel="stylesheet" href="default.css" type="text/css" />
<link rel="shortcut icon" href="favicon.ico" />
</head><body>
<%
if (System.getProperty("router.consoleNonce") == null) {

View File

@ -24,7 +24,7 @@
<!-- 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="syndie/">Syndie</a> | -->
<a href="i2psnark/">I2PSnark</a> |
<a href="http://localhost:7658/">My Eepsite</a> <br>
<a href="i2ptunnel/index.jsp">I2PTunnel</a> |

View File

@ -13,8 +13,8 @@
<b>Ident:</b> <jsp:getProperty name="helper" property="ident" /><br />
<b>Version:</b> <jsp:getProperty name="helper" property="version" /><br />
<b>Uptime:</b> <jsp:getProperty name="helper" property="uptime" /><br />
<b>Now:</b> <jsp:getProperty name="helper" property="time" /><br />
<b>Status:</b> <a href="config.jsp"><jsp:getProperty name="helper" property="reachability" /></a><%
<b>Now:</b> <jsp:getProperty name="helper" property="time" /><!--<br />
<b>Status:</b> <a href="config.jsp"><jsp:getProperty name="helper" property="reachability" /></a>--><%
if (helper.updateAvailable()) {
if ("true".equals(System.getProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"))) {
out.print("<br />" + update.getStatus());
@ -43,13 +43,16 @@
<b>Failing:</b> <jsp:getProperty name="helper" property="failingPeers" /><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) {
if (helper.getActivePeers() <= 0) {
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
}
}
// If showing the reseed link is allowed
if (helper.allowReseed()) {
if ("true".equals(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false"))) {
out.print(" <i>reseeding</i>");
// While reseed occurring, show status message instead
out.print("<i>" + System.getProperty("net.i2p.router.web.ReseedHandler.statusMessage","") + "</i><br />");
} else {
// While no reseed occurring, show reseed link
long nonce = new java.util.Random().nextLong();
String prev = System.getProperty("net.i2p.router.web.ReseedHandler.nonce");
if (prev != null) System.setProperty("net.i2p.router.web.ReseedHandler.noncePrev", prev);
@ -59,7 +62,14 @@
uri = uri + "&reseedNonce=" + nonce;
else
uri = uri + "?reseedNonce=" + nonce;
out.print(" <a href=\"" + uri + "\">reseed</a>");
out.print(" <a href=\"" + uri + "\">reseed</a><br />");
}
}
// If a new reseed ain't running, and the last reseed had errors, show error message
if ("false".equals(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false"))) {
String reseedErrorMessage = System.getProperty("net.i2p.router.web.ReseedHandler.errorMessage","");
if (reseedErrorMessage.length() > 0) {
out.print("<i>" + reseedErrorMessage + "</i><br />");
}
}
%><hr />

View File

@ -112,12 +112,15 @@ public class SAMHandlerFactory {
case 1:
handler = new SAMv1Handler(s, verMajor, verMinor, i2cpProps);
break;
case 2:
handler = new SAMv2Handler(s, verMajor, verMinor, i2cpProps);
break;
default:
_log.error("BUG! Trying to initialize the wrong SAM version!");
throw new SAMException("BUG! (in handler instantiation)");
}
} catch (IOException e) {
_log.error("Error creating the v1 handler", e);
_log.error("Error creating the handler for version "+verMajor, e);
throw new SAMException("IOException caught during SAM handler instantiation");
}
return handler;
@ -133,15 +136,16 @@ public class SAMHandlerFactory {
|| (maxMajor == -1) || (maxMinor == -1)) {
return null;
}
if (minMajor > maxMajor) {
return null;
} else if ((minMajor == maxMajor) && (minMinor > maxMinor)) {
return null;
}
if ((minMajor >= 1) && (minMinor >= 0)) {
return "1.0";
}
if ((minMinor >= 10) || (maxMinor >= 10)) return null ;
float fminVer = (float) minMajor + (float) minMinor / 10 ;
float fmaxVer = (float) maxMajor + (float) maxMinor / 10 ;
if ( ( fminVer <= 2.0 ) && ( fmaxVer >= 2.0 ) ) return "2.0" ;
if ( ( fminVer <= 1.0 ) && ( fmaxVer >= 1.0 ) ) return "1.0" ;
return null;
}

View File

@ -15,14 +15,31 @@ import net.i2p.data.Destination;
/**
* Interface for sending streaming data to a SAM client
*/
public interface SAMStreamReceiver {
/**
* Sends the result of a stream send operation
*/
public void streamSendAnswer( int id, String result, String bufferState ) throws IOException;
/**
* Notifies that the outwards buffer is free for writing
*/
public void notifyStreamSendBufferFree( int id ) throws IOException;
/**
* Notify about a new incoming connection
*
* @param id New connection id
*/
public void notifyStreamConnection(int id, Destination dest) throws IOException;
public void notifyStreamIncomingConnection ( int id, Destination dest ) throws IOException;
/**
* Notify about a new outgoing connection
*
* @param id New connection id
*/
public void notifyStreamOutgoingConnection(int id, String result, String msg) throws IOException;
/**
* Send a byte array to a SAM client.

View File

@ -47,13 +47,13 @@ public class SAMStreamSession {
private final static Log _log = new Log(SAMStreamSession.class);
private final static int SOCKET_HANDLER_BUF_SIZE = 32768;
protected final static int SOCKET_HANDLER_BUF_SIZE = 32768;
private SAMStreamReceiver recv = null;
protected SAMStreamReceiver recv = null;
private SAMStreamSessionServer server = null;
private I2PSocketManager socketMgr = null;
protected I2PSocketManager socketMgr = null;
private Object handlersMapLock = new Object();
/** stream id (Long) to SAMStreamSessionSocketReader */
@ -65,13 +65,14 @@ public class SAMStreamSession {
private int lastNegativeId = 0;
// Can we create outgoing connections?
private boolean canCreate = false;
protected boolean canCreate = false;
/**
* should we flush every time we get a STREAM SEND, or leave that up to
* the streaming lib to decide?
*/
private boolean forceFlush = false;
protected boolean forceFlush = false;
public static String PROP_FORCE_FLUSH = "sam.forceFlush";
public static String DEFAULT_FORCE_FLUSH = "false";
@ -189,7 +190,7 @@ public class SAMStreamSession {
* @throws InterruptedIOException if the connection timeouts
* @throws I2PException if there's another I2P-related error
*/
public boolean connect(int id, String dest, Properties props) throws I2PException, ConnectException, NoRouteToHostException, DataFormatException, InterruptedIOException, SAMInvalidDirectionException {
public boolean connect ( int id, String dest, Properties props ) throws I2PException, ConnectException, NoRouteToHostException, DataFormatException, InterruptedIOException, SAMInvalidDirectionException, IOException {
if (!canCreate) {
_log.debug("Trying to create an outgoing connection using a receive-only session");
throw new SAMInvalidDirectionException("Trying to create connections through a receive-only session");
@ -208,10 +209,15 @@ public class SAMStreamSession {
opts.setConnectTimeout(60 * 1000);
_log.debug("Connecting new I2PSocket...");
// blocking connection (SAMv1)
I2PSocket i2ps = socketMgr.connect(d, opts);
createSocketHandler(i2ps, id);
recv.notifyStreamOutgoingConnection ( id, "OK", null );
return true;
}
@ -277,7 +283,7 @@ public class SAMStreamSession {
*
* @return An id associated to the socket handler
*/
private int createSocketHandler(I2PSocket s, int id) {
protected int createSocketHandler ( I2PSocket s, int id ) {
SAMStreamSessionSocketReader reader = null;
StreamSender sender = null;
if (id == 0) {
@ -285,8 +291,8 @@ public class SAMStreamSession {
}
try {
reader = new SAMStreamSessionSocketReader(s, id);
sender = new StreamSender(s, id);
reader = newSAMStreamSessionSocketReader(s, id);
sender = newStreamSender(s, id);
} catch (IOException e) {
_log.error("IOException when creating SAM STREAM session socket handler", e);
recv.stopStreamReceiving();
@ -318,7 +324,7 @@ public class SAMStreamSession {
*
* @param id Handler id
*/
private SAMStreamSessionSocketReader getSocketReader(int id) {
protected SAMStreamSessionSocketReader getSocketReader ( int id ) {
synchronized (handlersMapLock) {
return (SAMStreamSessionSocketReader)handlersMap.get(new Integer(id));
}
@ -334,7 +340,7 @@ public class SAMStreamSession {
*
* @param id Handler id
*/
private boolean checkSocketHandlerId(int id) {
protected boolean checkSocketHandlerId ( int id ) {
synchronized (handlersMapLock) {
return (!(handlersMap.get(new Integer(id)) == null));
}
@ -345,7 +351,7 @@ public class SAMStreamSession {
*
* @param id Handler id to be removed
*/
private void removeSocketHandler(int id) {
protected void removeSocketHandler ( int id ) {
SAMStreamSessionSocketReader reader = null;
StreamSender sender = null;
@ -446,7 +452,8 @@ public class SAMStreamSession {
}
_log.debug("New connection id: " + id);
recv.notifyStreamConnection(id, i2ps.getPeerDestination());
recv.notifyStreamIncomingConnection ( id, i2ps.getPeerDestination() );
} catch (I2PException e) {
_log.debug("Caught I2PException", e);
break;
@ -469,29 +476,62 @@ public class SAMStreamSession {
}
boolean setReceiveLimit ( int id, long limit, boolean nolimit )
{
_log.debug ( "Protocol v1 does not support a receive limit for streams" );
return false ;
}
/**
* SAM STREAM socket handler, running in its own thread. It forwards
* SAM STREAM socket reader, running in its own thread. It forwards
* forward data to/from an I2P socket.
*
* @author human
*/
public class SAMStreamSessionSocketReader implements Runnable {
private I2PSocket i2pSocket = null;
protected I2PSocket i2pSocket = null;
private Object runningLock = new Object();
private boolean stillRunning = true;
protected Object runningLock = new Object();
protected boolean stillRunning = true;
protected int id;
private int id;
/**
* Create a new SAM STREAM session socket reader
*
* @param s Socket to be handled
* @param id Unique id assigned to the handler
*/
public SAMStreamSessionSocketReader(I2PSocket s, int id) throws IOException {
_log.debug("Instantiating new SAM STREAM session socket handler");
public SAMStreamSessionSocketReader ( I2PSocket s, int id ) throws IOException {}
/**
* Stop a SAM STREAM session socket reader thread immediately.
*/
public void stopRunning() {}
public void run() {}
}
protected SAMStreamSessionSocketReader
newSAMStreamSessionSocketReader ( I2PSocket s, int id ) throws IOException {
return new SAMv1StreamSessionSocketReader ( s, id );
}
public class SAMv1StreamSessionSocketReader extends SAMStreamSessionSocketReader {
/**
* Create a new SAM STREAM session socket reader
*
* @param s Socket to be handled
* @param id Unique id assigned to the handler
*/
public SAMv1StreamSessionSocketReader ( I2PSocket s, int id ) throws IOException {
super(s, id);
_log.debug("Instantiating new SAM STREAM session socket reader");
i2pSocket = s;
this.id = id;
@ -507,6 +547,7 @@ public class SAMStreamSession {
if (stillRunning) {
stillRunning = false;
}
runningLock.notifyAll() ;
}
}
@ -558,7 +599,40 @@ public class SAMStreamSession {
* Lets us push data through the stream without blocking, (even after exceeding
* the I2PSocket's buffer)
*/
private class StreamSender implements Runnable {
protected class StreamSender implements Runnable {
public StreamSender ( I2PSocket s, int id ) throws IOException {}
/**
* Send bytes through the SAM STREAM session socket sender
*
* @param data Data to be sent
*
* @throws IOException if the client didnt provide enough data
*/
public void sendBytes ( InputStream in, int size ) throws IOException {}
/**
* Stop a SAM STREAM session socket sender thread immediately
*
*/
public void stopRunning() {}
/**
* Stop a SAM STREAM session socket sender gracefully: stop the
* sender thread once all pending data has been sent.
*/
public void shutDownGracefully() {}
public void run() {}
}
protected StreamSender newStreamSender ( I2PSocket s, int id ) throws IOException {
return new v1StreamSender ( s, id ) ;
}
protected class v1StreamSender extends StreamSender
{
private List _data;
private int _id;
private ByteCache _cache;
@ -567,7 +641,8 @@ public class SAMStreamSession {
private Object runningLock = new Object();
private I2PSocket i2pSocket = null;
public StreamSender(I2PSocket s, int id) throws IOException {
public v1StreamSender ( I2PSocket s, int id ) throws IOException {
super ( s, id );
_data = new ArrayList(1);
_id = id;
_cache = ByteCache.getInstance(4, 32*1024);

View File

@ -35,6 +35,8 @@ import net.i2p.util.Log;
* @author human
*/
public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramReceiver, SAMStreamReceiver {
protected int verMajorId = 1;
protected int verMinorId = 0;
private final static Log _log = new Log(SAMv1Handler.class);
@ -42,7 +44,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
private SAMRawSession rawSession = null;
private SAMDatagramSession datagramSession = null;
private SAMStreamSession streamSession = null;
protected SAMStreamSession streamSession = null;
private long _id;
private static volatile long __id = 0;
@ -74,11 +76,15 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
_id = ++__id;
_log.debug("SAM version 1 handler instantiated");
if ((this.verMajor != 1) || (this.verMinor != 0)) {
if ( ! verifVersion() ) {
throw new SAMException("BUG! Wrong protocol version!");
}
}
public boolean verifVersion() {
return ( verMajor == 1 && verMinor == 0 ) ;
}
public void handle() {
String msg = null;
String domain = null;
@ -248,7 +254,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
props.remove("DIRECTION");
streamSession = new SAMStreamSession(destKeystream, dir,props,this);
streamSession = newSAMStreamSession(destKeystream, dir,props);
} else {
_log.debug("Unrecognized SESSION STYLE: \"" + style +"\"");
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n");
@ -275,6 +281,13 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
}
SAMStreamSession newSAMStreamSession(String destKeystream, String direction, Properties props )
throws IOException, DataFormatException, SAMException
{
return new SAMStreamSession(destKeystream, direction, props, this) ;
}
/* Parse and execute a DEST message*/
private boolean execDestMessage(String opcode, Properties props) {
@ -489,7 +502,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
/* Parse and execute a STREAM message */
private boolean execStreamMessage(String opcode, Properties props) {
protected boolean execStreamMessage(String opcode, Properties props) {
if (streamSession == null) {
_log.error("STREAM message received, but no STREAM session exists");
return false;
@ -508,7 +521,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
}
private boolean execStreamSend(Properties props) {
protected boolean execStreamSend(Properties props) {
if (props == null) {
_log.debug("No parameters specified in STREAM SEND message");
return false;
@ -570,7 +583,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
}
private boolean execStreamConnect(Properties props) {
protected boolean execStreamConnect(Properties props) {
if (props == null) {
_log.debug("No parameters specified in STREAM CONNECT message");
return false;
@ -604,39 +617,38 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
props.remove("DESTINATION");
try {
if (!streamSession.connect(id, dest, props)) {
_log.debug("STREAM connection failed");
return false;
try {
if (!streamSession.connect(id, dest, props)) {
_log.debug("STREAM connection failed");
return false;
}
} catch (DataFormatException e) {
_log.debug("Invalid destination in STREAM CONNECT message");
notifyStreamOutgoingConnection ( id, "INVALID_KEY", null );
} catch (SAMInvalidDirectionException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
notifyStreamOutgoingConnection ( id, "INVALID_DIRECTION", null );
} catch (ConnectException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
notifyStreamOutgoingConnection ( id, "CONNECTION_REFUSED", null );
} catch (NoRouteToHostException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
notifyStreamOutgoingConnection ( id, "CANT_REACH_PEER", null );
} catch (InterruptedIOException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
notifyStreamOutgoingConnection ( id, "TIMEOUT", null );
} catch (I2PException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
notifyStreamOutgoingConnection ( id, "I2P_ERROR", null );
}
return writeString("STREAM STATUS RESULT=OK ID=" + id + "\n");
} catch (DataFormatException e) {
_log.debug("Invalid destination in STREAM CONNECT message");
return writeString("STREAM STATUS RESULT=INVALID_KEY ID="
+ id + "\n");
} catch (SAMInvalidDirectionException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=INVALID_DIRECTION ID="
+ id + "\n");
} catch (ConnectException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=CONNECTION_REFUSED ID="
+ id + "\n");
} catch (NoRouteToHostException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=CANT_REACH_PEER ID="
+ id + "\n");
} catch (InterruptedIOException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=TIMEOUT ID="
+ id + "\n");
} catch (I2PException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
+ id + "\n");
} catch (IOException e) {
return false ;
}
return true ;
}
private boolean execStreamClose(Properties props) {
protected boolean execStreamClose(Properties props) {
if (props == null) {
_log.debug("No parameters specified in STREAM CLOSE message");
return false;
@ -745,7 +757,41 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
// SAMStreamReceiver implementation
public void notifyStreamConnection(int id, Destination d) throws IOException {
public void streamSendAnswer( int id, String result, String bufferState ) throws IOException
{
if ( streamSession == null )
{
_log.error ( "BUG! Want to answer to stream SEND, but session is null!" );
throw new NullPointerException ( "BUG! STREAM session is null!" );
}
if ( !writeString ( "STREAM SEND ID=" + id
+ " RESULT=" + result
+ " STATE=" + bufferState
+ "\n" ) )
{
throw new IOException ( "Error notifying connection to SAM client" );
}
}
public void notifyStreamSendBufferFree( int id ) throws IOException
{
if ( streamSession == null )
{
_log.error ( "BUG! Stream outgoing buffer is free, but session is null!" );
throw new NullPointerException ( "BUG! STREAM session is null!" );
}
if ( !writeString ( "STREAM READY_TO_SEND ID=" + id + "\n" ) )
{
throw new IOException ( "Error notifying connection to SAM client" );
}
}
public void notifyStreamIncomingConnection(int id, Destination d) throws IOException {
if (streamSession == null) {
_log.error("BUG! Received stream connection, but session is null!");
throw new NullPointerException("BUG! STREAM session is null!");
@ -758,6 +804,28 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
}
public void notifyStreamOutgoingConnection ( int id, String result, String msg ) throws IOException
{
if ( streamSession == null )
{
_log.error ( "BUG! Received stream connection, but session is null!" );
throw new NullPointerException ( "BUG! STREAM session is null!" );
}
String msgString = "" ;
if ( msg != null ) msgString = " MESSAGE=\"" + msg + "\"";
if ( !writeString ( "STREAM STATUS RESULT="
+ result
+ " ID=" + id
+ msgString
+ "\n" ) )
{
throw new IOException ( "Error notifying connection to SAM client" );
}
}
public void receiveStreamBytes(int id, byte data[], int len) throws IOException {
if (streamSession == null) {
_log.error("Received stream bytes, but session is null!");

View File

@ -0,0 +1,196 @@
package net.i2p.sam;
/*
* free (adj.): unencumbered; not under the control of others
* Written by human in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PException;
import net.i2p.client.I2PSessionException;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Class able to handle a SAM version 2 client connection.
*
* @author mkvore
*/
public class SAMv2Handler extends SAMv1Handler implements SAMRawReceiver, SAMDatagramReceiver, SAMStreamReceiver
{
private final static Log _log = new Log ( SAMv2Handler.class );
/**
* Create a new SAM version 2 handler. This constructor expects
* that the SAM HELLO message has been still answered (and
* stripped) from the socket input stream.
*
* @param s Socket attached to a SAM client
* @param verMajor SAM major version to manage (should be 2)
* @param verMinor SAM minor version to manage
*/
public SAMv2Handler ( Socket s, int verMajor, int verMinor ) throws SAMException, IOException
{
this ( s, verMajor, verMinor, new Properties() );
}
/**
* Create a new SAM version 2 handler. This constructor expects
* that the SAM HELLO message has been still answered (and
* stripped) from the socket input stream.
*
* @param s Socket attached to a SAM client
* @param verMajor SAM major version to manage (should be 2)
* @param verMinor SAM minor version to manage
* @param i2cpProps properties to configure the I2CP connection (host, port, etc)
*/
public SAMv2Handler ( Socket s, int verMajor, int verMinor, Properties i2cpProps ) throws SAMException, IOException
{
super ( s, verMajor, verMinor, i2cpProps );
}
public boolean verifVersion()
{
return (verMajor == 2 && verMinor == 0) ;
}
SAMStreamSession newSAMStreamSession(String destKeystream, String direction, Properties props )
throws IOException, DataFormatException, SAMException
{
return new SAMv2StreamSession(destKeystream, direction, props, this) ;
}
/* Parse and execute a STREAM message */
protected boolean execStreamMessage ( String opcode, Properties props )
{
if ( streamSession == null )
{
_log.error ( "STREAM message received, but no STREAM session exists" );
return false;
}
if ( opcode.equals ( "SEND" ) )
{
return execStreamSend ( props );
}
else if ( opcode.equals ( "CONNECT" ) )
{
return execStreamConnect ( props );
}
else if ( opcode.equals ( "CLOSE" ) )
{
return execStreamClose ( props );
}
else if ( opcode.equals ( "RECEIVE") )
{
return execStreamReceive( props );
}
else
{
_log.debug ( "Unrecognized RAW message opcode: \""
+ opcode + "\"" );
return false;
}
}
private boolean execStreamReceive ( Properties props )
{
if ( props == null )
{
_log.debug ( "No parameters specified in STREAM RECEIVE message" );
return false;
}
int id;
{
String strid = props.getProperty ( "ID" );
if ( strid == null )
{
_log.debug ( "ID not specified in STREAM RECEIVE message" );
return false;
}
try
{
id = Integer.parseInt ( strid );
}
catch ( NumberFormatException e )
{
_log.debug ( "Invalid STREAM RECEIVE ID specified: " + strid );
return false;
}
}
boolean nolimit = false;
long limit = 0;
{
String strsize = props.getProperty ( "LIMIT" );
if ( strsize == null )
{
_log.debug ( "Limit not specified in STREAM RECEIVE message" );
return false;
}
if ( strsize.equals( "NONE" ) )
{
nolimit = true ;
}
else
{
try
{
limit = Long.parseLong ( strsize );
}
catch ( NumberFormatException e )
{
_log.debug ( "Invalid STREAM RECEIVE size specified: " + strsize );
return false;
}
if ( limit < 0 )
{
_log.debug ( "Specified limit (" + limit
+ ") is out of protocol limits" );
return false;
}
}
}
streamSession.setReceiveLimit ( id, limit, nolimit ) ;
return true;
}
}

View File

@ -0,0 +1,574 @@
package net.i2p.sam;
/*
* free (adj.): unencumbered; not under the control of others
* Written by human in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
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.client.streaming.I2PSocketOptions;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.ByteCache;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* SAMv2 STREAM session class.
*
* @author mkvore
*/
public class SAMv2StreamSession extends SAMStreamSession
{
private final static Log _log = new Log ( SAMv2StreamSession.class );
/**
* Create a new SAM STREAM session.
*
* @param dest Base64-encoded destination (private key)
* @param dir Session direction ("RECEIVE", "CREATE" or "BOTH")
* @param props Properties to setup the I2P session
* @param recv Object that will receive incoming data
*/
public SAMv2StreamSession ( String dest, String dir, Properties props,
SAMStreamReceiver recv ) throws IOException, DataFormatException, SAMException
{
super ( dest, dir, props, recv );
}
/**
* Create a new SAM STREAM session.
*
* @param destStream Input stream containing the destination keys
* @param dir Session direction ("RECEIVE", "CREATE" or "BOTH")
* @param props Properties to setup the I2P session
* @param recv Object that will receive incoming data
*/
public SAMv2StreamSession ( InputStream destStream, String dir,
Properties props, SAMStreamReceiver recv ) throws IOException, DataFormatException, SAMException
{
super ( destStream, dir, props, recv );
}
/**
* Connect the SAM STREAM session to the specified Destination
*
* @param id Unique id for the connection
* @param dest Base64-encoded Destination to connect to
* @param props Options to be used for connection
*
* @throws DataFormatException if the destination is not valid
* @throws SAMInvalidDirectionException if trying to connect through a
* receive-only session
* @return true if the communication with the SAM client is ok
*/
public boolean connect ( int id, String dest, Properties props )
throws DataFormatException, SAMInvalidDirectionException
{
if ( !canCreate )
{
_log.debug ( "Trying to create an outgoing connection using a receive-only session" );
throw new SAMInvalidDirectionException ( "Trying to create connections through a receive-only session" );
}
if ( checkSocketHandlerId ( id ) )
{
_log.debug ( "The specified id (" + id + ") is already in use" );
return false ;
}
Destination d = new Destination();
d.fromBase64 ( dest );
I2PSocketOptions opts = socketMgr.buildOptions ( props );
if ( props.getProperty ( I2PSocketOptions.PROP_CONNECT_TIMEOUT ) == null )
opts.setConnectTimeout ( 60 * 1000 );
_log.debug ( "Connecting new I2PSocket..." );
// non-blocking connection (SAMv2)
StreamConnector connector ;
connector = new StreamConnector ( id, d, opts );
I2PThread connectThread = new I2PThread ( connector, "StreamConnector" + id ) ;
connectThread.start() ;
return true ;
}
/**
* SAM STREAM socket connecter, running in its own thread.
*
* @author mkvore
*/
public class StreamConnector implements Runnable
{
private Object runningLock = new Object();
private boolean stillRunning = true;
private int id;
private Destination dest ;
private I2PSocketOptions opts ;
/**
* Create a new SAM STREAM session socket reader
*
* @param id Unique id assigned to the handler
* @param dest Destination to reach
* @param opts Socket options (I2PSocketOptions)
*/
public StreamConnector ( int id, Destination dest, I2PSocketOptions opts )// throws IOException
{
_log.debug ( "Instantiating new SAM STREAM connector" );
this.id = id ;
this.opts = opts ;
this.dest = dest ;
}
public void run()
{
_log.debug ( "run() called for socket connector " + id );
try
{
try
{
I2PSocket i2ps = socketMgr.connect ( dest, opts );
createSocketHandler ( i2ps, id );
recv.notifyStreamOutgoingConnection ( id, "OK", null );
}
catch ( DataFormatException e )
{
_log.debug ( "Invalid destination in STREAM CONNECT message" );
recv.notifyStreamOutgoingConnection ( id, "INVALID_KEY", e.getMessage() );
}
catch ( ConnectException e )
{
_log.debug ( "STREAM CONNECT failed: " + e.getMessage() );
recv.notifyStreamOutgoingConnection ( id, "CONNECTION_REFUSED", e.getMessage() );
}
catch ( NoRouteToHostException e )
{
_log.debug ( "STREAM CONNECT failed: " + e.getMessage() );
recv.notifyStreamOutgoingConnection ( id, "CANT_REACH_PEER", e.getMessage() );
}
catch ( InterruptedIOException e )
{
_log.debug ( "STREAM CONNECT failed: " + e.getMessage() );
recv.notifyStreamOutgoingConnection ( id, "TIMEOUT", e.getMessage() );
}
catch ( I2PException e )
{
_log.debug ( "STREAM CONNECT failed: " + e.getMessage() );
recv.notifyStreamOutgoingConnection ( id, "I2P_ERROR", e.getMessage() );
}
}
catch ( IOException e )
{
_log.debug ( "Error sending disconnection notice for handler "
+ id, e );
}
_log.debug ( "Shutting down SAM STREAM session connector " + id );
}
}
/**
* Lets us push data through the stream without blocking, (even after exceeding
* the I2PSocket's buffer)
*/
protected StreamSender newStreamSender ( I2PSocket s, int id ) throws IOException
{
return new v2StreamSender ( s, id ) ;
}
protected SAMStreamSessionSocketReader
newSAMStreamSessionSocketReader(I2PSocket s, int id ) throws IOException
{
return new SAMv2StreamSessionSocketReader(s,id);
}
protected class v2StreamSender extends StreamSender
{
private List _data;
private int _dataSize;
private int _id;
private ByteCache _cache;
private OutputStream _out = null;
private boolean _stillRunning, _shuttingDownGracefully;
private Object runningLock = new Object();
private I2PSocket i2pSocket = null;
public v2StreamSender ( I2PSocket s, int id ) throws IOException
{
super ( s, id );
_data = new ArrayList ( 1 );
_dataSize = 0;
_id = id;
_cache = ByteCache.getInstance ( 10, 32 * 1024 );
_out = s.getOutputStream();
_stillRunning = true;
_shuttingDownGracefully = false;
i2pSocket = s;
}
/**
* Send bytes through the SAM STREAM session socket sender
*
* @param data Data to be sent
*
* @throws IOException if the client didnt provide enough data
*/
public void sendBytes ( InputStream in, int size ) throws IOException
{
if ( _log.shouldLog ( Log.DEBUG ) )
_log.debug ( "Handler " + _id + ": sending " + size + " bytes" );
ByteArray ba = _cache.acquire();
int read = DataHelper.read ( in, ba.getData(), 0, size );
if ( read != size )
throw new IOException ( "Insufficient data from the SAM client (" + read + "/" + size + ")" );
ba.setValid ( read );
synchronized ( _data )
{
if ( _dataSize >= SOCKET_HANDLER_BUF_SIZE )
{
_cache.release ( ba, false );
recv.streamSendAnswer ( _id, "FAILED", "BUFFER_FULL" ) ;
}
else
{
_dataSize += size ;
_data.add ( ba );
_data.notifyAll();
if ( _dataSize >= SOCKET_HANDLER_BUF_SIZE )
{
recv.streamSendAnswer ( _id, "OK", "BUFFER_FULL" ) ;
}
else
{
recv.streamSendAnswer ( _id, "OK", "READY" );
}
}
}
}
/**
* Stop a SAM STREAM session socket sender thread immediately
*
*/
public void stopRunning()
{
_log.debug ( "stopRunning() invoked on socket sender " + _id );
synchronized ( runningLock )
{
if ( _stillRunning )
{
_stillRunning = false;
try
{
i2pSocket.close();
}
catch ( IOException e )
{
_log.debug ( "Caught IOException", e );
}
synchronized ( _data )
{
_data.clear();
_data.notifyAll();
}
}
}
}
/**
* Stop a SAM STREAM session socket sender gracefully: stop the
* sender thread once all pending data has been sent.
*/
public void shutDownGracefully()
{
_log.debug ( "shutDownGracefully() invoked on socket sender " + _id );
_shuttingDownGracefully = true;
}
public void run()
{
_log.debug ( "run() called for socket sender " + _id );
ByteArray data = null;
while ( _stillRunning )
{
data = null;
try
{
synchronized ( _data )
{
if ( _data.size() > 0 )
{
int formerSize = _dataSize ;
data = ( ByteArray ) _data.remove ( 0 );
_dataSize -= data.getValid();
if ( ( formerSize >= SOCKET_HANDLER_BUF_SIZE ) && ( _dataSize < SOCKET_HANDLER_BUF_SIZE ) )
recv.notifyStreamSendBufferFree ( _id );
}
else if ( _shuttingDownGracefully )
{
/* No data left and shutting down gracefully?
If so, stop the sender. */
stopRunning();
break;
}
else
{
/* Wait for data. */
_data.wait ( 5000 );
}
}
if ( data != null )
{
try
{
_out.write ( data.getData(), 0, data.getValid() );
if ( forceFlush )
{
// i dont like doing this, but it clears the buffer issues
_out.flush();
}
}
catch ( IOException ioe )
{
// ok, the stream failed, but the SAM client didn't
if ( _log.shouldLog ( Log.WARN ) )
_log.warn ( "Stream failed", ioe );
removeSocketHandler ( _id );
stopRunning();
}
finally
{
_cache.release ( data, false );
}
}
}
catch ( InterruptedException ie ) {}
catch ( IOException e ) {}}
synchronized ( _data )
{
_data.clear();
}
}
}
/**
* Send bytes through a SAM STREAM session.
*
* @param data Bytes to be sent
*
* @return True if the data was queued for sending, false otherwise
*/
public boolean setReceiveLimit ( int id, long limit, boolean nolimit )
{
SAMStreamSessionSocketReader reader = getSocketReader ( id );
if ( reader == null )
{
if ( _log.shouldLog ( Log.WARN ) )
_log.warn ( "Trying to set a limit to a nonexistent reader " + id );
return false;
}
( (SAMv2StreamSessionSocketReader) reader).setLimit ( limit, nolimit );
return true;
}
/**
* SAM STREAM socket reader, running in its own thread. It forwards
* forward data to/from an I2P socket.
*
* @author human
*/
public class SAMv2StreamSessionSocketReader extends SAMv1StreamSessionSocketReader
{
protected boolean nolimit ;
protected long limit ;
protected long totalReceived ;
/**
* Create a new SAM STREAM session socket reader
*
* @param s Socket to be handled
* @param id Unique id assigned to the handler
*/
public SAMv2StreamSessionSocketReader ( I2PSocket s, int id ) throws IOException
{
super ( s, id );
nolimit = false ;
limit = 0 ;
totalReceived = 0 ;
}
public void setLimit ( long limit, boolean nolimit )
{
synchronized (runningLock)
{
this.limit = limit ;
this.nolimit = nolimit ;
runningLock.notify() ;
}
_log.debug ( "new limit set for socket reader " + id + " : " + (nolimit ? "NOLIMIT" : limit + " bytes" ) );
}
public void run()
{
_log.debug ( "run() called for socket reader " + id );
int read = -1;
byte[] data = new byte[SOCKET_HANDLER_BUF_SIZE];
try
{
InputStream in = i2pSocket.getInputStream();
while ( stillRunning )
{
synchronized (runningLock)
{
while ( stillRunning && ( !nolimit && totalReceived >= limit) )
{
try{
runningLock.wait() ;
}
catch (InterruptedException ie)
{}
}
if ( !stillRunning )
break ;
}
read = in.read ( data );
if ( read == -1 )
{
_log.debug ( "Handler " + id + ": connection closed" );
break;
}
totalReceived += read ;
recv.receiveStreamBytes ( id, data, read );
}
}
catch ( IOException e )
{
_log.debug ( "Caught IOException", e );
}
try
{
i2pSocket.close();
}
catch ( IOException e )
{
_log.debug ( "Caught IOException", e );
}
if ( stillRunning )
{
removeSocketHandler ( id );
// FIXME: we need error reporting here!
try
{
recv.notifyStreamDisconnection ( id, "OK", null );
}
catch ( IOException e )
{
_log.debug ( "Error sending disconnection notice for handler "
+ id, e );
}
}
_log.debug ( "Shutting down SAM STREAM session socket handler " + id );
}
}
}

View File

@ -72,7 +72,7 @@ public class Connection {
private long _lifetimeDupMessageSent;
private long _lifetimeDupMessageReceived;
public static final long MAX_RESEND_DELAY = 10*1000;
public static final long MAX_RESEND_DELAY = 45*1000;
public static final long MIN_RESEND_DELAY = 2*1000;
/** wait up to 5 minutes after disconnection so we can ack/close packets */
@ -247,6 +247,7 @@ public class Connection {
}
long now = _context.clock().now();
if (_resetSentOn + 10*1000 > now) return; // don't send resets too fast
if (_resetReceived) return;
_resetSent = true;
if (_resetSentOn <= 0)
_resetSentOn = now;

View File

@ -57,7 +57,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
static final int INITIAL_WINDOW_SIZE = 12;
static final int DEFAULT_MAX_SENDS = 8;
static final int MIN_WINDOW_SIZE = INITIAL_WINDOW_SIZE;
static final int MIN_WINDOW_SIZE = 1;
public ConnectionOptions() {
super();
@ -205,8 +205,8 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
public int getRTT() { return _rtt; }
public void setRTT(int ms) {
if (_rto == 0) {
_rttDev = ms;
_rto = (int)Connection.MAX_RESEND_DELAY;
_rttDev = ms / 2;
_rto = ms + ms / 2;
}
synchronized (_trend) {
_trend[0] = _trend[1];

View File

@ -29,9 +29,15 @@ public class I2PSocketFull implements I2PSocket {
if (c == null) return;
if (c.getIsConnected()) {
OutputStream out = c.getOutputStream();
if (out != null)
out.close();
c.disconnect(true);
if (out != null) {
try {
out.close();
} catch (IOException ioe) {
// ignore any write error, as we want to keep on and kill the
// con (thanks Complication!)
}
}
c.disconnect(true);
} else {
//throw new IOException("Not connected");
}

View File

@ -129,7 +129,7 @@ public class I2PSocketManagerFull implements I2PSocketManager {
public long getAcceptTimeout() { return _acceptTimeout; }
public void setDefaultOptions(I2PSocketOptions options) {
_defaultOptions = new ConnectionOptions(options);
_defaultOptions = new ConnectionOptions((ConnectionOptions) options);
}
public I2PSocketOptions getDefaultOptions() {

View File

@ -19,7 +19,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Revision: 1.7 $
* $Revision: 1.2 $
*/
package i2p.susi.dns;
@ -76,6 +76,8 @@ public class AddressbookBean
deletionMarks = new LinkedList();
}
private long configLastLoaded = 0;
private static final String PRIVATE_BOOK = "private_addressbook";
private static final String DEFAULT_PRIVATE_BOOK = "../privatehosts.txt";
private void loadConfig()
{
long currentTime = System.currentTimeMillis();
@ -86,6 +88,9 @@ public class AddressbookBean
try {
properties.clear();
properties.load( new FileInputStream( ConfigBean.configFileName ) );
// added in 0.5, for compatibility with 0.4 config.txt
if( properties.getProperty(PRIVATE_BOOK) == null)
properties.setProperty(PRIVATE_BOOK, DEFAULT_PRIVATE_BOOK);
configLastLoaded = currentTime;
}
catch (Exception e) {
@ -99,7 +104,6 @@ public class AddressbookBean
return ConfigBean.addressbookPrefix + filename;
}
private Object[] entries;
public Object[] getEntries()
{
return entries;
@ -113,8 +117,9 @@ public class AddressbookBean
public String getBook()
{
if( book == null || ( book.compareToIgnoreCase( "master" ) != 0 &&
book.compareToIgnoreCase( "router" ) != 0 ) &&
book.compareToIgnoreCase( "published" ) != 0 )
book.compareToIgnoreCase( "router" ) != 0 &&
book.compareToIgnoreCase( "private" ) != 0 &&
book.compareToIgnoreCase( "published" ) != 0 ))
book = "master";
return book;
@ -130,10 +135,63 @@ public class AddressbookBean
public void setSerial(String serial) {
this.serial = serial;
}
/** Load addressbook and apply filter, returning messages about this. */
public String getLoadBookMessages()
{
// Config and addressbook now loaded here, hence not needed in getMessages()
loadConfig();
addressbook = new Properties();
String message = "";
try {
addressbook.load( new FileInputStream( getFileName() ) );
LinkedList list = new LinkedList();
Enumeration e = addressbook.keys();
while( e.hasMoreElements() ) {
String name = (String)e.nextElement();
String destination = addressbook.getProperty( name );
if( filter != null && filter.length() > 0 ) {
if( filter.compareTo( "0-9" ) == 0 ) {
char first = name.charAt(0);
if( first < '0' || first > '9' )
continue;
}
else if( ! name.toLowerCase().startsWith( filter.toLowerCase() ) ) {
continue;
}
}
if( search != null && search.length() > 0 ) {
if( name.indexOf( search ) == -1 ) {
continue;
}
}
list.addLast( new AddressBean( name, destination ) );
}
// Format a message about filtered addressbook size, and the number of displayed entries
if( filter != null && filter.length() > 0 )
message = "Filtered l";
else
message = "L";
message += "ist contains " + list.size() + " entries";
if (list.size() > 300) message += ", displaying the first 300."; else message += ".";
Object array[] = list.toArray();
Arrays.sort( array, sorter );
entries = array;
}
catch (Exception e) {
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
}
if( message.length() > 0 )
message = "<p>" + message + "</p>";
return message;
}
/** Perform actions, returning messages about this. */
public String getMessages()
{
loadConfig();
// Loading config and addressbook moved into getLoadBookMessages()
String message = "";
if( action != null ) {
@ -175,42 +233,7 @@ public class AddressbookBean
}
action = null;
addressbook = new Properties();
try {
addressbook.load( new FileInputStream( getFileName() ) );
LinkedList list = new LinkedList();
Enumeration e = addressbook.keys();
while( e.hasMoreElements() ) {
String name = (String)e.nextElement();
String destination = addressbook.getProperty( name );
if( filter != null && filter.length() > 0 ) {
if( filter.compareTo( "0-9" ) == 0 ) {
char first = name.charAt(0);
if( first < '0' || first > '9' )
continue;
}
else if( ! name.toLowerCase().startsWith( filter.toLowerCase() ) ) {
continue;
}
}
if( search != null && search.length() > 0 ) {
if( name.indexOf( search ) == -1 ) {
continue;
}
}
list.addLast( new AddressBean( name, destination ) );
}
Object array[] = list.toArray();
Arrays.sort( array, sorter );
entries = array;
}
catch (Exception e) {
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
}
if( message.length() > 0 )
message = "<p class=\"messages\">" + message + "</p>";
return message;
@ -234,6 +257,14 @@ public class AddressbookBean
{
return getBook().compareToIgnoreCase( "router" ) == 0;
}
public boolean isPublished()
{
return getBook().compareToIgnoreCase( "published" ) == 0;
}
public boolean isPrivate()
{
return getBook().compareToIgnoreCase( "private" ) == 0;
}
public void setFilter(String filter) {
if( filter != null && ( filter.length() == 0 || filter.compareToIgnoreCase( "none" ) == 0 ) ) {
filter = null;

View File

@ -19,14 +19,14 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Revision: 1.4 $
* $Revision: 1.1 $
*/
package i2p.susi.dns;
public class VersionBean {
private static String version = "0.4";
private static String version = "0.5";
private static String url = "http://susi.i2p/?i2paddresshelper=T2DU1KAz3meB0B53U8Y06-I7vHR7XmC0qXAJfLW6b-1L1FVKoySRZz4xazHAwyv2xtRpvKrv6ukLm1tThEW0zQWtZPtX8G6KkzMibD8t7IS~4yw-9VkBtUydyYfsX08AK3v~-egSW8HCXTdyIJVtrETJb337VDUHW-7D4L1JLbwSH4if2ooks6yFTrljK5aVMi-16dZOVvmoyJc3jBqSdK6kraO4gW5-vHTmbLwL498p9nug1KOg1DqgN2GeU5X1QlVrlpFb~IIfdP~O8NT7u-LAjW3jSJsMbLDHMSYTIhC7xmJIiBoi-qk8p6TLynAmvJ7HRvbx4N1EB-uJHyD16wsZkkHyEOfmXbj0ZqLyKEGb3thPwCz-M9v~c2Qt3WbwjXJAtHpjlHkdJ4Fg91cX2oak~JoapnPf6Syw8hko5syf6VVoCYLnrrYyM8oGl8mLclHkj~VCidQNqMSM74IhrHfK6HmRikqtZBexb5M6wfMTTqBvaHURdD21GOpFKYBUAAAA";
public String getVersion() {

View File

@ -20,7 +20,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Revision: 1.1 $
* $Revision: 1.3 $
*/
%>
<%@ page contentType="text/html"%>
@ -48,9 +48,11 @@
<p>addressbooks
<a href="addressbook.jsp?book=master">master</a> |
<a href="addressbook.jsp?book=router">router</a> |
<a href="addressbook.jsp?book=published">published</a> *
<a href="addressbook.jsp?book=published">published</a> |
<a href="addressbook.jsp?book=private">private</a> *
<a href="subscriptions.jsp">subscriptions</a> *
<a href="config.jsp">configuration</a>
<a href="config.jsp">configuration</a> *
<a href="index.jsp">overview</a>
</p>
</div>
@ -60,6 +62,8 @@
<div id="messages">${book.messages}</div>
<span>${book.loadBookMessages}</span>
<div id="filter">
<p>Filter: <a href="addressbook.jsp?filter=a">a</a>
<a href="addressbook.jsp?filter=b">b</a>
@ -90,7 +94,8 @@
<a href="addressbook.jsp?filter=0-9">0-9</a>
<a href="addressbook.jsp?filter=none">all</a></p>
<c:if test="${book.hasFilter}">
<p>Current filter: ${book.filter}</p>
<p>Current filter: ${book.filter}
(<a href="addressbook.jsp?filter=none">clear filter</a>)</p>
</c:if>
</div>
@ -115,16 +120,17 @@
<table class="book" cellspacing="0" cellpadding="5">
<tr class="head">
<c:if test="${book.master || book.router}">
<c:if test="${book.master || book.router || book.published || book.private}">
<th>&nbsp;</th>
</c:if>
<th>Name</th>
<th>Destination</th>
</tr>
<c:forEach items="${book.entries}" var="addr">
<!-- limit iterator to 300, or "Form too large" may result on submit -->
<c:forEach items="${book.entries}" var="addr" begin="0" end="299">
<tr class="list${book.trClass}">
<c:if test="${book.master || book.router}">
<c:if test="${book.master || book.router || book.published || book.private}">
<td class="checkbox"><input type="checkbox" name="checked" value="${addr.name}" alt="Mark for deletion"></td>
</c:if>
<td class="names"><a href="http://${addr.name}/">${addr.name}</a> -
@ -136,7 +142,7 @@
</table>
</div>
<c:if test="${book.master || book.router}">
<c:if test="${book.master || book.router || book.published || book.private}">
<div id="buttons">
<p class="buttons"><input type="image" name="action" value="delete" src="images/delete.png" alt="Delete checked" />
</p>

View File

@ -20,7 +20,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Revision: 1.14 $
* $Revision: 1.1 $
*/
%>
<%@ page contentType="text/html" %>
@ -43,9 +43,11 @@
addressbooks
<a href="addressbook.jsp?book=master">master</a> |
<a href="addressbook.jsp?book=router">router</a> |
<a href="addressbook.jsp?book=published">published</a> *
<a href="addressbook.jsp?book=published">published</a> |
<a href="addressbook.jsp?book=private">private</a> *
<a href="subscriptions.jsp">subscriptions</a> *
<a href="config.jsp">configuration</a>
configuration *
<a href="index.jsp">overview</a>
</p>
</div>
<div id="headline">
@ -65,22 +67,22 @@ addressbooks
<div id="help">
<h3>Hints</h3>
<ol>
<li>All file or directory paths here are relative to the addressbooks working directory, which normally
<li>All file or directory paths here are relative to the addressbook's working directory, which normally
is located at $I2P/addressbook/.</li>
<li>If you want to manually add lines to an addressbook, add them to the master addressbook. The router
<li>If you want to manually add lines to an addressbook, add them to the private or master addressbooks. The router
addressbook and the published addressbook are overwritten by the addressbook application.</li>
<li><b>Important:</b>When you publish your addressbook, <b>ALL</b> destinations appear there, even those
from your master addressbook. Unfortunately the master addressbook points to your userhosts.txt, which was
used for private destinations before. So if you want to keep the destinations in your userhosts.txt secret,
please change the master addressbook to a different file before turning on addressbook publishing.</li>
<li><b>Important:</b>When you publish your addressbook, <b>ALL</b> destinations from the master and router addressbooks appear there.
Use the private addressbook for private destinations, these are not published.
</li>
</ol>
<h3>Options</h3>
<ul>
<li><b>subscriptions</b> - file containing the list of subscriptions URLs (no need to change)</li>
<li><b>update_delay</b> - update interval in hours (no need to change)</li>
<li><b>published_addressbook</b> - your public hosts.txt file (choose a path within your webserver document root)</li>
<li><b>router_addressbook</b> - your hosts.txt (no need to change)</li>
<li><b>master_addressbook</b> - your personal addressbook, it gets never overwritten by the addressbook</li>
<li><b>router_addressbook</b> - your hosts.txt (don't change)</li>
<li><b>master_addressbook</b> - your personal addressbook, it never gets overwritten by the addressbook (don't change)</li>
<li><b>private_addressbook</b> - your private addressbook, it is never published (defaults to ../privatehosts.txt, don't change)</li>
<li><b>proxy_port</b> - http port for your eepProxy (no need to change)</li>
<li><b>proxy_host</b> - hostname for your eepProxy (no need to change)</li>
<li><b>should_publish</b> - true/false whether to write the published addressbook</li>
@ -93,4 +95,4 @@ please change the master addressbook to a different file before turning on addre
<p class="footer">susidns v${version.version} &copy; <a href="${version.url}">susi</a> 2005 </p>
</div>
</body>
</html>
</html>

View File

@ -20,7 +20,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Revision: 1.1 $
* $Revision: 1.2 $
*/
%>
<%@ page contentType="text/html"%>
@ -42,9 +42,11 @@
<p>addressbooks
<a href="addressbook.jsp?book=master">master</a> |
<a href="addressbook.jsp?book=router">router</a> |
<a href="addressbook.jsp?book=published">published</a> *
<a href="addressbook.jsp?book=published">published</a> |
<a href="addressbook.jsp?book=private">private</a> *
<a href="subscriptions.jsp">subscriptions</a> *
<a href="config.jsp">configuration</a>
<a href="config.jsp">configuration</a> *
overview
</p>
</div>
@ -52,23 +54,25 @@
<h3>Huh? what addressbook?</h3>
<p>
The addressbook application is part of your i2p installation. It regularly updates your hosts.txt file
from distributed sources. It keeps your hosts.txt up to date, so it automatically contains all new
eepsites announced on <a href="http://orion.i2p">orion</a>
or in the <a href="http://forum.i2p/viewforum.php?f=16">forum</a>.
from distributed sources. It keeps your hosts.txt up to date, so it can automatically add
eepsites announced on other sites if you subscribe to those sites' addressbooks.
</p>
<p>
(To speak the truth: In its default configuration the addressbook does not poll
orion, but dev.i2p only. Subscribing to <a href="http://orion.i2p">orion</a> is an easy task,
just add <a href="http://orion.i2p/hosts.txt">http://orion.i2p/hosts.txt</a> to your <a href="subscriptions.jsp">subscriptions</a> file.)
additional sites, but www.i2p2.i2p only. Subscribing to additional sites is an easy task,
just add them to your <a href="subscriptions.jsp">subscriptions</a> file.)
</p>
<p>If you have questions about naming in i2p, there is an excellent <a href="http://forum.i2p.net/viewtopic.php?t=134">introduction</a>
from duck in the forum.</p>
<h3>How does the addressbook work?</h3>
<p>The addressbook application regularly (normally once per hour) polls your subscriptions and merges their content
into your so called router addressbook (normally your plain hosts.txt). Then it merges your so called master addressbook (normally
your userhosts.txt) into the router addressbook as well. If configured the router addressbook is now written to the so published addressbook,
which is a publicly available copy of your hosts.txt somewhere in your eepsites document root. (Yes, this means that, with activated publication,
your once private keys from userhosts.txt now are publicly available for everybody.)
into your so-called router addressbook (normally your plain hosts.txt). Then it merges your so-called master addressbook (normally
your userhosts.txt) into the router addressbook as well. If configured, the router addressbook is now written to the published addressbook,
which is a publicly available copy of your hosts.txt somewhere in your eepsite's document root.
</p><p>
The router also uses a private addressbook (privatehosts.txt, not shown in the picture), which is not merged or published.
Hosts in the private addressbook can be accessed by you but their addresses are never distributed to others.
The private addressbook can also be used for aliases of hosts in your other addressbooks.
</p>
<p><img src="images/how.png" border="0" alt="addressbook working scheme"/></p>
</div>

View File

@ -20,7 +20,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Revision: 1.7 $
* $Revision: 1.2 $
*/
%>
<%@ page contentType="text/html"%>
@ -42,9 +42,11 @@
<p>addressbooks
<a href="addressbook.jsp?book=master">master</a> |
<a href="addressbook.jsp?book=router">router</a> |
<a href="addressbook.jsp?book=published">published</a> *
<a href="subscriptions.jsp">subscriptions</a> *
<a href="config.jsp">configuration</a>
<a href="addressbook.jsp?book=published">published</a> |
<a href="addressbook.jsp?book=private">private</a> *
subscriptions *
<a href="config.jsp">configuration</a> *
<a href="index.jsp">overview</a>
</p>
</div>
<div id="headline">
@ -66,9 +68,8 @@
<p class="help">
The subscription file contains a list of (i2p) URLs. The addressbook application
regularly (once per hour) checks this list for new eepsites. Those URLs simply contain the published hosts.txt
file of other people. Default subscription is the hosts.txt from dev.i2p. The most
popular collaboration site for eepsite is orion.i2p. So its a good idea to add http://orion.i2p/hosts.txt
as a 2nd subscription.
file of other people. The default subscription is the hosts.txt from www.i2p2.i2p, which is updated infrequently.
So it is a good idea to add additional subscriptions to sites that have the latest addresses.
</p>
</div>
<div id="footer">

View File

@ -28,10 +28,13 @@ public class UpdaterServlet extends GenericServlet {
super.init(config);
} catch (ServletException exp) {
}
/*
UpdaterThread thread = new UpdaterThread();
thread.setDaemon(true);
thread.start();
System.out.println("INFO: Starting Syndie Updater " + Updater.VERSION);
*/
System.out.println("INFO: Syndie Updater DISABLED. Use the new Syndie from http://syndie.i2p.net/");
}
}

View File

@ -9,6 +9,11 @@
<a href="blogs.jsp">blogs</a></p>
<p><a href="post.jsp">Create</a> a new post of your own</p>
<p><a href="about.html">Learn more</a> about Syndie</p>
<p><b>NOTE:</b> This version of Syndie is being replaced by
<a href="http://syndie.i2p.net">the new Syndie</a>!
The new Syndie is a standalone application under active development.
Please give the new Syndie a try, as it has lots more traffic
than this version. Don't expect anybody to see your posts here.</p>
</td></tr></table>
</div>
</body></html>

View File

@ -7,6 +7,7 @@
<echo message=" installer: build the GUI installer" />
<echo message=" tarball: tar the full install into i2p.tar.bz2 (extracts to build a new clean install)" />
<echo message=" updater: tar the built i2p specific files into an i2pupdate.zip (extracts safely over existing installs)" />
<echo message=" updaterWithJetty: tar the built i2p specific files and jetty into an i2pupdate.zip (extracts safely over existing installs)" />
<echo message=" distclean: clean up all derived files" />
<echo message=" syndie: generate a standalone syndie install" />
<echo message=" i2psnark: generate a standalone i2psnark install" />
@ -29,7 +30,7 @@
<ant dir="apps/addressbook/" target="war" />
<ant dir="apps/susimail/" target="war" />
<ant dir="apps/susidns/src" target="all" />
<ant dir="apps/syndie/java/" target="jar" />
<!-- <ant dir="apps/syndie/java/" target="jar" /> -->
<ant dir="apps/i2psnark/java/" target="standalone" />
</target>
<target name="buildrouter">
@ -102,9 +103,11 @@
<copy file="apps/addressbook/dist/addressbook.war" todir="build/" />
<copy file="apps/susimail/susimail.war" todir="build/" />
<copy file="apps/susidns/src/susidns.war" todir="build/" />
<!--
<copy file="apps/syndie/syndie.war" todir="build/" />
<copy file="apps/syndie/java/build/syndie.jar" todir="build/" />
<copy file="apps/syndie/java/build/sucker.jar" todir="build/" />
-->
<copy file="apps/i2psnark/i2psnark.war" todir="build/" />
<copy file="apps/i2psnark/java/build/i2psnark.jar" todir="build/" />
<copy file="apps/jdom/jdom.jar" todir="build/" />
@ -135,6 +138,9 @@
<pathelement location="apps/i2ptunnel/java/src" />
<pathelement location="apps/systray/java/src" />
<pathelement location="apps/routerconsole/java/src" />
<pathelement location="apps/addressbook/java/src" />
<pathelement location="apps/i2psnark/java/src" />
<pathelement location="apps/sam/java/src" />
</sourcepath>
<classpath>
<pathelement location="apps/jetty/jettylib/org.mortbay.jetty.jar" />
@ -194,7 +200,7 @@
<copy file="build/routerconsole.jar" todir="pkg-temp/lib/" />
<copy file="build/sam.jar" todir="pkg-temp/lib/" />
<copy file="build/systray.jar" todir="pkg-temp/lib" />
<copy file="build/sucker.jar" todir="pkg-temp/lib" />
<!-- <copy file="build/sucker.jar" todir="pkg-temp/lib" /> -->
<copy file="build/i2psnark.jar" todir="pkg-temp/lib/" />
<copy file="i2p.exe" todir="pkg-temp/" failonerror="false" />
<copy file="installer/resources/runplain.sh" todir="pkg-temp/" />
@ -208,7 +214,7 @@
<copy file="build/addressbook.war" todir="pkg-temp/webapps/" />
<copy file="build/susimail.war" todir="pkg-temp/webapps/" />
<copy file="build/susidns.war" todir="pkg-temp/webapps/" />
<copy file="build/syndie.war" todir="pkg-temp/webapps/" />
<!-- <copy file="build/syndie.war" todir="pkg-temp/webapps/" /> -->
<copy file="build/i2psnark.war" todir="pkg-temp/webapps/" />
<copy file="apps/i2psnark/java/build/launch-i2psnark.jar" todir="pkg-temp/" />
<copy file="apps/i2psnark/jetty-i2psnark.xml" todir="pkg-temp/" />
@ -278,11 +284,13 @@
<copy file="installer/resources/eepsite_index.html" tofile="pkg-temp/eepsite/docroot/index.html" />
<copy file="installer/resources/favicon.ico" tofile="pkg-temp/eepsite/docroot/favicon.ico" />
<copy file="installer/resources/jetty.xml" tofile="pkg-temp/eepsite/jetty.xml" />
<!--
<mkdir dir="pkg-temp/syndie" />
<mkdir dir="pkg-temp/syndie/archive" />
<mkdir dir="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=" />
<copy file="installer/resources/blogMeta.snm" tofile="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/meta.snm" />
<copy file="installer/resources/blogPost.snd" tofile="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/1132012800001.snd" />
-->
</target>
<target name="tarball" depends="preppkg">
<tar compression="bzip2" destfile="i2p.tar.bz2">
@ -292,6 +300,9 @@
<target name="updater" depends="prepupdate">
<zip destfile="i2pupdate.zip" basedir="pkg-temp" />
</target>
<target name="updaterWithJetty" depends="prepjupdate">
<zip destfile="i2pupdate.zip" basedir="pkg-temp" />
</target>
<target name="updateTest" depends="prepupdate">
<ant dir="core/java/" target="jarTest" />
<copy file="core/java/build/i2ptest.jar" todir="pkg-temp/lib" />
@ -316,7 +327,7 @@
<copy file="build/addressbook.war" todir="pkg-temp/webapps/" />
<copy file="build/susimail.war" todir="pkg-temp/webapps/" />
<copy file="build/susidns.war" todir="pkg-temp/webapps/" />
<copy file="build/syndie.war" todir="pkg-temp/webapps/" />
<!-- <copy file="build/syndie.war" todir="pkg-temp/webapps/" /> -->
<copy file="build/i2psnark.war" todir="pkg-temp/webapps/" />
<!-- <copy file="apps/i2psnark/java/build/launch-i2psnark.jar" todir="pkg-temp/" /> -->
<copy file="apps/i2psnark/jetty-i2psnark.xml" todir="pkg-temp/" />
@ -347,11 +358,23 @@
<mkdir dir="pkg-temp/eepsite" />
<mkdir dir="pkg-temp/eepsite/webapps" />
<mkdir dir="pkg-temp/eepsite/cgi-bin" />
<!--
<mkdir dir="pkg-temp/syndie" />
<mkdir dir="pkg-temp/syndie/archive" />
<mkdir dir="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=" />
<copy file="installer/resources/blogMeta.snm" tofile="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/meta.snm" />
<copy file="installer/resources/blogPost.snd" tofile="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/1132012800001.snd" />
-->
</target>
<target name="prepjupdate" depends="prepupdate, buildWEB">
<copy file="build/ant.jar" todir="pkg-temp/lib/" />
<copy file="build/jasper-compiler.jar" todir="pkg-temp/lib/" />
<copy file="build/jasper-runtime.jar" todir="pkg-temp/lib/" />
<copy file="build/commons-logging.jar" todir="pkg-temp/lib/" />
<copy file="build/commons-el.jar" todir="pkg-temp/lib/" />
<copy file="build/javax.servlet.jar" todir="pkg-temp/lib/" />
<copy file="build/org.mortbay.jetty.jar" todir="pkg-temp/lib/" />
<copy file="build/xercesImpl.jar" todir="pkg-temp/lib/" />
</target>
<target name="installer" depends="preppkg">
<taskdef name="izpack" classpath="${basedir}/installer/lib/izpack/standalone-compiler.jar" classname="com.izforge.izpack.ant.IzPackTask" />

52
checklist.txt Normal file
View File

@ -0,0 +1,52 @@
Release checklist
-----------------
Sync with mtn.i2p2.i2p
Start with a clean checkout mtn -d i2p.mtn co --branch=i2p.i2p
Change revision in:
history.txt
initialNews.xml
installer/install.xml
news.xml
router/java/src/net/i2p/router/RouterVersion.java
Build and tag:
ant dist
mtn ci
mtn tag i2p-0.6.1.xx
Sync with mtn.i2p2.i2p
Create a signed update file with:
export I2P=~/i2p
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign i2pupdate.zip i2pupdate.sud /path/to/private.key 0.6.1.xx
Verify signed update file with:
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate showversion i2pupdate.sud
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate verifysig i2pupdate.sud
Make the source tarball:
Start with a clean checkout mtn -d i2p.mtn co --branch=i2p.i2p i2p-0.6.1.xx
tar tjf i2p-0.6.1.xx.tar.bz2 --exclude i2p-0.6.1.xx/_MTN i2p-0.6.1.xx
mv i2p-0.6.1.xx.tar.bz2 i2p.i2p
More signatures:
sha1sum i2pinstall.exe i2p.tar.bz2 i2p-0.6.1.xx.tar.bz2 i2pupdate.zip
gpg -b i2pinstall.exe
gpg -b i2p.tar.bz2
gpg -b i2p-0.6.1.xx.tar.bz2
gpg -b i2pupdate.zip
Distribute files to download locations and to www.i2p2.i2p
Website files to change:
Sync with mtn.i2p2.i2p
announcements.html
download.html (change SHA1s)
index.html
hosts.txt (copy from mtn)
Sync with mtn.i2p2.i2p
Copy news.xml to subscription location
Announce on #i2p, forum.i2p, Syndie

View File

@ -32,7 +32,7 @@ public class CPUID {
* initialization? this would otherwise use the Log component, but this makes
* it easier for other systems to reuse this class
*/
private static final boolean _doLog = true;
private static final boolean _doLog = System.getProperty("jcpuid.dontLog") == null;
//.matches() is a java 1.4+ addition, using a simplified version for 1.3+
//private static final boolean isX86 = System.getProperty("os.arch").toLowerCase().matches("i?[x0-9]86(_64)?");

View File

@ -1,7 +1,7 @@
package gnu.crypto.hash;
// ----------------------------------------------------------------------------
// $Id: BaseHash.java,v 1.10 2005/10/06 04:24:14 rsdio Exp $
// $Id: BaseHashStandalone.java,v 1.1 2006/02/26 16:30:59 jrandom Exp $
//
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
//
@ -46,9 +46,9 @@ package gnu.crypto.hash;
/**
* <p>A base abstract class to facilitate hash implementations.</p>
*
* @version $Revision: 1.10 $
* @version $Revision: 1.1 $
*/
public abstract class BaseHash implements IMessageDigest {
public abstract class BaseHashStandalone implements IMessageDigestStandalone {
// Constants and variables
// -------------------------------------------------------------------------
@ -78,7 +78,7 @@ public abstract class BaseHash implements IMessageDigest {
* @param hashSize the block size of the output in bytes.
* @param blockSize the block size of the internal transform.
*/
protected BaseHash(String name, int hashSize, int blockSize) {
protected BaseHashStandalone(String name, int hashSize, int blockSize) {
super();
this.name = name;
@ -95,7 +95,7 @@ public abstract class BaseHash implements IMessageDigest {
// Instance methods
// -------------------------------------------------------------------------
// IMessageDigest interface implementation ---------------------------------
// IMessageDigestStandalone interface implementation ---------------------------------
public String name() {
return name;

View File

@ -1,7 +1,7 @@
package gnu.crypto.hash;
// ----------------------------------------------------------------------------
// $Id: IMessageDigest.java,v 1.11 2005/10/06 04:24:14 rsdio Exp $
// $Id: IMessageDigestStandalone.java,v 1.1 2006/02/26 16:30:59 jrandom Exp $
//
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
//
@ -49,9 +49,9 @@ package gnu.crypto.hash;
* <p>A hash (or message digest) algorithm produces its output by iterating a
* basic compression function on blocks of data.</p>
*
* @version $Revision: 1.11 $
* @version $Revision: 1.1 $
*/
public interface IMessageDigest extends Cloneable {
public interface IMessageDigestStandalone extends Cloneable {
// Constants
// -------------------------------------------------------------------------

View File

@ -1,7 +1,7 @@
package gnu.crypto.hash;
// ----------------------------------------------------------------------------
// $Id: Sha256Standalone.java,v 1.1 2006/02/26 16:30:59 jrandom Exp $
// $Id: Sha256Standalone.java,v 1.2 2006/03/16 16:45:19 jrandom Exp $
//
// Copyright (C) 2003 Free Software Foundation, Inc.
//
@ -59,9 +59,9 @@ package gnu.crypto.hash;
* renamed from Sha256 to avoid conflicts with JVMs using gnu-crypto as their JCE
* provider.
*
* @version $Revision: 1.1 $
* @version $Revision: 1.2 $
*/
public class Sha256Standalone extends BaseHash {
public class Sha256Standalone extends BaseHashStandalone {
// Constants and variables
// -------------------------------------------------------------------------
private static final int[] k = {
@ -143,7 +143,7 @@ public class Sha256Standalone extends BaseHash {
return new Sha256Standalone(this);
}
// Implementation of concrete methods in BaseHash --------------------------
// Implementation of concrete methods in BaseHashStandalone --------------------------
private int transformResult[] = new int[8];
protected void transform(byte[] in, int offset) {

View File

@ -0,0 +1,175 @@
package gnu.crypto.prng;
import java.util.*;
/**
* fortuna instance that tries to avoid blocking if at all possible by using separate
* filled buffer segments rather than one buffer (and blocking when that buffer's data
* has been eaten)
*/
public class AsyncFortunaStandalone extends FortunaStandalone implements Runnable {
private static final int BUFFERS = 16;
private static final int BUFSIZE = 256*1024;
private final byte asyncBuffers[][] = new byte[BUFFERS][BUFSIZE];
private final int status[] = new int[BUFFERS];
private int nextBuf = 0;
private static final int STATUS_NEED_FILL = 0;
private static final int STATUS_FILLING = 1;
private static final int STATUS_FILLED = 2;
private static final int STATUS_LIVE = 3;
public AsyncFortunaStandalone() {
super();
for (int i = 0; i < BUFFERS; i++)
status[i] = STATUS_NEED_FILL;
}
public void startup() {
Thread refillThread = new Thread(this, "PRNG");
refillThread.setDaemon(true);
refillThread.setPriority(Thread.MIN_PRIORITY+1);
refillThread.start();
}
/** the seed is only propogated once the prng is started with startup() */
public void seed(byte val[]) {
Map props = new HashMap(1);
props.put(SEED, (Object)val);
init(props);
//fillBlock();
}
protected void allocBuffer() {}
/**
* make the next available filled buffer current, scheduling any unfilled
* buffers for refill, and blocking until at least one buffer is ready
*/
protected void rotateBuffer() {
synchronized (asyncBuffers) {
// wait until we get some filled
long before = System.currentTimeMillis();
long waited = 0;
while (status[nextBuf] != STATUS_FILLED) {
//System.out.println(Thread.currentThread().getName() + ": Next PRNG buffer "
// + nextBuf + " isn't ready (" + status[nextBuf] + ")");
//new Exception("source").printStackTrace();
asyncBuffers.notifyAll();
try {
asyncBuffers.wait();
} catch (InterruptedException ie) {}
waited = System.currentTimeMillis()-before;
}
if (waited > 10*1000)
System.out.println(Thread.currentThread().getName() + ": Took " + waited
+ "ms for a full PRNG buffer to be found");
//System.out.println(Thread.currentThread().getName() + ": Switching to prng buffer " + nextBuf);
buffer = asyncBuffers[nextBuf];
status[nextBuf] = STATUS_LIVE;
int prev=nextBuf-1;
if (prev<0)
prev = BUFFERS-1;
if (status[prev] == STATUS_LIVE)
status[prev] = STATUS_NEED_FILL;
nextBuf++;
if (nextBuf >= BUFFERS)
nextBuf = 0;
asyncBuffers.notify();
}
}
public void run() {
while (true) {
int toFill = -1;
try {
synchronized (asyncBuffers) {
for (int i = 0; i < BUFFERS; i++) {
if (status[i] == STATUS_NEED_FILL) {
status[i] = STATUS_FILLING;
toFill = i;
break;
}
}
if (toFill == -1) {
//System.out.println(Thread.currentThread().getName() + ": All pending buffers full");
asyncBuffers.wait();
}
}
} catch (InterruptedException ie) {}
if (toFill != -1) {
//System.out.println(Thread.currentThread().getName() + ": Filling prng buffer " + toFill);
long before = System.currentTimeMillis();
doFill(asyncBuffers[toFill]);
long after = System.currentTimeMillis();
synchronized (asyncBuffers) {
status[toFill] = STATUS_FILLED;
//System.out.println(Thread.currentThread().getName() + ": Prng buffer " + toFill + " filled after " + (after-before));
asyncBuffers.notifyAll();
}
Thread.yield();
long waitTime = (after-before)*5;
if (waitTime <= 0) // somehow postman saw waitTime show up as negative
waitTime = 50;
try { Thread.sleep(waitTime); } catch (InterruptedException ie) {}
}
}
}
public void fillBlock()
{
rotateBuffer();
}
private void doFill(byte buf[]) {
long start = System.currentTimeMillis();
if (pool0Count >= MIN_POOL_SIZE
&& System.currentTimeMillis() - lastReseed > 100)
{
reseedCount++;
//byte[] seed = new byte[0];
for (int i = 0; i < NUM_POOLS; i++)
{
if (reseedCount % (1 << i) == 0) {
generator.addRandomBytes(pools[i].digest());
}
}
lastReseed = System.currentTimeMillis();
}
generator.nextBytes(buf);
long now = System.currentTimeMillis();
long diff = now-lastRefill;
lastRefill = now;
long refillTime = now-start;
//System.out.println("Refilling " + (++refillCount) + " after " + diff + " for the PRNG took " + refillTime);
}
public static void main(String args[]) {
try {
AsyncFortunaStandalone rand = new AsyncFortunaStandalone();
byte seed[] = new byte[1024];
rand.seed(seed);
System.out.println("Before starting prng");
rand.startup();
System.out.println("Starting prng, waiting 1 minute");
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
System.out.println("PRNG started, beginning test");
long before = System.currentTimeMillis();
byte buf[] = new byte[1024];
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
java.util.zip.GZIPOutputStream gos = new java.util.zip.GZIPOutputStream(baos);
for (int i = 0; i < 1024; i++) {
rand.nextBytes(buf);
gos.write(buf);
}
long after = System.currentTimeMillis();
gos.finish();
byte compressed[] = baos.toByteArray();
System.out.println("Compressed size of 1MB: " + compressed.length + " took " + (after-before));
} catch (Exception e) { e.printStackTrace(); }
try { Thread.sleep(5*60*1000); } catch (InterruptedException ie) {}
}
}

View File

@ -49,7 +49,7 @@ import java.util.Map;
* Modified slightly by jrandom for I2P (removing unneeded exceptions)
* @version $Revision: 1.1 $
*/
public abstract class BasePRNGStandalone implements IRandom {
public abstract class BasePRNGStandalone implements IRandomStandalone {
// Constants and variables
// -------------------------------------------------------------------------
@ -88,7 +88,7 @@ public abstract class BasePRNGStandalone implements IRandom {
// Instance methods
// -------------------------------------------------------------------------
// IRandom interface implementation ----------------------------------------
// IRandomStandalone interface implementation ----------------------------------------
public String name() {
return name;

View File

@ -97,20 +97,22 @@ import net.i2p.crypto.CryptixAESKeyCache;
* gnu-crypto implementation, which has been imported into GNU/classpath
*
*/
public class FortunaStandalone extends BasePRNGStandalone implements Serializable, RandomEventListener
public class FortunaStandalone extends BasePRNGStandalone implements Serializable, RandomEventListenerStandalone
{
private static final long serialVersionUID = 0xFACADE;
private static final int SEED_FILE_SIZE = 64;
private static final int NUM_POOLS = 32;
private static final int MIN_POOL_SIZE = 64;
private final Generator generator;
private final Sha256Standalone[] pools;
private long lastReseed;
private int pool;
private int pool0Count;
private int reseedCount;
static final int NUM_POOLS = 32;
static final int MIN_POOL_SIZE = 64;
final Generator generator;
final Sha256Standalone[] pools;
long lastReseed;
int pool;
int pool0Count;
int reseedCount;
static long refillCount = 0;
static long lastRefill = System.currentTimeMillis();
public static final String SEED = "gnu.crypto.prng.fortuna.seed";
@ -124,6 +126,9 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
lastReseed = 0;
pool = 0;
pool0Count = 0;
allocBuffer();
}
protected void allocBuffer() {
buffer = new byte[4*1024*1024]; //256]; // larger buffer to reduce churn
}
@ -145,6 +150,7 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
public void fillBlock()
{
long start = System.currentTimeMillis();
if (pool0Count >= MIN_POOL_SIZE
&& System.currentTimeMillis() - lastReseed > 100)
{
@ -159,6 +165,11 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
lastReseed = System.currentTimeMillis();
}
generator.nextBytes(buffer);
long now = System.currentTimeMillis();
long diff = now-lastRefill;
lastRefill = now;
long refillTime = now-start;
System.out.println("Refilling " + (++refillCount) + " after " + diff + " for the PRNG took " + refillTime);
}
public void addRandomByte(byte b)
@ -177,7 +188,7 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
pool = (pool + 1) % NUM_POOLS;
}
public void addRandomEvent(RandomEvent event)
public void addRandomEvent(RandomEventStandalone event)
{
if (event.getPoolNumber() < 0 || event.getPoolNumber() >= pools.length)
throw new IllegalArgumentException("pool number out of range: "
@ -338,6 +349,34 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
}
public static void main(String args[]) {
byte in[] = new byte[16];
byte out[] = new byte[16];
byte key[] = new byte[32];
try {
CryptixAESKeyCache.KeyCacheEntry buf = CryptixAESKeyCache.createNew();
Object cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, buf);
long beforeAll = System.currentTimeMillis();
for (int i = 0; i < 256; i++) {
//long before =System.currentTimeMillis();
for (int j = 0; j < 1024; j++)
CryptixRijndael_Algorithm.blockEncrypt(in, out, 0, 0, cryptixKey);
//long after = System.currentTimeMillis();
//System.out.println("encrypting 16KB took " + (after-before));
}
long after = System.currentTimeMillis();
System.out.println("encrypting 4MB took " + (after-beforeAll));
} catch (Exception e) { e.printStackTrace(); }
try {
CryptixAESKeyCache.KeyCacheEntry buf = CryptixAESKeyCache.createNew();
Object cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, buf);
byte data[] = new byte[4*1024*1024];
long beforeAll = System.currentTimeMillis();
//CryptixRijndael_Algorithm.ecbBulkEncrypt(data, data, cryptixKey);
long after = System.currentTimeMillis();
System.out.println("encrypting 4MB took " + (after-beforeAll));
} catch (Exception e) { e.printStackTrace(); }
/*
FortunaStandalone f = new FortunaStandalone();
java.util.HashMap props = new java.util.HashMap();
byte initSeed[] = new byte[1234];
@ -351,5 +390,6 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
}
long time = System.currentTimeMillis() - before;
System.out.println("512MB took " + time + ", or " + (8*64d)/((double)time/1000d) +"MBps");
*/
}
}

View File

@ -1,7 +1,7 @@
package gnu.crypto.prng;
// ----------------------------------------------------------------------------
// $Id: IRandom.java,v 1.12 2005/10/06 04:24:17 rsdio Exp $
// $Id: IRandomStandalone.java,v 1.1 2005/10/22 13:10:00 jrandom Exp $
//
// Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
//
@ -82,9 +82,9 @@ import java.util.Map;
* Menezes, A., van Oorschot, P. and S. Vanstone.</li>
* </ol>
*
* @version $Revision: 1.12 $
* @version $Revision: 1.1 $
*/
public interface IRandom extends Cloneable {
public interface IRandomStandalone extends Cloneable {
// Constants
// -------------------------------------------------------------------------
@ -110,32 +110,32 @@ public interface IRandom extends Cloneable {
void init(Map attributes);
/**
* <p>Returns the next 8 bits of random data generated from this instance.</p>
*
* @return the next 8 bits of random data generated from this instance.
* @exception IllegalStateException if the instance is not yet initialised.
* @exception LimitReachedException if this instance has reached its
* theoretical limit for generating non-repetitive pseudo-random data.
*/
byte nextByte() throws IllegalStateException, LimitReachedException;
* <p>Returns the next 8 bits of random data generated from this instance.</p>
*
* @return the next 8 bits of random data generated from this instance.
* @exception IllegalStateException if the instance is not yet initialised.
* @exception LimLimitReachedExceptionStandalone this instance has reached its
* theoretical limit for generating non-repetitive pseudo-random data.
*/
byte nextByte() throws IllegalStateException, LimitReachedExceptionStandalone;
/**
* <p>Fills the designated byte array, starting from byte at index
* <code>offset</code>, for a maximum of <code>length</code> bytes with the
* output of this generator instance.
*
* @param out the placeholder to contain the generated random bytes.
* @param offset the starting index in <i>out</i> to consider. This method
* does nothing if this parameter is not within <code>0</code> and
* <code>out.length</code>.
* @param length the maximum number of required random bytes. This method
* does nothing if this parameter is less than <code>1</code>.
* @exception IllegalStateException if the instance is not yet initialised.
* @exception LimitReachedException if this instance has reached its
* theoretical limit for generating non-repetitive pseudo-random data.
*/
* <p>Fills the designated byte array, starting from byte at index
* <code>offset</code>, for a maximum of <code>length</code> bytes with the
* output of this generator instance.
*
* @param out the placeholder to contain the generated random bytes.
* @param offset the starting index in <i>out</i> to consider. This method
* does nothing if this parameter is not within <code>0</code> and
* <code>out.length</code>.
* @param length the maximum number of required random bytes. This method
* does nothing if this parameter is less than <code>1</code>.
* @exception IllegalStateException if the instance is not yet initialised.
* @exception LimitLimitReachedExceptionStandalonehis instance has reached its
* theoretical limit for generating non-repetitive pseudo-random data.
*/
void nextBytes(byte[] out, int offset, int length)
throws IllegalStateException, LimitReachedException;
throws IllegalStateException, LimitReachedExceptionStandalone;
/**
* <p>Supplement, or possibly replace, the random state of this PRNG with

View File

@ -1,7 +1,7 @@
package gnu.crypto.prng;
// ----------------------------------------------------------------------------
// $Id: LimitReachedException.java,v 1.5 2005/10/06 04:24:17 rsdio Exp $
// $Id: LimitReachedExceptionStandalone.java,v 1.1 2005/10/22 13:10:00 jrandom Exp $
//
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
//
@ -47,9 +47,9 @@ package gnu.crypto.prng;
* A checked exception that indicates that a pseudo random number generated has
* reached its theoretical limit in generating random bytes.
*
* @version $Revision: 1.5 $
* @version $Revision: 1.1 $
*/
public class LimitReachedException extends Exception {
public class LimitReachedExceptionStandalone extends Exception {
// Constants and variables
// -------------------------------------------------------------------------
@ -57,11 +57,11 @@ public class LimitReachedException extends Exception {
// Constructor(s)
// -------------------------------------------------------------------------
public LimitReachedException() {
public LimitReachedExceptionStandalone() {
super();
}
public LimitReachedException(String msg) {
public LimitReachedExceptionStandalone(String msg) {
super(msg);
}

View File

@ -1,4 +1,4 @@
/* RandomEventListener.java -- event listener
/* RandomEventListenerStandalone.java -- event listener
Copyright (C) 2004 Free Software Foundation, Inc.
This file is part of GNU Crypto.
@ -47,7 +47,7 @@ import java.util.EventListener;
* An interface for entropy accumulators that will be notified of random
* events.
*/
public interface RandomEventListener extends EventListener
public interface RandomEventListenerStandalone extends EventListener
{
void addRandomEvent(RandomEvent event);
void addRandomEvent(RandomEventStandalone event);
}

View File

@ -1,4 +1,4 @@
/* RandomEvent.java -- a random event.
/* RandomEventStandalone.java -- a random event.
Copyright (C) 2004 Free Software Foundation, Inc.
This file is part of GNU Crypto.
@ -47,14 +47,14 @@ import java.util.EventObject;
* An interface for entropy accumulators that will be notified of random
* events.
*/
public class RandomEvent extends EventObject
public class RandomEventStandalone extends EventObject
{
private final byte sourceNumber;
private final byte poolNumber;
private final byte[] data;
public RandomEvent(Object source, byte sourceNumber, byte poolNumber,
public RandomEventStandalone(Object source, byte sourceNumber, byte poolNumber,
byte[] data)
{
super(source);

View File

@ -14,8 +14,8 @@ package net.i2p;
*
*/
public class CoreVersion {
public final static String ID = "$Revision: 1.63 $ $Date: 2006-06-04 17:25:14 $";
public final static String VERSION = "0.6.1.21";
public final static String ID = "$Revision: 1.72 $ $Date: 2007-08-23 19:33:31 $";
public final static String VERSION = "0.6.1.30";
public static void main(String args[]) {
System.out.println("I2P Core version: " + VERSION);

View File

@ -14,6 +14,7 @@ import net.i2p.crypto.DummyElGamalEngine;
import net.i2p.crypto.DummyPooledRandomSource;
import net.i2p.crypto.ElGamalAESEngine;
import net.i2p.crypto.ElGamalEngine;
import net.i2p.crypto.HMAC256Generator;
import net.i2p.crypto.HMACGenerator;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.PersistentSessionKeyManager;
@ -67,8 +68,9 @@ public class I2PAppContext {
private AESEngine _AESEngine;
private LogManager _logManager;
private HMACGenerator _hmac;
private HMAC256Generator _hmac256;
private SHA256Generator _sha;
private Clock _clock;
protected Clock _clock; // overridden in RouterContext
private DSAEngine _dsa;
private RoutingKeyGenerator _routingKeyGenerator;
private RandomSource _random;
@ -82,8 +84,9 @@ public class I2PAppContext {
private volatile boolean _AESEngineInitialized;
private volatile boolean _logManagerInitialized;
private volatile boolean _hmacInitialized;
private volatile boolean _hmac256Initialized;
private volatile boolean _shaInitialized;
private volatile boolean _clockInitialized;
protected volatile boolean _clockInitialized; // used in RouterContext
private volatile boolean _dsaInitialized;
private volatile boolean _routingKeyGeneratorInitialized;
private volatile boolean _randomInitialized;
@ -353,6 +356,19 @@ public class I2PAppContext {
_hmacInitialized = true;
}
}
public HMAC256Generator hmac256() {
if (!_hmac256Initialized) initializeHMAC256();
return _hmac256;
}
private void initializeHMAC256() {
synchronized (this) {
if (_hmac256 == null) {
_hmac256 = new HMAC256Generator(this);
}
_hmac256Initialized = true;
}
}
/**
* Our SHA256 instance (see the hmac discussion for why its context specific)
@ -411,11 +427,11 @@ public class I2PAppContext {
* enable simulators to play with clock skew among different instances.
*
*/
public Clock clock() {
public Clock clock() { // overridden in RouterContext
if (!_clockInitialized) initializeClock();
return _clock;
}
private void initializeClock() {
protected void initializeClock() { // overridden in RouterContext
synchronized (this) {
if (_clock == null)
_clock = new Clock(this);

View File

@ -320,9 +320,9 @@ public class DHSessionKeyBuilder {
if (_myPrivateValue == null) generateMyValue();
_sessionKey = calculateSessionKey(_myPrivateValue, _peerValue);
} else {
System.err.println("Not ready yet.. privateValue and peerValue must be set ("
+ (_myPrivateValue != null ? "set" : "null") + ","
+ (_peerValue != null ? "set" : "null") + ")");
//System.err.println("Not ready yet.. privateValue and peerValue must be set ("
// + (_myPrivateValue != null ? "set" : "null") + ","
// + (_peerValue != null ? "set" : "null") + ")");
}
return _sessionKey;
}

View File

@ -0,0 +1,51 @@
package net.i2p.crypto;
import gnu.crypto.hash.Sha256Standalone;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.macs.HMac;
/**
* Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs
* in {@link org.bouncycastle.crypto.macs.HMac} and
* {@link org.bouncycastle.crypto.digests.MD5Digest}.
*
*/
public class HMAC256Generator extends HMACGenerator {
public HMAC256Generator(I2PAppContext context) { super(context); }
protected HMac acquire() {
synchronized (_available) {
if (_available.size() > 0)
return (HMac)_available.remove(0);
}
// the HMAC is hardcoded to use SHA256 digest size
// for backwards compatability. next time we have a backwards
// incompatible change, we should update this by removing ", 32"
return new HMac(new Sha256ForMAC());
}
private class Sha256ForMAC extends Sha256Standalone implements Digest {
public String getAlgorithmName() { return "sha256 for hmac"; }
public int getDigestSize() { return 32; }
public int doFinal(byte[] out, int outOff) {
byte rv[] = digest();
System.arraycopy(rv, 0, out, outOff, rv.length);
reset();
return rv.length;
}
}
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
byte data[] = new byte[64];
ctx.random().nextBytes(data);
SessionKey key = ctx.keyGenerator().generateSessionKey();
Hash mac = ctx.hmac256().calculate(key, data);
System.out.println(Base64.encode(mac.getData()));
}
}

View File

@ -20,7 +20,7 @@ import org.bouncycastle.crypto.macs.HMac;
public class HMACGenerator {
private I2PAppContext _context;
/** set of available HMAC instances for calculate */
private List _available;
protected List _available;
/** set of available byte[] buffers for verify */
private List _availableTmp;
@ -85,7 +85,7 @@ public class HMACGenerator {
return eq;
}
private HMac acquire() {
protected HMac acquire() {
synchronized (_available) {
if (_available.size() > 0)
return (HMac)_available.remove(0);

View File

@ -9,10 +9,13 @@ package net.i2p.crypto;
*
*/
import gnu.crypto.hash.Sha256Standalone;
import java.math.BigInteger;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
@ -53,6 +56,18 @@ public class KeyGenerator {
return key;
}
private static final int PBE_ROUNDS = 1000;
/** PBE the passphrase with the salt */
public SessionKey generateSessionKey(byte salt[], byte passphrase[]) {
byte salted[] = new byte[16+passphrase.length];
System.arraycopy(salt, 0, salted, 0, Math.min(salt.length, 16));
System.arraycopy(passphrase, 0, salted, 16, passphrase.length);
byte h[] = _context.sha().calculateHash(salted).getData();
for (int i = 1; i < PBE_ROUNDS; i++)
_context.sha().calculateHash(h, 0, Hash.HASH_LENGTH, h, 0);
return new SessionKey(h);
}
/** standard exponent size */
private static final int PUBKEY_EXPONENT_SIZE_FULL = 2048;
/**
@ -95,7 +110,7 @@ public class KeyGenerator {
* @return the corresponding PublicKey object
*/
public static PublicKey getPublicKey(PrivateKey priv) {
BigInteger a = new NativeBigInteger(priv.toByteArray());
BigInteger a = new NativeBigInteger(1, priv.toByteArray());
BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp);
PublicKey pub = new PublicKey();
byte [] pubBytes = aalpha.toByteArray();
@ -132,7 +147,7 @@ public class KeyGenerator {
* @return a SigningPublicKey object
*/
public static SigningPublicKey getSigningPublicKey(SigningPrivateKey priv) {
BigInteger x = new NativeBigInteger(priv.toByteArray());
BigInteger x = new NativeBigInteger(1, priv.toByteArray());
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
SigningPublicKey pub = new SigningPublicKey();
byte [] pubBytes = padBuffer(y.toByteArray(), SigningPublicKey.KEYSIZE_BYTES);

View File

@ -67,7 +67,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
_log = context.logManager().getLog(TransientSessionKeyManager.class);
_context = context;
_outboundSessions = new HashMap(1024);
_inboundTagSets = new HashMap(64*1024);
_inboundTagSets = new HashMap(1024);
context.statManager().createRateStat("crypto.sessionTagsExpired", "How many tags/sessions are expired?", "Encryption", new long[] { 10*60*1000, 60*60*1000, 3*60*60*1000 });
context.statManager().createRateStat("crypto.sessionTagsRemaining", "How many tags/sessions are remaining after a cleanup?", "Encryption", new long[] { 10*60*1000, 60*60*1000, 3*60*60*1000 });
SimpleTimer.getInstance().addEvent(new CleanupEvent(), 60*1000);

View File

@ -60,6 +60,45 @@ jP69nPbh4KLGhF+SD0+0bW4=
=npPe
-----END PGP SIGNATURE-----
*/
/*
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
*/
/* zzz's key */
private static final String DEFAULT_TRUSTED_KEY2 =
"lT54eq3SH0TWWwQ1wgH6XPelIno7wH7UfiZOpQg-ZuxdNhc4UjjrohKdK" +
"Zqfswt1ANPnmOlMewLGBESl7kJB9c5sByz~IOlNyz5BMLRC~R~ZC9QI4W" +
"XwUBYW8BhYO2mkvtdOrcy690lDkwzdf5xLxlCBpQlTaLYzQVjVWBcvbCA=";
/*
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
iD8DBQFHdupcQVV2uqduC+0RAocuAKCR4ILLuz3RB8QT7zkadmS2LmFuMwCgweqG
lFm5Fqx/iW5+k0QaQZ3W9mY=
=V3i7
-----END PGP SIGNATURE-----
*/
/*
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
*/
/* Complication's key */
private static final String DEFAULT_TRUSTED_KEY3 =
"JHFA0yXUgKtmhajXFZH9Nk62OPRHbvvQHTi8EANV-D~3tjLjaz9p9cs6F" +
"s8W3FSLfUwsQeFg7dfVSQQZga~1jMjboo94vIcm3j6XbW4mbcorVQ74uP" +
"jd8EA1AQhJ6bBTxDAFk~6fVDOdhHT0Wo5CcUn7v8bAYY3x3UWiL8Remx0=";
/*
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
iD8DBQFHphOV+h38a3n8zjMRAll+AJ9KA6WiDJcTN4qfrslSemUMr+FBrwCeM8pF
D8usM7Dxp5yrDrCYZ5AIijc=
=SrXI
-----END PGP SIGNATURE-----
*/
private static final String VALID_VERSION_CHARS = "0123456789.";
private static final int VERSION_BYTES = 16;
private static final int HEADER_BYTES = Signature.SIGNATURE_BYTES + VERSION_BYTES;
@ -92,14 +131,18 @@ jP69nPbh4KLGhF+SD0+0bW4=
String propertyTrustedKeys = context.getProperty(PROP_TRUSTED_KEYS);
if ( (propertyTrustedKeys != null) && (propertyTrustedKeys.length() > 0) ) {
StringTokenizer propertyTrustedKeysTokens = new StringTokenizer(propertyTrustedKeys, ",");
StringTokenizer propertyTrustedKeysTokens = new StringTokenizer(propertyTrustedKeys, " ,\r\n");
while (propertyTrustedKeysTokens.hasMoreTokens())
_trustedKeys.add(propertyTrustedKeysTokens.nextToken().trim());
} else {
_trustedKeys.add(DEFAULT_TRUSTED_KEY);
_trustedKeys.add(DEFAULT_TRUSTED_KEY2);
_trustedKeys.add(DEFAULT_TRUSTED_KEY3);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("TrustedUpdate created, trusting " + _trustedKeys.size() + " keys.");
}
/**
@ -180,6 +223,7 @@ jP69nPbh4KLGhF+SD0+0bW4=
private static final void genKeysCLI(String publicKeyFile, String privateKeyFile) {
FileOutputStream fileOutputStream = null;
_context = I2PAppContext.getGlobalContext();
try {
Object signingKeypair[] = _context.keyGenerator().generateSigningKeypair();
SigningPublicKey signingPublicKey = (SigningPublicKey) signingKeypair[0];
@ -273,7 +317,26 @@ jP69nPbh4KLGhF+SD0+0bW4=
public ArrayList getTrustedKeys() {
return _trustedKeys;
}
/**
* Fetches the trusted keys for the current instance.
*
* @return A <code>String</code> containing the trusted keys,
* delimited by CR LF line breaks.
*/
public String getTrustedKeysString() {
StringBuffer buf = new StringBuffer(1024);
for (int i = 0; i < _trustedKeys.size(); i++) {
// If something already buffered, first add line break.
if (buf.length() > 0) buf.append("\r\n");
buf.append((String) _trustedKeys.get(i));
}
return buf.toString();
}
/**
* Reads the version string from a signed update file.
*

View File

@ -9,6 +9,7 @@ package net.i2p.data;
*
*/
import gnu.crypto.hash.Sha256Standalone;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
@ -235,6 +236,10 @@ public class DataHelper {
if (split <= 0) continue;
String key = line.substring(0, split);
String val = line.substring(split+1);
// Unescape line breaks after loading.
// Remember: "\" needs escaping both for regex and string.
val = val.replaceAll("\\\\r","\r");
val = val.replaceAll("\\\\n","\n");
if ( (key.length() > 0) && (val.length() > 0) )
if (forceLowerCase)
props.setProperty(key.toLowerCase(), val);
@ -770,9 +775,11 @@ public class DataHelper {
* Read a newline delimited line from the stream, returning the line (without
* the newline), or null if EOF reached before the newline was found
*/
public static String readLine(InputStream in) throws IOException {
public static String readLine(InputStream in) throws IOException { return readLine(in, (Sha256Standalone)null); }
/** update the hash along the way */
public static String readLine(InputStream in, Sha256Standalone hash) throws IOException {
StringBuffer buf = new StringBuffer(128);
boolean ok = readLine(in, buf);
boolean ok = readLine(in, buf, hash);
if (ok)
return buf.toString();
else
@ -785,8 +792,13 @@ public class DataHelper {
* newline was found
*/
public static boolean readLine(InputStream in, StringBuffer buf) throws IOException {
return readLine(in, buf, null);
}
/** update the hash along the way */
public static boolean readLine(InputStream in, StringBuffer buf, Sha256Standalone hash) throws IOException {
int c = -1;
while ( (c = in.read()) != -1) {
if (hash != null) hash.update((byte)c);
if (c == '\n')
break;
buf.append((char)c);
@ -797,6 +809,10 @@ public class DataHelper {
return true;
}
public static void write(OutputStream out, byte data[], Sha256Standalone hash) throws IOException {
hash.update(data);
out.write(data);
}
public static List sortStructures(Collection dataStructures) {
if (dataStructures == null) return new ArrayList();
@ -822,6 +838,8 @@ public class DataHelper {
return (ms / (60 * 1000)) + "m";
} else if (ms < 3 * 24 * 60 * 60 * 1000) {
return (ms / (60 * 60 * 1000)) + "h";
} else if (ms > 365l * 24l * 60l * 60l * 1000l) {
return "n/a";
} else {
return (ms / (24 * 60 * 60 * 1000)) + "d";
}

View File

@ -32,6 +32,7 @@ public class PrivateKey extends DataStructureImpl {
public PrivateKey() {
setData(null);
}
public PrivateKey(byte data[]) { setData(data); }
/** constructs from base64
* @param base64Data a string of base64 data (the output of .toBase64() called

View File

@ -31,6 +31,11 @@ public class PublicKey extends DataStructureImpl {
public PublicKey() {
setData(null);
}
public PublicKey(byte data[]) {
if ( (data == null) || (data.length != KEYSIZE_BYTES) )
throw new IllegalArgumentException("Data must be specified, and the correct size");
setData(data);
}
/** constructs from base64
* @param base64Data a string of base64 data (the output of .toBase64() called

View File

@ -86,7 +86,7 @@ public class RouterIdentity extends DataStructureImpl {
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if ((_certificate == null) || (_publicKey == null) || (_signingKey == null))
throw new DataFormatException("Not enough data to format the destination");
throw new DataFormatException("Not enough data to format the router identity");
_publicKey.writeBytes(out);
_signingKey.writeBytes(out);
_certificate.writeBytes(out);

View File

@ -53,6 +53,9 @@ public class RouterInfo extends DataStructureImpl {
public static final String PROP_CAPABILITIES = "caps";
public static final char CAPABILITY_HIDDEN = 'H';
// Public string of chars which serve as bandwidth capacity markers
// NOTE: individual chars defined in Router.java
public static final String BW_CAPABILITY_CHARS = "KLMNO";
public RouterInfo() {
setIdentity(null);
@ -179,6 +182,12 @@ public class RouterInfo extends DataStructureImpl {
return (Properties) _options.clone();
}
}
public String getOption(String opt) {
if (_options == null) return null;
synchronized (_options) {
return _options.getProperty(opt);
}
}
/**
* Configure a set of options or statistics that the router can expose
@ -334,6 +343,24 @@ public class RouterInfo extends DataStructureImpl {
return (getCapabilities().indexOf(CAPABILITY_HIDDEN) != -1);
}
/**
* Return a string representation of this node's bandwidth tier,
* or "Unknown"
*/
public String getBandwidthTier() {
String bwTiers = BW_CAPABILITY_CHARS;
String bwTier = "Unknown";
String capabilities = getCapabilities();
// Iterate through capabilities, searching for known bandwidth tier
for (int i = 0; i < capabilities.length(); i++) {
if (bwTiers.indexOf(String.valueOf(capabilities.charAt(i))) != -1) {
bwTier = String.valueOf(capabilities.charAt(i));
break;
}
}
return (bwTier);
}
public void addCapability(char cap) {
if (_options == null) _options = new OrderedProperties();
synchronized (_options) {

View File

@ -184,6 +184,7 @@ public class Rate {
private static final int SLACK = 2000;
public void coalesce() {
long now = now();
double correctedTotalValue; // for summaryListener which divides by rounded EventCount
synchronized (_lock) {
long measuredPeriod = now - _lastCoalesceDate;
if (measuredPeriod < _period - SLACK) {
@ -198,9 +199,14 @@ public class Rate {
// how much were we off by? (so that we can sample down the measured values)
double periodFactor = measuredPeriod / (double)_period;
_lastTotalValue = _currentTotalValue / periodFactor;
_lastEventCount = (long) ( (_currentEventCount + periodFactor - 1) / periodFactor);
_lastEventCount = (long) (0.499999 + (_currentEventCount / periodFactor));
_lastTotalEventTime = (long) (_currentTotalEventTime / periodFactor);
_lastCoalesceDate = now;
if (_currentEventCount == 0)
correctedTotalValue = 0;
else
correctedTotalValue = _currentTotalValue *
(_lastEventCount / (double) _currentEventCount);
if (_lastTotalValue > _extremeTotalValue) {
_extremeTotalValue = _lastTotalValue;
@ -213,7 +219,7 @@ public class Rate {
_currentTotalEventTime = 0;
}
if (_summaryListener != null)
_summaryListener.add(_lastTotalValue, _lastEventCount, _lastTotalEventTime, _period);
_summaryListener.add(correctedTotalValue, _lastEventCount, _lastTotalEventTime, _period);
}
public void setSummaryListener(RateSummaryListener listener) { _summaryListener = listener; }

View File

@ -43,7 +43,7 @@ public class StatManager {
_log = context.logManager().getLog(StatManager.class);
_context = context;
_frequencyStats = Collections.synchronizedMap(new HashMap(128));
_rateStats = Collections.synchronizedMap(new HashMap(128));
_rateStats = new HashMap(128); // synchronized only on add //Collections.synchronizedMap(new HashMap(128));
_statLog = new BufferedStatLog(context);
}
@ -80,10 +80,12 @@ public class StatManager {
* @param periods array of period lengths (in milliseconds)
*/
public void createRateStat(String name, String description, String group, long periods[]) {
if (_rateStats.containsKey(name)) return;
RateStat rs = new RateStat(name, description, group, periods);
if (_statLog != null) rs.setStatLog(_statLog);
_rateStats.put(name, rs);
synchronized (_rateStats) {
if (_rateStats.containsKey(name)) return;
RateStat rs = new RateStat(name, description, group, periods);
if (_statLog != null) rs.setStatLog(_statLog);
_rateStats.put(name, rs);
}
}
/** update the given frequency statistic, taking note that an event occurred (and recalculating all frequencies) */
@ -94,7 +96,7 @@ public class StatManager {
/** update the given rate statistic, taking note that the given data point was received (and recalculating all rates) */
public void addRateData(String name, long data, long eventDuration) {
RateStat stat = (RateStat) _rateStats.get(name);
RateStat stat = (RateStat) _rateStats.get(name); // unsynchronized
if (stat != null) stat.addData(data, eventDuration);
}

View File

@ -112,11 +112,19 @@ public class NtpClient {
// Process response
NtpMessage msg = new NtpMessage(packet.getData());
double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
(msg.receiveTimestamp-msg.transmitTimestamp);
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
(msg.transmitTimestamp - destinationTimestamp)) / 2;
socket.close();
// Stratum must be between 1 (atomic) and 15 (maximum defined value)
// Anything else is right out, treat such responses like errors
if ((msg.stratum < 1) || (msg.stratum > 15)) {
//System.out.println("Response from NTP server of unacceptable stratum " + msg.stratum + ", failing.");
return(-1);
}
long rv = (long)(System.currentTimeMillis() + localClockOffset*1000);
//System.out.println("host: " + address.getHostAddress() + " rtt: " + roundTripDelay + " offset: " + localClockOffset + " seconds");

View File

@ -26,7 +26,7 @@ public class Timestamper implements Runnable {
private boolean _initialized;
private static final int DEFAULT_QUERY_FREQUENCY = 5*60*1000;
private static final String DEFAULT_SERVER_LIST = "pool.ntp.org, pool.ntp.org, pool.ntp.org";
private static final String DEFAULT_SERVER_LIST = "0.pool.ntp.org, 1.pool.ntp.org, 2.pool.ntp.org";
private static final boolean DEFAULT_DISABLED = true;
/** how many times do we have to query if we are changing the clock? */
private static final int DEFAULT_CONCURRING_SERVERS = 3;

View File

@ -13,12 +13,16 @@ import net.i2p.time.Timestamper;
* between the local computer's current time and the time as known by some reference
* (such as an NTP synchronized clock).
*
* Protected members are used in the subclass RouterClock,
* which has access to a router's transports (particularly peer clock skews)
* to second-guess the sanity of clock adjustments.
*
*/
public class Clock implements Timestamper.UpdateListener {
private I2PAppContext _context;
protected I2PAppContext _context;
private Timestamper _timestamper;
private long _startedOn;
private boolean _statCreated;
protected long _startedOn;
protected boolean _statCreated;
public Clock(I2PAppContext context) {
_context = context;
@ -36,10 +40,10 @@ public class Clock implements Timestamper.UpdateListener {
public Timestamper getTimestamper() { return _timestamper; }
/** we fetch it on demand to avoid circular dependencies (logging uses the clock) */
private Log getLog() { return _context.logManager().getLog(Clock.class); }
protected Log getLog() { return _context.logManager().getLog(Clock.class); }
private volatile long _offset;
private boolean _alreadyChanged;
protected volatile long _offset;
protected boolean _alreadyChanged;
private Set _listeners;
/** if the clock is skewed by 3+ days, fuck 'em */
@ -132,7 +136,7 @@ public class Clock implements Timestamper.UpdateListener {
}
}
private void fireOffsetChanged(long delta) {
protected void fireOffsetChanged(long delta) {
synchronized (_listeners) {
for (Iterator iter = _listeners.iterator(); iter.hasNext();) {
ClockUpdateListener lsnr = (ClockUpdateListener) iter.next();

View File

@ -18,6 +18,7 @@ import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.SocketTimeout;
/**
* EepGet [-p localhost:4444]
@ -33,8 +34,14 @@ public class EepGet {
private String _proxyHost;
private int _proxyPort;
private int _numRetries;
private long _minSize; // minimum and maximum acceptable response size, -1 signifies unlimited,
private long _maxSize; // applied both against whole responses and chunks
private String _outputFile;
private OutputStream _outputStream;
/** url we were asked to fetch */
private String _url;
/** the URL we actually fetch from (may differ from the _url in case of redirect) */
private String _actualURL;
private String _postData;
private boolean _allowCaching;
private List _listeners;
@ -49,9 +56,18 @@ public class EepGet {
private long _bytesRemaining;
private int _currentAttempt;
private String _etag;
private String _lastModified;
private boolean _encodingChunked;
private boolean _notModified;
private String _contentType;
private boolean _transferFailed;
private boolean _headersRead;
private boolean _aborted;
private long _fetchHeaderTimeout;
private long _fetchEndTime;
private long _fetchInactivityTimeout;
private int _redirects;
private String _redirectLocation;
public EepGet(I2PAppContext ctx, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
this(ctx, true, proxyHost, proxyPort, numRetries, outputFile, url);
@ -69,27 +85,44 @@ public class EepGet {
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, outputFile, url, true, null);
}
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url, String postData) {
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, outputFile, url, true, null, postData);
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, -1, -1, outputFile, null, url, true, null, postData);
}
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url, boolean allowCaching, String etag) {
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, outputFile, url, allowCaching, etag, null);
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, -1, -1, outputFile, null, url, allowCaching, etag, null);
}
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url, boolean allowCaching, String etag, String postData) {
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url, boolean allowCaching, String etag, String lastModified) {
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, -1, -1, outputFile, null, url, allowCaching, etag, lastModified, null);
}
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) {
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, minSize, maxSize, outputFile, outputStream, url, allowCaching, etag, null, postData);
}
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize,
String outputFile, OutputStream outputStream, String url, boolean allowCaching,
String etag, String lastModified, String postData) {
_context = ctx;
_log = ctx.logManager().getLog(EepGet.class);
_shouldProxy = shouldProxy;
_shouldProxy = (proxyHost != null) && (proxyHost.length() > 0) && (proxyPort > 0) && shouldProxy;
_proxyHost = proxyHost;
_proxyPort = proxyPort;
_numRetries = numRetries;
_outputFile = outputFile;
_minSize = minSize;
_maxSize = maxSize;
_outputFile = outputFile; // if outputFile is set, outputStream must be null
_outputStream = outputStream; // if both are set, outputStream overrides outputFile
_url = url;
_actualURL = url;
_postData = postData;
_alreadyTransferred = 0;
_bytesTransferred = 0;
_bytesRemaining = -1;
_currentAttempt = 0;
_transferFailed = false;
_headersRead = false;
_aborted = false;
_fetchHeaderTimeout = 45*1000;
_listeners = new ArrayList(1);
_etag = etag;
_lastModified = lastModified;
}
/**
@ -187,6 +220,7 @@ public class EepGet {
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause);
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt);
public void headerReceived(String url, int currentAttempt, String key, String val);
public void attempting(String url);
}
private class CLIStatusListener implements StatusListener {
private int _markSize;
@ -301,6 +335,7 @@ public class EepGet {
buf.append("KBps");
System.out.println(buf.toString());
}
public void attempting(String url) {}
public void headerReceived(String url, int currentAttempt, String key, String val) {}
}
@ -313,19 +348,52 @@ public class EepGet {
* Blocking fetch, returning true if the URL was retrieved, false if all retries failed
*
*/
public boolean fetch() {
public boolean fetch() { return fetch(_fetchHeaderTimeout); }
/**
* Blocking fetch, timing out individual attempts if the HTTP response headers
* don't come back in the time given. If the timeout is zero or less, this will
* wait indefinitely.
*/
public boolean fetch(long fetchHeaderTimeout) {
return fetch(fetchHeaderTimeout, -1, -1);
}
public boolean fetch(long fetchHeaderTimeout, long totalTimeout, long inactivityTimeout) {
_fetchHeaderTimeout = fetchHeaderTimeout;
_fetchEndTime = (totalTimeout > 0 ? System.currentTimeMillis() + totalTimeout : -1);
_fetchInactivityTimeout = inactivityTimeout;
_keepFetching = true;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetching (proxied? " + _shouldProxy + ") url=" + _url);
_log.debug("Fetching (proxied? " + _shouldProxy + ") url=" + _actualURL);
while (_keepFetching) {
SocketTimeout timeout = null;
if (_fetchHeaderTimeout > 0)
timeout = new SocketTimeout(_fetchHeaderTimeout);
final SocketTimeout stimeout = timeout; // ugly
timeout.setTimeoutCommand(new Runnable() {
public void run() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("timeout reached on " + _url + ": " + stimeout);
_aborted = true;
}
});
timeout.setTotalTimeoutPeriod(_fetchEndTime);
try {
sendRequest();
doFetch();
return true;
for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).attempting(_url);
sendRequest(timeout);
timeout.resetTimer();
doFetch(timeout);
timeout.cancel();
if (!_transferFailed)
return true;
break;
} catch (IOException ioe) {
timeout.cancel();
for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
if (_log.shouldLog(Log.WARN))
_log.warn("ERR: doFetch failed " + ioe);
} finally {
if (_out != null) {
try {
@ -344,39 +412,122 @@ public class EepGet {
_currentAttempt++;
if (_currentAttempt > _numRetries)
break;
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
try {
long delay = _context.random().nextInt(60*1000);
Thread.sleep(5*1000+delay);
} catch (InterruptedException ie) {}
}
for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt);
if (_log.shouldLog(Log.WARN))
_log.warn("All attempts failed for " + _url);
return false;
}
/** return true if the URL was completely retrieved */
private void doFetch() throws IOException {
readHeaders();
private void doFetch(SocketTimeout timeout) throws IOException {
_headersRead = false;
_aborted = false;
try {
readHeaders();
} finally {
_headersRead = true;
}
if (_aborted)
throw new IOException("Timed out reading the HTTP headers");
timeout.resetTimer();
if (_fetchInactivityTimeout > 0)
timeout.setInactivityTimeout(_fetchInactivityTimeout);
else
timeout.setInactivityTimeout(60*1000);
if (_redirectLocation != null) {
try {
URL oldURL = new URL(_actualURL);
String query = oldURL.getQuery();
if (query == null) query = "";
if (_redirectLocation.startsWith("http://")) {
if ( (_redirectLocation.indexOf('?') < 0) && (query.length() > 0) )
_actualURL = _redirectLocation + "?" + query;
else
_actualURL = _redirectLocation;
} else {
URL url = new URL(_actualURL);
if (_redirectLocation.startsWith("/"))
_actualURL = "http://" + url.getHost() + ":" + url.getPort() + _redirectLocation;
else
_actualURL = "http://" + url.getHost() + ":" + url.getPort() + "/" + _redirectLocation;
if ( (_actualURL.indexOf('?') < 0) && (query.length() > 0) )
_actualURL = _actualURL + "?" + query;
else
_actualURL = _actualURL;
}
} catch (MalformedURLException mue) {
throw new IOException("Redirected from an invalid URL");
}
_redirects++;
if (_redirects > 5)
throw new IOException("Too many redirects: to " + _redirectLocation);
if (_log.shouldLog(Log.INFO)) _log.info("Redirecting to " + _redirectLocation);
sendRequest(timeout);
doFetch(timeout);
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Headers read completely, reading " + _bytesRemaining);
boolean strictSize = (_bytesRemaining >= 0);
// If minimum or maximum size defined, ensure they aren't exceeded
if ((_minSize > 0) && (_bytesRemaining < _minSize))
throw new IOException("HTTP response size " + _bytesRemaining + " violates minimum of " + _minSize + " bytes");
if ((_maxSize > -1) && (_bytesRemaining > _maxSize))
throw new IOException("HTTP response size " + _bytesRemaining + " violates maximum of " + _maxSize + " bytes");
int remaining = (int)_bytesRemaining;
byte buf[] = new byte[1024];
while (_keepFetching && ( (remaining > 0) || !strictSize )) {
while (_keepFetching && ( (remaining > 0) || !strictSize ) && !_aborted) {
int toRead = buf.length;
if (strictSize && toRead > remaining)
toRead = remaining;
int read = _proxyIn.read(buf, 0, toRead);
if (read == -1)
break;
timeout.resetTimer();
_out.write(buf, 0, read);
_bytesTransferred += read;
// This seems necessary to properly resume a partial download into a stream,
// as nothing else increments _alreadyTransferred, and there's no file length to check.
// Hopefully this won't break compatibility with existing status listeners
// (cause them to behave weird, or show weird numbers).
_alreadyTransferred += read;
if ((_maxSize > -1) && (_alreadyTransferred > _maxSize)) // could transfer a little over maxSize
throw new IOException("Bytes transferred " + _alreadyTransferred + " violates maximum of " + _maxSize + " bytes");
remaining -= read;
if (remaining==0 && _encodingChunked) {
if(_proxyIn.read()=='\r' && _proxyIn.read()=='\n') {
remaining = (int) readChunkLength();
int char1 = _proxyIn.read();
if (char1 == '\r') {
int char2 = _proxyIn.read();
if (char2 == '\n') {
remaining = (int) readChunkLength();
} else {
_out.write(char1);
_out.write(char2);
_bytesTransferred += 2;
remaining -= 2;
read += 2;
}
} else {
_out.write(char1);
_bytesTransferred++;
remaining--;
read++;
}
}
timeout.resetTimer();
if (read > 0)
for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).bytesTransferred(
@ -386,15 +537,27 @@ public class EepGet {
_encodingChunked?-1:_bytesRemaining,
_url);
}
if (_out != null)
_out.close();
_out = null;
if (_aborted)
throw new IOException("Timed out reading the HTTP data");
timeout.cancel();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Done transferring " + _bytesTransferred);
_log.debug("Done transferring " + _bytesTransferred + " (ok? " + !_transferFailed + ")");
if ( (_bytesRemaining == -1) || (remaining == 0) ){
if (_transferFailed) {
// 404, etc - transferFailed is called after all attempts fail, by fetch() above
for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, new Exception("Attempt failed"));
} else if ((_minSize > 0) && (_alreadyTransferred < _minSize)) {
throw new IOException("Bytes transferred " + _alreadyTransferred + " violates minimum of " + _minSize + " bytes");
} else if ( (_bytesRemaining == -1) || (remaining == 0) ) {
for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).transferComplete(
_alreadyTransferred,
@ -415,30 +578,57 @@ public class EepGet {
boolean read = DataHelper.readLine(_proxyIn, buf);
if (!read) throw new IOException("Unable to read the first line");
int responseCode = handleStatus(buf.toString());
boolean redirect = false;
if (_log.shouldLog(Log.DEBUG))
_log.debug("rc: " + responseCode + " for " + _actualURL);
boolean rcOk = false;
switch (responseCode) {
case 200: // full
_out = new FileOutputStream(_outputFile, false);
if (_outputStream != null)
_out = _outputStream;
else
_out = new FileOutputStream(_outputFile, false);
_alreadyTransferred = 0;
rcOk = true;
break;
case 206: // partial
_out = new FileOutputStream(_outputFile, true);
if (_outputStream != null)
_out = _outputStream;
else
_out = new FileOutputStream(_outputFile, true);
rcOk = true;
break;
case 301: // various redirections
case 302:
case 303:
case 307:
_alreadyTransferred = 0;
rcOk = true;
redirect = true;
break;
case 304: // not modified
_bytesRemaining = 0;
_keepFetching = false;
_notModified = true;
return;
return;
case 404: // not found
_keepFetching = false;
_transferFailed = true;
return;
case 416: // completed (or range out of reach)
_bytesRemaining = 0;
_keepFetching = false;
return;
default:
rcOk = false;
_transferFailed = true;
}
// clear out the arguments, as we use the same variables for return values
_etag = null;
_lastModified = null;
buf.setLength(0);
byte lookahead[] = new byte[3];
while (true) {
@ -471,6 +661,7 @@ public class EepGet {
if (_encodingChunked) {
_bytesRemaining = readChunkLength();
}
if (!redirect) _redirectLocation = null;
return;
}
break;
@ -560,11 +751,15 @@ public class EepGet {
}
} else if (key.equalsIgnoreCase("ETag")) {
_etag = val.trim();
} else if (key.equalsIgnoreCase("Last-Modified")) {
_lastModified = val.trim();
} else if (key.equalsIgnoreCase("Transfer-encoding")) {
if (val.indexOf("chunked") != -1)
_encodingChunked = true;
} else if (key.equalsIgnoreCase("Content-Type")) {
_contentType=val;
} else if (key.equalsIgnoreCase("Location")) {
_redirectLocation=val.trim();
} else {
// ignore the rest
}
@ -587,31 +782,48 @@ public class EepGet {
private static final byte NL = '\n';
private boolean isNL(byte b) { return (b == NL); }
private void sendRequest() throws IOException {
File outFile = new File(_outputFile);
if (outFile.exists())
_alreadyTransferred = outFile.length();
private void sendRequest(SocketTimeout timeout) throws IOException {
if (_outputStream != null) {
// We are reading into a stream supplied by a caller,
// for which we cannot easily determine how much we've written.
// Assume that _alreadyTransferred holds the right value
// (we should never be restarted to work on an old stream).
} else {
File outFile = new File(_outputFile);
if (outFile.exists())
_alreadyTransferred = outFile.length();
}
String req = getRequest();
if (_proxyIn != null) try { _proxyIn.close(); } catch (IOException ioe) {}
if (_proxyOut != null) try { _proxyOut.close(); } catch (IOException ioe) {}
if (_proxy != null) try { _proxy.close(); } catch (IOException ioe) {}
if (_shouldProxy) {
_proxy = new Socket(_proxyHost, _proxyPort);
} else {
try {
URL url = new URL(_url);
String host = url.getHost();
int port = url.getPort();
if (port == -1)
port = 80;
_proxy = new Socket(host, port);
URL url = new URL(_actualURL);
if ("http".equals(url.getProtocol())) {
String host = url.getHost();
int port = url.getPort();
if (port == -1)
port = 80;
_proxy = new Socket(host, port);
} else {
throw new IOException("URL is not supported:" + _actualURL);
}
} catch (MalformedURLException mue) {
throw new IOException("Request URL is invalid");
}
}
_proxyIn = _proxy.getInputStream();
_proxyOut = _proxy.getOutputStream();
_proxyOut.write(req.toString().getBytes());
timeout.setSocket(_proxy);
_proxyOut.write(DataHelper.getUTF8(req.toString()));
_proxyOut.flush();
if (_log.shouldLog(Log.DEBUG))
@ -623,12 +835,24 @@ public class EepGet {
boolean post = false;
if ( (_postData != null) && (_postData.length() > 0) )
post = true;
URL url = new URL(_actualURL);
String proto = url.getProtocol();
String host = url.getHost();
int port = url.getPort();
String path = url.getPath();
String query = url.getQuery();
if (query != null)
path = path + "?" + query;
if (!path.startsWith("/"))
path = "/" + path;
if ( (port == 80) || (port == 443) || (port <= 0) ) path = proto + "://" + host + path;
else path = proto + "://" + host + ":" + port + path;
if (_log.shouldLog(Log.DEBUG)) _log.debug("Requesting " + path);
if (post) {
buf.append("POST ").append(_url).append(" HTTP/1.1\r\n");
buf.append("POST ").append(_actualURL).append(" HTTP/1.1\r\n");
} else {
buf.append("GET ").append(_url).append(" HTTP/1.1\r\n");
buf.append("GET ").append(_actualURL).append(" HTTP/1.1\r\n");
}
URL url = new URL(_url);
buf.append("Host: ").append(url.getHost()).append("\r\n");
if (_alreadyTransferred > 0) {
buf.append("Range: bytes=");
@ -641,11 +865,16 @@ public class EepGet {
buf.append("Cache-control: no-cache\r\n");
buf.append("Pragma: no-cache\r\n");
}
if (_etag != null) {
if ((_etag != null) && (_alreadyTransferred <= 0)) {
buf.append("If-None-Match: ");
buf.append(_etag);
buf.append("\r\n");
}
if ((_lastModified != null) && (_alreadyTransferred <= 0)) {
buf.append("If-Modified-Since: ");
buf.append(_lastModified);
buf.append("\r\n");
}
if (post)
buf.append("Content-length: ").append(_postData.length()).append("\r\n");
buf.append("Connection: close\r\n\r\n");
@ -660,6 +889,10 @@ public class EepGet {
return _etag;
}
public String getLastModified() {
return _lastModified;
}
public boolean getNotModified() {
return _notModified;
}

View File

@ -36,7 +36,8 @@ public class EepGetScheduler implements EepGet.StatusListener {
public void fetch(boolean shouldBlock) {
//Checking for a valid index is done in fetchNext, so we don't have to worry about it.
if (shouldBlock) {
fetchNext();
while (_curURL < _urls.size())
fetchNext();
} else {
fetch();
}
@ -77,6 +78,7 @@ public class EepGetScheduler implements EepGet.StatusListener {
_listener.transferFailed(url, bytesTransferred, bytesRemaining, currentAttempt);
fetchNext();
}
public void attempting(String url) { _listener.attempting(url); }
public void headerReceived(String url, int attemptNum, String key, String val) {}
}

View File

@ -13,6 +13,7 @@ import net.i2p.util.Log;
public class EepPost {
private I2PAppContext _context;
private Log _log;
private static final String CRLF = "\r\n";
public EepPost() {
this(I2PAppContext.getGlobalContext());
@ -65,6 +66,7 @@ public class EepPost {
_onCompletion = onCompletion;
}
public void run() {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Running the post task");
Socket s = null;
try {
URL u = new URL(_url);
@ -81,17 +83,20 @@ public class EepPost {
_proxyPort = p;
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("Connecting to the server/proxy...");
s = new Socket(_proxyHost, _proxyPort);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Connected");
OutputStream out = s.getOutputStream();
String sep = getSeparator();
long length = calcContentLength(sep, _fields);
if (_log.shouldLog(Log.DEBUG)) _log.debug("Separator: " + sep + " content length: " + length);
String header = getHeader(isProxy, path, h, p, sep, length);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Header: \n" + header);
out.write(header.getBytes());
out.flush();
if (false) {
out.write(("--" + sep + "\ncontent-disposition: form-data; name=\"field1\"\n\nStuff goes here\n--" + sep + "--\n").getBytes());
out.write(("--" + sep + CRLF + "content-disposition: form-data; name=\"field1\"" + CRLF + CRLF + "Stuff goes here" + CRLF + "--" + sep + "--" + CRLF).getBytes());
} else {
sendFields(out, sep, _fields);
}
@ -121,18 +126,18 @@ public class EepPost {
Object val = fields.get(key);
if (val instanceof File) {
File f = (File)val;
len += ("--" + sep + "\nContent-Disposition: form-data; name=\"" + key + "\"; filename=\"" + f.getName() + "\"\n").length();
len += ("--" + sep + CRLF + "Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + f.getName() + "\"" + CRLF).length();
//len += ("Content-length: " + f.length() + "\n").length();
len += ("Content-Type: application/octet-stream\n\n").length();
len += ("Content-Type: application/octet-stream" + CRLF + CRLF).length();
len += f.length();
len += 1; // nl
len += CRLF.length(); // nl
} else {
len += ("--" + sep + "\nContent-Disposition: form-data; name=\"" + key + "\"\n\n").length();
len += ("--" + sep + CRLF + "Content-Disposition: form-data; name=\"" + key + "\"" + CRLF + CRLF).length();
len += val.toString().length();
len += 1; // nl
len += CRLF.length(); // nl
}
}
len += 2 + sep.length() + 2;
len += 2 + sep.length() + 2 + CRLF.length(); //2 + sep.length() + 2;
//len += 2;
return len;
}
@ -145,29 +150,29 @@ public class EepPost {
else
sendField(out, separator, field, val.toString());
}
out.write(("--" + separator + "--\n").getBytes());
out.write(("--" + separator + "--" + CRLF).getBytes());
}
private void sendFile(OutputStream out, String separator, String field, File file) throws IOException {
long len = file.length();
out.write(("--" + separator + "\n").getBytes());
out.write(("Content-Disposition: form-data; name=\"" + field + "\"; filename=\"" + file.getName() + "\"\n").getBytes());
out.write(("--" + separator + CRLF).getBytes());
out.write(("Content-Disposition: form-data; name=\"" + field + "\"; filename=\"" + file.getName() + "\"" + CRLF).getBytes());
//out.write(("Content-length: " + len + "\n").getBytes());
out.write(("Content-Type: application/octet-stream\n\n").getBytes());
out.write(("Content-Type: application/octet-stream" + CRLF + CRLF).getBytes());
FileInputStream in = new FileInputStream(file);
byte buf[] = new byte[1024];
int read = -1;
while ( (read = in.read(buf)) != -1)
out.write(buf, 0, read);
out.write("\n".getBytes());
out.write(CRLF.getBytes());
in.close();
}
private void sendField(OutputStream out, String separator, String field, String val) throws IOException {
out.write(("--" + separator + "\n").getBytes());
out.write(("Content-Disposition: form-data; name=\"" + field + "\"\n\n").getBytes());
out.write(("--" + separator + CRLF).getBytes());
out.write(("Content-Disposition: form-data; name=\"" + field + "\"" + CRLF + CRLF).getBytes());
out.write(val.getBytes());
out.write("\n".getBytes());
out.write(CRLF.getBytes());
}
private String getHeader(boolean isProxy, String path, String host, int port, String separator, long length) {
@ -179,16 +184,16 @@ public class EepPost {
buf.append(":").append(port);
}
buf.append(path);
buf.append(" HTTP/1.1\n");
buf.append(" HTTP/1.1" + CRLF);
buf.append("Host: ").append(host);
if (port != 80)
buf.append(":").append(port);
buf.append("\n");
buf.append("Connection: close\n");
buf.append("Content-length: ").append(length).append("\n");
buf.append(CRLF);
buf.append("Connection: close" + CRLF);
buf.append("Content-length: ").append(length).append(CRLF);
buf.append("Content-type: multipart/form-data, boundary=").append(separator);
buf.append("\n");
buf.append("\n");
buf.append(CRLF);
buf.append(CRLF);
return buf.toString();
}

View File

@ -0,0 +1,51 @@
package net.i2p.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import net.i2p.I2PAppContext;
class Executor implements Runnable {
private I2PAppContext _context;
private Log _log;
private List _readyEvents;
public Executor(I2PAppContext ctx, Log log, List events) {
_context = ctx;
_readyEvents = events;
}
public void run() {
while (true) {
SimpleTimer.TimedEvent evt = null;
synchronized (_readyEvents) {
if (_readyEvents.size() <= 0)
try { _readyEvents.wait(); } catch (InterruptedException ie) {}
if (_readyEvents.size() > 0)
evt = (SimpleTimer.TimedEvent)_readyEvents.remove(0);
}
if (evt != null) {
long before = _context.clock().now();
try {
evt.timeReached();
} catch (Throwable t) {
log("wtf, event borked: " + evt, t);
}
long time = _context.clock().now() - before;
if ( (time > 1000) && (_log != null) && (_log.shouldLog(Log.WARN)) )
_log.warn("wtf, event execution took " + time + ": " + evt);
}
}
}
private void log(String msg, Throwable t) {
synchronized (this) {
if (_log == null)
_log = I2PAppContext.getGlobalContext().logManager().getLog(SimpleTimer.class);
}
_log.log(Log.CRIT, msg, t);
}
}

View File

@ -14,7 +14,7 @@ import java.security.SecureRandom;
import net.i2p.I2PAppContext;
import net.i2p.crypto.EntropyHarvester;
import gnu.crypto.prng.FortunaStandalone;
import gnu.crypto.prng.AsyncFortunaStandalone;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@ -26,13 +26,13 @@ import java.io.IOException;
*
*/
public class FortunaRandomSource extends RandomSource implements EntropyHarvester {
private FortunaStandalone _fortuna;
private AsyncFortunaStandalone _fortuna;
private double _nextGaussian;
private boolean _haveNextGaussian;
public FortunaRandomSource(I2PAppContext context) {
super(context);
_fortuna = new FortunaStandalone();
_fortuna = new AsyncFortunaStandalone();
byte seed[] = new byte[1024];
if (initSeed(seed)) {
_fortuna.seed(seed);
@ -41,6 +41,7 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste
sr.nextBytes(seed);
_fortuna.seed(seed);
}
_fortuna.startup();
// kickstart it
_fortuna.nextBytes(seed);
_haveNextGaussian = false;
@ -202,6 +203,13 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste
public static void main(String args[]) {
try {
RandomSource rand = I2PAppContext.getGlobalContext().random();
if (true) {
for (int i = 0; i < 1000; i++)
if (rand.nextFloat() < 0)
throw new RuntimeException("negative!");
System.out.println("All positive");
return;
}
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
java.util.zip.GZIPOutputStream gos = new java.util.zip.GZIPOutputStream(baos);
for (int i = 0; i < 1024*1024; i++) {

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