Compare commits

..

45 Commits

Author SHA1 Message Date
jrandom
80c6290b89 oh five oh five 2005-03-30 03:27:55 +00:00
jrandom
6492ad165a Added tracker.fr.i2p 2005-03-30 03:21:18 +00:00
cervantes
f0d1b1a40e added v2mail.i2p, complication.i2p 2005-03-30 01:49:49 +00:00
jrandom
17f044e6cd if using numACKs, use a 2 byte value (to handle higher transfer rates) 2005-03-30 00:20:07 +00:00
jrandom
63f3a9cd7b * 2005-03-29 0.5.0.5 released
2005-03-29  jrandom
    * Decreased the initial RTT estimate to 10s to allow more retries.
    * Increased the default netDb store replication factor from 2 to 6 to take
      into consideration tunnel failures.
    * Address some statistical anonymity attacks against the netDb that could
      be mounted by an active internal adversary by only answering lookups for
      leaseSets we received through an unsolicited store.
    * Don't throttle lookup responses (we throttle enough elsewhere)
    * Fix the NewsFetcher so that it doesn't incorrectly resume midway through
      the file (thanks nickster!)
    * Updated the I2PTunnel HTML (thanks postman!)
    * Added support to the I2PTunnel pages for the URL parameter "passphrase",
      which, if matched against the router.config "i2ptunnel.passphrase" value,
      skips the nonce check.  If the config prop doesn't exist or is blank, no
      passphrase is accepted.
    * Implemented HMAC-SHA256.
    * Enable the tunnel batching with a 500ms delay by default
    * Dropped compatability with 0.5.0.3 and earlier releases
2005-03-30 00:07:36 +00:00
jrandom
b8ddbf13b4 added lazyguy.i2p 2005-03-28 02:41:19 +00:00
jrandom
be9bdbfe0f * simplify the MAC construct with a single HMAC (the other setup was an oracle anyway)
* split out the encryption and MAC keys
2005-03-27 22:08:16 +00:00
cervantes
bc74bf1402 added confessions.i2p, rsync.thetower.i2p, redzara.i2p, gaytorrents.i2p 2005-03-27 01:03:42 +00:00
jrandom
5c2a57f95a minor cleanup 2005-03-26 09:22:17 +00:00
jrandom
9cd8cc692e added replay prevention blurb, minor cleanup 2005-03-26 09:19:42 +00:00
jrandom
ebac4df2d3 2005-03-26 jrandom
* Added some error handling and fairly safe to cache data to the streaming
      lib (good call Tom!)
2005-03-26 07:13:38 +00:00
jrandom
0626f714c6 speling (thanks cervantes) 2005-03-26 06:23:57 +00:00
jrandom
21842291e9 *cough* 2005-03-26 05:56:06 +00:00
jrandom
d461c295f6 first draft of secure semireliable UDP protocol 2005-03-26 05:47:40 +00:00
jrandom
85b3450525 2005-03-25 jrandom
* Fixed up building dependencies for the routerconsole on some more
      aggressive compilers (thanks polecat!)
2005-03-25 04:07:05 +00:00
aum
75d7c81b7c Oops, forgot the DataFormatException 2005-03-24 08:39:04 +00:00
aum
1433e20f73 Added Destination constructor which accepts/uses a base64 string arg 2005-03-24 08:37:17 +00:00
jrandom
e614a2f726 * 2005-03-24 0.5.0.4 released 2005-03-24 07:29:27 +00:00
jrandom
32be7f1fd8 grr 2005-03-24 04:58:28 +00:00
jrandom
66e1d95a2a *cough* oops 2005-03-24 04:49:15 +00:00
jrandom
ff03be217e 2005-03-23 jrandom
* Added more intelligent version checking in news.xml, in case we have a
      version newer than the one specified.
2005-03-24 03:18:15 +00:00
jrandom
a52f8b89dc 2005-03-23 jrandom
* Added support for Transfer-Encoding: chunked to the EepGet, so that the
      cvsweb.cgi doesn't puke on us.
2005-03-24 02:38:10 +00:00
connelly
21c7c043b3 Fixed Bugzilla Bug #99 2005-03-24 01:54:23 +00:00
connelly
45e6608ad3 Added 'Unit test passed' log message and made test check that Bug #99 is fixed. 2005-03-24 01:50:19 +00:00
connelly
28978e3680 Fixed Bug #99: Data pending to be sent is still sent even if STREAM CLOSE is issued. 2005-03-24 01:49:00 +00:00
jrandom
904f755c8c 2005-03-23 jrandom
* Implemented the news fetch / update policy code, as configurated on
      /configupdate.jsp.  Defaults are to grab the news every 24h (or if it
      doesn't exist yet, on startup).  No action is taken however, though if
      the news.xml specifies that a new release is available, an option to
      update will be shown on the router console.
    * New initialNews.xml delivered with new installs, and moved news.xml out
      of the i2pwww module and into the i2p module so that we can bundle it
      within each update.
2005-03-24 01:19:52 +00:00
jrandom
a2c309ddd3 2005-03-23 jrandom
* New /configupdate.jsp page for controlling the update / notification
      process, as well as various minor related updates.  Note that not all
      options are exposed yet, and the update detection code isn't in place
      in this commit - it currently says there is always an update available.
    * New EepGet component for reliable downloading, with a CLI exposed in
      java -cp lib/i2p.jar net.i2p.util.EepGet url
    * Added a default signing key to the TrustedUpdate component to be used
      for verifying updates.  This signing key can be authenticated via
      gpg --verify i2p/core/java/src/net/i2p/crypto/TrustedUpdate.java
    * New public domain SHA1 implementation for the DSA code so that we can
      handle signing streams of arbitrary size without excess memory usage
      (thanks P.Verdy!)
    * Added some helpers to the TrustedUpdate to work off streams and to offer
      a minimal CLI:
          TrustedUpdate keygen pubKeyFile privKeyFile
          TrustedUpdate sign origFile signedFile privKeyFile
          TrustedUpdate verify signedFile
2005-03-23 21:13:03 +00:00
aum
677eeac8f7 changed existing 'decodeToString' to public 2005-03-23 06:30:31 +00:00
aum
b232cc0f24 D'oh, .decodeToString was already there, eliminated my vers 2005-03-23 06:26:23 +00:00
aum
18bbae1d1e changed 'String decode(String raw)' to 'String decodeToString(String raw)'
to eliminate name clash.
2005-03-23 06:24:25 +00:00
aum
08ee62b52c Added convenience methods:
- String encode(String raw)
 - String decode(String raw)
2005-03-23 06:21:16 +00:00
smeghead
5b83aed719 * Added basic trusted update creation/verification 2005-03-22 17:08:01 +00:00
jrandom
b5875ca07b 2005-03-21 jrandom
* Fixed the tunnel fragmentation handler to deal with multiple fragments
      in a single message properly (rather than release the buffer into the
      cache after processing the first one) (duh!)
    * Added the batching preprocessor which will bundle together multiple
      small messages inside a single tunnel message by delaying their delivery
      up to .5s, or whenever the pending data will fill a full message,
      whichever comes first.  This is disabled at the moment, since without the
      above bugfix widely deployed, lots and lots of messages would fail.
    * Within each tunnel pool, stick with a randomly selected peer for up to
      .5s before randomizing and selecting again, instead of randomizing the
      pool each time a tunnel is needed.
2005-03-22 02:00:10 +00:00
jrandom
3f9bf28382 2005-03-21 jrandom
* Fixed the tunnel fragmentation handler to deal with multiple fragments
      in a single message properly (rather than release the buffer into the
      cache after processing the first one) (duh!)
    * Added the batching preprocessor which will bundle together multiple
      small messages inside a single tunnel message by delaying their delivery
      up to .5s, or whenever the pending data will fill a full message,
      whichever comes first.  This is disabled at the moment, since without the
      above bugfix widely deployed, lots and lots of messages would fail.
    * Within each tunnel pool, stick with a randomly selected peer for up to
      .5s before randomizing and selecting again, instead of randomizing the
      pool each time a tunnel is needed.
2005-03-22 01:38:21 +00:00
jrandom
a2bd71c75b * 2005-03-18 0.5.0.3 released
2005-03-18  jrandom
    * Minor tweak to the timestamper to help reduce small skews
    * Adjust the stats published to include only the relevent ones
    * Only show the currently used speed calculation on the profile page
    * Allow the full max # resends to be sent, rather than piggybacking the
      RESET packet along side the final resend (duh)
    * Add irc.postman.i2p to the default list of IRC servers for new installs
    * Drop support for routers running 0.5 or 0.5.0.1 while maintaining
      backwards compatability for users running 0.5.0.2.
2005-03-18 22:34:51 +00:00
jrandom
89509490c5 2005-03-18 jrandom
* Eepproxy Fix for corrupted HTTP headers (thanks nickster!)
    * Fixed case sensitivity issues on the HTTP headers (thanks duck!)
2005-03-18 08:48:00 +00:00
jrandom
a997a46040 2005-03-17 jrandom
* Update the old speed calculator and associated profile data points to
      use a non-tiered moving average of the tunnel test time, avoiding the
      freshness issues of the old tiered speed stats.
    * Explicitly synchronize all of the methods on the PRNG, rather than just
      the feeder methods (sun and kaffe only need the feeder, but it seems ibm
      needs all of them synchronized).
    * Properly use the tunnel tests as part of the profile stats.
    * Don't flood the jobqueue with sequential persist profile tasks, but
      instead, inject a brief scheduling delay between them.
    * Reduce the TCP connection establishment timeout to 20s (which is still
      absurdly excessive)
    * Reduced the max resend delay to 30s so we can get some resends in when
      dealing with client apps that hang up early (e.g. wget)
    * Added more alternative socketManager factories (good call aum!)
2005-03-17 22:12:51 +00:00
jrandom
538dd07e7b 2005-03-16 jrandom
* Adjust the old speed calculator to include end to end RTT data in its
      estimates, and use that as the primary speed calculator again.
    * Use the mean of the high capacity speeds to determine the fast
      threshold, rather than the median.  Perhaps we should use the mean of
      all active non-failing peers?
    * Updated the profile page to sort by tier, then alphabetically.
    * Added some alternative socketManager factories (good call aum!)
2005-03-17 05:29:55 +00:00
cervantes
046778404e added arkan.i2p, search.i2p, floureszination.i2p, antipiratbyran.i2p
asylum.i2p, templar.i2p
2005-03-16 02:56:01 +00:00
jrandom
766f83d653 added feedspace.i2p 2005-03-16 02:46:17 +00:00
jrandom
b20aee6753 2005-03-14 jrandom
* New strict speed calculator that goes off the actual number of messages
      verifiably sent through the peer by way of tunnels.  Initially, this only
      contains the successful message count on inbound tunnels, but may be
      augmented later to include verified outbound messages, peers queried in
      the netDb, etc.  The speed calculation decays quickly, but should give
      a better differential than the previous stat (both values are shown on
      the /profiles.jsp page)
2005-03-15 03:47:14 +00:00
cervantes
f9aa3aef18 added wiki.fr.i2p 2005-03-14 04:31:55 +00:00
jrandom
d74aa6e53d (no, this doesnt fix things yet, but its a save point along the path)
2005-03-11  jrandom
    * Rather than the fixed resend timeout floor (10s), use 10s+RTT as the
      minimum (increased on resends as before, of course).
    * Always prod the clock update listeners, even if just to tell them that
      the time hasn't changed much.
    * Added support for explicit peer selection for individual tunnel pools,
      which will be useful in debugging but not recommended for use by normal
      end users.
    * More aggressively search for the next hop's routerInfo on tunnel join.
    * Give messages received via inbound tunnels that are bound to remote
      locations sufficient time (taking into account clock skew).
    * Give alternate direct send messages sufficient time (10s min, not 5s)
    * Always give the end to end data message the explicit timeout (though the
      old default was sufficient before)
    * No need to give end to end messages an insane expiration (+2m), as we
      are already handling skew on the receiving side.
    * Don't complain too loudly about expired TunnelCreateMessages (at least,
      not until after all those 0.5 and 0.5.0.1 users upgrade ;)
    * Properly keep the sendBps stat
    * When running the router with router.keepHistory=true, log more data to
      messageHistory.txt
    * Logging updates
    * Minor formatting updates
2005-03-11 22:23:36 +00:00
cervantes
ea6fbc7835 added septu.i2p 2005-03-09 20:02:14 +00:00
jrandom
536e604b8e 2005-03-07 jrandom
* Fix the HTTP response header filter to allow multiple headers with the
      same name (thanks duck and spotteri!)
2005-03-08 02:45:14 +00:00
112 changed files with 6837 additions and 1586 deletions

View File

@@ -12,7 +12,8 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.FilterOutputStream;
import java.io.OutputStream;
import java.util.Properties;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import net.i2p.data.ByteArray;
import net.i2p.util.ByteCache;
@@ -68,19 +69,6 @@ class HTTPResponseOutputStream extends FilterOutputStream {
}
}
/**
* filter any headers (adding or removing as necessary), and tweak
* the first response line as necessary.
*
* @return response line ("200 OK", etc)
*/
protected String filterHeaders(String responseLine, Properties props) {
props.setProperty("Connection", "close");
props.setProperty("Proxy-Connection", "close");
return responseLine;
}
/** grow (and free) the buffer as necessary */
private void ensureCapacity() {
if (_headerBuffer.getValid() + 1 >= _headerBuffer.getData().length) {
@@ -105,26 +93,52 @@ class HTTPResponseOutputStream extends FilterOutputStream {
(isNL(first) && isNL(third)); // \n\r\n
}
/**
* Tweak that first HTTP response line (HTTP 200 OK, etc)
*
*/
protected String filterResponseLine(String line) {
return line;
}
/** we ignore any potential \r, since we trim it on write anyway */
private static final byte NL = '\n';
private boolean isNL(byte b) { return (b == NL); }
/** ok, received, now munge & write it */
private void writeHeader() throws IOException {
Properties props = new Properties();
String responseLine = null;
boolean connectionSent = false;
boolean proxyConnectionSent = false;
int lastEnd = -1;
for (int i = 0; i < _headerBuffer.getValid(); i++) {
if (isNL(_headerBuffer.getData()[i])) {
if (lastEnd == -1) {
responseLine = new String(_headerBuffer.getData(), 0, i+1); // includes NL
responseLine = filterResponseLine(responseLine);
responseLine = (responseLine.trim() + "\n");
out.write(responseLine.getBytes());
} else {
for (int j = lastEnd+1; j < i; j++) {
if (_headerBuffer.getData()[j] == ':') {
String key = new String(_headerBuffer.getData(), lastEnd+1, j-(lastEnd+1));
String val = new String(_headerBuffer.getData(), j+2, i-(j+2));
props.setProperty(key, val);
int keyLen = j-(lastEnd+1);
int valLen = i-(j+2);
if ( (keyLen <= 0) || (valLen <= 0) )
throw new IOException("Invalid header @ " + j);
String key = new String(_headerBuffer.getData(), lastEnd+1, keyLen);
String val = new String(_headerBuffer.getData(), j+2, valLen);
if ("Connection".equalsIgnoreCase(key)) {
out.write("Connection: close\n".getBytes());
connectionSent = true;
} else if ("Proxy-Connection".equalsIgnoreCase(key)) {
out.write("Proxy-Connection: close\n".getBytes());
proxyConnectionSent = true;
} else {
out.write((key.trim() + ": " + val.trim() + "\n").getBytes());
}
break;
}
}
@@ -133,34 +147,11 @@ class HTTPResponseOutputStream extends FilterOutputStream {
}
}
if (responseLine == null)
throw new IOException("No HTTP response line, with props=" + props);
responseLine = filterHeaders(responseLine, props);
responseLine = (responseLine.trim() + "\n");
if (_log.shouldLog(Log.DEBUG)) {
StringBuffer msg = new StringBuffer(responseLine.length() + props.size() * 64);
msg.append("HTTP response: first line [").append(responseLine.trim());
msg.append("] options: \n");
if (!connectionSent)
out.write("Connection: close\n".getBytes());
if (!proxyConnectionSent)
out.write("Proxy-Connection: close\n".getBytes());
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = props.getProperty(key);
msg.append('[').append(key.trim()).append("]=[").append(val.trim()).append("]\n");
}
_log.debug(msg.toString());
}
out.write(responseLine.getBytes());
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = props.getProperty(key);
String line = key.trim() + ": " + val.trim() + "\n";
out.write(line.getBytes());
}
out.write("\n".getBytes()); // end of the headers
// done, shove off
@@ -198,6 +189,17 @@ class HTTPResponseOutputStream extends FilterOutputStream {
String invalid2 = "HTTP/1.1 200 OK";
String invalid3 = "HTTP 200 OK\r\n";
String invalid4 = "HTTP 200 OK\r";
String invalid5 = "HTTP/1.1 200 OK\r\n" +
"I am broken, and I smell\r\n" +
"\r\n";
String invalid6 = "HTTP/1.1 200 OK\r\n" +
":I am broken, and I smell\r\n" +
"\r\n";
String invalid7 = "HTTP/1.1 200 OK\n" +
"I am broken, and I smell:\n" +
":asdf\n" +
":\n" +
"\n";
String large = "HTTP/1.1 200 OK\n" +
"Last-modified: Tue, 25 Nov 2003 12:05:38 GMT\n" +
"Expires: Tue, 25 Nov 2003 12:05:38 GMT\n" +
@@ -205,20 +207,23 @@ class HTTPResponseOutputStream extends FilterOutputStream {
"\n" +
"hi ho, this is the body";
/* */
test("Simple", simple);
test("Filtered", filtered);
test("Filtered windows", winfilter);
test("Minimal", minimal);
test("Windows", winmin);
test("Large", large);
test("Invalid (short headers)", invalid1);
test("Invalid (no headers)", invalid2);
test("Invalid (windows with short headers)", invalid3);
test("Invalid (windows no headers)", invalid4);
test("Simple", simple, true);
test("Filtered", filtered, true);
test("Filtered windows", winfilter, true);
test("Minimal", minimal, true);
test("Windows", winmin, true);
test("Large", large, true);
test("Invalid (short headers)", invalid1, true);
test("Invalid (no headers)", invalid2, true);
test("Invalid (windows with short headers)", invalid3, true);
test("Invalid (windows no headers)", invalid4, true);
test("Invalid (bad headers)", invalid5, true);
test("Invalid (bad headers2)", invalid6, false);
test("Invalid (bad headers3)", invalid7, false);
/* */
}
private static void test(String name, String orig) {
private static void test(String name, String orig, boolean shouldPass) {
System.out.println("====Testing: " + name + "\n" + orig + "\n------------");
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
@@ -228,7 +233,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
String received = new String(baos.toByteArray());
System.out.println(received);
} catch (Exception e) {
e.printStackTrace();
if (shouldPass)
e.printStackTrace();
else
System.out.println("Properly fails with " + e.getMessage());
}
}
}

View File

@@ -194,9 +194,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
if (line.startsWith("Connection: ") ||
line.startsWith("Keep-Alive: ") ||
line.startsWith("Proxy-Connection: "))
String lowercaseLine = line.toLowerCase();
if (lowercaseLine.startsWith("connection: ") ||
lowercaseLine.startsWith("keep-alive: ") ||
lowercaseLine.startsWith("proxy-connection: "))
continue;
if (method == null) { // first line (GET /base64/realaddr)
@@ -335,29 +336,29 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
} else {
if (line.startsWith("Host: ") && !usingWWWProxy) {
if (lowercaseLine.startsWith("host: ") && !usingWWWProxy) {
line = "Host: " + host;
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix(requestId) + "Setting host = " + host);
} else if (line.startsWith("User-Agent: ")) {
} else if (lowercaseLine.startsWith("user-agent: ")) {
// always stripped, added back at the end
line = null;
continue;
} else if (line.startsWith("Accept")) {
} else if (lowercaseLine.startsWith("accept")) {
// strip the accept-blah headers, as they vary dramatically from
// browser to browser
line = null;
continue;
} else if (line.startsWith("Referer: ")) {
} else if (lowercaseLine.startsWith("referer: ")) {
// Shouldn't we be more specific, like accepting in-site referers ?
//line = "Referer: i2p";
line = null;
continue; // completely strip the line
} else if (line.startsWith("Via: ")) {
} else if (lowercaseLine.startsWith("via: ")) {
//line = "Via: i2p";
line = null;
continue; // completely strip the line
} else if (line.startsWith("From: ")) {
} else if (lowercaseLine.startsWith("from: ")) {
//line = "From: i2p";
line = null;
continue; // completely strip the line

View File

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

View File

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

View File

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

View File

@@ -1,28 +1,38 @@
package net.i2p.i2ptunnel;
package net.i2p.i2ptunnel.web;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2005 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.Log;
/**
* UUUUuuuuuugly glue code to handle bean interaction from the web, process
* that data, and spit out the results (or the form requested). The basic
* usage is to set any of the fields with data then query the bean via
* getActionResults() which triggers the request processing (taking all the
* provided data, doing what needs to be done) and returns the results of those
* activites. Then a subsequent call to getEditForm() generates the HTML form
* to either edit the currently selected tunnel (if specified) or add a new one.
* This functionality is delegated to the WebEditPageFormGenerator.
* Simple accessor for exposing tunnel info, but also an ugly form handler
*
*/
public class WebEditPageHelper {
private Log _log;
public class IndexBean {
protected I2PAppContext _context;
protected Log _log;
protected TunnelControllerGroup _group;
private String _action;
private int _tunnel;
private long _prevNonce;
private long _curNonce;
private long _nextNonce;
private String _passphrase;
private String _type;
private String _id;
private String _name;
private String _description;
private String _i2cpHost;
@@ -44,30 +54,306 @@ public class WebEditPageHelper {
private boolean _startOnLoad;
private boolean _privKeyGenerate;
private boolean _removeConfirmed;
private long _nonce;
public WebEditPageHelper() {
public static final int RUNNING = 1;
public static final int STARTING = 2;
public static final int NOT_RUNNING = 3;
public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase";
static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
static final String CLIENT_NICKNAME = "shared clients";
public IndexBean() {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(IndexBean.class);
_group = TunnelControllerGroup.getInstance();
_action = null;
_type = null;
_id = null;
_removeConfirmed = false;
_log = I2PAppContext.getGlobalContext().logManager().getLog(WebEditPageHelper.class);
_tunnel = -1;
_curNonce = -1;
_prevNonce = -1;
try {
String nonce = System.getProperty(PROP_NONCE);
if (nonce != null)
_prevNonce = Long.parseLong(nonce);
} catch (NumberFormatException nfe) {}
_nextNonce = _context.random().nextLong();
System.setProperty(PROP_NONCE, Long.toString(_nextNonce));
}
public long getNextNonce() { return _nextNonce; }
public void setNonce(String nonce) {
if (nonce != null) {
try {
_nonce = Long.parseLong(nonce);
} catch (NumberFormatException nfe) {}
if ( (nonce == null) || (nonce.trim().length() <= 0) ) return;
try {
_curNonce = Long.parseLong(nonce);
} catch (NumberFormatException nfe) {
_curNonce = -1;
}
}
public void setPassphrase(String phrase) {
_passphrase = phrase;
}
public void setAction(String action) {
if ( (action == null) || (action.trim().length() <= 0) ) return;
_action = action;
}
public void setTunnel(String tunnel) {
if ( (tunnel == null) || (tunnel.trim().length() <= 0) ) return;
try {
_tunnel = Integer.parseInt(tunnel);
} catch (NumberFormatException nfe) {
_tunnel = -1;
}
}
/**
* Used for form submit - either "Save" or Remove"
*/
public void setAction(String action) {
_action = (action != null ? action.trim() : null);
private boolean validPassphrase(String proposed) {
if (proposed == null) return false;
String pass = _context.getProperty(PROP_TUNNEL_PASSPHRASE);
if ( (pass != null) && (pass.trim().length() > 0) )
return pass.trim().equals(proposed.trim());
else
return false;
}
private String processAction() {
if ( (_action == null) || (_action.trim().length() <= 0) )
return "";
if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) )
return "Invalid nonce, are you being spoofed?";
if ("Stop all tunnels".equals(_action))
return stopAll();
else if ("Start all tunnels".equals(_action))
return startAll();
else if ("Restart all".equals(_action))
return restartAll();
else if ("Reload config".equals(_action))
return reloadConfig();
else if ("stop".equals(_action))
return stop();
else if ("start".equals(_action))
return start();
else if ("Save changes".equals(_action))
return saveChanges();
else if ("Delete this proxy".equals(_action))
return deleteTunnel();
else
return "Action " + _action + " unknown";
}
private String stopAll() {
if (_group == null) return "";
List msgs = _group.stopAllControllers();
return getMessages(msgs);
}
private String startAll() {
if (_group == null) return "";
List msgs = _group.startAllControllers();
return getMessages(msgs);
}
private String restartAll() {
if (_group == null) return "";
List msgs = _group.restartAllControllers();
return getMessages(msgs);
}
private String reloadConfig() {
if (_group == null) return "";
_group.reloadControllers();
return "Config reloaded";
}
private String start() {
if (_tunnel < 0) return "Invalid tunnel";
List controllers = _group.getControllers();
if (_tunnel >= controllers.size()) return "Invalid tunnel";
TunnelController controller = (TunnelController)controllers.get(_tunnel);
controller.startTunnelBackground();
return "";
}
private String stop() {
if (_tunnel < 0) return "Invalid tunnel";
List controllers = _group.getControllers();
if (_tunnel >= controllers.size()) return "Invalid tunnel";
TunnelController controller = (TunnelController)controllers.get(_tunnel);
controller.stopTunnel();
return "";
}
private String saveChanges() {
TunnelController cur = getController(_tunnel);
Properties config = getConfig();
if (config == null)
return "Invalid params";
if (cur == null) {
// creating new
cur = new TunnelController(config, "", true);
_group.addController(cur);
if (cur.getStartOnLoad())
cur.startTunnelBackground();
} else {
cur.setConfig(config, "");
}
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
// all clients use the same I2CP session, and as such, use the same
// I2CP options
List controllers = _group.getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelController c = (TunnelController)controllers.get(i);
if (c == cur) continue;
if ("httpclient".equals(c.getType()) || "client".equals(c.getType())) {
Properties cOpt = c.getConfig("");
if (_tunnelCount != null) {
cOpt.setProperty("option.inbound.quantity", _tunnelCount);
cOpt.setProperty("option.outbound.quantity", _tunnelCount);
}
if (_tunnelDepth != null) {
cOpt.setProperty("option.inbound.length", _tunnelDepth);
cOpt.setProperty("option.outbound.length", _tunnelDepth);
}
cOpt.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
cOpt.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
c.setConfig(cOpt, "");
}
}
}
List msgs = doSave();
msgs.add(0, "Changes saved");
return getMessages(msgs);
}
private List doSave() {
_group.saveConfig();
return _group.clearAllMessages();
}
private String deleteTunnel() {
if (!_removeConfirmed)
return "Please confirm removal";
TunnelController cur = getController(_tunnel);
if (cur == null)
return "Invalid tunnel number";
List msgs = _group.removeController(cur);
msgs.addAll(doSave());
return getMessages(msgs);
}
/**
* Executes any action requested (start/stop/etc) and dump out the
* messages.
*
*/
public String getMessages() {
if (_group == null)
return "";
StringBuffer buf = new StringBuffer(512);
if (_action != null) {
try {
buf.append(processAction()).append("\n");
} catch (Exception e) {
_log.log(Log.CRIT, "Error processing " + _action, e);
}
}
getMessages(_group.clearAllMessages(), buf);
return buf.toString();
}
////
// The remaining methods are simple bean props for the jsp to query
////
public int getTunnelCount() {
if (_group == null) return 0;
return _group.getControllers().size();
}
public boolean isClient(int tunnelNum) {
TunnelController cur = getController(tunnelNum);
if (cur == null) return false;
return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
}
public String getTunnelName(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getName();
else
return "";
}
public String getClientPort(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getListenPort();
else
return "";
}
public String getTunnelType(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return getTypeName(tun.getType());
else
return "";
}
public String getTypeName(String internalType) {
if ("client".equals(internalType)) return "Client proxy";
else if ("httpclient".equals(internalType)) return "HTTP proxy";
else if ("server".equals(internalType)) return "Server";
else if ("httpserver".equals(internalType)) return "HTTP server";
else return internalType;
}
public String getClientInterface(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getListenOnInterface();
else
return "";
}
public int getTunnelStatus(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun == null) return NOT_RUNNING;
if (tun.getIsRunning()) return RUNNING;
else if (tun.getIsStarting()) return STARTING;
else return NOT_RUNNING;
}
public String getTunnelDescription(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getDescription();
else
return "";
}
public String getClientDestination(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun == null) return "";
if ("client".equals(tun.getType())) return tun.getTargetDestination();
else return tun.getProxyList();
}
public String getServerTarget(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getTargetHost() + ':' + tun.getTargetPort();
else
return "";
}
///
/// bean props for form submission
///
/**
* What type of tunnel (httpclient, client, or server). This is
* required when adding a new tunnel.
@@ -76,17 +362,7 @@ public class WebEditPageHelper {
public void setType(String type) {
_type = (type != null ? type.trim() : null);
}
/**
* Which particular tunnel should be edited (index into the current
* TunnelControllerGroup's getControllers() list). This is required
* when editing a tunnel, but not when adding a new one.
*
*/
public void setNum(String id) {
_id = (id != null ? id.trim() : null);
}
String getType() { return _type; }
String getNum() { return _id; }
/** Short name of the tunnel */
public void setName(String name) {
@@ -158,14 +434,6 @@ public class WebEditPageHelper {
public void setPrivKeyFile(String file) {
_privKeyFile = (file != null ? file.trim() : null);
}
/**
* If called with any value, we want to generate a new destination
* for this server tunnel. This won't cause any existing private keys
* to be overwritten, however.
*/
public void setPrivKeyGenerate(String moo) {
_privKeyGenerate = true;
}
/**
* If called with any value (and the form submitted with action=Remove),
* we really do want to stop and remove the tunnel.
@@ -186,145 +454,7 @@ public class WebEditPageHelper {
public void setProfile(String profile) {
_profile = profile;
}
/**
* Process the form and display any resulting messages
*
*/
public String getActionResults() {
try {
return processAction();
} catch (Throwable t) {
_log.log(Log.CRIT, "Internal error processing request", t);
return "Internal error - " + t.getMessage();
}
}
/**
* Generate an HTML form to edit / create a tunnel according to the
* specified fields
*/
public String getEditForm() {
try {
return WebEditPageFormGenerator.getForm(this);
} catch (Throwable t) {
_log.log(Log.CRIT, "Internal error retrieving edit form", t);
return "Internal error - " + t.getMessage();
}
}
/**
* Retrieve the tunnel pointed to by the current id
*
*/
TunnelController getTunnelController() {
if (_id == null) return null;
int id = -1;
try {
id = Integer.parseInt(_id);
List controllers = TunnelControllerGroup.getInstance().getControllers();
if ( (id < 0) || (id >= controllers.size()) )
return null;
else
return (TunnelController)controllers.get(id);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid tunnel id [" + _id + "]", nfe);
return null;
}
}
private String processAction() {
if ( (_action == null) || (_action.trim().length() <= 0) )
return "";
String expected = System.getProperty(getClass().getName() + ".nonce");
if ( (expected == null) || (!expected.equals(Long.toString(_nonce))) )
return "<b>Invalid nonce, are you being spoofed?</b>";
if ("Save".equals(_action))
return save();
else if ("Remove".equals(_action))
return remove();
else
return "Action <i>" + _action + "</i> unknown";
}
private String remove() {
if (!_removeConfirmed)
return "Please confirm removal";
TunnelController cur = getTunnelController();
if (cur == null)
return "Invalid tunnel number";
List msgs = TunnelControllerGroup.getInstance().removeController(cur);
msgs.addAll(doSave());
return getMessages(msgs);
}
private String save() {
if (_type == null)
return "<b>Invalid form submission (no type?)</b>";
Properties config = getConfig();
if (config == null)
return "<b>Invalid params</b>";
TunnelController cur = getTunnelController();
if (cur == null) {
// creating new
cur = new TunnelController(config, "", _privKeyGenerate);
TunnelControllerGroup.getInstance().addController(cur);
if (cur.getStartOnLoad())
cur.startTunnelBackground();
} else {
cur.setConfig(config, "");
}
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
// all clients use the same I2CP session, and as such, use the same
// I2CP options
List controllers = TunnelControllerGroup.getInstance().getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelController c = (TunnelController)controllers.get(i);
if (c == cur) continue;
if ("httpclient".equals(c.getType()) || "client".equals(c.getType())) {
Properties cOpt = c.getConfig("");
if (_tunnelCount != null) {
cOpt.setProperty("option.inbound.quantity", _tunnelCount);
cOpt.setProperty("option.outbound.quantity", _tunnelCount);
}
if (_tunnelDepth != null) {
cOpt.setProperty("option.inbound.length", _tunnelDepth);
cOpt.setProperty("option.outbound.length", _tunnelDepth);
}
// these are per-proxy settings, not per-session settings, and
// as such don't need to be shared. the values are propogated
// to the current tunnel's settings via cur.setConfig above
/*
if (_connectDelay)
cOpt.setProperty("option.i2p.streaming.connectDelay", "1000");
else
cOpt.setProperty("option.i2p.streaming.connectDelay", "0");
if ("interactive".equals(_profile))
cOpt.setProperty("option.i2p.streaming.maxWindowSize", "1");
else
cOpt.remove("option.i2p.streaming.maxWindowSize");
*/
if (_name != null) {
cOpt.setProperty("option.inbound.nickname", _name);
cOpt.setProperty("option.outbound.nickname", _name);
}
c.setConfig(cOpt, "");
}
}
}
return getMessages(doSave());
}
private List doSave() {
TunnelControllerGroup.getInstance().saveConfig();
return TunnelControllerGroup.getInstance().clearAllMessages();
}
/**
* Based on all provided data, create a set of configuration parameters
* suitable for use in a TunnelController. This will replace (not add to)
@@ -344,6 +474,9 @@ public class WebEditPageHelper {
config.setProperty("interface", _reachableBy);
if (_proxyList != null)
config.setProperty("proxyList", _proxyList);
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
} else if ("client".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
@@ -353,6 +486,9 @@ public class WebEditPageHelper {
config.setProperty("interface", _reachableBy);
if (_targetDestination != null)
config.setProperty("targetDestination", _targetDestination);
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
} else if ("server".equals(_type)) {
if (_targetHost != null)
config.setProperty("targetHost", _targetHost);
@@ -422,32 +558,43 @@ public class WebEditPageHelper {
else
config.setProperty("option.i2p.streaming.connectDelay", "0");
if (_name != null) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
}
if ( (!"client".equals(_type)) && (!"httpclient".equals(_type)) ) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
} else {
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
}
}
if ("interactive".equals(_profile))
config.setProperty("option.i2p.streaming.maxWindowSize", "1");
else
config.remove("option.i2p.streaming.maxWindowSize");
}
/**
* Pretty print the messages provided
*
*/
///
///
///
protected TunnelController getController(int tunnel) {
if (tunnel < 0) return null;
if (_group == null) return null;
List controllers = _group.getControllers();
if (controllers.size() > tunnel)
return (TunnelController)controllers.get(tunnel);
else
return null;
}
private String getMessages(List msgs) {
if (msgs == null) return "";
int num = msgs.size();
switch (num) {
case 0: return "";
case 1: return (String)msgs.get(0);
default:
StringBuffer buf = new StringBuffer(512);
buf.append("<ul>");
for (int i = 0; i < num; i++)
buf.append("<li>").append((String)msgs.get(i)).append("</li>\n");
buf.append("</ul>\n");
return buf.toString();
StringBuffer buf = new StringBuffer(128);
getMessages(msgs, buf);
return buf.toString();
}
private void getMessages(List msgs, StringBuffer buf) {
if (msgs == null) return;
for (int i = 0; i < msgs.size(); i++) {
buf.append((String)msgs.get(i)).append("\n");
}
}
}

View File

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

View File

@@ -0,0 +1,280 @@
<%@page contentType="text/html" %>
<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
<% String tun = request.getParameter("tunnel");
int curTunnel = -1;
if (tun != null) {
try {
curTunnel = Integer.parseInt(tun);
} catch (NumberFormatException nfe) {
curTunnel = -1;
}
}
%>
<html>
<head>
<title>I2PTunnel Webmanager</title>
</head>
<body>
<form action="index.jsp">
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td colspan="2" align="center">
<% if (curTunnel >= 0) { %>
<b>Edit proxy settings</b>
<% } else { %>
<b>New proxy settings</b>
<% } %>
</td>
</tr>
<tr>
<td><b>Name: </b>
</td>
<td>
<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b>Type: </b>
<td><%
if (curTunnel >= 0) {
%><%=editBean.getTunnelType(curTunnel)%>
<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
<%
} else {
%><%=editBean.getTypeName(request.getParameter("type"))%>
<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
<%
}
%></td>
</tr>
<tr>
<td><b>Description: </b>
</td>
<td>
<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b>Start automatically?:</b>
</td>
<td>
<% if (editBean.startAutomatically(curTunnel)) { %>
<input value="1" type="checkbox" name="startOnLoad" checked="true" />
<% } else { %>
<input value="1" type="checkbox" name="startOnLoad" />
<% } %>
<i>(Check the Box for 'YES')</i>
</td>
</tr>
<tr>
<td> <b>Listening Port:</b>
</td>
<td>
<input type="text" size="6" maxlength="5" name="port" value="<%=editBean.getClientPort(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b> Accessable by:</b>
</td>
<td>
<select name="reachableBy">
<% String clientInterface = editBean.getClientInterface(curTunnel); %>
<% if (("127.0.0.1".equals(clientInterface)) || (clientInterface == null) || (clientInterface.trim().length() <= 0)) { %>
<option value="127.0.0.1" selected="true">Locally (127.0.0.1)</option>
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
<option value="other">LAN Hosts (Please specify your LAN address)</option>
</select>
&nbsp;&nbsp;
<b>others:</b>
<input type="text" name="reachablyByOther" size="20" />
<% } else if ("0.0.0.0".equals(clientInterface)) { %>
<option value="127.0.0.1">Locally (127.0.0.1)</option>
<option value="0.0.0.0" selected="true">Everyone (0.0.0.0)</option>
<option value="other">LAN Hosts (Please specify your LAN address)</option>
</select>
&nbsp;&nbsp;
<b>others:</b>
<input type="text" name="reachablyByOther" size="20" />
<% } else { %>
<option value="127.0.0.1">Locally (127.0.0.1)</option>
<option value="0.0.0.0">Everyone (0.0.0.0)</option>
<option value="other" selected="true">LAN Hosts (Please specify your LAN address)</option>
</select>
&nbsp;&nbsp;
<b>others:</b>
<input type="text" name="reachablyByOther" size="20" value="<%=clientInterface%>" />
<% } %>
</td>
</tr>
<tr>
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
<td><b>Outproxies:</b>
<% } else { %>
<td><b>Target:</b>
<% } %>
</td>
<td>
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
<input type="text" name="proxyList" value="<%=editBean.getClientDestination(curTunnel)%>" />
<% } else { %>
<input type="text" name="targetDestination" value="<%=editBean.getClientDestination(curTunnel)%>" />
<% } %>
<i>(name or destination)</i>
</td>
</tr>
<tr>
<td>
<b>Delayed connect?</b>
</td>
<td>
<% if (editBean.shouldDelay(curTunnel)) { %>
<input type="checkbox" value="1000" name="connectDelay" checked="true" />
<% } else { %>
<input type="checkbox" value="1000" name="connectDelay" />
<% } %>
<i>(for request/response connections)</i>
</td>
</tr>
<tr>
<td><b>Profile:</b>
</td>
<td>
<select name="profile">
<% if (editBean.isInteractive(curTunnel)) { %>
<option value="interactive" selected="true">interactive connection </option>
<option value="bulk">bulk connection (downloads/websites/BT) </option>
<% } else { %>
<option value="interactive">interactive connection </option>
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
<% } %>
</select>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<b><hr size="1">
Advanced networking options<br />
<span style="color:#dd0000;">(Those are shared between ALL your Client proxies!)</span></b>
</td>
</tr>
<tr>
<td>
<b>Tunnel depth:</b>
</td>
<td><select name="tunnelDepth">
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
switch (tunnelDepth) {
case 0: %>
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<% break;
case 1: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<% break;
case 2: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
<% break;
default: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
<% } %>
</select>
</td>
</tr>
<tr>
<td><b>Tunnel count:</b>
</td>
<td>
<select name="tunnelCount">
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
switch (tunnelCount) {
case 1: %>
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
case 2: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
case 3: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
default: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
<% } %>
</select>
</td>
<tr>
<td><b>I2CP host:</b>
</td>
<td>
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b>I2CP port:</b>
</td>
<td>
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
</td>
</tr>
<tr>
<td><b>Custom options:</b>
</td>
<td>
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
</td>
</tr>
<tr>
<td colspan="2">
<hr size="1">
</td>
</tr>
<tr>
<td>
<b>Save:</b>
</td>
<td>
<input type="submit" name="action" value="Save changes" />
</td>
</tr>
<tr>
<td><b>Delete?</b>
</td>
<td>
<input type="submit" name="action" value="Delete this proxy" /> &nbsp;&nbsp;
<b><span style="color:#dd0000;">confirm delete:</span></b>
<input type="checkbox" value="true" name="removeConfirm" />
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>

View File

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

View File

@@ -1,26 +1,181 @@
<%@page contentType="text/html" %>
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<jsp:useBean class="net.i2p.i2ptunnel.web.IndexBean" id="indexBean" scope="request" />
<jsp:setProperty name="indexBean" property="*" />
<html><head>
<title>I2PTunnel status</title>
</head><body>
<html>
<head>
<title>I2PTunnel Webmanager</title>
</head>
<body style="font-family: Verdana, Tahoma, Helvetica, sans-serif;font-size:12px;">
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td nowrap="true"><b>New Messages: </b><br />
<a href="index.jsp">refresh</a>
</td>
<td>
<textarea rows="3" cols="60" readonly="true"><jsp:getProperty name="indexBean" property="messages" /></textarea>
</td>
</tr>
</table>
</td>
</tr>
</table>
<br />
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<jsp:useBean class="net.i2p.i2ptunnel.WebStatusPageHelper" id="helper" scope="request" />
<jsp:setProperty name="helper" property="*" />
<h2>Messages since last page load:</h2>
<b><jsp:getProperty name="helper" property="actionResults" /></b>
<jsp:getProperty name="helper" property="summaryList" />
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td colspan="7" align="center" valign="middle" style="font-size:14px;">
<b>Your Client Tunnels:</b><br />
<hr size="1" />
</td>
</tr>
<tr>
<td width="15%"><b>Name:</b></td>
<td><b>Port:</b></td>
<td><b>Type:</b></td>
<td><b>Interface:</b></td>
<td><b>Status:</b></td>
</tr>
<% for (int curClient = 0; curClient < indexBean.getTunnelCount(); curClient++) {
if (!indexBean.isClient(curClient)) continue; %>
<tr>
<td valign="top" align="left">
<b><a href="edit.jsp?tunnel=<%=curClient%>"><%=indexBean.getTunnelName(curClient) %></a></b></td>
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientPort(curClient) %></td>
<td valign="top" align="left" nowrap="true"><%=indexBean.getTunnelType(curClient) %></td>
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientInterface(curClient) %></td>
<td valign="top" align="left" nowrap="true"><%
switch (indexBean.getTunnelStatus(curClient)) {
case IndexBean.STARTING:
%><b><span style="color:#339933">Starting...</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
break;
case IndexBean.RUNNING:
%><b><span style="color:#00dd00">Running</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
break;
case IndexBean.NOT_RUNNING:
%><b><span style="color:#dd0000">Not Running</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curClient%>">[START]</a><%
break;
}
%></td>
</tr>
<tr><td align="right" valign="top">Destination:</td>
<td colspan="4"><input align="left" size="40" valign="top" style="overflow: hidden" readonly="true" value="<%=indexBean.getClientDestination(curClient) %>" /></td></tr>
<tr>
<td valign="top" align="right">Description:</td>
<td valign="top" align="left" colspan="4"><%=indexBean.getTunnelDescription(curClient) %></td>
</tr>
<% } %>
</table>
</td>
</tr>
</table>
<br />
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td colspan="5" align="center" valign="middle" style="font-size:14px;">
<b>Your Server Tunnels:</b><br />
<hr size="1" />
</td>
</tr>
<tr>
<td width="15%"><b>Name: </b>
</td>
<td>
<b>Points at:</b>
</td>
<td>
<b>Status:</b>
</td>
</tr>
<% for (int curServer = 0; curServer < indexBean.getTunnelCount(); curServer++) {
if (indexBean.isClient(curServer)) continue; %>
<tr>
<td valign="top">
<b><a href="edit.jsp?tunnel=<%=curServer%>"><%=indexBean.getTunnelName(curServer)%></a></b>
</td>
<td valign="top"><%=indexBean.getServerTarget(curServer)%></td>
<td valign="top" nowrap="true"><%
switch (indexBean.getTunnelStatus(curServer)) {
case IndexBean.RUNNING:
%><b><span style="color:#00dd00">Running</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
break;
case IndexBean.NOT_RUNNING:
%><b><span style="color:#dd0000">Not Running</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curServer%>">[START]</a><%
break;
case IndexBean.STARTING:
%>
<b><span style="color:#339933">Starting...</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
break;
}
%>
</td>
</tr>
<tr><td valign="top" align="right">Description:</td>
<td valign="top" align="left" colspan="2"><%=indexBean.getTunnelDescription(curServer)%></td></tr>
<% } %>
</table>
</td>
</tr>
</table>
<br />
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
<tr>
<td style="background-color:#000">
<div style="background-color:#ffffed">
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
<tr>
<td colspan="2" align="center" valign="middle">
<b>Operations Menu - Please chose from below!</b><br /><br />
</td>
</tr>
<tr>
<form action="index.jsp" method="GET">
<td >
<input type="hidden" name="nonce" value="<%=indexBean.getNextNonce()%>" />
<input type="submit" name="action" value="Stop all tunnels" />
<input type="submit" name="action" value="Start all tunnels" />
<input type="submit" name="action" value="Restart all" />
<input type="submit" name="action" value="Reload config" />
</td>
</form>
<form action="edit.jsp">
<td >
<b>Add new:</b>
<select name="type">
<select name="type">
<option value="httpclient">HTTP proxy</option>
<option value="client">Client tunnel</option>
<option value="server">Server tunnel</option>
<option value="httpserver">HTTP server tunnel</option>
</select> <input type="submit" value="GO" />
<option value="httpserver">HTTP server tunnel</option>
</select> <input type="submit" value="Create" />
</td>
</form>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

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

View File

@@ -233,6 +233,8 @@ class I2PSocketImpl implements I2PSocket {
in.notifyClosed();
}
public boolean isClosed() { return _closedOn > 0; }
/**
* Close the socket from the I2P side (by a close packet)
*/

View File

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

View File

@@ -82,7 +82,12 @@
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
<pathelement location="../../jetty/jettylib/commons-el.jar" />
<pathelement location="../../systray/java/build/obj" />
<pathelement location="../../systray/java/lib/systray4j.jar" />
<pathelement location="../../../installer/lib/wrapper/win32/wrapper.jar" />
<pathelement location="build/routerconsole.jar" />
<pathelement location="../../../router/java/build/router.jar" />
<pathelement location="../../../core/java/build/i2p.jar" />
</classpath>
</javac>
<delete>

View File

@@ -20,7 +20,7 @@ import org.tanukisoftware.wrapper.WrapperManager;
*/
public class ConfigServiceHandler extends FormHandler {
private class UpdateWrapperManagerTask implements Runnable {
public static class UpdateWrapperManagerTask implements Runnable {
private int _exitCode;
public UpdateWrapperManagerTask(int exitCode) {
_exitCode = exitCode;

View File

@@ -0,0 +1,103 @@
package net.i2p.router.web;
import net.i2p.data.DataHelper;
/**
*
*/
public class ConfigUpdateHandler extends FormHandler {
private String _newsURL;
private long _refreshFrequency;
private String _updateURL;
private String _updatePolicy;
private String _proxyHost;
private String _proxyPort;
private boolean _updateThroughProxy;
private String _trustedKeys;
public static final String PROP_NEWS_URL = "router.newsURL";
public static final String DEFAULT_NEWS_URL = "http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/news.xml?rev=HEAD";
public static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
public static final String DEFAULT_REFRESH_FREQUENCY = 12*60*60*1000 + "";
public static final String PROP_UPDATE_URL = "router.updateURL";
public static final String DEFAULT_UPDATE_URL = "http://dev.i2p.net/i2p/i2pupdate.sud";
public static final String PROP_UPDATE_POLICY = "router.updatePolicy";
public static final String DEFAULT_UPDATE_POLICY = "notify";
public static final String PROP_SHOULD_PROXY = "router.updateThroughProxy";
public static final String DEFAULT_SHOULD_PROXY = Boolean.FALSE.toString();
public static final String PROP_PROXY_HOST = "router.updateProxyHost";
public static final String DEFAULT_PROXY_HOST = "localhost";
public static final String PROP_PROXY_PORT = "router.updateProxyPort";
public static final String DEFAULT_PROXY_PORT = "4444";
protected void processForm() {
if ( (_newsURL != null) && (_newsURL.length() > 0) ) {
String oldURL = _context.router().getConfigSetting(PROP_NEWS_URL);
if ( (oldURL == null) || (!_newsURL.equals(oldURL)) ) {
_context.router().setConfigSetting(PROP_NEWS_URL, _newsURL);
addFormNotice("Updating news URL to " + _newsURL);
}
}
if ( (_updateURL != null) && (_updateURL.length() > 0) ) {
String oldURL = _context.router().getConfigSetting(PROP_UPDATE_URL);
if ( (oldURL == null) || (!_updateURL.equals(oldURL)) ) {
_context.router().setConfigSetting(PROP_UPDATE_URL, _updateURL);
addFormNotice("Updating update URL to " + _updateURL);
}
}
if ( (_proxyHost != null) && (_proxyHost.length() > 0) ) {
String oldHost = _context.router().getConfigSetting(PROP_PROXY_HOST);
if ( (oldHost == null) || (!_proxyHost.equals(oldHost)) ) {
_context.router().setConfigSetting(PROP_PROXY_HOST, _proxyHost);
addFormNotice("Updating proxy host to " + _proxyHost);
}
}
if ( (_proxyPort != null) && (_proxyPort.length() > 0) ) {
String oldPort = _context.router().getConfigSetting(PROP_PROXY_PORT);
if ( (oldPort == null) || (!_proxyHost.equals(oldPort)) ) {
_context.router().setConfigSetting(PROP_PROXY_PORT, _proxyPort);
addFormNotice("Updating proxy port to " + _proxyPort);
}
}
if (_updateThroughProxy) {
_context.router().setConfigSetting(PROP_SHOULD_PROXY, Boolean.TRUE.toString());
} else {
_context.router().setConfigSetting(PROP_SHOULD_PROXY, Boolean.FALSE.toString());
}
String oldFreqStr = _context.router().getConfigSetting(PROP_REFRESH_FREQUENCY);
long oldFreq = -1;
if (oldFreqStr != null)
try { oldFreq = Long.parseLong(oldFreqStr); } catch (NumberFormatException nfe) {}
if (_refreshFrequency != oldFreq) {
_context.router().setConfigSetting(PROP_REFRESH_FREQUENCY, ""+_refreshFrequency);
addFormNotice("Updating refresh frequency to " + DataHelper.formatDuration(_refreshFrequency));
}
if ( (_updatePolicy != null) && (_updatePolicy.length() > 0) ) {
String oldPolicy = _context.router().getConfigSetting(PROP_UPDATE_POLICY);
if ( (oldPolicy == null) || (!_updatePolicy.equals(oldPolicy)) ) {
_context.router().setConfigSetting(PROP_UPDATE_POLICY, _updatePolicy);
addFormNotice("Updating update policy to " + _updatePolicy);
}
}
// should save the keys...
_context.router().saveConfig();
}
public void setNewsURL(String url) { _newsURL = url; }
public void setRefreshFrequency(String freq) {
try { _refreshFrequency = Long.parseLong(freq); } catch (NumberFormatException nfe) {}
}
public void setUpdateURL(String url) { _updateURL = url; }
public void setUpdatePolicy(String policy) { _updatePolicy = policy; }
public void setTrustedKeys(String keys) { _trustedKeys = keys; }
public void setUpdateThroughProxy(String foo) { _updateThroughProxy = true; }
public void setProxyHost(String host) { _proxyHost = host; }
public void setProxyPort(String port) { _proxyPort = port; }
}

View File

@@ -0,0 +1,123 @@
package net.i2p.router.web;
import java.util.List;
import net.i2p.data.DataHelper;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.router.RouterContext;
public class ConfigUpdateHelper {
private RouterContext _context;
/**
* Configure this bean to query a particular router context
*
* @param contextId begging few characters of the routerHash, or null to pick
* the first one we come across.
*/
public void setContextId(String contextId) {
try {
_context = ContextHelper.getContext(contextId);
} catch (Throwable t) {
t.printStackTrace();
}
}
public ConfigUpdateHelper() {}
public boolean updateAvailable() {
return true;
}
public String getNewsURL() {
String url = _context.getProperty(ConfigUpdateHandler.PROP_NEWS_URL);
if (url != null)
return url;
else
return ConfigUpdateHandler.DEFAULT_NEWS_URL;
}
public String getUpdateURL() {
String url = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL);
if (url != null)
return url;
else
return ConfigUpdateHandler.DEFAULT_UPDATE_URL;
}
public String getProxyHost() {
String host = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST);
if (host != null)
return host;
else
return ConfigUpdateHandler.DEFAULT_PROXY_HOST;
}
public String getProxyPort() {
String port = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT);
if (port != null)
return port;
else
return ConfigUpdateHandler.DEFAULT_PROXY_PORT;
}
public String getUpdateThroughProxy() {
String proxy = _context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY);
if (Boolean.valueOf(proxy).booleanValue())
return "<input type=\"checkbox\" value=\"true\" name=\"updateThroughProxy\" checked=\"true\" >";
else
return "<input type=\"checkbox\" value=\"true\" name=\"updateThroughProxy\" >";
}
private static final long PERIODS[] = new long[] { 12*60*60*1000l, 24*60*60*1000l, 48*60*60*1000l, -1l };
public String getRefreshFrequencySelectBox() {
String freq = _context.getProperty(ConfigUpdateHandler.PROP_REFRESH_FREQUENCY);
if (freq == null) freq = ConfigUpdateHandler.DEFAULT_REFRESH_FREQUENCY;
long ms = -1;
try {
ms = Long.parseLong(freq);
} catch (NumberFormatException nfe) {}
StringBuffer buf = new StringBuffer(256);
buf.append("<select name=\"refreshFrequency\">");
for (int i = 0; i < PERIODS.length; i++) {
buf.append("<option value=\"").append(PERIODS[i]);
if (PERIODS[i] == ms)
buf.append("\" selected=\"true\"");
if (PERIODS[i] == -1)
buf.append("\">Never</option>\n");
else
buf.append("\">Every ").append(DataHelper.formatDuration(PERIODS[i])).append("</option>\n");
}
buf.append("</select>\n");
return buf.toString();
}
public String getUpdatePolicySelectBox() {
String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
if (policy == null) policy = ConfigUpdateHandler.DEFAULT_UPDATE_POLICY;
StringBuffer buf = new StringBuffer(256);
buf.append("<select name=\"updatePolicy\">");
if ("notify".equals(policy))
buf.append("<option value=\"notify\" selected=\"true\">Notify only</option>");
else
buf.append("<option value=\"notify\">Notify only</option>");
if ("install".equals(policy))
buf.append("<option value=\"install\" selected=\"true\">Download and install</option>");
else
buf.append("<option value=\"install\">Download and install</option>");
buf.append("</select>\n");
return buf.toString();
}
public String getTrustedKeys() {
StringBuffer buf = new StringBuffer(1024);
TrustedUpdate up = new TrustedUpdate(_context);
List keys = up.getTrustedKeys();
for (int i = 0; i < keys.size(); i++)
buf.append((String)keys.get(i)).append('\n');
return buf.toString();
}
}

View File

@@ -0,0 +1,255 @@
package net.i2p.router.web;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion;
import net.i2p.util.EepGet;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
/**
* Task to periodically look for updates to the news.xml, and to keep
* track of whether that has an announcement for a new version.
*/
public class NewsFetcher implements Runnable, EepGet.StatusListener {
private I2PAppContext _context;
private Log _log;
private boolean _updateAvailable;
private long _lastFetch;
private static NewsFetcher _instance;
public static final NewsFetcher getInstance() { return _instance; }
private static final String NEWS_FILE = "docs/news.xml";
private static final String TEMP_NEWS_FILE = "docs/news.xml.temp";
public NewsFetcher(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(NewsFetcher.class);
_instance = this;
File news = new File(NEWS_FILE);
if (news.exists())
_lastFetch = news.lastModified();
else
_lastFetch = 0;
}
public boolean updateAvailable() { return _updateAvailable; }
public void run() {
try { Thread.sleep(_context.random().nextLong(5*60*1000)); } catch (InterruptedException ie) {}
while (true) {
if (!_updateAvailable) checkForUpdates();
if (shouldFetchNews())
fetchNews();
try { Thread.sleep(10*60*1000); } catch (InterruptedException ie) {}
}
}
private boolean shouldInstall() {
String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
return ("install".equals(policy));
}
private boolean shouldFetchNews() {
String freq = _context.getProperty(ConfigUpdateHandler.PROP_REFRESH_FREQUENCY);
if (freq == null)
freq = ConfigUpdateHandler.DEFAULT_REFRESH_FREQUENCY;
try {
long ms = Long.parseLong(freq);
if (ms <= 0)
return false;
if (_lastFetch + ms < _context.clock().now()) {
return true;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Last fetched " + DataHelper.formatDuration(_context.clock().now() - _lastFetch) + " ago");
return false;
}
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Invalid refresh frequency: " + freq);
return false;
}
}
private void fetchNews() {
String newsURL = _context.getProperty(ConfigUpdateHandler.PROP_NEWS_URL, ConfigUpdateHandler.DEFAULT_NEWS_URL);
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
String port = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT);
File tempFile = new File(TEMP_NEWS_FILE);
if (tempFile.exists())
tempFile.delete();
int proxyPort = -1;
try {
proxyPort = Integer.parseInt(port);
EepGet get = null;
if (shouldProxy)
get = new EepGet(_context, proxyHost, proxyPort, 10, TEMP_NEWS_FILE, newsURL);
else
get = new EepGet(_context, 10, TEMP_NEWS_FILE, newsURL);
get.addStatusListener(this);
get.fetch();
} catch (Throwable t) {
_log.error("Error fetching the news", t);
}
_lastFetch = _context.clock().now();
}
private static final String VERSION_STRING = "version=\"" + RouterVersion.VERSION + "\"";
private static final String VERSION_PREFIX = "version=\"";
private void checkForUpdates() {
File news = new File(NEWS_FILE);
if (!news.exists()) return;
FileInputStream in = null;
try {
in = new FileInputStream(news);
StringBuffer buf = new StringBuffer(128);
while (DataHelper.readLine(in, buf)) {
int index = buf.indexOf(VERSION_PREFIX);
if (index == -1) {
// skip
} else {
int end = buf.indexOf("\"", index + VERSION_PREFIX.length());
if (end > index) {
String ver = buf.substring(index+VERSION_PREFIX.length(), end);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Found version: [" + ver + "]");
if (needsUpdate(ver)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Our version is out of date, update!");
break;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Our version is current");
return;
}
}
}
if (buf.indexOf(VERSION_STRING) != -1) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Our version found, no need to update: " + buf.toString());
return;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("No match in " + buf.toString());
}
buf.setLength(0);
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error checking the news for an update", ioe);
return;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
// could not find version="0.5.0.1", so there must be an update ;)
if (_log.shouldLog(Log.DEBUG))
_log.debug("Our version was NOT found (" + RouterVersion.VERSION + "), update needed");
_updateAvailable = true;
if (shouldInstall()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Policy requests update, so we update");
UpdateHandler handler = null;
if (_context instanceof RouterContext) {
handler = new UpdateHandler((RouterContext)_context);
} else {
List contexts = RouterContext.listContexts();
if (contexts.size() > 0)
handler = new UpdateHandler((RouterContext)contexts.get(0));
else
_log.log(Log.CRIT, "No router context to update with?");
}
if (handler != null)
handler.update();
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Policy requests manual update, so we do nothing");
}
}
private boolean needsUpdate(String version) {
StringTokenizer newTok = new StringTokenizer(sanitize(version), ".");
StringTokenizer ourTok = new StringTokenizer(sanitize(RouterVersion.VERSION), ".");
while (newTok.hasMoreTokens() && ourTok.hasMoreTokens()) {
String newVer = newTok.nextToken();
String oldVer = ourTok.nextToken();
switch (compare(newVer, oldVer)) {
case -1: // newVer is smaller
return false;
case 0: // eq
break;
case 1: // newVer is larger
return true;
}
}
if (newTok.hasMoreTokens() && !ourTok.hasMoreTokens())
return true;
return false;
}
private static final String VALID = "0123456789.";
private static final String sanitize(String str) {
StringBuffer buf = new StringBuffer(str);
for (int i = 0; i < buf.length(); i++) {
if (VALID.indexOf(buf.charAt(i)) == -1) {
buf.deleteCharAt(i);
i--;
}
}
return buf.toString();
}
private static final int compare(String lhs, String rhs) {
try {
int left = Integer.parseInt(lhs);
int right = Integer.parseInt(rhs);
if (left < right)
return -1;
else if (left == right)
return 0;
else
return 1;
} catch (NumberFormatException nfe) {
return 0;
}
}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
// ignore
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
// ignore
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
if (_log.shouldLog(Log.INFO))
_log.info("News fetched from " + url);
File temp = new File(TEMP_NEWS_FILE);
if (temp.exists()) {
boolean copied = FileUtil.copy(TEMP_NEWS_FILE, NEWS_FILE, true);
if (copied)
temp.delete();
}
checkForUpdates();
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
if (_log.shouldLog(Log.ERROR))
_log.error("Failed to fetch the news from " + url);
File temp = new File(TEMP_NEWS_FILE);
temp.delete();
}
}

View File

@@ -4,9 +4,11 @@ import java.io.File;
import java.io.IOException;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.router.RouterContext;
import net.i2p.apps.systray.SysTray;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PThread;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.WebApplicationContext;
@@ -70,6 +72,10 @@ public class RouterConsoleRunner {
} catch (Throwable t) {
t.printStackTrace();
}
I2PThread t = new I2PThread(new NewsFetcher(I2PAppContext.getGlobalContext()), "NewsFetcher");
t.setDaemon(true);
t.start();
}
private void initialize(WebApplicationContext context) {

View File

@@ -84,7 +84,7 @@ public class SummaryHelper {
if (ms < 60 * 1000) {
return now + " (" + (ms / 1000) + "s)";
} else if (ms < 60 * 1000) {
} else if (ms < 60 * 60 * 1000) {
return now + " (" + (ms / (60 * 1000)) + "m)";
} else if (ms < 24 * 60 * 60 * 1000) {
return now + " (" + (ms / (60 * 60 * 1000)) + "h)";
@@ -96,7 +96,7 @@ public class SummaryHelper {
public boolean allowReseed() {
return (_context.netDb().getKnownRouters() < 10);
}
/**
* Retrieve amount of used memory.
*
@@ -467,4 +467,8 @@ public class SummaryHelper {
return _context.throttle().getTunnelLag() + "ms";
}
public boolean updateAvailable() {
return NewsFetcher.getInstance().updateAvailable();
}
}

View File

@@ -0,0 +1,168 @@
package net.i2p.router.web;
import java.io.File;
import java.text.DecimalFormat;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.EepGet;
import net.i2p.util.Log;
/**
* Handle the request to update the router by firing off an EepGet call and
* displaying its status to anyone who asks. After the download completes,
* it is verified with the TrustedUpdate, and if it is authentic, the router
* is restarted.
*
*/
public class UpdateHandler {
private static UpdateRunner _updateRunner;
private RouterContext _context;
private Log _log;
private DecimalFormat _pct = new DecimalFormat("00.0%");
private static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
public UpdateHandler() {}
public UpdateHandler(RouterContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(UpdateHandler.class);
}
/**
* Configure this bean to query a particular router context
*
* @param contextId begging few characters of the routerHash, or null to pick
* the first one we come across.
*/
public void setContextId(String contextId) {
try {
_context = ContextHelper.getContext(contextId);
_log = _context.logManager().getLog(UpdateHandler.class);
} catch (Throwable t) {
t.printStackTrace();
}
}
public void setUpdateNonce(String nonce) {
if (nonce == null) return;
if (nonce.equals(System.getProperty("net.i2p.router.web.UpdateHandler.nonce")) ||
nonce.equals(System.getProperty("net.i2p.router.web.UpdateHandler.noncePrev"))) {
update();
}
}
public void update() {
synchronized (UpdateHandler.class) {
if (_updateRunner == null)
_updateRunner = new UpdateRunner();
if (_updateRunner.isRunning()) {
return;
} else {
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "true");
I2PThread update = new I2PThread(_updateRunner, "Update");
update.start();
}
}
}
public String getStatus() {
return _updateRunner.getStatus();
}
public class UpdateRunner implements Runnable, EepGet.StatusListener {
private boolean _isRunning;
private String _status;
private long _startedOn;
private long _written;
public UpdateRunner() {
_isRunning = false;
_status = "<b>Updating</b><br />";
}
public boolean isRunning() { return _isRunning; }
public String getStatus() { return _status; }
public void run() {
_isRunning = true;
update();
System.setProperty("net.i2p.router.web.ReseedHandler.updateInProgress", "false");
_isRunning = false;
}
private void update() {
_startedOn = -1;
_status = "<b>Updating</b><br />";
String updateURL = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL, ConfigUpdateHandler.DEFAULT_UPDATE_URL);
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
String port = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT);
int proxyPort = -1;
try {
proxyPort = Integer.parseInt(port);
} catch (NumberFormatException nfe) {
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
return;
}
try {
EepGet get = null;
if (shouldProxy)
get = new EepGet(_context, proxyHost, proxyPort, 10, SIGNED_UPDATE_FILE, updateURL);
else
get = new EepGet(_context, 10, SIGNED_UPDATE_FILE, updateURL);
get.addStatusListener(UpdateRunner.this);
_startedOn = _context.clock().now();
get.fetch();
} catch (Throwable t) {
_context.logManager().getLog(UpdateHandler.class).error("Error updating", t);
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
}
}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Attempt failed on " + url, cause);
_written = 0;
// ignored
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
_written += currentWrite;
StringBuffer buf = new StringBuffer(64);
buf.append("<b>Updating</b> ");
double pct = ((double)alreadyTransferred + (double)_written) / ((double)alreadyTransferred + (double)bytesRemaining);
synchronized (_pct) {
buf.append(_pct.format(pct));
}
buf.append(":<br />\n").append(_written+alreadyTransferred);
buf.append(" transferred<br />");
_status = buf.toString();
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
_status = "<b>Update downloaded</b><br />";
TrustedUpdate up = new TrustedUpdate(_context);
boolean ok = up.migrateVerified(SIGNED_UPDATE_FILE, "i2pupdate.zip");
File f = new File(SIGNED_UPDATE_FILE);
f.delete();
if (ok) {
_log.log(Log.CRIT, "Update was VERIFIED, restarting to install it");
_status = "<b>Update verified</b><br />Restarting<br />";
restart();
} else {
_log.log(Log.CRIT, "Update was INVALID - have you changed your keys?");
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
}
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
_log.log(Log.CRIT, "Update did not download completely (" + bytesTransferred + " with "
+ bytesRemaining + " after " + currentAttempt + " tries)");
_status = "<b>Transfer failed</b><br />";
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
}
}
private void restart() {
_context.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
}
}

View File

@@ -2,9 +2,11 @@
%>Network | <% } else { %><a href="config.jsp">Network</a> | <% }
if (request.getRequestURI().indexOf("configservice.jsp") != -1) {
%>Service | <% } else { %><a href="configservice.jsp">Service</a> | <% }
if (request.getRequestURI().indexOf("configupdate.jsp") != -1) {
%>Update | <% } else { %><a href="configupdate.jsp">Update</a> | <% }
if (request.getRequestURI().indexOf("configtunnels.jsp") != -1) {
%>Tunnels | <% } else { %><a href="configtunnels.jsp">Tunnels</a> | <% }
if (request.getRequestURI().indexOf("configlogging.jsp") != -1) {
%>Logging | <% } else { %><a href="configlogging.jsp">Logging</a> | <% }
if (request.getRequestURI().indexOf("configadvanced.jsp") != -1) {
%>Advanced | <% } else { %><a href="configadvanced.jsp">Advanced</a> | <% } %></h4>
%>Advanced<% } else { %><a href="configadvanced.jsp">Advanced</a><% } %></h4>

View File

@@ -0,0 +1,51 @@
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - config update</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>
<%@include file="nav.jsp" %>
<%@include file="summary.jsp" %>
<div class="main" id="main">
<%@include file="confignav.jsp" %>
<jsp:useBean class="net.i2p.router.web.ConfigUpdateHandler" id="formhandler" scope="request" />
<jsp:setProperty name="formhandler" property="*" />
<jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<font color="red"><jsp:getProperty name="formhandler" property="errors" /></font>
<i><jsp:getProperty name="formhandler" property="notices" /></i>
<jsp:useBean class="net.i2p.router.web.ConfigUpdateHelper" id="updatehelper" scope="request" />
<jsp:setProperty name="updatehelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<form action="configupdate.jsp" method="POST">
<% String prev = System.getProperty("net.i2p.router.web.ConfigUpdateHandler.nonce");
if (prev != null) System.setProperty("net.i2p.router.web.ConfigUpdateHandler.noncePrev", prev);
System.setProperty("net.i2p.router.web.ConfigUpdateHandler.nonce", new java.util.Random().nextLong()+""); %>
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigUpdateHandler.nonce")%>" />
<input type="hidden" name="action" value="update" />
News URL:
<input type="text" size="60" name="newsURL" value="<jsp:getProperty name="updatehelper" property="newsURL" />"><br />
Refresh frequency:
<jsp:getProperty name="updatehelper" property="refreshFrequencySelectBox" /><br />
Update URL:
<input type="text" size="60" name="updateURL" value="<jsp:getProperty name="updatehelper" property="updateURL" />"><br />
Update policy:
<jsp:getProperty name="updatehelper" property="updatePolicySelectBox" /><br />
Update anonymously?
<jsp:getProperty name="updatehelper" property="updateThroughProxy" /><br />
Proxy host: <input type="text" size="10" name="proxyHost" value="<jsp:getProperty name="updatehelper" property="proxyHost" />" /><br />
Proxy port: <input type="text" size="4" name="proxyPort" value="<jsp:getProperty name="updatehelper" property="proxyPort" />" /><br />
<!-- prompt for the eepproxy -->
Trusted keys:
<textarea name="trustedKeys" disabled="true" cols="60" rows="2"><jsp:getProperty name="updatehelper" property="trustedKeys" /></textarea>
<input type="submit" value="Save" />
</form>
</div>
</body>
</html>

View File

@@ -60,3 +60,12 @@ div.main {
text-align: left;
color: inherit;
}
div.news {
margin: 0em 1em 1em 224px;
padding: .5em 1em;
background-color: #ffffc0;
border: medium solid #ffffd0;
text-align: left;
color: inherit;
}

View File

@@ -10,6 +10,13 @@
<%@include file="nav.jsp" %>
<%@include file="summary.jsp" %>
<div class="news" id="news">
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="newshelper" scope="request" />
<jsp:setProperty name="newshelper" property="page" value="docs/news.xml" />
<jsp:setProperty name="newshelper" property="maxLines" value="300" />
<jsp:getProperty name="newshelper" property="content" />
</div>
<div class="main" id="main">
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="contenthelper" scope="request" />
<jsp:setProperty name="contenthelper" property="page" value="docs/readme.html" />

View File

@@ -4,6 +4,9 @@
<jsp:useBean class="net.i2p.router.web.ReseedHandler" id="reseed" scope="request" />
<jsp:setProperty name="reseed" property="*" />
<jsp:useBean class="net.i2p.router.web.UpdateHandler" id="update" scope="request" />
<jsp:setProperty name="update" property="*" />
<jsp:setProperty name="update" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<div class="routersummary">
<u><b>General</b></u><br />
@@ -11,8 +14,24 @@
<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>Memory:</b> <jsp:getProperty name="helper" property="memory" /><br />
<hr />
<b>Memory:</b> <jsp:getProperty name="helper" property="memory" /><br /><%
if (helper.updateAvailable()) {
if ("true".equals(System.getProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"))) {
out.print(update.getStatus());
} else {
long nonce = new java.util.Random().nextLong();
String prev = System.getProperty("net.i2p.router.web.UpdateHandler.nonce");
if (prev != null) System.setProperty("net.i2p.router.web.UpdateHandler.noncePrev", prev);
System.setProperty("net.i2p.router.web.UpdateHandler.nonce", nonce+"");
String uri = request.getRequestURI();
if (uri.indexOf('?') > 0)
uri = uri + "&updateNonce=" + nonce;
else
uri = uri + "?updateNonce=" + nonce;
out.print(" <a href=\"" + uri + "\">Update available</a>");
}
}
%><hr />
<u><b>Peers</b></u><br />
<b>Active:</b> <jsp:getProperty name="helper" property="activePeers" />/<jsp:getProperty name="helper" property="activeProfiles" /><br />

View File

@@ -0,0 +1,12 @@
<%@page contentType="text/html" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - verify update file signature</title>
</head>
<body>
<!-- net.i2p.crypto.TrustedUpdate.verify(request.getParameter("filename")) -->
</body>
</html>

View File

@@ -341,7 +341,7 @@ public class SAMStreamSession {
}
/**
* Remove and close a SAM STREAM session socket handler.
* Remove and gracefully close a SAM STREAM session socket handler.
*
* @param id Handler id to be removed
*/
@@ -357,12 +357,12 @@ public class SAMStreamSession {
if (reader != null)
reader.stopRunning();
if (sender != null)
sender.stopRunning();
_log.debug("Removed SAM STREAM session socket handler " + id);
sender.shutDownGracefully();
_log.debug("Removed SAM STREAM session socket handler (gracefully) " + id);
}
/**
* Remove and close all the socket handlers managed by this SAM
* Remove and hard close all the socket handlers managed by this SAM
* STREAM session.
*
*/
@@ -378,7 +378,7 @@ public class SAMStreamSession {
while (iter.hasNext()) {
id = (Integer)iter.next();
((SAMStreamSessionSocketReader)handlersMap.get(id)).stopRunning();
((StreamSender)sendersMap.get(id)).stopRunning();
((StreamSender)sendersMap.get(id)).shutDownGracefully();
}
handlersMap.clear();
sendersMap.clear();
@@ -498,25 +498,20 @@ public class SAMStreamSession {
}
/**
* Stop a SAM STREAM session socket reader
* Stop a SAM STREAM session socket reader thead immediately.
*
*/
public void stopRunning() {
_log.debug("stopRunning() invoked on socket handler " + id);
_log.debug("stopRunning() invoked on socket reader " + id);
synchronized (runningLock) {
if (stillRunning) {
stillRunning = false;
try {
i2pSocket.close();
} catch (IOException e) {
_log.debug("Caught IOException", e);
}
}
}
}
public void run() {
_log.debug("SAM STREAM session socket handler running");
_log.debug("run() called for socket reader " + id);
int read = -1;
byte[] data = new byte[SOCKET_HANDLER_BUF_SIZE];
@@ -568,7 +563,9 @@ public class SAMStreamSession {
private int _id;
private ByteCache _cache;
private OutputStream _out = null;
private boolean _stillRunning;
private boolean _stillRunning, _shuttingDownGracefully;
private Object runningLock = new Object();
private I2PSocket i2pSocket = null;
public StreamSender(I2PSocket s, int id) throws IOException {
_data = new ArrayList(1);
@@ -576,6 +573,8 @@ public class SAMStreamSession {
_cache = ByteCache.getInstance(4, 32*1024);
_out = s.getOutputStream();
_stillRunning = true;
_shuttingDownGracefully = false;
i2pSocket = s;
}
/**
@@ -602,28 +601,54 @@ public class SAMStreamSession {
}
/**
* Stop a SAM STREAM session socket sender
* Stop a SAM STREAM session socket sender thread immediately
*
*/
public void stopRunning() {
_log.debug("stopRunning() invoked on socket sender " + _id);
_stillRunning = false;
synchronized (_data) {
_data.clear();
_data.notifyAll();
synchronized (runningLock) {
if (_stillRunning) {
_stillRunning = false;
try {
i2pSocket.close();
} catch (IOException e) {
_log.debug("Caught IOException", e);
}
synchronized (_data) {
_data.clear();
_data.notifyAll();
}
}
}
}
/**
* Stop a SAM STREAM session socket sender gracefully: stop the
* sender thread once all pending data has been sent.
*/
public void shutDownGracefully() {
_log.debug("shutDownGracefully() invoked on socket sender " + _id);
_shuttingDownGracefully = true;
}
public void run() {
_log.debug("run() called for socket sender " + _id);
ByteArray data = null;
while (_stillRunning) {
data = null;
try {
synchronized (_data) {
if (_data.size() > 0)
if (_data.size() > 0) {
data = (ByteArray)_data.remove(0);
else
} else if (_shuttingDownGracefully) {
/* No data left and shutting down gracefully?
If so, stop the sender. */
stopRunning();
break;
} else {
/* Wait for data. */
_data.wait(5000);
}
}
if (data != null) {

View File

@@ -30,14 +30,32 @@ public class TestStreamTransfer {
private static Log _log = new Log(TestStreamTransfer.class);
private static String _alice = null;
private static boolean _dead = false;
private static Object _counterLock = new Object();
private static int _recvCounter = 0, _closeCounter = 0;
private static void runTest(String samHost, int samPort, String conOptions) {
int nTests = 20;
startAlice(samHost, samPort, conOptions);
for (int i = 0; i < 20; i++) {
/* Start up nTests different test threads. */
for (int i = 0; i < nTests; i++) {
testBob("bob" + i, samHost, samPort, conOptions);
if (i % 2 == 1)
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
/* Wait until the correct number of messages have been received
by Alices and the correct number of streams have been closed
by Bobs. */
while (true) {
synchronized (_counterLock) {
if (_recvCounter == nTests * 2 && _closeCounter == nTests) {
break;
}
}
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
_log.info("Receive counter is: " + _recvCounter + " Close counter is: " + _closeCounter);
}
/* Return, assuming the test has passed. */
_log.info("Unit test passed.");
}
private static void startAlice(String host, int port, String conOptions) {
@@ -151,6 +169,9 @@ public class TestStreamTransfer {
return;
}
_log.info("\n== Received from the stream " + id + ": [" + new String(payload) + "]");
synchronized (_counterLock) {
_recvCounter++;
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
/*
// now echo it back
@@ -225,11 +246,15 @@ public class TestStreamTransfer {
_log.info("\n** Sending FooBarBaz!");
out.write(req.getBytes());
out.flush();
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}
/* Don't delay here, so we can test whether all data is
sent even if we do a STREAM CLOSE immediately. */
_log.info("Sending close");
req = "STREAM CLOSE ID=42\n";
out.write(req.getBytes());
out.flush();
synchronized (_counterLock) {
_closeCounter++;
}
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
//_dead = true;
s.close();

View File

@@ -71,7 +71,7 @@ public class Connection {
private long _lifetimeDupMessageSent;
private long _lifetimeDupMessageReceived;
public static final long MAX_RESEND_DELAY = 60*1000;
public static final long MAX_RESEND_DELAY = 20*1000;
public static final long MIN_RESEND_DELAY = 10*1000;
/** wait up to 5 minutes after disconnection so we can ack/close packets */
@@ -258,7 +258,7 @@ public class Connection {
}
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
long timeout = (_options.getRTT() < MIN_RESEND_DELAY ? MIN_RESEND_DELAY : _options.getRTT());
long timeout = _options.getRTT() + MIN_RESEND_DELAY;
if (timeout > MAX_RESEND_DELAY)
timeout = MAX_RESEND_DELAY;
if (_log.shouldLog(Log.DEBUG))
@@ -892,13 +892,15 @@ public class Connection {
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
}
if (_log.shouldLog(Log.WARN))
_log.warn("Resend packet " + _packet + " time " + numSends +
" activeResends: " + _activeResends +
" (wsize "
+ newWindowSize + " lifetime "
+ (_context.clock().now() - _packet.getCreatedOn()) + "ms)");
_outboundQueue.enqueue(_packet);
if (numSends - 1 <= _options.getMaxResends()) {
if (_log.shouldLog(Log.WARN))
_log.warn("Resend packet " + _packet + " time " + numSends +
" activeResends: " + _activeResends +
" (wsize "
+ newWindowSize + " lifetime "
+ (_context.clock().now() - _packet.getCreatedOn()) + "ms)");
_outboundQueue.enqueue(_packet);
}
_lastSendTime = _context.clock().now();
@@ -911,7 +913,7 @@ public class Connection {
return;
}
if (numSends > _options.getMaxResends()) {
if (numSends - 1 > _options.getMaxResends()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Too many resends");
_packet.cancelled();

View File

@@ -82,7 +82,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1));
setProfile(getInt(opts, PROP_PROFILE, PROFILE_BULK));
setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, 4*1024));
setRTT(getInt(opts, PROP_INITIAL_RTT, 30*1000));
setRTT(getInt(opts, PROP_INITIAL_RTT, 10*1000));
setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 1000));
setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, 1000));
@@ -107,7 +107,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
if (opts.containsKey(PROP_MAX_MESSAGE_SIZE))
setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, Packet.MAX_PAYLOAD_SIZE));
if (opts.containsKey(PROP_INITIAL_RTT))
setRTT(getInt(opts, PROP_INITIAL_RTT, 30*1000));
setRTT(getInt(opts, PROP_INITIAL_RTT, 10*1000));
if (opts.containsKey(PROP_INITIAL_RECEIVE_WINDOW))
setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
if (opts.containsKey(PROP_INITIAL_RESEND_DELAY))

View File

@@ -13,9 +13,15 @@ import net.i2p.data.Destination;
public class I2PSocketFull implements I2PSocket {
private Connection _connection;
private I2PSocket.SocketErrorListener _listener;
private Destination _remotePeer;
private Destination _localPeer;
public I2PSocketFull(Connection con) {
_connection = con;
if (con != null) {
_remotePeer = con.getRemotePeer();
_localPeer = con.getSession().getMyDestination();
}
}
public void close() throws IOException {
@@ -35,44 +41,70 @@ public class I2PSocketFull implements I2PSocket {
Connection getConnection() { return _connection; }
public InputStream getInputStream() {
return _connection.getInputStream();
Connection c = _connection;
if (c != null)
return c.getInputStream();
else
return null;
}
public I2PSocketOptions getOptions() {
return _connection.getOptions();
Connection c = _connection;
if (c != null)
return c.getOptions();
else
return null;
}
public OutputStream getOutputStream() throws IOException {
return _connection.getOutputStream();
Connection c = _connection;
if (c != null)
return c.getOutputStream();
else
return null;
}
public Destination getPeerDestination() {
return _connection.getRemotePeer();
}
public Destination getPeerDestination() { return _remotePeer; }
public long getReadTimeout() {
return _connection.getOptions().getReadTimeout();
I2PSocketOptions opts = getOptions();
if (opts != null)
return opts.getReadTimeout();
else
return -1;
}
public Destination getThisDestination() {
return _connection.getSession().getMyDestination();
}
public Destination getThisDestination() { return _localPeer; }
public void setOptions(I2PSocketOptions options) {
Connection c = _connection;
if (c == null) return;
if (options instanceof ConnectionOptions)
_connection.setOptions((ConnectionOptions)options);
c.setOptions((ConnectionOptions)options);
else
_connection.setOptions(new ConnectionOptions(options));
c.setOptions(new ConnectionOptions(options));
}
public void setReadTimeout(long ms) {
_connection.getOptions().setReadTimeout(ms);
Connection c = _connection;
if (c == null) return;
c.getOptions().setReadTimeout(ms);
}
public void setSocketErrorListener(I2PSocket.SocketErrorListener lsnr) {
_listener = lsnr;
}
public boolean isClosed() {
Connection c = _connection;
return ((c == null) ||
(!c.getIsConnected()) ||
(c.getResetReceived()) ||
(c.getResetSent()));
}
void destroy() {
_connection = null;
_listener = null;

View File

@@ -552,6 +552,11 @@ public class Packet {
}
public String toString() {
StringBuffer str = formatAsString();
return str.toString();
}
protected StringBuffer formatAsString() {
StringBuffer buf = new StringBuffer(64);
buf.append(toId(_sendStreamId));
//buf.append("<-->");
@@ -570,7 +575,7 @@ public class Packet {
}
if ( (_payload != null) && (_payload.getValid() > 0) )
buf.append(" data: ").append(_payload.getValid());
return buf.toString();
return buf;
}
private static final String toId(byte id[]) {

View File

@@ -107,11 +107,15 @@ public class PacketHandler {
private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss.SSS");
void displayPacket(Packet packet, String prefix, String suffix) {
if (!_log.shouldLog(Log.DEBUG)) return;
String msg = null;
StringBuffer buf = new StringBuffer(256);
synchronized (_fmt) {
msg = _fmt.format(new Date()) + ": " + prefix + " " + packet.toString() + (suffix != null ? " " + suffix : "");
buf.append(_fmt.format(new Date()));
}
System.out.println(msg);
buf.append(": ").append(prefix).append(" ");
buf.append(packet.toString());
if (suffix != null)
buf.append(" ").append(suffix);
System.out.println(buf.toString());
}
private void receiveKnownCon(Connection con, Packet packet) {

View File

@@ -114,16 +114,44 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
public void setResendPacketEvent(SimpleTimer.TimedEvent evt) { _resendEvent = evt; }
public String toString() {
String str = super.toString();
public StringBuffer formatAsString() {
StringBuffer buf = super.formatAsString();
Connection con = _connection;
if (con != null)
buf.append(" rtt ").append(con.getOptions().getRTT());
if ( (_tagsSent != null) && (_tagsSent.size() > 0) )
str = str + " with tags";
buf.append(" with tags");
if (_ackOn > 0)
return str + " ack after " + getAckTime() + (_numSends <= 1 ? "" : " sent " + _numSends + " times");
else
return str + (_numSends <= 1 ? "" : " sent " + _numSends + " times");
buf.append(" ack after ").append(getAckTime());
if (_numSends > 1)
buf.append(" sent ").append(_numSends).append(" times");
if (isFlagSet(Packet.FLAG_SYNCHRONIZE) ||
isFlagSet(Packet.FLAG_CLOSE) ||
isFlagSet(Packet.FLAG_RESET)) {
if (con != null) {
buf.append(" from ");
Destination local = con.getSession().getMyDestination();
if (local != null)
buf.append(local.calculateHash().toBase64().substring(0,4));
else
buf.append("unknown");
buf.append(" to ");
Destination remote = con.getRemotePeer();
if (remote != null)
buf.append(remote.calculateHash().toBase64().substring(0,4));
else
buf.append("unknown");
}
}
return buf;
}
public void waitForAccept(int maxWaitMs) {

View File

@@ -222,6 +222,7 @@
<copy file="core/perl/i2ptest.sh" todir="pkg-temp/scripts/" />
<mkdir dir="pkg-temp/docs" />
<copy file="readme.html" todir="pkg-temp/docs/" />
<copy file="initialNews.xml" tofile="pkg-temp/docs/news.xml" />
<copy file="installer/resources/startconsole.html" todir="pkg-temp/docs/" />
<copy file="installer/resources/start.ico" todir="pkg-temp/docs/" />
<copy file="installer/resources/console.ico" todir="pkg-temp/docs/" />
@@ -270,6 +271,8 @@
<copy file="build/addressbook.war" todir="pkg-temp/webapps/" />
<copy file="build/susimail.war" todir="pkg-temp/webapps/" />
<copy file="history.txt" todir="pkg-temp/" />
<mkdir dir="pkg-temp/docs/" />
<copy file="news.xml" todir="pkg-temp/docs/" />
<!-- the addressbook handles this for updates -->
<!-- <copy file="hosts.txt" todir="pkg-temp/" /> -->
<mkdir dir="pkg-temp/eepsite" />

View File

@@ -14,8 +14,8 @@ package net.i2p;
*
*/
public class CoreVersion {
public final static String ID = "$Revision: 1.29 $ $Date: 2005/02/23 00:00:52 $";
public final static String VERSION = "0.5.0.2";
public final static String ID = "$Revision: 1.32 $ $Date: 2005/03/24 02:29:28 $";
public final static String VERSION = "0.5.0.5";
public static void main(String args[]) {
System.out.println("I2P Core version: " + VERSION);

View File

@@ -91,7 +91,6 @@ public class I2PAppContext {
public static I2PAppContext getGlobalContext() {
synchronized (I2PAppContext.class) {
if (_globalAppContext == null) {
System.err.println("*** Building a seperate global context!");
_globalAppContext = new I2PAppContext(false, null);
}
}

View File

@@ -52,13 +52,17 @@ class I2CPMessageProducer {
CreateSessionMessage msg = new CreateSessionMessage();
SessionConfig cfg = new SessionConfig(session.getMyDestination());
cfg.setOptions(session.getOptions());
if (_log.shouldLog(Log.DEBUG)) _log.debug("config created");
try {
cfg.signSessionConfig(session.getPrivateKey());
} catch (DataFormatException dfe) {
throw new I2PSessionException("Unable to sign the session config", dfe);
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("config signed");
msg.setSessionConfig(cfg);
if (_log.shouldLog(Log.DEBUG)) _log.debug("config loaded into message");
session.sendMessage(msg);
if (_log.shouldLog(Log.DEBUG)) _log.debug("config message sent");
}
/**

View File

@@ -504,12 +504,15 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
long beforeSync = _context.clock().now();
long inSync = 0;
if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync to write");
try {
synchronized (_out) {
inSync = _context.clock().now();
if (_log.shouldLog(Log.DEBUG)) _log.debug("before writeMessage");
message.writeMessage(_out);
if (_log.shouldLog(Log.DEBUG)) _log.debug("after writeMessage");
_out.flush();
if (_log.shouldLog(Log.DEBUG)) _log.debug("after flush");
}
} catch (I2CPMessageException ime) {
throw new I2PSessionException(getPrefix() + "Error writing out the message", ime);

View File

@@ -73,10 +73,11 @@ class I2PSessionImpl2 extends I2PSessionImpl {
}
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent)
throws I2PSessionException {
if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message");
if (isClosed()) throw new I2PSessionException("Already closed");
if (SHOULD_COMPRESS) payload = DataHelper.compress(payload, offset, size);
else throw new IllegalStateException("we need to update sendGuaranteed to support partial send");
if (_log.shouldLog(Log.DEBUG)) _log.debug("message compressed");
// we always send as guaranteed (so we get the session keys/tags acked),
// but only block until the appropriate event has been reached (guaranteed
// success or accepted). we may want to break this out into a seperate
@@ -111,10 +112,12 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent)
throws I2PSessionException {
long begin = _context.clock().now();
if (_log.shouldLog(Log.DEBUG)) _log.debug("begin sendBestEffort");
SessionKey key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
if (_log.shouldLog(Log.DEBUG)) _log.debug("key fetched");
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
if (_log.shouldLog(Log.DEBUG)) _log.debug("tag consumed");
Set sentTags = null;
int oldTags = _context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key);
long availTimeLeft = _context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key);
@@ -151,7 +154,10 @@ class I2PSessionImpl2 extends I2PSessionImpl {
sentTags.addAll(tagsSent);
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("before creating nonce");
long nonce = _context.random().nextInt(Integer.MAX_VALUE);
if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state");
MessageState state = new MessageState(nonce, getPrefix());
state.setKey(key);
state.setTags(sentTags);
@@ -171,6 +177,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
}
}
if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state");
long beforeSendingSync = _context.clock().now();
long inSendingSync = 0;
synchronized (_sendingStates) {

View File

@@ -29,10 +29,13 @@ package net.i2p.crypto;
* POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.InputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
@@ -55,6 +58,12 @@ public class DSAEngine {
return verifySignature(signature, signedData, 0, signedData.length, verifyingKey);
}
public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) {
return verifySignature(signature, calculateHash(signedData, offset, size), verifyingKey);
}
public boolean verifySignature(Signature signature, InputStream in, SigningPublicKey verifyingKey) {
return verifySignature(signature, calculateHash(in), verifyingKey);
}
public boolean verifySignature(Signature signature, Hash hash, SigningPublicKey verifyingKey) {
long start = _context.clock().now();
try {
@@ -72,7 +81,7 @@ public class DSAEngine {
BigInteger r = new NativeBigInteger(1, rbytes);
BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
BigInteger w = s.modInverse(CryptoConstants.dsaq);
byte data[] = calculateHash(signedData, offset, size).getData();
byte data[] = hash.getData();
NativeBigInteger bi = new NativeBigInteger(1, data);
BigInteger u1 = bi.multiply(w).mod(CryptoConstants.dsaq);
BigInteger u2 = r.multiply(w).mod(CryptoConstants.dsaq);
@@ -99,6 +108,18 @@ public class DSAEngine {
}
public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) {
if ((signingKey == null) || (data == null) || (data.length <= 0)) return null;
Hash h = calculateHash(data, offset, length);
return sign(h, signingKey);
}
public Signature sign(InputStream in, SigningPrivateKey signingKey) {
if ((signingKey == null) || (in == null) ) return null;
Hash h = calculateHash(in);
return sign(h, signingKey);
}
public Signature sign(Hash hash, SigningPrivateKey signingKey) {
if ((signingKey == null) || (hash == null)) return null;
long start = _context.clock().now();
Signature sig = new Signature();
@@ -110,11 +131,8 @@ public class DSAEngine {
BigInteger r = CryptoConstants.dsag.modPow(k, CryptoConstants.dsap).mod(CryptoConstants.dsaq);
BigInteger kinv = k.modInverse(CryptoConstants.dsaq);
Hash h = calculateHash(data, offset, length);
if (h == null) return null;
BigInteger M = new NativeBigInteger(1, h.getData());
BigInteger M = new NativeBigInteger(1, hash.getData());
BigInteger x = new NativeBigInteger(1, signingKey.getData());
BigInteger s = (kinv.multiply(M.add(x.multiply(r)))).mod(CryptoConstants.dsaq);
@@ -157,141 +175,27 @@ public class DSAEngine {
return sig;
}
private int[] H0 = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0};
private Hash calculateHash(byte[] source, int offset, int len) {
long length = len * 8;
int k = 448 - (int) ((length + 1) % 512);
if (k < 0) {
k += 512;
}
int padbytes = k / 8;
int wordlength = len / 4 + padbytes / 4 + 3;
int[] M0 = new int[wordlength];
int wordcount = 0;
int x = 0;
for (x = 0; x < (len / 4) * 4; x += 4) {
M0[wordcount] = source[offset + x] << 24 >>> 24 << 24;
M0[wordcount] |= source[offset + x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= source[offset + x + 2] << 24 >>> 24 << 8;
M0[wordcount] |= source[offset + x + 3] << 24 >>> 24 << 0;
wordcount++;
}
switch (len - (wordcount + 1) * 4 + 4) {
case 0:
M0[wordcount] |= 0x80000000;
break;
case 1:
M0[wordcount] = source[offset + x] << 24 >>> 24 << 24;
M0[wordcount] |= 0x00800000;
break;
case 2:
M0[wordcount] = source[offset + x] << 24 >>> 24 << 24;
M0[wordcount] |= source[offset + x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= 0x00008000;
break;
case 3:
M0[wordcount] = source[offset + x] << 24 >>> 24 << 24;
M0[wordcount] |= source[offset + x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= source[offset + x + 2] << 24 >>> 24 << 8;
M0[wordcount] |= 0x00000080;
break;
}
M0[wordlength - 2] = (int) (length >>> 32);
M0[wordlength - 1] = (int) (length);
int[] H = new int[5];
for (x = 0; x < 5; x++) {
H[x] = H0[x];
}
int blocks = M0.length / 16;
int[] W = new int[80];
for (int bl = 0; bl < blocks; bl++) {
int a = H[0];
int b = H[1];
int c = H[2];
int d = H[3];
int e = H[4];
Arrays.fill(W, 0);
for (x = 0; x < 80; x++) {
if (x < 16) {
W[x] = M0[bl * 16 + x];
} else {
W[x] = ROTL(1, W[x - 3] ^ W[x - 8] ^ W[x - 14] ^ W[x - 16]);
}
public Hash calculateHash(InputStream in) {
SHA1 digest = new SHA1();
byte buf[] = new byte[64];
int read = 0;
try {
while ( (read = in.read(buf)) != -1) {
digest.engineUpdate(buf, 0, read);
}
for (x = 0; x < 80; x++) {
int T = add(ROTL(5, a), add(f(x, b, c, d), add(e, add(k(x), W[x]))));
e = d;
d = c;
c = ROTL(30, b);
b = a;
a = T;
}
H[0] = add(a, H[0]);
H[1] = add(b, H[1]);
H[2] = add(c, H[2]);
H[3] = add(d, H[3]);
H[4] = add(e, H[4]);
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to hash the stream", ioe);
return null;
}
byte[] hashbytes = new byte[20];
for (x = 0; x < 5; x++) {
hashbytes[x * 4] = (byte) (H[x] << 0 >>> 24);
hashbytes[x * 4 + 1] = (byte) (H[x] << 8 >>> 24);
hashbytes[x * 4 + 2] = (byte) (H[x] << 16 >>> 24);
hashbytes[x * 4 + 3] = (byte) (H[x] << 24 >>> 24);
}
Hash hash = new Hash();
hash.setData(hashbytes);
return hash;
return new Hash(digest.engineDigest());
}
private int k(int t) {
if (t > -1 && t < 20) {
return 0x5a827999;
} else if (t > 19 && t < 40) {
return 0x6ed9eba1;
} else if (t > 39 && t < 60) {
return 0x8f1bbcdc;
} else if (t > 59 && t < 80) { return 0xca62c1d6; }
return 0x00000000;
}
private int f(int t, int x, int y, int z) {
if (t > -1 && t < 20) {
return Ch(x, y, z);
} else if (t > 19 && t < 40) {
return Parity(x, y, z);
} else if (t > 39 && t < 60) {
return Maj(x, y, z);
} else if (t > 59 && t < 80) { return Parity(x, y, z); }
return 0x00000000;
}
private int Ch(int x, int y, int z) {
return (x & y) ^ (~x & z);
}
private int Parity(int x, int y, int z) {
return x ^ y ^ z;
}
private int Maj(int x, int y, int z) {
return (x & y) ^ (x & z) ^ (y & z);
}
private int ROTL(int n, int x) {
return (x << n) | (x >>> 32 - n);
}
private int add(int x, int y) {
return x + y;
public static Hash calculateHash(byte[] source, int offset, int len) {
SHA1 h = new SHA1();
h.engineUpdate(source, offset, len);
byte digested[] = h.digest();
return new Hash(digested);
}
}

View File

@@ -1,33 +1,101 @@
package net.i2p.crypto;
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
/**
* Calculate the HMAC-SHA256 of a key+message. Currently FAKE - returns a stupid
* kludgy hash: H(H(key) XOR H(data)). Fix me!
* Calculate the HMAC-SHA256 of a key+message.
*
*/
public class HMACSHA256Generator {
public HMACSHA256Generator(I2PAppContext context) { // nop
private I2PAppContext _context;
public HMACSHA256Generator(I2PAppContext context) {
_context = context;
}
public static HMACSHA256Generator getInstance() {
return I2PAppContext.getGlobalContext().hmac();
}
private static final int PAD_LENGTH = 64;
private static final byte[] _IPAD = new byte[PAD_LENGTH];
private static final byte[] _OPAD = new byte[PAD_LENGTH];
static {
for (int i = 0; i < _IPAD.length; i++) {
_IPAD[i] = 0x36;
_OPAD[i] = 0x5C;
}
}
public Buffer createBuffer(int dataLen) { return new Buffer(dataLen); }
public class Buffer {
private byte padded[];
private byte innerBuf[];
private SHA256EntryCache.CacheEntry innerEntry;
private byte rv[];
private byte outerBuf[];
private SHA256EntryCache.CacheEntry outerEntry;
public Buffer(int dataLength) {
padded = new byte[PAD_LENGTH];
innerBuf = new byte[dataLength + PAD_LENGTH];
innerEntry = _context.sha().cache().acquire(innerBuf.length);
rv = new byte[Hash.HASH_LENGTH];
outerBuf = new byte[Hash.HASH_LENGTH + PAD_LENGTH];
outerEntry = _context.sha().cache().acquire(outerBuf.length);
}
public void releaseCached() {
_context.sha().cache().release(innerEntry);
_context.sha().cache().release(outerEntry);
}
public byte[] getHash() { return rv; }
}
/**
* This should calculate the HMAC/SHA256, but it DOESNT. Its just a kludge.
* Fix me.
* Calculate the HMAC of the data with the given key
*/
public Hash calculate(SessionKey key, byte data[]) {
if ((key == null) || (key.getData() == null) || (data == null))
throw new NullPointerException("Null arguments for HMAC");
Hash hkey = SHA256Generator.getInstance().calculateHash(key.getData());
Hash hdata = SHA256Generator.getInstance().calculateHash(data);
return SHA256Generator.getInstance().calculateHash(DataHelper.xor(hkey.getData(), hdata.getData()));
Buffer buf = new Buffer(data.length);
calculate(key, data, buf);
Hash rv = new Hash(buf.rv);
buf.releaseCached();
return rv;
}
/**
* Calculate the HMAC of the data with the given key
*/
public void calculate(SessionKey key, byte data[], Buffer buf) {
// inner hash
padKey(key.getData(), _IPAD, buf.padded);
System.arraycopy(buf.padded, 0, buf.innerBuf, 0, PAD_LENGTH);
System.arraycopy(data, 0, buf.innerBuf, PAD_LENGTH, data.length);
Hash h = _context.sha().calculateHash(buf.innerBuf, buf.innerEntry);
// outer hash
padKey(key.getData(), _OPAD, buf.padded);
System.arraycopy(buf.padded, 0, buf.outerBuf, 0, PAD_LENGTH);
System.arraycopy(h.getData(), 0, buf.outerBuf, PAD_LENGTH, Hash.HASH_LENGTH);
h = _context.sha().calculateHash(buf.outerBuf, buf.outerEntry);
System.arraycopy(h.getData(), 0, buf.rv, 0, Hash.HASH_LENGTH);
}
private static final void padKey(byte key[], byte pad[], byte out[]) {
for (int i = 0; i < SessionKey.KEYSIZE_BYTES; i++)
out[i] = (byte) (key[i] ^ pad[i]);
Arrays.fill(out, SessionKey.KEYSIZE_BYTES, PAD_LENGTH, pad[0]);
}
}

View File

@@ -0,0 +1,697 @@
package net.i2p.crypto;
/* @(#)SHA1.java 1.11 2004-04-26
* This file was freely contributed to the LimeWire project and is covered
* by its existing GPL licence, but it may be used individually as a public
* domain implementation of a published algorithm (see below for references).
* It was also freely contributed to the Bitzi public domain sources.
* @author Philippe Verdy
*/
/* Sun may wish to change the following package name, if integrating this
* class in the Sun JCE Security Provider for Java 1.5 (code-named Tiger).
*
* You can include it in your own Security Provider by inserting
* this property in your Provider derived class:
* put("MessageDigest.SHA-1", "com.bitzi.util.SHA1");
*/
//package com.bitzi.util;
import java.security.*;
//--+---+1--+---+--2+---+---+3--+---+--4+---+---+5--+---+--6+---+---+7--+---+--
//34567890123456789012345678901234567890123456789012345678901234567890123456789
/**
* <p>The FIPS PUB 180-2 standard specifies four secure hash algorithms (SHA-1,
* SHA-256, SHA-384 and SHA-512) for computing a condensed representation of
* electronic data (message). When a message of any length < 2^^64 bits (for
* SHA-1 and SHA-256) or < 2^^128 bits (for SHA-384 and SHA-512) is input to
* an algorithm, the result is an output called a message digest. The message
* digests range in length from 160 to 512 bits, depending on the algorithm.
* Secure hash algorithms are typically used with other cryptographic
* algorithms, such as digital signature algorithms and keyed-hash message
* authentication codes, or in the generation of random numbers (bits).</p>
*
* <p>The four hash algorithms specified in this "SHS" standard are called
* secure because, for a given algorithm, it is computationally infeasible
* 1) to find a message that corresponds to a given message digest, or 2)
* to find two different messages that produce the same message digest. Any
* change to a message will, with a very high probability, result in a
* different message digest. This will result in a verification failure when
* the secure hash algorithm is used with a digital signature algorithm or a
* keyed-hash message authentication algorithm.</p>
*
* <p>A "SHS change notice" adds a SHA-224 algorithm for interoperability,
* which, like SHA-1 and SHA-256, operates on 512-bit blocks and 32-bit words,
* but truncates the final digest and uses distinct initialization values.</p>
*
* <p><b>References:</b></p>
* <ol>
* <li> NIST FIPS PUB 180-2, "Secure Hash Signature Standard (SHS) with
* change notice", National Institute of Standards and Technology (NIST),
* 2002 August 1, and U.S. Department of Commerce, August 26.<br>
* <a href="http://csrc.ncsl.nist.gov/CryptoToolkit/Hash.html">
* http://csrc.ncsl.nist.gov/CryptoToolkit/Hash.html</a>
* <li> NIST FIPS PUB 180-1, "Secure Hash Standard",
* U.S. Department of Commerce, May 1993.<br>
* <a href="http://www.itl.nist.gov/div897/pubs/fip180-1.htm">
* http://www.itl.nist.gov/div897/pubs/fip180-1.htm</a></li>
* <li> Bruce Schneier, "Section 18.7 Secure Hash Algorithm (SHA)",
* <cite>Applied Cryptography, 2nd edition</cite>, <br>
* John Wiley & Sons, 1996</li>
* </ol>
*/
public final class SHA1 extends MessageDigest implements Cloneable {
/**
* This implementation returns a fixed-size digest.
*/
private static final int HASH_LENGTH = 20; // bytes == 160 bits
/**
* Private context for incomplete blocks and padding bytes.
* INVARIANT: padding must be in 0..63.
* When the padding reaches 64, a new block is computed, and
* the 56 last bytes are kept in the padding history.
*/
private byte[] pad;
private int padding;
/**
* Private contextual byte count, sent in the next block,
* after the ending padding block.
*/
private long bytes;
/**
* Private context that contains the current digest key.
*/
private int hA, hB, hC, hD, hE;
/**
* Creates a SHA1 object with default initial state.
*/
public SHA1() {
super("SHA-1");
pad = new byte[64];
init();
}
/**
* Clones this object.
*/
public Object clone() throws CloneNotSupportedException {
SHA1 that = (SHA1)super.clone();
that.pad = (byte[])this.pad.clone();
return that;
}
/**
* Returns the digest length in bytes.
*
* Can be used to allocate your own output buffer when
* computing multiple digests.
*
* Overrides the protected abstract method of
* <code>java.security.MessageDigestSpi</code>.
* @return the digest length in bytes.
*/
public int engineGetDigestLength() {
return HASH_LENGTH;
}
/**
* Reset athen initialize the digest context.
*
* Overrides the protected abstract method of
* <code>java.security.MessageDigestSpi</code>.
*/
protected void engineReset() {
int i = 60;
do {
pad[i ] = (byte)0x00;
pad[i + 1] = (byte)0x00;
pad[i + 2] = (byte)0x00;
pad[i + 3] = (byte)0x00;
} while ((i -= 4) >= 0);
padding = 0;
bytes = 0;
init();
}
/**
* Initialize the digest context.
*/
protected void init() {
hA = 0x67452301;
hB = 0xefcdab89;
hC = 0x98badcfe;
hD = 0x10325476;
hE = 0xc3d2e1f0;
}
/**
* Updates the digest using the specified byte.
* Requires internal buffering, and may be slow.
*
* Overrides the protected abstract method of
* java.security.MessageDigestSpi.
* @param input the byte to use for the update.
*/
public void engineUpdate(byte input) {
bytes++;
if (padding < 63) {
pad[padding++] = input;
return;
}
pad[63] = input;
computeBlock(pad, 0);
padding = 0;
}
/**
* Updates the digest using the specified array of bytes,
* starting at the specified offset.
*
* Input length can be any size. May require internal buffering,
* if input blocks are not multiple of 64 bytes.
*
* Overrides the protected abstract method of
* java.security.MessageDigestSpi.
* @param input the array of bytes to use for the update.
* @param offset the offset to start from in the array of bytes.
* @param len the number of bytes to use, starting at offset.
*/
public void engineUpdate(byte[] input, int offset, int len) {
if (offset >= 0 && len >= 0 && offset + len <= input.length) {
bytes += len;
/* Terminate the previous block. */
int padlen = 64 - padding;
if (padding > 0 && len >= padlen) {
System.arraycopy(input, offset, pad, padding, padlen);
computeBlock(pad, 0);
padding = 0;
offset += padlen;
len -= padlen;
}
/* Loop on large sets of complete blocks. */
while (len >= 512) {
computeBlock(input, offset);
computeBlock(input, offset + 64);
computeBlock(input, offset + 128);
computeBlock(input, offset + 192);
computeBlock(input, offset + 256);
computeBlock(input, offset + 320);
computeBlock(input, offset + 384);
computeBlock(input, offset + 448);
offset += 512;
len -= 512;
}
/* Loop on remaining complete blocks. */
while (len >= 64) {
computeBlock(input, offset);
offset += 64;
len -= 64;
}
/* remaining bytes kept for next block. */
if (len > 0) {
System.arraycopy(input, offset, pad, padding, len);
padding += len;
}
return;
}
throw new ArrayIndexOutOfBoundsException(offset);
}
/**
* Completes the hash computation by performing final operations
* such as padding. Computes the final hash and returns the final
* value as a byte[20] array. Once engineDigest has been called,
* the engine will be automatically reset as specified in the
* JavaSecurity MessageDigest specification.
*
* For faster operations with multiple digests, allocate your own
* array and use engineDigest(byte[], int offset, int len).
*
* Overrides the protected abstract method of
* java.security.MessageDigestSpi.
* @return the length of the digest stored in the output buffer.
*/
public byte[] engineDigest() {
try {
final byte hashvalue[] = new byte[HASH_LENGTH];
engineDigest(hashvalue, 0, HASH_LENGTH);
return hashvalue;
} catch (DigestException e) {
return null;
}
}
/**
* Completes the hash computation by performing final operations
* such as padding. Once engineDigest has been called, the engine
* will be automatically reset (see engineReset).
*
* Overrides the protected abstract method of
* java.security.MessageDigestSpi.
* @param hashvalue the output buffer in which to store the digest.
* @param offset offset to start from in the output buffer
* @param len number of bytes within buf allotted for the digest.
* Both this default implementation and the SUN provider
* do not return partial digests. The presence of this
* parameter is solely for consistency in our API's.
* If the value of this parameter is less than the
* actual digest length, the method will throw a
* DigestException. This parameter is ignored if its
* value is greater than or equal to the actual digest
* length.
* @return the length of the digest stored in the output buffer.
*/
public int engineDigest(byte[] hashvalue, int offset, final int len)
throws DigestException {
if (len >= HASH_LENGTH) {
if (hashvalue.length - offset >= HASH_LENGTH) {
/* Flush the trailing bytes, adding padding bytes into last
* blocks. */
int i;
/* Add padding null bytes but replace the last 8 padding bytes
* by the little-endian 64-bit digested message bit-length. */
pad[i = padding] = (byte)0x80; /* required 1st padding byte */
/* Check if 8 bytes available in pad to store the total
* message size */
switch (i) { /* INVARIANT: i must be in [0..63] */
case 52: pad[53] = (byte)0x00; /* no break; falls thru */
case 53: pad[54] = (byte)0x00; /* no break; falls thru */
case 54: pad[55] = (byte)0x00; /* no break; falls thru */
case 55: break;
case 56: pad[57] = (byte)0x00; /* no break; falls thru */
case 57: pad[58] = (byte)0x00; /* no break; falls thru */
case 58: pad[59] = (byte)0x00; /* no break; falls thru */
case 59: pad[60] = (byte)0x00; /* no break; falls thru */
case 60: pad[61] = (byte)0x00; /* no break; falls thru */
case 61: pad[62] = (byte)0x00; /* no break; falls thru */
case 62: pad[63] = (byte)0x00; /* no break; falls thru */
case 63:
computeBlock(pad, 0);
/* Clear the 56 first bytes of pad[]. */
i = 52;
do {
pad[i ] = (byte)0x00;
pad[i + 1] = (byte)0x00;
pad[i + 2] = (byte)0x00;
pad[i + 3] = (byte)0x00;
} while ((i -= 4) >= 0);
break;
default:
/* Clear the rest of 56 first bytes of pad[]. */
switch (i & 3) {
case 3: i++;
break;
case 2: pad[(i += 2) - 1] = (byte)0x00;
break;
case 1: pad[(i += 3) - 2] = (byte)0x00;
pad[ i - 1] = (byte)0x00;
break;
case 0: pad[(i += 4) - 3] = (byte)0x00;
pad[ i - 2] = (byte)0x00;
pad[ i - 1] = (byte)0x00;
}
do {
pad[i ] = (byte)0x00;
pad[i + 1] = (byte)0x00;
pad[i + 2] = (byte)0x00;
pad[i + 3] = (byte)0x00;
} while ((i += 4) < 56);
}
/* Convert the message size from bytes to big-endian bits. */
pad[56] = (byte)((i = (int)(bytes >>> 29)) >> 24);
pad[57] = (byte)(i >>> 16);
pad[58] = (byte)(i >>> 8);
pad[59] = (byte)i;
pad[60] = (byte)((i = (int)bytes << 3) >> 24);
pad[61] = (byte)(i >>> 16);
pad[62] = (byte)(i >>> 8);
pad[63] = (byte)i;
computeBlock(pad, 0);
/* Return the computed digest in big-endian byte order. */
hashvalue[offset ] = (byte)((i = hA) >>> 24);
hashvalue[offset + 1] = (byte)(i >>> 16);
hashvalue[offset + 2] = (byte)(i >>> 8);
hashvalue[offset + 3] = (byte)i;
hashvalue[offset + 4] = (byte)((i = hB) >>> 24);
hashvalue[offset += 5] = (byte)(i >>> 16);
hashvalue[offset + 1] = (byte)(i >>> 8);
hashvalue[offset + 2] = (byte)i;
hashvalue[offset + 3] = (byte)((i = hC) >>> 24);
hashvalue[offset + 4] = (byte)(i >>> 16);
hashvalue[offset += 5] = (byte)(i >>> 8);
hashvalue[offset + 1] = (byte)i;
hashvalue[offset + 2] = (byte)((i = hD) >>> 24);
hashvalue[offset + 3] = (byte)(i >>> 16);
hashvalue[offset + 4] = (byte)(i >>> 8);
hashvalue[offset += 5] = (byte)i;
hashvalue[offset + 1] = (byte)((i = hE) >>> 24);
hashvalue[offset + 2] = (byte)(i >>> 16);
hashvalue[offset + 3] = (byte)(i >>> 8);
hashvalue[offset + 4] = (byte)i;
engineReset(); /* clear the evidence */
return HASH_LENGTH;
}
throw new DigestException(
"insufficient space in output buffer to store the digest");
}
throw new DigestException("partial digests not returned");
}
/**
* Updates the digest using the specified array of bytes,
* starting at the specified offset, but an implied length
* of exactly 64 bytes.
*
* Requires no internal buffering, but assumes a fixed input size,
* in which the required padding bytes may have been added.
*
* @param input the array of bytes to use for the update.
* @param offset the offset to start from in the array of bytes.
*/
private void computeBlock(final byte[] input, int offset) {
/* Local temporary work variables for intermediate digests. */
int a, b, c, d, e;
/* Cache the input block into the local working set of 32-bit
* values, in big-endian byte order. Be careful when
* widening bytes or integers due to sign extension! */
int i00, i01, i02, i03, i04, i05, i06, i07,
i08, i09, i10, i11, i12, i13, i14, i15;
/* Use hash schedule function Ch (rounds 0..19):
* Ch(x,y,z) = (x & y) ^ (~x & z) = (x & (y ^ z)) ^ z,
* and K00 = .... = K19 = 0x5a827999. */
/* First pass, on big endian input (rounds 0..15). */
e = hE
+ (((a = hA) << 5) | (a >>> 27)) + 0x5a827999 // K00
+ (((b = hB) & ((c = hC) ^ (d = hD))) ^ d) // Ch(b,c,d)
+ (i00 = input[offset ] << 24
| (input[offset + 1] & 0xff) << 16
| (input[offset + 2] & 0xff) << 8
| (input[offset + 3] & 0xff)); // W00
d += ((e << 5) | (e >>> 27)) + 0x5a827999 // K01
+ ((a & ((b = (b << 30) | (b >>> 2)) ^ c)) ^ c) // Ch(a,b,c)
+ (i01 = input[offset + 4] << 24
| (input[offset += 5] & 0xff) << 16
| (input[offset + 1] & 0xff) << 8
| (input[offset + 2] & 0xff)); // W01
c += ((d << 5) | (d >>> 27)) + 0x5a827999 // K02
+ ((e & ((a = (a << 30) | (a >>> 2)) ^ b)) ^ b) // Ch(e,a,b)
+ (i02 = input[offset + 3] << 24
| (input[offset + 4] & 0xff) << 16
| (input[offset += 5] & 0xff) << 8
| (input[offset + 1] & 0xff)); // W02
b += ((c << 5) | (c >>> 27)) + 0x5a827999 // K03
+ ((d & ((e = (e << 30) | (e >>> 2)) ^ a)) ^ a) // Ch(d,e,a)
+ (i03 = input[offset + 2] << 24
| (input[offset + 3] & 0xff) << 16
| (input[offset + 4] & 0xff) << 8
| (input[offset += 5] & 0xff)); // W03
a += ((b << 5) | (b >>> 27)) + 0x5a827999 // K04
+ ((c & ((d = (d << 30) | (d >>> 2)) ^ e)) ^ e) // Ch(c,d,e)
+ (i04 = input[offset + 1] << 24
| (input[offset + 2] & 0xff) << 16
| (input[offset + 3] & 0xff) << 8
| (input[offset + 4] & 0xff)); // W04
e += ((a << 5) | (a >>> 27)) + 0x5a827999 // K05
+ ((b & ((c = (c << 30) | (c >>> 2)) ^ d)) ^ d) // Ch(b,c,d)
+ (i05 = input[offset += 5] << 24
| (input[offset + 1] & 0xff) << 16
| (input[offset + 2] & 0xff) << 8
| (input[offset + 3] & 0xff)); // W05
d += ((e << 5) | (e >>> 27)) + 0x5a827999 // K06
+ ((a & ((b = (b << 30) | (b >>> 2)) ^ c)) ^ c) // Ch(a,b,c)
+ (i06 = input[offset + 4] << 24
| (input[offset += 5] & 0xff) << 16
| (input[offset + 1] & 0xff) << 8
| (input[offset + 2] & 0xff)); // W06
c += ((d << 5) | (d >>> 27)) + 0x5a827999 // K07
+ ((e & ((a = (a << 30) | (a >>> 2)) ^ b)) ^ b) // Ch(e,a,b)
+ (i07 = input[offset + 3] << 24
| (input[offset + 4] & 0xff) << 16
| (input[offset += 5] & 0xff) << 8
| (input[offset + 1] & 0xff)); // W07
b += ((c << 5) | (c >>> 27)) + 0x5a827999 // K08
+ ((d & ((e = (e << 30) | (e >>> 2)) ^ a)) ^ a) // Ch(d,e,a)
+ (i08 = input[offset + 2] << 24
| (input[offset + 3] & 0xff) << 16
| (input[offset + 4] & 0xff) << 8
| (input[offset += 5] & 0xff)); // W08
a += ((b << 5) | (b >>> 27)) + 0x5a827999 // K09
+ ((c & ((d = (d << 30) | (d >>> 2)) ^ e)) ^ e) // Ch(c,d,e)
+ (i09 = input[offset + 1] << 24
| (input[offset + 2] & 0xff) << 16
| (input[offset + 3] & 0xff) << 8
| (input[offset + 4] & 0xff)); // W09
e += ((a << 5) | (a >>> 27)) + 0x5a827999 // K10
+ ((b & ((c = (c << 30) | (c >>> 2)) ^ d)) ^ d) // Ch(b,c,d)
+ (i10 = input[offset += 5] << 24
| (input[offset + 1] & 0xff) << 16
| (input[offset + 2] & 0xff) << 8
| (input[offset + 3] & 0xff)); // W10
d += ((e << 5) | (e >>> 27)) + 0x5a827999 // K11
+ ((a & ((b = (b << 30) | (b >>> 2)) ^ c)) ^ c) // Ch(a,b,c)
+ (i11 = input[offset + 4] << 24
| (input[offset += 5] & 0xff) << 16
| (input[offset + 1] & 0xff) << 8
| (input[offset + 2] & 0xff)); // W11
c += ((d << 5) | (d >>> 27)) + 0x5a827999 // K12
+ ((e & ((a = (a << 30) | (a >>> 2)) ^ b)) ^ b) // Ch(e,a,b)
+ (i12 = input[offset + 3] << 24
| (input[offset + 4] & 0xff) << 16
| (input[offset += 5] & 0xff) << 8
| (input[offset + 1] & 0xff)); // W12
b += ((c << 5) | (c >>> 27)) + 0x5a827999 // K13
+ ((d & ((e = (e << 30) | (e >>> 2)) ^ a)) ^ a) // Ch(d,e,a)
+ (i13 = input[offset + 2] << 24
| (input[offset + 3] & 0xff) << 16
| (input[offset + 4] & 0xff) << 8
| (input[offset += 5] & 0xff)); // W13
a += ((b << 5) | (b >>> 27)) + 0x5a827999 // K14
+ ((c & ((d = (d << 30) | (d >>> 2)) ^ e)) ^ e) // Ch(c,d,e)
+ (i14 = input[offset + 1] << 24
| (input[offset + 2] & 0xff) << 16
| (input[offset + 3] & 0xff) << 8
| (input[offset + 4] & 0xff)); // W14
e += ((a << 5) | (a >>> 27)) + 0x5a827999 // K15
+ ((b & ((c = (c << 30) | (c >>> 2)) ^ d)) ^ d) // Ch(b,c,d)
+ (i15 = input[offset += 5] << 24
| (input[offset + 1] & 0xff) << 16
| (input[offset + 2] & 0xff) << 8
| (input[offset + 3] & 0xff)); // W15
/* Second pass, on scheduled input (rounds 16..31). */
d += ((e << 5) | (e >>> 27)) + 0x5a827999 // K16
+ ((a & ((b = (b << 30) | (b >>> 2)) ^ c)) ^ c) // Ch(a,b,c)
+ (i00 = ((i00 ^= i02 ^ i08 ^ i13) << 1) | (i00 >>> 31)); // W16
c += ((d << 5) | (d >>> 27)) + 0x5a827999 // K17
+ ((e & ((a = (a << 30) | (a >>> 2)) ^ b)) ^ b) // Ch(e,a,b)
+ (i01 = ((i01 ^= i03 ^ i09 ^ i14) << 1) | (i01 >>> 31)); // W17
b += ((c << 5) | (c >>> 27)) + 0x5a827999 // K18
+ ((d & ((e = (e << 30) | (e >>> 2)) ^ a)) ^ a) // Ch(d,e,a)
+ (i02 = ((i02 ^= i04 ^ i10 ^ i15) << 1) | (i02 >>> 31)); // W18
a += ((b << 5) | (b >>> 27)) + 0x5a827999 // K19
+ ((c & ((d = (d << 30) | (d >>> 2)) ^ e)) ^ e) // Ch(c,d,e)
+ (i03 = ((i03 ^= i05 ^ i11 ^ i00) << 1) | (i03 >>> 31)); // W19
/* Use hash schedule function Parity (rounds 20..39):
* Parity(x,y,z) = x ^ y ^ z,
* and K20 = .... = K39 = 0x6ed9eba1. */
e += ((a << 5) | (a >>> 27)) + 0x6ed9eba1 // K20
+ (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d)
+ (i04 = ((i04 ^= i06 ^ i12 ^ i01) << 1) | (i04 >>> 31)); // W20
d += ((e << 5) | (e >>> 27)) + 0x6ed9eba1 // K21
+ (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c)
+ (i05 = ((i05 ^= i07 ^ i13 ^ i02) << 1) | (i05 >>> 31)); // W21
c += ((d << 5) | (d >>> 27)) + 0x6ed9eba1 // K22
+ (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b)
+ (i06 = ((i06 ^= i08 ^ i14 ^ i03) << 1) | (i06 >>> 31)); // W22
b += ((c << 5) | (c >>> 27)) + 0x6ed9eba1 // K23
+ (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a)
+ (i07 = ((i07 ^= i09 ^ i15 ^ i04) << 1) | (i07 >>> 31)); // W23
a += ((b << 5) | (b >>> 27)) + 0x6ed9eba1 // K24
+ (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e)
+ (i08 = ((i08 ^= i10 ^ i00 ^ i05) << 1) | (i08 >>> 31)); // W24
e += ((a << 5) | (a >>> 27)) + 0x6ed9eba1 // K25
+ (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d)
+ (i09 = ((i09 ^= i11 ^ i01 ^ i06) << 1) | (i09 >>> 31)); // W25
d += ((e << 5) | (e >>> 27)) + 0x6ed9eba1 // K26
+ (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c)
+ (i10 = ((i10 ^= i12 ^ i02 ^ i07) << 1) | (i10 >>> 31)); // W26
c += ((d << 5) | (d >>> 27)) + 0x6ed9eba1 // K27
+ (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b)
+ (i11 = ((i11 ^= i13 ^ i03 ^ i08) << 1) | (i11 >>> 31)); // W27
b += ((c << 5) | (c >>> 27)) + 0x6ed9eba1 // K28
+ (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a)
+ (i12 = ((i12 ^= i14 ^ i04 ^ i09) << 1) | (i12 >>> 31)); // W28
a += ((b << 5) | (b >>> 27)) + 0x6ed9eba1 // K29
+ (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e)
+ (i13 = ((i13 ^= i15 ^ i05 ^ i10) << 1) | (i13 >>> 31)); // W29
e += ((a << 5) | (a >>> 27)) + 0x6ed9eba1 // K30
+ (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d)
+ (i14 = ((i14 ^= i00 ^ i06 ^ i11) << 1) | (i14 >>> 31)); // W30
d += ((e << 5) | (e >>> 27)) + 0x6ed9eba1 // K31
+ (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c)
+ (i15 = ((i15 ^= i01 ^ i07 ^ i12) << 1) | (i15 >>> 31)); // W31
/* Third pass, on scheduled input (rounds 32..47). */
c += ((d << 5) | (d >>> 27)) + 0x6ed9eba1 // K32
+ (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b)
+ (i00 = ((i00 ^= i02 ^ i08 ^ i13) << 1) | (i00 >>> 31)); // W32
b += ((c << 5) | (c >>> 27)) + 0x6ed9eba1 // K33
+ (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a)
+ (i01 = ((i01 ^= i03 ^ i09 ^ i14) << 1) | (i01 >>> 31)); // W33
a += ((b << 5) | (b >>> 27)) + 0x6ed9eba1 // K34
+ (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e)
+ (i02 = ((i02 ^= i04 ^ i10 ^ i15) << 1) | (i02 >>> 31)); // W34
e += ((a << 5) | (a >>> 27)) + 0x6ed9eba1 // K35
+ (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d)
+ (i03 = ((i03 ^= i05 ^ i11 ^ i00) << 1) | (i03 >>> 31)); // W35
d += ((e << 5) | (e >>> 27)) + 0x6ed9eba1 // K36
+ (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c)
+ (i04 = ((i04 ^= i06 ^ i12 ^ i01) << 1) | (i04 >>> 31)); // W36
c += ((d << 5) | (d >>> 27)) + 0x6ed9eba1 // K37
+ (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b)
+ (i05 = ((i05 ^= i07 ^ i13 ^ i02) << 1) | (i05 >>> 31)); // W37
b += ((c << 5) | (c >>> 27)) + 0x6ed9eba1 // K38
+ (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a)
+ (i06 = ((i06 ^= i08 ^ i14 ^ i03) << 1) | (i06 >>> 31)); // W38
a += ((b << 5) | (b >>> 27)) + 0x6ed9eba1 // K39
+ (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e)
+ (i07 = ((i07 ^= i09 ^ i15 ^ i04) << 1) | (i07 >>> 31)); // W39
/* Use hash schedule function Maj (rounds 40..59):
* Maj(x,y,z) = (x&y) ^ (x&z) ^ (y&z) = (x & y) | ((x | y) & z),
* and K40 = .... = K59 = 0x8f1bbcdc. */
e += ((a << 5) | (a >>> 27)) + 0x8f1bbcdc // K40
+ ((b & (c = (c << 30) | (c >>> 2))) | ((b | c) & d)) // Maj(b,c,d)
+ (i08 = ((i08 ^= i10 ^ i00 ^ i05) << 1) | (i08 >>> 31)); // W40
d += ((e << 5) | (e >>> 27)) + 0x8f1bbcdc // K41
+ ((a & (b = (b << 30) | (b >>> 2))) | ((a | b) & c)) // Maj(a,b,c)
+ (i09 = ((i09 ^= i11 ^ i01 ^ i06) << 1) | (i09 >>> 31)); // W41
c += ((d << 5) | (d >>> 27)) + 0x8f1bbcdc // K42
+ ((e & (a = (a << 30) | (a >>> 2))) | ((e | a) & b)) // Maj(e,a,b)
+ (i10 = ((i10 ^= i12 ^ i02 ^ i07) << 1) | (i10 >>> 31)); // W42
b += ((c << 5) | (c >>> 27)) + 0x8f1bbcdc // K43
+ ((d & (e = (e << 30) | (e >>> 2))) | ((d | e) & a)) // Maj(d,e,a)
+ (i11 = ((i11 ^= i13 ^ i03 ^ i08) << 1) | (i11 >>> 31)); // W43
a += ((b << 5) | (b >>> 27)) + 0x8f1bbcdc // K44
+ ((c & (d = (d << 30) | (d >>> 2))) | ((c | d) & e)) // Maj(c,d,e)
+ (i12 = ((i12 ^= i14 ^ i04 ^ i09) << 1) | (i12 >>> 31)); // W44
e += ((a << 5) | (a >>> 27)) + 0x8f1bbcdc // K45
+ ((b & (c = (c << 30) | (c >>> 2))) | ((b | c) & d)) // Maj(b,c,d)
+ (i13 = ((i13 ^= i15 ^ i05 ^ i10) << 1) | (i13 >>> 31)); // W45
d += ((e << 5) | (e >>> 27)) + 0x8f1bbcdc // K46
+ ((a & (b = (b << 30) | (b >>> 2))) | ((a | b) & c)) // Maj(a,b,c)
+ (i14 = ((i14 ^= i00 ^ i06 ^ i11) << 1) | (i14 >>> 31)); // W46
c += ((d << 5) | (d >>> 27)) + 0x8f1bbcdc // K47
+ ((e & (a = (a << 30) | (a >>> 2))) | ((e | a) & b)) // Maj(e,a,b)
+ (i15 = ((i15 ^= i01 ^ i07 ^ i12) << 1) | (i15 >>> 31)); // W47
/* Fourth pass, on scheduled input (rounds 48..63). */
b += ((c << 5) | (c >>> 27)) + 0x8f1bbcdc // K48
+ ((d & (e = (e << 30) | (e >>> 2))) | ((d | e) & a)) // Maj(d,e,a)
+ (i00 = ((i00 ^= i02 ^ i08 ^ i13) << 1) | (i00 >>> 31)); // W48
a += ((b << 5) | (b >>> 27)) + 0x8f1bbcdc // K49
+ ((c & (d = (d << 30) | (d >>> 2))) | ((c | d) & e)) // Maj(c,d,e)
+ (i01 = ((i01 ^= i03 ^ i09 ^ i14) << 1) | (i01 >>> 31)); // W49
e += ((a << 5) | (a >>> 27)) + 0x8f1bbcdc // K50
+ ((b & (c = (c << 30) | (c >>> 2))) | ((b | c) & d)) // Maj(b,c,d)
+ (i02 = ((i02 ^= i04 ^ i10 ^ i15) << 1) | (i02 >>> 31)); // W50
d += ((e << 5) | (e >>> 27)) + 0x8f1bbcdc // K51
+ ((a & (b = (b << 30) | (b >>> 2))) | ((a | b) & c)) // Maj(a,b,c)
+ (i03 = ((i03 ^= i05 ^ i11 ^ i00) << 1) | (i03 >>> 31)); // W51
c += ((d << 5) | (d >>> 27)) + 0x8f1bbcdc // K52
+ ((e & (a = (a << 30) | (a >>> 2))) | ((e | a) & b)) // Maj(e,a,b)
+ (i04 = ((i04 ^= i06 ^ i12 ^ i01) << 1) | (i04 >>> 31)); // W52
b += ((c << 5) | (c >>> 27)) + 0x8f1bbcdc // K53
+ ((d & (e = (e << 30) | (e >>> 2))) | ((d | e) & a)) // Maj(d,e,a)
+ (i05 = ((i05 ^= i07 ^ i13 ^ i02) << 1) | (i05 >>> 31)); // W53
a += ((b << 5) | (b >>> 27)) + 0x8f1bbcdc // K54
+ ((c & (d = (d << 30) | (d >>> 2))) | ((c | d) & e)) // Maj(c,d,e)
+ (i06 = ((i06 ^= i08 ^ i14 ^ i03) << 1) | (i06 >>> 31)); // W54
e += ((a << 5) | (a >>> 27)) + 0x8f1bbcdc // K55
+ ((b & (c = (c << 30) | (c >>> 2))) | ((b | c) & d)) // Maj(b,c,d)
+ (i07 = ((i07 ^= i09 ^ i15 ^ i04) << 1) | (i07 >>> 31)); // W55
d += ((e << 5) | (e >>> 27)) + 0x8f1bbcdc // K56
+ ((a & (b = (b << 30) | (b >>> 2))) | ((a | b) & c)) // Maj(a,b,c)
+ (i08 = ((i08 ^= i10 ^ i00 ^ i05) << 1) | (i08 >>> 31)); // W56
c += ((d << 5) | (d >>> 27)) + 0x8f1bbcdc // K57
+ ((e & (a = (a << 30) | (a >>> 2))) | ((e | a) & b)) // Maj(e,a,b)
+ (i09 = ((i09 ^= i11 ^ i01 ^ i06) << 1) | (i09 >>> 31)); // W57
b += ((c << 5) | (c >>> 27)) + 0x8f1bbcdc // K58
+ ((d & (e = (e << 30) | (e >>> 2))) | ((d | e) & a)) // Maj(d,e,a)
+ (i10 = ((i10 ^= i12 ^ i02 ^ i07) << 1) | (i10 >>> 31)); // W58
a += ((b << 5) | (b >>> 27)) + 0x8f1bbcdc // K59
+ ((c & (d = (d << 30) | (d >>> 2))) | ((c | d) & e)) // Maj(c,d,e)
+ (i11 = ((i11 ^= i13 ^ i03 ^ i08) << 1) | (i11 >>> 31)); // W59
/* Use hash schedule function Parity (rounds 60..79):
* Parity(x,y,z) = x ^ y ^ z,
* and K60 = .... = K79 = 0xca62c1d6. */
e += ((a << 5) | (a >>> 27)) + 0xca62c1d6 // K60
+ (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d)
+ (i12 = ((i12 ^= i14 ^ i04 ^ i09) << 1) | (i12 >>> 31)); // W60
d += ((e << 5) | (e >>> 27)) + 0xca62c1d6 // K61
+ (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c)
+ (i13 = ((i13 ^= i15 ^ i05 ^ i10) << 1) | (i13 >>> 31)); // W61
c += ((d << 5) | (d >>> 27)) + 0xca62c1d6 // K62
+ (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b)
+ (i14 = ((i14 ^= i00 ^ i06 ^ i11) << 1) | (i14 >>> 31)); // W62
b += ((c << 5) | (c >>> 27)) + 0xca62c1d6 // K63
+ (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a)
+ (i15 = ((i15 ^= i01 ^ i07 ^ i12) << 1) | (i15 >>> 31)); // W63
/* Fifth pass, on scheduled input (rounds 64..79). */
a += ((b << 5) | (b >>> 27)) + 0xca62c1d6 // K64
+ (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e)
+ (i00 = ((i00 ^= i02 ^ i08 ^ i13) << 1) | (i00 >>> 31)); // W64
e += ((a << 5) | (a >>> 27)) + 0xca62c1d6 // K65
+ (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d)
+ (i01 = ((i01 ^= i03 ^ i09 ^ i14) << 1) | (i01 >>> 31)); // W65
d += ((e << 5) | (e >>> 27)) + 0xca62c1d6 // K66
+ (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c)
+ (i02 = ((i02 ^= i04 ^ i10 ^ i15) << 1) | (i02 >>> 31)); // W66
c += ((d << 5) | (d >>> 27)) + 0xca62c1d6 // K67
+ (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b)
+ (i03 = ((i03 ^= i05 ^ i11 ^ i00) << 1) | (i03 >>> 31)); // W67
b += ((c << 5) | (c >>> 27)) + 0xca62c1d6 // K68
+ (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a)
+ (i04 = ((i04 ^= i06 ^ i12 ^ i01) << 1) | (i04 >>> 31)); // W68
a += ((b << 5) | (b >>> 27)) + 0xca62c1d6 // K69
+ (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e)
+ (i05 = ((i05 ^= i07 ^ i13 ^ i02) << 1) | (i05 >>> 31)); // W69
e += ((a << 5) | (a >>> 27)) + 0xca62c1d6 // K70
+ (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d)
+ (i06 = ((i06 ^= i08 ^ i14 ^ i03) << 1) | (i06 >>> 31)); // W70
d += ((e << 5) | (e >>> 27)) + 0xca62c1d6 // K71
+ (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c)
+ (i07 = ((i07 ^= i09 ^ i15 ^ i04) << 1) | (i07 >>> 31)); // W71
c += ((d << 5) | (d >>> 27)) + 0xca62c1d6 // K72
+ (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b)
+ (i08 = ((i08 ^= i10 ^ i00 ^ i05) << 1) | (i08 >>> 31)); // W72
b += ((c << 5) | (c >>> 27)) + 0xca62c1d6 // K73
+ (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a)
+ (i09 = ((i09 ^= i11 ^ i01 ^ i06) << 1) | (i09 >>> 31)); // W73
a += ((b << 5) | (b >>> 27)) + 0xca62c1d6 // K74
+ (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e)
+ (i10 = ((i10 ^= i12 ^ i02 ^ i07) << 1) | (i10 >>> 31)); // W74
e += ((a << 5) | (a >>> 27)) + 0xca62c1d6 // K75
+ (b ^ (c = (c << 30) | (c >>> 2)) ^ d) // Parity(b,c,d)
+ (i11 = ((i11 ^= i13 ^ i03 ^ i08) << 1) | (i11 >>> 31)); // W75
d += ((e << 5) | (e >>> 27)) + 0xca62c1d6 // K76
+ (a ^ (b = (b << 30) | (b >>> 2)) ^ c) // Parity(a,b,c)
+ (i12 = ((i12 ^= i14 ^ i04 ^ i09) << 1) | (i12 >>> 31)); // W76
c += ((d << 5) | (d >>> 27)) + 0xca62c1d6 // K77
+ (e ^ (a = (a << 30) | (a >>> 2)) ^ b) // Parity(e,a,b)
+ (i13 = ((i13 ^= i15 ^ i05 ^ i10) << 1) | (i13 >>> 31)); // W77
/* Terminate the last two rounds of fifth pass,
* feeding the final digest on the fly. */
hB +=
b += ((c << 5) | (c >>> 27)) + 0xca62c1d6 // K78
+ (d ^ (e = (e << 30) | (e >>> 2)) ^ a) // Parity(d,e,a)
+ (i14 = ((i14 ^= i00 ^ i06 ^ i11) << 1) | (i14 >>> 31)); // W78
hA +=
a += ((b << 5) | (b >>> 27)) + 0xca62c1d6 // K79
+ (c ^ (d = (d << 30) | (d >>> 2)) ^ e) // Parity(c,d,e)
+ (i15 = ((i15 ^= i01 ^ i07 ^ i12) << 1) | (i15 >>> 31)); // W79
hE += e;
hD += d;
hC += /* c= */ (c << 30) | (c >>> 2);
}
}

View File

@@ -0,0 +1,191 @@
package net.i2p.crypto;
/* @(#)SHA1Test.java 1.10 2004-04-24
* This file was freely contributed to the LimeWire project and is covered
* by its existing GPL licence, but it may be used individually as a public
* domain implementation of a published algorithm (see below for references).
* It was also freely contributed to the Bitzi public domain sources.
* @author Philippe Verdy
*/
/* Sun may wish to change the following package name, if integrating this
* class in the Sun JCE Security Provider for Java 1.5 (code-named Tiger).
*/
//package com.bitzi.util;
import java.security.*;
public class SHA1Test {
private static final SHA1 hash = new SHA1();
public static void main(String args[]) {
// http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
System.out.println("****************************************");
System.out.println("* Basic FIPS PUB 180-1 test vectors... *");
System.out.println("****************************************");
tst(1, 1,
"abc",
"A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D");
tst(1, 2,
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"84983E44 1C3BD26e BAAE4AA1 F95129E5 E54670F1");
tst(1, 3, /* one million bytes */
1000000, "a",
"34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F");
System.out.println();
// http://csrc.ncsl.nist.gov/cryptval/shs/SHAVS.pdf
System.out.println("********************************************************");
System.out.println("* SHSV Examples of the selected short messages test... *");
System.out.println("********************************************************");
tst(2, 2, new byte[] {/* 8 bits, i.e. 1 byte */
(byte)0x5e},
"5e6f80a3 4a9798ca fc6a5db9 6cc57ba4 c4db59c2");
tst(2, 4, new byte[] {/* 128 bits, i.e. 16 bytes */
(byte)0x9a,(byte)0x7d,(byte)0xfd,(byte)0xf1,(byte)0xec,(byte)0xea,(byte)0xd0,(byte)0x6e,
(byte)0xd6,(byte)0x46,(byte)0xaa,(byte)0x55,(byte)0xfe,(byte)0x75,(byte)0x71,(byte)0x46},
"82abff66 05dbe1c1 7def12a3 94fa22a8 2b544a35");
System.out.println();
System.out.println("*******************************************************");
System.out.println("* SHSV Examples of the selected long messages test... *");
System.out.println("*******************************************************");
tst(3, 2, new byte[] {/* 1304 bits, i.e. 163 bytes */
(byte)0xf7,(byte)0x8f,(byte)0x92,(byte)0x14,(byte)0x1b,(byte)0xcd,(byte)0x17,(byte)0x0a,
(byte)0xe8,(byte)0x9b,(byte)0x4f,(byte)0xba,(byte)0x15,(byte)0xa1,(byte)0xd5,(byte)0x9f,
(byte)0x3f,(byte)0xd8,(byte)0x4d,(byte)0x22,(byte)0x3c,(byte)0x92,(byte)0x51,(byte)0xbd,
(byte)0xac,(byte)0xbb,(byte)0xae,(byte)0x61,(byte)0xd0,(byte)0x5e,(byte)0xd1,(byte)0x15,
(byte)0xa0,(byte)0x6a,(byte)0x7c,(byte)0xe1,(byte)0x17,(byte)0xb7,(byte)0xbe,(byte)0xea,
(byte)0xd2,(byte)0x44,(byte)0x21,(byte)0xde,(byte)0xd9,(byte)0xc3,(byte)0x25,(byte)0x92,
(byte)0xbd,(byte)0x57,(byte)0xed,(byte)0xea,(byte)0xe3,(byte)0x9c,(byte)0x39,(byte)0xfa,
(byte)0x1f,(byte)0xe8,(byte)0x94,(byte)0x6a,(byte)0x84,(byte)0xd0,(byte)0xcf,(byte)0x1f,
(byte)0x7b,(byte)0xee,(byte)0xad,(byte)0x17,(byte)0x13,(byte)0xe2,(byte)0xe0,(byte)0x95,
(byte)0x98,(byte)0x97,(byte)0x34,(byte)0x7f,(byte)0x67,(byte)0xc8,(byte)0x0b,(byte)0x04,
(byte)0x00,(byte)0xc2,(byte)0x09,(byte)0x81,(byte)0x5d,(byte)0x6b,(byte)0x10,(byte)0xa6,
(byte)0x83,(byte)0x83,(byte)0x6f,(byte)0xd5,(byte)0x56,(byte)0x2a,(byte)0x56,(byte)0xca,
(byte)0xb1,(byte)0xa2,(byte)0x8e,(byte)0x81,(byte)0xb6,(byte)0x57,(byte)0x66,(byte)0x54,
(byte)0x63,(byte)0x1c,(byte)0xf1,(byte)0x65,(byte)0x66,(byte)0xb8,(byte)0x6e,(byte)0x3b,
(byte)0x33,(byte)0xa1,(byte)0x08,(byte)0xb0,(byte)0x53,(byte)0x07,(byte)0xc0,(byte)0x0a,
(byte)0xff,(byte)0x14,(byte)0xa7,(byte)0x68,(byte)0xed,(byte)0x73,(byte)0x50,(byte)0x60,
(byte)0x6a,(byte)0x0f,(byte)0x85,(byte)0xe6,(byte)0xa9,(byte)0x1d,(byte)0x39,(byte)0x6f,
(byte)0x5b,(byte)0x5c,(byte)0xbe,(byte)0x57,(byte)0x7f,(byte)0x9b,(byte)0x38,(byte)0x80,
(byte)0x7c,(byte)0x7d,(byte)0x52,(byte)0x3d,(byte)0x6d,(byte)0x79,(byte)0x2f,(byte)0x6e,
(byte)0xbc,(byte)0x24,(byte)0xa4,(byte)0xec,(byte)0xf2,(byte)0xb3,(byte)0xa4,(byte)0x27,
(byte)0xcd,(byte)0xbb,(byte)0xfb},
"cb0082c8 f197d260 991ba6a4 60e76e20 2bad27b3");
System.out.println();
// See also http://csrc.ncsl.nist.gov/cryptval/shs/sha1-vectors.zip
{
final int RETRIES = 10;
final int ITERATIONS = 2000;
final int BLOCKSIZE = 65536;
byte[] input = new byte[BLOCKSIZE];
for (int i = BLOCKSIZE; --i >= 0; )
input[i] = (byte)i;
long best = 0;
for (int i = 0; i < 1000; i++) // training for stable measure
System.currentTimeMillis();
for (int retry = 0; retry < RETRIES; retry++) {
long t0 = System.currentTimeMillis();
for (int i = ITERATIONS; --i >= 0; );
long t1 = System.currentTimeMillis();
for (int i = ITERATIONS; --i >= 0; )
hash.engineUpdate(input, 0, BLOCKSIZE);
long t2 = System.currentTimeMillis();
long time = (t2 - t1) - (t1 - t0);
if (retry == 0 || time < best)
best = time;
}
hash.engineReset();
double rate = 1000.0 * ITERATIONS * BLOCKSIZE / best;
System.out.println("Our rate = " +
(float)(rate * 8) + " bits/s = " +
(float)(rate / (1024 * 1024)) + " Megabytes/s");
// Java 1.5 beta-b32c, on Athlon XP 1800+:
// with java -client: 48.21 Megabytes/s.
// with java -server: 68.23 Megabytes/s.
try {
MessageDigest md = MessageDigest.getInstance("SHA");
for (int retry = 0; retry < RETRIES; retry++) {
long t0 = System.currentTimeMillis();
for (int i = ITERATIONS; --i >= 0; );
long t1 = System.currentTimeMillis();
for (int i = ITERATIONS; --i >= 0; )
md.update(input, 0, BLOCKSIZE);
long t2 = System.currentTimeMillis();
long time = (t2 - t1) - (t1 - t0);
if (retry == 0 || time < best)
best = time;
}
md.reset();
rate = 1000.0 * ITERATIONS * BLOCKSIZE / best;
System.out.println("JCE rate = " +
(float)(rate * 8) + " bits/s = " +
(float)(rate / (1024 * 1024)) + " Megabytes/s");
} catch (NoSuchAlgorithmException nsae) {
System.out.println("No SHA algorithm in local JCE Security Providers");
}
// Java 1.5 beta-b32c, on Athlon XP 1800+:
// with java -client: 23.20 Megabytes/s.
// with java -server: 45.72 Megabytes/s.
}
}
private static final boolean tst(final int set, final int vector,
final String source,
final String expect) {
byte[] input = new byte[source.length()];
for (int i = 0; i < input.length; i++)
input[i] = (byte)source.charAt(i);
return tst(set, vector, input, expect);
}
private static final boolean tst(final int set, final int vector,
final byte[] input,
final String expect) {
System.out.print("Set " + set + ", vector# " + vector + ": ");
hash.engineUpdate(input, 0, input.length);
return tstResult(expect);
}
private static final boolean tst(final int set, final int vector,
final int times, final String source,
final String expect) {
byte[] input = new byte[source.length()];
for (int i = 0; i < input.length; i++)
input[i] = (byte)source.charAt(i);
System.out.print("Set " + set + ", vector# " + vector + ": ");
for (int i = 0; i < times; i++)
hash.engineUpdate(input, 0, input.length);
return tstResult(expect);
}
private static final boolean tstResult(String expect) {
final String result = toHex(hash.engineDigest());
expect = expect.toUpperCase();
if (!expect.equals(result)) {
System.out.println("**************** WRONG ***************");
System.out.println(" expect: " + expect);
System.out.println(" result: " + result);
return false;
}
System.out.println("OK");
return true;
}
private static final String toHex(final byte[] bytes) {
StringBuffer buf = new StringBuffer(bytes.length * 2);
for (int i = 0; i < bytes.length; i++) {
if ((i & 3) == 0 && i != 0)
buf.append(' ');
buf.append(HEX.charAt((bytes[i] >> 4) & 0xF))
.append(HEX.charAt( bytes[i] & 0xF));
}
return buf.toString();
}
private static final String HEX = "0123456789ABCDEF";
}

View File

@@ -0,0 +1,380 @@
package net.i2p.crypto;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.util.Log;
/**
* Handles DSA signing and verification of I2P update archives.
*
* @author smeghead
*/
public class TrustedUpdate {
/**
* default trusted key, generated by jrandom. This can be authenticated
* via gpg without modification (gpg --verify TrustedUpdate.java)
*
*/
/*
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
*/
private static final String DEFAULT_TRUSTED_KEY =
"W4kJbnv9KSVwbnapV7SaNW2kMIZKs~hwL0ro9pZXFo1xTwqz45nykCp1H" +
"M7sAKYDZay5z1HvYYOl9CNVz00xF03KPU9RUCVxhDZ1YXhZIskPKjUPUs" +
"CIpE~Z1C~N9KSEV6~2stDlBNH10VZ4T0X1TrcXwb3IBXliWo2y2GAx~Ow=";
/*
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.4 (GNU/Linux)
iD8DBQFCQXoJGnFL2th344YRAtsIAKCUy/sOIsxahUnT2hKLXFL9lXsAmACfUHa5
CPah6TDXYJCWmR0n3oPtrvo=
=mD0t
-----END PGP SIGNATURE-----
*/
private ArrayList _trustedKeys;
private I2PAppContext _context;
private Log _log;
private static final int VERSION_BYTES = 16;
private static final int HEADER_BYTES = VERSION_BYTES + Signature.SIGNATURE_BYTES;
public static final String PROP_TRUSTED_KEYS = "router.trustedUpdateKeys";
public TrustedUpdate() {
this(I2PAppContext.getGlobalContext());
}
public TrustedUpdate(I2PAppContext ctx) {
_context = ctx;
_log = _context.logManager().getLog(TrustedUpdate.class);
_trustedKeys = new ArrayList(1);
String keys = ctx.getProperty(PROP_TRUSTED_KEYS);
if ( (keys != null) && (keys.length() > 0) ) {
StringTokenizer tok = new StringTokenizer(keys, ", ");
while (tok.hasMoreTokens())
_trustedKeys.add(tok.nextToken());
} else {
_trustedKeys.add(DEFAULT_TRUSTED_KEY);
}
}
public ArrayList getTrustedKeys() { return _trustedKeys; }
public static void main(String[] args) {
if (args.length <= 0) {
usage();
} else if ("keygen".equals(args[0])) {
genKeysCLI(args[1], args[2]);
} else if ("sign".equals(args[0])) {
signCLI(args[1], args[2], args[3], args[4]);
} else if ("verify".equals(args[0])) {
verifyCLI(args[1]);
} else {
usage();
}
}
private static final void usage() {
System.err.println("Usage: TrustedUpdate keygen publicKeyFile privateKeyFile");
System.err.println(" TrustedUpdate sign origFile signedFile privateKeyFile version");
System.err.println(" TrustedUpdate verify signedFile");
}
private static final void genKeysCLI(String publicKeyFile, String privateKeyFile) {
FileOutputStream out = null;
try {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
Object keys[] = ctx.keyGenerator().generateSigningKeypair();
SigningPublicKey pub = (SigningPublicKey)keys[0];
SigningPrivateKey priv = (SigningPrivateKey)keys[1];
out = new FileOutputStream(publicKeyFile);
pub.writeBytes(out);
out.close();
out = new FileOutputStream(privateKeyFile);
priv.writeBytes(out);
out.close();
out = null;
System.out.println("Private keys writen to " + privateKeyFile + " and public to " + publicKeyFile);
System.out.println("Public: " + pub.toBase64());
} catch (Exception e) {
e.printStackTrace();
System.err.println("Error writing out the keys");
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
}
private static final void signCLI(String origFile, String outFile, String privKeyFile, String version) {
TrustedUpdate up = new TrustedUpdate();
Signature sig = up.sign(origFile, outFile, privKeyFile, version);
if (sig != null)
System.out.println("Signed and written to " + outFile);
else
System.out.println("Error signing");
}
private static final void verifyCLI(String signedFile) {
TrustedUpdate up = new TrustedUpdate();
boolean ok = up.verify(signedFile);
if (ok)
System.out.println("Signature VALID");
else
System.out.println("Signature INVALID");
}
/**
* Reads the version string from a signed I2P update file.
*
* @param inputFile A signed I2P update file.
*
* @return The update version string read, or an empty string if no version
* string is present.
*/
public String getUpdateVersion(String inputFile) {
FileInputStream in = null;
try {
in = new FileInputStream(inputFile);
byte data[] = new byte[VERSION_BYTES];
int read = DataHelper.read(in, data);
if (read != VERSION_BYTES)
return null;
for (int i = 0; i < VERSION_BYTES; i++)
if (data[i] == 0x00)
return new String(data, 0, i, "UTF-8");
return new String(data, "UTF-8");
} catch (UnsupportedEncodingException uee) {
// If this ever gets called, you need a new JVM.
throw new RuntimeException("wtf, your JVM doesnt support utf-8? " + uee.getMessage());
} catch (IOException ioe) {
return "";
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
}
/**
* Uses the given private key to sign the given input file with DSA. The
* output will be a binary file where the first 16 bytes are the I2P
* update's version string encoded in UTF-8 (padded with trailing
* <code>0h</code> characters if necessary), the next 40 bytes are the
* resulting DSA signature, and the remaining bytes are the input file.
*
* @param inputFile The file to be signed.
* @param outputFile The signed file to write.
* @param privateKeyFile The name of the file containing the private key to
* sign <code>inputFile</code> with.
* @param updateVersion The version number of the I2P update. If this
* string is longer than 16 characters it will be
* truncated.
*
* @return An instance of {@link net.i2p.data.Signature}, or null if there was an error
*/
public Signature sign(String inputFile, String outputFile, String privateKeyFile, String updateVersion) {
SigningPrivateKey key = new SigningPrivateKey();
FileInputStream in = null;
try {
in = new FileInputStream(privateKeyFile);
key.readBytes(in);
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to load the signing key", ioe);
return null;
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to load the signing key", dfe);
return null;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
return sign(inputFile, outputFile, key, updateVersion);
}
public Signature sign(String inputFile, String outputFile, SigningPrivateKey privKey, String updateVersion) {
byte[] headerUpdateVersion = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
byte[] updateVersionBytes = null;
if (updateVersion.length() > VERSION_BYTES)
updateVersion = updateVersion.substring(0, VERSION_BYTES);
try {
updateVersionBytes = updateVersion.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
// If this ever gets called, you need a new JVM.
throw new RuntimeException("wtf, your JVM doesnt support utf-8? " + e.getMessage());
}
System.arraycopy(updateVersionBytes, 0, headerUpdateVersion, 0, updateVersionBytes.length);
Signature signature = null;
FileInputStream in = null;
try {
in = new FileInputStream(inputFile);
signature = _context.dsa().sign(in, privKey);
} catch (Exception e) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error signing", e);
return null;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
in = null;
}
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(outputFile);
fileOutputStream.write(headerUpdateVersion);
fileOutputStream.write(signature.getData());
in = new FileInputStream(inputFile);
byte buf[] = new byte[1024];
int read = 0;
while ( (read = in.read(buf)) != -1)
fileOutputStream.write(buf, 0, read);
fileOutputStream.close();
fileOutputStream = null;
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.log(Log.WARN, "Error writing signed I2P update file " + outputFile, ioe);
return null;
} finally {
if (fileOutputStream != null) try { fileOutputStream.close(); } catch (IOException ioe) {}
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
return signature;
}
/**
* Verifies the DSA signature of a signed I2P update.
*
* @param inputFile The signed update file to check.
*
* @return <code>true</code> if the file has a valid signature.
*/
public boolean verify(String inputFile) {
for (int i = 0; i < _trustedKeys.size(); i++) {
SigningPublicKey key = new SigningPublicKey();
try {
key.fromBase64((String)_trustedKeys.get(i));
boolean ok = verify(inputFile, key);
if (ok) return true;
} catch (DataFormatException dfe) {
_log.log(Log.CRIT, "Trusted key " + i + " is not valid");
}
}
if (_log.shouldLog(Log.WARN))
_log.warn("None of the keys match");
return false;
}
/**
* Verifies the DSA signature of a signed I2P update.
*
* @param inputFile The signed update file to check.
* @param key public key to verify against
*
* @return <code>true</code> if the file has a valid signature.
*/
public boolean verify(String inputFile, SigningPublicKey key) {
FileInputStream in = null;
try {
in = new FileInputStream(inputFile);
byte version[] = new byte[VERSION_BYTES];
Signature sig = new Signature();
if (VERSION_BYTES != DataHelper.read(in, version))
throw new IOException("Not enough data for the version bytes");
sig.readBytes(in);
return _context.dsa().verifySignature(sig, in, key);
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error reading " + inputFile + " to verify", ioe);
return false;
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error reading the signature", dfe);
return false;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
}
/**
* Verifies the DSA signature of a signed I2P update.
*
* @param inputFile The signed update file to check.
* @param publicKeyFile The public key to use for verification.
*
* @return <code>true</code> if the file has a valid signature.
*/
public boolean verify(String inputFile, String publicKeyFile) {
SigningPublicKey pub = new SigningPublicKey();
FileInputStream in = null;
try {
in = new FileInputStream(inputFile);
pub.readBytes(in);
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to load the signature", ioe);
return false;
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to load the signature", dfe);
return false;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
return verify(inputFile, pub);
}
/**
* Verify the signature on the signed inputFile, and if it is valid, migrate
* the raw data out of it and into the outputFile
*
* @return true if the signature was valid and the data moved, false otherwise.
*/
public boolean migrateVerified(String inputFile, String outputFile) {
boolean ok = verify(inputFile);
if (!ok) return false;
FileOutputStream out = null;
FileInputStream in = null;
try {
out = new FileOutputStream(outputFile);
in = new FileInputStream(inputFile);
long skipped = 0;
while (skipped < HEADER_BYTES) {
skipped += in.skip(HEADER_BYTES - skipped);
}
byte buf[] = new byte[1024];
int read = 0;
while ( (read = in.read(buf)) != -1)
out.write(buf, 0, read);
} catch (IOException ioe) {
return false;
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
return true;
}
}

View File

@@ -42,6 +42,10 @@ public class Base64 {
private final static Log _log = new Log(Base64.class);
/** added by aum */
public static String encode(String source) {
return encode(source.getBytes());
}
public static String encode(byte[] source) {
return encode(source, 0, (source != null ? source.length : 0));
}
@@ -325,6 +329,8 @@ public class Base64 {
* replacing / with ~, and + with -
*/
private static String safeEncode(byte[] source, int off, int len, boolean useStandardAlphabet) {
if (len + off > source.length)
throw new ArrayIndexOutOfBoundsException("Trying to encode too much! source.len=" + source.length + " off=" + off + " len=" + len);
String encoded = encodeBytes(source, off, len, false);
if (useStandardAlphabet) {
// noop
@@ -564,7 +570,7 @@ public class Base64 {
* @return The data as a string
* @since 1.4
*/
private static String decodeToString(String s) {
public static String decodeToString(String s) {
return new String(decode(s));
} // end decodeToString

View File

@@ -35,6 +35,15 @@ public class Destination extends DataStructureImpl {
__calculatedHash = null;
}
/**
* alternative constructor which takes a base64 string representation
* @param s a Base64 representation of the destination, as (eg) is used in hosts.txt
*/
public Destination(String s) throws DataFormatException {
this();
fromBase64(s);
}
public Certificate getCertificate() {
return _certificate;
}

View File

@@ -35,6 +35,7 @@ public class LeaseSet extends DataStructureImpl {
private Signature _signature;
private volatile Hash _currentRoutingKey;
private volatile byte[] _routingKeyGenMod;
private boolean _receivedAsPublished;
/** um, no lease can last more than a year. */
private final static long MAX_FUTURE_EXPIRATION = 365 * 24 * 60 * 60 * 1000L;
@@ -47,6 +48,7 @@ public class LeaseSet extends DataStructureImpl {
setRoutingKey(null);
_leases = new ArrayList();
_routingKeyGenMod = null;
_receivedAsPublished = false;
}
public Destination getDestination() {
@@ -72,6 +74,14 @@ public class LeaseSet extends DataStructureImpl {
public void setSigningKey(SigningPublicKey key) {
_signingKey = key;
}
/**
* If true, we received this LeaseSet by a remote peer publishing it to
* us, rather than by searching for it ourselves or locally creating it.
*
*/
public boolean getReceivedAsPublished() { return _receivedAsPublished; }
public void setReceivedAsPublished(boolean received) { _receivedAsPublished = received; }
public void addLease(Lease lease) {
if (lease == null) throw new IllegalArgumentException("erm, null lease");

View File

@@ -158,21 +158,18 @@ public class Timestamper implements Runnable {
*/
private boolean queryTime(String serverList[]) throws IllegalArgumentException {
long found[] = new long[_concurringServers];
long localTime = -1;
long now = -1;
long expectedDelta = 0;
for (int i = 0; i < _concurringServers; i++) {
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
localTime = _context.clock().now();
now = NtpClient.currentTime(serverList);
long delta = now - localTime;
long delta = now - _context.clock().now();
found[i] = delta;
if (i == 0) {
if (Math.abs(delta) < MAX_VARIANCE) {
if (_log.shouldLog(Log.INFO))
_log.info("a single SNTP query was within the tolerance (" + delta + "ms)");
return true;
break;
} else {
// outside the tolerance, lets iterate across the concurring queries
expectedDelta = delta;

View File

@@ -0,0 +1,554 @@
package net.i2p.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
/**
* EepGet [-p localhost:4444]
* [-n #retries]
* [-o outputFile]
* [-m markSize lineLen]
* url
*/
public class EepGet {
private I2PAppContext _context;
private Log _log;
private boolean _shouldProxy;
private String _proxyHost;
private int _proxyPort;
private int _numRetries;
private String _outputFile;
private String _url;
private List _listeners;
private boolean _keepFetching;
private Socket _proxy;
private OutputStream _proxyOut;
private InputStream _proxyIn;
private OutputStream _out;
private long _alreadyTransferred;
private long _bytesTransferred;
private long _bytesRemaining;
private int _currentAttempt;
private String _etag;
private boolean _encodingChunked;
public EepGet(I2PAppContext ctx, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
this(ctx, true, proxyHost, proxyPort, numRetries, outputFile, url);
}
public EepGet(I2PAppContext ctx, int numRetries, String outputFile, String url) {
this(ctx, false, null, -1, numRetries, outputFile, url);
}
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
_context = ctx;
_log = ctx.logManager().getLog(EepGet.class);
_shouldProxy = shouldProxy;
_proxyHost = proxyHost;
_proxyPort = proxyPort;
_numRetries = numRetries;
_outputFile = outputFile;
_url = url;
_alreadyTransferred = 0;
_bytesTransferred = 0;
_bytesRemaining = -1;
_currentAttempt = 0;
_listeners = new ArrayList(1);
}
/**
* EepGet [-p localhost:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] url
*
*/
public static void main(String args[]) {
String proxyHost = "localhost";
int proxyPort = 4444;
int numRetries = 5;
int markSize = 1024;
int lineLen = 40;
String saveAs = null;
String url = null;
try {
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-p")) {
proxyHost = args[i+1].substring(0, args[i+1].indexOf(':'));
String port = args[i+1].substring(args[i+1].indexOf(':')+1);
proxyPort = Integer.parseInt(port);
i++;
} else if (args[i].equals("-n")) {
numRetries = Integer.parseInt(args[i+1]);
i++;
} else if (args[i].equals("-o")) {
saveAs = args[i+1];
i++;
} else if (args[i].equals("-m")) {
markSize = Integer.parseInt(args[i+1]);
lineLen = Integer.parseInt(args[i+2]);
i += 2;
} else {
url = args[i];
}
}
} catch (Exception e) {
e.printStackTrace();
usage();
return;
}
if (url == null) {
usage();
return;
}
if (saveAs == null)
saveAs = suggestName(url);
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), proxyHost, proxyPort, numRetries, saveAs, url);
get.addStatusListener(get.new CLIStatusListener(markSize, lineLen));
get.fetch();
}
public static String suggestName(String url) {
String name = null;
if (url.lastIndexOf('/') >= 0)
name = sanitize(url.substring(url.lastIndexOf('/')+1));
if (name != null)
return name;
else
return sanitize(url);
}
private static final String _safeChars = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"01234567890.,_=@#:";
private static String sanitize(String name) {
name = name.replace('/', '_');
StringBuffer buf = new StringBuffer(name);
for (int i = 0; i < name.length(); i++)
if (_safeChars.indexOf(buf.charAt(i)) == -1)
buf.setCharAt(i, '_');
return buf.toString();
}
private static void usage() {
System.err.println("EepGet [-p localhost:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] url");
}
public static interface StatusListener {
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url);
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile);
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause);
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt);
}
private class CLIStatusListener implements StatusListener {
private int _markSize;
private int _lineSize;
private long _startedOn;
private long _written;
private long _lastComplete;
private DecimalFormat _pct = new DecimalFormat("00.0%");
private DecimalFormat _kbps = new DecimalFormat("###,000.00");
public CLIStatusListener() {
this(1024, 40);
}
public CLIStatusListener(int markSize, int lineSize) {
_markSize = markSize;
_lineSize = lineSize;
_written = 0;
_lastComplete = _context.clock().now();
_startedOn = _lastComplete;
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
for (int i = 0; i < currentWrite; i++) {
_written++;
if ( (_markSize > 0) && (_written % _markSize == 0) ) {
System.out.print("#");
if ( (_lineSize > 0) && (_written % ((long)_markSize*(long)_lineSize) == 0l) ) {
long now = _context.clock().now();
long timeToSend = now - _lastComplete;
if (timeToSend > 0) {
StringBuffer buf = new StringBuffer(50);
buf.append(" ");
double pct = ((double)alreadyTransferred + (double)_written) / ((double)alreadyTransferred + (double)bytesRemaining);
synchronized (_pct) {
buf.append(_pct.format(pct));
}
buf.append(": ");
buf.append(_written+alreadyTransferred);
buf.append(" @ ");
double lineKBytes = ((double)_markSize * (double)_lineSize)/1024.0d;
double kbps = lineKBytes/((double)timeToSend/1000.0d);
synchronized (_kbps) {
buf.append(_kbps.format(kbps));
}
buf.append("KBps");
buf.append(" / ");
long lifetime = _context.clock().now() - _startedOn;
double lifetimeKBps = (1000.0d*(double)(_written+alreadyTransferred)/((double)lifetime*1024.0d));
synchronized (_kbps) {
buf.append(_kbps.format(lifetimeKBps));
}
buf.append("KBps");
System.out.println(buf.toString());
}
_lastComplete = now;
}
}
}
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
System.out.println();
System.out.println("== " + new Date());
System.out.println("== Transfer of " + url + " completed with " + (alreadyTransferred+bytesTransferred)
+ " and " + (bytesRemaining - bytesTransferred) + " remaining");
System.out.println("== Output saved to " + outputFile);
long timeToSend = _context.clock().now() - _startedOn;
System.out.println("== Transfer time: " + DataHelper.formatDuration(timeToSend));
StringBuffer buf = new StringBuffer(50);
buf.append("== Transfer rate: ");
double kbps = (1000.0d*(double)(_written)/((double)timeToSend*1024.0d));
synchronized (_kbps) {
buf.append(_kbps.format(kbps));
}
buf.append("KBps");
System.out.println(buf.toString());
}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
System.out.println();
System.out.println("** " + new Date());
System.out.println("** Attempt " + currentAttempt + " of " + url + " failed");
System.out.println("** Transfered " + bytesTransferred
+ " with " + (bytesRemaining < 0 ? "unknown" : ""+bytesRemaining) + " remaining");
System.out.println("** " + cause.getMessage());
_written = 0;
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
System.out.println("== " + new Date());
System.out.println("== Transfer of " + url + " failed after " + currentAttempt + " attempts");
System.out.println("== Transfer size: " + bytesTransferred + " with "
+ (bytesRemaining < 0 ? "unknown" : ""+bytesRemaining) + " remaining");
long timeToSend = _context.clock().now() - _startedOn;
System.out.println("== Transfer time: " + DataHelper.formatDuration(timeToSend));
double kbps = (timeToSend > 0 ? (1000.0d*(double)(bytesTransferred)/((double)timeToSend*1024.0d)) : 0);
StringBuffer buf = new StringBuffer(50);
buf.append("== Transfer rate: ");
synchronized (_kbps) {
buf.append(_kbps.format(kbps));
}
buf.append("KBps");
System.out.println(buf.toString());
}
}
public void addStatusListener(StatusListener lsnr) {
synchronized (_listeners) { _listeners.add(lsnr); }
}
public void stopFetching() { _keepFetching = false; }
public void fetch() {
_keepFetching = true;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetching (proxied? " + _shouldProxy + ") url=" + _url);
while (_keepFetching) {
try {
sendRequest();
doFetch();
return;
} catch (IOException ioe) {
for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
} finally {
if (_out != null) {
try {
_out.close();
} catch (IOException cioe) {}
_out = null;
}
if (_proxy != null) {
try {
_proxy.close();
_proxy = null;
} catch (IOException ioe) {}
}
}
_currentAttempt++;
if (_currentAttempt > _numRetries)
break;
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
}
for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt);
}
/** return true if the URL was completely retrieved */
private void doFetch() throws IOException {
readHeaders();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Headers read completely, reading " + _bytesRemaining);
int remaining = (int)_bytesRemaining;
byte buf[] = new byte[1024];
while (_keepFetching && remaining > 0) {
int toRead = buf.length;
int read = _proxyIn.read(buf, 0, (buf.length > remaining ? remaining : buf.length));
if (read == -1)
break;
_out.write(buf, 0, read);
_bytesTransferred += read;
remaining -= read;
if (read > 0)
for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).bytesTransferred(_alreadyTransferred, read, _bytesTransferred, _bytesRemaining, _url);
}
if (_out != null)
_out.close();
_out = null;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Done transferring " + _bytesTransferred);
if ( (_bytesRemaining == -1) || (remaining == 0) ){
for (int i = 0; i < _listeners.size(); i++)
((StatusListener)_listeners.get(i)).transferComplete(_alreadyTransferred, _bytesTransferred, _bytesRemaining, _url, _outputFile);
} else {
throw new IOException("Disconnection on attempt " + _currentAttempt + " after " + _bytesTransferred);
}
}
private void readHeaders() throws IOException {
String key = null;
StringBuffer buf = new StringBuffer(32);
boolean read = DataHelper.readLine(_proxyIn, buf);
if (!read) throw new IOException("Unable to read the first line");
int responseCode = handleStatus(buf.toString());
boolean rcOk = false;
switch (responseCode) {
case 200: // full
_out = new FileOutputStream(_outputFile, false);
rcOk = true;
break;
case 206: // partial
_out = new FileOutputStream(_outputFile, true);
rcOk = true;
break;
case 416: // completed (or range out of reach)
_bytesRemaining = 0;
_keepFetching = false;
return;
default:
rcOk = false;
}
byte lookahead[] = new byte[3];
while (true) {
int cur = _proxyIn.read();
switch (cur) {
case -1:
throw new IOException("Headers ended too soon");
case ':':
if (key == null) {
key = buf.toString();
buf.setLength(0);
increment(lookahead, cur);
break;
} else {
buf.append((char)cur);
increment(lookahead, cur);
break;
}
case '\n':
case '\r':
if (key != null)
handle(key, buf.toString());
buf.setLength(0);
key = null;
increment(lookahead, cur);
if (isEndOfHeaders(lookahead)) {
if (!rcOk)
throw new IOException("Invalid HTTP response code: " + responseCode);
if (_encodingChunked) {
readChunkLength();
}
return;
}
break;
default:
buf.append((char)cur);
increment(lookahead, cur);
}
if (buf.length() > 1024)
throw new IOException("Header line too long: " + buf.toString());
}
}
private void readChunkLength() throws IOException {
StringBuffer buf = new StringBuffer(8);
int nl = 0;
while (true) {
int cur = _proxyIn.read();
switch (cur) {
case -1:
throw new IOException("Chunk ended too soon");
case '\n':
case '\r':
nl++;
default:
buf.append((char)cur);
}
if (nl >= 2)
break;
}
String len = buf.toString().trim();
try {
long bytes = Long.parseLong(len, 16);
_bytesRemaining = bytes;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Chunked length: " + bytes);
} catch (NumberFormatException nfe) {
throw new IOException("Invalid chunk length [" + len + "]");
}
}
/**
* parse the first status line and grab the response code.
* e.g. "HTTP/1.1 206 OK" vs "HTTP/1.1 200 OK" vs
* "HTTP/1.1 404 NOT FOUND", etc.
*
* @return HTTP response code (200, 206, other)
*/
private int handleStatus(String line) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Status line: [" + line + "]");
StringTokenizer tok = new StringTokenizer(line, " ");
if (!tok.hasMoreTokens()) {
System.err.println("ERR: status "+ line);
return -1;
}
String protocol = tok.nextToken(); // ignored
if (!tok.hasMoreTokens()) {
System.err.println("ERR: status "+ line);
return -1;
}
String rc = tok.nextToken();
try {
return Integer.parseInt(rc);
} catch (NumberFormatException nfe) {
nfe.printStackTrace();
return -1;
}
}
private void handle(String key, String val) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Header line: [" + key + "] = [" + val + "]");
if (key.equalsIgnoreCase("Content-length")) {
try {
_bytesRemaining = Long.parseLong(val.trim());
} catch (NumberFormatException nfe) {
nfe.printStackTrace();
}
} else if (key.equalsIgnoreCase("ETag")) {
_etag = val.trim();
} else if (key.equalsIgnoreCase("Transfer-encoding")) {
if (val.indexOf("chunked") != -1)
_encodingChunked = true;
} else {
// ignore the rest
}
}
private void increment(byte[] lookahead, int cur) {
lookahead[0] = lookahead[1];
lookahead[1] = lookahead[2];
lookahead[2] = (byte)cur;
}
private boolean isEndOfHeaders(byte lookahead[]) {
byte first = lookahead[0];
byte second = lookahead[1];
byte third = lookahead[2];
return (isNL(second) && isNL(third)) || // \n\n
(isNL(first) && isNL(third)); // \n\r\n
}
/** we ignore any potential \r, since we trim it on write anyway */
private static final byte NL = '\n';
private boolean isNL(byte b) { return (b == NL); }
private void sendRequest() throws IOException {
File outFile = new File(_outputFile);
if (outFile.exists())
_alreadyTransferred = outFile.length();
String req = getRequest();
if (_shouldProxy) {
_proxy = new Socket(_proxyHost, _proxyPort);
} else {
try {
URL url = new URL(_url);
String host = url.getHost();
int port = url.getPort();
if (port == -1)
port = 80;
_proxy = new Socket(host, port);
} catch (MalformedURLException mue) {
throw new IOException("Request URL is invalid");
}
}
_proxyIn = _proxy.getInputStream();
_proxyOut = _proxy.getOutputStream();
_proxyOut.write(req.toString().getBytes());
_proxyOut.flush();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Request flushed");
}
private String getRequest() {
StringBuffer buf = new StringBuffer(512);
buf.append("GET ").append(_url).append(" HTTP/1.1\n");
try {
URL url = new URL(_url);
buf.append("Host: ").append(url.getHost()).append("\n");
} catch (MalformedURLException mue) {
mue.printStackTrace();
}
if (_alreadyTransferred > 0) {
buf.append("Range: bytes=");
buf.append(_alreadyTransferred);
buf.append("-\n");
}
buf.append("Accept-Encoding: identity;q=1, *;q=0\n");
buf.append("Connection: close\n\n");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Request: [" + buf.toString() + "]");
return buf.toString();
}
}

View File

@@ -245,7 +245,7 @@ public class LogManager {
if (!_alreadyNoticedMissingConfig) {
if (_log.shouldLog(Log.WARN))
_log.warn("Log file " + _location + " does not exist");
System.err.println("Log file " + _location + " does not exist");
//System.err.println("Log file " + _location + " does not exist");
_alreadyNoticedMissingConfig = true;
}
parseConfig(new Properties());
@@ -644,7 +644,7 @@ public class LogManager {
}
public void shutdown() {
_log.log(Log.CRIT, "Shutting down logger");
_log.log(Log.WARN, "Shutting down logger");
_writer.flushRecords();
}

View File

@@ -42,7 +42,7 @@ public class RandomSource extends SecureRandom {
* thats what it has been used for.
*
*/
public int nextInt(int n) {
public synchronized int nextInt(int n) {
if (n == 0) return 0;
int val = super.nextInt(n);
if (val < 0) val = 0 - val;
@@ -54,19 +54,48 @@ public class RandomSource extends SecureRandom {
* Like the modified nextInt, nextLong(n) returns a random number from 0 through n,
* including 0, excluding n.
*/
public long nextLong(long n) {
public synchronized long nextLong(long n) {
long v = super.nextLong();
if (v < 0) v = 0 - v;
if (v >= n) v = v % n;
return v;
}
/** synchronized for older versions of kaffe */
public void nextBytes(byte bytes[]) {
synchronized (this) {
super.nextBytes(bytes);
}
}
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public synchronized boolean nextBoolean() { return super.nextBoolean(); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public synchronized void nextBytes(byte buf[]) { super.nextBytes(buf); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public synchronized double nextDouble() { return super.nextDouble(); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public synchronized float nextFloat() { return super.nextFloat(); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public synchronized double nextGaussian() { return super.nextGaussian(); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public synchronized int nextInt() { return super.nextInt(); }
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public synchronized long nextLong() { return super.nextLong(); }
public EntropyHarvester harvester() { return _entropyHarvester; }

View File

@@ -29,6 +29,7 @@ package net.i2p.crypto;
* POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.ByteArrayInputStream;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
@@ -57,9 +58,13 @@ public class DSABench {
long endkeys = System.currentTimeMillis();
long startsign = System.currentTimeMillis();
Signature s = DSAEngine.getInstance().sign(message, privkey);
Signature s1 = DSAEngine.getInstance().sign(new ByteArrayInputStream(message), privkey);
long endsignstartverify = System.currentTimeMillis();
boolean v = DSAEngine.getInstance().verifySignature(s, message, pubkey);
long endverify = System.currentTimeMillis();
boolean v1 = DSAEngine.getInstance().verifySignature(s1, new ByteArrayInputStream(message), pubkey);
boolean v2 = DSAEngine.getInstance().verifySignature(s1, message, pubkey);
boolean v3 = DSAEngine.getInstance().verifySignature(s, new ByteArrayInputStream(message), pubkey);
long endverify = System.currentTimeMillis();
System.out.print(".");
keygentime += endkeys - startkeys;
signtime += endsignstartverify - startsign;
@@ -67,6 +72,8 @@ public class DSABench {
if (!v) {
throw new RuntimeException("Holy crap, did not verify");
}
if (!(v1 && v2 && v3))
throw new RuntimeException("Stream did not verify");
if ( (minKey == 0) && (minS == 0) && (minV == 0) ) {
minKey = endkeys - startkeys;
maxKey = endkeys - startkeys;

View File

@@ -0,0 +1,112 @@
package net.i2p.crypto;
/*
* Copyright (c) 2003, TheCrypto
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the TheCrypto may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import net.i2p.I2PAppContext;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
public class HMACSHA256Bench {
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
SessionKey key = ctx.keyGenerator().generateSessionKey();
Hash asdfs = HMACSHA256Generator.getInstance().calculate(key, "qwerty".getBytes());
int times = 100000;
long shorttime = 0;
long medtime = 0;
long longtime = 0;
long minShort = 0;
long maxShort = 0;
long minMed = 0;
long maxMed = 0;
long minLong = 0;
long maxLong = 0;
long shorttime1 = 0;
long medtime1 = 0;
long longtime1 = 0;
long minShort1 = 0;
long maxShort1 = 0;
long minMed1 = 0;
long maxMed1 = 0;
long minLong1 = 0;
long maxLong1 = 0;
byte[] smess = new String("abc").getBytes();
StringBuffer buf = new StringBuffer();
for (int x = 0; x < 2*1024; x++) {
buf.append("a");
}
byte[] mmess = buf.toString().getBytes(); // new String("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq").getBytes();
buf = new StringBuffer();
for (int x = 0; x < 10000; x++) {
buf.append("a");
}
byte[] lmess = buf.toString().getBytes();
HMACSHA256Generator.Buffer sbuf = ctx.hmac().createBuffer(smess.length);
HMACSHA256Generator.Buffer mbuf = ctx.hmac().createBuffer(mmess.length);
HMACSHA256Generator.Buffer lbuf = ctx.hmac().createBuffer(lmess.length);
// warm up the engines
ctx.hmac().calculate(key, smess, sbuf);
ctx.hmac().calculate(key, mmess, mbuf);
ctx.hmac().calculate(key, lmess, lbuf);
long before = System.currentTimeMillis();
for (int x = 0; x < times; x++)
ctx.hmac().calculate(key, smess, sbuf);
long after = System.currentTimeMillis();
display(times, before, after, smess.length, "3 byte");
before = System.currentTimeMillis();
for (int x = 0; x < times; x++)
ctx.hmac().calculate(key, mmess, mbuf);
after = System.currentTimeMillis();
display(times, before, after, mmess.length, "2KB");
before = System.currentTimeMillis();
for (int x = 0; x < times; x++)
ctx.hmac().calculate(key, lmess, lbuf);
after = System.currentTimeMillis();
display(times, before, after, lmess.length, "10KB");
}
private static void display(int times, long before, long after, int len, String name) {
double rate = ((double)times)/(((double)after-(double)before)/1000.0d);
double kbps = ((double)len/1024.0d)*((double)times)/(((double)after-(double)before)/1000.0d);
System.out.println(name + " HMAC-SHA256 pulled " + kbps + "KBps or " + rate + " calcs per second");
}
}

View File

@@ -1,4 +1,174 @@
$Id: history.txt,v 1.165 2005/03/06 19:07:27 jrandom Exp $
$Id: history.txt,v 1.182 2005/03/26 02:13:38 jrandom Exp $
* 2005-03-29 0.5.0.5 released
2005-03-29 jrandom
* Decreased the initial RTT estimate to 10s to allow more retries.
* Increased the default netDb store replication factor from 2 to 6 to take
into consideration tunnel failures.
* Address some statistical anonymity attacks against the netDb that could
be mounted by an active internal adversary by only answering lookups for
leaseSets we received through an unsolicited store.
* Don't throttle lookup responses (we throttle enough elsewhere)
* Fix the NewsFetcher so that it doesn't incorrectly resume midway through
the file (thanks nickster!)
* Updated the I2PTunnel HTML (thanks postman!)
* Added support to the I2PTunnel pages for the URL parameter "passphrase",
which, if matched against the router.config "i2ptunnel.passphrase" value,
skips the nonce check. If the config prop doesn't exist or is blank, no
passphrase is accepted.
* Implemented HMAC-SHA256.
* Enable the tunnel batching with a 500ms delay by default
* Dropped compatability with 0.5.0.3 and earlier releases
2005-03-26 jrandom
* Added some error handling and fairly safe to cache data to the streaming
lib (good call Tom!)
2005-03-25 jrandom
* Fixed up building dependencies for the routerconsole on some more
aggressive compilers (thanks polecat!)
* 2005-03-24 0.5.0.4 released
2005-03-23 jrandom
* Added more intelligent version checking in news.xml, in case we have a
version newer than the one specified.
2005-03-23 jrandom
* Added support for Transfer-Encoding: chunked to the EepGet, so that the
cvsweb.cgi doesn't puke on us.
2005-03-23 Connelly
* Fixed Bugzilla Bug #99 in the SAM Bridge, which caused pending
stream send data to not be sent if STREAM CLOSE is issued too fast.
2005-03-23 jrandom
* Implemented the news fetch / update policy code, as configurated on
/configupdate.jsp. Defaults are to grab the news every 24h (or if it
doesn't exist yet, on startup). No action is taken however, though if
the news.xml specifies that a new release is available, an option to
update will be shown on the router console.
* New initialNews.xml delivered with new installs, and moved news.xml out
of the i2pwww module and into the i2p module so that we can bundle it
within each update.
2005-03-23 jrandom
* New /configupdate.jsp page for controlling the update / notification
process, as well as various minor related updates. Note that not all
options are exposed yet, and the update detection code isn't in place
in this commit - it currently says there is always an update available.
* New EepGet component for reliable downloading, with a CLI exposed in
java -cp lib/i2p.jar net.i2p.util.EepGet url
* Added a default signing key to the TrustedUpdate component to be used
for verifying updates. This signing key can be authenticated via
gpg --verify i2p/core/java/src/net/i2p/crypto/TrustedUpdate.java
* New public domain SHA1 implementation for the DSA code so that we can
handle signing streams of arbitrary size without excess memory usage
(thanks P.Verdy!)
* Added some helpers to the TrustedUpdate to work off streams and to offer
a minimal CLI:
TrustedUpdate keygen pubKeyFile privKeyFile
TrustedUpdate sign origFile signedFile privKeyFile
TrustedUpdate verify signedFile
2005-03-22 smeghead
* New TrustedUpdate component for signing/verifying files with a DSA
signature.
2005-03-21 jrandom
* Fixed the tunnel fragmentation handler to deal with multiple fragments
in a single message properly (rather than release the buffer into the
cache after processing the first one) (duh!)
* Added the batching preprocessor which will bundle together multiple
small messages inside a single tunnel message by delaying their delivery
up to .5s, or whenever the pending data will fill a full message,
whichever comes first. This is disabled at the moment, since without the
above bugfix widely deployed, lots and lots of messages would fail.
* Within each tunnel pool, stick with a randomly selected peer for up to
.5s before randomizing and selecting again, instead of randomizing the
pool each time a tunnel is needed.
* 2005-03-18 0.5.0.3 released
2005-03-18 jrandom
* Minor tweak to the timestamper to help reduce small skews
* Adjust the stats published to include only the relevent ones
* Only show the currently used speed calculation on the profile page
* Allow the full max # resends to be sent, rather than piggybacking the
RESET packet along side the final resend (duh)
* Add irc.postman.i2p to the default list of IRC servers for new installs
* Drop support for routers running 0.5 or 0.5.0.1 while maintaining
backwards compatability for users running 0.5.0.2.
2005-03-18 jrandom
* Eepproxy Fix for corrupted HTTP headers (thanks nickster!)
* Fixed case sensitivity issues on the HTTP headers (thanks duck!)
2005-03-17 jrandom
* Update the old speed calculator and associated profile data points to
use a non-tiered moving average of the tunnel test time, avoiding the
freshness issues of the old tiered speed stats.
* Explicitly synchronize all of the methods on the PRNG, rather than just
the feeder methods (sun and kaffe only need the feeder, but it seems ibm
needs all of them synchronized).
* Properly use the tunnel tests as part of the profile stats.
* Don't flood the jobqueue with sequential persist profile tasks, but
instead, inject a brief scheduling delay between them.
* Reduce the TCP connection establishment timeout to 20s (which is still
absurdly excessive)
* Reduced the max resend delay to 30s so we can get some resends in when
dealing with client apps that hang up early (e.g. wget)
* Added more alternative socketManager factories (good call aum!)
2005-03-16 jrandom
* Adjust the old speed calculator to include end to end RTT data in its
estimates, and use that as the primary speed calculator again.
* Use the mean of the high capacity speeds to determine the fast
threshold, rather than the median. Perhaps we should use the mean of
all active non-failing peers?
* Updated the profile page to sort by tier, then alphabetically.
* Added some alternative socketManager factories (good call aum!)
2005-03-14 jrandom
* New strict speed calculator that goes off the actual number of messages
verifiably sent through the peer by way of tunnels. Initially, this only
contains the successful message count on inbound tunnels, but may be
augmented later to include verified outbound messages, peers queried in
the netDb, etc. The speed calculation decays quickly, but should give
a better differential than the previous stat (both values are shown on
the /profiles.jsp page)
2005-03-11 jrandom
* Rather than the fixed resend timeout floor (10s), use 10s+RTT as the
minimum (increased on resends as before, of course).
* Always prod the clock update listeners, even if just to tell them that
the time hasn't changed much.
* Added support for explicit peer selection for individual tunnel pools,
which will be useful in debugging but not recommended for use by normal
end users.
* More aggressively search for the next hop's routerInfo on tunnel join.
* Give messages received via inbound tunnels that are bound to remote
locations sufficient time (taking into account clock skew).
* Give alternate direct send messages sufficient time (10s min, not 5s)
* Always give the end to end data message the explicit timeout (though the
old default was sufficient before)
* No need to give end to end messages an insane expiration (+2m), as we
are already handling skew on the receiving side.
* Don't complain too loudly about expired TunnelCreateMessages (at least,
not until after all those 0.5 and 0.5.0.1 users upgrade ;)
* Properly keep the sendBps stat
* When running the router with router.keepHistory=true, log more data to
messageHistory.txt
* Logging updates
* Minor formatting updates
2005-03-08 jrandom
* More aggressively adjust the clock
2005-03-07 jrandom
* Fix the HTTP response header filter to allow multiple headers with the
same name (thanks duck and spotteri!)
* 2005-03-06 0.5.0.2 released

View File

@@ -1,6 +1,15 @@
; TC's hosts.txt guaranteed freshness
; $Id: hosts.txt,v 1.128 2005/03/04 18:37:39 duck Exp $
; $Id: hosts.txt,v 1.136 2005/03/29 20:49:49 cervantes Exp $
; changelog:
; (1.158) added tracker.fr.i2p
; (1.157) added v2mail.i2p, complication.i2p
; (1.156) added lazyguy.i2p
; (1.155) added confessions.i2p, rsync.thetower.i2p, redzara.i2p, gaytorrents.i2p
; (1.154) added arkan.i2p, search.i2p, floureszination.i2p, antipiratbyran.i2p
; asylum.i2p, templar.i2p
; (1.153) added feedspace.i2p
; (1.152) added wiki.fr.i2p
; (1.151) added septu.i2p
; (1.150) added music.i2p, rotten.i2p, wintermute.i2p, kaji2.i2p, aspnet.i2p,
; gaming.i2p, nntp.i2p
; (1.149) added cowsay.i2p
@@ -347,5 +356,21 @@ kaji2.i2p=kbW8N9ZMxbpLjR4EjJuc7GYlKIp3Z4rXpMwAU07-CoyndW3YNUftvKi8PTyg1FMVrf-7IT
wintermute.i2p=o6uDc3O3Esz0RizDwJ9ty5j4j3XFJhuh~d9zFvQqLojjq0c4a5nrXsop105DRBwFJ9h3OpOoWA8wXZUtdgqcvXoQXH-LmiApfyzrNLnbBzlwZGmd1LEWZEUMHo~EiU7JsEkE8BylLEfYXXrcqaMC3NxrM3gIwIieCRuxfIOzlYN-hIORs9-rqBurSdS4aBuIr3pz4H7pH43w1RgxiLKaFN5C9qbaDVDzhGKr35J-frRDApmlsL-foUYuFM7olmyleyG-C0DBskvJIoIfAPRvm25LtatOqr79dIA62gJOLo3SuifndMSwLcblNkzlh~H8kD4Cl~o5qCyE0sx96I7d-QkURVvKnbTJKc-uc6J3fgA2KByAXWmDfJfXb1ArlTcrE598FbjPfAGrmd8bvDAQZlABneuy190z08Ati1YGKId7Y-9MrmCcTTAFC08J6rgmhoeIA1mpE68WGz~xMuU0zwvT4K2czLWq4IRlmZb2o2RsxEGulDZuzH4ozxXq~CiRAAAA
rotten.i2p=ECnru16NdLMkgYzmtMgz1~mg8eIo~5FJ8x2LOj9f7NLx-8Me3lgyAMpcYuoohN08hNJYI74rvvS0mS67f5tw8j9CkFc0mPqUQN0g-RyHK9QWq8kghstnoexr5P2TjusQ3n99YLFujlbUDBTsStP-OjRxSGO8Ho173STIZeVlkPILkttm6cfHrFEcrd1MZrYFRlbrY-yv8ipcEh2ymx0bdII~8Htc1~hLIZyeD4G7FMVWyIxSQwL7Wgv3JB0BcBkq-1oB699uTVXemGizq4VTjywzktR~gj98gvyPrff3KnNfL2RYxruxb3uWoVRHlBQadk9W-5pZyVsaPsn8LuCluB2ax6KdxL-Uj98zavn35YiMIsr3g~aq3aX885lV9Oc7Dndq68zfuXsMo5yF~158XylkykWR-fykTcR-abVgScSNa1AFgHVXZnbOEA5NXUG6C0AYBbOjQ~rFXNZKnxQOljWJgPIWOsZYI2CLTbaz9tAUXZ6t1qXBJOlxAQCW62DfAAAA
music.i2p=sE8lmVzZgjq6bLbS89c8OCrsJXbV6qmxtBIb1289NZTw9DnwNDosmgfQO9g9txB28FZVLJ09aTgRbDm~D4BQNMRfF1kKgIyxSr0x-VTMwX58-9bB~0a3eGrHHT6OS7jSuOgHji-uqylLCc2JiB5sIApO0wgQ4GGas8mKyGyqUExiBhoNybL8y1Ks3qC97x3x-7U8ZMzU-7~V64sfAWdN7qSK0UdHK20kyFBCD8KcMKG3Ctl3SX3B8NYPRHnFq~l8UpXpTY2dWHmjceSRUY4FrXJNju1nfogfX2gv~r121VD7Hn3tyNX-Fbt0vOIsUJut1d9RjVCwie7dCiudf3Rf-S7nMEVmQzyaQzgbFZl27MRmEV5KHjOHVZXpwOFAes8uXh50BqjSmkTb2QfHxe~9Qb-mw~~oF9G95EQHe3H135d7YXJMepjUcju8UUoOzX94nSRWq5XCJdDrd-5K~oMIQs7GoIJxDzWLV9P7baSHbvRlRnZ7jCeyda4kDFbB0UTQAAAA
septu.i2p=PAMLJCc4LMNO3lULWpWYQR6sLwLuRdDmBxCLrv7GN32hfsMq3y18VJ9RdTmk8YZjuF1be6udYsadkgzXVyAUJ2yB~ZcuJiVr4crYNhGcQQ582ZLrqyye6rfNMxkKmg1JdZhOilJRn8-N-9Y90hWcc77ClNrFHmYE3y0ZSffMQuoPNXx7Ra03xBXfXxcYmIyslBNrbbBqZaqQktKXK9HL5PVlRoGCw1xNvMU9lCxZfdpKJSogljsg0sledwVl2p5DwhWhAGVzDYihcZ~PY6QAyATVhmWIpPk691geBPfpleKauwuj9~ZneRbMN5Lh0qsz1i-XG1zvGn9NDTbkdVAXu4Uop-DC-gN5aYYI-bVQtHVTSl9N2eCWorPYTEhsi6plOR4KXnoD3s3yzu4pphQGv0r8xLCdX4~wIcXJnIXW2W7e5hEzYqSe7EzuUw~PUEwskYqspXOLNzGzsK4r8k2X2fNB7LYLHqwWkKozkPmce3wti1XxyqoD0Ndq~JPJQzTcAAAA
wiki.fr.i2p=zuKLG6BVohlrgsNhIhG-SPxhsiaXz049s0GC9bnFAaKiWaXEOXH75m4wSF1e8kJkXfSvK~PHaREj7ZQxql-wtaFM9IXIQX3Wk2rfkdfGj-eWuMlh7OxRpqftDhWk36EDFua6Hs17d58uluipy~BI3i55LLgiKz23qprunABjL843zAWD7Oq3NHxe7jorIz7c5Sx5D8~jxbQOxwhMKVrPpOs~cQU2sqftPX0nrkSh5pBJxhGbGKIRdzHCAlSgGy~Ydf5DsBH1k0HpHMks49JFX~JzXJHtCEJxaMVrSnDCh9nXUJF5gGpCXhI9aXTFE6LzCRkxsazbPy0gHqyHNZ2nupt8LqN~ZJNxAJxtPyC79ikWTxE4akk3CrEGDYEgHGxrEATrKpMfMx9f2MqDKbV-8BJVdkpFk4EtkLhfVJjYgc1qxH7kFEG9S6cm53Oopaz7CZW-0PR0vbZzCMfyJrR0ExkqmmbiHrXnryrKvB2bJ8emsmTBvguvqO7qUiAwzIQPAAAA
feedspace.i2p=KuW5sR2iGCfnnuwdslHbFsNyNCsoZnoIwAmHeypOV-s8OQxokBpdNazksBrhoQum9nv81vprl6k15Mhcd~KWE4OajjmdU7v2fjqps7QK3KmLv4UTrX-ihSIUdhb5B9FLh2XEFEQ4-8guFTVxBRqQQE~c058AL6~uZpuFpLtEOg0HEZ6BydndOhx-FCDm8ip12pPwZ3a5O86l1UoATZBXxoctGafTjnUlx64jyQs6y0WB811l36wVrc~~dqEcanxab0yfg8dJ~1M4EUNrXcHT-PwYYrr3GgpimuF4oUtYjkeDKlq5WjfMAa8bE73HFgquxq99fuW5aI1JbLPxnTLHi00-2On0dSDwJxSP08HOhKFKMNzykI9Asg8CywzNO6kWpbX9yaML36ohCJF0iaLvvDyhS4a2B65crSJRJPVkbxIvsyyUyYMGi31EK593ijOLjOvugglxJoRizUehvD8c6KCpLLSjCc~ASoMbitolOXq8HCeJ5C9uSgUxKhZVk4~iAAAA
antipiratbyran.i2p=K~sebW76Q4eiNalKMrYrAs6S8Ctnfs-MVmMVfGeo1-9fMI7YhSBBZ9SI9zkNiur5MGBlqZvIY21-nP6RObwj4GGI9OxUXJXQ9gO~FMuuluGelDrb47AfJwhb0XPFyUQWXz2yYcsJkUQRikuNG~gy-vm~yP0847dw7nBsLOtnaiyD3LndwNKO7KLFV8lDWniu7cHENJ-srQrdxy9DfbTc-M1GjzL2qKrg8SF9DSahkNGp9rG~7rykzFlqisJkaepcBnB0MEAb8kzFXiOIz1HScf91CDdyS0nUClA0GLVF7WGftA~1Coih0I7bWLl27UyfoV4DA4-JhwB6yf8CL8k2hn9vWg6zBK1AifaG-ha8tmZvd1ZCoar670WSb2~dtILxMHvTeDEEuKyzTkDZIkJ8bqstB0guYwjkH98GANeUQCD23CRpmm8JW2tY80N6zt~KAlIq436m6SkhOgZ2cOPs454KgtDsmEV7vqlL0xS-0jlb9HHwouNga50Q~txhcUXTAAAA
floureszination.i2p=XnaY8f3pEycYWVdtYH21H~PgT3StiASiMvCHwDIQAVVnAswUY6L8sHr2nGSyCSjEWspgCj8kjwO0zwzgdq5IYLkwzPVTY~quYv3xBFVkWXDz37WM2cJ8rmaI0AVBmalh5v3WFdCKpMsn78TL0cF-JvnAw24SPbDfVTCG5AwaWQ1dBCscfVLkP1JI1mckSp-7Ld9S~AN7Uajw4NobAkaaSTjqHK3adeuOekHqi8iwGY-DEcmuSNAbJ4OVZlLLZeg~Mqp-jukEQPbcxhOlEM~JI-8nfjt1fTvxmGT7LtNVYpC2qoofZ6CEYPqOZRduacHiW0mGQWdm7BrcGmjMWIr~eUk8XabgpOb9BHCjciZdiTrgtx~VmwETSgKceNfFGkHNTkNwrqmHz5rQhTPx29~mp5GNS6XG4dhgNlhYXeEOhQnPPuTo~Wa4giu83sNq8OZJZqcWeTSa~P2NLvxqdMXFfHcD5jjZjB5VyEHdWXk5Vja97bWHCVhxaK0HZ~wcGoafAAAA
search.i2p=9GIzUtJ7aQTTPsQtMFQ-RFzKENhODuftOcD9ZyQig6bkuYND2MNMPmcSO8YjtN74MKBjpOc2XyZIj3wy869ChLbwSVXj5H3ZKVY6H3Fxq-8BH8gFU91EyNIKvFQbASqfDBDIvMAWYQItq5oicXyb6T-hRY5nNnRO7XtxIDn94S46jqWVocb2YEaO2kKqABwRBv1HEoWCNGG3nuUAIcYZkeCuEgvsL6q2Us3te6RAz~Dpqv5EIVbsiEA-uxYUmJHVFP4NwchV53~TLQMXBZv6C5y89N~pFoO-7MGeWl76tI0i~FDlbuAvZWHIeHEIgMCZV45f~QVHGz0LR1PW51KobDpyrjzgSvvWxhPCCCpj0fKA0~wf4dXLbKo9XzgLKsspEH6-MJx6D0LJBjzsEtllzYQ96JpfG-yrqAHhz1pbL-2mfuJEBBu1d0VxypQkMNN9U-ZZL3~04CNidL1XPgU7Z50NsSxdJAj6lHqzt4b8RweXSm7zv3NsJuBR2btuyp6GAAAA
arkan.i2p=wxq-X1NM2u~K7w9tSjbOq6QNvvXJERI8rDq0qSj1yCgytNnb52RuHPoWsTbkZ6joJF1WS3Xm8WfwF7ceYg-uvuTWhW1uytJZBFbvkhP6~Z~OzUUMppiP2AWboq0awD5Dw3JQN9RWSRClEDgzxUSJ2U0Y3MZTQdCyPNF1-gIIMrppxFtLzTbTDY7XBnDd5yvYEXNm1ftp~EGiEl5za1Y3txTLTwC~oNoSWD901WttKYU9DiL35ZZFhJU~1cbK4R9~uIRYhow3rV~8IpCtmSjwE942-qcS5rDdgD06L~QqHbE64KieEm-WBAm8wgh4EX5VlXZTgp1bcCwrn-Vn2DTQ1AUJNLVBiOc90irsN4WXMgc0Subuk9QaWrqk~bc6aPvM02m-Muw1MKaXdMgSsflS9di2OX4kmGwwiO8eUdsH0laqOnV1Jdg1CM~CmXz1pgkV8SsqkGNKHiNDMlfyqEiOrOsGg9T9Kcm7fdXdlCM-KV3xTpaT-lnKY0mO1yKklh7TAAAA
templar.i2p=VlRjolSuETmcCSiSEan-NxU5uZAbyn~BSt2mqKhdiF6ppV-dtPZnay5PmzpOULJoMOqjTZGNd4Sf7-zMKtZBB9UI7aBIQG9LsfeGF-JlK1jVkAkuw291E66RlrYQdG4nD~4mn3wXiwkaswb8rHleruTpCa6~tJx-gfK6YVclro5DA1s1AvLZYcgQ3bv7S7n5jNXVWLvvzJSnT~OJ3x9tHOaRdJPdFduVXBn2zchj7KEPZya9XJgwbsOzJtuZArP2Pxtodtta1C8AgS09r2dy0EO52Neb1sHS-wnG~ITcPnqbgGqM0hiP0nYk7Kg-PfWH5jaP2w0yhmHh2OEOMEtWTWAGQW9f77I-MBOG0nLASKQNhFZkKLxdf76Ab-xEX3AVdc~rPqmxuB0osCTAfUnAYtE7PxfPP1jt1cVw4lA6n-~eJTTwE-6-fBcclt1ptoCwXXxZWcX9umtkg8TsvPkUveI8K6kNwcnPU57m0cjb3dnj-62CQh5VuWsBxVyAPpaKAAAA
asylum.i2p=97-3nsICymUlwgSQTEhO6JQowiQ5IcY82DPQ3lrxaf4Y1VigIDAd0Ny36Fen0XfEC3R80WxOdLAALNiS-nh8XWhY0flmvTNt9Wd7Oz~KHXyjxiPQnC82oD2mRYq2euUcJm~GZ1NU3QzB6-RBAxTj4zLGbeJ9e3fRoijwWL2prAdgV19MCurUfQ346BbmZG9e~EfAE9ZLKVRa7eILV0GWiyuvVMA~Z8bJReJdwaHzkCsKybC9a-HvwAjTnDq9fywC43yEGOTBI~4nmDz77pNKvgUykN-MLZfqqRzgrbmf91kLxlJ0Mj36cs-yx8ZTyfTh1mti3Ys0nEqnxyxD~1jsFD09y~6A00khU1-ptepKQugfxxJHL264yGTd4dy7dL9r5AqPiS14CvvwMS9QpdoUgOD1xazquB217kYVpO6tPPt3aqZ2h~w1WgrztG0se3uI7UE9bl0JkfZtgpGblVzIh2lBx7uSh9b9d7-rnYT4d-buV~Va1T9mKQQRQBc1Tp48AAAA
confessions.i2p=hgEwASKmmhKk0a0U9Wu2dVno7h6eZVqC3wdqWvul~Td47taUBEL6N6H5uQ2~nFcwcEYrCgJfhDoaBBUDyE6kpwCJU2xTC96F4YcBCIQhM3JVZqvKNB6QZGJd1oyGOv5JWq87K8ItSnXBazczdRDFknL0xa-f2oaDddTp9lz00BU1hpgvd9K93SMeZR7Bfq7VtfyvxyBA5S10cfNljbjk6RVVEqesHV-SqSyL7jSavzGVPIXERokb5xahosRkf7iGFsh5e14Wu9wPapsxWlKGCKiAhBqU-rVH-skENq-0se0sMcItilGaIeGeS2horymamv8VpXNZkIB0ikiEN2z83jb2Om6W33r81MXsE~gM3XQQ534ausN1LF7KM3~QWs5e1ElycRhBjmyvkZkwjR-jqZPjAFuJxmX-xC9QqI9pkOhPPD6exlptdEx6L6cJzJJSqFZHE9wGw8aCiKKHsH7eS9O85DEQB58FT~3TxHnTBYRwbBMfnP~Wj89AFwPXVpX7AAAA
rsync.thetower.i2p=FGylQtqVT-2~~aXTCtTR6sKURHvuT7fcUKW2eETpUHU~yahtXDamexxJY4fZQoSLqHIQC1RCmrixohxKjYLjXJtDlIX0uqADcEEqHZ1odc01xKUm1gHvxUgnxLQdlFHj0cH5de2fOT9Fiqkl54svaJyKbGi3WN7T0jRQqqH6ZuG5XQA90c6cfmLOpdK4hXdebm6YXrVJKxFirZ3rAKKlaFjeD4Wvgp4cTGxTXOzwdO75ZUmebRE6rpL1z0927MGg~hyOFbeqG7naje7RBe64PjTwRj46DH4EdKTT8NlRn5M7-qqTb8A2GnXYKrGJOpfx9HGSMxBSf0P2Usrpp7BamHJuBmLU0SH~J19SLq-SiOlWwrNgQzgKJoJ2J6zSOgZqFhBNnNtySj-zkOeC6ODN9QXxzHSZWzF9m4XwGKcrdLsZ8pxVIXMBqQY5BfZd0bigupwENVjEbMGPxSEDtffFeEKjoLybRUEcLB1jcn~FlnhWXL0tUqIEdfgDQ7VxAytTAAAA
redzara.i2p=X11wIDxPBZ5p08Eifivuv3-xDW~k4CMyjBky2xIyOgGNsNfktxSGWNQ-cjhVYY1-FLZheX~8SdUBEa8FGb20jKtkfsKBmejbkZFhpQ53oHUITj5kN5WPcr06ORB~Ofska637DSzJs5ZVLQb13wNVXqtuKc-JDJa~kanQPsc60-D~Vu7LKKRYMKsUJx5xz9knr2TaLdDmGlzjOcbII4dw-7WBhydLEm9DhEHuaCReuoT3nSv5aCRyXuzf~PcBrT1gmoTLEAf-GxxZdGqNf5GWQ48IzZUVoR3NR1lKS4JbQNBfhC6S~~FrY-2~u5RRwF6HrNQfYEdKmnQqf6euahOpkD0pEMIqQdkWG2L2DZP9BWZBebxQxpv~4FNeRj-7ltpeipiut2yx3QSDLXrvWtkRRRcx2WugIp8svaR98e5yZ1nztx4H-LE7BbuNeIlITjo7lG-ddRspoJ29G1neRoQdYMhpFHFaNZfNWvPDu9DQ7w1Ryn-n1YZWvfFUYaMUeOYOAAAA
gaytorrents.i2p=uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA
lazyguy.i2p=jVkB2blHl6nNSI6rLxNcim5mPrwR5vnMFBFrZRcsZxKFRjbbNBxFqJd5aZmJyykUbpYYj0HZ3acGIL880Klfp4ufhDof1oBR1SObm~qF7ussK7ME9-mB3rO9XS7KHFLgr~Nr95uTqCn~PECE-kMEOb42ZXzYvKOhnTQS4xjrZVP8hE-8MowjINVglAexk7yAVWda3gU-008N5ycuD-H3x2vlK~izZmfj62UbgVhmydJ7bERYKkA1221LbyAGUMA-bS1oe7~-mJugz3o1mlLbn-vWqY5-GeByiuip1ollddYxk62yYUG0jugH708~21NUw6SutxKleDOEvkHmJDoO1B6rEFT~N7MH-V4g0wetQdaAZrVb4tdDko9K4Lmq69vln3su9egPx5d8gBLgKOtMEhoGvuNM8t-88TTGGaAvRjVDnP4QpiubKYCPs2l4GzeLTXspaRrk~M5Kwbjg9IgDrNXclZdBGApC-oF8di~ZHOuxmMv8lXTBX3KyNRb-xwZqAAAA
v2mail.i2p=FE~x8chFTUEGogpe8aaDBYES0gnm072OCMJ2M9F9Dt2MZgLsogZH8UDSPdu59QHW695-qZR5CTzSc3IZNTNlPJQRb5EjMXAIYkrvVcghaJ5-QUVhhIE8iXNwcogJclQBP-rDK~CrdXp0FloAczd~WSk48vOjApRVhZFte~GZw~rL85ZZw3-27DFZh77wc57E1y~7MKXNm5PuEF24VMowp0Qnb9WPcennJmTEqNMPSs3-vNRytAASkVN6~Ign7tApUHsvHpFcRkTiERBRDuHOpUAjBGZmBW1mQxPYqueMEKC7nZu3Pxgp3Pzxld-AOrl0wDAbg~uggoWz-edyTKylj3V9JT~4lsBbKnf2hK2LMJ4XOCqv9svlpu7E6bvQopvWy70hHzhest52OcyqhGxIkRf~jozHAYH88rRoOAW~LRtqOvCrqbkSTkT8uAvRKcZDDQGBHU8S~KBzp2Cejk1gljmQdBKCeuLJzr8uq3IgrL1J5GKcrnFENaJNd~BbLvDhAAAA
complication.i2p=oGGGEsxv4TDMS-hCGHDbzDJC6f0R~4NbrPHxPdemxBAJ8JQQR34R6NsY4ocLc-lUqftIzDs7EAR~Qtkj6ixyGgrN4S7~XdoJZQPUCtC2zfiUSeehArzDNGBe~rCF2UzT-bCCejbSG57vE4i4QzI84-c9CEUww56veR2VdCULktv32P156LWlnDO26nyZsWj88sAgcBTwxZC4Lh8ms2YnQLyVJwIRlzX1wUwh4H8nlm0buUHx3tr9raIm6Ru0ORk2LGQss1g3rEVX5hm48S2vbvG9OT5ggghfYrjyJ6CZeIJmPTTYyMJfEJAvEzdlg0YvROd4LjQ9T5DaO36cHtFUYSJPuFeCvR9AWBoGbreZfrN1Q3mJ1g7TnYFbaLYYOC2Aj9DOnZyGaGoFCt4iMP9n6kZ~ktqOrwT-b6evLucxAxfMX6uLCsnwjY5GHywT9opsxtp-XXG4j8cbtz43SBQKurI1NWBrUwLvFqLqJMzJwMLtGrPwt-mH6ysvhzqOTEIoAAAA
tracker.fr.i2p=608f0LeGqSCul8ppxGNMROY5zndsC99Xrm9x5HsySsqusoDw~7UWo41UkOM71qJpFE5XyphFmKNTtiHp7TvED-QFgRyDVQ656R3R8nMuxIO5NSkk-J7ibSYkoDI0qB0~-sWzyC0dDmoZ313ko0szuc9ORzxcejHKXftP4D4a35Q82TxWNn2jeyCf3YlkPSH3YMkJJ-2EvQxBp98vUpmWU-pN~eQdowHBIhkTxhh3jIpnv5ShHD6YeFYYEZlEHmYHHm85C9f1CryuKS2qq5owrafPUpF4ZLTuT6bOE0go8m0FYfWg0XpcP95felXN20~Fq2JQJFqz3Z9YYhfQPScsDQ4bwFH2GihltoegoNSdBp7VTxja4H-lCOQAOGW1xWTrvGuknTfJK2fSPrSbds2KgWdgqzP50ooxblBytAGWjzbkm89VtG7dn3xJBXoIjpSBp4IpIbER83pbfccvdJE9y24AOXQK-VDwmpP6ndq0QnQDNwsrxy-daAk1DRZLbGrSAAAA

14
initialNews.xml Normal file
View File

@@ -0,0 +1,14 @@
<i2p.news date="$Date: 2005/03/24 02:29:27 $">
<i2p.release version="0.5.0.5" date="2005/03/29" minVersion="0.5.0.4"
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/March-2005/000652.html"
publicannouncement="http://dev.i2p.net/pipermail/i2p/March-2005/000662.html" />
<i2p.notes date="2005/03/29"
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000661.html"
publicurl="http://dev.i2p.net/pipermail/i2p/March-2005/000661.html"
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/meeting135"
publiclogs="http://www.i2p.net/meeting135" />
<h1>Congratulations on getting I2P installed!</h1>
</i2p.news>

View File

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

View File

@@ -18,7 +18,7 @@ tunnel.1.description=IRC proxy to access the anonymous irc net
tunnel.1.type=client
tunnel.1.interface=127.0.0.1
tunnel.1.listenPort=6668
tunnel.1.targetDestination=irc.duck.i2p,irc.baffled.i2p
tunnel.1.targetDestination=irc.duck.i2p,irc.baffled.i2p,irc.postman.i2p
tunnel.1.i2cpHost=127.0.0.1
tunnel.1.i2cpPort=7654
tunnel.1.option.inbound.nickname=ircProxy

40
news.xml Normal file
View File

@@ -0,0 +1,40 @@
<!-- this comment is filler to work around a bug in the 0.5.0.4 updater, where
** it would try to *resume* instead of overwriting the old news file. oops.
** so, to get around this, i need to babble on a bit, otherwise the
** oh-so-interesting text below wouldn't be seen by the router. still, i
** think the updater might not get the updated announcement, but perhaps it
** will. Or maybe i should put in a message telling people to delete their
** news.xml file (at least, until 0.5.0.5 is out)? hmm...
**
** ok, this is getting a bit tedious. come hither, ^C^V!
<i2p.news date="$Date: 2005/03/24 02:29:27 $">
<i2p.release version="0.5.0.5" date="2005/03/29" minVersion="0.5.0.4"
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/March-2005/000652.html"
publicannouncement="http://dev.i2p.net/pipermail/i2p/March-2005/000662.html" />
<i2p.notes date="2005/03/29"
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000661.html"
publicurl="http://dev.i2p.net/pipermail/i2p/March-2005/000661.html"
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/meeting135"
publiclogs="http://www.i2p.net/meeting135" />
** Bah, enough
-->
<i2p.news date="$Date: 2005/03/24 02:29:27 $">
<i2p.release version="0.5.0.5" date="2005/03/29" minVersion="0.5.0.4"
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/March-2005/000652.html"
publicannouncement="http://dev.i2p.net/pipermail/i2p/March-2005/000662.html" />
<i2p.notes date="2005/03/29"
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000661.html"
publicurl="http://dev.i2p.net/pipermail/i2p/March-2005/000661.html"
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/meeting135"
publiclogs="http://www.i2p.net/meeting135" />
<b>I2P news online!</b> For those still on 0.5.0.4, please update ASAP. If you
don't see a link to update with, delete the docs/news.xml file and wait 10
minutes. Otherwise, swing on by the website to download it.<br />
</i2p.news>

View File

@@ -1,5 +1,3 @@
<h1>Congratulations on getting I2P installed!</h1>
<p>If this is your first time running I2P, you will see a link on the left hand
side telling you to "reseed" - click that to get connected to the network (you
only need to do it if that link shows up). Within 5 minutes, you should see

515
router/doc/udp.html Normal file
View File

@@ -0,0 +1,515 @@
<code>$Id: udp.html,v 1.6 2005/03/27 17:08:16 jrandom Exp $</code>
<h1>Secure Semireliable UDP (SSU)</h1>
<b>DRAFT</b>
<p>
The goal of this protocol is to provide secure, authenticated,
semireliable, and unordered message delivery, exposing only a minimal
amount of data easily discernible to third parties. It should
support high degree communication as well as TCP-friendly congestion
control, and may include PMTU detection. It should be capable of
efficiently moving bulk data at rates sufficient for home users.
In addition, it should support techniques for addressing network
obstacles, like most NATs or firewalls.</p>
<h2><a name="addressing">Addressing and introduction</a></h2>
<p>To contact an SSU peer, one of two sets of information is necessary:
a direct address, for when the peer is publicly reachable, or an
indirect address, for using a third party to introduce the peer.
There is no restriction on the number of addresses a peer may have.</p>
<pre>
Direct: udp://host:port/introKey
Indirect: udp://tag@relayhost:port/relayIntroKey/targetIntroKey
</pre>
<p>These introduction keys are delivered through an external channel
and must be used when establishing a session key. For the indirect
address, the peer must first contact the relayhost and ask them for
an introduction to the peer known at that relayhost under the given
tag. If possible, the relayhost sends a message to the addressed
peer telling them to contact the requesting peer, and also gives
the requesting peer the IP and port on which the addressed peer is
located. In addition, the peer establishing the connection must
already know the public keys of the peer they are connecting to (but
not necessary to any intermediary relay peer).</p>
<h2><a name="header">Header</a></h2>
<p>All UDP datagrams begin with a MAC and an IV, followed by a variable
size payload encrypted with the appropriate key. The MAC used is
HMAC-SHA256, truncated to 16 bytes, while the key is a full AES256
key. The specific construct of the MAC is the first 16 bytes from:</p>
<pre>
HMAC-SHA256(payload || IV || payloadLength, macKey)
</pre>
<p>The payload itself is AES256/CBC encrypted with the IV and the
sessionKey, with replay prevention addressed within its body,
explained below.</p>
<h2><a name="payload">Payload</a></h2>
<p>Within the AES encrypted payload, there is a minimal common structure
to the various messages - a one byte flag and a four byte sending
timestamp (*seconds* since the unix epoch). The flag byte contains
the following bitfields:</p>
<pre>
bits 0-3: payload type
bit 4: rekey?
bit 5: extended options included
bits 6-7: reserved
</pre>
<p>If the rekey flag is set, 64 bytes of keying material follow the
timestamp. If the extended options flag is set, a one byte option
size value is appended to, followed by that many extended option
bytes, which are currently uninterpreted.</p>
<p>When rekeying, the first 32 bytes of the keying material is fed
into a SHA256 to produce the new MAC key, and the next 32 bytes are
fed into a SHA256 to produce the new session key, though the keys are
not immediately used. The other side should also reply with the
rekey flag set and that same keying material. Once both sides have
sent and received those values, the new keys should be used and the
previous keys discarded. It may be useful to keep the old keys
around briefly, to address packet loss and reordering.</p>
<pre>
Header: 37+ bytes
+----+----+----+----+----+----+----+----+
| MAC |
| |
+----+----+----+----+----+----+----+----+
| IV |
| |
+----+----+----+----+----+----+----+----+
|flag| time | (optionally |
+----+----+----+----+----+ |
| this may have 64 byte keying material |
| and/or a one+N byte extended options) |
+---------------------------------------|
</pre>
<h2><a name="messages">Messages</a></h2>
<h3><a name="sessionRequest">SessionRequest (type 0)</a></h3>
<table border="1">
<tr><td align="right" valign="top"><b>Peer:</b></td>
<td>Alice to Bob</td></tr>
<tr><td align="right" valign="top"><b>Data:</b></td>
<td><ul>
<li>256 byte X, to begin the DH agreement</li>
<li>1 byte IP address size</li>
<li>that many byte representation of Bob's IP address</li>
<li>N bytes, currently uninterpreted (later, for challenges)</li>
</ul></td></tr>
<tr><td align="right" valign="top"><b>Key used:</b></td>
<td>introKey</td></tr>
</table>
<pre>
+----+----+----+----+----+----+----+----+
| X, as calculated from DH |
| |
. . .
| |
+----+----+----+----+----+----+----+----+
|size| that many byte IP address (4-16) |
+----+----+----+----+----+----+----+----+
| arbitrary amount |
| of uninterpreted data |
. . .
| |
+----+----+----+----+----+----+----+----+
</pre>
<h3><a name="sessionCreated">SessionCreated (type 1)</a></h3>
<table border="1">
<tr><td align="right" valign="top"><b>Peer:</b></td>
<td>Bob to Alice</td></tr>
<tr><td align="right" valign="top"><b>Data:</b></td>
<td><ul>
<li>256 byte Y, to complete the DH agreement</li>
<li>1 byte IP address size</li>
<li>that many byte representation of Alice's IP address</li>
<li>2 byte port number (unsigned, big endian 2s complement)</li>
<li>0-15 pad bytes to reach the 16 byte boundary</li>
<li>4 byte relay tag which Alice can publish (else 0x0)</li>
<li>40 byte DSA signature of the critical exchanged data</li>
<li>N bytes, currently uninterpreted (later, for challenges)</li>
</ul></td></tr>
<tr><td align="right" valign="top"><b>Key used:</b></td>
<td>introKey for the data through the pad bytes, and the
sessionKey for the DSA signature</td></tr>
</table>
<pre>
+----+----+----+----+----+----+----+----+
| Y, as calculated from DH |
| |
. . .
| |
+----+----+----+----+----+----+----+----+
|size| that many byte IP address (4-16) |
+----+----+----+----+----+----+----+----+
| Port (A)| (pad to 16 byte boundary) |
+----+----+----+----+----+----+----+----+
| public relay tag | DSA signature |
+----+----+----+----+ |
| |
| |
| |
| |
+ +----+----+----+----+
| | arbitrary amount |
+----+----+----+----+ |
| of uninterpreted data |
+----+----+----+----+----+----+----+----+
</pre>
<h3><a name="sessionConfirmed">SessionConfirmed (type 2)</a></h3>
<table border="1">
<tr><td align="right" valign="top"><b>Peer:</b></td>
<td>Bob to Alice</td></tr>
<tr><td align="right" valign="top"><b>Data:</b></td>
<td><ul>
<li>1 byte identity fragment info:<pre>
bits 0-3: current identity fragment #
bits 4-7: total identity fragments</pre></li>
<li>N byte fragment of Alice's identity, sent over a number
of messages.</li>
<li>on the last identity fragment, the last 40 bytes contain
the DSA signature of the critical exchanged data</li>
</ul></td></tr>
<tr><td align="right" valign="top"><b>Key used:</b></td>
<td>sessionKey</td></tr>
</table>
<pre>
<b>Fragment 1 through N-1</b>
+----+----+----+----+----+----+----+----+
|info| fragment of Alice's full |
+----+ |
| identity keys |
. . .
| |
+----+----+----+----+----+----+----+----+
<b>Fragment N:</b>
+----+----+----+----+----+----+----+----+
|info| fragment of Alice's full |
+----+ |
| identity keys |
. . .
| |
+----+----+----+----+----+----+----+----+
| arbitrary amount of uninterpreted |
| data, up from the end of the |
| identity key to 40 bytes prior to |
| end of the current packet |
+----+----+----+----+----+----+----+----+
| DSA signature |
| |
| |
| |
| |
+----+----+----+----+----+----+----+----+
</pre>
<h3><a name="relayRequest">RelayRequest (type 3)</a></h3>
<table border="1">
<tr><td align="right" valign="top"><b>Peer:</b></td>
<td>Alice to Bob</td></tr>
<tr><td align="right" valign="top"><b>Data:</b></td>
<td><ul>
<li>4 byte relay tag</li>
<li>1 byte IP address size</li>
<li>that many byte representation of Bob's IP address</li>
<li>1 byte IP address size</li>
<li>that many byte representation of Alice's IP address</li>
<li>2 byte port number (of Alice)</li>
<li>1 byte challenge size</li>
<li>that many bytes to be relayed to Charlie in the intro</li>
<li>N bytes, currently uninterpreted</li>
</ul></td></tr>
<tr><td align="right" valign="top"><b>Key used:</b></td>
<td>introKey (or sessionKey, if Alice/Bob is established)</td></tr>
</table>
<pre>
+----+----+----+----+----+----+----+----+
| relay tag |size| that many |
+----+----+----+----+----+ +----|
| bytes making up Bob's IP address |size|
+----+----+----+----+----+----+----+----+
| that many bytes making up Alice's IP |
+----+----+----+----+----+----+----+----+
| Port (A)|size| that many challenge |
+----+----+----+ |
| bytes to be delivered to Charlie |
+----+----+----+----+----+----+----+----+
| arbitrary amount of uninterpreted data|
+----+----+----+----+----+----+----+----+
</pre>
<h3><a name="relayResponse">RelayResponse (type 4)</a></h3>
<table border="1">
<tr><td align="right" valign="top"><b>Peer:</b></td>
<td>Bob to Alice</td></tr>
<tr><td align="right" valign="top"><b>Data:</b></td>
<td><ul>
<li>1 byte IP address size</li>
<li>that many byte representation of Charlie's IP address</li>
<li>2 byte port number</li>
<li>1 byte IP address size</li>
<li>that many byte representation of Alice's IP address</li>
<li>2 byte port number</li>
<li>N bytes, currently uninterpreted</li>
</ul></td></tr>
<tr><td align="right" valign="top"><b>Key used:</b></td>
<td>introKey (or sessionKey, if Alice/Bob is established)</td></tr>
</table>
<pre>
+----+----+----+----+----+----+----+----+
|size| that many bytes making up |
+----+ +----+----+
| Charlie's IP address | Port (C)|
+----+----+----+----+----+----+----+----+
|size| that many bytes making up |
+----+ +----+----+
| Alice's IP address | Port (A)|
+----+----+----+----+----+----+----+----+
| arbitrary amount of uninterpreted data|
+----+----+----+----+----+----+----+----+
</pre>
<h3><a name="relayIntro">RelayIntro (type 5)</a></h3>
<table border="1">
<tr><td align="right" valign="top"><b>Peer:</b></td>
<td>Bob to Charlie</td></tr>
<tr><td align="right" valign="top"><b>Data:</b></td>
<td><ul>
<li>1 byte IP address size</li>
<li>that many byte representation of Alice's IP address</li>
<li>2 byte port number (of Alice)</li>
<li>1 byte challenge size</li>
<li>that many bytes relayed from Alice</li>
<li>N bytes, currently uninterpreted</li>
</ul></td></tr>
<tr><td align="right" valign="top"><b>Key used:</b></td>
<td>sessionKey</td></tr>
</table>
<pre>
+----+----+----+----+----+----+----+----+
|size| that many bytes making up |
+----+ +----+----+
| Charlie's IP address | Port (C)|
+----+----+----+----+----+----+----+----+
|size| that many bytes of challenge |
+----+ |
| data relayed from Alice |
+----+----+----+----+----+----+----+----+
| arbitrary amount of uninterpreted data|
+----+----+----+----+----+----+----+----+
</pre>
<h3><a name="data">Data (type 6)</a></h3>
<table border="1">
<tr><td align="right" valign="top"><b>Peer:</b></td>
<td>Any</td></tr>
<tr><td align="right" valign="top"><b>Data:</b></td>
<td><ul>
<li>1 byte flags:<pre>
bit 0: explicit ACKs included
bit 1: explicit NACKs included
bit 2: numACKs included
bits 3-4: reserved for congestion control
bit 5: want reply
bits 6-7: reserved</pre></li>
<li>if explicit ACKs are included:<ul>
<li>a 1 byte number of ACKs</li>
<li>that many 4 byte MessageIds being fully ACKed</li>
</ul></li>
<li>if explicit NACKs are included:<ul>
<li>a 1 byte number of NACKs</li>
<li>that many 4 byte MessageIds + 1 byte fragmentNum NACKs</li>
</ul></li>
<li>if numACKs included:<ul>
<li>a 2 byte number for how many messages were fully
received in the last minute.</li></ul></li>
<li>1 byte number of fragments</li>
<li>that many message fragments:<ul>
<li>4 byte messageId</li>
<li>1 byte fragment info:<pre>
bits 0-4: fragment #
bit 5: isLast (1 = true)
bits 6-7: unused</pre></li>
<li>2 byte fragment size</li>
<li>that many bytes</li>
<li>1 byte fragment size</li></ul>
<li>N bytes padding, uninterpreted</li>
</ul></td></tr>
<tr><td align="right" valign="top"><b>Key used:</b></td>
<td>sessionKey</td></tr>
</table>
<pre>
+----+----+----+----+----+----+----+----+
|flag| (additional headers, determined |
+----+ |
| by the flags, such as ACKs, NACKs, or |
| simple rate of full ACKs) |
+----+----+----+----+----+----+----+----+
|#frg| messageId |info|fragSize |
+----+----+----+----+----+----+----+----+
| that many bytes of fragment data |
. . .
| |
+----+----+----+----+----+----+----+----+
| messageId |info|fragSize | |
+----+----+----+----+----+----+----+ |
| that many bytes of fragment data |
. . .
| |
+----+----+----+----+----+----+----+----+
| messageId |info|fragSize | |
+----+----+----+----+----+----+----+ |
| that many bytes of fragment data |
. . .
| |
+----+----+----+----+----+----+----+----+
| arbitrary amount of uninterpreted data|
+----+----+----+----+----+----+----+----+
</pre>
<h2><a name="keys">Keys</a></h2>
<p>All encryption used is AES256/CBC with 32 byte keys and 16 byte IVs.
The MAC and session keys are negotiated as part of the DH exchange, used
for the HMAC and encryption, respectively. Prior to the DH exchange,
the publicly knowable introKey is used for the MAC and encryption.</p>
<p>When using the introKey, both the initial message and any subsequent
reply use the introKey of the responder (Bob) - the responder does
not need to know the introKey of the requestor (Alice). The DSA
signing key used by Bob should already be known to Alice when she
contacts him, though Alice's DSA key may not already be known by
Bob.</p>
<p>Upon receiving a message, the receiver checks the from IP address
with any established sessions - if there is one or more matches,
those session's MAC keys are tested sequentially in the HMAC. If none
of those verify or if there are no matching IP addresses, the
receiver tries their introKey in the MAC. If that does not verify,
the packet is dropped. If it does verify, it is interpreted
according to the message type, though if the receiver is overloaded,
it may be dropped anyway.</p>
<p>If Alice and Bob have an established session, but Alice loses the
keys for some reason and she wants to contact Bob, she may at any
time simply establish a new session through the SessionRequest and
related messages. If Bob has lost the key but Alice does not know
that, she will first attempt to prod him to reply, by sending a
DataMessage with the wantReply flag set, and if Bob continually
fails to reply, she will assume the key is lost and reestablish a
new one.</p>
<p>For the DH key agreement,
<a href="http://www.faqs.org/rfcs/rfc3526.html">RFC3526</a> 2048bit
MODP group (#14) is used:</p>
<pre>
p = 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }
g = 2
</pre>
<p>The DSA p, q, and g are shared according to the scope of the
identity which created them.</p>
<h2><a name="replay">Replay prevention</a></h2>
<p>Replay prevention at the SSU layer occurs by rejecting packets
with exceedingly old timestamps or those which reuse an IV. To
detect duplicate IVs, a sequence of Bloom filters are employed to
"decay" periodically so that only recently added IVs are detected.</p>
<p>The messageIds used in DataMessages are defined at layers above
the SSU transport and are passed through transparently. These IDs
are not in any particular order - in fact, they are likely to be
entirely random. The SSU layer makes no attempt at messageId
replay prevention - higher layers should take that into account.</p>
<h2><a name="messageSequences">Message sequences</a></h2>
<h3><a name="establishDirect">Connection establishment (direct)</a></h3>
<pre>
Alice Bob
SessionRequest---------------------&gt;
&lt;---------------------SessionCreated
SessionConfirmed-------------------&gt;
SessionConfirmed-------------------&gt;
SessionConfirmed-------------------&gt;
SessionConfirmed-------------------&gt;
&lt;--------------------------Data
</pre>
<h3><a name="establishIndirect">Connection establishment (indirect)</a></h3>
<pre>
Alice Bob Charlie
RelayRequest ----------------------&gt;
&lt;--------------RelayResponse RelayIntro-----------&gt;
&lt;--------------------------------------------Data (ignored)
SessionRequest--------------------------------------------&gt;
&lt;--------------------------------------------SessionCreated
SessionConfirmed------------------------------------------&gt;
SessionConfirmed------------------------------------------&gt;
SessionConfirmed------------------------------------------&gt;
SessionConfirmed------------------------------------------&gt;
&lt;---------------------------------------------------Data
</pre>
<h2><a name="sampleDatagrams">Sample datagrams</a></h2>
<b>Minimal data message (no fragments, no ACKs, no NACKs, etc)</b><br />
<i>(Size: 39 bytes)</i>
<pre>
+----+----+----+----+----+----+----+----+
| MAC |
| |
+----+----+----+----+----+----+----+----+
| IV |
| |
+----+----+----+----+----+----+----+----+
|flag| time |flag|#frg| |
+----+----+----+----+----+----+----+ |
| padding to fit a full AES256 block |
+----+----+----+----+----+----+----+----+
</pre>
<b>Minimal data message with payload</b><br />
<i>(Size: 46+fragmentSize bytes)</i>
<pre>
+----+----+----+----+----+----+----+----+
| MAC |
| |
+----+----+----+----+----+----+----+----+
| IV |
| |
+----+----+----+----+----+----+----+----+
|flag| time |flag|#frg|
+----+----+----+----+----+----+----+----+
messageId |info| fragSize| |
+----+----+----+----+----+----+ |
| that many bytes of fragment data |
. . .
| |
+----+----+----+----+----+----+----+----+
</pre>

View File

@@ -67,6 +67,8 @@ public interface I2NPMessage extends DataStructure {
*
*/
public long getMessageExpiration();
public void setMessageExpiration(long exp);
/** How large the message is, including any checksums */
public int getMessageSize();

View File

@@ -17,6 +17,7 @@ import net.i2p.data.i2np.DeliveryStatusMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.TunnelCreateMessage;
import net.i2p.data.i2np.TunnelCreateStatusMessage;
import net.i2p.data.i2np.TunnelDataMessage;
import net.i2p.data.i2np.TunnelGatewayMessage;
@@ -103,11 +104,15 @@ public class InNetMessagePool implements Service {
if (messageBody instanceof TunnelDataMessage) {
// do not validate the message with the validator - the IV validator is sufficient
} else {
boolean valid = _context.messageValidator().validateMessage(messageBody.getUniqueId(), exp);
if (!valid) {
if (_log.shouldLog(Log.WARN))
_log.warn("Duplicate message received [" + messageBody.getUniqueId()
+ " expiring on " + exp + "]: " + messageBody.getClass().getName());
String invalidReason = _context.messageValidator().validateMessage(messageBody.getUniqueId(), exp);
if (invalidReason != null) {
int level = Log.WARN;
if (messageBody instanceof TunnelCreateMessage)
level = Log.INFO;
if (_log.shouldLog(level))
_log.log(level, "Duplicate message received [" + messageBody.getUniqueId()
+ " expiring on " + exp + "]: " + messageBody.getClass().getName() + ": " + invalidReason
+ ": " + messageBody);
_context.statManager().addRateData("inNetPool.dropped", 1, 0);
_context.statManager().addRateData("inNetPool.duplicate", 1, 0);
_context.messageHistory().droppedOtherMessage(messageBody);

View File

@@ -318,7 +318,7 @@ public class MessageHistory {
*/
public void sendMessage(String messageType, long messageId, long expiration, Hash peer, boolean sentOk) {
if (!_doLog) return;
if (true) return;
if (false) return;
StringBuffer buf = new StringBuffer(256);
buf.append(getPrefix());
buf.append("send [").append(messageType).append("] message [").append(messageId).append("] ");
@@ -344,7 +344,7 @@ public class MessageHistory {
*/
public void receiveMessage(String messageType, long messageId, long expiration, Hash from, boolean isValid) {
if (!_doLog) return;
if (true) return;
if (false) return;
StringBuffer buf = new StringBuffer(256);
buf.append(getPrefix());
buf.append("receive [").append(messageType).append("] with id [").append(messageId).append("] ");

View File

@@ -31,20 +31,20 @@ public class MessageValidator {
/**
* Determine if this message should be accepted as valid (not expired, not a duplicate)
*
* @return true if the message should be accepted as valid, false otherwise
* @return reason why the message is invalid (or null if the message is valid)
*/
public boolean validateMessage(long messageId, long expiration) {
public String validateMessage(long messageId, long expiration) {
long now = _context.clock().now();
if (now - Router.CLOCK_FUDGE_FACTOR >= expiration) {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting message " + messageId + " because it expired " + (now-expiration) + "ms ago");
_context.statManager().addRateData("router.invalidMessageTime", (now-expiration), 0);
return false;
return "expired " + (now-expiration) + "ms ago";
} else if (now + 4*Router.CLOCK_FUDGE_FACTOR < expiration) {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting message " + messageId + " because it will expire too far in the future (" + (expiration-now) + "ms)");
_context.statManager().addRateData("router.invalidMessageTime", (now-expiration), 0);
return false;
return "expire too far in the future (" + (expiration-now) + "ms)";
}
boolean isDuplicate = noteReception(messageId, expiration);
@@ -52,11 +52,11 @@ public class MessageValidator {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting message " + messageId + " because it is a duplicate", new Exception("Duplicate origin"));
_context.statManager().addRateData("router.duplicateMessageId", 1, 0);
return false;
return "duplicate";
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Accepting message " + messageId + " because it is NOT a duplicate", new Exception("Original origin"));
return true;
return null;
}
}

View File

@@ -89,7 +89,7 @@ public class OutNetMessage {
*/
public long timestamp(String eventName) {
long now = _context.clock().now();
if (_log.shouldLog(Log.DEBUG)) {
if (_log.shouldLog(Log.INFO)) {
// only timestamp if we are debugging
synchronized (this) {
locked_initTimestamps();
@@ -103,7 +103,7 @@ public class OutNetMessage {
return now - _created;
}
public Map getTimestamps() {
if (_log.shouldLog(Log.DEBUG)) {
if (_log.shouldLog(Log.INFO)) {
synchronized (this) {
locked_initTimestamps();
return (Map)_timestamps.clone();
@@ -112,7 +112,7 @@ public class OutNetMessage {
return Collections.EMPTY_MAP;
}
public Long getTimestamp(String eventName) {
if (_log.shouldLog(Log.DEBUG)) {
if (_log.shouldLog(Log.INFO)) {
synchronized (this) {
locked_initTimestamps();
return (Long)_timestamps.get(eventName);
@@ -301,7 +301,7 @@ public class OutNetMessage {
}
private void renderTimestamps(StringBuffer buf) {
if (_log.shouldLog(Log.DEBUG)) {
if (_log.shouldLog(Log.INFO)) {
synchronized (this) {
long lastWhen = -1;
for (int i = 0; i < _timestampOrder.size(); i++) {

View File

@@ -371,7 +371,7 @@ public class Router {
RateStat sendRate = _context.statManager().getRate("transport.sendMessageSize");
if (sendRate != null) {
Rate rate = receiveRate.getRate(60*1000);
Rate rate = sendRate.getRate(60*1000);
if (rate != null) {
double bytes = rate.getLastTotalValue();
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);

View File

@@ -18,6 +18,7 @@ import net.i2p.router.peermanager.ProfileManagerImpl;
import net.i2p.router.peermanager.ProfileOrganizer;
import net.i2p.router.peermanager.ReliabilityCalculator;
import net.i2p.router.peermanager.SpeedCalculator;
import net.i2p.router.peermanager.StrictSpeedCalculator;
import net.i2p.router.transport.CommSystemFacadeImpl;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.router.transport.OutboundMessageRegistry;
@@ -62,6 +63,7 @@ public class RouterContext extends I2PAppContext {
private Calculator _speedCalc;
private Calculator _reliabilityCalc;
private Calculator _capacityCalc;
private Calculator _oldSpeedCalc;
private static List _contexts = new ArrayList(1);
@@ -115,6 +117,7 @@ public class RouterContext extends I2PAppContext {
_isFailingCalc = new IsFailingCalculator(this);
_integrationCalc = new IntegrationCalculator(this);
_speedCalc = new SpeedCalculator(this);
_oldSpeedCalc = new StrictSpeedCalculator(this);
_reliabilityCalc = new ReliabilityCalculator(this);
_capacityCalc = new CapacityCalculator(this);
}
@@ -248,6 +251,7 @@ public class RouterContext extends I2PAppContext {
public Calculator integrationCalculator() { return _integrationCalc; }
/** how do we rank the speed of profiles? */
public Calculator speedCalculator() { return _speedCalc; }
public Calculator oldSpeedCalculator() { return _oldSpeedCalc; }
/** how do we rank the reliability of profiles? */
public Calculator reliabilityCalculator() { return _reliabilityCalc; }
/** how do we rank the capacity of profiles? */

View File

@@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
*
*/
public class RouterVersion {
public final static String ID = "$Revision: 1.159 $ $Date: 2005/03/04 21:54:43 $";
public final static String VERSION = "0.5.0.2";
public final static String ID = "$Revision: 1.175 $ $Date: 2005/03/26 02:13:38 $";
public final static String VERSION = "0.5.0.5";
public final static long BUILD = 0;
public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION);

View File

@@ -102,24 +102,27 @@ public class StatisticsManager implements Service {
stats.putAll(_context.profileManager().summarizePeers(_publishedStats));
includeThroughput(stats);
includeRate("router.invalidMessageTime", stats, new long[] { 10*60*1000, 3*60*60*1000 });
includeRate("router.invalidMessageTime", stats, new long[] { 10*60*1000 });
includeRate("router.duplicateMessageId", stats, new long[] { 24*60*60*1000 });
includeRate("tunnel.duplicateIV", stats, new long[] { 24*60*60*1000 });
includeRate("tunnel.fragmentedComplete", stats, new long[] { 10*60*1000, 3*60*60*1000 });
includeRate("tunnel.fragmentedDropped", stats, new long[] { 10*60*1000, 3*60*60*1000 });
includeRate("tunnel.fullFragments", stats, new long[] { 10*60*1000, 3*60*60*1000 });
includeRate("tunnel.smallFragments", stats, new long[] { 10*60*1000, 3*60*60*1000 });
includeRate("tunnel.testFailedTime", stats, new long[] { 60*60*1000, 3*60*60*1000 });
includeRate("tunnel.dispatchOutboundTime", stats, new long[] { 60*60*1000 });
includeRate("tunnel.dispatchGatewayTime", stats, new long[] { 60*60*1000 });
includeRate("tunnel.dispatchDataTime", stats, new long[] { 60*60*1000 });
includeRate("tunnel.buildFailure", stats, new long[] { 10*60*1000, 60*60*1000 });
includeRate("tunnel.buildSuccess", stats, new long[] { 10*60*1000, 60*60*1000 });
includeRate("tunnel.testFailedTime", stats, new long[] { 60*60*1000 });
includeRate("tunnel.buildFailure", stats, new long[] { 60*60*1000 });
includeRate("tunnel.buildSuccess", stats, new long[] { 60*60*1000 });
//includeRate("tunnel.batchDelaySent", stats, new long[] { 10*60*1000, 60*60*1000 });
includeRate("tunnel.batchMultipleCount", stats, new long[] { 10*60*1000, 60*60*1000 });
includeRate("tunnel.corruptMessage", stats, new long[] { 60*60*1000l, 3*60*60*1000l });
includeRate("router.throttleTunnelProbTestSlow", stats, new long[] { 60*60*1000 });
includeRate("router.throttleTunnelProbTooFast", stats, new long[] { 60*60*1000 });
includeRate("router.throttleTunnelProcessingTime1m", stats, new long[] { 60*60*1000 });
includeRate("router.fastPeers", stats, new long[] { 60*60*1000 });
includeRate("clock.skew", stats, new long[] { 10*60*1000, 3*60*60*1000, 24*60*60*1000 });
includeRate("transport.sendProcessingTime", stats, new long[] { 60*60*1000 });

View File

@@ -109,6 +109,10 @@ public class GarlicMessageBuilder {
msg.setData(encData);
msg.setMessageExpiration(config.getExpiration());
long timeFromNow = config.getExpiration() - ctx.clock().now();
if (timeFromNow < 10*1000)
log.error("Building a message expiring in " + timeFromNow + "ms: " + config, new Exception("created by"));
if (log.shouldLog(Log.WARN))
log.warn("CloveSet size for message " + msg.getUniqueId() + " is " + cloveSet.length
+ " and encrypted message data is " + encData.length);

View File

@@ -99,13 +99,13 @@ public class GarlicMessageReceiver {
}
private boolean isValid(GarlicClove clove) {
boolean valid = _context.messageValidator().validateMessage(clove.getCloveId(),
clove.getExpiration().getTime());
if (!valid) {
String invalidReason = _context.messageValidator().validateMessage(clove.getCloveId(),
clove.getExpiration().getTime());
if (invalidReason != null) {
String howLongAgo = DataHelper.formatDuration(_context.clock().now()-clove.getExpiration().getTime());
if (_log.shouldLog(Log.ERROR))
_log.error("Clove is NOT valid: id=" + clove.getCloveId()
+ " expiration " + howLongAgo + " ago");
+ " expiration " + howLongAgo + " ago: " + invalidReason + ": " + clove);
if (_log.shouldLog(Log.WARN))
_log.warn("Clove is NOT valid: id=" + clove.getCloveId()
+ " expiration " + howLongAgo + " ago", new Exception("Invalid within..."));
@@ -113,6 +113,6 @@ public class GarlicMessageReceiver {
clove.getData().getClass().getName(),
"Clove is not valid (expiration " + howLongAgo + " ago)");
}
return valid;
return (invalidReason == null);
}
}

View File

@@ -52,10 +52,11 @@ class OutboundClientMessageJobHelper {
* @return garlic, or null if no tunnels were found (or other errors)
*/
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
Payload data, Hash from, Destination dest, SessionKey wrappedKey, Set wrappedTags,
Payload data, Hash from, Destination dest, TunnelInfo replyTunnel,
SessionKey wrappedKey, Set wrappedTags,
boolean requireAck, LeaseSet bundledReplyLeaseSet) {
PayloadGarlicConfig dataClove = buildDataClove(ctx, data, dest, expiration);
return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, wrappedKey,
return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, replyTunnel, wrappedKey,
wrappedTags, requireAck, bundledReplyLeaseSet);
}
/**
@@ -65,9 +66,9 @@ class OutboundClientMessageJobHelper {
* @return garlic, or null if no tunnels were found (or other errors)
*/
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
PayloadGarlicConfig dataClove, Hash from, Destination dest, SessionKey wrappedKey,
PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel, SessionKey wrappedKey,
Set wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet) {
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, requireAck, bundledReplyLeaseSet);
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, replyTunnel, requireAck, bundledReplyLeaseSet);
if (config == null)
return null;
GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, config, wrappedKey, wrappedTags);
@@ -75,7 +76,7 @@ class OutboundClientMessageJobHelper {
}
private static GarlicConfig createGarlicConfig(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
PayloadGarlicConfig dataClove, Hash from, Destination dest, boolean requireAck,
PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel, boolean requireAck,
LeaseSet bundledReplyLeaseSet) {
Log log = ctx.logManager().getLog(OutboundClientMessageJobHelper.class);
if (log.shouldLog(Log.DEBUG))
@@ -85,7 +86,7 @@ class OutboundClientMessageJobHelper {
config.addClove(dataClove);
if (requireAck) {
PayloadGarlicConfig ackClove = buildAckClove(ctx, from, replyToken, expiration);
PayloadGarlicConfig ackClove = buildAckClove(ctx, from, replyTunnel, replyToken, expiration);
if (ackClove == null)
return null; // no tunnels
config.addClove(ackClove);
@@ -108,7 +109,7 @@ class OutboundClientMessageJobHelper {
config.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
config.setDeliveryInstructions(instructions);
config.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE));
config.setExpiration(expiration+2*Router.CLOCK_FUDGE_FACTOR);
config.setExpiration(expiration); // +2*Router.CLOCK_FUDGE_FACTOR);
config.setRecipientPublicKey(recipientPK);
config.setRequestAck(false);
@@ -122,14 +123,13 @@ class OutboundClientMessageJobHelper {
/**
* Build a clove that sends a DeliveryStatusMessage to us
*/
private static PayloadGarlicConfig buildAckClove(RouterContext ctx, Hash from, long replyToken, long expiration) {
private static PayloadGarlicConfig buildAckClove(RouterContext ctx, Hash from, TunnelInfo replyToTunnel, long replyToken, long expiration) {
Log log = ctx.logManager().getLog(OutboundClientMessageJobHelper.class);
PayloadGarlicConfig ackClove = new PayloadGarlicConfig();
Hash replyToTunnelRouter = null; // inbound tunnel gateway
TunnelId replyToTunnelId = null; // tunnel id on that gateway
TunnelInfo replyToTunnel = ctx.tunnelManager().selectInboundTunnel(from);
if (replyToTunnel == null) {
if (log.shouldLog(Log.ERROR))
log.error("Unable to send client message from " + from.toBase64()

View File

@@ -51,6 +51,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
private int _clientMessageSize;
private Destination _from;
private Destination _to;
private String _toString;
/** target destination's leaseSet, if known */
private LeaseSet _leaseSet;
/** Actual lease the message is being routed through */
@@ -60,6 +61,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
private long _start;
private boolean _finished;
private long _leaseSetLookupBegin;
private TunnelInfo _outTunnel;
private TunnelInfo _inTunnel;
/**
* final timeout (in milliseconds) that the outbound message will fail in.
@@ -117,6 +120,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
_clientMessageSize = msg.getPayload().getSize();
_from = msg.getFromDestination();
_to = msg.getDestination();
_toString = _to.calculateHash().toBase64().substring(0,4);
_leaseSetLookupBegin = -1;
String param = msg.getSenderConfig().getOptions().getProperty(OVERALL_TIMEOUT_MS_PARAM);
@@ -146,22 +150,24 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
_log.debug(getJobId() + ": Send outbound client message job beginning");
buildClove();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Clove built");
_log.debug(getJobId() + ": Clove built to " + _toString);
long timeoutMs = _overallExpiration - getContext().clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": preparing to search for the leaseSet");
_log.debug(getJobId() + ": preparing to search for the leaseSet for " + _toString);
Hash key = _to.calculateHash();
SendJob success = new SendJob(getContext());
LookupLeaseSetFailedJob failed = new LookupLeaseSetFailedJob(getContext());
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Send outbound client message - sending off leaseSet lookup job");
LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(key);
if (ls != null) {
getContext().statManager().addRateData("client.leaseSetFoundLocally", 1, 0);
_leaseSetLookupBegin = -1;
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Send outbound client message - leaseSet found locally for " + _toString);
success.runJob();
} else {
_leaseSetLookupBegin = getContext().clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Send outbound client message - sending off leaseSet lookup job for " + _toString);
getContext().netDb().lookupLeaseSet(key, success, failed, timeoutMs);
}
}
@@ -203,8 +209,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
boolean ok = getNextLease();
if (ok)
send();
else
else {
if (_log.shouldLog(Log.ERROR))
_log.error("Unable to send on a random lease, as getNext returned null (to=" + _toString + ")");
dieFatal();
}
}
}
@@ -218,7 +227,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
_leaseSet = getContext().netDb().lookupLeaseSetLocally(_to.calculateHash());
if (_leaseSet == null) {
if (_log.shouldLog(Log.WARN))
_log.warn(getJobId() + ": Lookup locally didn't find the leaseSet");
_log.warn(getJobId() + ": Lookup locally didn't find the leaseSet for " + _toString);
return false;
}
long now = getContext().clock().now();
@@ -229,7 +238,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
Lease lease = _leaseSet.getLease(i);
if (lease.isExpired(Router.CLOCK_FUDGE_FACTOR)) {
if (_log.shouldLog(Log.WARN))
_log.warn(getJobId() + ": getNextLease() - expired lease! - " + lease);
_log.warn(getJobId() + ": getNextLease() - expired lease! - " + lease + " for " + _toString);
continue;
} else {
leases.add(lease);
@@ -278,6 +287,9 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
long lookupTime = getContext().clock().now() - _leaseSetLookupBegin;
getContext().statManager().addRateData("client.leaseSetFailedRemoteTime", lookupTime, lookupTime);
}
if (_log.shouldLog(Log.ERROR))
_log.error("Unable to send to " + _toString + " because we couldn't find their leaseSet");
dieFatal();
}
@@ -302,22 +314,26 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
replyLeaseSet = getContext().netDb().lookupLeaseSetLocally(_from.calculateHash());
}
_inTunnel = selectInboundTunnel();
GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token,
_overallExpiration, key,
_clove, _from.calculateHash(),
_to,
_to, _inTunnel,
sessKey, tags,
true, replyLeaseSet);
if (msg == null) {
// set to null if there are no tunnels to ack the reply back through
// (should we always fail for this? or should we send it anyway, even if
// we dont receive the reply? hmm...)
if (_log.shouldLog(Log.ERROR))
_log.error(getJobId() + ": Unable to create the garlic message (no tunnels left) to " + _toString);
dieFatal();
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": send() - token expected " + token);
_log.debug(getJobId() + ": send() - token expected " + token + " to " + _toString);
SendSuccessJob onReply = new SendSuccessJob(getContext(), sessKey, tags);
SendTimeoutJob onFail = new SendTimeoutJob(getContext());
@@ -325,18 +341,20 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Placing GarlicMessage into the new tunnel message bound for "
+ _toString + " at "
+ _lease.getTunnelId() + " on "
+ _lease.getGateway().toBase64());
TunnelInfo outTunnel = selectOutboundTunnel();
if (outTunnel != null) {
_outTunnel = selectOutboundTunnel();
if (_outTunnel != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Sending tunnel message out " + outTunnel.getSendTunnelId(0) + " to "
_log.debug(getJobId() + ": Sending tunnel message out " + _outTunnel.getSendTunnelId(0) + " to "
+ _toString + " at "
+ _lease.getTunnelId() + " on "
+ _lease.getGateway().toBase64());
// dispatch may take 100+ms, so toss it in its own job
getContext().jobQueue().addJob(new DispatchJob(getContext(), msg, outTunnel, selector, onReply, onFail, (int)(_overallExpiration-getContext().clock().now())));
getContext().jobQueue().addJob(new DispatchJob(getContext(), msg, selector, onReply, onFail, (int)(_overallExpiration-getContext().clock().now())));
} else {
if (_log.shouldLog(Log.ERROR))
_log.error(getJobId() + ": Could not find any outbound tunnels to send the payload through... wtf?");
@@ -348,15 +366,13 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
private class DispatchJob extends JobImpl {
private GarlicMessage _msg;
private TunnelInfo _outTunnel;
private ReplySelector _selector;
private SendSuccessJob _replyFound;
private SendTimeoutJob _replyTimeout;
private int _timeoutMs;
public DispatchJob(RouterContext ctx, GarlicMessage msg, TunnelInfo out, ReplySelector sel, SendSuccessJob success, SendTimeoutJob timeout, int timeoutMs) {
public DispatchJob(RouterContext ctx, GarlicMessage msg, ReplySelector sel, SendSuccessJob success, SendTimeoutJob timeout, int timeoutMs) {
super(ctx);
_msg = msg;
_outTunnel = out;
_selector = sel;
_replyFound = success;
_replyTimeout = timeout;
@@ -365,7 +381,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
public String getName() { return "Dispatch outbound client message"; }
public void runJob() {
getContext().messageRegistry().registerPending(_selector, _replyFound, _replyTimeout, _timeoutMs);
if (_log.shouldLog(Log.INFO))
_log.info("Dispatching message to " + _toString + ": " + _msg);
getContext().tunnelDispatcher().dispatchOutbound(_msg, _outTunnel.getSendTunnelId(0), _lease.getTunnelId(), _lease.getGateway());
if (_log.shouldLog(Log.INFO))
_log.info("Dispatching message to " + _toString + " complete");
}
}
@@ -378,6 +398,13 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
private TunnelInfo selectOutboundTunnel() {
return getContext().tunnelManager().selectOutboundTunnel(_from.calculateHash());
}
/**
* Pick an arbitrary outbound tunnel for any deliveryStatusMessage to come back in
*
*/
private TunnelInfo selectInboundTunnel() {
return getContext().tunnelManager().selectInboundTunnel(_from.calculateHash());
}
/**
* give up the ghost, this message just aint going through. tell the client to fuck off.
@@ -391,7 +418,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
long sendTime = getContext().clock().now() - _start;
if (_log.shouldLog(Log.WARN))
_log.warn(getJobId() + ": Failed to send the message " + _clientMessageId + " after "
+ sendTime + "ms", new Exception("Message send failure"));
+ sendTime + "ms");
long messageDelay = getContext().throttle().getMessageDelay();
long tunnelLag = getContext().throttle().getTunnelLag();
@@ -427,6 +454,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
DataMessage msg = new DataMessage(getContext());
msg.setData(_clientMessage.getPayload().getEncryptedData());
msg.setMessageExpiration(_overallExpiration);
clove.setPayload(msg);
clove.setRecipientPublicKey(null);
@@ -471,6 +499,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
return false;
}
}
public String toString() {
return "sending " + _toString + " waiting for token " + _pendingToken
+ " for cloveId " + _cloveId;
}
}
/**
@@ -516,8 +549,14 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
getContext().statManager().addRateData("client.sendAckTime", sendTime, 0);
getContext().statManager().addRateData("client.sendMessageSize", _clientMessageSize, sendTime);
if (_outTunnel != null)
for (int i = 0; i < _outTunnel.getLength(); i++)
getContext().profileManager().tunnelTestSucceeded(_outTunnel.getPeer(i), sendTime);
if (_inTunnel != null)
for (int i = 0; i < _inTunnel.getLength(); i++)
getContext().profileManager().tunnelTestSucceeded(_inTunnel.getPeer(i), sendTime);
}
public void setMessage(I2NPMessage msg) {}
}

View File

@@ -48,10 +48,10 @@ public class SendMessageDirectJob extends JobImpl {
_message = message;
_targetHash = toPeer;
_router = null;
if (timeoutMs < 5*1000) {
if (timeoutMs < 10*1000) {
if (_log.shouldLog(Log.WARN))
_log.warn("Very little time given [" + timeoutMs + "], resetting to 5s", new Exception("stingy bastard"));
_expiration = ctx.clock().now() + 5*1000;
_expiration = ctx.clock().now() + 10*1000;
} else {
_expiration = timeoutMs + ctx.clock().now();
}

View File

@@ -34,7 +34,7 @@ public class DatabaseLookupMessageHandler implements HandlerJobBuilder {
public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) {
_context.statManager().addRateData("netDb.lookupsReceived", 1, 0);
if (_context.throttle().acceptNetDbLookupRequest(((DatabaseLookupMessage)receivedMessage).getSearchKey())) {
if (true || _context.throttle().acceptNetDbLookupRequest(((DatabaseLookupMessage)receivedMessage).getSearchKey())) {
return new HandleDatabaseLookupMessageJob(_context, (DatabaseLookupMessage)receivedMessage, from, fromHash);
} else {
if (_log.shouldLog(Log.INFO))

View File

@@ -40,6 +40,7 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
private RouterIdentity _from;
private Hash _fromHash;
private final static int MAX_ROUTERS_RETURNED = 3;
private final static int CLOSENESS_THRESHOLD = 10; // StoreJob.REDUNDANCY * 2
private final static int REPLY_TIMEOUT = 60*1000;
private final static int MESSAGE_PRIORITY = 300;
@@ -48,6 +49,9 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
_log = getContext().logManager().getLog(HandleDatabaseLookupMessageJob.class);
getContext().statManager().createRateStat("netDb.lookupsHandled", "How many netDb lookups have we handled?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
getContext().statManager().createRateStat("netDb.lookupsMatched", "How many netDb lookups did we have the data for?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
getContext().statManager().createRateStat("netDb.lookupsMatchedReceivedPublished", "How many netDb lookups did we have the data for that were published to us?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
getContext().statManager().createRateStat("netDb.lookupsMatchedLocalClosest", "How many netDb lookups for local data were received where we are the closest peers?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
getContext().statManager().createRateStat("netDb.lookupsMatchedLocalNotClosest", "How many netDb lookups for local data were received where we are NOT the closest peers?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
_message = receivedMessage;
_from = from;
_fromHash = fromHash;
@@ -65,26 +69,26 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
+ " (tunnel " + _message.getReplyTunnel() + ")");
}
if (getContext().netDb().lookupRouterInfoLocally(_message.getFrom()) == null) {
// hmm, perhaps don't always send a lookup for this...
// but for now, wtf, why not. we may even want to adjust it so that
// we penalize or benefit peers who send us that which we can or
// cannot lookup
getContext().netDb().lookupRouterInfo(_message.getFrom(), null, null, REPLY_TIMEOUT);
}
// whatdotheywant?
handleRequest(fromKey);
}
private void handleRequest(Hash fromKey) {
LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(_message.getSearchKey());
if (ls != null) {
// send that lease set to the _message.getFromHash peer
if (_log.shouldLog(Log.DEBUG))
_log.debug("We do have key " + _message.getSearchKey().toBase64()
+ " locally as a lease set. sending to " + fromKey.toBase64());
sendData(_message.getSearchKey(), ls, fromKey, _message.getReplyTunnel());
// only answer a request for a LeaseSet if it has been published
// to us, or, if its local, if we would have published to ourselves
if (ls.getReceivedAsPublished()) {
getContext().statManager().addRateData("netDb.lookupsMatchedReceivedPublished", 1, 0);
sendData(_message.getSearchKey(), ls, fromKey, _message.getReplyTunnel());
} else {
Set routerInfoSet = getContext().netDb().findNearestRouters(_message.getSearchKey(),
CLOSENESS_THRESHOLD,
_message.getDontIncludePeers());
if (getContext().clientManager().isLocal(ls.getDestination()) &&
weAreClosest(routerInfoSet)) {
getContext().statManager().addRateData("netDb.lookupsMatchedLocalClosest", 1, 0);
sendData(_message.getSearchKey(), ls, fromKey, _message.getReplyTunnel());
} else {
getContext().statManager().addRateData("netDb.lookupsMatchedLocalNotClosest", 1, 0);
sendClosest(_message.getSearchKey(), routerInfoSet, fromKey, _message.getReplyTunnel());
}
}
} else {
RouterInfo info = getContext().netDb().lookupRouterInfoLocally(_message.getSearchKey());
if (info != null) {
@@ -106,6 +110,17 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
}
}
private boolean weAreClosest(Set routerInfoSet) {
boolean weAreClosest = false;
for (Iterator iter = routerInfoSet.iterator(); iter.hasNext(); ) {
RouterInfo cur = (RouterInfo)iter.next();
if (cur.getIdentity().calculateHash().equals(getContext().routerHash())) {
return true;
}
}
return false;
}
private void sendData(Hash key, DataStructure data, Hash toPeer, TunnelId replyTunnel) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending data matching key key " + key.toBase64() + " to peer " + toPeer.toBase64()
@@ -129,27 +144,27 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
_log.debug("Sending closest routers to key " + key.toBase64() + ": # peers = "
+ routerInfoSet.size() + " tunnel " + replyTunnel);
DatabaseSearchReplyMessage msg = new DatabaseSearchReplyMessage(getContext());
msg.setFromHash(getContext().router().getRouterInfo().getIdentity().getHash());
msg.setFromHash(getContext().routerHash());
msg.setSearchKey(key);
for (Iterator iter = routerInfoSet.iterator(); iter.hasNext(); ) {
RouterInfo peer = (RouterInfo)iter.next();
msg.addReply(peer.getIdentity().getHash());
if (msg.getNumReplies() >= MAX_ROUTERS_RETURNED)
break;
}
getContext().statManager().addRateData("netDb.lookupsHandled", 1, 0);
sendMessage(msg, toPeer, replyTunnel); // should this go via garlic messages instead?
}
private void sendMessage(I2NPMessage message, Hash toPeer, TunnelId replyTunnel) {
Job send = null;
if (replyTunnel != null) {
sendThroughTunnel(message, toPeer, replyTunnel);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending reply directly to " + toPeer);
send = new SendMessageDirectJob(getContext(), message, toPeer, REPLY_TIMEOUT, MESSAGE_PRIORITY);
Job send = new SendMessageDirectJob(getContext(), message, toPeer, REPLY_TIMEOUT, MESSAGE_PRIORITY);
getContext().netDb().lookupRouterInfo(toPeer, send, null, REPLY_TIMEOUT);
}
getContext().netDb().lookupRouterInfo(toPeer, send, null, REPLY_TIMEOUT);
}
private void sendThroughTunnel(I2NPMessage message, Hash toPeer, TunnelId replyTunnel) {
@@ -171,28 +186,6 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
}
}
private void sendToGateway(I2NPMessage message, Hash toPeer, TunnelId replyTunnel, TunnelInfo info) {
if (_log.shouldLog(Log.INFO))
_log.info("Want to reply to a db request via a tunnel, but we're a participant in the reply! so send it to the gateway");
if ( (toPeer == null) || (replyTunnel == null) ) {
if (_log.shouldLog(Log.ERROR))
_log.error("Someone br0ke us. where is this message supposed to go again?", getAddedBy());
return;
}
long expiration = REPLY_TIMEOUT + getContext().clock().now();
TunnelGatewayMessage msg = new TunnelGatewayMessage(getContext());
msg.setMessage(message);
msg.setTunnelId(replyTunnel);
msg.setMessageExpiration(expiration);
getContext().jobQueue().addJob(new SendMessageDirectJob(getContext(), msg, toPeer, null, null, null, null, REPLY_TIMEOUT, MESSAGE_PRIORITY));
String bodyType = message.getClass().getName();
getContext().messageHistory().wrap(bodyType, message.getUniqueId(), TunnelGatewayMessage.class.getName(), msg.getUniqueId());
}
public String getName() { return "Handle Database Lookup Message"; }
public void dropped() {

View File

@@ -10,6 +10,7 @@ package net.i2p.router.networkdb;
import java.util.Date;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterIdentity;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.DeliveryStatusMessage;
@@ -48,8 +49,18 @@ public class HandleDatabaseStoreMessageJob extends JobImpl {
boolean wasNew = false;
if (_message.getValueType() == DatabaseStoreMessage.KEY_TYPE_LEASESET) {
try {
Object match = getContext().netDb().store(_message.getKey(), _message.getLeaseSet());
wasNew = (null == match);
LeaseSet ls = _message.getLeaseSet();
// mark it as something we received, so we'll answer queries
// for it. this flag does NOT get set on entries that we
// receive in response to our own lookups.
ls.setReceivedAsPublished(true);
LeaseSet match = getContext().netDb().store(_message.getKey(), _message.getLeaseSet());
if (match == null) {
wasNew = true;
} else {
wasNew = false;
match.setReceivedAsPublished(true);
}
} catch (IllegalArgumentException iae) {
invalidMessage = iae.getMessage();
}

View File

@@ -264,8 +264,9 @@ class SearchJob extends JobImpl {
_log.error(getJobId() + ": Dont send search to ourselves - why did we try?");
return;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Send search to " + router.getIdentity().getHash().toBase64());
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Send search to " + router.getIdentity().getHash().toBase64()
+ " for " + _state.getTarget().toBase64());
}
getContext().statManager().addRateData("netDb.searchMessageCount", 1, 0);
@@ -330,8 +331,8 @@ class SearchJob extends JobImpl {
DatabaseLookupMessage msg = buildMessage(expiration);
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Sending router search to " + router.getIdentity().getHash().toBase64()
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Sending router search to " + router.getIdentity().getHash().toBase64()
+ " for " + msg.getSearchKey().toBase64() + " w/ replies to us ["
+ msg.getFrom().toBase64() + "]");
SearchMessageSelector sel = new SearchMessageSelector(getContext(), router, _expiration, _state);

View File

@@ -36,8 +36,8 @@ class StoreJob extends JobImpl {
private long _expiration;
private PeerSelector _peerSelector;
private final static int PARALLELIZATION = 1; // how many sent at a time
private final static int REDUNDANCY = 2; // we want the data sent to 2 peers
private final static int PARALLELIZATION = 2; // how many sent at a time
private final static int REDUNDANCY = 6; // we want the data sent to 6 peers
/**
* additionally send to 1 outlier(s), in case all of the routers chosen in our
* REDUNDANCY set are attacking us by accepting DbStore messages but dropping

View File

@@ -19,6 +19,7 @@ public class PeerProfile {
private long _lastSentToSuccessfully;
private long _lastFailedSend;
private long _lastHeardFrom;
private double _tunnelTestResponseTimeAvg;
// periodic rates
private RateStat _sendSuccessSize = null;
private RateStat _sendFailureSize = null;
@@ -26,6 +27,7 @@ public class PeerProfile {
private RateStat _dbResponseTime = null;
private RateStat _tunnelCreateResponseTime = null;
private RateStat _tunnelTestResponseTime = null;
private RateStat _tunnelTestResponseTimeSlow = null;
private RateStat _commError = null;
private RateStat _dbIntroduction = null;
// calculation bonuses
@@ -38,6 +40,7 @@ public class PeerProfile {
private double _reliabilityValue;
private double _capacityValue;
private double _integrationValue;
private double _oldSpeedValue;
private boolean _isFailing;
// good vs bad behavior
private TunnelHistory _tunnelHistory;
@@ -59,6 +62,7 @@ public class PeerProfile {
_integrationValue = 0;
_isFailing = false;
_consecutiveShitlists = 0;
_tunnelTestResponseTimeAvg = 0.0d;
_peer = peer;
if (expand)
expandProfile();
@@ -142,6 +146,8 @@ public class PeerProfile {
public RateStat getTunnelCreateResponseTime() { return _tunnelCreateResponseTime; }
/** how long it takes to successfully test a tunnel this peer participates in (in milliseconds), calculated over a 10 minute, 1 hour, and 1 day period */
public RateStat getTunnelTestResponseTime() { return _tunnelTestResponseTime; }
/** how long it takes to successfully test the peer (in milliseconds) when the time exceeds 5s */
public RateStat getTunnelTestResponseTimeSlow() { return _tunnelTestResponseTimeSlow; }
/** how long between communication errors with the peer (disconnection, etc), calculated over a 1 minute, 1 hour, and 1 day period */
public RateStat getCommError() { return _commError; }
/** how many new peers we get from dbSearchReplyMessages or dbStore messages, calculated over a 1 hour, 1 day, and 1 week period */
@@ -186,6 +192,7 @@ public class PeerProfile {
*
*/
public double getSpeedValue() { return _speedValue; }
public double getOldSpeedValue() { return _oldSpeedValue; }
/**
* How likely are they to stay up and pass on messages over the next few minutes.
* Positive numbers means more likely, negative numbers means its probably not
@@ -208,6 +215,24 @@ public class PeerProfile {
* is this peer actively failing (aka not worth touching)?
*/
public boolean getIsFailing() { return _isFailing; }
public double getTunnelTestTimeAverage() { return _tunnelTestResponseTimeAvg; }
void setTunnelTestTimeAverage(double avg) { _tunnelTestResponseTimeAvg = avg; }
void updateTunnelTestTimeAverage(long ms) {
if (_tunnelTestResponseTimeAvg <= 0)
_tunnelTestResponseTimeAvg = 30*1000; // should we instead start at $ms?
// weighted since we want to let the average grow quickly and shrink slowly
if (ms < _tunnelTestResponseTimeAvg)
_tunnelTestResponseTimeAvg = 0.95*_tunnelTestResponseTimeAvg + .05*(double)ms;
else
_tunnelTestResponseTimeAvg = 0.75*_tunnelTestResponseTimeAvg + .25*(double)ms;
if ( (_peer != null) && (_log.shouldLog(Log.INFO)) )
_log.info("Updating tunnel test time for " + _peer.toBase64().substring(0,6)
+ " to " + _tunnelTestResponseTimeAvg + " via " + ms);
}
/**
* when the given peer is performing so poorly that we don't want to bother keeping
@@ -222,6 +247,7 @@ public class PeerProfile {
_dbResponseTime = null;
_tunnelCreateResponseTime = null;
_tunnelTestResponseTime = null;
_tunnelTestResponseTimeSlow = null;
_commError = null;
_dbIntroduction = null;
_tunnelHistory = null;
@@ -250,7 +276,9 @@ public class PeerProfile {
if (_tunnelCreateResponseTime == null)
_tunnelCreateResponseTime = new RateStat("tunnelCreateResponseTime", "how long it takes to get a tunnel create response from the peer (in milliseconds)", group, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_tunnelTestResponseTime == null)
_tunnelTestResponseTime = new RateStat("tunnelTestResponseTime", "how long it takes to successfully test a tunnel this peer participates in (in milliseconds)", group, new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000 } );
_tunnelTestResponseTime = new RateStat("tunnelTestResponseTime", "how long it takes to successfully test a tunnel this peer participates in (in milliseconds)", group, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 } );
if (_tunnelTestResponseTimeSlow == null)
_tunnelTestResponseTimeSlow = new RateStat("tunnelTestResponseTimeSlow", "how long it takes to successfully test a peer when the time exceeds 5s", group, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l, });
if (_commError == null)
_commError = new RateStat("commErrorRate", "how long between communication errors with the peer (e.g. disconnection)", group, new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000 } );
if (_dbIntroduction == null)
@@ -267,6 +295,7 @@ public class PeerProfile {
_dbResponseTime.setStatLog(_context.statManager().getStatLog());
_tunnelCreateResponseTime.setStatLog(_context.statManager().getStatLog());
_tunnelTestResponseTime.setStatLog(_context.statManager().getStatLog());
_tunnelTestResponseTimeSlow.setStatLog(_context.statManager().getStatLog());
_commError.setStatLog(_context.statManager().getStatLog());
_dbIntroduction.setStatLog(_context.statManager().getStatLog());
_expanded = true;
@@ -283,10 +312,12 @@ public class PeerProfile {
_sendSuccessSize.coalesceStats();
_tunnelCreateResponseTime.coalesceStats();
_tunnelTestResponseTime.coalesceStats();
_tunnelTestResponseTimeSlow.coalesceStats();
_dbHistory.coalesceStats();
_tunnelHistory.coalesceStats();
_speedValue = calculateSpeed();
_oldSpeedValue = calculateOldSpeed();
_reliabilityValue = calculateReliability();
_capacityValue = calculateCapacity();
_integrationValue = calculateIntegration();
@@ -297,6 +328,7 @@ public class PeerProfile {
}
private double calculateSpeed() { return _context.speedCalculator().calc(this); }
private double calculateOldSpeed() { return _context.oldSpeedCalculator().calc(this); }
private double calculateReliability() { return _context.reliabilityCalculator().calc(this); }
private double calculateCapacity() { return _context.capacityCalculator().calc(this); }
private double calculateIntegration() { return _context.integrationCalculator().calc(this); }

View File

@@ -46,7 +46,7 @@ class PersistProfilesJob extends JobImpl {
PersistProfilesJob.this.getContext().jobQueue().addJob(PersistProfilesJob.this);
} else {
// we've got peers left to persist, so requeue the persist profile job
PersistProfilesJob.this.getContext().jobQueue().addJob(PersistProfileJob.this);
PersistProfilesJob.PersistProfileJob.this.requeue(1000);
}
}
public String getName() { return "Persist profile"; }

View File

@@ -111,7 +111,15 @@ public class ProfileManagerImpl implements ProfileManager {
public void tunnelTestSucceeded(Hash peer, long responseTimeMs) {
PeerProfile data = getProfile(peer);
if (data == null) return;
data.updateTunnelTestTimeAverage(responseTimeMs);
data.getTunnelTestResponseTime().addData(responseTimeMs, responseTimeMs);
if (responseTimeMs > getSlowThreshold())
data.getTunnelTestResponseTimeSlow().addData(responseTimeMs, responseTimeMs);
}
private int getSlowThreshold() {
// perhaps we should have this compare vs. tunnel.testSuccessTime?
return 5*1000;
}
/**

View File

@@ -357,7 +357,7 @@ public class ProfileOrganizer {
// we dont want the good peers, just random ones
continue;
} else {
if (isOk(cur))
if (isSelectable(cur))
selected.add(cur);
}
}
@@ -474,7 +474,7 @@ public class ProfileOrganizer {
for (Iterator iter = _strictCapacityOrder.iterator(); iter.hasNext(); ) {
PeerProfile cur = (PeerProfile)iter.next();
if ( (!_fastPeers.containsKey(cur.getPeer())) && (!cur.getIsFailing()) ) {
if (!isOk(cur.getPeer())) {
if (!isSelectable(cur.getPeer())) {
// skip peers we dont have in the netDb
if (_log.shouldLog(Log.INFO))
_log.info("skip unknown peer from fast promotion: " + cur.getPeer().toBase64());
@@ -644,6 +644,10 @@ public class ProfileOrganizer {
* (highest first) for active nonfailing peers
*/
private void locked_calculateSpeedThreshold(Set reordered) {
if (true) {
locked_calculateSpeedThresholdMean(reordered);
return;
}
Set speeds = new TreeSet();
for (Iterator iter = reordered.iterator(); iter.hasNext(); ) {
PeerProfile profile = (PeerProfile)iter.next();
@@ -669,6 +673,28 @@ public class ProfileOrganizer {
_log.info("Threshold value for speed: " + _thresholdSpeedValue + " out of speeds: " + speeds);
}
private void locked_calculateSpeedThresholdMean(Set reordered) {
double total = 0;
int count = 0;
for (Iterator iter = reordered.iterator(); iter.hasNext(); ) {
PeerProfile profile = (PeerProfile)iter.next();
if (profile.getCapacityValue() >= _thresholdCapacityValue) {
// duplicates being clobbered is fine by us
total += profile.getSpeedValue();
count++;
} else {
// its ordered
break;
}
}
if (count > 0)
_thresholdSpeedValue = total / count;
if (_log.shouldLog(Log.INFO))
_log.info("Threshold value for speed: " + _thresholdSpeedValue + " out of speeds: " + count);
}
/** simple average, or 0 if NaN */
private final static double avg(double total, double quantity) {
if ( (total > 0) && (quantity > 0) )
@@ -701,7 +727,7 @@ public class ProfileOrganizer {
Collections.shuffle(all, _random);
for (int i = 0; (matches.size() < howMany) && (i < all.size()); i++) {
Hash peer = (Hash)all.get(i);
boolean ok = isOk(peer);
boolean ok = isSelectable(peer);
if (ok)
matches.add(peer);
else
@@ -709,7 +735,7 @@ public class ProfileOrganizer {
}
}
private boolean isOk(Hash peer) {
public boolean isSelectable(Hash peer) {
NetworkDatabaseFacade netDb = _context.netDb();
// the CLI shouldn't depend upon the netDb
if (netDb == null) return true;
@@ -755,7 +781,7 @@ public class ProfileOrganizer {
if (_log.shouldLog(Log.DEBUG))
_log.debug("High capacity: \t" + profile.getPeer().toBase64());
if (_thresholdSpeedValue <= profile.getSpeedValue()) {
if (!isOk(profile.getPeer())) {
if (!isSelectable(profile.getPeer())) {
if (_log.shouldLog(Log.INFO))
_log.info("Skipping fast mark [!ok] for " + profile.getPeer().toBase64());
} else if (!profile.getIsActive()) {

View File

@@ -6,10 +6,11 @@ import java.io.Writer;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
@@ -21,23 +22,25 @@ import net.i2p.router.RouterContext;
class ProfileOrganizerRenderer {
private RouterContext _context;
private ProfileOrganizer _organizer;
private ProfileComparator _comparator;
public ProfileOrganizerRenderer(ProfileOrganizer organizer, RouterContext context) {
_context = context;
_organizer = organizer;
_comparator = new ProfileComparator();
}
public void renderStatusHTML(Writer out) throws IOException {
Set peers = _organizer.selectAllPeers();
long hideBefore = _context.clock().now() - 3*60*60*1000;
TreeMap order = new TreeMap();
TreeSet order = new TreeSet(_comparator);
for (Iterator iter = peers.iterator(); iter.hasNext();) {
Hash peer = (Hash)iter.next();
if (_organizer.getUs().equals(peer)) continue;
PeerProfile prof = _organizer.getProfile(peer);
if (prof.getLastSendSuccessful() <= hideBefore) continue;
order.put(peer.toBase64(), prof);
order.add(prof);
}
int fast = 0;
@@ -56,11 +59,35 @@ class ProfileOrganizerRenderer {
buf.append("<td><b>Failing?</b></td>");
buf.append("<td>&nbsp;</td>");
buf.append("</tr>");
for (Iterator iter = order.keySet().iterator(); iter.hasNext();) {
String name = (String)iter.next();
PeerProfile prof = (PeerProfile)order.get(name);
int prevTier = 1;
for (Iterator iter = order.iterator(); iter.hasNext();) {
PeerProfile prof = (PeerProfile)iter.next();
Hash peer = prof.getPeer();
int tier = 0;
boolean isIntegrated = false;
if (_organizer.isFast(peer)) {
tier = 1;
fast++;
reliable++;
} else if (_organizer.isHighCapacity(peer)) {
tier = 2;
reliable++;
} else if (_organizer.isFailing(peer)) {
failing++;
} else {
tier = 3;
}
if (_organizer.isWellIntegrated(peer)) {
isIntegrated = true;
integrated++;
}
if (tier != prevTier)
buf.append("<tr><td colspan=\"7\"><hr /></td></tr>\n");
prevTier = tier;
buf.append("<tr>");
buf.append("<td><code>");
if (prof.getIsFailing()) {
@@ -74,25 +101,6 @@ class ProfileOrganizerRenderer {
}
buf.append("</code></td>");
buf.append("<td>");
int tier = 0;
boolean isIntegrated = false;
if (_organizer.isFast(peer)) {
tier = 1;
fast++;
reliable++;
} else if (_organizer.isHighCapacity(peer)) {
tier = 2;
reliable++;
} else if (_organizer.isFailing(peer)) {
failing++;
} else {
tier = 3;
}
if (_organizer.isWellIntegrated(peer)) {
isIntegrated = true;
integrated++;
}
switch (tier) {
case 1: buf.append("Fast"); break;
@@ -102,7 +110,9 @@ class ProfileOrganizerRenderer {
}
if (isIntegrated) buf.append(", Integrated");
buf.append("<td align=\"right\">").append(num(prof.getSpeedValue())).append("</td>");
buf.append("<td align=\"right\">").append(num(prof.getSpeedValue()));
//buf.append('/').append(num(prof.getOldSpeedValue()));
buf.append("</td>");
buf.append("<td align=\"right\">").append(num(prof.getCapacityValue())).append("</td>");
buf.append("<td align=\"right\">").append(num(prof.getIntegrationValue())).append("</td>");
buf.append("<td align=\"right\">").append(prof.getIsFailing()).append("</td>");
@@ -130,6 +140,56 @@ class ProfileOrganizerRenderer {
out.flush();
}
private class ProfileComparator implements Comparator {
public int compare(Object lhs, Object rhs) {
if ( (lhs == null) || (rhs == null) )
throw new NullPointerException("lhs=" + lhs + " rhs=" + rhs);
if ( !(lhs instanceof PeerProfile) || !(rhs instanceof PeerProfile) )
throw new ClassCastException("lhs=" + lhs.getClass().getName() + " rhs=" + rhs.getClass().getName());
PeerProfile left = (PeerProfile)lhs;
PeerProfile right = (PeerProfile)rhs;
if (_context.profileOrganizer().isFast(left.getPeer())) {
if (_context.profileOrganizer().isFast(right.getPeer())) {
return compareHashes(left, right);
} else {
return -1; // fast comes first
}
} else if (_context.profileOrganizer().isHighCapacity(left.getPeer())) {
if (_context.profileOrganizer().isFast(right.getPeer())) {
return 1;
} else if (_context.profileOrganizer().isHighCapacity(right.getPeer())) {
return compareHashes(left, right);
} else {
return -1;
}
} else if (_context.profileOrganizer().isFailing(left.getPeer())) {
if (_context.profileOrganizer().isFailing(right.getPeer())) {
return compareHashes(left, right);
} else {
return 1;
}
} else {
// left is not failing
if (_context.profileOrganizer().isFast(right.getPeer())) {
return 1;
} else if (_context.profileOrganizer().isHighCapacity(right.getPeer())) {
return 1;
} else if (_context.profileOrganizer().isFailing(right.getPeer())) {
return -1;
} else {
return compareHashes(left, right);
}
}
}
private int compareHashes(PeerProfile left, PeerProfile right) {
return left.getPeer().toBase64().compareTo(right.getPeer().toBase64());
}
}
private final static DecimalFormat _fmt = new DecimalFormat("###,##0.00", new DecimalFormatSymbols(Locale.UK));
private final static String num(double num) { synchronized (_fmt) { return _fmt.format(num); } }
}

View File

@@ -109,6 +109,8 @@ class ProfilePersistenceHelper {
buf.append("lastFailedSend=").append(profile.getLastSendFailed()).append(NL);
buf.append("# Last heard from: when did we last get a message from the peer? (milliseconds from the epoch)").append(NL);
buf.append("lastHeardFrom=").append(profile.getLastHeardFrom()).append(NL);
buf.append("# moving average as to how fast the peer replies").append(NL);
buf.append("tunnelTestTimeAverage=").append(profile.getTunnelTestTimeAverage()).append(NL);
buf.append(NL);
out.write(buf.toString().getBytes());
@@ -126,6 +128,7 @@ class ProfilePersistenceHelper {
profile.getSendSuccessSize().store(out, "sendSuccessSize");
profile.getTunnelCreateResponseTime().store(out, "tunnelCreateResponseTime");
profile.getTunnelTestResponseTime().store(out, "tunnelTestResponseTime");
profile.getTunnelTestResponseTimeSlow().store(out, "tunnelTestResponseTimeSlow");
}
}
@@ -177,6 +180,7 @@ class ProfilePersistenceHelper {
profile.setLastSendSuccessful(getLong(props, "lastSentToSuccessfully"));
profile.setLastSendFailed(getLong(props, "lastFailedSend"));
profile.setLastHeardFrom(getLong(props, "lastHeardFrom"));
profile.setTunnelTestTimeAverage(getDouble(props, "tunnelTestTimeAverage"));
profile.getTunnelHistory().load(props);
profile.getDBHistory().load(props);
@@ -189,6 +193,7 @@ class ProfilePersistenceHelper {
profile.getSendSuccessSize().load(props, "sendSuccessSize", true);
profile.getTunnelCreateResponseTime().load(props, "tunnelCreateResponseTime", true);
profile.getTunnelTestResponseTime().load(props, "tunnelTestResponseTime", true);
profile.getTunnelTestResponseTimeSlow().load(props, "tunnelTestResponseTimeSlow", true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Loaded the profile for " + peer.toBase64() + " from " + file.getName());
@@ -212,6 +217,18 @@ class ProfilePersistenceHelper {
}
return 0;
}
private final static double getDouble(Properties props, String key) {
String val = props.getProperty(key);
if (val != null) {
try {
return Double.parseDouble(val);
} catch (NumberFormatException nfe) {
return 0.0;
}
}
return 0.0;
}
private void loadProps(Properties props, File file) {
try {

View File

@@ -38,6 +38,7 @@ public class SpeedCalculator extends Calculator {
}
public double calc(PeerProfile profile) {
if (true) return calcAverage(profile);
long threshold = getEventThreshold();
boolean tunnelTestOnly = getUseTunnelTestOnly();
@@ -72,10 +73,35 @@ public class SpeedCalculator extends Calculator {
double estimateFactor = getEstimateFactor(threshold, events);
double rv = (1-estimateFactor)*measuredRTPerMinute + (estimateFactor)*estimatedRTPerMinute;
long slowCount = 0;
RateStat rs = profile.getTunnelTestResponseTimeSlow();
if (rs != null) {
Rate r = rs.getRate(period);
if (r != null)
slowCount = r.getCurrentEventCount();
}
long fastCount = 0;
rs = profile.getTunnelTestResponseTime();
if (rs != null) {
Rate r = rs.getRate(period);
if (r != null)
fastCount = r.getCurrentEventCount();
}
double slowPct = 0;
if (fastCount > 0)
slowPct = slowCount / fastCount;
else
rv /= 100; // if there are no tunnel tests, weigh against it
if (slowPct > 0.01) // if 1% of the traffic is dogshit slow, the peer probably sucks
rv /= 100.0*slowPct;
rv = adjust(period, rv);
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("\n\nrv: " + rv + " events: " + events + " threshold: " + threshold + " period: " + period + " useTunnelTestOnly? " + tunnelTestOnly + "\n"
+ "measuredRTT: " + measuredRoundTripTime + " measured events per minute: " + measuredRTPerMinute + "\n"
+ "estimateRTT: " + estimatedRoundTripTime + " estimated events per minute: " + estimatedRTPerMinute + "\n"
+ "slow count: " + slowCount + " fast count: " + fastCount + "\n"
+ "estimateFactor: " + estimateFactor + "\n"
+ "for peer: " + profile.getPeer().toBase64());
}
@@ -83,6 +109,27 @@ public class SpeedCalculator extends Calculator {
rv += profile.getSpeedBonus();
return rv;
}
private double calcAverage(PeerProfile profile) {
double avg = profile.getTunnelTestTimeAverage();
if (avg == 0)
return 0.0;
else
return (60.0*1000.0) / avg;
}
private double adjust(long period, double value) {
switch ((int)period) {
case 10*60*1000:
return value;
case 60*60*1000:
return value * 0.75;
case 24*60*60*1000:
return value * 0.1;
default:
return value * 0.01;
}
}
/**
* How much do we want to prefer the measured values more than the estimated

View File

@@ -0,0 +1,90 @@
package net.i2p.router.peermanager;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
* Simple speed calculator that just counts how many messages go through the
* tunnel.
*
*/
public class StrictSpeedCalculator extends Calculator {
private Log _log;
private RouterContext _context;
public StrictSpeedCalculator(RouterContext context) {
_context = context;
_log = context.logManager().getLog(StrictSpeedCalculator.class);
}
public double calc(PeerProfile profile) {
return countSuccesses(profile);
}
private double countSuccesses(PeerProfile profile) {
RateStat success = profile.getTunnelHistory().getProcessSuccessRate();
RateStat failure = profile.getTunnelHistory().getProcessFailureRate();
return messagesPerMinute(success, failure);
}
private double messagesPerMinute(RateStat success, RateStat failure) {
double rv = 0.0d;
if (success != null) {
Rate rate = null;
long periods[] = success.getPeriods();
for (int i = 0; i < periods.length; i++) {
rate = success.getRate(periods[i]);
if ( (rate != null) && (rate.getCurrentTotalValue() > 0) )
break;
}
double value = rate.getCurrentTotalValue();
value += rate.getLastTotalValue();
rv = value * 10.0d * 60.0d * 1000.0d / (double)rate.getPeriod();
// if any of the messages are getting fragmented and cannot be
// handled, penalize like crazy
Rate fail = failure.getRate(rate.getPeriod());
if (fail.getCurrentTotalValue() > 0)
rv /= fail.getCurrentTotalValue();
}
return rv;
}
/*
public double calc(PeerProfile profile) {
double successCount = countSuccesses(profile);
double failureCount = countFailures(profile);
double rv = successCount - 5*failureCount;
if (rv < 0)
rv = 0;
return rv;
}
private double countSuccesses(PeerProfile profile) {
RateStat success = profile.getTunnelHistory().getProcessSuccessRate();
return messagesPerMinute(success);
}
private double countFailures(PeerProfile profile) {
RateStat failure = profile.getTunnelHistory().getProcessFailureRate();
return messagesPerMinute(failure);
}
private double messagesPerMinute(RateStat stat) {
double rv = 0.0d;
if (stat != null) {
Rate rate = null;
long periods[] = stat.getPeriods();
for (int i = 0; i < periods.length; i++) {
rate = stat.getRate(periods[i]);
if ( (rate != null) && (rate.getCurrentTotalValue() > 0) )
break;
}
double value = rate.getCurrentTotalValue();
value += rate.getLastTotalValue();
rv = value * 60.0d * 1000.0d / (double)rate.getPeriod();
}
return rv;
}
*/
}

View File

@@ -26,6 +26,8 @@ public class TunnelHistory {
private volatile long _lastFailed;
private RateStat _rejectRate;
private RateStat _failRate;
private RateStat _processSuccessRate;
private RateStat _processFailureRate;
private String _statGroup;
/** probabalistic tunnel rejection due to a flood of requests */
@@ -47,8 +49,12 @@ public class TunnelHistory {
private void createRates(String statGroup) {
_rejectRate = new RateStat("tunnelHistory.rejectRate", "How often does this peer reject a tunnel request?", statGroup, new long[] { 60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l });
_failRate = new RateStat("tunnelHistory.failRate", "How often do tunnels this peer accepts fail?", statGroup, new long[] { 60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l });
_processSuccessRate = new RateStat("tunnelHistory.processSuccessRate", "How many messages does a tunnel process?", statGroup, new long[] { 5*60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l });
_processFailureRate = new RateStat("tunnelHistory.processfailureRate", "How many messages does a tunnel fail?", statGroup, new long[] { 5*60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l });
_rejectRate.setStatLog(_context.statManager().getStatLog());
_failRate.setStatLog(_context.statManager().getStatLog());
_processSuccessRate.setStatLog(_context.statManager().getStatLog());
_processFailureRate.setStatLog(_context.statManager().getStatLog());
}
/** total tunnels the peer has agreed to participate in */
@@ -70,6 +76,13 @@ public class TunnelHistory {
/** when the last tunnel the peer participated in failed */
public long getLastFailed() { return _lastFailed; }
public void incrementProcessed(int processedSuccessfully, int failedProcessing) {
if (processedSuccessfully > 0)
_processSuccessRate.addData(processedSuccessfully, 0);
if (failedProcessing > 0)
_processFailureRate.addData(failedProcessing, 0);
}
public void incrementAgreedTo() {
_lifetimeAgreedTo++;
_lastAgreedTo = _context.clock().now();
@@ -113,12 +126,16 @@ public class TunnelHistory {
public RateStat getRejectionRate() { return _rejectRate; }
public RateStat getFailedRate() { return _failRate; }
public RateStat getProcessSuccessRate() { return _processSuccessRate; }
public RateStat getProcessFailureRate() { return _processFailureRate; }
public void coalesceStats() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Coallescing stats");
_rejectRate.coalesceStats();
_failRate.coalesceStats();
_processFailureRate.coalesceStats();
_processSuccessRate.coalesceStats();
}
private final static String NL = System.getProperty("line.separator");
@@ -140,7 +157,9 @@ public class TunnelHistory {
add(buf, "lifetimeRejected", _lifetimeRejected, "How many tunnels has the peer ever refused to participate in?");
out.write(buf.toString().getBytes());
_rejectRate.store(out, "tunnelHistory.rejectRate");
_rejectRate.store(out, "tunnelHistory.failRate");
_failRate.store(out, "tunnelHistory.failRate");
_processSuccessRate.store(out, "tunnelHistory.processSuccessRate");
_processFailureRate.store(out, "tunnelHistory.processFailureRate");
}
private void add(StringBuffer buf, String name, long val, String description) {
@@ -162,9 +181,15 @@ public class TunnelHistory {
_rejectRate.load(props, "tunnelHistory.rejectRate", true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Loading tunnelHistory.rejectRate");
_rejectRate.load(props, "tunnelHistory.failRate", true);
_failRate.load(props, "tunnelHistory.failRate", true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Loading tunnelHistory.failRate");
_processFailureRate.load(props, "tunnelHistory.processFailureRate", true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Loading tunnelHistory.processFailureRate");
_processSuccessRate.load(props, "tunnelHistory.processSuccessRate", true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Loading tunnelHistory.processSuccessRate");
} catch (IllegalArgumentException iae) {
_log.warn("TunnelHistory rates are corrupt, resetting", iae);
createRates(_statGroup);

View File

@@ -76,7 +76,7 @@ public class ConnectionBuilder {
private String _error;
/** If the connection hasn't been built in 30 seconds, give up */
public static final int CONNECTION_TIMEOUT = 30*1000;
public static final int CONNECTION_TIMEOUT = 20*1000;
public static final int WRITE_BUFFER_SIZE = 2*1024;

View File

@@ -87,9 +87,7 @@ public class TCPTransport extends TransportImpl {
public static final int DEFAULT_ESTABLISHERS = 3;
/** Ordered list of supported I2NP protocols */
public static final int[] SUPPORTED_PROTOCOLS = new int[] { 2
, 3 // forward compat, so we can drop 0.5 builds after 0.5.0.2
};
public static final int[] SUPPORTED_PROTOCOLS = new int[] { 4 }; // drop <= 0.5.0.3
/** blah, people shouldnt use defaults... */
public static final int DEFAULT_LISTEN_PORT = 8887;
@@ -196,6 +194,7 @@ public class TCPTransport extends TransportImpl {
newPeer = true;
}
msgs.add(msg);
msg.timestamp("TCPTransport.outboundMessageReady queued behind " +(msgs.size()-1));
if (newPeer)
_connectionLock.notifyAll();
@@ -305,17 +304,18 @@ public class TCPTransport extends TransportImpl {
void connectionClosed(TCPConnection con) {
synchronized (_connectionLock) {
TCPConnection cur = (TCPConnection)_connectionsByIdent.remove(con.getRemoteRouterIdentity().getHash());
if (cur != con)
if ( (cur != null) && (cur != con) )
_connectionsByIdent.put(cur.getRemoteRouterIdentity().getHash(), cur);
cur = (TCPConnection)_connectionsByAddress.remove(con.getRemoteAddress().toString());
if (cur != con)
if ( (cur != null) && (cur != con) )
_connectionsByAddress.put(cur.getRemoteAddress().toString(), cur);
if (_log.shouldLog(Log.DEBUG)) {
StringBuffer buf = new StringBuffer(256);
buf.append("\nCLOSING ").append(con.getRemoteRouterIdentity().getHash().toBase64().substring(0,6));
buf.append(".");
buf.append("\nconnectionsByAddress: (cur=").append(con.getRemoteAddress().toString()).append(") ");
if (cur != null)
buf.append("\nconnectionsByAddress: (cur=").append(con.getRemoteAddress().toString()).append(") ");
for (Iterator iter = _connectionsByAddress.keySet().iterator(); iter.hasNext(); ) {
String addr = (String)iter.next();
buf.append(addr).append(" ");

View File

@@ -0,0 +1,184 @@
package net.i2p.router.tunnel;
import java.util.ArrayList;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.Hash;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DataMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.util.Log;
/**
* Test the batching behavior of the preprocessor with one, two, or three
* messages of various sizes and settings.
*
*/
public class BatchedFragmentTest extends FragmentTest {
public BatchedFragmentTest() {
super();
BatchedPreprocessor.DEFAULT_DELAY = 200;
}
protected TunnelGateway.QueuePreprocessor createPreprocessor(I2PAppContext ctx) {
return new BatchedPreprocessor(ctx);
}
/**
* Send a small message, wait a second, then send a large message, pushing
* the first one through immediately, with the rest of the large one passed
* after a brief delay.
*
*/
public void runBatched() {
TunnelGateway.Pending pending1 = createPending(10, false, false);
ArrayList messages = new ArrayList();
messages.add(pending1);
TunnelGateway.Pending pending2 = createPending(1024, false, false);
TunnelGateway.QueuePreprocessor pre = createPreprocessor(_context);
SenderImpl sender = new SenderImpl();
DefragmentedReceiverImpl handleReceiver = new DefragmentedReceiverImpl(pending1.getData(), pending2.getData());
FragmentHandler handler = new FragmentHandler(_context, handleReceiver);
ReceiverImpl receiver = new ReceiverImpl(handler, 0);
byte msg[] = pending1.getData();
_log.debug("SEND(" + msg.length + "): " + Base64.encode(msg) + " " + _context.sha().calculateHash(msg).toBase64());
boolean keepGoing = true;
boolean alreadyAdded = false;
while (keepGoing) {
keepGoing = pre.preprocessQueue(messages, new SenderImpl(), receiver);
if (keepGoing) {
try { Thread.sleep(150); } catch (InterruptedException ie) {}
if (!alreadyAdded) {
messages.add(pending2);
alreadyAdded = true;
}
}
}
if (handleReceiver.receivedOk())
_log.info("Receive batched ok");
else
_log.info("Failed to receive batched");
}
/**
* Send a small message, wait a second, then send a large message, pushing
* the first one through immediately, with the rest of the large one passed
* after a brief delay.
*
*/
public void runBatches() {
int success = 0;
//success += testBatched(1, false, false, 1024, false, false);
// this takes a long fucking time
for (int i = 1; i <= 1024; i++) {
success += testBatched(i, false, false, 1024, false, false, 1024, false, false);
success += testBatched(i, true, false, 1024, false, false, 1024, false, false);
success += testBatched(i, true, true, 1024, false, false, 1024, false, false);
success += testBatched(i, false, false, 1024, true, false, 1024, false, false);
success += testBatched(i, true, false, 1024, true, false, 1024, false, false);
success += testBatched(i, true, true, 1024, true, false, 1024, false, false);
success += testBatched(i, false, false, 1024, true, true, 1024, false, false);
success += testBatched(i, true, false, 1024, true, true, 1024, false, false);
success += testBatched(i, true, true, 1024, true, true, 1024, false, false);
success += testBatched(i, false, false, 1024, false, false, 1024, true, false);
success += testBatched(i, true, false, 1024, false, false, 1024, true, false);
success += testBatched(i, true, true, 1024, false, false, 1024, true, false);
success += testBatched(i, false, false, 1024, true, false, 1024, true, false);
success += testBatched(i, true, false, 1024, true, false, 1024, true, false);
success += testBatched(i, true, true, 1024, true, false, 1024, true, false);
success += testBatched(i, false, false, 1024, true, true, 1024, true, false);
success += testBatched(i, true, false, 1024, true, true, 1024, true, false);
success += testBatched(i, true, true, 1024, true, true, 1024, true, false);
success += testBatched(i, false, false, 1024, false, false, 1024, true, true);
success += testBatched(i, true, false, 1024, false, false, 1024, true, true);
success += testBatched(i, true, true, 1024, false, false, 1024, true, true);
success += testBatched(i, false, false, 1024, true, false, 1024, true, true);
success += testBatched(i, true, false, 1024, true, false, 1024, true, true);
success += testBatched(i, true, true, 1024, true, false, 1024, true, true);
success += testBatched(i, false, false, 1024, true, true, 1024, true, true);
success += testBatched(i, true, false, 1024, true, true, 1024, true, true);
success += testBatched(i, true, true, 1024, true, true, 1024, true, true);
}
_log.info("** Batches complete with " + success + " successful runs");
}
private int testBatched(int firstSize, boolean firstRouter, boolean firstTunnel,
int secondSize, boolean secondRouter, boolean secondTunnel,
int thirdSize, boolean thirdRouter, boolean thirdTunnel) {
TunnelGateway.Pending pending1 = createPending(firstSize, firstRouter, firstTunnel);
TunnelGateway.Pending pending2 = createPending(secondSize, secondRouter, secondTunnel);
TunnelGateway.Pending pending3 = createPending(thirdSize, thirdRouter, thirdTunnel);
boolean ok = runBatch(pending1, pending2, pending3);
if (ok) {
_log.info("OK: " + firstSize + "." + firstRouter + "." + firstTunnel
+ " " + secondSize + "." + secondRouter + "." + secondTunnel
+ " " + thirdSize + "." + thirdRouter + "." + thirdTunnel);
return 1;
} else {
_log.info("FAIL: " + firstSize + "." + firstRouter + "." + firstTunnel
+ " " + secondSize + "." + secondRouter + "." + secondTunnel
+ " " + thirdSize + "." + thirdRouter + "." + thirdTunnel);
return 0;
}
}
private boolean runBatch(TunnelGateway.Pending pending1, TunnelGateway.Pending pending2, TunnelGateway.Pending pending3) {
ArrayList messages = new ArrayList();
messages.add(pending1);
TunnelGateway.QueuePreprocessor pre = createPreprocessor(_context);
SenderImpl sender = new SenderImpl();
DefragmentedReceiverImpl handleReceiver = new DefragmentedReceiverImpl(pending1.getData(), pending2.getData(), pending3.getData());
FragmentHandler handler = new FragmentHandler(_context, handleReceiver);
ReceiverImpl receiver = new ReceiverImpl(handler, 0);
byte msg[] = pending1.getData();
_log.debug("SEND(" + msg.length + "): " + Base64.encode(msg) + " " + _context.sha().calculateHash(msg).toBase64());
boolean keepGoing = true;
int added = 0;
while (keepGoing) {
keepGoing = pre.preprocessQueue(messages, new SenderImpl(), receiver);
if ( (keepGoing) || ((messages.size() == 0) && (added < 2) ) ) {
try { Thread.sleep(150); } catch (InterruptedException ie) {}
if (added == 0) {
_log.debug("Adding pending2");
messages.add(pending2);
added++;
keepGoing = true;
} else if (added == 1) {
_log.debug("Adding pending3");
messages.add(pending3);
added++;
keepGoing = true;
}
}
}
return handleReceiver.receivedOk();
}
public void runTests() {
//super.runVaried();
//super.runTests();
//runBatched();
runBatches();
}
public static void main(String args[]) {
BatchedFragmentTest t = new BatchedFragmentTest();
t.runTests();
}
}

View File

@@ -0,0 +1,203 @@
package net.i2p.router.tunnel;
import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.util.Log;
/**
* Batching preprocessor that will briefly delay the sending of a message
* if it doesn't fill up a full tunnel message, in which case it queues up
* an additional flush task. This is a very simple threshold algorithm -
* as soon as there is enough data for a full tunnel message, it is sent. If
* after the delay there still isn't enough data, what is available is sent
* and padded.
*
*/
public class BatchedPreprocessor extends TrivialPreprocessor {
private Log _log;
private long _pendingSince;
public BatchedPreprocessor(I2PAppContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(BatchedPreprocessor.class);
_pendingSince = 0;
ctx.statManager().createRateStat("tunnel.batchMultipleCount", "How many messages are batched into a tunnel message", "Tunnels", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
ctx.statManager().createRateStat("tunnel.batchDelay", "How many messages were pending when the batching waited", "Tunnels", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
ctx.statManager().createRateStat("tunnel.batchDelaySent", "How many messages were flushed when the batching delay completed", "Tunnels", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
}
private static final int FULL_SIZE = PREPROCESSED_SIZE
- IV_SIZE
- 1 // 0x00 ending the padding
- 4; // 4 byte checksum
private static final boolean DISABLE_BATCHING = false;
/* not final or private so the test code can adjust */
static long DEFAULT_DELAY = 500;
/** wait up to 2 seconds before sending a small message */
protected long getSendDelay() { return DEFAULT_DELAY; }
public boolean preprocessQueue(List pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Preprocess queue with " + pending.size() + " to send");
if (DISABLE_BATCHING || getSendDelay() <= 0) {
if (_log.shouldLog(Log.INFO))
_log.info("No batching, send all messages immediately");
while (pending.size() > 0) {
// loops because sends may be partial
TunnelGateway.Pending msg = (TunnelGateway.Pending)pending.get(0);
send(pending, 0, 0, sender, rec);
if (msg.getOffset() >= msg.getData().length)
pending.remove(0);
}
return false;
}
while (pending.size() > 0) {
int allocated = 0;
for (int i = 0; i < pending.size(); i++) {
TunnelGateway.Pending msg = (TunnelGateway.Pending)pending.get(i);
int instructionsSize = getInstructionsSize(msg);
instructionsSize += getInstructionAugmentationSize(msg, allocated, instructionsSize);
int curWanted = msg.getData().length - msg.getOffset() + instructionsSize;
allocated += curWanted;
if (allocated >= FULL_SIZE) {
if (allocated - curWanted + instructionsSize >= FULL_SIZE) {
// the instructions alone exceed the size, so we won't get any
// of the message into it. don't include it
i--;
msg = (TunnelGateway.Pending)pending.get(i);
allocated -= curWanted;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Pushback of " + curWanted + " (message " + (i+1) + ")");
}
if (_pendingSince > 0)
_context.statManager().addRateData("tunnel.batchDelaySent", pending.size(), 0);
_pendingSince = 0;
send(pending, 0, i, sender, rec);
if (_log.shouldLog(Log.INFO))
_log.info("Allocated=" + allocated + " so we sent " + (i+1)
+ " (last complete? " + (msg.getOffset() >= msg.getData().length) + ")");
for (int j = 0; j < i; j++)
pending.remove(0);
if (msg.getOffset() >= msg.getData().length) {
// ok, this last message fit perfectly, remove it too
pending.remove(0);
}
if (i > 0)
_context.statManager().addRateData("tunnel.batchMultipleCount", i+1, 0);
allocated = 0;
break;
}
}
if (allocated > 0) {
// after going through the entire pending list, we still don't
// have enough data to send a full message
if ( (_pendingSince > 0) && (_pendingSince + getSendDelay() <= _context.clock().now()) ) {
if (_log.shouldLog(Log.INFO))
_log.info("Passed through pending list, with " + allocated + "/" + pending.size()
+ " left to clean up, but we've waited, so flush");
// not even a full message, but we want to flush it anyway
if (pending.size() > 1)
_context.statManager().addRateData("tunnel.batchMultipleCount", pending.size(), 0);
_context.statManager().addRateData("tunnel.batchDelaySent", pending.size(), 0);
send(pending, 0, pending.size()-1, sender, rec);
pending.clear();
_pendingSince = 0;
return false;
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Passed through pending list, with " + allocated + "/"+ pending.size()
+ " left to clean up, but we've haven't waited, so don't flush (wait="
+ (_context.clock().now() - _pendingSince) + " / " + _pendingSince + ")");
_context.statManager().addRateData("tunnel.batchDelay", pending.size(), 0);
if (_pendingSince <= 0)
_pendingSince = _context.clock().now();
// not yet time to send the delayed flush
return true;
}
} else {
// ok, we sent some, but haven't gone back for another
// pass yet. keep looping
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sent everything on the list (pending=" + pending.size() + ")");
// sent everything from the pending list, no need to delayed flush
return false;
}
/**
* Preprocess the messages from the pending list, grouping items startAt
* through sendThrough (though only part of the last one may be fully
* sent), delivering them through the sender/receiver.
*
* @param startAt first index in pending to send (inclusive)
* @param sendThrough last index in pending to send (inclusive)
*/
protected void send(List pending, int startAt, int sendThrough, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending " + startAt + ":" + sendThrough + " out of " + pending.size());
byte preprocessed[] = _dataCache.acquire().getData();
int offset = 0;
offset = writeFragments(pending, startAt, sendThrough, preprocessed, offset);
// preprocessed[0:offset] now contains the fragments from the pending,
// so we need to format, pad, and rearrange according to the spec to
// generate the final preprocessed data
if (offset <= 0) {
StringBuffer buf = new StringBuffer(128);
buf.append("wtf, written offset is ").append(offset);
buf.append(" for ").append(startAt).append(" through ").append(sendThrough);
for (int i = startAt; i <= sendThrough; i++) {
buf.append(" ").append(pending.get(i).toString());
}
_log.log(Log.CRIT, buf.toString());
return;
}
preprocess(preprocessed, offset);
sender.sendPreprocessed(preprocessed, rec);
}
/**
* Write the fragments out of the pending list onto the target, updating
* each of the Pending message's offsets accordingly.
*
* @return new offset into the target for further bytes to be written
*/
private int writeFragments(List pending, int startAt, int sendThrough, byte target[], int offset) {
for (int i = startAt; i <= sendThrough; i++) {
TunnelGateway.Pending msg = (TunnelGateway.Pending)pending.get(i);
int prevOffset = offset;
if (msg.getOffset() == 0) {
offset = writeFirstFragment(msg, target, offset);
if (_log.shouldLog(Log.DEBUG))
_log.debug("writing " + msg.getMessageId() + " fragment 0, ending at " + offset + " prev " + prevOffset
+ " leaving " + (msg.getData().length - msg.getOffset()) + " bytes for later");
} else {
offset = writeSubsequentFragment(msg, target, offset);
if (_log.shouldLog(Log.DEBUG))
_log.debug("writing " + msg.getMessageId() + " fragment " + (msg.getFragmentNumber()-1)
+ ", ending at " + offset + " prev " + prevOffset
+ " leaving " + (msg.getData().length - msg.getOffset()) + " bytes for later");
}
}
return offset;
}
}

View File

@@ -0,0 +1,55 @@
package net.i2p.router.tunnel;
import java.util.Properties;
import net.i2p.router.RouterContext;
/**
* Honor the 'batchFrequency' tunnel pool setting or the 'router.batchFrequency'
* router config setting, and track fragmentation.
*
*/
public class BatchedRouterPreprocessor extends BatchedPreprocessor {
private RouterContext _routerContext;
private TunnelCreatorConfig _config;
/**
* How frequently should we flush non-full messages, in milliseconds
*/
public static final String PROP_BATCH_FREQUENCY = "batchFrequency";
public static final String PROP_ROUTER_BATCH_FREQUENCY = "router.batchFrequency";
public static final int DEFAULT_BATCH_FREQUENCY = 500;
public BatchedRouterPreprocessor(RouterContext ctx) {
this(ctx, null);
}
public BatchedRouterPreprocessor(RouterContext ctx, TunnelCreatorConfig cfg) {
super(ctx);
_routerContext = ctx;
_config = cfg;
}
/** how long should we wait before flushing */
protected long getSendDelay() {
String freq = null;
if (_config != null) {
Properties opts = _config.getOptions();
if (opts != null)
freq = opts.getProperty(PROP_BATCH_FREQUENCY);
} else {
freq = _routerContext.getProperty(PROP_ROUTER_BATCH_FREQUENCY);
}
if (freq != null) {
try {
return Integer.parseInt(freq);
} catch (NumberFormatException nfe) {
return DEFAULT_BATCH_FREQUENCY;
}
}
return DEFAULT_BATCH_FREQUENCY;
}
protected void notePreprocessing(long messageId, int numFragments) {
_routerContext.messageHistory().fragmentMessage(messageId, numFragments);
}
}

View File

@@ -29,10 +29,13 @@ public class FragmentHandler {
private Log _log;
private Map _fragmentedMessages;
private DefragmentedReceiver _receiver;
private int _completed;
private int _failed;
/** don't wait more than 60s to defragment the partial message */
private static final long MAX_DEFRAGMENT_TIME = 60*1000;
static long MAX_DEFRAGMENT_TIME = 60*1000;
private static final ByteCache _cache = ByteCache.getInstance(512, TrivialPreprocessor.PREPROCESSED_SIZE);
public FragmentHandler(I2PAppContext context, DefragmentedReceiver receiver) {
_context = context;
_log = context.logManager().getLog(FragmentHandler.class);
@@ -46,6 +49,8 @@ public class FragmentHandler {
"Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 });
_context.statManager().createRateStat("tunnel.fragmentedDropped", "How many fragments were in a partially received yet failed message?",
"Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 });
_context.statManager().createRateStat("tunnel.corruptMessage", "How many corrupted messages arrived?",
"Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 });
}
/**
@@ -58,8 +63,11 @@ public class FragmentHandler {
public void receiveTunnelMessage(byte preprocessed[], int offset, int length) {
boolean ok = verifyPreprocessed(preprocessed, offset, length);
if (!ok) {
_log.error("Unable to verify preprocessed data (pre.length=" + preprocessed.length
+ " off=" +offset + " len=" + length, new Exception("failed"));
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to verify preprocessed data (pre.length="
+ preprocessed.length + " off=" +offset + " len=" + length);
_cache.release(new ByteArray(preprocessed));
_context.statManager().addRateData("tunnel.corruptMessage", 1, 1);
return;
}
offset += HopProcessor.IV_LENGTH; // skip the IV
@@ -80,10 +88,19 @@ public class FragmentHandler {
} catch (RuntimeException e) {
if (_log.shouldLog(Log.ERROR))
_log.error("Corrupt fragment received: offset = " + offset, e);
_context.statManager().addRateData("tunnel.corruptMessage", 1, 1);
throw e;
} finally {
// each of the FragmentedMessages populated make a copy out of the
// payload, which they release separately, so we can release
// immediately
_cache.release(new ByteArray(preprocessed));
}
}
public int getCompleteCount() { return _completed; }
public int getFailedCount() { return _failed; }
private static final ByteCache _validateCache = ByteCache.getInstance(512, TrivialPreprocessor.PREPROCESSED_SIZE);
/**
@@ -98,8 +115,19 @@ public class FragmentHandler {
private boolean verifyPreprocessed(byte preprocessed[], int offset, int length) {
// now we need to verify that the message was received correctly
int paddingEnd = HopProcessor.IV_LENGTH + 4;
while (preprocessed[offset+paddingEnd] != (byte)0x00)
while (preprocessed[offset+paddingEnd] != (byte)0x00) {
paddingEnd++;
if (offset+paddingEnd >= length) {
if (_log.shouldLog(Log.ERROR))
_log.error("Corrupt tunnel message padding");
if (_log.shouldLog(Log.WARN))
_log.warn("cannot verify, going past the end [off="
+ offset + " len=" + length + " paddingEnd="
+ paddingEnd + " data:\n"
+ Base64.encode(preprocessed, offset, length));
return false;
}
}
paddingEnd++; // skip the last
ByteArray ba = _validateCache.acquire(); // larger than necessary, but always sufficient
@@ -117,10 +145,11 @@ public class FragmentHandler {
boolean eq = DataHelper.eq(v.getData(), 0, preprocessed, offset + HopProcessor.IV_LENGTH, 4);
if (!eq) {
if (_log.shouldLog(Log.ERROR))
_log.error("Endpoint data doesn't match: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1));
if (_log.shouldLog(Log.DEBUG))
_log.debug("nomatching endpoint: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1) + "\n"
+ Base64.encode(preprocessed, offset + paddingEnd, preV.length-HopProcessor.IV_LENGTH));
_log.error("Corrupt tunnel message - verification fails");
if (_log.shouldLog(Log.WARN))
_log.warn("nomatching endpoint: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1) + "\n"
+ " offset=" + offset + " length=" + length + " paddingEnd=" + paddingEnd
+ Base64.encode(preprocessed, offset, length));
}
_context.sha().cache().release(cache);
@@ -249,6 +278,9 @@ public class FragmentHandler {
}
offset += size;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handling finished message " + msg.getMessageId() + " at offset " + offset);
return offset;
}
@@ -312,6 +344,7 @@ public class FragmentHandler {
}
private void receiveComplete(FragmentedMessage msg) {
_completed++;
try {
byte data[] = msg.toByteArray();
if (_log.shouldLog(Log.DEBUG))
@@ -362,6 +395,7 @@ public class FragmentHandler {
removed = (null != _fragmentedMessages.remove(new Long(_msg.getMessageId())));
}
if (removed && !_msg.getReleased()) {
_failed++;
noteFailure(_msg.getMessageId());
if (_log.shouldLog(Log.WARN))
_log.warn("Dropped failed fragmented message: " + _msg);

View File

@@ -3,6 +3,7 @@ package net.i2p.router.tunnel;
import java.util.ArrayList;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DataMessage;
@@ -15,12 +16,18 @@ import net.i2p.util.Log;
*
*/
public class FragmentTest {
private I2PAppContext _context;
private Log _log;
protected I2PAppContext _context;
protected Log _log;
public FragmentTest() {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(FragmentTest.class);
_log = _context.logManager().getLog(getClass());
_context.random().nextBoolean();
FragmentHandler.MAX_DEFRAGMENT_TIME = 10*1000;
}
protected TunnelGateway.QueuePreprocessor createPreprocessor(I2PAppContext ctx) {
return new TrivialPreprocessor(ctx);
}
/**
@@ -28,23 +35,26 @@ public class FragmentTest {
*
*/
public void runSingle() {
DataMessage m = new DataMessage(_context);
byte data[] = new byte[949];
_context.random().nextBytes(data);
m.setData(data);
m.setUniqueId(42);
m.setMessageExpiration(_context.clock().now() + 60*1000);
TunnelGateway.Pending pending = createPending(949, false, false);
ArrayList messages = new ArrayList();
TunnelGateway.Pending pending = new TunnelGateway.Pending(m, null, null);
messages.add(pending);
TrivialPreprocessor pre = new TrivialPreprocessor(_context);
TunnelGateway.QueuePreprocessor pre = createPreprocessor(_context);
SenderImpl sender = new SenderImpl();
FragmentHandler handler = new FragmentHandler(_context, new DefragmentedReceiverImpl(m));
DefragmentedReceiverImpl handleReceiver = new DefragmentedReceiverImpl(pending.getData());
FragmentHandler handler = new FragmentHandler(_context, handleReceiver);
ReceiverImpl receiver = new ReceiverImpl(handler, 0);
byte msg[] = m.toByteArray();
byte msg[] = pending.getData();
_log.debug("SEND(" + msg.length + "): " + Base64.encode(msg) + " " + _context.sha().calculateHash(msg).toBase64());
pre.preprocessQueue(messages, new SenderImpl(), receiver);
boolean keepGoing = true;
while (keepGoing) {
keepGoing = pre.preprocessQueue(messages, new SenderImpl(), receiver);
if (keepGoing)
try { Thread.sleep(100); } catch (InterruptedException ie) {}
}
if (handleReceiver.receivedOk())
_log.info("received OK");
}
/**
@@ -52,23 +62,26 @@ public class FragmentTest {
*
*/
public void runMultiple() {
DataMessage m = new DataMessage(_context);
byte data[] = new byte[2048];
_context.random().nextBytes(data);
m.setData(data);
m.setUniqueId(42);
m.setMessageExpiration(_context.clock().now() + 60*1000);
TunnelGateway.Pending pending = createPending(2048, false, false);
ArrayList messages = new ArrayList();
TunnelGateway.Pending pending = new TunnelGateway.Pending(m, null, null);
messages.add(pending);
TrivialPreprocessor pre = new TrivialPreprocessor(_context);
TunnelGateway.QueuePreprocessor pre = createPreprocessor(_context);
SenderImpl sender = new SenderImpl();
FragmentHandler handler = new FragmentHandler(_context, new DefragmentedReceiverImpl(m));
DefragmentedReceiverImpl handleReceiver = new DefragmentedReceiverImpl(pending.getData());
FragmentHandler handler = new FragmentHandler(_context, handleReceiver);
ReceiverImpl receiver = new ReceiverImpl(handler, 0);
byte msg[] = m.toByteArray();
byte msg[] = pending.getData();
_log.debug("SEND(" + msg.length + "): " + Base64.encode(msg) + " " + _context.sha().calculateHash(msg).toBase64());
pre.preprocessQueue(messages, new SenderImpl(), receiver);
boolean keepGoing = true;
while (keepGoing) {
keepGoing = pre.preprocessQueue(messages, new SenderImpl(), receiver);
if (keepGoing)
try { Thread.sleep(100); } catch (InterruptedException ie) {}
}
if (handleReceiver.receivedOk())
_log.info("received OK");
}
/**
@@ -77,31 +90,88 @@ public class FragmentTest {
*
*/
public void runDelayed() {
DataMessage m = new DataMessage(_context);
byte data[] = new byte[2048];
_context.random().nextBytes(data);
m.setData(data);
m.setUniqueId(42);
m.setMessageExpiration(_context.clock().now() + 60*1000);
TunnelGateway.Pending pending = createPending(2048, false, false);
ArrayList messages = new ArrayList();
TunnelGateway.Pending pending = new TunnelGateway.Pending(m, null, null);
messages.add(pending);
TrivialPreprocessor pre = new TrivialPreprocessor(_context);
TunnelGateway.QueuePreprocessor pre = createPreprocessor(_context);
SenderImpl sender = new SenderImpl();
FragmentHandler handler = new FragmentHandler(_context, new DefragmentedReceiverImpl(m));
ReceiverImpl receiver = new ReceiverImpl(handler, 21*1000);
byte msg[] = m.toByteArray();
FragmentHandler handler = new FragmentHandler(_context, new DefragmentedReceiverImpl(pending.getData()));
ReceiverImpl receiver = new ReceiverImpl(handler, 11*1000);
byte msg[] = pending.getData();
_log.debug("SEND(" + msg.length + "): " + Base64.encode(msg) + " " + _context.sha().calculateHash(msg).toBase64());
pre.preprocessQueue(messages, new SenderImpl(), receiver);
boolean keepGoing = true;
while (keepGoing) {
keepGoing = pre.preprocessQueue(messages, new SenderImpl(), receiver);
if (keepGoing)
try { Thread.sleep(100); } catch (InterruptedException ie) {}
}
}
private class SenderImpl implements TunnelGateway.Sender {
public void runVaried() {
int failures = 0;
for (int i = 0; i <= 4096; i++) {
boolean ok = runVaried(i, false, false);
if (!ok) { _log.error("** processing " + i+ " w/ no router, no tunnel failed"); failures++; }
ok = runVaried(i, true, false);
if (!ok) { _log.error("** processing " + i+ " w/ router, no tunnel failed"); failures++; }
ok = runVaried(i, true, true);
if (!ok) { _log.error("** processing " + i+ " w/ router, tunnel failed"); failures++; }
else _log.info("Tests pass for size " + i);
}
if (failures == 0)
_log.info("** success after all varied tests");
else
_log.error("** failed " + failures +" varied tests");
}
protected boolean runVaried(int size, boolean includeRouter, boolean includeTunnel) {
TunnelGateway.Pending pending = createPending(size, includeRouter, includeTunnel);
ArrayList messages = new ArrayList();
messages.add(pending);
DefragmentedReceiverImpl handleReceiver = new DefragmentedReceiverImpl(pending.getData());
TunnelGateway.QueuePreprocessor pre = createPreprocessor(_context);
SenderImpl sender = new SenderImpl();
FragmentHandler handler = new FragmentHandler(_context, handleReceiver);
ReceiverImpl receiver = new ReceiverImpl(handler, 0);
byte msg[] = pending.getData();
_log.debug("SEND(" + msg.length + "): " + Base64.encode(msg) + " " + _context.sha().calculateHash(msg).toBase64());
boolean keepGoing = true;
while (keepGoing) {
keepGoing = pre.preprocessQueue(messages, new SenderImpl(), receiver);
if (keepGoing)
try { Thread.sleep(100); } catch (InterruptedException ie) {}
}
return handleReceiver.receivedOk();
}
protected TunnelGateway.Pending createPending(int size, boolean includeRouter, boolean includeTunnel) {
DataMessage m = new DataMessage(_context);
byte data[] = new byte[size];
_context.random().nextBytes(data);
m.setData(data);
m.setUniqueId(_context.random().nextLong(I2NPMessage.MAX_ID_VALUE));
m.setMessageExpiration(_context.clock().now() + 60*1000);
Hash toRouter = null;
TunnelId toTunnel = null;
if (includeRouter) {
toRouter = new Hash(new byte[Hash.HASH_LENGTH]);
_context.random().nextBytes(toRouter.getData());
}
if (includeTunnel)
toTunnel = new TunnelId(_context.random().nextLong(TunnelId.MAX_ID_VALUE));
return new TunnelGateway.Pending(m, toRouter, toTunnel);
}
protected class SenderImpl implements TunnelGateway.Sender {
public void sendPreprocessed(byte[] preprocessed, TunnelGateway.Receiver receiver) {
receiver.receiveEncrypted(preprocessed);
}
}
private class ReceiverImpl implements TunnelGateway.Receiver {
protected class ReceiverImpl implements TunnelGateway.Receiver {
private FragmentHandler _handler;
private int _delay;
public ReceiverImpl(FragmentHandler handler, int delay) {
@@ -114,21 +184,62 @@ public class FragmentTest {
}
}
private class DefragmentedReceiverImpl implements FragmentHandler.DefragmentedReceiver {
private I2NPMessage _expected;
public DefragmentedReceiverImpl(I2NPMessage expected) {
protected class DefragmentedReceiverImpl implements FragmentHandler.DefragmentedReceiver {
private byte _expected[];
private byte _expected2[];
private byte _expected3[];
private int _received;
public DefragmentedReceiverImpl(byte expected[]) {
this(expected, null);
}
public DefragmentedReceiverImpl(byte expected[], byte expected2[]) {
this(expected, expected2, null);
}
public DefragmentedReceiverImpl(byte expected[], byte expected2[], byte expected3[]) {
_expected = expected;
_expected2 = expected2;
_expected3 = expected3;
_received = 0;
}
public void receiveComplete(I2NPMessage msg, Hash toRouter, TunnelId toTunnel) {
_log.debug("equal? " + _expected.equals(msg));
boolean ok = false;
byte m[] = msg.toByteArray();
if ( (_expected != null) && (DataHelper.eq(_expected, m)) )
ok = true;
if (!ok && (_expected2 != null) && (DataHelper.eq(_expected2, m)) )
ok = true;
if (!ok && (_expected3 != null) && (DataHelper.eq(_expected3, m)) )
ok = true;
if (ok)
_received++;
//_log.info("** equal? " + ok);
}
public boolean receivedOk() {
if ( (_expected != null) && (_expected2 != null) && (_expected3 != null) )
return _received == 3;
else if ( (_expected != null) && (_expected2 != null) )
return _received == 2;
else if ( (_expected != null) || (_expected2 != null) )
return _received == 1;
else
return _received == 0;
}
}
public void runTests() {
runVaried();
_log.info("\n===========================Begin runSingle()\n\n");
runSingle();
_log.info("\n===========================Begin runMultiple()\n\n");
runMultiple();
_log.info("\n===========================Begin runDelayed() (should have 3 errors)\n\n");
runDelayed();
_log.info("\n===========================After runDelayed()\n\n");
}
public static void main(String args[]) {
FragmentTest t = new FragmentTest();
t.runSingle();
t.runMultiple();
t.runDelayed();
t.runTests();
}
}

View File

@@ -74,7 +74,10 @@ public class FragmentedMessage {
_log.debug("Receive message " + messageId + " fragment " + fragmentNum + " with " + length + " bytes (last? " + isLast + ") offset = " + offset);
_messageId = messageId;
// we should just use payload[] and use an offset/length on it
ByteArray ba = new ByteArray(payload, offset, length); //new byte[length]);
ByteArray ba = _cache.acquire(); //new ByteArray(payload, offset, length); //new byte[length]);
System.arraycopy(payload, offset, ba.getData(), 0, length);
ba.setValid(length);
ba.setOffset(0);
//System.arraycopy(payload, offset, ba.getData(), 0, length);
if (_log.shouldLog(Log.DEBUG))
_log.debug("fragment[" + fragmentNum + "/" + offset + "/" + length + "]: "
@@ -107,7 +110,10 @@ public class FragmentedMessage {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive message " + messageId + " with " + length + " bytes (last? " + isLast + ") targetting " + toRouter + " / " + toTunnel + " offset=" + offset);
_messageId = messageId;
ByteArray ba = new ByteArray(payload, offset, length); // new byte[length]);
ByteArray ba = _cache.acquire(); // new ByteArray(payload, offset, length); // new byte[length]);
System.arraycopy(payload, offset, ba.getData(), 0, length);
ba.setValid(length);
ba.setOffset(0);
//System.arraycopy(payload, offset, ba.getData(), 0, length);
if (_log.shouldLog(Log.DEBUG))
_log.debug("fragment[0/" + offset + "/" + length + "]: "

View File

@@ -90,6 +90,8 @@ public class InboundMessageDistributor implements GarlicMessageReceiver.CloveRec
+ " failing to distribute " + msg);
return;
}
if (msg.getMessageExpiration() < _context.clock().now() + 10*1000)
msg.setMessageExpiration(_context.clock().now() + 10*1000);
_context.tunnelDispatcher().dispatchOutbound(msg, outId, tunnel, target);
}
}

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