Compare commits

...

107 Commits

Author SHA1 Message Date
jrandom
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
jrandom
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
jrandom
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
jrandom
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
complication
58f10d14b2 2007-01-20 Complication
* Drop overlooked comment
2007-01-21 03:49:41 +00:00
complication
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
jrandom
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
jrandom
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
complication
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
jrandom
12819a2a17 added mtn.i2p 2006-10-15 02:10:36 +00:00
jrandom
87eedff254 0.6.1.26 2006-10-09 05:10:22 +00:00
jrandom
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
complication
ef707e7956 2006-10-08 Complication
* Update comment to reflect current status
2006-10-08 23:10:58 +00:00
complication
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
jrandom
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
complication
32a579e480 2006-09-26 Complication
* Take back another inadverent logging change in NTCPConnection
2006-09-27 04:44:13 +00:00
complication
0a240a4436 2006-09-26 Complication
* Take back an accidental log level change
2006-09-27 04:31:34 +00:00
complication
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
complication
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
jrandom
22d76a1b64 * 2006-09-09 0.6.1.25 released 2006-09-09 17:46:21 +00:00
jrandom
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
complication
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
complication
03e8875c27 2006-08-21 Complication
* Fix error reporting discrepancy (thanks for helping notice, yojoe!)
2006-08-21 05:55:33 +00:00
complication
48921a0875 2006-08-03 Complication
* news.xml update
2006-08-04 00:49:40 +00:00
jrandom
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
jrandom
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
complication
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
jrandom
034803add7 * 2006-07-28 0.6.1.24 released 2006-07-29 18:03:14 +00:00
jrandom
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
jrandom
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
jrandom
06b8670410 * 2006-07-27 0.6.1.23 released 2006-07-28 03:34:59 +00:00
jrandom
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
jrandom
54bc5485ec oops, thanks bar! 2006-07-27 07:06:55 +00:00
jrandom
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
jrandom
c48c419d74 quick prng workaround 2006-07-27 01:34:31 +00:00
jrandom
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
jrandom
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
jrandom
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
complication
e1c686baa6 2006-07-18 Complication
* URL and date fix in news.xml
2006-07-18 23:35:54 +00:00
jrandom
d57af1aef4 remove 1.5ism 2006-07-18 20:20:08 +00:00
jrandom
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
complication
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
jrandom
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
jrandom
900d8a2026 oops, test method. thanks cervantes 2006-07-07 19:04:24 +00:00
jrandom
ccc9a87e8c unnecessary 2006-07-07 18:59:16 +00:00
jrandom
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
complication
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
cervantes
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
jrandom
b92b9d2618 * 2006-06-14 0.6.1.21 released 2006-06-14 02:17:40 +00:00
jrandom
a3db9429a7 2006-06-13 jrandom
* Use a minimum uptime of 2 hours, not 4 (oops)
2006-06-13 23:29:51 +00:00
jrandom
291a5c9578 2006-06-13 jrandom
* Cut down the proactive rejections due to queue size - if we are
      at the point of having decrypted the request off the queue, might
      as well let it through, rather than waste that decryption
2006-06-13 09:38:48 +00:00
jrandom
0a3281c279 2006-06-11 Kloug
* Bugfix to the I2PTunnel IRC filter to support multiple concurrent
      outstanding pings/pongs
2006-06-11 19:48:37 +00:00
jrandom
23f30ba576 2006-06-10 jrandom
* Further reduction in proactive rejections
2006-06-10 20:14:57 +00:00
jrandom
f3de85c4de 2006-06-09 jrandom
* Don't let the pending tunnel request queue grow beyond reason
      (letting things sit for up to 30s when they fail after 10s
      seems a bit... off)
2006-06-10 00:34:44 +00:00
jrandom
a3a4888e0b 2006-06-08 jrandom
* Be more conservative in the proactive rejections
2006-06-09 01:02:40 +00:00
jrandom
6fd7881f8e thanks bar 2006-06-05 06:41:11 +00:00
complication
381f716769 2006-06-04 Complication
* Stop sending a blank line before USER in susimail.
      Seemed to break in rare cases, thanks for reporting, Brachtus!
2006-06-05 01:33:03 +00:00
126 changed files with 7554 additions and 844 deletions

View File

@@ -318,6 +318,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;
@@ -458,4 +466,25 @@ public class Peer implements Comparable
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();
}
}

View File

@@ -48,10 +48,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;
@@ -80,17 +77,7 @@ 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();
@@ -113,7 +100,7 @@ class PeerCheckerTask extends TimerTask
// interested peers try to make some room.
// (Note use of coordinator.uploaders)
if (coordinator.uploaders >= PeerCoordinator.MAX_UPLOADERS
&& interested > PeerCoordinator.MAX_UPLOADERS
&& coordinator.interestedAndChoking > 0
&& !peer.isChoking())
{
// Check if it still wants pieces from us.
@@ -130,7 +117,7 @@ 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)
@@ -138,6 +125,21 @@ class PeerCheckerTask extends TimerTask
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
if (Snark.debug >= Snark.DEBUG)
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();
@@ -154,27 +156,37 @@ class PeerCheckerTask extends TimerTask
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.
// Remove the worst downloader if needed. (uploader if seeding)
if (uploaders >= PeerCoordinator.MAX_UPLOADERS
&& interested > PeerCoordinator.MAX_UPLOADERS
&& coordinator.interestedAndChoking > 0
&& worstDownloader != null)
{
if (Snark.debug >= Snark.DEBUG)
@@ -183,6 +195,7 @@ class PeerCheckerTask extends TimerTask
worstDownloader.setChoking(true);
coordinator.uploaders--;
removedCount++;
// Put it at the back of the list
coordinator.peers.remove(worstDownloader);
@@ -196,6 +209,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,7 +33,7 @@ class PeerConnectionIn implements Runnable
private final DataInputStream din;
private Thread thread;
private boolean quit;
private volatile boolean quit;
public PeerConnectionIn(Peer peer, DataInputStream din)
{
@@ -51,6 +51,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()

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);
@@ -259,7 +270,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 +335,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 +364,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 +398,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,11 +100,8 @@ 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; }
@@ -123,7 +133,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 +152,43 @@ 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)
{
for (int i = RATE_DEPTH-1; i > 0; i--){
uploaded_old[i] = uploaded_old[i-1];
downloaded_old[i] = downloaded_old[i-1];
}
uploaded_old[0] = up;
downloaded_old[0] = down;
}
/**
* Returns the 4-minute-average rate in Bps
*/
public long getDownloadRate()
{
long rate = 0;
for (int i = 0; i < RATE_DEPTH; i++){
rate += downloaded_old[i];
}
return rate / (RATE_DEPTH * CHECK_PERIOD / 1000);
}
/**
* Returns the 4-minute-average rate in Bps
*/
public long getUploadRate()
{
long rate = 0;
for (int i = 0; i < RATE_DEPTH; i++){
rate += uploaded_old[i];
}
return rate / (RATE_DEPTH * CHECK_PERIOD / 1000);
}
public MetaInfo getMetaInfo()
{
return metainfo;
@@ -191,8 +238,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() > 4*60*1000) ) {
// idle for 4 minutes, kill the old con (64KB/4min = 273B/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;
@@ -235,12 +284,13 @@ 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;
@@ -265,6 +315,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 +325,7 @@ public class PeerCoordinator implements PeerListener
_log.info("MAX_CONNECTIONS = " + MAX_CONNECTIONS
+ " not accepting extra peer: " + peer);
}
return false;
}
@@ -285,19 +337,23 @@ 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;
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 < MAX_UPLOADERS)
{
if (!peer.isChoked())
interested.add(unchokedCount++, peer);
else
interested.add(peer);
}
}
}
@@ -308,11 +364,13 @@ public class PeerCoordinator implements PeerListener
_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 +409,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 +451,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 +464,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 +486,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)
{
@@ -582,4 +651,124 @@ 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)
{
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 (peer.state == null)
return;
int[] arr = peer.state.getRequestedPieces();
for (int i = 0; arr[i] >= 0; i++)
markUnrequestedIfOnlyOne(peer, arr[i]);
}
}

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,7 +62,7 @@ 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 MAX_PIPELINE = 2;
private final static int PARTSIZE = 64*1024; // default was 16K, i2p-bt uses 64KB
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
@@ -184,7 +184,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 +194,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 +221,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 +235,8 @@ class PeerState
downloaded += size;
listener.downloaded(peer, size);
pendingRequest = null;
// Last chunk needed for this piece?
if (getFirstOutstandingRequest(req.piece) == -1)
{
@@ -318,14 +324,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 +336,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 +462,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 +475,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 +495,6 @@ class PeerState
if (!choked)
out.sendRequest(req);
lastRequest = req;
}
}
}
}
@@ -472,16 +510,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 +554,6 @@ class PeerState
lastRequest = req;
return true;
}
}
}
return false;
@@ -523,4 +585,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

@@ -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 call trackerclient.start() which 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;
@@ -371,14 +378,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 +398,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 +434,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();
@@ -693,6 +736,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

View File

@@ -16,6 +16,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;
@@ -34,12 +35,13 @@ public class SnarkManager implements Snark.CompleteListener {
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,6 +93,8 @@ 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))
@@ -267,7 +271,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 +291,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 +316,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 +328,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);
@@ -333,7 +348,7 @@ public class SnarkManager implements Snark.CompleteListener {
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());
@@ -444,10 +459,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...
@@ -464,6 +478,9 @@ 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"
, "eBook Tracker", "http://E71FRom6PZNEqTN2Lr8P-sr23b7HJVC32KoGnVQjaX6zJiXwhJy2HsXob36Qmj81TYFZdewFZa9mSJ533UZgGyQkXo2ahctg82JKYZfDe5uDxAn1E9YPjxZCWJaFJh0S~UwSs~9AZ7UcauSJIoNtpxrtbmRNVFLqnkEDdLZi26TeucfOmiFmIWnVblLniWv3tG1boE9Abd-6j3FmYVrRucYuepAILYt6katmVNOk6sXmno1Eynrp~~MBuFq0Ko6~jsc2E2CRVYXDhGHEMdt-j6JUz5D7S2RIVzDRqQyAZLKJ7OdQDmI31przzmne1vOqqqLC~1xUumZVIvF~yOeJUGNjJ1Vx0J8i2BQIusn1pQJ6UCB~ZtZZLQtEb8EPVCfpeRi2ri1M5CyOuxN0V5ekmPHrYIBNevuTCRC26NP7ZS5VDgx1~NaC3A-CzJAE6f1QXi0wMI9aywNG5KGzOPifcsih8eyGyytvgLtrZtV7ykzYpPCS-rDfITncpn5hliPUAAAA.i2p/pub/bt/announce.php"
, "Gaytorrents Tracker", "http://uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA.i2p/announce.php"
, "NickyB Tracker", "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"
, "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"
// , "The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php"
};

View File

@@ -39,7 +39,7 @@ 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
@@ -48,6 +48,7 @@ public class Storage
/** 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;
@@ -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;
@@ -155,7 +156,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++)
@@ -183,7 +184,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 +219,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++;
@@ -334,6 +337,49 @@ public class Storage
checkCreateFiles();
}
/**
* 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 (!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 = createFileFromNames(base, (List)files.get(i));
if (!f.exists())
throw new IOException("Could not reopen file " + f);
if (!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.
*/
@@ -399,7 +445,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)
{
@@ -462,16 +508,23 @@ public class Storage
}
/**
* 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 +535,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 +558,7 @@ public class Storage
needed--;
complete = needed == 0;
}
}
// Early typecast, avoid possibly overflowing a temp integer
@@ -538,23 +593,44 @@ public class Storage
}
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);
}
}
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 +648,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

@@ -43,6 +43,8 @@ public class TrackerClient extends I2PThread
private static final String STOPPED_EVENT = "stopped";
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 +112,7 @@ public class TrackerClient extends I2PThread
long left = coordinator.getLeft();
boolean completed = (left == 0);
int sleptTime = 0;
try
{
@@ -117,6 +120,7 @@ public class TrackerClient extends I2PThread
boolean started = false;
while (!started)
{
sleptTime = 0;
try
{
// Send start.
@@ -125,18 +129,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)
{
@@ -147,7 +153,10 @@ public class TrackerClient extends I2PThread
coordinator.trackerProblems = ioe.getMessage();
}
if (!started && !stop)
if (stop)
break;
if (!started)
{
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
try
@@ -168,8 +177,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 +214,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 +225,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 +236,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 +253,7 @@ public class TrackerClient extends I2PThread
Snark.debug
("WARNING: Could not contact tracker at '"
+ announce + "': " + ioe, Snark.WARNING);
coordinator.trackerProblems = ioe.getMessage();
}
}
}

View File

@@ -43,6 +43,7 @@ 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))) )
@@ -55,10 +56,19 @@ public class I2PSnarkServlet extends HttpServlet {
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() + "\" class=\"snarkRefresh\">Refresh</a><br />\n");
out.write("<td><a href=\"http://forum.i2p/viewforum.php?f=21\" class=\"snarkRefresh\">Forum</a><br />\n");
out.write("<tr><td><a href=\"http://de-ebook-archiv.i2p/pub/bt/\" class=\"snarkRefresh\">eBook</a><br />\n");
out.write("<td><a href=\"http://gaytorrents.i2p/\" class=\"snarkRefresh\">GayTorrents</a><br />\n");
out.write("<tr><td><a href=\"http://nickyb.i2p/bittorrent/\" class=\"snarkRefresh\">NickyB</a><br />\n");
out.write("<td><a href=\"http://orion.i2p/bt/\" class=\"snarkRefresh\">Orion</a><br />\n");
out.write("<tr><td><a href=\"http://tracker.postman.i2p/\" class=\"snarkRefresh\">Postman</a><br />\n");
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,16 +76,30 @@ 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);
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);
displaySnark(out, snark, uri, i, stats);
}
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);
@@ -240,7 +264,9 @@ 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();
@@ -258,12 +284,22 @@ public class I2PSnarkServlet extends HttpServlet {
} 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 +315,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 = 30;
private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[]) 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 +332,62 @@ 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
else {
if (err.length() > MAX_DISPLAYED_ERROR_LENGTH)
err = err.substring(0, MAX_DISPLAYED_ERROR_LENGTH) + "...";
statusString = "TrackerErr (" + err + ")";
}
} else if (remaining <= 0) {
if (isRunning)
statusString = "Seeding (" + curPeers + "/" + knownPeers + " peers)";
else
statusString = "Complete";
} else {
if (isRunning)
if (isRunning && curPeers > 0 && downBps > 0)
statusString = "OK (" + curPeers + "/" + knownPeers + " peers)";
else if (isRunning && curPeers > 0)
statusString = "Stalled (" + curPeers + "/" + knownPeers + " peers)";
else if (isRunning)
statusString = "No Peers (0/" + knownPeers + ")";
else
statusString = "Stopped";
}
@@ -336,20 +406,26 @@ public class I2PSnarkServlet extends HttpServlet {
out.write("</a>");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
if (remaining > 0) {
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
// lets hold off on the ETA until we have rates sorted...
//out.write(" (eta " + DataHelper.formatDuration(remainingSeconds*1000) + ")"); // (eta 6h)
} else {
out.write(formatSize(total)); // 3GB
}
out.write("<td 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=\"left\" class=\"snarkTorrentUploaded " + rowClass
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 + "\">");
if (isRunning) {
out.write("<a href=\"" + uri + "?action=Stop&nonce=" + _nonce
@@ -381,7 +457,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,10 +473,11 @@ 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
@@ -480,7 +558,7 @@ 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");
@@ -540,7 +618,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 +631,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 +647,24 @@ 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" +
" <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,16 @@ 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();
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
@@ -483,7 +493,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
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());

View File

@@ -27,8 +27,6 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
protected List dests;
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
protected long readTimeout = DEFAULT_READ_TIMEOUT;
/** this is the pong response the client expects for their last ping. at least, i hope so... */
private String _expectedPong;
/**
* @throws IllegalArgumentException if the I2PTunnel does not contain
@@ -47,8 +45,6 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
notifyThis,
"IRCHandler " + (++__clientId), tunnel);
_expectedPong = null;
StringTokenizer tok = new StringTokenizer(destinations, ",");
dests = new ArrayList(1);
while (tok.hasMoreTokens()) {
@@ -85,9 +81,10 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
try {
i2ps = createI2PSocket(dest);
i2ps.setReadTimeout(readTimeout);
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps));
StringBuffer expectedPong = new StringBuffer();
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong));
in.start();
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps));
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong));
out.start();
} catch (Exception ex) {
if (_log.shouldLog(Log.ERROR))
@@ -123,10 +120,12 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
private Socket local;
private I2PSocket remote;
private StringBuffer expectedPong;
IrcInboundFilter(Socket _local, I2PSocket _remote) {
IrcInboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
local=_local;
remote=_remote;
expectedPong=pong;
}
public void run() {
@@ -153,7 +152,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
inmsg=inmsg.substring(0,inmsg.length()-1);
if (_log.shouldLog(Log.DEBUG))
_log.debug("in: [" + inmsg + "]");
String outmsg = inboundFilter(inmsg);
String outmsg = inboundFilter(inmsg, expectedPong);
if(outmsg!=null)
{
if(!inmsg.equals(outmsg)) {
@@ -195,10 +194,12 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
private Socket local;
private I2PSocket remote;
private StringBuffer expectedPong;
IrcOutboundFilter(Socket _local, I2PSocket _remote) {
IrcOutboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
local=_local;
remote=_remote;
expectedPong=pong;
}
public void run() {
@@ -225,7 +226,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
inmsg=inmsg.substring(0,inmsg.length()-1);
if (_log.shouldLog(Log.DEBUG))
_log.debug("out: [" + inmsg + "]");
String outmsg = outboundFilter(inmsg);
String outmsg = outboundFilter(inmsg, expectedPong);
if(outmsg!=null)
{
if(!inmsg.equals(outmsg)) {
@@ -264,7 +265,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
*
*/
public String inboundFilter(String s) {
public String inboundFilter(String s, StringBuffer expectedPong) {
String field[]=s.split(" ",4);
String command;
@@ -281,6 +282,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
"PART",
"WALLOPS",
"ERROR",
"KICK",
"H", // "hide operator status" (after kicking an op)
"TOPIC"
};
@@ -311,9 +314,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
// 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 null.
String pong = _expectedPong;
_expectedPong = null;
// is blank.
String pong = expectedPong.length() > 0 ? expectedPong.toString() : null;
expectedPong.setLength(0);
return pong;
}
@@ -347,7 +350,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
return null;
}
public String outboundFilter(String s) {
public String outboundFilter(String s, StringBuffer expectedPong) {
String field[]=s.split(" ",3);
String command;
@@ -397,24 +400,24 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
// Yuck.
String rv = null;
expectedPong.setLength(0);
if (field.length == 1) { // PING
rv = "PING";
_expectedPong = "PONG 127.0.0.1";
expectedPong.append("PONG 127.0.0.1");
} else if (field.length == 2) { // PING nonce
rv = "PING " + field[1];
_expectedPong = "PONG " + field[1];
expectedPong.append("PONG ").append(field[1]);
} else if (field.length == 3) { // PING nonce serverLocation
rv = "PING " + field[1];
_expectedPong = "PONG " + field[1];
expectedPong.append("PONG ").append(field[1]);
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")");
rv = null;
_expectedPong = null;
}
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;
}

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

@@ -37,6 +37,8 @@ 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 String _inboundRate;
@@ -51,9 +53,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 +62,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; }
@@ -78,6 +77,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 +104,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 +141,30 @@ public class ConfigNetHandler extends FormHandler {
restartRequired = true;
}
}
if ( (_ntcpHostname != null) && (_ntcpHostname.length() > 0) && (_ntcpPort != null) && (_ntcpPort.length() > 0) ) {
String oldHost = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
String oldPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
if ( (oldHost == null) || (!oldHost.equalsIgnoreCase(_ntcpHostname)) ||
(oldPort == null) || (!oldPort.equalsIgnoreCase(_ntcpPort)) ) {
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME, _ntcpHostname);
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT, _ntcpPort);
addFormNotice("Updating inbound TCP settings from " + oldHost + ":" + oldPort
+ " to " + _ntcpHostname + ":" + _ntcpPort);
restartRequired = true;
}
} else {
String oldHost = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
String oldPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
if ( (oldHost != null) || (oldPort != null) ) {
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
addFormNotice("Updating inbound TCP settings from " + oldHost + ":" + oldPort
+ " so that we no longer receive inbound TCP connections");
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,18 @@ 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 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");

View File

@@ -22,6 +22,7 @@ import net.i2p.util.I2PThread;
*
*/
public class ReseedHandler {
private static ReseedRunner _reseedRunner = new ReseedRunner();
public void setReseedNonce(String nonce) {
@@ -66,7 +67,7 @@ public class ReseedHandler {
*
*/
private static void reseed(boolean echoStatus) {
String seedURL = System.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
String seedURL = I2PAppContext.getGlobalContext().getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
seedURL = DEFAULT_SEED_URL;
try {

View File

@@ -60,6 +60,10 @@ public class StatSummarizer implements Runnable {
",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";

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

@@ -66,6 +66,20 @@
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 />
Externally reachable TCP port:
<input name ="ntcpport" type="text" size="6" value="<jsp:getProperty name="nethelper" property="ntcpport" />" /><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 this setting 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>

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

View File

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

@@ -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.1 $
*/
package i2p.susi.dns;
@@ -99,7 +99,6 @@ public class AddressbookBean
return ConfigBean.addressbookPrefix + filename;
}
private Object[] entries;
public Object[] getEntries()
{
return entries;
@@ -130,10 +129,59 @@ 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
message = "Filtered list 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 +223,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 +247,10 @@ public class AddressbookBean
{
return getBook().compareToIgnoreCase( "router" ) == 0;
}
public boolean isPublished()
{
return getBook().compareToIgnoreCase( "published" ) == 0;
}
public void setFilter(String filter) {
if( filter != null && ( filter.length() == 0 || filter.compareToIgnoreCase( "none" ) == 0 ) ) {
filter = null;

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"%>
@@ -60,6 +60,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>
@@ -115,16 +117,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}">
<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}">
<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 +139,7 @@
</table>
</div>
<c:if test="${book.master || book.router}">
<c:if test="${book.master || book.router || book.published}">
<div id="buttons">
<p class="buttons"><input type="image" name="action" value="delete" src="images/delete.png" alt="Delete checked" />
</p>

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.8 $
* $Revision: 1.1 $
*/
package i2p.susi.webmail.pop3;
@@ -373,8 +373,7 @@ public class POP3MailBox {
}
if (socket != null) {
try {
if (sendCmd1a("")
&& sendCmd1a("USER " + user)
if (sendCmd1a("USER " + user)
&& sendCmd1a("PASS " + pass)
&& sendCmd1a("STAT") ) {

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

@@ -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.62 $ $Date: 2006-05-18 17:31:09 $";
public final static String VERSION = "0.6.1.20";
public final static String ID = "$Revision: 1.69 $ $Date: 2006-10-08 20:44:55 $";
public final static String VERSION = "0.6.1.27";
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

@@ -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;
@@ -770,9 +771,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 +788,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 +805,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 +834,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

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

@@ -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++) {

View File

@@ -48,6 +48,12 @@ public class I2PThread extends Thread {
if ( (_log == null) || (_log.shouldLog(Log.DEBUG)) )
_createdBy = new Exception("Created by");
}
public I2PThread(Runnable r, String name, boolean isDaemon) {
super(r, name);
setDaemon(isDaemon);
if ( (_log == null) || (_log.shouldLog(Log.DEBUG)) )
_createdBy = new Exception("Created by");
}
private void log(int level, String msg) { log(level, msg, null); }
private void log(int level, String msg, Throwable t) {
@@ -113,4 +119,4 @@ public class I2PThread extends Thread {
} catch (Throwable tt) { // nop
}
}
}
}

View File

@@ -91,7 +91,7 @@ public class NativeBigInteger extends BigInteger {
* 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("jbigi.dontLog") == null;
private final static String JBIGI_OPTIMIZATION_K6 = "k6";
private final static String JBIGI_OPTIMIZATION_K6_2 = "k62";

View File

@@ -31,14 +31,14 @@ public class SimpleTimer {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(SimpleTimer.class);
_events = new TreeMap();
_eventTimes = new HashMap(1024);
_eventTimes = new HashMap(256);
_readyEvents = new ArrayList(4);
I2PThread runner = new I2PThread(new SimpleTimerRunner());
runner.setName(name);
runner.setDaemon(true);
runner.start();
for (int i = 0; i < 3; i++) {
I2PThread executor = new I2PThread(new Executor());
I2PThread executor = new I2PThread(new Executor(_context, _log, _readyEvents));
executor.setName(name + "Executor " + i);
executor.setDaemon(true);
executor.start();
@@ -114,7 +114,7 @@ public class SimpleTimer {
long timeToAdd = System.currentTimeMillis() - now;
if (timeToAdd > 50) {
if (_log.shouldLog(Log.WARN))
_log.warn("timer contention: took " + timeToAdd + "ms to add a job");
_log.warn("timer contention: took " + timeToAdd + "ms to add a job with " + totalEvents + " queued");
}
}
@@ -141,14 +141,6 @@ public class SimpleTimer {
public void timeReached();
}
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);
}
private long _occurredTime;
private long _occurredEventCount;
private TimedEvent _recentEvents[] = new TimedEvent[5];
@@ -228,30 +220,5 @@ public class SimpleTimer {
}
}
}
private class Executor implements Runnable {
public void run() {
while (true) {
TimedEvent evt = null;
synchronized (_readyEvents) {
if (_readyEvents.size() <= 0)
try { _readyEvents.wait(); } catch (InterruptedException ie) {}
if (_readyEvents.size() > 0)
evt = (TimedEvent)_readyEvents.remove(0);
}
if (evt != null) {
long before = _context.clock().now();
try {
evt.timeReached();
} catch (Throwable t) {
log("wtf, event borked: " + evt, t);
}
long time = _context.clock().now() - before;
if ( (time > 1000) && (_log != null) && (_log.shouldLog(Log.WARN)) )
_log.warn("wtf, event execution took " + time + ": " + evt);
}
}
}
}
}

View File

@@ -1,4 +1,380 @@
$Id: history.txt,v 1.479 2006-05-18 17:31:08 jrandom Exp $
$Id: history.txt,v 1.550 2007-02-14 16:35:43 jrandom Exp $
* 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-14 jrandom
* Don't filter out KICK and H(ide oper status) IRC messages
(thanks Takk and postman!)
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-01-30 zzz
* i2psnark: Don't hold _snarks lock while checking a snark,
so web page is responsive at startup
2007-01-29 zzz
* i2psnark: Add NickyB tracker
2007-01-28 zzz
* i2psnark: Don't hold sendQueue lock while flushing output,
to make everything run smoother
2007-01-27 zzz
* i2psnark: Fix orphaned Snark reader tasks leading to OOMs
2007-01-20 Complication
* Drop overlooked comment
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-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-17 zzz
* Add new HTTP Proxy error message for non-http protocols
2007-01-17 zzz
* Add note on Syndie index.html steering people to new Syndie
2007-01-16 zzz
* i2psnark: Fix crash when autostart off and
tcrrent started manually
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-15 jrandom
* small guard against unnecessary streaming lib reset packets
(thanks Complication!)
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-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)
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-10-10 0.6.1.26 released
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-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-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-09-29 zzz
* i2psnark: Second try at synchronization fix - synch addRequest()
completely rather than just portions of it and requestNextPiece()
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-26 Complication
* Take back another inadverent logging change in NTCPConnection
2006-09-26 Complication
* Take back an accidental log level change
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-25 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-24 zzz
* i2psnark: Add some synchronization to prevent rare problem
after restoring orphan piece
2006-09-20 zzz
* i2psnark: Eliminate duplicate requests caused by i2p-bt's
rapid choke/unchokes
* i2psnark: Truncate long TrackerErr messages on web page
2006-09-16 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-13 zzz
* i2psnark: Fix restoral of partial pieces broken by last patch
2006-09-13 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-09 zzz
* i2psnark: Add "Stalled" indication and stat totals on web page
2006-09-09 zzz
* i2psnark: Fix bug where new peers would always be sent an "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 0.6.1.25 released
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-07 zzz
* i2psnark: Increase output timeout from 2 min to 4 min
* i2psnark: Orphan debug msg cleanup
* i2psnark: More web rate report cleanup
2006-09-05 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-04 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 zzz
* Enable pipelining in i2psnark
* Make i2psnark tunnel default be 1 + 0-1
2006-09-03 zzz
* Add rate reporting to i2psnark
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-08-21 Complication
* Fix error reporting discrepancy (thanks for helping notice, yojoe!)
2006-08-03 jrandom
* Decrease the recently modified tunnel building timeout, though keep
the scaling on their processing
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-07-29 Complication
* Treat NTP responses from unexpected stratums like failures
* 2006-07-28 0.6.1.24 released
2006-07-28 jrandom
* Don't try to reverify too many netDb entries at once (thanks
cervantes and Complication!)
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-27 0.6.1.23 released
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 jrandom
* Further NTCP write status cleanup
* Handle more oddly-timed NTCP disconnections (thanks bar!)
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-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-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-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-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-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-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-06-14 cervantes
* Small tweak to I2PTunnel CSS, so it looks better with desktops
that use Bitstream Vera fonts @ 96 dpi
* 2006-06-14 0.6.1.21 released
2006-06-13 jrandom
* Use a minimum uptime of 2 hours, not 4 (oops)
2006-06-13 jrandom
* Cut down the proactive rejections due to queue size - if we are
at the point of having decrypted the request off the queue, might
as well let it through, rather than waste that decryption
2006-06-11 Kloug
* Bugfix to the I2PTunnel IRC filter to support multiple concurrent
outstanding pings/pongs
2006-06-10 jrandom
* Further reduction in proactive rejections
2006-06-09 jrandom
* Don't let the pending tunnel request queue grow beyond reason
(letting things sit for up to 30s when they fail after 10s
seems a bit... off)
2006-06-08 jrandom
* Be more conservative in the proactive rejections
2006-06-04 Complication
* Trim out sending a blank line before USER in susimail.
Seemed to break in rare cases, thanks for reporting, Brachtus!
* 2006-06-04 0.6.1.20 released

View File

@@ -1,6 +1,9 @@
; TC's hosts.txt guaranteed freshness
; $Id: hosts.txt,v 1.166 2006/01/23 10:29:36 cervantes Exp $
; $Id: hosts.txt,v 1.169 2006-12-16 17:31:07 jrandom Exp $
; changelog:
; (1.191) added trac.i2p
; (1.190) added archive.syndie.i2p
; (1.189) added mtn.i2p
; (1.188) added downloads.legion.i2p, politguy.i2p, ninja.i2p
; (1.187) added hidden.i2p, bk1k.i2p, antipiracyagency.i2p
; (1.186) added decadence.i2p, freedomarchives.i2p, closedshop.i2p
@@ -498,4 +501,7 @@ antipiracyagency.i2p=lnOKgQBEcsbZHgsuN5rJQA5mXK59fWPoCtag-EGfgYkbO1YbbPAqUZHqF4a
downloads.legion.i2p=p0eQqZscgqFvpoQtftNXFVRJpNzMkW1gx0cEmVz5xcpT195DxoVEwGlQ34mP4Da5nnUggcCaHzW9JBduqxiZU73quiO6VYeE65b70EhS0mRgsoVaU9-nsqo7ikYZ0-Rr6Qrhn32M6vktf4b2KljmpHgaBnJzMsFiMaEj3QuxGY8Q4tH6P34tgKiv2hYzZ8DGCj9bcmzzW0LX8XwA9tufi4XGM31qAZu~CiW14J5I8AwDUQRnyaWiZ8OzN~o3qTbkPyMAfXnAewcoA~GJPF9oAb-lHdESGeSEE38Krk2OYv3gQNUTiZcVaEck3VktqFmQiHCWDtAO0z8DIv9qmSjCI41w0MTCVlXNPRDO-YCE9JHlZv5zvS1~uCKltJwKGAHxHv~8N4oKMjJiB52XHXo3sWk503NTF-OK29ng~T4qfDQgEL6mm~jFhF3X-aruSkbn3OcRhWEgSJ2YdQYERnSwZn8gg4k78kvx02reisAHBN04UKa9YW95~Tz4S5Mqn8lrAAAA
politguy.i2p=nyQ37u3uP7Zm5YoFXL4MFEjJH6iQhrMs0K~Bk57fEIYRGJIG31~tqScmDLOUPjxiwzHEsbbLJo-o9rll7744HMOHmlfNGegKMO-n7~pWCVRW2XtMQL17NnZl97bPiDdymTVJoAKrs2ZfEayDYiiBxKbUlbcnT51O0~aTymSmlyh1i0VQajbQOveEdN-QDiOWEdBLZCQx7lg4BXT78Sb76qQVj4c6jdYRxP~q1nL1XHyjp~qd0jjLFNeTqS8YPdEDJFWAKo7M1bIYV4SUcj40GzGIuWRnaSY-utizQFrRUC0NcdwZK7Mo1JivGC0TFtWFJsQ-xx5ix4vP4vYeQ8rGiyED32s1kwII9rCBu9DmLLTwGBjTX0yjXI4XEH2Q-C0fgIw6YniDYQj50a3q1KTkaqiXm~EtnWqkv9dGvSYY8-mfBlRKD8k1X83-MhycdZHNrsWhGxA~zpBmKcMVHD0CDbW2ZF6sHyS3wPZiQ2haeBJL6mm7ZWQT6J875w3SP-nnAAAA
ninja.i2p=XhyN8MgL6DwWE98g2DJlDLHy~02g58IF14LTb472cmSLRj~cHv7wyi1mxy21vtf12oPWRquwMqpn4vJbKtnoQrLqRBgKAWtHgvUWOKPA8HqOmP4g9di3vGLefUNHUjkZWPXANBs-HwWH~~zPV0zwjN5keEZLVNQKfUkc0-qJ77zfxrDAPVjUBlyx1nlH7HcSxgzXS8qNlQuxWEqlqOiPushnalVFZSul-kVzG0Zo1PIQQHDKA1kpmwr2EX5xvCq0PgDZgj~xc3xkTzBMXC-m~NnmB1NRrH80lpY3nWMo-ySTVS7KcHH--zPUkvQhE2PCjLcSQsqpK2xsq0XEQOeAqoeDhosc3XxQDnDTmgBC0Bnus4N6RMBZgBRpniFT4ofwgGmFS1nDGkI-F4fLJSQ4f~P4sUR6HCZnwWIEvcr7lJgr26RP584nev9Dzhr4dq1hfTesShfluB19KJ17eBsimy1xIGDbiAeCSVtUjEviyb1lm7C~we3c0oCUUt2NvOGWAAAA
mtn.i2p=RqXC4xbFK6t3g2wk9SO4RjY7mj1c0DmtMra5c1Md8t-DcNPSjQFmqT97pcZ5IR1JDKqyCO7RI~aATTTMPQexoEeqK9-6Poeh2RA1C81FzcA9sHvjLeg3eB1Cju-sE-IDeFntEvCC4w7wWnpmLzCfdXK7OjSK1wYc6OkqPOLVDEy-N-4UUPlZFWWUghpjBGXGayXz6JRKtoMIwhFQaiKdRvAs32ozM9RM0NWzrCaRLZBIQ6Gg1Ys1wF0-oBJgC4T4CN6SxJNaz9Dfw4GNtPyD6lq3S1osY1ccflm3itvUt3JC1J9ypoXzylBE5MuS-LTgbgbMdMFty07AoWB~EY8TwW8EQQO07GSzB7hm53u0iCEH44GexhKHtQP-hYbIr3mklo89BvfWIRGMTwUkAzYojzC-vOyIh16LWrQQhGbLvByKQSdWk9nInm3GEfqRVtSpuqd4m6iHzrBDZ3fvKjbuywot3hDNHitOHOedmBNA8neCzLkod8b0Z4xx~qRIEObxAAAA
archive.syndie.i2p=iXX0DadZTJQpPr1to0OmQ4xokHgx1HYd5ec7~zIjQ80W~p4kRCYJmEzibH2Kn59Gi04SAXeA2O9i3bNqfGCQjsbz7UcjPGrW6-UrckXVXW67Moxp7QWY6i-aKuVYM3bqYxUL2mWvcDzJ8D0ecMpvasxhxwXpdFn2J6CGboMxeGV8R3hwwlNYbYoKgHr74qEJaIZpm1FrRWvNHV5cMv363iWnPy72XspQefk79-VOjPsxfummosU7gqlxl5teyiGKNzMs3G6iJyfVHO8IlKtdn~P~ET9p7zWlTPgV8NTyCVB-Wn5S3JMkMGOFZR7wSlxSwGFpTFQKc7mxVTtLZ5nWcV2OhvOIxRZ31RvGJZyVs562RC5aMfyqcM5IHQiZVlmkhzJKIy9VDw8tKayQtRM-xeN5k6Qr7iMmYIRORwuAODkYApoMD9a0eJ6ZYOSgBMOCSvYcwfT8axRY~GabiHm0QC82mo-nDgrUypGKtOPMI9MIqMTsb8Yl-UGWn6twBAIzAAAA
trac.i2p=OBnF9NtkEsPij2F-lp3bWDVrJsPQQPdq6adlpq0N4BY1XRjtDBZl~EpDdk7roq49~ptKAQG2cNUeBEKIIrdlZhJio5pMwUl6YinizzkNTFfZipB5OKoB7PBulxkw-N9mKMhS1btd9ajcV8tiP3xiv7VSlgiDwbdKg1fmkvNrVrJnzkN3-ey2kebYnbh7jjU2gPFUl~CwSEkIi6AK9EfqmFR-DUVohyygqAY~fi4EMeTVXGUqftXSNFYUwpRJgFrWRPTurtZnJK5403q67oEk0eWrPIZ8ytJWSBfffAXL3ts~0O1FZeKXUccsAl33j70~lklSolNVLJ40y-6X5ZLWajmX0ONU3j0qI5A~7fgNgsg-vKypPDuzl8ug-D~BmhqdAf0sRYmziDVwTgU~WRB6IzhhXFR6CbwrGXdgOGg2qNT1eOnMwGo3SMMJ7kK88VC5LdYg2dyiyjZATuvT92QdZglrVQIeBqAehcFjOBuycC1ED3AOak8D9Xplj7V6hN-HAAAA

View File

@@ -1,5 +1,5 @@
<i2p.news date="$Date: 2006-05-18 17:31:08 $">
<i2p.release version="0.6.1.20" date="2006/05/18" minVersion="0.6"
<i2p.news date="$Date: 2006-10-09 00:10:22 $">
<i2p.release version="0.6.1.27" date="2007/02/15" minVersion="0.6"
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-September/000878.html"

View File

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

View File

@@ -9,13 +9,6 @@
and they'll be reachable. The 'key' to your eepsite that you need to
give to other people is shown on the eepsite's I2PTunnel
<a href="http://localhost:7657/i2ptunnel/edit.jsp?tunnel=3">configuration page</a>). </p>
<p>
If you have any standard java web applications (.war files) such as
<a href="http://wiki.blojsom.com/wiki/display/blojsom/About%2Bblojsom">blojsom</a>
or <a href="http://snipsnap.org/space/start">SnipSnap</a>, simply drop their .war
file into ./eepsite/webapps/ and they'll be reachable at
http://$yourEeepsite/warFileName/</p>
<p>You can also reach your eepsite locally through
<a href="http://localhost:7658/">http://localhost:7658/</a>. If you
want to change the port number, edit the file ./eepsite/jetty.xml and

View File

@@ -55,7 +55,7 @@ tunnel.3.i2cpHost=127.0.0.1
tunnel.3.i2cpPort=7654
tunnel.3.option.inbound.nickname=eepsite
tunnel.3.option.outbound.nickname=eepsite
tunnel.3.startOnLoad=true
tunnel.3.startOnLoad=false
# postman's SMTP server - see www.postman.i2p
tunnel.4.description=smtp server

View File

@@ -138,7 +138,7 @@
#tunnelListPage .footer label {
text-align : right;
height : 24px;
width : 160px;
width : 360px;
float : left;
}

View File

@@ -122,7 +122,7 @@
#tunnelListPage .footer label {
text-align : right;
height : 24px;
width : 160px;
width : 360px;
float : left;
}

View File

@@ -1,5 +1,5 @@
<i2p.news date="$Date: 2006-05-30 22:19:24 $">
<i2p.release version="0.6.1.20" date="2006/05/18" minVersion="0.6"
<i2p.news date="$Date: 2007-02-13 22:04:11 $">
<i2p.release version="0.6.1.27" date="2007/02/15" minVersion="0.6"
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-September/000878.html"
@@ -10,13 +10,13 @@
anonlogs="http://i2p/Nf3ab-ZFkmI-LyMt7GjgT-jfvZ3zKDl0L96pmGQXF1B82W2Bfjf0n7~288vafocjFLnQnVcmZd~-p0-Oolfo9aW2Rm-AhyqxnxyLlPBqGxsJBXjPhm1JBT4Ia8FB-VXt0BuY0fMKdAfWwN61-tj4zIcQWRxv3DFquwEf035K~Ra4SWOqiuJgTRJu7~o~DzHVljVgWIzwf8Z84cz0X33pv-mdG~~y0Bsc2qJVnYwjjR178YMcRSmNE0FVMcs6f17c6zqhMw-11qjKpY~EJfHYCx4lBWF37CD0obbWqTNUIbL~78vxqZRT3dgAgnLixog9nqTO-0Rh~NpVUZnoUi7fNR~awW5U3Cf7rU7nNEKKobLue78hjvRcWn7upHUF45QqTDuaM3yZa7OsjbcH-I909DOub2Q0Dno6vIwuA7yrysccN1sbnkwZbKlf4T6~iDdhaSLJd97QCyPOlbyUfYy9QLNExlRqKgNVJcMJRrIual~Lb1CLbnzt0uvobM57UpqSAAAA/meeting141"
publiclogs="http://www.i2p.net/meeting141" />
&#149;
2006-05-18: 0.6.1.19 <a href="http://dev.i2p/pipermail/i2p/2006-May/001290.html">released</a>
with PRNG bugfixes, congestion handling and SSU improvements.
2006-10-09: 0.6.1.26 <a href="http://dev.i2p/pipermail/i2p/2006-October/001312.html">released</a>
with i2psnark and NTP fixes.
<br />
&#149;
2006-05-30:
<a href="http://dev.i2p/pipermail/i2p/2006-May/001291.html">status notes</a>
2007-02-13:
<a href="http://dev.i2p/pipermail/i2p/2007-February/001334.html">status notes</a>
and
<a href="http://www.i2p/meeting181">meeting log</a>
<a href="http://www.i2p/meeting200">meeting log</a>
<br />
</i2p.news>

View File

@@ -3,7 +3,7 @@ grow over the next few minutes and you'll see some local "destinations" listed
on the left (if not, <a href="#trouble">see below</a>). Once those show up,
you can:</p>
<ul>
<li><b>blog anonymously</b> - check out <a href="/syndie/">Syndie</a></li>
<li><b>blog anonymously</b> - check out <a href="http://syndie.i2pt/">Syndie</a></li>
<li><b>chat anonymously</b> - fire up your own IRC client and connect to the
server at <b>localhost port 6668</b>. This points at one of two anonymously hosted
IRC servers, but neither you nor they know where the other is.</li>
@@ -44,9 +44,8 @@ you can:</p>
<a href="http://localhost:7658/">http://localhost:7658/</a>. Simply place your files in
the <code>eepsite/docroot/</code> directory (or place any standard JSP/Servlet <code>.war</code>
files under <code>eepsite/webapps</code>, or standard CGI script under <code>eepsite/cgi-bin</code>)
and they'll show up. Your eepsite's
<i>destination</i> (which uniquely and securely identifies it) can be found via the I2PTunnel
<a href="/i2ptunnel/">configuration page</a> - on the details page of the "eepsite" tunnel.
and they'll show up. After starting up an <a href="/i2ptunnel/">eepsite tunnel</a> pointing at it, your eepsite's
<i>destination</i> (which uniquely and securely identifies it) will be visible.
If you want other people to see your eepsite,
you need to give them that really huge string. Just paste it into the
<a href="http://forum.i2p/viewforum.php?f=16">Eepsite announce</a> forum, add it to

View File

@@ -95,8 +95,18 @@ public class I2NPMessageHandler {
cur++;
_lastReadBegin = System.currentTimeMillis();
I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type);
if (msg == null)
throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message");
if (msg == null) {
int sz = data.length-offset;
boolean allZero = false;
for (int i = offset; i < data.length; i++) {
if (data[i] != 0) {
allZero = false;
break;
}
}
throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message (remaining sz="
+ sz + " all zeros? " + allZero + ")");
}
try {
_lastSize = msg.readBytes(data, type, cur);
cur += _lastSize;

View File

@@ -33,6 +33,18 @@ public abstract class CommSystemFacade implements Service {
public int countActiveSendPeers() { return 0; }
public List getMostRecentErrorMessages() { return Collections.EMPTY_LIST; }
/**
* Median clock skew of connected peers in seconds, or null if we cannot answer.
* CommSystemFacadeImpl overrides this.
*/
public Long getMedianPeerClockSkew() { return null; }
/**
* Return framed average clock skew of connected peers in seconds, or null if we cannot answer.
* CommSystemFacadeImpl overrides this.
*/
public Long getFramedAveragePeerClockSkew(int percentToInclude) { return null; }
/**
* Determine under what conditions we are remotely reachable.
*

View File

@@ -78,7 +78,11 @@ public class LoadTestManager {
private static final boolean DEFAULT_ENABLE = false;
/** disable all load testing for the moment */
private static final boolean FORCE_DISABLE = true;
public static boolean isEnabled(I2PAppContext ctx) {
if (FORCE_DISABLE) return false;
String enable = ctx.getProperty("router.enableLoadTesting");
if ( (DEFAULT_ENABLE) && (enable != null) && (!Boolean.valueOf(enable).booleanValue()) )
return false;
@@ -130,6 +134,7 @@ public class LoadTestManager {
* Actually send the messages through the given tunnel
*/
private void runTest(LoadTestTunnelConfig tunnel) {
if (!isEnabled(_context)) return;
log(tunnel, "start");
int peerMessages = getPeerMessages();
if (_log.shouldLog(Log.DEBUG))
@@ -208,9 +213,17 @@ public class LoadTestManager {
// this should take into consideration both the inbound and outbound tunnels
// ... but it doesn't, yet.
_context.messageRegistry().registerPending(new Selector(tunnel, payloadMessage.getUniqueId()),
new SendAgain(_context, tunnel, payloadMessage.getUniqueId(), true),
new SendAgain(_context, tunnel, payloadMessage.getUniqueId(), false),
long uniqueId = -1;
if (payloadMessage != null) {
uniqueId = payloadMessage.getUniqueId();
} else {
tunnel.logComplete();
_active.remove(tunnel);
return;
}
_context.messageRegistry().registerPending(new Selector(tunnel, uniqueId),
new SendAgain(_context, tunnel, uniqueId, true),
new SendAgain(_context, tunnel, uniqueId, false),
10*1000);
_context.tunnelDispatcher().dispatchOutbound(payloadMessage, outbound.getSendTunnelId(0),
inbound.getReceiveTunnelId(0),

View File

@@ -48,6 +48,7 @@ public class OutNetMessage {
private MessageSelector _replySelector;
private Set _failedTransports;
private long _sendBegin;
private long _transmitBegin;
private Exception _createdBy;
private long _created;
/** for debugging, contains a mapping of even name to Long (e.g. "begin sending", "handleOutbound", etc) */
@@ -57,6 +58,10 @@ public class OutNetMessage {
* (some JVMs have less than 10ms resolution, so the Long above doesn't guarantee order)
*/
private List _timestampOrder;
private int _queueSize;
private long _prepareBegin;
private long _prepareEnd;
private Object _preparationBuf;
public OutNetMessage(RouterContext context) {
_context = context;
@@ -148,6 +153,7 @@ public class OutNetMessage {
_messageType = msg.getClass().getName();
_messageTypeId = msg.getType();
_messageId = msg.getUniqueId();
_messageSize = _message.getMessageSize();
}
}
@@ -235,9 +241,31 @@ public class OutNetMessage {
/** when did the sending process begin */
public long getSendBegin() { return _sendBegin; }
public void beginSend() { _sendBegin = _context.clock().now(); }
public void beginTransmission() { _transmitBegin = _context.clock().now(); }
public void beginPrepare() { _prepareBegin = _context.clock().now(); }
public void prepared() { prepared(null); }
public void prepared(Object buf) {
_prepareEnd = _context.clock().now();
_preparationBuf = buf;
}
public Object releasePreparationBuffer() {
Object rv = _preparationBuf;
_preparationBuf = null;
return rv;
}
public long getCreated() { return _created; }
/** time since the message was created */
public long getLifetime() { return _context.clock().now() - _created; }
/** time the transport tries to send the message (including any queueing) */
public long getSendTime() { return _context.clock().now() - _sendBegin; }
/** time during which the i2np message is actually in flight */
public long getTransmissionTime() { return _context.clock().now() - _transmitBegin; }
/** how long it took to prepare the i2np message for transmission (including serialization and transport layer encryption) */
public long getPreparationTime() { return _prepareEnd - _prepareBegin; }
/** number of messages ahead of this one going to the targetted peer when it is first enqueued */
public int getQueueSize() { return _queueSize; }
public void setQueueSize(int size) { _queueSize = size; }
/**
* We've done what we need to do with the data from this message, though
@@ -245,6 +273,8 @@ public class OutNetMessage {
*/
public void discardData() {
long timeToDiscard = _context.clock().now() - _created;
if ( (_message != null) && (_messageSize <= 0) )
_messageSize = _message.getMessageSize();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Discard " + _messageSize + "byte " + _messageType + " message after "
+ timeToDiscard);

View File

@@ -240,8 +240,10 @@ public class Router {
readConfig();
setupHandlers();
if ("true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false")))
killKeys();
if (ALLOW_DYNAMIC_KEYS) {
if ("true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false")))
killKeys();
}
_context.messageValidator().startup();
_context.tunnelDispatcher().startup();
@@ -323,6 +325,7 @@ public class Router {
ri.setPublished(_context.clock().now());
Properties stats = _context.statPublisher().publishStatistics();
stats.setProperty(RouterInfo.PROP_NETWORK_ID, NETWORK_ID+"");
ri.setOptions(stats);
ri.setAddresses(_context.commSystem().createAddresses());
@@ -371,7 +374,8 @@ public class Router {
public void addCapabilities(RouterInfo ri) {
int bwLim = Math.min(_context.bandwidthLimiter().getInboundKBytesPerSecond(),
_context.bandwidthLimiter().getInboundKBytesPerSecond());
_context.bandwidthLimiter().getOutboundKBytesPerSecond());
bwLim = (int)(((float)bwLim) * getSharePercentage());
if (_log.shouldLog(Log.WARN))
_log.warn("Adding capabilities w/ bw limit @ " + bwLim, new Exception("caps"));
@@ -444,15 +448,32 @@ public class Router {
"keyBackup/publicSigning.key",
"sessionKeys.dat" };
static final String IDENTLOG = "identlog.txt";
public static void killKeys() {
new Exception("Clearing identity files").printStackTrace();
int remCount = 0;
for (int i = 0; i < _rebuildFiles.length; i++) {
File f = new File(_rebuildFiles[i]);
if (f.exists()) {
boolean removed = f.delete();
if (removed)
if (removed) {
System.out.println("INFO: Removing old identity file: " + _rebuildFiles[i]);
else
remCount++;
} else {
System.out.println("ERROR: Could not remove old identity file: " + _rebuildFiles[i]);
}
}
}
if (remCount > 0) {
FileOutputStream log = null;
try {
log = new FileOutputStream(IDENTLOG, true);
log.write((new Date() + ": Old router identity keys cleared\n").getBytes());
} catch (IOException ioe) {
// ignore
} finally {
if (log != null)
try { log.close(); } catch (IOException ioe) {}
}
}
}
@@ -796,11 +817,19 @@ public class Router {
finalShutdown(exitCode);
}
/**
* disable dynamic key functionality for the moment, as it may be harmful and doesn't
* add meaningful anonymity
*/
private static final boolean ALLOW_DYNAMIC_KEYS = false;
public void finalShutdown(int exitCode) {
_log.log(Log.CRIT, "Shutdown(" + exitCode + ") complete", new Exception("Shutdown"));
try { _context.logManager().shutdown(); } catch (Throwable t) { }
if ("true".equalsIgnoreCase(_context.getProperty(PROP_DYNAMIC_KEYS, "false")))
killKeys();
if (ALLOW_DYNAMIC_KEYS) {
if ("true".equalsIgnoreCase(_context.getProperty(PROP_DYNAMIC_KEYS, "false")))
killKeys();
}
File f = new File(getPingFile());
f.delete();
@@ -1300,4 +1329,4 @@ class PersistRouterInfoJob extends JobImpl {
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
}
}
}
}

View File

@@ -0,0 +1,104 @@
package net.i2p.router;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
import net.i2p.router.RouterContext;
import net.i2p.util.Clock;
/**
* Alternate location for determining the time which takes into account an offset.
* This offset will ideally be periodically updated so as to serve as the difference
* between the local computer's current time and the time as known by some reference
* (such as an NTP synchronized clock).
*
* RouterClock is a subclass of Clock with access to router transports.
* Configuration permitting, it will block clock offset changes
* which would increase peer clock skew.
*/
public class RouterClock extends Clock {
RouterContext _context;
public RouterClock(RouterContext context) {
super(context);
_context = context;
}
/**
* Specify how far away from the "correct" time the computer is - a positive
* value means that we are slow, while a negative value means we are fast.
*
*/
public void setOffset(long offsetMs, boolean force) {
if (false) return;
long delta = offsetMs - _offset;
if (!force) {
if ((offsetMs > MAX_OFFSET) || (offsetMs < 0 - MAX_OFFSET)) {
getLog().error("Maximum offset shift exceeded [" + offsetMs + "], NOT HONORING IT");
return;
}
// only allow substantial modifications before the first 10 minutes
if (_alreadyChanged && (System.currentTimeMillis() - _startedOn > 10 * 60 * 1000)) {
if ( (delta > MAX_LIVE_OFFSET) || (delta < 0 - MAX_LIVE_OFFSET) ) {
getLog().log(Log.CRIT, "The clock has already been updated, but you want to change it by "
+ delta + " to " + offsetMs + "? Did something break?");
return;
}
}
if ((delta < MIN_OFFSET_CHANGE) && (delta > 0 - MIN_OFFSET_CHANGE)) {
getLog().debug("Not changing offset since it is only " + delta + "ms");
_alreadyChanged = true;
return;
}
// If so configured, check sanity of proposed clock offset
if (Boolean.valueOf(_context.getProperty("router.clockOffsetSanityCheck","true")).booleanValue() == true) {
// Try calculating peer clock skew
Long peerClockSkew = _context.commSystem().getFramedAveragePeerClockSkew(50);
if (peerClockSkew != null) {
// Predict the effect of applying the proposed clock offset
long currentPeerClockSkew = peerClockSkew.longValue();
long predictedPeerClockSkew = currentPeerClockSkew + (delta / 1000l);
// Fail sanity check if applying the offset would increase peer clock skew
if ((Math.abs(predictedPeerClockSkew) > (Math.abs(currentPeerClockSkew) + 5)) ||
(Math.abs(predictedPeerClockSkew) > 20)) {
getLog().error("Ignoring clock offset " + offsetMs + "ms (current " + _offset +
"ms) since it would increase peer clock skew from " + currentPeerClockSkew +
"s to " + predictedPeerClockSkew + "s. Broken server in pool.ntp.org?");
return;
} else {
getLog().debug("Approving clock offset " + offsetMs + "ms (current " + _offset +
"ms) since it would decrease peer clock skew from " + currentPeerClockSkew +
"s to " + predictedPeerClockSkew + "s.");
}
}
} // check sanity
}
if (_alreadyChanged) {
if (delta > 15*1000)
getLog().log(Log.CRIT, "Updating clock offset to " + offsetMs + "ms from " + _offset + "ms");
else if (getLog().shouldLog(Log.INFO))
getLog().info("Updating clock offset to " + offsetMs + "ms from " + _offset + "ms");
if (!_statCreated)
_context.statManager().createRateStat("clock.skew", "How far is the already adjusted clock being skewed?", "Clock", new long[] { 10*60*1000, 3*60*60*1000, 24*60*60*60 });
_statCreated = true;
_context.statManager().addRateData("clock.skew", delta, 0);
} else {
getLog().log(Log.INFO, "Initializing clock offset to " + offsetMs + "ms from " + _offset + "ms");
}
_alreadyChanged = true;
_offset = offsetMs;
fireOffsetChanged(delta);
}
}

View File

@@ -26,6 +26,8 @@ import net.i2p.router.transport.OutboundMessageRegistry;
import net.i2p.router.transport.VMCommSystem;
import net.i2p.router.tunnel.pool.TunnelPoolManager;
import net.i2p.router.tunnel.TunnelDispatcher;
import net.i2p.util.Clock;
import net.i2p.router.RouterClock;
/**
* Build off the core I2P context to provide a root for a router instance to
@@ -59,13 +61,15 @@ public class RouterContext extends I2PAppContext {
private MessageValidator _messageValidator;
private MessageStateMonitor _messageStateMonitor;
private RouterThrottle _throttle;
private RouterClock _clock;
private Calculator _isFailingCalc;
private Calculator _integrationCalc;
private Calculator _speedCalc;
private Calculator _reliabilityCalc;
private Calculator _capacityCalc;
private Calculator _oldSpeedCalc;
private static List _contexts = new ArrayList(1);
public RouterContext(Router router) { this(router, null); }
@@ -323,4 +327,25 @@ public class RouterContext extends I2PAppContext {
}
return super.getProperty(propName, defaultVal);
}
/**
* The context's synchronized clock, which is kept context specific only to
* enable simulators to play with clock skew among different instances.
*
* It wouldn't be necessary to override clock(), except for the reason
* that it triggers initializeClock() of which we definitely
* need the local version to run.
*/
public Clock clock() {
if (!_clockInitialized) initializeClock();
return _clock;
}
protected void initializeClock() {
synchronized (this) {
if (_clock == null)
_clock = new RouterClock(this);
_clockInitialized = true;
}
}
}

View File

@@ -151,8 +151,8 @@ class RouterThrottleImpl implements RouterThrottle {
else
avg10m = tunnelTestTime10m.getLifetimeAverageValue();
if (avg10m < 2000)
avg10m = 2000; // minimum before complaining
if (avg10m < 5000)
avg10m = 5000; // minimum before complaining
if ( (avg10m > 0) && (avg1m > avg10m * tunnelTestTimeGrowthFactor) ) {
double probAccept = (avg10m*tunnelTestTimeGrowthFactor)/avg1m;
@@ -163,7 +163,7 @@ class RouterThrottleImpl implements RouterThrottle {
if (_log.shouldLog(Log.INFO))
_log.info("Probabalistically accept tunnel request (p=" + probAccept
+ " v=" + v + " test time avg 1m=" + avg1m + " 10m=" + avg10m + ")");
} else {
} else if (false) {
if (_log.shouldLog(Log.WARN))
_log.warn("Probabalistically refusing tunnel request (test time avg 1m=" + avg1m
+ " 10m=" + avg10m + ")");
@@ -227,8 +227,12 @@ class RouterThrottleImpl implements RouterThrottle {
else
timePerRequest = (int)rs.getLifetimeAverageValue();
}
float pctFull = (queuedRequests * timePerRequest) / (10*1000f);
float pReject = 1 - ((1-pctFull) * (1-pctFull));
float pctFull = (queuedRequests * timePerRequest) / (4*1000f);
double pReject = Math.pow(pctFull, 16); //1 - ((1-pctFull) * (1-pctFull));
// let it in because we drop overload- rejecting may be overkill,
// especially since we've done the cpu-heavy lifting to figure out
// whats up
/*
if ( (pctFull >= 1) || (pReject >= _context.random().nextFloat()) ) {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting a new tunnel request because we have too many pending requests (" + queuedRequests
@@ -236,6 +240,7 @@ class RouterThrottleImpl implements RouterThrottle {
_context.statManager().addRateData("router.throttleTunnelQueueOverload", queuedRequests, timePerRequest);
return TunnelHistory.TUNNEL_REJECT_TRANSIENT_OVERLOAD;
}
*/
// ok, all is well, let 'er in
_context.statManager().addRateData("tunnel.bytesAllocatedAtAccept", (long)bytesAllocated, 60*10*1000);
@@ -269,7 +274,10 @@ class RouterThrottleImpl implements RouterThrottle {
_context.statManager().addRateData("router.throttleTunnelBytesUsed", used, maxKBps);
_context.statManager().addRateData("router.throttleTunnelBytesAllowed", availBps, (long)bytesAllocated);
if (used1m > (maxKBps*1024)) {
long overage = used1m - (maxKBps*1024);
if ( (overage > 0) &&
((overage/(float)(maxKBps*1024f)) > _context.random().nextFloat()) ) {
if (_log.shouldLog(Log.WARN)) _log.warn("Reject tunnel, 1m rate (" + used1m + ") indicates overload.");
return false;
}
@@ -342,9 +350,9 @@ class RouterThrottleImpl implements RouterThrottle {
/** dont ever probabalistically throttle tunnels if we have less than this many */
private int getMinThrottleTunnels() {
try {
return Integer.parseInt(_context.getProperty("router.minThrottleTunnels", "40"));
return Integer.parseInt(_context.getProperty("router.minThrottleTunnels", "1000"));
} catch (NumberFormatException nfe) {
return 40;
return 1000;
}
}

View File

@@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
*
*/
public class RouterVersion {
public final static String ID = "$Revision: 1.419 $ $Date: 2006-05-18 17:31:10 $";
public final static String VERSION = "0.6.1.20";
public final static String ID = "$Revision: 1.486 $ $Date: 2007-02-14 16:35:44 $";
public final static String VERSION = "0.6.1.27";
public final static long BUILD = 0;
public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION + "-" + BUILD);

View File

@@ -10,47 +10,85 @@ package net.i2p.router;
import java.io.IOException;
import java.io.Writer;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.*;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.router.peermanager.PeerProfile;
import net.i2p.util.Log;
/**
* Manage in memory the routers we are oh so fond of.
* This needs to get a little bit more sophisticated... currently there is no
* way out of the shitlist
*
* Routers are shitlisted only if none of our transports can talk to them
* or their signed router info is completely screwy. Individual transports
* manage their own unreachable lists and do not generally add to the overall
* shitlist.
*/
public class Shitlist {
private Log _log;
private RouterContext _context;
private Map _shitlist; // H(routerIdent) --> Date
private Map _shitlistCause; // H(routerIdent) --> String
private Map _entries;
private class Entry {
/** when it should expire, per the i2p clock */
long expireOn;
/** why they were shitlisted */
String cause;
/** what transports they were shitlisted for (String), or null for all transports */
Set transports;
}
public final static long SHITLIST_DURATION_MS = 4*60*1000; // 4 minute shitlist
public Shitlist(RouterContext context) {
_context = context;
_log = context.logManager().getLog(Shitlist.class);
_shitlist = new HashMap(5);
_shitlistCause = new HashMap(5);
_entries = new HashMap(32);
_context.jobQueue().addJob(new Cleanup(_context));
}
private class Cleanup extends JobImpl {
private List _toUnshitlist;
public Cleanup(RouterContext ctx) {
super(ctx);
_toUnshitlist = new ArrayList(4);
}
public String getName() { return "Cleanup shitlist"; }
public void runJob() {
_toUnshitlist.clear();
long now = getContext().clock().now();
synchronized (_entries) {
for (Iterator iter = _entries.keySet().iterator(); iter.hasNext(); ) {
Hash peer = (Hash)iter.next();
Entry entry = (Entry)_entries.get(peer);
if (entry.expireOn <= now) {
iter.remove();
_toUnshitlist.add(peer);
}
}
}
for (int i = 0; i < _toUnshitlist.size(); i++) {
Hash peer = (Hash)_toUnshitlist.get(i);
PeerProfile prof = _context.profileOrganizer().getProfile(peer);
if (prof != null)
prof.unshitlist();
_context.messageHistory().unshitlist(peer);
}
requeue(30*1000);
}
}
public int getRouterCount() {
purge();
synchronized (_shitlist) {
return _shitlist.size();
synchronized (_entries) {
return _entries.size();
}
}
public boolean shitlistRouter(Hash peer) {
return shitlistRouter(peer, null);
}
public boolean shitlistRouter(Hash peer, String reason) {
public boolean shitlistRouter(Hash peer, String reason) { return shitlistRouter(peer, reason, null); }
public boolean shitlistRouter(Hash peer, String reason, String transport) {
if (peer == null) {
_log.error("wtf, why did we try to shitlist null?", new Exception("shitfaced"));
return false;
@@ -73,18 +111,33 @@ public class Shitlist {
if (period > 60*60*1000)
period = 60*60*1000;
synchronized (_shitlist) {
Date oldDate = (Date)_shitlist.put(peer, new Date(_context.clock().now() + period));
wasAlready = (null == oldDate);
if (reason != null) {
if (!wasAlready || (!_shitlistCause.containsKey(peer)) )
_shitlistCause.put(peer, reason);
} else {
_shitlistCause.remove(peer);
Entry e = new Entry();
e.expireOn = _context.clock().now() + period;
e.cause = reason;
e.transports = null;
if (transport != null) {
e.transports = new HashSet(1);
e.transports.add(transport);
}
synchronized (_entries) {
Entry old = (Entry)_entries.put(peer, e);
if (old != null) {
wasAlready = true;
_entries.put(peer, old);
if (e.transports == null) {
old.transports = null;
} else if (old.transports != null) {
old.transports.addAll(e.transports);
}
e = old;
}
}
_context.netDb().fail(peer);
if (transport == null) {
// we hate the peer on *any* transport
_context.netDb().fail(peer);
}
//_context.tunnelManager().peerFailed(peer);
//_context.messageRegistry().peerFailed(peer);
if (!wasAlready)
@@ -95,91 +148,107 @@ public class Shitlist {
public void unshitlistRouter(Hash peer) {
unshitlistRouter(peer, true);
}
private void unshitlistRouter(Hash peer, boolean realUnshitlist) {
private void unshitlistRouter(Hash peer, boolean realUnshitlist) { unshitlistRouter(peer, realUnshitlist, null); }
public void unshitlistRouter(Hash peer, String transport) { unshitlistRouter(peer, true, transport); }
private void unshitlistRouter(Hash peer, boolean realUnshitlist, String transport) {
if (peer == null) return;
if (_log.shouldLog(Log.INFO))
_log.info("Unshitlisting router " + peer.toBase64());
synchronized (_shitlist) {
_shitlist.remove(peer);
_shitlistCause.remove(peer);
_log.info("Unshitlisting router " + peer.toBase64()
+ (transport != null ? "/" + transport : ""));
boolean fully = false;
synchronized (_entries) {
Entry e = (Entry)_entries.remove(peer);
if ( (e == null) || (e.transports == null) || (transport == null) || (e.transports.size() <= 1) ) {
// fully unshitlisted
fully = true;
} else {
e.transports.remove(transport);
if (e.transports.size() <= 0)
fully = true;
else
_entries.put(peer, e);
}
}
if (realUnshitlist) {
if (fully) {
if (realUnshitlist) {
PeerProfile prof = _context.profileOrganizer().getProfile(peer);
if (prof != null)
prof.unshitlist();
}
_context.messageHistory().unshitlist(peer);
}
}
public boolean isShitlisted(Hash peer) { return isShitlisted(peer, null); }
public boolean isShitlisted(Hash peer, String transport) {
boolean rv = false;
boolean unshitlist = false;
synchronized (_entries) {
Entry entry = (Entry)_entries.get(peer);
if (entry == null) {
rv = false;
} else {
if (entry.expireOn <= _context.clock().now()) {
_entries.remove(peer);
unshitlist = true;
rv = false;
} else {
if (entry.transports == null) {
rv = true;
} else if (entry.transports.contains(transport)) {
rv = true;
} else {
rv = false;
}
}
}
}
if (unshitlist) {
PeerProfile prof = _context.profileOrganizer().getProfile(peer);
if (prof != null)
prof.unshitlist();
}
_context.messageHistory().unshitlist(peer);
}
public boolean isShitlisted(Hash peer) {
Date shitlistDate = null;
synchronized (_shitlist) {
shitlistDate = (Date)_shitlist.get(peer);
}
if (shitlistDate == null) return false;
// check validity
if (shitlistDate.getTime() > _context.clock().now()) {
return true;
} else {
unshitlistRouter(peer, false);
return false;
}
}
/**
* We already unshitlist on isShitlisted, but this purge
* lets us get the correct value when rendering the HTML or
* getting the shitlist count. wheee
*
*/
private void purge() {
Map shitlist = null;
synchronized (_shitlist) {
shitlist = new HashMap(_shitlist);
_context.messageHistory().unshitlist(peer);
}
long limit = _context.clock().now();
for (Iterator iter = shitlist.keySet().iterator(); iter.hasNext(); ) {
Hash key = (Hash)iter.next();
Date shitDate = (Date)shitlist.get(key);
if (shitDate.getTime() < limit) {
unshitlistRouter(key, false);
}
}
return rv;
}
public void renderStatusHTML(Writer out) throws IOException {
StringBuffer buf = new StringBuffer(1024);
buf.append("<h2>Shitlist</h2>");
Map shitlist = null;
Map causes = null;
Map entries = null;
purge();
synchronized (_shitlist) {
shitlist = new HashMap(_shitlist);
causes = new HashMap(_shitlistCause);
synchronized (_entries) {
entries = new HashMap(_entries);
}
buf.append("<ul>");
for (Iterator iter = shitlist.keySet().iterator(); iter.hasNext(); ) {
int partial = 0;
for (Iterator iter = entries.keySet().iterator(); iter.hasNext(); ) {
Hash key = (Hash)iter.next();
Date shitDate = (Date)shitlist.get(key);
Entry entry = (Entry)entries.get(key);
if ( (entry.transports != null) && (entry.transports.size() > 0) ) {
partial++;
continue;
}
buf.append("<li><b>").append(key.toBase64()).append("</b>");
buf.append(" <a href=\"netdb.jsp#").append(key.toBase64().substring(0, 6)).append("\">(?)</a>");
buf.append(" expiring on ");
buf.append(shitDate);
String cause = (String)causes.get(key);
if (cause != null) {
buf.append(" expiring in ");
buf.append(DataHelper.formatDuration(entry.expireOn-_context.clock().now()));
Set transports = entry.transports;
if ( (transports != null) && (transports.size() > 0) )
buf.append(" on the following transports: ").append(transports);
if (entry.cause != null) {
buf.append("<br />\n");
buf.append(cause);
buf.append(entry.cause);
}
buf.append("</li>\n");
}
buf.append("</ul>\n");
buf.append("<i>Partial shitlisted peers (only blocked on some transports): ");
buf.append(partial);
buf.append("</i>\n");
out.write(buf.toString());
out.flush();
}

View File

@@ -8,6 +8,8 @@ package net.i2p.router;
*
*/
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Writer;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
@@ -100,7 +102,28 @@ public class StatisticsManager implements Service {
// No longer expose, to make build tracking more expensive
// stats.setProperty("router.id", RouterVersion.ID);
// stats.setProperty("core.id", CoreVersion.ID);
int newlines = 0;
FileInputStream in = null;
try {
in = new FileInputStream(Router.IDENTLOG);
int c = -1;
// perhaps later filter this to only include ident changes this
// day/week/month
while ( (c = in.read()) != -1) {
if (c == '\n')
newlines++;
}
} catch (IOException ioe) {
// ignore
} finally {
if (in != null)
try { in.close(); } catch (IOException ioe) {}
}
if (newlines > 0)
stats.setProperty("stat_identities", newlines+"");
if (_includePeerRankings) {
if (false)
stats.putAll(_context.profileManager().summarizePeers(_publishedStats));

View File

@@ -320,7 +320,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
}
boolean wantACK = true;
int existingTags = GarlicMessageBuilder.estimateAvailableTags(getContext(), _leaseSet.getEncryptionKey());
if (existingTags > 30)
if ( (existingTags > 30) && (getContext().random().nextInt(100) >= 5) )
wantACK = false;
long token = (wantACK ? getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE) : -1);

View File

@@ -44,7 +44,7 @@ class FloodOnlySearchJob extends FloodSearchJob {
_isLease = isLease;
_lookupsRemaining = 0;
_dead = false;
_out = new ArrayList(2);
_out = Collections.synchronizedList(new ArrayList(2));
_replySelector = new FloodOnlyLookupSelector(getContext(), this);
_onReply = new FloodOnlyLookupMatchJob(getContext(), this);
_onTimeout = new FloodOnlyLookupTimeoutJob(getContext(), this);
@@ -70,7 +70,7 @@ class FloodOnlySearchJob extends FloodSearchJob {
return;
}
OutNetMessage out = getContext().messageRegistry().registerPending(_replySelector, _onReply, _onTimeout, _timeoutMs);
_out.add(out);
synchronized (_out) { _out.add(out); }
for (int i = 0; _lookupsRemaining < CONCURRENT_SEARCHES && i < floodfillPeers.size(); i++) {
Hash peer = (Hash)floodfillPeers.get(i);
@@ -113,8 +113,10 @@ class FloodOnlySearchJob extends FloodSearchJob {
if (_dead) return;
_dead = true;
}
for (int i = 0; i < _out.size(); i++) {
OutNetMessage out = (OutNetMessage)_out.get(i);
List outBuf = null;
synchronized (_out) { outBuf = new ArrayList(_out); }
for (int i = 0; i < outBuf.size(); i++) {
OutNetMessage out = (OutNetMessage)outBuf.get(i);
getContext().messageRegistry().unregisterPending(out);
}
int timeRemaining = (int)(_origExpiration - getContext().clock().now());

View File

@@ -227,10 +227,59 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
}
/** list of the Hashes of currently known floodfill peers */
List getFloodfillPeers() {
public List getFloodfillPeers() {
FloodfillPeerSelector sel = (FloodfillPeerSelector)getPeerSelector();
return sel.selectFloodfillParticipants(getKBuckets());
}
protected void lookupBeforeDropping(Hash peer, RouterInfo info) {
if (_context.jobQueue().getMaxLag() > 500) {
// don't try to overload ourselves (e.g. failing 3000 router refs at
// once, and then firing off 3000 netDb lookup tasks)
super.lookupBeforeDropping(peer, info);
return;
}
// this sends out the search to the floodfill peers even if we already have the
// entry locally, firing no job if it gets a reply with an updated value (meaning
// we shouldn't drop them but instead use the new data), or if they all time out,
// firing the dropLookupFailedJob, which actually removes out local reference
search(peer, new DropLookupFoundJob(_context, peer, info), new DropLookupFailedJob(_context, peer, info), 10*1000, false);
}
private class DropLookupFailedJob extends JobImpl {
private Hash _peer;
private RouterInfo _info;
public DropLookupFailedJob(RouterContext ctx, Hash peer, RouterInfo info) {
super(ctx);
_peer = peer;
_info = info;
}
public String getName() { return "Lookup on failure of netDb peer timed out"; }
public void runJob() {
dropAfterLookupFailed(_peer, _info);
}
}
private class DropLookupFoundJob extends JobImpl {
private Hash _peer;
private RouterInfo _info;
public DropLookupFoundJob(RouterContext ctx, Hash peer, RouterInfo info) {
super(ctx);
_peer = peer;
_info = info;
}
public String getName() { return "Lookup on failure of netDb peer matched"; }
public void runJob() {
RouterInfo updated = lookupRouterInfoLocally(_peer);
if ( (updated != null) && (updated.getPublished() > _info.getPublished()) ) {
// great, a legitimate update
} else {
// they just sent us what we already had. kill 'em both
dropAfterLookupFailed(_peer, _info);
}
}
}
}
/**

View File

@@ -28,6 +28,7 @@ class KBucketSet {
private I2PAppContext _context;
private Hash _us;
private KBucket _buckets[];
private volatile int _size;
public final static int BASE = 8; // must go into KEYSIZE_BITS evenly
public final static int KEYSIZE_BITS = Hash.HASH_LENGTH * 8;
@@ -51,6 +52,8 @@ class KBucketSet {
if (bucket >= 0) {
int oldSize = _buckets[bucket].getKeyCount();
int numInBucket = _buckets[bucket].add(peer);
if (numInBucket != oldSize)
_size++;
if (numInBucket > BUCKET_SIZE) {
// perhaps queue up coalesce job? naaahh.. lets let 'er grow for now
}
@@ -62,17 +65,26 @@ class KBucketSet {
}
}
/**
* Not an exact count (due to concurrency issues) but generally correct
*
*/
public int size() {
return _size;
/*
int size = 0;
for (int i = 0; i < _buckets.length; i++)
size += _buckets[i].getKeyCount();
return size;
*/
}
public boolean remove(Hash entry) {
int bucket = pickBucket(entry);
KBucket kbucket = getBucket(bucket);
boolean removed = kbucket.remove(entry);
if (removed)
_size--;
return removed;
}

View File

@@ -117,7 +117,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
* dont accept any dbDtore of a router over 24 hours old (unless we dont
* know anyone or just started up)
*/
private final static long ROUTER_INFO_EXPIRATION = 24*60*60*1000l;
private final static long ROUTER_INFO_EXPIRATION = 3*24*60*60*1000l;
public KademliaNetworkDatabaseFacade(RouterContext context) {
_context = context;
@@ -278,8 +278,14 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_context.jobQueue().addJob(new DataPublisherJob(_context, this));
// expire old leases
_context.jobQueue().addJob(new ExpireLeasesJob(_context, this));
// expire some routers in overly full kbuckets
_context.jobQueue().addJob(new ExpireRoutersJob(_context, this));
// the ExpireRoutersJob never fired since the tunnel pool manager lied
// and said all peers are in use (for no good reason), but this expire
// thing was a bit overzealous anyway, since the kbuckets are only
// relevent when the network is huuuuuuuuge.
//// expire some routers in overly full kbuckets
////_context.jobQueue().addJob(new ExpireRoutersJob(_context, this));
if (!_quiet) {
// fill the passive queue periodically
_context.jobQueue().addJob(new DataRepublishingSelectorJob(_context, this));
@@ -456,9 +462,22 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
if (!_initialized) return null;
DataStructure ds = _ds.get(key);
if (ds != null) {
if (ds instanceof RouterInfo)
if (ds instanceof RouterInfo) {
// more aggressive than perhaps is necessary, but makes sure we
// drop old references that we had accepted on startup (since
// startup allows some lax rules).
boolean valid = true;
try {
valid = (null == validate(key, (RouterInfo)ds));
} catch (IllegalArgumentException iae) {
valid = false;
}
if (!valid) {
fail(key);
return null;
}
return (RouterInfo)ds;
else {
} else {
//_log.debug("Looking for a router [" + key + "] but it ISN'T a RouterInfo! " + ds, new Exception("Who thought that lease was a router?"));
return null;
}
@@ -643,7 +662,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo);
return "Invalid routerInfo signature on " + key.toBase64();
} else if (!routerInfo.isCurrent(ROUTER_INFO_EXPIRATION)) {
} else if (!routerInfo.isCurrent(ROUTER_INFO_EXPIRATION) && (_context.router().getUptime() > 60*60*1000) ) {
if (routerInfo.getNetworkId() != Router.NETWORK_ID) {
_context.shitlist().shitlistRouter(key, "Peer is not in our network");
return "Peer is not in our network (" + routerInfo.getNetworkId() + ", wants "
@@ -661,7 +680,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
+ " peers left (curPeer: " + key.toBase64() + " published on "
+ new Date(routerInfo.getPublished()));
}
} else if (routerInfo.getPublished() > now + Router.CLOCK_FUDGE_FACTOR) {
} else if (routerInfo.getPublished() > now + 2*Router.CLOCK_FUDGE_FACTOR) {
long age = routerInfo.getPublished() - _context.clock().now();
if (_log.shouldLog(Log.INFO))
_log.info("Peer " + key.toBase64() + " published their routerInfo in the future?! ["
@@ -671,6 +690,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
String rv = "Peer " + key.toBase64() + " is from another network, not accepting it (id="
+ routerInfo.getNetworkId() + ", want " + Router.NETWORK_ID + ")";
return rv;
} else if ( (_context.router().getUptime() > 60*60*1000) && (routerInfo.getPublished() < now - 2*24*60*60*1000l) ) {
long age = _context.clock().now() - routerInfo.getPublished();
return "Peer " + key.toBase64() + " published " + DataHelper.formatDuration(age) + " ago";
}
return null;
}
@@ -736,12 +758,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
}
}
_context.peerManager().removeCapabilities(dbEntry);
boolean removed = _kb.remove(dbEntry);
if (removed) {
if (_log.shouldLog(Log.INFO))
_log.info("Removed kbucket entry for " + dbEntry);
}
lookupBeforeDropping(dbEntry, (RouterInfo)o);
return;
} else {
// we always drop leaseSets that are failed [timed out],
// regardless of how many routers we have. this is called on a lease if
@@ -769,6 +787,30 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
}
}
protected void lookupBeforeDropping(Hash peer, RouterInfo info) {
//bah, humbug.
dropAfterLookupFailed(peer, info);
}
protected void dropAfterLookupFailed(Hash peer, RouterInfo info) {
_context.peerManager().removeCapabilities(peer);
boolean removed = _kb.remove(peer);
if (removed) {
if (_log.shouldLog(Log.INFO))
_log.info("Removed kbucket entry for " + peer);
}
_ds.remove(peer);
synchronized (_lastSent) {
_lastSent.remove(peer);
}
synchronized (_explicitSendKeys) {
_explicitSendKeys.remove(peer);
}
synchronized (_passiveSendKeys) {
_passiveSendKeys.remove(peer);
}
}
public void unpublish(LeaseSet localLeaseSet) {
if (!_initialized) return;
Hash h = localLeaseSet.getDestination().calculateHash();
@@ -929,8 +971,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
renderRouterInfo(buf, ri, false);
out.write(buf.toString());
buf.setLength(0);
String coreVersion = ri.getOptions().getProperty("coreVersion");
String routerVersion = ri.getOptions().getProperty("router.version");
String coreVersion = ri.getOption("coreVersion");
String routerVersion = ri.getOption("router.version");
if ( (coreVersion != null) && (routerVersion != null) ) {
Map routerVersions = (Map)versions.get(coreVersion);
if (routerVersions == null) {
@@ -995,7 +1037,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
buf.append("Stats: <br /><i><code>\n");
for (Iterator iter = info.getOptions().keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = info.getOptions().getProperty(key);
String val = info.getOption(key);
buf.append(DataHelper.stripHTML(key)).append(" = ").append(DataHelper.stripHTML(val)).append("<br />\n");
}
buf.append("</code></i><hr />\n");

View File

@@ -159,7 +159,8 @@ class PersistentDataStore extends TransientDataStore {
}
private void write(Hash key, DataStructure data) {
_log.info("Writing key " + key);
if (_log.shouldLog(Log.INFO))
_log.info("Writing key " + key);
FileOutputStream fos = null;
File dbFile = null;
try {
@@ -349,9 +350,10 @@ class PersistentDataStore extends TransientDataStore {
ri.readBytes(fis);
if (ri.getNetworkId() != Router.NETWORK_ID) {
corrupt = true;
if (_log.shouldLog(Log.WARN))
_log.warn("The router is from a different network: "
+ ri.getIdentity().calculateHash().toBase64());
if (_log.shouldLog(Log.ERROR))
_log.error("The router "
+ ri.getIdentity().calculateHash().toBase64()
+ " is from a different network");
} else {
try {
_facade.store(ri.getIdentity().getHash(), ri);
@@ -361,14 +363,16 @@ class PersistentDataStore extends TransientDataStore {
}
}
} catch (DataFormatException dfe) {
_log.warn("Error reading the routerInfo from " + _routerFile.getAbsolutePath(), dfe);
if (_log.shouldLog(Log.INFO))
_log.info("Error reading the routerInfo from " + _routerFile.getName(), dfe);
corrupt = true;
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
if (corrupt) _routerFile.delete();
} catch (IOException ioe) {
_log.warn("Error reading the RouterInfo from " + _routerFile.getAbsolutePath(), ioe);
if (_log.shouldLog(Log.INFO))
_log.info("Unable to read the router reference in " + _routerFile.getName(), ioe);
}
}
}

View File

@@ -841,15 +841,17 @@ class SearchReplyJob extends JobImpl {
if (!sendsBadInfo) {
// we don't need to search for everthing we're given here - only ones that
// are next in our search path...
if (getContext().shitlist().isShitlisted(peer)) {
if (_log.shouldLog(Log.INFO))
_log.info("Not looking for a shitlisted peer...");
getContext().statManager().addRateData("netDb.searchReplyValidationSkipped", 1, 0);
} else {
// note: no need to think about shitlisted targets in the netdb search, given
// the floodfill's behavior
//if (getContext().shitlist().isShitlisted(peer)) {
// if (_log.shouldLog(Log.INFO))
// _log.info("Not looking for a shitlisted peer...");
// getContext().statManager().addRateData("netDb.searchReplyValidationSkipped", 1, 0);
//} else {
//getContext().netDb().lookupRouterInfo(peer, new ReplyVerifiedJob(getContext(), peer), new ReplyNotVerifiedJob(getContext(), peer), _timeoutMs);
//_repliesPendingVerification++;
shouldAdd = true;
}
//}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Peer " + _peer.toBase64() + " sends us bad replies, so not verifying " + peer.toBase64());

View File

@@ -21,6 +21,7 @@ import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.router.NetworkDatabaseFacade;
import net.i2p.router.tunnel.pool.TunnelPeerSelector;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.util.Log;
@@ -361,6 +362,8 @@ public class ProfileOrganizer {
Hash cur = (Hash)_notFailingPeersList.get(curIndex);
if (matches.contains(cur) ||
(exclude != null && exclude.contains(cur))) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("matched? " + matches.contains(cur) + " exclude: " + exclude + " cur=" + cur.toBase64());
continue;
} else if (onlyNotFailing && _highCapacityPeers.containsKey(cur)) {
// we dont want the good peers, just random ones
@@ -368,12 +371,14 @@ public class ProfileOrganizer {
} else {
if (isSelectable(cur))
selected.add(cur);
else if (_log.shouldLog(Log.DEBUG))
_log.debug("Not selectable: " + cur.toBase64());
}
}
}
if (_log.shouldLog(Log.INFO))
_log.info("Selecting all not failing (strict? " + onlyNotFailing + " start=" + start
+ ") found " + selected.size() + " new peers: " + selected);
+ ") found " + selected.size() + " new peers: " + selected + " all=" + _notFailingPeersList.size() + " strict=" + _strictCapacityOrder.size());
matches.addAll(selected);
}
if (matches.size() < howMany) {
@@ -809,11 +814,16 @@ public class ProfileOrganizer {
_log.warn("Peer " + peer.toBase64() + " is marked as hidden, disallowing its use");
return false;
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Peer " + peer.toBase64() + " is locally known, allowing its use");
// perhaps check to see if they are active?
return true;
boolean exclude = TunnelPeerSelector.shouldExclude(_context, info);
if (exclude) {
if (_log.shouldLog(Log.WARN))
_log.warn("Peer " + peer.toBase64() + " has capabilities or other stats suggesting we avoid it");
return false;
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Peer " + peer.toBase64() + " is locally known, allowing its use");
return true;
}
}
} else {
if (_log.shouldLog(Log.WARN))

View File

@@ -16,11 +16,15 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import java.util.Collections;
import net.i2p.data.RouterAddress;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.ntcp.NTCPAddress;
import net.i2p.router.transport.ntcp.NTCPTransport;
import net.i2p.router.transport.tcp.TCPTransport;
import net.i2p.util.Log;
@@ -56,6 +60,48 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
public int countActivePeers() { return (_manager == null ? 0 : _manager.countActivePeers()); }
public int countActiveSendPeers() { return (_manager == null ? 0 : _manager.countActiveSendPeers()); }
/**
* Framed average clock skew of connected peers in seconds, or null if we cannot answer.
* Average is calculated over the middle "percentToInclude" peers.
*/
public Long getFramedAveragePeerClockSkew(int percentToInclude) {
if (_manager == null) {
if (_log.shouldLog(Log.INFO))
_log.info("Returning null for framed averege peer clock skew (no transport manager)!");
return null;
}
Vector skews = _manager.getClockSkews();
if (skews == null) {
if (_log.shouldLog(Log.ERROR))
_log.error("Returning null for framed average peer clock skew (no data)!");
return null;
}
if (skews.size() < 20) {
if (_log.shouldLog(Log.WARN))
_log.warn("Returning null for framed average peer clock skew (only " + skews.size() + " peers)!");
return null;
}
// Going to calculate, sort them
Collections.sort(skews);
// Calculate frame size
int frameSize = (skews.size() * percentToInclude / 100);
int first = (skews.size() / 2) - (frameSize / 2);
int last = (skews.size() / 2) + (frameSize / 2);
// Sum skew values
long sum = 0;
for (int i = first; i < last; i++) {
long value = ((Long) (skews.get(i))).longValue();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Adding clock skew " + i + " valued " + value + " s.");
sum = sum + value;
}
// Calculate average
Long framedAverageClockSkew = new Long(sum / frameSize);
if (_log.shouldLog(Log.INFO))
_log.info("Our framed average peer clock skew is " + framedAverageClockSkew + " s.");
return framedAverageClockSkew;
}
public List getBids(OutNetMessage msg) {
return _manager.getBids(msg);
}
@@ -65,6 +111,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
public TransportBid getNextBid(OutNetMessage msg) {
return _manager.getNextBid(msg);
}
int getTransportCount() { return _manager.getTransportCount(); }
public void processMessage(OutNetMessage msg) {
//GetBidsJob j = new GetBidsJob(_context, this, msg);
@@ -89,20 +136,30 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
public Set createAddresses() {
Map addresses = null;
boolean newCreated = false;
if (_manager != null)
if (_manager != null) {
addresses = _manager.getAddresses();
else
} else {
addresses = new HashMap(1);
newCreated = true;
}
if (!addresses.containsKey(TCPTransport.STYLE)) {
RouterAddress addr = createTCPAddress();
if (addr != null)
addresses.put(TCPTransport.STYLE, addr);
}
if (!addresses.containsKey(NTCPTransport.STYLE)) {
RouterAddress addr = createNTCPAddress(_context);
if (_log.shouldLog(Log.INFO))
_log.info("NTCP address: " + addr);
if (addr != null)
addresses.put(NTCPTransport.STYLE, addr);
}
if (_log.shouldLog(Log.INFO))
_log.info("Creating addresses: " + addresses);
_log.info("Creating addresses: " + addresses + " isNew? " + newCreated, new Exception("creator"));
return new HashSet(addresses.values());
}
@@ -134,4 +191,48 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
addr.setTransportStyle(TCPTransport.STYLE);
return addr;
}
public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
public static RouterAddress createNTCPAddress(RouterContext ctx) {
if (!TransportManager.enableNTCP(ctx)) return null;
RouterAddress addr = new RouterAddress();
addr.setCost(10);
addr.setExpiration(null);
Properties props = new Properties();
String name = ctx.router().getConfigSetting(PROP_I2NP_NTCP_HOSTNAME);
String port = ctx.router().getConfigSetting(PROP_I2NP_NTCP_PORT);
/*
boolean isNew = false;
if (name == null) {
name = "localhost";
isNew = true;
}
if (port == null) {
port = String.valueOf(ctx.random().nextInt(10240)+1024);
isNew = true;
}
*/
if ( (name == null) || (port == null) || (name.trim().length() <= 0) || ("null".equals(name)) )
return null;
try {
int p = Integer.parseInt(port);
if ( (p <= 0) || (p > 64*1024) )
return null;
} catch (NumberFormatException nfe) {
return null;
}
props.setProperty(NTCPAddress.PROP_HOST, name);
props.setProperty(NTCPAddress.PROP_PORT, port);
addr.setOptions(props);
addr.setTransportStyle(NTCPTransport.STYLE);
//if (isNew) {
if (false) return null;
ctx.router().setConfigSetting(PROP_I2NP_NTCP_HOSTNAME, name);
ctx.router().setConfigSetting(PROP_I2NP_NTCP_PORT, port);
ctx.router().saveConfig();
//}
return addr;
}
}

View File

@@ -129,13 +129,14 @@ public class FIFOBandwidthLimiter {
* Request some bytes, blocking until they become available
*
*/
public Request requestInbound(int bytesIn, String purpose) {
public Request requestInbound(int bytesIn, String purpose) { return requestInbound(bytesIn, purpose, null, null); }
public Request requestInbound(int bytesIn, String purpose, CompleteListener lsnr, Object attachment) {
if (_inboundUnlimited) {
_totalAllocatedInboundBytes += bytesIn;
return _noop;
}
SimpleRequest req = new SimpleRequest(bytesIn, 0, purpose);
SimpleRequest req = new SimpleRequest(bytesIn, 0, purpose, lsnr, attachment);
requestInbound(req, bytesIn, purpose);
return req;
}
@@ -156,13 +157,14 @@ public class FIFOBandwidthLimiter {
* Request some bytes, blocking until they become available
*
*/
public Request requestOutbound(int bytesOut, String purpose) {
public Request requestOutbound(int bytesOut, String purpose) { return requestOutbound(bytesOut, purpose, null, null); }
public Request requestOutbound(int bytesOut, String purpose, CompleteListener lsnr, Object attachment) {
if (_outboundUnlimited) {
_totalAllocatedOutboundBytes += bytesOut;
return _noop;
}
SimpleRequest req = new SimpleRequest(0, bytesOut, purpose);
SimpleRequest req = new SimpleRequest(0, bytesOut, purpose, lsnr, attachment);
requestOutbound(req, bytesOut, purpose);
return req;
}
@@ -296,8 +298,8 @@ public class FIFOBandwidthLimiter {
_recvBps = (0.9f)*_recvBps + (0.1f)*((float)recv*1000)/(float)time;
if (_log.shouldLog(Log.WARN)) {
if (_log.shouldLog(Log.INFO))
_log.info("BW: time = " + time + " sent: " + sent + " recv: " + recv);
//if (_log.shouldLog(Log.INFO))
// _log.info("BW: time = " + time + " sent: " + _sendBps + " recv: " + _recvBps);
_context.statManager().getStatLog().addData("bw", "bw.sendBps1s", (long)_sendBps, sent);
_context.statManager().getStatLog().addData("bw", "bw.recvBps1s", (long)_recvBps, recv);
}
@@ -305,19 +307,19 @@ public class FIFOBandwidthLimiter {
// Maintain an approximate average with a 15-second halflife
// Weights (0.955 and 0.045) are tuned so that transition between two values (e.g. 0..10)
// would reach their midpoint (e.g. 5) in 15s
if (_sendBps15s <= 0)
_sendBps15s = ((float)sent*15*1000f)/(float)time;
else
//if (_sendBps15s <= 0)
// _sendBps15s = (0.045f)*((float)sent*15*1000f)/(float)time;
//else
_sendBps15s = (0.955f)*_sendBps15s + (0.045f)*((float)sent*1000f)/(float)time;
if (_recvBps15s <= 0)
_recvBps15s = ((float)recv*15*1000f)/(float)time;
else
//if (_recvBps15s <= 0)
// _recvBps15s = (0.045f)*((float)recv*15*1000f)/(float)time;
//else
_recvBps15s = (0.955f)*_recvBps15s + (0.045f)*((float)recv*1000)/(float)time;
if (_log.shouldLog(Log.WARN)) {
if (_log.shouldLog(Log.INFO))
_log.info("BW15: time = " + time + " sent: " + sent + " recv: " + recv);
if (_log.shouldLog(Log.DEBUG))
_log.debug("BW15: time = " + time + " sent: " + _sendBps + " recv: " + _recvBps);
_context.statManager().getStatLog().addData("bw", "bw.sendBps15s", (long)_sendBps15s, sent);
_context.statManager().getStatLog().addData("bw", "bw.recvBps15s", (long)_recvBps15s, recv);
}
@@ -432,10 +434,12 @@ public class FIFOBandwidthLimiter {
i--;
continue;
}
if (req.getAllocationsSinceWait() > 0) {
if ( (req.getAllocationsSinceWait() > 0) && (req.getCompleteListener() == null) ) {
// we have already allocated some values to this request, but
// they haven't taken advantage of it yet (most likely they're
// IO bound)
// (also, the complete listener is only set for situations where
// waitForNextAllocation() is never called)
continue;
}
// ok, they are really waiting for us to give them stuff
@@ -482,8 +486,8 @@ public class FIFOBandwidthLimiter {
locked_satisfyOutboundAvailable(satisfied);
} else {
// no bandwidth available
if (_log.shouldLog(Log.DEBUG))
_log.debug("Still denying the " + _pendingOutboundRequests.size()
if (_log.shouldLog(Log.INFO))
_log.info("Still denying the " + _pendingOutboundRequests.size()
+ " pending outbound requests (status: " + getStatus().toString()
+ ", longest waited " + locked_getLongestOutboundWait() + " out)");
}
@@ -549,6 +553,8 @@ public class FIFOBandwidthLimiter {
// we have already allocated some values to this request, but
// they haven't taken advantage of it yet (most likely they're
// IO bound)
if (_log.shouldLog(Log.WARN))
_log.warn("multiple allocations since wait... ntcp shouldn't do this: " + req.getRequestName());
continue;
}
// ok, they are really waiting for us to give them stuff
@@ -563,6 +569,15 @@ public class FIFOBandwidthLimiter {
req.allocateBytes(0, allocated);
satisfied.add(req);
if (req.getPendingOutboundRequested() > 0) {
if (req.attachment() != null) {
if (_log.shouldLog(Log.INFO))
_log.info("Allocating " + allocated + " bytes outbound as a partial grant to "
+ req.getRequestName() + " (wanted "
+ req.getTotalOutboundRequested() + " bytes, waited "
+ waited
+ "ms) pending " + _pendingOutboundRequests.size()
+ ", longest waited " + locked_getLongestOutboundWait() + " out");
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Allocating " + allocated + " bytes outbound as a partial grant to "
+ req.getRequestName() + " (wanted "
@@ -571,6 +586,15 @@ public class FIFOBandwidthLimiter {
+ "ms) pending " + _pendingOutboundRequests.size()
+ ", longest waited " + locked_getLongestOutboundWait() + " out");
} else {
if (req.attachment() != null) {
if (_log.shouldLog(Log.INFO))
_log.info("Allocating " + allocated + " bytes outbound to finish the partial grant to "
+ req.getRequestName() + " (total "
+ req.getTotalOutboundRequested() + " bytes, waited "
+ waited
+ "ms) pending " + _pendingOutboundRequests.size()
+ ", longest waited " + locked_getLongestOutboundWait() + " out)");
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Allocating " + allocated + " bytes outbound to finish the partial grant to "
+ req.getRequestName() + " (total "
@@ -629,7 +653,10 @@ public class FIFOBandwidthLimiter {
private String _target;
private int _allocationsSinceWait;
private boolean _aborted;
private boolean _waited;
List satisfiedBuffer;
private CompleteListener _lsnr;
private Object _attachment;
public SimpleRequest() {
satisfiedBuffer = new ArrayList(1);
@@ -639,7 +666,14 @@ public class FIFOBandwidthLimiter {
satisfiedBuffer = new ArrayList(1);
init(in, out, target);
}
public SimpleRequest(int in, int out, String target, CompleteListener lsnr, Object attachment) {
satisfiedBuffer = new ArrayList(1);
_lsnr = lsnr;
_attachment = attachment;
init(in, out, target);
}
public void init(int in, int out, String target) {
_waited = false;
_inTotal = in;
_outTotal = out;
_inAllocated = 0;
@@ -659,36 +693,77 @@ public class FIFOBandwidthLimiter {
public int getPendingInboundRequested() { return _inTotal - _inAllocated; }
public boolean getAborted() { return _aborted; }
public void abort() { _aborted = true; }
public CompleteListener getCompleteListener() { return _lsnr; }
public void setCompleteListener(CompleteListener lsnr) {
boolean complete = false;
synchronized (SimpleRequest.this) {
_lsnr = lsnr;
if (isComplete()) {
complete = true;
}
}
if (complete && lsnr != null) {
if (_log.shouldLog(Log.INFO))
_log.info("complete listener set AND completed: " + lsnr);
lsnr.complete(SimpleRequest.this);
}
}
private boolean isComplete() { return (_outAllocated >= _outTotal) && (_inAllocated >= _inTotal); }
public void waitForNextAllocation() {
_waited = true;
_allocationsSinceWait = 0;
if ( (_outAllocated >= _outTotal) &&
(_inAllocated >= _inTotal) )
return;
boolean complete = false;
try {
synchronized (SimpleRequest.this) {
SimpleRequest.this.wait();
if (isComplete())
complete = true;
else
SimpleRequest.this.wait();
}
} catch (InterruptedException ie) {}
if (complete && _lsnr != null)
_lsnr.complete(SimpleRequest.this);
}
int getAllocationsSinceWait() { return _allocationsSinceWait; }
int getAllocationsSinceWait() { return _waited ? _allocationsSinceWait : 0; }
void allocateAll() {
_inAllocated = _inTotal;
_outAllocated = _outTotal;
_outAllocated = _outTotal;
_allocationsSinceWait++;
if (_lsnr == null)
_allocationsSinceWait++;
if (_log.shouldLog(Log.DEBUG)) _log.debug("allocate all");
notifyAllocation();
}
void allocateBytes(int in, int out) {
_inAllocated += in;
_outAllocated += out;
_allocationsSinceWait++;
if (_lsnr == null)
_allocationsSinceWait++;
if (isComplete()) {
if (_log.shouldLog(Log.INFO))
_log.info("allocate " + in +"/"+ out + " completed, listener=" + _lsnr);
}
//notifyAllocation(); // handled within the satisfy* methods
}
void notifyAllocation() {
boolean complete = false;
synchronized (SimpleRequest.this) {
if (isComplete())
complete = true;
SimpleRequest.this.notifyAll();
}
if (complete && _lsnr != null) {
_lsnr.complete(SimpleRequest.this);
if (_log.shouldLog(Log.INFO))
_log.info("at completion for " + _inTotal + "/" + _outTotal
+ ", recvBps=" + _recvBps + "/"+ _recvBps15s + " listener is " + _lsnr);
}
}
public void attach(Object obj) { _attachment = obj; }
public Object attachment() { return _attachment; }
public String toString() { return getRequestName(); }
}
public interface Request {
@@ -712,10 +787,20 @@ public class FIFOBandwidthLimiter {
public boolean getAborted();
/** thar be dragons */
public void init(int in, int out, String target);
public void setCompleteListener(CompleteListener lsnr);
public void attach(Object obj);
public Object attachment();
public CompleteListener getCompleteListener();
}
public interface CompleteListener {
public void complete(Request req);
}
private static final NoopRequest _noop = new NoopRequest();
private static class NoopRequest implements Request {
private CompleteListener _lsnr;
private Object _attachment;
public void abort() {}
public boolean getAborted() { return false; }
public int getPendingInboundRequested() { return 0; }
@@ -726,5 +811,12 @@ public class FIFOBandwidthLimiter {
public int getTotalOutboundRequested() { return 0; }
public void waitForNextAllocation() {}
public void init(int in, int out, String target) {}
public CompleteListener getCompleteListener() { return _lsnr; }
public void setCompleteListener(CompleteListener lsnr) {
_lsnr = lsnr;
lsnr.complete(NoopRequest.this);
}
public void attach(Object obj) { _attachment = obj; }
public Object attachment() { return _attachment; }
}
}

View File

@@ -48,6 +48,7 @@ public class GetBidsJob extends JobImpl {
if (log.shouldLog(Log.WARN))
log.warn("Attempt to send a message to a shitlisted peer - " + to);
//context.messageRegistry().peerFailed(to);
context.statManager().addRateData("transport.bidFailShitlisted", msg.getLifetime(), 0);
fail(context, msg);
return;
}
@@ -56,14 +57,20 @@ public class GetBidsJob extends JobImpl {
if (to.equals(us)) {
if (log.shouldLog(Log.ERROR))
log.error("wtf, send a message to ourselves? nuh uh. msg = " + msg);
context.statManager().addRateData("transport.bidFailSelf", msg.getLifetime(), 0);
fail(context, msg);
return;
}
TransportBid bid = facade.getNextBid(msg);
if (bid == null) {
if (msg.getFailedTransports().size() == 0) {
int failedCount = msg.getFailedTransports().size();
if (failedCount == 0) {
context.statManager().addRateData("transport.bidFailNoTransports", msg.getLifetime(), 0);
context.shitlist().shitlistRouter(to, "We share no common transports with them");
} else if (failedCount >= facade.getTransportCount()) {
context.statManager().addRateData("transport.bidFailAllTransports", msg.getLifetime(), 0);
// fail after all transports were unsuccessful
context.netDb().fail(to);
}
fail(context, msg);

View File

@@ -171,6 +171,7 @@ public class OutboundMessageRegistry {
}
public void unregisterPending(OutNetMessage msg) {
if (msg == null) return;
MessageSelector sel = msg.getReplySelector();
boolean stillActive = false;
synchronized (_selectorToMessage) {

View File

@@ -12,6 +12,8 @@ import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
@@ -39,9 +41,12 @@ public interface Transport {
public int countActivePeers();
public int countActiveSendPeers();
public Vector getClockSkews();
public List getMostRecentErrorMessages();
public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException;
public short getReachabilityStatus();
public void recheckReachability();
public boolean isUnreachable(Hash peer);
}

View File

@@ -10,13 +10,7 @@ package net.i2p.router.transport;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.*;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
@@ -24,6 +18,7 @@ import net.i2p.data.RouterIdentity;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.Job;
import net.i2p.router.JobImpl;
import net.i2p.router.MessageSelector;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
@@ -39,6 +34,8 @@ public abstract class TransportImpl implements Transport {
private RouterAddress _currentAddress;
private List _sendPool;
protected RouterContext _context;
/** map from routerIdentHash to timestamp (Long) that the peer was last unreachable */
private Map _unreachableEntries;
/**
* Initialize the new transport
@@ -56,6 +53,7 @@ public abstract class TransportImpl implements Transport {
_context.statManager().createRateStat("transport.sendProcessingTime", "How long does it take from noticing that we want to send the message to having it completely sent (successfully or failed)?", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("transport.expiredOnQueueLifetime", "How long a message that expires on our outbound queue is processed", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l } );
_sendPool = new ArrayList(16);
_unreachableEntries = new HashMap(16);
_currentAddress = null;
}
@@ -69,6 +67,13 @@ public abstract class TransportImpl implements Transport {
*/
public int countActiveSendPeers() { return 0; }
/**
* Return our peer clock skews on a transport.
* Vector composed of Long, each element representing a peer skew in seconds.
* Dummy version. Transports override it.
*/
public Vector getClockSkews() { return new Vector(); }
public List getMostRecentErrorMessages() { return Collections.EMPTY_LIST; }
/**
* Nonblocking call to pull the next outbound message
@@ -138,8 +143,11 @@ public abstract class TransportImpl implements Transport {
if (_log.shouldLog(Log.WARN))
_log.warn("afterSend slow: [success=" + sendSuccessful + "] " + msg.getMessageSize() + "byte "
+ msg.getMessageType() + " " + msg.getMessageId() + " from "
+ _context.routerHash().toBase64().substring(0,6) + " took " + msToSend);
+ _context.routerHash().toBase64().substring(0,6) + " took " + msToSend
+ "/" + msg.getTransmissionTime());
}
//if (true)
// _log.error("(not error) I2NP message sent? " + sendSuccessful + " " + msg.getMessageId() + " after " + msToSend + "/" + msg.getTransmissionTime());
long lifetime = msg.getLifetime();
if (lifetime > 3000) {
@@ -147,7 +155,7 @@ public abstract class TransportImpl implements Transport {
if (!sendSuccessful)
level = Log.INFO;
if (_log.shouldLog(level))
_log.log(level, "afterSend slow (" + lifetime + "): [success=" + sendSuccessful + "]" + msg.getMessageSize() + "byte "
_log.log(level, "afterSend slow (" + lifetime + "/" + msToSend + "/" + msg.getTransmissionTime() + "): [success=" + sendSuccessful + "]" + msg.getMessageSize() + "byte "
+ msg.getMessageType() + " " + msg.getMessageId() + " from " + _context.routerHash().toBase64().substring(0,6)
+ " to " + msg.getTarget().getIdentity().calculateHash().toBase64().substring(0,6) + ": " + msg.toString());
} else {
@@ -229,7 +237,7 @@ public abstract class TransportImpl implements Transport {
if (allTime > 5*1000) {
if (_log.shouldLog(Log.INFO))
_log.info("Took too long from preperation to afterSend(ok? " + sendSuccessful
+ "): " + allTime + "ms " + " after failing on: "
+ "): " + allTime + "ms/" + sendTime + "ms after failing on: "
+ msg.getFailedTransports() + " and succeeding on " + getStyle());
if ( (allTime > 60*1000) && (sendSuccessful) ) {
// WTF!!@#
@@ -299,6 +307,9 @@ public abstract class TransportImpl implements Transport {
*
*/
public void messageReceived(I2NPMessage inMsg, RouterIdentity remoteIdent, Hash remoteIdentHash, long msToReceive, int bytesReceived) {
//if (true)
// _log.error("(not error) I2NP message received: " + inMsg.getUniqueId() + " after " + msToReceive);
int level = Log.INFO;
if (msToReceive > 5000)
level = Log.WARN;
@@ -368,13 +379,68 @@ public abstract class TransportImpl implements Transport {
public RouterContext getContext() { return _context; }
public short getReachabilityStatus() { return CommSystemFacade.STATUS_UNKNOWN; }
public void recheckReachability() {}
private static final long UNREACHABLE_PERIOD = 5*60*1000;
public boolean isUnreachable(Hash peer) {
long now = _context.clock().now();
synchronized (_unreachableEntries) {
Long when = (Long)_unreachableEntries.get(peer);
if (when == null) return false;
if (when.longValue() + UNREACHABLE_PERIOD < now) {
_unreachableEntries.remove(peer);
return false;
} else {
return true;
}
}
}
/** called when we can't reach a peer */
public void markUnreachable(Hash peer) {
long now = _context.clock().now();
synchronized (_unreachableEntries) {
_unreachableEntries.put(peer, new Long(now));
}
}
/** called when we establish a peer connection (outbound or inbound) */
public void markReachable(Hash peer) {
// if *some* transport can reach them, then we shouldn't shitlist 'em
_context.shitlist().unshitlistRouter(peer);
synchronized (_unreachableEntries) {
_unreachableEntries.remove(peer);
}
}
private class CleanupUnreachable extends JobImpl {
public CleanupUnreachable(RouterContext ctx) {
super(ctx);
}
public String getName() { return "Cleanup " + getStyle() + " unreachable list"; }
public void runJob() {
long now = getContext().clock().now();
synchronized (_unreachableEntries) {
for (Iterator iter = _unreachableEntries.keySet().iterator(); iter.hasNext(); ) {
Hash peer = (Hash)iter.next();
Long when = (Long)_unreachableEntries.get(peer);
if (when.longValue() + UNREACHABLE_PERIOD < now)
iter.remove();
}
}
requeue(60*1000);
}
}
public static boolean isPubliclyRoutable(byte addr[]) {
if ((addr[0]&0xFF) == 127) return false;
if ((addr[0]&0xFF) == 10) return false;
if ( ((addr[0]&0xFF) == 172) && ((addr[1]&0xFF) >= 16) && ((addr[1]&0xFF) <= 31) ) return false;
if ( ((addr[0]&0xFF) == 192) && ((addr[1]&0xFF) == 168) ) return false;
if ((addr[0]&0xFF) >= 224) return false; // no multicast
return true; // or at least possible to be true
if (addr.length == 4) {
if ((addr[0]&0xFF) == 127) return false;
if ((addr[0]&0xFF) == 10) return false;
if ( ((addr[0]&0xFF) == 172) && ((addr[1]&0xFF) >= 16) && ((addr[1]&0xFF) <= 31) ) return false;
if ( ((addr[0]&0xFF) == 192) && ((addr[1]&0xFF) == 168) ) return false;
if ((addr[0]&0xFF) >= 224) return false; // no multicast
return true; // or at least possible to be true
} else if (addr.length == 16) {
return false;
} else {
// ipv?
return false;
}
}
}

View File

@@ -18,6 +18,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterIdentity;
@@ -27,6 +28,7 @@ import net.i2p.router.CommSystemFacade;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.tcp.TCPTransport;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.router.transport.ntcp.NTCPTransport;
import net.i2p.util.Log;
public class TransportManager implements TransportEventListener {
@@ -36,11 +38,19 @@ public class TransportManager implements TransportEventListener {
private final static String PROP_DISABLE_TCP = "i2np.tcp.disable";
private final static String PROP_ENABLE_UDP = "i2np.udp.enable";
private static final String DEFAULT_ENABLE_UDP = "true";
private final static String PROP_ENABLE_NTCP = "i2np.ntcp.enable";
private final static String DEFAULT_ENABLE_NTCP = "true";
private final static String DEFAULT_ENABLE_UDP = "true";
public TransportManager(RouterContext context) {
_context = context;
_log = _context.logManager().getLog(TransportManager.class);
_context.statManager().createRateStat("transport.shitlistOnUnreachable", "Add a peer to the shitlist since none of the transports can reach them", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("transport.noBidsYetNotAllUnreachable", "Add a peer to the shitlist since none of the transports can reach them", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("transport.bidFailShitlisted", "Could not attempt to bid on message, as they were shitlisted", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("transport.bidFailSelf", "Could not attempt to bid on message, as it targeted ourselves", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("transport.bidFailNoTransports", "Could not attempt to bid on message, as none of the transports could attempt it", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("transport.bidFailAllTransports", "Could not attempt to bid on message, as all of the transports had failed", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_transports = new ArrayList();
}
@@ -76,6 +86,17 @@ public class TransportManager implements TransportEventListener {
udp.setListener(this);
_transports.add(udp);
}
enableNTCP(_context);
NTCPTransport ntcp = new NTCPTransport(_context);
ntcp.setListener(this);
_transports.add(ntcp);
}
static boolean enableNTCP(RouterContext ctx) {
String enableNTCP = ctx.router().getConfigSetting(PROP_ENABLE_NTCP);
if (enableNTCP == null)
enableNTCP = DEFAULT_ENABLE_NTCP;
return "true".equalsIgnoreCase(enableNTCP);
}
public void startListening() {
@@ -92,7 +113,7 @@ public class TransportManager implements TransportEventListener {
public void restart() {
stopListening();
try { Thread.sleep(1*1000); } catch (InterruptedException ie) {}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
startListening();
}
@@ -103,6 +124,8 @@ public class TransportManager implements TransportEventListener {
_transports.clear();
}
int getTransportCount() { return _transports.size(); }
private boolean isSupported(Set addresses, Transport t) {
for (Iterator iter = addresses.iterator(); iter.hasNext(); ) {
RouterAddress addr = (RouterAddress)iter.next();
@@ -128,6 +151,23 @@ public class TransportManager implements TransportEventListener {
return peers;
}
/**
* Return our peer clock skews on all transports.
* Vector composed of Long, each element representing a peer skew in seconds.
* Note: this method returns them in whimsical order.
*/
public Vector getClockSkews() {
Vector skews = new Vector();
for (int i = 0; i < _transports.size(); i++) {
Vector tempSkews = ((Transport)_transports.get(i)).getClockSkews();
if ((tempSkews == null) || (tempSkews.size() <= 0)) continue;
skews.addAll(tempSkews);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Transport manager returning " + skews.size() + " peer clock skews.");
return skews;
}
public short getReachabilityStatus() {
if (_transports.size() <= 0) return CommSystemFacade.STATUS_UNKNOWN;
short status[] = new short[_transports.size()];
@@ -195,10 +235,16 @@ public class TransportManager implements TransportEventListener {
}
public TransportBid getNextBid(OutNetMessage msg) {
int unreachableTransports = 0;
Hash peer = msg.getTarget().getIdentity().calculateHash();
Set failedTransports = msg.getFailedTransports();
TransportBid rv = null;
for (int i = 0; i < _transports.size(); i++) {
Transport t = (Transport)_transports.get(i);
if (t.isUnreachable(peer)) {
unreachableTransports++;
continue;
}
if (failedTransports.contains(t.getStyle())) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Skipping transport " + t.getStyle() + " as it already failed");
@@ -217,8 +263,16 @@ public class TransportManager implements TransportEventListener {
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Transport " + t.getStyle() + " did not produce a bid");
if (t.isUnreachable(peer))
unreachableTransports++;
}
}
if (unreachableTransports >= _transports.size()) {
_context.statManager().addRateData("transport.shitlistOnUnreachable", msg.getLifetime(), msg.getLifetime());
_context.shitlist().shitlistRouter(peer, "Unreachable on any transport");
} else if (rv == null) {
_context.statManager().addRateData("transport.noBidsYetNotAllUnreachable", unreachableTransports, msg.getLifetime());
}
return rv;
}
@@ -260,6 +314,8 @@ public class TransportManager implements TransportEventListener {
Transport t = (Transport)_transports.get(i);
if (t.getCurrentAddress() != null)
buf.append(t.getCurrentAddress()).append("\n\n");
else
buf.append(t.getStyle()).append(" is used for outbound connections only");
}
buf.append("</pre>\n");
out.write(buf.toString());

View File

@@ -0,0 +1,888 @@
package net.i2p.router.transport.ntcp;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.channels.SelectionKey;
import net.i2p.I2PAppContext;
import net.i2p.crypto.DHSessionKeyBuilder;
import net.i2p.data.*;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
import java.nio.ByteBuffer;
/*
* Alice contacts Bob
* =========================================================
* X+(H(X) xor Bob.identHash)----------------------------->
* <----------------------------------------Y+E(H(X+Y)+tsB, sk, Y[239:255])
* E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB+padding), sk, hX_xor_Bob.identHash[16:31])--->
* <----------------------E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev)
*
* Alternately, when Bob receives a connection, it could be a
* check connection (perhaps prompted by Bob asking for someone
* to verify his listener). check connections are formatted per
* {@link #isCheckInfo()}
*/
public class EstablishState {
private RouterContext _context;
private Log _log;
// bob receives (and alice sends)
private byte _X[];
private byte _hX_xor_bobIdentHash[];
private int _aliceIdentSize;
/** contains the decrypted aliceIndexSize + aliceIdent + tsA + padding + aliceSig */
private ByteArrayOutputStream _sz_aliceIdent_tsA_padding_aliceSig;
/** how long we expect _sz_aliceIdent_tsA_padding_aliceSig to be when its full */
private int _sz_aliceIdent_tsA_padding_aliceSigSize;
// alice receives (and bob sends)
private byte _Y[];
private transient byte _e_hXY_tsB[];
private transient long _tsB;
private transient long _tsA;
private transient byte _e_bobSig[];
/** previously received encrypted block (or the IV) */
private byte _prevEncrypted[];
/** current encrypted block we are reading */
private byte _curEncrypted[];
/**
* next index in _curEncrypted to write to (equals _curEncrypted length if the block is
* ready to decrypt)
*/
private int _curEncryptedOffset;
/** decryption buffer */
private byte _curDecrypted[];
/** bytes received so far */
private int _received;
/** bytes sent so far */
private int _sent;
private byte _extra[];
private DHSessionKeyBuilder _dh;
private NTCPTransport _transport;
private NTCPConnection _con;
private boolean _corrupt;
/** error causing the corruption */
private String _err;
/** exception causing the error */
private Exception _e;
private boolean _verified;
private boolean _confirmWritten;
private boolean _failedBySkew;
public EstablishState(RouterContext ctx, NTCPTransport transport, NTCPConnection con) {
_context = ctx;
_log = ctx.logManager().getLog(getClass());
_transport = transport;
_con = con;
_verified = false;
_corrupt = false;
_confirmWritten = false;
_dh = new DHSessionKeyBuilder();
if (_con.isInbound()) {
_X = new byte[256];
_hX_xor_bobIdentHash = new byte[Hash.HASH_LENGTH];
_sz_aliceIdent_tsA_padding_aliceSig = new ByteArrayOutputStream(512);
} else {
_X = _dh.getMyPublicValueBytes();
_Y = new byte[256];
_hX_xor_bobIdentHash = new byte[Hash.HASH_LENGTH];
byte hx[] = ctx.sha().calculateHash(_X).getData();
DataHelper.xor(hx, 0, con.getRemotePeer().calculateHash().getData(), 0, _hX_xor_bobIdentHash, 0, hx.length);
}
_prevEncrypted = new byte[16];
_curEncrypted = new byte[16];
_curEncryptedOffset = 0;
_curDecrypted = new byte[16];
_received = 0;
}
/**
* parse the contents of the buffer as part of the handshake. if the
* handshake is completed and there is more data remaining, the buffer is
* updated so that the next read will be the (still encrypted) remaining
* data (available from getExtraBytes)
*/
public void receive(ByteBuffer src) {
if (_corrupt || _verified)
throw new IllegalStateException(prefix() + "received after completion [corrupt?" + _corrupt + " verified? " + _verified + "] on " + _con);
if (!src.hasRemaining())
return; // nothing to receive
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"receive " + src);
if (_con.isInbound())
receiveInbound(src);
else
receiveOutbound(src);
}
/**
* we have written all of the data required to confirm the connection
* establishment
*/
public boolean confirmWritten() { return _confirmWritten; }
public boolean getFailedBySkew() { return _failedBySkew; }
/** we are Bob, so receive these bytes as part of an inbound connection */
private void receiveInbound(ByteBuffer src) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"Receiving inbound: prev received=" + _received + " src.remaining=" + src.remaining());
while (_received < _X.length && src.hasRemaining()) {
byte c = src.get();
_X[_received++] = c;
//if (_log.shouldLog(Log.DEBUG)) _log.debug("recv x" + (int)c + " received=" + _received);
if (_received >= _X.length) {
if (isCheckInfo(_context, _context.routerHash(), _X)) {
_context.statManager().addRateData("ntcp.inboundCheckConnection", 1, 0);
fail("Incoming connection was a check connection");
return;
}
}
}
while (_received < _X.length + _hX_xor_bobIdentHash.length && src.hasRemaining()) {
int i = _received-_X.length;
_received++;
byte c = src.get();
_hX_xor_bobIdentHash[i] = c;
//if (_log.shouldLog(Log.DEBUG)) _log.debug("recv bih" + (int)c + " received=" + _received);
}
if (_received >= _X.length + _hX_xor_bobIdentHash.length) {
if (_dh.getSessionKey() == null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"Enough data for a DH received");
// first verify that Alice knows who she is trying to talk with and that the X
// isn't corrupt
Hash hX = _context.sha().calculateHash(_X);
byte realXor[] = DataHelper.xor(hX.getData(), _context.routerHash().getData());
if (_log.shouldLog(Log.DEBUG)) {
//_log.debug(prefix()+"_X = " + Base64.encode(_X));
_log.debug(prefix()+"hx = " + Base64.encode(hX.getData()));
_log.debug(prefix()+"bih=" + Base64.encode(_context.routerHash().getData()));
_log.debug(prefix()+"xor=" + Base64.encode(realXor));
}
if (!DataHelper.eq(realXor, _hX_xor_bobIdentHash)) {
_context.statManager().addRateData("ntcp.invalidHXxorBIH", 1, 0);
fail("Invalid hX_xor");
return;
}
try {
// ok, they're actually trying to talk to us, and we got their (unauthenticated) X
_dh.setPeerPublicValue(_X);
_dh.getSessionKey(); // force the calc
System.arraycopy(realXor, 16, _prevEncrypted, 0, _prevEncrypted.length);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
// now prepare our response: Y+E(H(X+Y)+tsB+padding, sk, Y[239:255])
_Y = _dh.getMyPublicValueBytes();
byte xy[] = new byte[_X.length+_Y.length];
System.arraycopy(_X, 0, xy, 0, _X.length);
System.arraycopy(_Y, 0, xy, _X.length, _Y.length);
Hash hxy = _context.sha().calculateHash(xy);
_tsB = _context.clock().now()/1000l; // our (Bob's) timestamp in seconds
byte padding[] = new byte[12]; // the encrypted data needs an extra 12 bytes
_context.random().nextBytes(padding);
byte toEncrypt[] = new byte[hxy.getData().length+4+padding.length];
System.arraycopy(hxy.getData(), 0, toEncrypt, 0, hxy.getData().length);
byte tsB[] = DataHelper.toLong(4, _tsB);
System.arraycopy(tsB, 0, toEncrypt, hxy.getData().length, tsB.length);
//DataHelper.toLong(toEncrypt, hxy.getData().length, 4, _tsB);
System.arraycopy(padding, 0,toEncrypt, hxy.getData().length+4, padding.length);
if (_log.shouldLog(Log.DEBUG)) {
//_log.debug(prefix()+"Y="+Base64.encode(_Y));
//_log.debug(prefix()+"x+y="+Base64.encode(xy));
_log.debug(prefix()+"h(x+y)="+Base64.encode(hxy.getData()));
_log.debug(prefix()+"tsb="+Base64.encode(tsB));
_log.debug(prefix()+"unencrypted H(X+Y)+tsB+padding: " + Base64.encode(toEncrypt));
_log.debug(prefix()+"encryption iv= " + Base64.encode(_Y, _Y.length-16, 16));
_log.debug(prefix()+"encryption key= " + _dh.getSessionKey().toBase64());
}
_e_hXY_tsB = new byte[toEncrypt.length];
_context.aes().encrypt(toEncrypt, 0, _e_hXY_tsB, 0, _dh.getSessionKey(), _Y, _Y.length-16, toEncrypt.length);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"encrypted H(X+Y)+tsB+padding: " + Base64.encode(_e_hXY_tsB));
byte write[] = new byte[_Y.length + _e_hXY_tsB.length];
System.arraycopy(_Y, 0, write, 0, _Y.length);
System.arraycopy(_e_hXY_tsB, 0, write, _Y.length, _e_hXY_tsB.length);
// ok, now that is prepared, we want to actually send it, so make sure we are up for writing
_transport.getPumper().wantsWrite(_con, write);
if (!src.hasRemaining()) return;
} catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
_context.statManager().addRateData("ntcp.invalidDH", 1, 0);
fail("Invalid X", e);
return;
}
}
// ok, we are onto the encrypted area
while (src.hasRemaining() && !_corrupt) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"Encrypted bytes available (" + src.hasRemaining() + ")");
while (_curEncryptedOffset < _curEncrypted.length && src.hasRemaining()) {
_curEncrypted[_curEncryptedOffset++] = src.get();
_received++;
}
if (_curEncryptedOffset >= _curEncrypted.length) {
_context.aes().decrypt(_curEncrypted, 0, _curDecrypted, 0, _dh.getSessionKey(), _prevEncrypted, 0, _curEncrypted.length);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug(prefix()+"full block read and decrypted: " + Base64.encode(_curDecrypted));
byte swap[] = new byte[16];
_prevEncrypted = _curEncrypted;
_curEncrypted = swap;
_curEncryptedOffset = 0;
if (_aliceIdentSize <= 0) { // we are on the first decrypted block
_aliceIdentSize = (int)DataHelper.fromLong(_curDecrypted, 0, 2);
_sz_aliceIdent_tsA_padding_aliceSigSize = 2 + _aliceIdentSize + 4 + Signature.SIGNATURE_BYTES;
int rem = (_sz_aliceIdent_tsA_padding_aliceSigSize % 16);
int padding = 0;
if (rem > 0)
padding = 16-rem;
_sz_aliceIdent_tsA_padding_aliceSigSize += padding;
try {
_sz_aliceIdent_tsA_padding_aliceSig.write(_curDecrypted);
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"alice ident size decrypted as " + _aliceIdentSize + ", making the padding at " + padding + " and total size at " + _sz_aliceIdent_tsA_padding_aliceSigSize);
} else {
// subsequent block...
try {
_sz_aliceIdent_tsA_padding_aliceSig.write(_curDecrypted);
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")");
if (_sz_aliceIdent_tsA_padding_aliceSig.size() >= _sz_aliceIdent_tsA_padding_aliceSigSize) {
verifyInbound();
if (!_corrupt && _verified && src.hasRemaining())
prepareExtra(src);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"verifying size (sz=" + _sz_aliceIdent_tsA_padding_aliceSig.size()
+ " expected=" + _sz_aliceIdent_tsA_padding_aliceSigSize
+ " corrupt=" + _corrupt
+ " verified=" + _verified + " extra=" + (_extra != null ? _extra.length : 0) + ")");
return;
}
}
} else {
// no more bytes available in the buffer, and only a partial
// block was read, so we can't decrypt it.
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"end of available data with only a partial block read (" + _curEncryptedOffset + ", " + _received + ")");
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"done with the data, not yet complete or corrupt");
}
}
/** we are Alice, so receive these bytes as part of an outbound connection */
private void receiveOutbound(ByteBuffer src) {
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix()+"Receive outbound " + src + " received=" + _received);
// recv Y+E(H(X+Y)+tsB, sk, Y[239:255])
while (_received < _Y.length && src.hasRemaining()) {
byte c = src.get();
_Y[_received++] = c;
//if (_log.shouldLog(Log.DEBUG)) _log.debug("recv x" + (int)c + " received=" + _received);
if (_received >= _Y.length) {
try {
_dh.setPeerPublicValue(_Y);
_dh.getSessionKey(); // force the calc
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
_e_hXY_tsB = new byte[Hash.HASH_LENGTH+4+12];
} catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
_context.statManager().addRateData("ntcp.invalidDH", 1, 0);
fail("Invalid X", e);
return;
}
}
}
if (_e_hXY_tsB == null) return; // !src.hasRemaining
while (_received < _Y.length + _e_hXY_tsB.length && src.hasRemaining()) {
int i = _received-_Y.length;
_received++;
byte c = src.get();
_e_hXY_tsB[i] = c;
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "recv _e_hXY_tsB " + (int)c + " received=" + _received);
if (i+1 >= _e_hXY_tsB.length) {
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "received _e_hXY_tsB fully");
byte hXY_tsB[] = new byte[_e_hXY_tsB.length];
_context.aes().decrypt(_e_hXY_tsB, 0, hXY_tsB, 0, _dh.getSessionKey(), _Y, _Y.length-16, _e_hXY_tsB.length);
byte XY[] = new byte[_X.length + _Y.length];
System.arraycopy(_X, 0, XY, 0, _X.length);
System.arraycopy(_Y, 0, XY, _X.length, _Y.length);
Hash h = _context.sha().calculateHash(XY);
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "h(XY)=" + h.toBase64());
if (!DataHelper.eq(h.getData(), 0, hXY_tsB, 0, Hash.HASH_LENGTH)) {
_context.statManager().addRateData("ntcp.invalidHXY", 1, 0);
fail("Invalid H(X+Y) - mitm attack attempted?");
return;
}
_tsB = DataHelper.fromLong(hXY_tsB, Hash.HASH_LENGTH, 4); // their (Bob's) timestamp in seconds
_tsA = _context.clock().now()/1000; // our (Alice's) timestamp in seconds
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"h(X+Y) is correct, tsA-tsB=" + (_tsA-_tsB));
// the skew is not authenticated yet, but it is certainly fatal to
// the establishment, so fail hard if appropriate
long diff = 1000*Math.abs(_tsA-_tsB);
if (diff >= Router.CLOCK_FUDGE_FACTOR) {
_context.statManager().addRateData("ntcp.invalidOutboundSkew", diff, 0);
_transport.markReachable(_con.getRemotePeer().calculateHash());
_context.shitlist().shitlistRouter(_con.getRemotePeer().calculateHash(), "Outbound clock skew of " + diff + " ms");
fail("Clocks too skewed (" + diff + " ms)", null, true);
return;
} else if (_log.shouldLog(Log.DEBUG)) {
_log.debug(prefix()+"Clock skew: " + diff + " ms");
}
// now prepare and send our response
// send E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB), sk, hX_xor_Bob.identHash[16:31])
int sigSize = _X.length+_Y.length+Hash.HASH_LENGTH+4+4;//+12;
byte preSign[] = new byte[sigSize];
System.arraycopy(_X, 0, preSign, 0, _X.length);
System.arraycopy(_Y, 0, preSign, _X.length, _Y.length);
System.arraycopy(_con.getRemotePeer().calculateHash().getData(), 0, preSign, _X.length+_Y.length, Hash.HASH_LENGTH);
DataHelper.toLong(preSign, _X.length+_Y.length+Hash.HASH_LENGTH, 4, _tsA);
DataHelper.toLong(preSign, _X.length+_Y.length+Hash.HASH_LENGTH+4, 4, _tsB);
// hXY_tsB has 12 bytes of padding (size=48, tsB=4 + hXY=32)
//System.arraycopy(hXY_tsB, hXY_tsB.length-12, preSign, _X.length+_Y.length+Hash.HASH_LENGTH+4+4, 12);
//byte sigPad[] = new byte[padSig];
//_context.random().nextBytes(sigPad);
//System.arraycopy(sigPad, 0, preSign, _X.length+_Y.length+Hash.HASH_LENGTH+4+4, padSig);
Signature sig = _context.dsa().sign(preSign, _context.keyManager().getSigningPrivateKey());
//if (_log.shouldLog(Log.DEBUG)) {
// _log.debug(prefix()+"signing " + Base64.encode(preSign));
//}
byte ident[] = _context.router().getRouterInfo().getIdentity().toByteArray();
int min = 2+ident.length+4+Signature.SIGNATURE_BYTES;
int rem = min % 16;
int padding = 0;
if (rem > 0)
padding = 16 - rem;
byte preEncrypt[] = new byte[min+padding];
DataHelper.toLong(preEncrypt, 0, 2, ident.length);
System.arraycopy(ident, 0, preEncrypt, 2, ident.length);
DataHelper.toLong(preEncrypt, 2+ident.length, 4, _tsA);
byte pad[] = new byte[padding];
_context.random().nextBytes(pad);
System.arraycopy(pad, 0, preEncrypt, 2+ident.length+4, padding);
System.arraycopy(sig.getData(), 0, preEncrypt, 2+ident.length+4+padding, Signature.SIGNATURE_BYTES);
_prevEncrypted = new byte[preEncrypt.length];
_context.aes().encrypt(preEncrypt, 0, _prevEncrypted, 0, _dh.getSessionKey(), _hX_xor_bobIdentHash, _hX_xor_bobIdentHash.length-16, preEncrypt.length);
if (_log.shouldLog(Log.DEBUG)) {
//_log.debug(prefix() + "unencrypted response to Bob: " + Base64.encode(preEncrypt));
//_log.debug(prefix() + "encrypted response to Bob: " + Base64.encode(_prevEncrypted));
}
// send 'er off (when the bw limiter says, etc)
_transport.getPumper().wantsWrite(_con, _prevEncrypted);
}
}
if (_received >= _Y.length + _e_hXY_tsB.length && src.hasRemaining()) {
// we are receiving their confirmation
// recv E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev)
int off = 0;
if (_e_bobSig == null) {
_e_bobSig = new byte[48];
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "receiving E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " + src.hasRemaining() + ")");
} else {
off = _received - _Y.length - _e_hXY_tsB.length;
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "continuing to receive E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " + src.hasRemaining() + " off=" + off + " recv=" + _received + ")");
}
while (src.hasRemaining() && off < _e_bobSig.length) {
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix()+"recv bobSig received=" + _received);
_e_bobSig[off++] = src.get();
_received++;
if (off >= _e_bobSig.length) {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug(prefix() + "received E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev): " + Base64.encode(_e_bobSig));
byte bobSig[] = new byte[_e_bobSig.length];
_context.aes().decrypt(_e_bobSig, 0, bobSig, 0, _dh.getSessionKey(), _e_hXY_tsB, _e_hXY_tsB.length-16, _e_bobSig.length);
// ignore the padding
byte bobSigData[] = new byte[Signature.SIGNATURE_BYTES];
System.arraycopy(bobSig, 0, bobSigData, 0, Signature.SIGNATURE_BYTES);
Signature sig = new Signature(bobSigData);
byte toVerify[] = new byte[_X.length+_Y.length+Hash.HASH_LENGTH+4+4];
int voff = 0;
System.arraycopy(_X, 0, toVerify, voff, _X.length); voff += _X.length;
System.arraycopy(_Y, 0, toVerify, voff, _Y.length); voff += _Y.length;
System.arraycopy(_context.routerHash().getData(), 0, toVerify, voff, Hash.HASH_LENGTH); voff += Hash.HASH_LENGTH;
DataHelper.toLong(toVerify, voff, 4, _tsA); voff += 4;
DataHelper.toLong(toVerify, voff, 4, _tsB); voff += 4;
_verified = _context.dsa().verifySignature(sig, toVerify, _con.getRemotePeer().getSigningPublicKey());
if (!_verified) {
_context.statManager().addRateData("ntcp.invalidSignature", 1, 0);
fail("Signature was invalid - attempt to spoof " + _con.getRemotePeer().calculateHash().toBase64() + "?");
return;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "signature verified from Bob. done!");
prepareExtra(src);
byte nextWriteIV[] = new byte[16];
System.arraycopy(_prevEncrypted, _prevEncrypted.length-16, nextWriteIV, 0, 16);
byte nextReadIV[] = new byte[16];
System.arraycopy(_e_bobSig, _e_bobSig.length-16, nextReadIV, 0, nextReadIV.length);
_con.finishOutboundEstablishment(_dh.getSessionKey(), (_tsA-_tsB), nextWriteIV, nextReadIV); // skew in seconds
return;
}
}
}
}
}
/** did the handshake fail for some reason? */
public boolean isCorrupt() { return _err != null; }
/** @return is the handshake complete and valid? */
public boolean isComplete() { return _verified; }
/**
* we are establishing an outbound connection, so prepare ourselves by
* queueing up the write of the first part of the handshake
*/
public void prepareOutbound() {
if (_received <= 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "write out the first part of our handshake");
byte toWrite[] = new byte[_X.length + _hX_xor_bobIdentHash.length];
System.arraycopy(_X, 0, toWrite, 0, _X.length);
System.arraycopy(_hX_xor_bobIdentHash, 0, toWrite, _X.length, _hX_xor_bobIdentHash.length);
_transport.getPumper().wantsWrite(_con, toWrite);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"prepare outbound with received=" + _received);
}
}
/**
* make sure the signatures are correct, and if they are, update the
* NIOConnection with the session key / peer ident / clock skew / iv.
* The NIOConnection itself is responsible for registering with the
* transport
*/
private void verifyInbound() {
if (_corrupt) return;
byte b[] = _sz_aliceIdent_tsA_padding_aliceSig.toByteArray();
//if (_log.shouldLog(Log.DEBUG))
// _log.debug(prefix()+"decrypted sz(etc) data: " + Base64.encode(b));
try {
RouterIdentity alice = new RouterIdentity();
int sz = (int)DataHelper.fromLong(b, 0, 2);
if ( (sz <= 0) || (sz > b.length-2-4-Signature.SIGNATURE_BYTES) ) {
_context.statManager().addRateData("ntcp.invalidInboundSize", sz, 0);
fail("size is invalid", new Exception("size is " + sz));
return;
}
byte aliceData[] = new byte[sz];
System.arraycopy(b, 2, aliceData, 0, sz);
alice.fromByteArray(aliceData);
long tsA = DataHelper.fromLong(b, 2+sz, 4);
ByteArrayOutputStream baos = new ByteArrayOutputStream(768);
baos.write(_X);
baos.write(_Y);
baos.write(_context.routerHash().getData());
baos.write(DataHelper.toLong(4, tsA));
baos.write(DataHelper.toLong(4, _tsB));
//baos.write(b, 2+sz+4, b.length-2-sz-4-Signature.SIGNATURE_BYTES);
byte toVerify[] = baos.toByteArray();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(prefix()+"checking " + Base64.encode(toVerify, 0, 16));
//_log.debug(prefix()+"check pad " + Base64.encode(b, 2+sz+4, 12));
}
byte s[] = new byte[Signature.SIGNATURE_BYTES];
System.arraycopy(b, b.length-s.length, s, 0, s.length);
Signature sig = new Signature(s);
_verified = _context.dsa().verifySignature(sig, toVerify, alice.getSigningPublicKey());
if (_verified) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "verification successful for " + _con);
long diff = 1000*Math.abs(tsA-_tsB);
if (diff >= Router.CLOCK_FUDGE_FACTOR) {
_context.statManager().addRateData("ntcp.invalidInboundSkew", diff, 0);
_transport.markReachable(alice.calculateHash());
_context.shitlist().shitlistRouter(alice.calculateHash(), "Clock skew of " + diff + " ms");
fail("Clocks too skewed (" + diff + " ms)", null, true);
return;
} else if (_log.shouldLog(Log.DEBUG)) {
_log.debug(prefix()+"Clock skew: " + diff + " ms");
}
sendInboundConfirm(alice, tsA);
_con.setRemotePeer(alice);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"e_bobSig is " + _e_bobSig.length + " bytes long");
byte iv[] = new byte[16];
System.arraycopy(_e_bobSig, _e_bobSig.length-16, iv, 0, 16);
_con.finishInboundEstablishment(_dh.getSessionKey(), (tsA-_tsB), iv, _prevEncrypted); // skew in seconds
if (_log.shouldLog(Log.INFO))
_log.info(prefix()+"Verified remote peer as " + alice.calculateHash().toBase64());
} else {
_context.statManager().addRateData("ntcp.invalidInboundSignature", 1, 0);
fail("Peer verification failed - spoof of " + alice.calculateHash().toBase64() + "?");
}
} catch (IOException ioe) {
_context.statManager().addRateData("ntcp.invalidInboundIOE", 1, 0);
fail("Error verifying peer", ioe);
} catch (DataFormatException dfe) {
_context.statManager().addRateData("ntcp.invalidInboundDFE", 1, 0);
fail("Error verifying peer", dfe);
}
}
private void sendInboundConfirm(RouterIdentity alice, long tsA) {
// send Alice E(S(X+Y+Alice.identHash+tsA+tsB), sk, prev)
byte toSign[] = new byte[256+256+32+4+4];
int off = 0;
System.arraycopy(_X, 0, toSign, off, 256); off += 256;
System.arraycopy(_Y, 0, toSign, off, 256); off += 256;
Hash h = alice.calculateHash();
System.arraycopy(h.getData(), 0, toSign, off, 32); off += 32;
DataHelper.toLong(toSign, off, 4, tsA); off += 4;
DataHelper.toLong(toSign, off, 4, _tsB); off += 4;
Signature sig = _context.dsa().sign(toSign, _context.keyManager().getSigningPrivateKey());
byte preSig[] = new byte[Signature.SIGNATURE_BYTES+8];
byte pad[] = new byte[8];
_context.random().nextBytes(pad);
System.arraycopy(sig.getData(), 0, preSig, 0, Signature.SIGNATURE_BYTES);
System.arraycopy(pad, 0, preSig, Signature.SIGNATURE_BYTES, pad.length);
_e_bobSig = new byte[preSig.length];
_context.aes().encrypt(preSig, 0, _e_bobSig, 0, _dh.getSessionKey(), _e_hXY_tsB, _e_hXY_tsB.length-16, _e_bobSig.length);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "Sending encrypted inbound confirmation");
_transport.getPumper().wantsWrite(_con, _e_bobSig);
}
/** anything left over in the byte buffer after verification is extra */
private void prepareExtra(ByteBuffer buf) {
int remaining = buf.remaining();
if (remaining > 0) {
_extra = new byte[remaining];
buf.get(_extra);
_received += remaining;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "prepare extra " + remaining + " (total received: " + _received + ")");
}
/**
* if complete, this will contain any bytes received as part of the
* handshake that were after the actual handshake. This may return null.
*/
public byte[] getExtraBytes() { return _extra; }
private void fail(String reason) { fail(reason, null); }
private void fail(String reason, Exception e) { fail(reason, e, false); }
private void fail(String reason, Exception e, boolean bySkew) {
_corrupt = true;
_failedBySkew = bySkew;
_err = reason;
_e = e;
if (_log.shouldLog(Log.WARN))
_log.warn(prefix()+"Failed to establish: " + _err, e);
}
public String getError() { return _err; }
public Exception getException() { return _e; }
private String prefix() { return toString(); }
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("est").append(System.identityHashCode(this));
if (_con.isInbound()) buf.append(" inbound");
else buf.append(" outbound");
if (_corrupt) buf.append(" corrupt");
if (_verified) buf.append(" verified");
if (_con.isEstablished()) buf.append(" established");
buf.append(": ");
return buf.toString();
}
/**
* a check info connection will receive 256 bytes containing:
* - 32 bytes of uninterpreted, ignored data
* - 1 byte size
* - that many bytes making up the local router's IP address (as reached by the remote side)
* - 2 byte port number that the local router was reached on
* - 4 byte i2p network time as known by the remote side (seconds since the epoch)
* - uninterpreted padding data, up to byte 223
* - xor of the local router's identity hash and the SHA256 of bytes 32 through bytes 223
*/
private static boolean isCheckInfo(I2PAppContext ctx, Hash us, byte first256[]) {
Log log = ctx.logManager().getLog(EstablishState.class);
int off = 32; // ignore the first 32 bytes
Hash h = ctx.sha().calculateHash(first256, off, first256.length-32-off);
byte xor[] = DataHelper.xor(h.getData(), us.getData());
if (log.shouldLog(Log.DEBUG))
log.debug("check hash: " + h.toBase64() + " xor: " + Base64.encode(xor));
if (DataHelper.eq(xor, 0, first256, first256.length-32, 32)) {
// ok, data is as expected
// parse our IP/port/etc out of the first256
int ipSize = (int)DataHelper.fromLong(first256, off, 1);
off++;
byte ip[] = new byte[ipSize];
System.arraycopy(first256, off, ip, 0, ipSize);
try {
InetAddress ourIP = InetAddress.getByAddress(ip);
off += ipSize;
int port = (int)DataHelper.fromLong(first256, off, 2);
off += 2;
long now = DataHelper.fromLong(first256, off, 4);
off += 4;
long skewSeconds = (ctx.clock().now()/1000)-now;
if (log.shouldLog(Log.INFO))
log.info("Check info received: our IP: " + ourIP + " our port: " + port
+ " skew: " + skewSeconds + " s");
} catch (UnknownHostException uhe) {
// ipSize is invalid
if (log.shouldLog(Log.WARN))
log.warn("Invalid IP received on check connection (size: " + ipSize + ")");
}
return true;
} else {
if (log.shouldLog(Log.DEBUG))
log.debug("Not a checkInfo connection");
return false;
}
}
public static void checkHost(String args[]) {
if (args.length != 3) {
System.err.println("Usage: EstablishState ipOrHostname portNum peerHashBase64");
return;
}
try {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
String host = args[0];
int port = Integer.parseInt(args[1]);
byte peer[] = Base64.decode(args[2]);
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
byte toSend[] = new byte[256];
ctx.random().nextBytes(toSend);
int off = 32;
byte ip[] = s.getInetAddress().getAddress();
DataHelper.toLong(toSend, off, 1, ip.length);
off++;
System.arraycopy(ip, 0, toSend, off, ip.length);
off += ip.length;
DataHelper.toLong(toSend, off, 2, port);
off += 2;
long now = ctx.clock().now()/1000;
DataHelper.toLong(toSend, off, 4, now);
off += 4;
Hash h = ctx.sha().calculateHash(toSend, 32, toSend.length-32-32);
DataHelper.xor(peer, 0, h.getData(), 0, toSend, toSend.length-32, peer.length);
System.out.println("check hash: " + h.toBase64());
out.write(toSend);
out.flush();
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
if (args.length == 3) {
checkHost(args);
return;
}
I2PAppContext ctx = I2PAppContext.getGlobalContext();
try {
java.net.Socket s = new java.net.Socket("localhost", 9094);
OutputStream out = s.getOutputStream();
DHSessionKeyBuilder dh = new DHSessionKeyBuilder();
byte X[] = dh.getMyPublicValueBytes();
// SEND X+(H(X) xor Bob.identHash)----------------------------->
out.write(X);
System.out.println("sent X =" + Base64.encode(X));
byte bih[] = Base64.decode("HuRdDx9t-RaZfYkYvacRwP~6s9mvbdkYzIMrpUCsZIo=");
System.out.println("bih = " + Base64.encode(bih));
Hash hx = ctx.sha().calculateHash(X);
System.out.println("hx = " + Base64.encode(hx.getData()));
byte hx_xor_bih[] = DataHelper.xor(bih, hx.getData());
System.out.println("xor = " + Base64.encode(hx_xor_bih));
out.write(hx_xor_bih);
out.flush();
// DONE SENDING X+(H(X) xor Bob.identHash)----------------------------->
// NOW READ Y+E(H(X+Y)+tsB+padding, sk, Y[239:255])
InputStream in = s.getInputStream();
byte toRead[] = new byte[256+(32+4+12)];
int read = 0;
while (read < toRead.length) {
int r = in.read(toRead, read, toRead.length-read);
if (r == -1)
throw new EOFException("eof at read=" + read);
read += r;
}
byte Y[] = new byte[256];
System.arraycopy(toRead, 0, Y, 0, Y.length);
dh.setPeerPublicValue(Y);
byte decrypted[] = new byte[(32+4+12)];
ctx.aes().decrypt(toRead, Y.length, decrypted, 0, dh.getSessionKey(), Y, Y.length-16, decrypted.length);
//display y, encrypted, decrypted, hx+y, tsb, padding
//unencrypted H(X+Y)+tsB+padding: bSJIv1ynFw9MhIqbObOpCqeZxtFvKEx-ilcsZQ31zYNEnVXyHCZagLbdQYRmd1oq
System.out.println("dh session key: " + dh.getSessionKey().toBase64());
System.out.println("decryption iv: " + Base64.encode(Y, Y.length-16, 16));
System.out.println("Y = " + Base64.encode(Y));
byte xy[] = new byte[512];
System.arraycopy(X, 0, xy, 0, X.length);
System.arraycopy(Y, 0, xy, X.length, Y.length);
System.out.println("h(x+y): " + ctx.sha().calculateHash(xy).toBase64());
System.out.println("encrypted H(X+Y)+tsB+padding: " + Base64.encode(toRead, Y.length, toRead.length-Y.length));
System.out.println("unencrypted H(X+Y)+tsB+padding: " + Base64.encode(decrypted));
long tsB = DataHelper.fromLong(decrypted, 32, 4);
//try { Thread.sleep(40*1000); } catch (InterruptedException ie) {}
RouterIdentity alice = new RouterIdentity();
Object k[] = ctx.keyGenerator().generatePKIKeypair();
PublicKey pub = (PublicKey)k[0];
PrivateKey priv = (PrivateKey)k[1];
k = ctx.keyGenerator().generateSigningKeypair();
SigningPublicKey spub = (SigningPublicKey)k[0];
SigningPrivateKey spriv = (SigningPrivateKey)k[1];
alice.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
alice.setPublicKey(pub);
alice.setSigningPublicKey(spub);
// SEND E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB+padding), sk, hX_xor_Bob.identHash[16:31])--->
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte aliceb[] = alice.toByteArray();
long tsA = ctx.clock().now()/1000l;
baos.write(DataHelper.toLong(2, aliceb.length));
baos.write(aliceb);
baos.write(DataHelper.toLong(4, tsA));
int base = baos.size() + Signature.SIGNATURE_BYTES;
int rem = base % 16;
int padding = 0;
if (rem > 0)
padding = 16 - rem;
byte pad[] = new byte[padding];
ctx.random().nextBytes(pad);
baos.write(pad);
base += padding;
ByteArrayOutputStream sbaos = new ByteArrayOutputStream(512);
sbaos.write(X);
sbaos.write(Y);
sbaos.write(bih);
sbaos.write(DataHelper.toLong(4, tsA));
sbaos.write(DataHelper.toLong(4, tsB));
//sbaos.write(pad);
Signature sig = ctx.dsa().sign(sbaos.toByteArray(), spriv);
baos.write(sig.toByteArray());
byte unencrypted[] = baos.toByteArray();
byte toWrite[] = new byte[unencrypted.length];
System.out.println("unencrypted.length = " + unencrypted.length + " alice.size = " + aliceb.length + " padding = " + padding + " base = " + base);
ctx.aes().encrypt(unencrypted, 0, toWrite, 0, dh.getSessionKey(), hx_xor_bih, 16, unencrypted.length);
out.write(toWrite);
out.flush();
System.out.println("unencrypted: " + Base64.encode(unencrypted));
System.out.println("encrypted: " + Base64.encode(toWrite));
System.out.println("Local peer: " + alice.calculateHash().toBase64());
// now check bob's signature
SigningPublicKey bobPubKey = null;
try {
RouterInfo info = new RouterInfo();
info.readBytes(new FileInputStream("/home/jrandom/routers/router1/netDb/routerInfo-HuRdDx9t-RaZfYkYvacRwP~6s9mvbdkYzIMrpUCsZIo=.dat"));
bobPubKey = info.getIdentity().getSigningPublicKey();
} catch (Exception e) {
e.printStackTrace();
return;
}
System.out.println("Reading in bob's sig");
byte bobRead[] = new byte[48];
read = 0;
while (read < bobRead.length) {
int r = in.read(bobRead, read, bobRead.length-read);
if (r == -1)
throw new EOFException("eof at read=" + read);
read += r;
}
// bob should have sent E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev)
byte preSig[] = new byte[Signature.SIGNATURE_BYTES+8];
ctx.aes().decrypt(bobRead, 0, preSig, 0, dh.getSessionKey(), toRead, toRead.length-16, preSig.length);
byte bobSigData[] = new byte[Signature.SIGNATURE_BYTES];
System.arraycopy(preSig, 0, bobSigData, 0, Signature.SIGNATURE_BYTES); // ignore the padding
System.out.println("Bob's sig: " + Base64.encode(bobSigData));
byte signed[] = new byte[256+256+32+4+4];
int off = 0;
System.arraycopy(X, 0, signed, off, 256); off += 256;
System.arraycopy(Y, 0, signed, off, 256); off += 256;
Hash h = alice.calculateHash();
System.arraycopy(h.getData(), 0, signed, off, 32); off += 32;
DataHelper.toLong(signed, off, 4, tsA); off += 4;
DataHelper.toLong(signed, off, 4, tsB); off += 4;
Signature bobSig = new Signature(bobSigData);
boolean ok = ctx.dsa().verifySignature(bobSig, signed, bobPubKey);
System.out.println("bob's sig matches? " + ok);
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
byte fakeI2NPbuf[] = new byte[128];
ctx.random().nextBytes(fakeI2NPbuf);
out.write(fakeI2NPbuf);
out.flush();
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

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