Compare commits

...

50 Commits

Author SHA1 Message Date
jrandom
348e845793 *cough* thanks cervantes 2005-04-06 16:38:38 +00:00
jrandom
80827c3aad * 2005-04-06 0.5.0.6 released 2005-04-06 15:43:25 +00:00
jrandom
3b4cf0a024 added 55cancri.i2p 2005-04-06 15:14:00 +00:00
jrandom
941252fd80 2005-04-05 jrandom
* Retry I2PTunnel startup if we are unable to build a socketManager for a
      client or httpclient tunnel.
    * Add some basic sanity checking on the I2CP settings (thanks duck!)
2005-04-05 22:24:32 +00:00
jrandom
bc626ece2d 2005-04-05 jrandom
* After a successfull netDb search for a leaseSet, republish it to all of
      the peers we have tried so far who did not give us the key (up to 10),
      rather than the old K closest (which may include peers who had given us
      the key)
    * Don't wait 5 minutes to publish a leaseSet (duh!), and rather than
      republish it every 5 minutes, republish it every 3.  In addition, always
      republish as soon as the leaseSet changes (duh^2).
    * Minor fix for oddball startup race (thanks travis_bickle!)
    * Minor AES update to allow in-place decryption.
2005-04-05 16:06:14 +00:00
jrandom
400feb3ba7 clarify crypto/hmac usage for simpler implementation 2005-04-05 15:28:54 +00:00
jrandom
756a4e3995 added a section for congestion control describing what I hope to implement. what
/actually/ gets implemented will be documented further once its, er, implemented
2005-04-04 17:21:30 +00:00
aum
578301240e Added constructors to PrivateKey, PublicKey, SigningPrivateKey and
SigningPublicKey, which take a single String argument and construct
the object from the Base64 data in that string (where this data is
the product of a .toBase64() call on a prior instance).
2005-04-04 06:13:50 +00:00
aum
9b8f91c7f9 Added 'toPublic()' methods to PrivateKey and SigningPrivateKey, such
that these return PublicKey and SigningPublicKey objects, respectively.
2005-04-04 06:01:13 +00:00
smeghead
c7c389d4fb added eepget wrapper script for *nix 2005-04-03 13:35:52 +00:00
smeghead
68f7adfa0b *** keyword substitution change *** 2005-04-03 13:33:29 +00:00
jrandom
c4ac5170c7 2005-04-03 jrandom
* EepGet fix for open-ended HTTP fetches (such as the news.xml
      feeding the NewsFetcher)
2005-04-03 12:50:11 +00:00
jrandom
32e0c8ac71 updated status blurb 2005-04-03 07:22:28 +00:00
jrandom
c9c1eae32f 2005-04-01 jrandom
* Allow editing I2PTunnel server instances with five digit ports
      (thanks nickless_head!)
    * More NewsFetcher debugging for reported weirdness
2005-04-01 13:29:26 +00:00
jrandom
33366cc291 2005-04-01 jrandom
* Fix to check for missing news file (thanks smeghead!)
    * Added destination display CLI:
      java -cp lib/i2p.jar net.i2p.data.Destination privKeyFilename
    * Added destination display to the web interface (thanks pnspns)
    * Installed CIA backdoor
2005-04-01 11:28:06 +00:00
jrandom
083ac1f125 n3wz0rz 2005-03-31 02:04:18 +00:00
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
90 changed files with 6531 additions and 1550 deletions

View File

@@ -209,8 +209,24 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
props.putAll(System.getProperties());
else
props.putAll(tunnel.getClientOptions());
I2PSocketManager sockManager = I2PSocketManagerFactory.createManager(tunnel.host, Integer.parseInt(tunnel.port), props);
if (sockManager == null) return null;
int portNum = 7654;
if (tunnel.port != null) {
try {
portNum = Integer.parseInt(tunnel.port);
} catch (NumberFormatException nfe) {
_log.log(Log.CRIT, "Invalid port specified [" + tunnel.port + "], reverting to " + portNum);
}
}
I2PSocketManager sockManager = null;
while (sockManager == null) {
sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
if (sockManager == null) {
_log.log(Log.CRIT, "Unable to create socket manager");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
sockManager.setName("Client");
return sockManager;
}

View File

@@ -75,9 +75,18 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
I2PClient client = I2PClientFactory.createClient();
Properties props = new Properties();
props.putAll(getTunnel().getClientOptions());
int portNum = 7654;
if (getTunnel().port != null) {
try {
portNum = Integer.parseInt(getTunnel().port);
} catch (NumberFormatException nfe) {
_log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
}
}
while (sockMgr == null) {
synchronized (slock) {
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, Integer.parseInt(getTunnel().port),
sockMgr = I2PSocketManagerFactory.createManager(privData, getTunnel().host, portNum,
props);
}

View File

@@ -258,8 +258,16 @@ public class TunnelController implements Logging {
if ("localhost".equals(_tunnel.host))
_tunnel.host = "127.0.0.1";
String port = getI2CPPort();
if ( (port != null) && (port.length() > 0) )
_tunnel.port = port;
if ( (port != null) && (port.length() > 0) ) {
try {
int portNum = Integer.parseInt(port);
_tunnel.port = String.valueOf(portNum);
} catch (NumberFormatException nfe) {
_tunnel.port = "7654";
}
} else {
_tunnel.port = "7654";
}
}
public void stopTunnel() {
@@ -324,6 +332,18 @@ public class TunnelController implements Logging {
public String getTargetDestination() { return _config.getProperty("targetDestination"); }
public String getProxyList() { return _config.getProperty("proxyList"); }
public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
public String getMyDestination() {
if (_tunnel != null) {
List sessions = _tunnel.getSessions();
for (int i = 0; i < sessions.size(); i++) {
I2PSession session = (I2PSession)sessions.get(i);
Destination dest = session.getMyDestination();
if (dest != null)
return dest.toBase64();
}
}
return null;
}
public boolean getIsRunning() { return _running; }
public boolean getIsStarting() { return _starting; }

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,217 @@
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 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,327 @@ 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 getInternalType(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getType();
else
return "";
}
public String getClientInterface(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getListenOnInterface();
else
return "";
}
public int getTunnelStatus(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun == null) return NOT_RUNNING;
if (tun.getIsRunning()) return RUNNING;
else if (tun.getIsStarting()) return STARTING;
else return NOT_RUNNING;
}
public String getTunnelDescription(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
return tun.getDescription();
else
return "";
}
public String 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 "";
}
public String getDestinationBase64(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
String rv = tun.getMyDestination();
if (rv != null)
return rv;
else
return "";
} else {
return "";
}
}
///
/// bean props for form submission
///
/**
* What type of tunnel (httpclient, client, or server). This is
* required when adding a new tunnel.
@@ -76,17 +383,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 +455,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 +475,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 +495,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 +507,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);
@@ -383,8 +540,10 @@ public class WebEditPageHelper {
config.setProperty("description", _description);
if (_i2cpHost != null)
config.setProperty("i2cpHost", _i2cpHost);
if (_i2cpPort != null)
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) )
config.setProperty("i2cpPort", _i2cpPort);
else
config.setProperty("i2cpPort", "7654");
if (_customOptions != null) {
StringTokenizer tok = new StringTokenizer(_customOptions);
@@ -422,32 +581,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,233 @@
<%@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="6" maxlength="5" name="targetPort" value="<%=editBean.getTargetPort(curTunnel)%>" />
</td>
</tr>
<% String curType = editBean.getInternalType(curTunnel);
if ( (curType == null) || (curType.trim().length() <= 0) )
curType = request.getParameter("type");
if ("httpserver".equals(curType)) { %>
<tr>
<td><b>Website name:</b></td>
<td><input type="text" size="20" name="spoofedHost" value="<%=editBean.getSpoofedHost(curTunnel)%>" />
</td></tr>
<% } %>
<tr>
<td><b>Private key file:</b>
</td>
<td><input type="text" size="30" name="privKeyFile" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" /></td>
</tr>
<tr>
<td><b>Profile:</b>
</td>
<td>
<select name="profile">
<% if (editBean.isInteractive(curTunnel)) { %>
<option value="interactive" selected="true">interactive connection </option>
<option value="bulk">bulk connection (downloads/websites/BT) </option>
<% } else { %>
<option value="interactive">interactive connection </option>
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
<% } %>
</select>
</td>
</tr>
<tr>
<td valign="top" align="left"><b>Local destination:</b><br /><i>(if known)</i></td>
<td valign="top" align="left"><input type="text" size="60" value="<%=editBean.getDestinationBase64(curTunnel)%>" /></td>
</tr>
<tr>
<td colspan="2" align="center">
<b><hr size="1">
Advanced networking options<br />
</b>
</td>
</tr>
<tr>
<td>
<b>Tunnel depth:</b>
</td>
<td><select name="tunnelDepth">
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
switch (tunnelDepth) {
case 0: %>
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<% break;
case 1: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<% break;
case 2: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
<% break;
default: %>
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
<% } %>
</select>
</td>
</tr>
<tr>
<td><b>Tunnel count:</b>
</td>
<td>
<select name="tunnelCount">
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
switch (tunnelCount) {
case 1: %>
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
case 2: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
case 3: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<% break;
default: %>
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
<% } %>
</select>
</td>
<tr>
<td><b>I2CP host:</b>
</td>
<td>
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
</td>
</tr>
<tr>
<td><b>I2CP port:</b>
</td>
<td>
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
</td>
</tr>
<tr>
<td><b>Custom options:</b>
</td>
<td>
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
</td>
</tr>
<tr>
<td colspan="2">
<hr size="1">
</td>
</tr>
<tr>
<td>
<b>Save:</b>
</td>
<td>
<input type="submit" name="action" value="Save changes" />
</td>
</tr>
<tr>
<td><b>Delete?</b>
</td>
<td>
<input type="submit" name="action" value="Delete this proxy" /> &nbsp;&nbsp;
<b><span style="color:#dd0000;">confirm delete:</span></b>
<input type="checkbox" value="true" name="removeConfirm" />
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>

View File

@@ -1,26 +1,184 @@
<%@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><%
if ("httpserver".equals(indexBean.getInternalType(curServer))) {
%> (<a href="http://<%=(new java.util.Random()).nextLong()%>.i2p/?i2paddresshelper=<%=indexBean.getDestinationBase64(curServer)%>">preview</a>)<%
}
break;
case IndexBean.NOT_RUNNING:
%><b><span style="color:#dd0000">Not Running</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curServer%>">[START]</a><%
break;
case IndexBean.STARTING:
%>
<b><span style="color:#339933">Starting...</span></b>
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
break;
}
%>
</td>
</tr>
<tr><td valign="top" align="right">Description:</td>
<td valign="top" align="left" colspan="2"><%=indexBean.getTunnelDescription(curServer)%></td></tr>
<% } %>
</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

@@ -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,274 @@
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 synchronized NewsFetcher getInstance() { return _instance; }
public static final synchronized NewsFetcher getInstance(I2PAppContext ctx) {
if (_instance != null)
return _instance;
_instance = new NewsFetcher(ctx);
return _instance;
}
private static final String NEWS_FILE = "docs/news.xml";
private static final String TEMP_NEWS_FILE = "docs/news.xml.temp";
private NewsFetcher(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(NewsFetcher.class);
_instance = this;
updateLastFetched();
}
private void updateLastFetched() {
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() {
updateLastFetched();
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() {
_updateAvailable = false;
File news = new File(NEWS_FILE);
if ( (!news.exists()) || (news.length() <= 0) ) 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 + " with " + (alreadyTransferred+bytesTransferred));
File temp = new File(TEMP_NEWS_FILE);
if (temp.exists()) {
boolean copied = FileUtil.copy(TEMP_NEWS_FILE, NEWS_FILE, true);
if (copied) {
temp.delete();
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Failed to copy the news file!");
}
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Transfer complete, but no file?");
}
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,11 @@ public class RouterConsoleRunner {
} catch (Throwable t) {
t.printStackTrace();
}
NewsFetcher fetcher = NewsFetcher.getInstance(I2PAppContext.getGlobalContext());
I2PThread t = new I2PThread(fetcher, "NewsFetcher");
t.setDaemon(true);
t.start();
}
private void initialize(WebApplicationContext context) {

View File

@@ -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(_context).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 = 30*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 */

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

@@ -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.30 $ $Date: 2005/03/06 19:07:28 $";
public final static String VERSION = "0.5.0.3";
public final static String ID = "$Revision: 1.33 $ $Date: 2005/03/29 19:07:37 $";
public final static String VERSION = "0.5.0.6";
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

@@ -40,6 +40,19 @@ public class AESEngine {
* @param length how much data to encrypt
*/
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
encrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length);
}
/** Encrypt the payload with the session key
* @param payload data to be encrypted
* @param payloadIndex index into the payload to start encrypting
* @param out where to store the result
* @param outIndex where in out to start writing
* @param sessionKey private esession key to encrypt to
* @param iv IV for CBC
* @param length how much data to encrypt
*/
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) {
System.arraycopy(payload, payloadIndex, out, outIndex, length);
_log.warn("Warning: AES is disabled");
}
@@ -120,6 +133,19 @@ public class AESEngine {
* @param length how much data to decrypt
*/
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
decrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length);
}
/** Decrypt the data with the session key
* @param payload data to be decrypted
* @param payloadIndex index into the payload to start decrypting
* @param out where to store the cleartext
* @param outIndex where in out to start writing
* @param sessionKey private session key to decrypt to
* @param iv IV for CBC
* @param length how much data to decrypt
*/
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) {
System.arraycopy(payload, payloadIndex, out, outIndex, length);
_log.warn("Warning: AES is disabled");
}

View File

@@ -12,8 +12,10 @@ package net.i2p.crypto;
import java.security.InvalidKeyException;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**
@@ -31,14 +33,20 @@ public class CryptixAESEngine extends AESEngine {
private final static byte FAKE_KEY = 0x2A;
private CryptixAESKeyCache _cache;
private static final ByteCache _prevCache = ByteCache.getInstance(16, 16);
public CryptixAESEngine(I2PAppContext context) {
super(context);
_log = context.logManager().getLog(CryptixAESEngine.class);
_cache = new CryptixAESKeyCache();
}
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
if ( (payload == null) || (out == null) || (sessionKey == null) || (iv == null) || (iv.length != 16) )
encrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length);
}
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) {
if ( (payload == null) || (out == null) || (sessionKey == null) || (iv == null) )
throw new NullPointerException("invalid args to aes");
if (payload.length < payloadIndex + length)
throw new IllegalArgumentException("Payload is too short");
@@ -57,7 +65,7 @@ public class CryptixAESEngine extends AESEngine {
int numblock = length / 16;
DataHelper.xor(iv, 0, payload, payloadIndex, out, outIndex, 16);
DataHelper.xor(iv, ivOffset, payload, payloadIndex, out, outIndex, 16);
encryptBlock(out, outIndex, sessionKey, out, outIndex);
for (int x = 1; x < numblock; x++) {
DataHelper.xor(out, outIndex + (x-1) * 16, payload, payloadIndex + x * 16, out, outIndex + x * 16, 16);
@@ -66,8 +74,10 @@ public class CryptixAESEngine extends AESEngine {
}
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
if ((iv== null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
|| (iv.length != 16) )
decrypt(payload, payloadIndex, out, outIndex, sessionKey, iv, 0, length);
}
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int ivOffset, int length) {
if ((iv== null) || (payload == null) || (payload.length <= 0) || (sessionKey == null) )
throw new IllegalArgumentException("bad setup");
else if (out == null)
throw new IllegalArgumentException("out is null");
@@ -84,12 +94,32 @@ public class CryptixAESEngine extends AESEngine {
int numblock = length / 16;
if (length % 16 != 0) numblock++;
ByteArray prevA = _prevCache.acquire();
byte prev[] = prevA.getData();
ByteArray curA = _prevCache.acquire();
byte cur[] = curA.getData();
System.arraycopy(iv, ivOffset, prev, 0, 16);
for (int x = 0; x < numblock; x++) {
System.arraycopy(payload, payloadIndex + (x * 16), cur, 0, 16);
decryptBlock(payload, payloadIndex + (x * 16), sessionKey, out, outIndex + (x * 16));
DataHelper.xor(out, outIndex + x * 16, prev, 0, out, outIndex + x * 16, 16);
iv = prev; // just use IV to switch 'em around
prev = cur;
cur = iv;
}
/*
decryptBlock(payload, payloadIndex, sessionKey, out, outIndex);
DataHelper.xor(out, outIndex, iv, 0, out, outIndex, 16);
for (int x = 1; x < numblock; x++) {
decryptBlock(payload, payloadIndex + (x * 16), sessionKey, out, outIndex + (x * 16));
DataHelper.xor(out, outIndex + x * 16, payload, payloadIndex + (x - 1) * 16, out, outIndex + x * 16, 16);
}
*/
_prevCache.release(prevA);
_prevCache.release(curA);
}
public final void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) {

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,117 @@
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()));
return calculate(key, data, 0, data.length);
}
/**
* Calculate the HMAC of the data with the given key
*/
public Hash calculate(SessionKey key, byte data[], int offset, int length) {
if ((key == null) || (key.getData() == null) || (data == null))
throw new NullPointerException("Null arguments for HMAC");
Buffer buf = new Buffer(length);
calculate(key, data, offset, length, 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) {
calculate(key, data, 0, data.length, buf);
}
/**
* Calculate the HMAC of the data with the given key
*/
public void calculate(SessionKey key, byte data[], int offset, int length, Buffer buf) {
// inner hash
padKey(key.getData(), _IPAD, buf.padded);
System.arraycopy(buf.padded, 0, buf.innerBuf, 0, PAD_LENGTH);
System.arraycopy(data, offset, buf.innerBuf, PAD_LENGTH, 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

@@ -76,6 +76,20 @@ public class KeyGenerator {
return keys;
}
/** Convert a PrivateKey to its corresponding PublicKey
* @param a PrivateKey object
* @return the corresponding PublicKey object
* @author aum
*/
public static PublicKey getPublicKey(PrivateKey priv) {
BigInteger a = new NativeBigInteger(priv.toByteArray());
BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp);
PublicKey pub = new PublicKey();
byte [] pubBytes = aalpha.toByteArray();
pub.setData(padBuffer(pubBytes, PublicKey.KEYSIZE_BYTES));
return pub;
}
/** Generate a pair of DSA keys, where index 0 is a SigningPublicKey, and
* index 1 is a SigningPrivateKey
* @return pair of keys
@@ -100,6 +114,20 @@ public class KeyGenerator {
return keys;
}
/** Convert a SigningPrivateKey to a SigningPublicKey
* @param a SigningPrivateKey object
* @return a SigningPublicKey object
* @author aum
*/
public static SigningPublicKey getSigningPublicKey(SigningPrivateKey priv) {
BigInteger x = new NativeBigInteger(priv.toByteArray());
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
SigningPublicKey pub = new SigningPublicKey();
byte [] pubBytes = padBuffer(y.toByteArray(), SigningPublicKey.KEYSIZE_BYTES);
pub.setData(pubBytes);
return pub;
}
/**
* Pad the buffer w/ leading 0s or trim off leading bits so the result is the
* given length.

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

@@ -12,6 +12,7 @@ package net.i2p.data;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import net.i2p.util.Log;
@@ -35,6 +36,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;
}
@@ -146,4 +156,22 @@ public class Destination extends DataStructureImpl {
if (__calculatedHash == null) __calculatedHash = super.calculateHash();
return __calculatedHash;
}
public static void main(String args[]) {
if (args.length == 0) {
System.err.println("Usage: Destination filename");
} else {
FileInputStream in = null;
try {
in = new FileInputStream(args[0]);
Destination d = new Destination();
d.readBytes(in);
System.out.println(d.toBase64());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
}
}
}

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

@@ -14,6 +14,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.util.Log;
import net.i2p.crypto.KeyGenerator;
/**
* Defines the PrivateKey as defined by the I2P data structure spec.
@@ -32,6 +33,16 @@ public class PrivateKey extends DataStructureImpl {
setData(null);
}
/** constructs from base64
* @param a string of base64 data (the output of .toBase64() called
* on a prior instance of PrivateKey
* @author aum
*/
public PrivateKey(String base64Data) throws DataFormatException {
this();
fromBase64(base64Data);
}
public byte[] getData() {
return _data;
}
@@ -77,4 +88,14 @@ public class PrivateKey extends DataStructureImpl {
buf.append("]");
return buf.toString();
}
/** derives a new PublicKey object derived from the secret contents
* of this PrivateKey
* @return a PublicKey object
* @author aum
*/
public PublicKey toPublic() {
return KeyGenerator.getPublicKey(this);
}
}

View File

@@ -32,6 +32,16 @@ public class PublicKey extends DataStructureImpl {
setData(null);
}
/** constructs from base64
* @param a string of base64 data (the output of .toBase64() called
* on a prior instance of PublicKey
* @author aum
*/
public PublicKey(String base64Data) throws DataFormatException {
this();
fromBase64(base64Data);
}
public byte[] getData() {
return _data;
}
@@ -76,4 +86,5 @@ public class PublicKey extends DataStructureImpl {
buf.append("]");
return buf.toString();
}
}

View File

@@ -14,6 +14,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.util.Log;
import net.i2p.crypto.KeyGenerator;
/**
* Defines the SigningPrivateKey as defined by the I2P data structure spec.
@@ -29,9 +30,19 @@ public class SigningPrivateKey extends DataStructureImpl {
public final static int KEYSIZE_BYTES = 20;
public SigningPrivateKey() { this(null); }
public SigningPrivateKey() { this((byte[])null); }
public SigningPrivateKey(byte data[]) { setData(data); }
/** constructs from base64
* @param a string of base64 data (the output of .toBase64() called
* on a prior instance of SigningPrivateKey
* @author aum
*/
public SigningPrivateKey(String base64Data) throws DataFormatException {
this();
fromBase64(base64Data);
}
public byte[] getData() {
return _data;
}
@@ -76,4 +87,12 @@ public class SigningPrivateKey extends DataStructureImpl {
buf.append("]");
return buf.toString();
}
/** converts this signing private key to its public equivalent
* @return a SigningPublicKey object derived from this private key
* @author aum
*/
public SigningPublicKey toPublic() {
return KeyGenerator.getSigningPublicKey(this);
}
}

View File

@@ -29,9 +29,19 @@ public class SigningPublicKey extends DataStructureImpl {
public final static int KEYSIZE_BYTES = 128;
public SigningPublicKey() { this(null); }
public SigningPublicKey() { this((byte[])null); }
public SigningPublicKey(byte data[]) { setData(data); }
/** constructs from base64
* @param a string of base64 data (the output of .toBase64() called
* on a prior instance of SigningPublicKey
* @author aum
*/
public SigningPublicKey(String base64Data) throws DataFormatException {
this();
fromBase64(base64Data);
}
public byte[] getData() {
return _data;
}

View File

@@ -0,0 +1,558 @@
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);
boolean strictSize = (_bytesRemaining >= 0);
int remaining = (int)_bytesRemaining;
byte buf[] = new byte[1024];
while (_keepFetching && ( (remaining > 0) || !strictSize )) {
int toRead = buf.length;
if (strictSize && toRead > remaining)
toRead = remaining;
int read = _proxyIn.read(buf, 0, toRead);
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

@@ -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,127 @@
$Id: history.txt,v 1.172 2005/03/18 03:48:00 jrandom Exp $
$Id: history.txt,v 1.188 2005/04/05 17:24:32 jrandom Exp $
* 2005-04-06 0.5.0.6 released
2005-04-05 jrandom
* Retry I2PTunnel startup if we are unable to build a socketManager for a
client or httpclient tunnel.
* Add some basic sanity checking on the I2CP settings (thanks duck!)
2005-04-05 jrandom
* After a successfull netDb search for a leaseSet, republish it to all of
the peers we have tried so far who did not give us the key (up to 10),
rather than the old K closest (which may include peers who had given us
the key)
* Don't wait 5 minutes to publish a leaseSet (duh!), and rather than
republish it every 5 minutes, republish it every 3. In addition, always
republish as soon as the leaseSet changes (duh^2).
* Minor fix for oddball startup race (thanks travis_bickle!)
* Minor AES update to allow in-place decryption.
2005-04-03 jrandom
* EepGet fix for open-ended HTTP fetches (such as the news.xml
feeding the NewsFetcher)
2005-04-01 jrandom
* Allow editing I2PTunnel server instances with five digit ports
(thanks nickless_head!)
* More NewsFetcher debugging for reported weirdness
2005-04-01 jrandom
* Fix to check for missing news file (thanks smeghead!)
* Added destination display CLI:
java -cp lib/i2p.jar net.i2p.data.Destination privKeyFilename
* Added destination display to the web interface (thanks pnspns)
* Installed CIA backdoor
* 2005-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

View File

@@ -1,6 +1,11 @@
; TC's hosts.txt guaranteed freshness
; $Id: hosts.txt,v 1.132 2005/03/15 21:46:17 jrandom Exp $
; $Id: hosts.txt,v 1.137 2005/03/29 22:21:18 jrandom Exp $
; changelog:
; (1.159) added 55cancri.i2p
; (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
@@ -361,4 +366,13 @@ search.i2p=9GIzUtJ7aQTTPsQtMFQ-RFzKENhODuftOcD9ZyQig6bkuYND2MNMPmcSO8YjtN74MKBjp
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
55cancri.i2p=ivtLWVLaJFWDE-K8ReYgJ6~E6iBYfUd05kR95aVx2KHVgV4KUqm2Q5j5Qi2aC~KTFXtTiuxmXYD~z9KH5t2FR3kYUgRVZ4zzMMy1CJXGnc7ktDpSPkCVxbAEADTVSIMot3l6LuqoLXTZIMgJAuP1xhIuWbs5QUD2AdeXd1TUhzQFff0vWU5SGiHhtvNgvK4kgR9rpLvEJQdp2vUZGOX7WQ1wTEec6DuRujsPorKlXhj6zE-eV5a73pYF0ZREUbFBJNtLzJ2gwBbKL7qQ1zHHF4pcNXBYhSjV5nibKp6bijvjH~DcAoo-bU3dKPdmYaX4FPyQtS6IbC4CxlU5mr4oqBRFEF9HWTIRS8Ye0vAaA~QbQ03sdve~LWIL3pjcwy8XhkjE8SLsS1vhJLlIQWhyfRnasDWvu0eGnZwsZzJEoDed5MPIqc6cJfQ3GcaVtLkfrzSpGazZFTNZ69YeCvN7tUc1kSRfjXtq-qD9qE3rg7SeAHcHTihwIPkjslgOkVyCAAAA

14
initialNews.xml Normal file
View File

@@ -0,0 +1,14 @@
<i2p.news date="$Date: 2005/03/29 22:27:55 $">
<i2p.release version="0.5.0.6" date="2005/04/06" 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/April-2005/000676.html"
publicannouncement="http://dev.i2p.net/pipermail/i2p/April-2005/000676.html" />
<i2p.notes date="2005/04/06"
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000675.html"
publicurl="http://dev.i2p.net/pipermail/i2p/March-2005/000675.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/meeting136"
publiclogs="http://www.i2p.net/meeting136" />
<h1>Congratulations on getting I2P installed!</h1>
</i2p.news>

View File

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

View File

@@ -0,0 +1,2 @@
#!/bin/sh
java -cp lib/i2p.jar net.i2p.util.EepGet $*

View File

@@ -38,6 +38,7 @@ start /b /i /d"%INSTALL_PATH%" i2prouter.bat %INSTALL_PATH%
) else (
del "%INSTALL_PATH%eepget"
del "%INSTALL_PATH%i2prouter"
:: del "%INSTALL_PATH%install_i2p_service_unix"
del "%INSTALL_PATH%install_i2p_service_winnt.bat"

View File

@@ -56,6 +56,7 @@ esac
cp $wrapperpath/wrapper.jar ./lib/
cp $wrapperpath/i2psvc .
chmod 744 ./eepget
chmod 744 ./i2psvc
chmod 744 ./scripts/i2pbench.sh
chmod 744 ./scripts/i2ptest.sh

View File

@@ -1,162 +1,162 @@
#********************************************************************
# Wrapper Properties
#********************************************************************
# Java Application
wrapper.java.command=java
# Java Main class. This class must implement the WrapperListener interface
# or guarantee that the WrapperManager class is initialized. Helper
# classes are provided to do this for you. See the Integration section
# of the documentation for details.
wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp
# Java Classpath (include wrapper.jar) Add class path elements as
# needed starting from 1
# i2p sdk, public domain/BSD/Cryptix
wrapper.java.classpath.1=lib/i2p.jar
# router, depends on i2p.jar, public domain
wrapper.java.classpath.2=lib/router.jar
# compiled jbigi libraries, contains static libGMP, lgpl
wrapper.java.classpath.3=lib/jbigi.jar
# sam bridge, public domain (depends on i2p.jar)
wrapper.java.classpath.4=lib/sam.jar
# ministreaming lib -interfaces for streaming, BSD (depends on i2p.jar)
wrapper.java.classpath.5=lib/mstreaming.jar
# full streaming lib, public domain (depends on mstreaming.jar, i2p.jar)
wrapper.java.classpath.6=lib/streaming.jar
# router console, public domain (depends on i2p.jar, router.jar)
wrapper.java.classpath.7=lib/routerconsole.jar
# i2ptunnel, GPL (depends on mstreaming.jar, i2p.jar)
wrapper.java.classpath.8=lib/i2ptunnel.jar
# jetty libraries (and dependencies), apache licensed
wrapper.java.classpath.9=lib/org.mortbay.jetty.jar
wrapper.java.classpath.10=lib/javax.servlet.jar
wrapper.java.classpath.11=lib/jasper-compiler.jar
wrapper.java.classpath.12=lib/jasper-runtime.jar
wrapper.java.classpath.13=lib/commons-logging.jar
wrapper.java.classpath.14=lib/commons-el.jar
wrapper.java.classpath.15=lib/ant.jar
wrapper.java.classpath.16=lib/xercesImpl.jar
# java service wrapper, BSD
wrapper.java.classpath.17=lib/wrapper.jar
# systray, LGPL
wrapper.java.classpath.18=lib/systray.jar
wrapper.java.classpath.19=lib/systray4j.jar
# Java Library Path (location of Wrapper.DLL or libwrapper.so)
wrapper.java.library.path.1=.
wrapper.java.library.path.2=lib
# Java Additional Parameters
wrapper.java.additional.1=-DloggerFilenameOverride=logs/log-router-@.txt
wrapper.java.additional.2=-Dorg.mortbay.http.Version.paranoid=true
wrapper.java.additional.3=-Dorg.mortbay.util.FileResource.checkAliases=false
# Initial Java Heap Size (in MB)
#wrapper.java.initmemory=4
# Maximum Java Heap Size (in MB)
#wrapper.java.maxmemory=32
# Application parameters. Add parameters as needed starting from 1
wrapper.app.parameter.1=net.i2p.router.Router
#********************************************************************
# Wrapper Logging Properties
#********************************************************************
# Format of output for the console. (See docs for formats)
wrapper.console.format=PM
# Log Level for console output. (See docs for log levels)
wrapper.console.loglevel=INFO
# Log file to use for wrapper output logging.
wrapper.logfile=wrapper.log
# Format of output for the log file. (See docs for formats)
wrapper.logfile.format=LPTM
# Log Level for log file output. (See docs for log levels)
wrapper.logfile.loglevel=INFO
# Maximum size that the log file will be allowed to grow to before
# the log is rolled. Size is specified in bytes. The default value
# of 0, disables log rolling. May abbreviate with the 'k' (kb) or
# 'm' (mb) suffix. For example: 10m = 10 megabytes.
wrapper.logfile.maxsize=1m
# Maximum number of rolled log files which will be allowed before old
# files are deleted. The default value of 0 implies no limit.
wrapper.logfile.maxfiles=2
# Log Level for sys/event log output. (See docs for log levels)
wrapper.syslog.loglevel=NONE
# choose what to do if the JVM kills itself based on the exit code
wrapper.on_exit.default=SHUTDOWN
wrapper.on_exit.0=SHUTDOWN
wrapper.on_exit.1=SHUTDOWN
# OOM
wrapper.on_exit.10=RESTART
# graceful shutdown
wrapper.on_exit.2=SHUTDOWN
# hard shutdown
wrapper.on_exit.3=SHUTDOWN
# hard restart
wrapper.on_exit.4=RESTART
# hard restart
wrapper.on_exit.5=RESTART
# the router may take a few seconds to save state, etc
wrapper.jvm_exit.timeout=10
# give the OS 60s to clear all the old sockets / etc before restarting
wrapper.restart.delay=60
wrapper.ping.interval=600
wrapper.ping.timeout=605
# use the wrapper's internal timer thread. otherwise this would
# force a restart of the router during daylight savings time as well
# as any time that the OS clock changes
wrapper.use_system_time=false
# pid file for the JVM
wrapper.java.pidfile=routerjvm.pid
# pid file for the service monitoring the JVM
#
# From i2prouter:
#
# PIDDIR="."
# APP_NAME="i2p"
# PIDFILE="$PIDDIR/$APP_NAME.pid"
#
# This means i2prouter looks for './i2p.pid'.
wrapper.pidfile=i2p.pid
#********************************************************************
# Wrapper NT Service Properties
#********************************************************************
# WARNING - Do not modify any of these properties when an application
# using this configuration file has been installed as a service.
# Please uninstall the service before modifying this section. The
# service can then be reinstalled.
# Name of the service
wrapper.ntservice.name=i2p
# Display name of the service
wrapper.ntservice.displayname=I2P Service
# Description of the service
wrapper.ntservice.description=The I2P router service
# Service dependencies. Add dependencies as needed starting from 1
wrapper.ntservice.dependency.1=
# Mode in which the service is installed. AUTO_START or DEMAND_START
wrapper.ntservice.starttype=AUTO_START
# Allow the service to interact with the desktop.
wrapper.ntservice.interactive=true
#********************************************************************
# Wrapper Properties
#********************************************************************
# Java Application
wrapper.java.command=java
# Java Main class. This class must implement the WrapperListener interface
# or guarantee that the WrapperManager class is initialized. Helper
# classes are provided to do this for you. See the Integration section
# of the documentation for details.
wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp
# Java Classpath (include wrapper.jar) Add class path elements as
# needed starting from 1
# i2p sdk, public domain/BSD/Cryptix
wrapper.java.classpath.1=lib/i2p.jar
# router, depends on i2p.jar, public domain
wrapper.java.classpath.2=lib/router.jar
# compiled jbigi libraries, contains static libGMP, lgpl
wrapper.java.classpath.3=lib/jbigi.jar
# sam bridge, public domain (depends on i2p.jar)
wrapper.java.classpath.4=lib/sam.jar
# ministreaming lib -interfaces for streaming, BSD (depends on i2p.jar)
wrapper.java.classpath.5=lib/mstreaming.jar
# full streaming lib, public domain (depends on mstreaming.jar, i2p.jar)
wrapper.java.classpath.6=lib/streaming.jar
# router console, public domain (depends on i2p.jar, router.jar)
wrapper.java.classpath.7=lib/routerconsole.jar
# i2ptunnel, GPL (depends on mstreaming.jar, i2p.jar)
wrapper.java.classpath.8=lib/i2ptunnel.jar
# jetty libraries (and dependencies), apache licensed
wrapper.java.classpath.9=lib/org.mortbay.jetty.jar
wrapper.java.classpath.10=lib/javax.servlet.jar
wrapper.java.classpath.11=lib/jasper-compiler.jar
wrapper.java.classpath.12=lib/jasper-runtime.jar
wrapper.java.classpath.13=lib/commons-logging.jar
wrapper.java.classpath.14=lib/commons-el.jar
wrapper.java.classpath.15=lib/ant.jar
wrapper.java.classpath.16=lib/xercesImpl.jar
# java service wrapper, BSD
wrapper.java.classpath.17=lib/wrapper.jar
# systray, LGPL
wrapper.java.classpath.18=lib/systray.jar
wrapper.java.classpath.19=lib/systray4j.jar
# Java Library Path (location of Wrapper.DLL or libwrapper.so)
wrapper.java.library.path.1=.
wrapper.java.library.path.2=lib
# Java Additional Parameters
wrapper.java.additional.1=-DloggerFilenameOverride=logs/log-router-@.txt
wrapper.java.additional.2=-Dorg.mortbay.http.Version.paranoid=true
wrapper.java.additional.3=-Dorg.mortbay.util.FileResource.checkAliases=false
# Initial Java Heap Size (in MB)
#wrapper.java.initmemory=4
# Maximum Java Heap Size (in MB)
#wrapper.java.maxmemory=32
# Application parameters. Add parameters as needed starting from 1
wrapper.app.parameter.1=net.i2p.router.Router
#********************************************************************
# Wrapper Logging Properties
#********************************************************************
# Format of output for the console. (See docs for formats)
wrapper.console.format=PM
# Log Level for console output. (See docs for log levels)
wrapper.console.loglevel=INFO
# Log file to use for wrapper output logging.
wrapper.logfile=wrapper.log
# Format of output for the log file. (See docs for formats)
wrapper.logfile.format=LPTM
# Log Level for log file output. (See docs for log levels)
wrapper.logfile.loglevel=INFO
# Maximum size that the log file will be allowed to grow to before
# the log is rolled. Size is specified in bytes. The default value
# of 0, disables log rolling. May abbreviate with the 'k' (kb) or
# 'm' (mb) suffix. For example: 10m = 10 megabytes.
wrapper.logfile.maxsize=1m
# Maximum number of rolled log files which will be allowed before old
# files are deleted. The default value of 0 implies no limit.
wrapper.logfile.maxfiles=2
# Log Level for sys/event log output. (See docs for log levels)
wrapper.syslog.loglevel=NONE
# choose what to do if the JVM kills itself based on the exit code
wrapper.on_exit.default=SHUTDOWN
wrapper.on_exit.0=SHUTDOWN
wrapper.on_exit.1=SHUTDOWN
# OOM
wrapper.on_exit.10=RESTART
# graceful shutdown
wrapper.on_exit.2=SHUTDOWN
# hard shutdown
wrapper.on_exit.3=SHUTDOWN
# hard restart
wrapper.on_exit.4=RESTART
# hard restart
wrapper.on_exit.5=RESTART
# the router may take a few seconds to save state, etc
wrapper.jvm_exit.timeout=10
# give the OS 60s to clear all the old sockets / etc before restarting
wrapper.restart.delay=60
wrapper.ping.interval=600
wrapper.ping.timeout=605
# use the wrapper's internal timer thread. otherwise this would
# force a restart of the router during daylight savings time as well
# as any time that the OS clock changes
wrapper.use_system_time=false
# pid file for the JVM
wrapper.java.pidfile=routerjvm.pid
# pid file for the service monitoring the JVM
#
# From i2prouter:
#
# PIDDIR="."
# APP_NAME="i2p"
# PIDFILE="$PIDDIR/$APP_NAME.pid"
#
# This means i2prouter looks for './i2p.pid'.
wrapper.pidfile=i2p.pid
#********************************************************************
# Wrapper NT Service Properties
#********************************************************************
# WARNING - Do not modify any of these properties when an application
# using this configuration file has been installed as a service.
# Please uninstall the service before modifying this section. The
# service can then be reinstalled.
# Name of the service
wrapper.ntservice.name=i2p
# Display name of the service
wrapper.ntservice.displayname=I2P Service
# Description of the service
wrapper.ntservice.description=The I2P router service
# Service dependencies. Add dependencies as needed starting from 1
wrapper.ntservice.dependency.1=
# Mode in which the service is installed. AUTO_START or DEMAND_START
wrapper.ntservice.starttype=AUTO_START
# Allow the service to interact with the desktop.
wrapper.ntservice.interactive=true

16
news.xml Normal file
View File

@@ -0,0 +1,16 @@
<i2p.news date="$Date: 2005/04/03 02:22:28 $">
<i2p.release version="0.5.0.6" date="2005/04/06" 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/April-2005/000676.html"
publicannouncement="http://dev.i2p.net/pipermail/i2p/April-2005/000676.html" />
<i2p.notes date="2005/04/06"
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000675.html"
publicurl="http://dev.i2p.net/pipermail/i2p/March-2005/000675.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/meeting136"
publiclogs="http://www.i2p.net/meeting136" />
<b>0.5.0.6 release available!</b> Thanks everyone for upgrading to 0.5.0.5 so
quickly - this new 0.5.0.6 should help out with eepsite reliability
substantially, fixing several major bugs in the netDb operation. <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

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

@@ -0,0 +1,620 @@
<code>$Id: udp.html,v 1.8 2005/04/04 12:21:30 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. The payloadLength in the MAC is a 2 byte unsigned
integer in 2s complement.</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>4 byte relay tag which Alice can publish (else 0x0)</li>
<li>40 byte DSA signature of the critical exchanged data, encrypted
with another layer of encryption using the negotiated sessionKey.
The IV is reused here (but with the sessionKey, not the introKey).</li>
<li>8 bytes padding, encrypted with an additional layer of encryption
using the negotiated session key as part of the DSA block</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, with an additional layer of encryption over the 40 byte
signature and the following 8 bytes padding.</td></tr>
</table>
<pre>
+----+----+----+----+----+----+----+----+
| Y, as calculated from DH |
| |
. . .
| |
+----+----+----+----+----+----+----+----+
|size| that many byte IP address (4-16) |
+----+----+----+----+----+----+----+----+
| Port (A)| public relay tag | |
+----+----+----+----+----+----+ |
| DSA signature |
| |
| |
| |
| +----+----+
| |
+----+----+----+----+----+----+----+----+
(8 bytes of padding) | |
+----+----+----+----+----+----+ |
| 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
bit 3: explicit congestion notification
bit 4: request previous ACKs
bit 5: want reply
bit 6: extended data included
bit 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>If extended data included:<ul>
<li>1 byte data size</li>
<li>that many bytes of extended data (currently uninterpreted)</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="congestioncontrol">Congestion control</a></h2>
<p>SSU's need for only semireliable delivery, TCP-friendly operation,
and the capacity for high throughput allows a great deal of latitude in
congestion control. The congestion control algorithm outlined below is
meant to be both efficient in bandwidth as well as simple to implement.</p>
<p>Data is transmitted in volleys of up to 1 second, sending N bytes within
P packets. The volley a packet is a part of is defined by the second field
in the encrypted <a href="#payload">payload</a>. The receiver of a volley
should send back a full set of ACKs and NACKs for message IDs received in
the previous volley - these ACKs and NACKs should be included in all messages
sent until either the volley changes again or the the receiver gets a message
in the current volley stating that the previous ACKs are no longer required.
Subsequent responses from the receiver in the current volley should not
contain the ACKs.</p>
<p>After receiving a volley with at least one data message fragment, the
receiver should send back at least one message with the ACKs. Each time
subsequent messages arrive on the current volley with the "request previous
ACKs" flag set, if no messages in the current volley have arrived without
that being set the receiver should send back a data message with the ACKs,
if the receiver has the bandwidth to do so.</p>
<p>The number of bytes sent in each volley (N) should be initialized as
8192 bytes (an arbitrarily high value). At the beginning of a volley, if
the ACKs/NACKs received for the volley two periods back contain any NACKs,
N should be set to the minimum of N and the number of bytes fully ACKed,
though no less than 1/2 of N. If there were no NACKs and all of the
messages sent were ACKed, N is increased by the average packet size. If
a message is received in a volley with the explicit congestion
notification bit set, at the beginning of the next volley N is set to
1/2 N.</p>
<p>Messages which are partially sent or NACKed have the unsent fragments
transmitted in the next volley, unless the message expiration occurs, in
which case it is dropped entirely.</p>
<p>The simplest possible implementation does not need to pad the packets to
any particular size, but instead just places a single message fragment into
a packet and sends it off (careful not to exceed the MTU). A more efficient
strategy would be to bundle multiple message fragments into the same packet,
so long as it doesn't exceed the MTU, but this is not necessary. Eventually,
a set of fixed packet sizes may be appropriate to further hide the data
fragmentation to external adversaries, but the tunnel, garlic, and end to
end padding should be sufficient for most needs until then.</p>
<h3><a name="congestionscenarios">Congestion scenarios</a></h3>
<b>Unidirectional transfer</b><br />
<pre>
Alice Bob
Data 1, volley 1, no ACKs---------&gt;
Data 2, volley 1, no ACKs---------&gt;
Data 3, volley 1, no ACKs---------&gt;
Data 4, volley 1, no ACKs---------&gt;
Data 5, volley 2, want ACKs-------&gt;
Data 6, volley 2, want ACKs-------&gt; // want ACK since ACKs not received
&lt;------------------ACK 1, 2, 3, 4 // automatically sent
&lt;------------------ACK 1, 2, 3, 4 // sent due to Data 6
Data 7, volley 2, no ACKs---------&gt; // no further ACKs required
Data 8, volley 2, no ACKs---------&gt;
Data 9, volley 3, want ACKs-------&gt; // new volley, we want ACKs!
&lt;------------------ACK 5, 6, 7, 8 // automatically sent
Data 10, volley 3, no ACKs---------&gt;
Data 11, volley 3, no ACKs---------&gt;
Data 12, volley 3, no ACKs---------&gt;
&lt;------------------ACK 9, 10, 11, 12 // automatically sent
</pre>
<b>Bidirectional transfer</b><br />
<pre>
Alice Bob
Data 1, volley 1, no ACKs-------------------------&gt;
&lt;-----------------------------Data 1, volley 1, no ACKs
Data 2, volley 1, no ACKs-------------------------&gt;
&lt;-----------------------------Data 2, volley 1, no ACKs
Data 3, volley 1, no ACKs-------------------------&gt;
&lt;-----------------------------Data 3, volley 1, no ACKs
Data 4, volley 1, no ACKs-------------------------&gt;
&lt;-----------------------------Data 4, volley 1, no ACKs
Data 5, volley 2, want ACKs, ACK 1, 2, 3, 4-------&gt; // new volley, send ACKs
&lt;---------------Data 5, volley 2, no ACKs, ACK 1, 2, 3, 4 // received ACKs, no need to ask for them
Data 6, volley 2, no ACKs-------------------------&gt;
&lt;-----------------------------Data 6, volley 2, no ACKs
Data 7, volley 2, no ACKs-------------------------&gt;
&lt;-----------------------------Data 8, volley 2, no ACKs
Data 8, volley 2, no ACKs-------------------------&gt;
&lt;-----------------------------Data 9, volley 2, no ACKs
ACK 5, 6, 7, 8, 9---------------------------------&gt;
&lt;-----------------------------------ACK 5, 6, 7, 8
</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

@@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
*
*/
public class RouterVersion {
public final static String ID = "$Revision: 1.166 $ $Date: 2005/03/18 03:48:01 $";
public final static String VERSION = "0.5.0.3";
public final static String ID = "$Revision: 1.182 $ $Date: 2005/04/06 10:43:25 $";
public final static String VERSION = "0.5.0.6";
public final static long BUILD = 0;
public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION);

View File

@@ -112,6 +112,10 @@ public class StatisticsManager implements Service {
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 });

View File

@@ -288,8 +288,10 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
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");
if (!_finished) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to send to " + _toString + " because we couldn't find their leaseSet");
}
dieFatal();
}
@@ -418,7 +420,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();

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

@@ -65,12 +65,12 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
private PeerSelector _peerSelector;
private RouterContext _context;
/**
* set of Hash objects of leases we're already managing (via RepublishLeaseSetJob).
* Map of Hash to RepublishLeaseSetJob for leases we'realready managing.
* This is added to when we create a new RepublishLeaseSetJob, and the values are
* removed when the job decides to stop running.
*
*/
private Set _publishingLeaseSets;
private Map _publishingLeaseSets;
/**
* Hash of the key currently being searched for, pointing the SearchJob that
@@ -126,7 +126,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_log = _context.logManager().getLog(KademliaNetworkDatabaseFacade.class);
_initialized = false;
_peerSelector = new PeerSelector(_context);
_publishingLeaseSets = new HashSet(8);
_publishingLeaseSets = new HashMap(8);
_lastExploreNew = 0;
_knownRouters = 0;
_activeRequests = new HashMap(8);
@@ -440,14 +440,16 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
synchronized (_explicitSendKeys) {
_explicitSendKeys.add(h);
}
Job j = null;
RepublishLeaseSetJob j = null;
synchronized (_publishingLeaseSets) {
boolean isNew = _publishingLeaseSets.add(h);
if (isNew)
j = (RepublishLeaseSetJob)_publishingLeaseSets.get(h);
if (j == null) {
j = new RepublishLeaseSetJob(_context, this, h);
_publishingLeaseSets.put(h, j);
}
}
if (j != null)
_context.jobQueue().addJob(j);
j.getTiming().setStartAfter(_context.clock().now());
_context.jobQueue().addJob(j);
}
void stopPublishing(Hash target) {

View File

@@ -22,7 +22,7 @@ import net.i2p.util.Log;
*/
public class RepublishLeaseSetJob extends JobImpl {
private Log _log;
private final static long REPUBLISH_LEASESET_DELAY = 5*60*1000; // 5 mins
private final static long REPUBLISH_LEASESET_DELAY = 3*60*1000; // 3 mins
private final static long REPUBLISH_LEASESET_TIMEOUT = 60*1000;
private Hash _dest;
private KademliaNetworkDatabaseFacade _facade;
@@ -32,7 +32,7 @@ public class RepublishLeaseSetJob extends JobImpl {
_log = ctx.logManager().getLog(RepublishLeaseSetJob.class);
_facade = facade;
_dest = destHash;
getTiming().setStartAfter(ctx.clock().now()+REPUBLISH_LEASESET_DELAY);
//getTiming().setStartAfter(ctx.clock().now()+REPUBLISH_LEASESET_DELAY);
}
public String getName() { return "Republish a local leaseSet"; }
public void runJob() {
@@ -40,23 +40,29 @@ public class RepublishLeaseSetJob extends JobImpl {
if (getContext().clientManager().isLocal(_dest)) {
LeaseSet ls = _facade.lookupLeaseSetLocally(_dest);
if (ls != null) {
_log.warn("Client " + _dest + " is local, so we're republishing it");
if (_log.shouldLog(Log.INFO))
_log.info("Client " + _dest + " is local, so we're republishing it");
if (!ls.isCurrent(Router.CLOCK_FUDGE_FACTOR)) {
_log.warn("Not publishing a LOCAL lease that isn't current - " + _dest, new Exception("Publish expired LOCAL lease?"));
if (_log.shouldLog(Log.WARN))
_log.warn("Not publishing a LOCAL lease that isn't current - " + _dest, new Exception("Publish expired LOCAL lease?"));
} else {
getContext().jobQueue().addJob(new StoreJob(getContext(), _facade, _dest, ls, new OnSuccess(getContext()), new OnFailure(getContext()), REPUBLISH_LEASESET_TIMEOUT));
}
} else {
_log.warn("Client " + _dest + " is local, but we can't find a valid LeaseSet? perhaps its being rebuilt?");
if (_log.shouldLog(Log.WARN))
_log.warn("Client " + _dest + " is local, but we can't find a valid LeaseSet? perhaps its being rebuilt?");
}
requeue(REPUBLISH_LEASESET_DELAY);
long republishDelay = getContext().random().nextLong(2*REPUBLISH_LEASESET_DELAY);
requeue(republishDelay);
return;
} else {
_log.info("Client " + _dest + " is no longer local, so no more republishing their leaseSet");
if (_log.shouldLog(Log.INFO))
_log.info("Client " + _dest + " is no longer local, so no more republishing their leaseSet");
}
_facade.stopPublishing(_dest);
} catch (RuntimeException re) {
_log.error("Uncaught error republishing the leaseSet", re);
if (_log.shouldLog(Log.ERROR))
_log.error("Uncaught error republishing the leaseSet", re);
_facade.stopPublishing(_dest);
throw re;
}

View File

@@ -16,10 +16,12 @@ import java.util.Set;
import net.i2p.data.DataHelper;
import net.i2p.data.DataStructure;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterInfo;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.router.Job;
import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
@@ -97,6 +99,7 @@ class SearchJob extends JobImpl {
getContext().statManager().createRateStat("netDb.searchReplyValidated", "How many search replies we get that we are able to validate (fetch)", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
getContext().statManager().createRateStat("netDb.searchReplyNotValidated", "How many search replies we get that we are NOT able to validate (fetch)", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
getContext().statManager().createRateStat("netDb.searchReplyValidationSkipped", "How many search replies we get from unreliable peers that we skip?", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
getContext().statManager().createRateStat("netDb.republishQuantity", "How many peers do we need to send a found leaseSet to?", "NetworkDatabase", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
if (_log.shouldLog(Log.DEBUG))
_log.debug("Search (" + getClass().getName() + " for " + key.toBase64(), new Exception("Search enqueued by"));
}
@@ -204,8 +207,12 @@ class SearchJob extends JobImpl {
+ peer + " : " + (ds == null ? "null" : ds.getClass().getName()));
_state.replyTimeout(peer);
} else {
sendSearch((RouterInfo)ds);
sent++;
if (getContext().shitlist().isShitlisted(peer)) {
// dont bother
} else {
sendSearch((RouterInfo)ds);
sent++;
}
}
}
if (sent <= 0) {
@@ -582,6 +589,15 @@ class SearchJob extends JobImpl {
resend();
}
/**
* After a successful search for a leaseSet, we resend that leaseSet to all
* of the peers we tried and failed to query. This var bounds how many of
* those peers will get the data, in case a search had to crawl about
* substantially.
*
*/
private static final int MAX_LEASE_RESEND = 10;
/**
* After we get the data we were searching for, rebroadcast it to the peers
* we would query first if we were to search for it again (healing the network).
@@ -589,12 +605,54 @@ class SearchJob extends JobImpl {
*/
private void resend() {
DataStructure ds = _facade.lookupLeaseSetLocally(_state.getTarget());
if (ds == null)
if (ds == null) {
ds = _facade.lookupRouterInfoLocally(_state.getTarget());
if (ds != null)
getContext().jobQueue().addJob(new StoreJob(getContext(), _facade, _state.getTarget(),
ds, null, null, RESEND_TIMEOUT,
_state.getSuccessful()));
if (ds != null)
getContext().jobQueue().addJob(new StoreJob(getContext(), _facade, _state.getTarget(),
ds, null, null, RESEND_TIMEOUT,
_state.getSuccessful()));
} else {
Set sendTo = _state.getFailed();
sendTo.addAll(_state.getPending());
int numSent = 0;
for (Iterator iter = sendTo.iterator(); iter.hasNext(); ) {
Hash peer = (Hash)iter.next();
RouterInfo peerInfo = _facade.lookupRouterInfoLocally(peer);
if (peerInfo == null) continue;
if (resend(peerInfo, (LeaseSet)ds))
numSent++;
if (numSent >= MAX_LEASE_RESEND)
break;
}
getContext().statManager().addRateData("netDb.republishQuantity", numSent, numSent);
}
}
/**
* Resend the leaseSet to the peer who had previously failed to
* provide us with the data when we asked them.
*/
private boolean resend(RouterInfo toPeer, LeaseSet ls) {
DatabaseStoreMessage msg = new DatabaseStoreMessage(getContext());
msg.setKey(ls.getDestination().calculateHash());
msg.setLeaseSet(ls);
msg.setMessageExpiration(getContext().clock().now() + RESEND_TIMEOUT);
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundTunnel();
if (outTunnel != null) {
TunnelId targetTunnelId = null; // not needed
Job onSend = null; // not wanted
if (_log.shouldLog(Log.DEBUG))
_log.debug("resending leaseSet out to " + toPeer.getIdentity().getHash() + " through " + outTunnel + ": " + msg);
getContext().tunnelDispatcher().dispatchOutbound(msg, outTunnel.getSendTunnelId(0), null, toPeer.getIdentity().getHash());
return true;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("unable to resend a leaseSet - no outbound exploratory tunnels!");
return false;
}
}
/**

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

@@ -87,8 +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[] { 3
};
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;

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

@@ -33,8 +33,9 @@ public class FragmentHandler {
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);
@@ -48,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 });
}
/**
@@ -60,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
@@ -82,7 +88,13 @@ 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));
}
}
@@ -103,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
@@ -122,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);
@@ -254,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;
}
@@ -370,8 +397,8 @@ public class FragmentHandler {
if (removed && !_msg.getReleased()) {
_failed++;
noteFailure(_msg.getMessageId());
if (_log.shouldLog(Log.ERROR))
_log.error("Dropped failed fragmented message: " + _msg);
if (_log.shouldLog(Log.WARN))
_log.warn("Dropped failed fragmented message: " + _msg);
_context.statManager().addRateData("tunnel.fragmentedDropped", _msg.getFragmentCount(), _msg.getLifetime());
_msg.failed();
} else {

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

@@ -20,19 +20,24 @@ import net.i2p.util.Log;
*
*/
public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
private I2PAppContext _context;
protected I2PAppContext _context;
private Log _log;
static final int PREPROCESSED_SIZE = 1024;
private static final int IV_SIZE = HopProcessor.IV_LENGTH;
private static final ByteCache _dataCache = ByteCache.getInstance(512, PREPROCESSED_SIZE);
private static final ByteCache _ivCache = ByteCache.getInstance(128, IV_SIZE);
protected static final int IV_SIZE = HopProcessor.IV_LENGTH;
protected static final ByteCache _dataCache = ByteCache.getInstance(512, PREPROCESSED_SIZE);
protected static final ByteCache _ivCache = ByteCache.getInstance(128, IV_SIZE);
public TrivialPreprocessor(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(TrivialPreprocessor.class);
}
/**
* Return true if there were messages remaining, and we should queue up
* a delayed flush to clear them
*
*/
public boolean preprocessQueue(List pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
while (pending.size() > 0) {
TunnelGateway.Pending msg = (TunnelGateway.Pending)pending.remove(0);
@@ -75,13 +80,82 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
*
*/
private byte[] preprocessFragment(TunnelGateway.Pending msg) {
byte target[] = _dataCache.acquire().getData();
int offset = 0;
if (msg.getOffset() <= 0)
return preprocessFirstFragment(msg);
offset = writeFirstFragment(msg, target, offset);
else
return preprocessSubsequentFragment(msg);
offset = writeSubsequentFragment(msg, target, offset);
preprocess(target, offset);
return target;
}
/**
* Wrap the preprocessed fragments with the necessary padding / checksums
* to act as a tunnel message.
*
* @param fragmentLength fragments[0:fragmentLength] is used
*/
protected void preprocess(byte fragments[], int fragmentLength) {
ByteArray ivBuf = _ivCache.acquire();
byte iv[] = ivBuf.getData(); // new byte[IV_SIZE];
_context.random().nextBytes(iv);
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(PREPROCESSED_SIZE);
// payload ready, now H(instructions+payload+IV)
System.arraycopy(iv, 0, fragments, fragmentLength, IV_SIZE);
Hash h = _context.sha().calculateHash(fragments, 0, fragmentLength + IV_SIZE, cache);
//Hash h = _context.sha().calculateHash(target, 0, offset + IV_SIZE);
//_log.debug("before shift: " + Base64.encode(target));
// now shiiiiiift
int distance = PREPROCESSED_SIZE - fragmentLength;
System.arraycopy(fragments, 0, fragments, distance, fragmentLength);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug(msg.getMessageId() + ": fragments begin at " + distance + " (size="
// + payloadLength + " offset=" + offset +")");
java.util.Arrays.fill(fragments, 0, distance, (byte)0x0);
//_log.debug("after shift: " + Base64.encode(target));
int offset = 0;
System.arraycopy(iv, 0, fragments, offset, IV_SIZE);
offset += IV_SIZE;
System.arraycopy(h.getData(), 0, fragments, offset, 4);
offset += 4;
//_log.debug("before pad : " + Base64.encode(target));
_context.sha().cache().release(cache);
_ivCache.release(ivBuf);
// fits in a single message, so may be smaller than the full size
int numPadBytes = PREPROCESSED_SIZE // max
- IV_SIZE // hmm..
- 4 // 4 bytes of the SHA256
- 1 // the 0x00 after the padding
- fragmentLength; // the size of the fragments (instructions+payload)
//_log.debug("# pad bytes: " + numPadBytes + " payloadLength: " + payloadLength + " instructions: " + instructionsLength);
int paddingRemaining = numPadBytes;
while (paddingRemaining > 0) {
byte b = (byte)(_context.random().nextInt() & 0xFF);
if (b != 0x00) {
fragments[offset] = b;
offset++;
paddingRemaining--;
}
}
fragments[offset] = 0x0; // no more padding
offset++;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Preprocessing beginning of the fragment instructions at " + offset);
}
/** is this a follw up byte? */
private static final byte MASK_IS_SUBSEQUENT = FragmentHandler.MASK_IS_SUBSEQUENT;
/** how should this be delivered? shift this 5 the right and get TYPE_* */
@@ -92,24 +166,28 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
private static final byte MASK_EXTENDED = FragmentHandler.MASK_EXTENDED;
private static final byte MASK_TUNNEL = (byte)(FragmentHandler.TYPE_TUNNEL << 5);
private static final byte MASK_ROUTER = (byte)(FragmentHandler.TYPE_ROUTER << 5);
private byte[] preprocessFirstFragment(TunnelGateway.Pending msg) {
protected int writeFirstFragment(TunnelGateway.Pending msg, byte target[], int offset) {
boolean fragmented = false;
ByteArray ivBuf = _ivCache.acquire();
byte iv[] = ivBuf.getData(); // new byte[IV_SIZE];
_context.random().nextBytes(iv);
byte target[] = _dataCache.acquire().getData(); //new byte[PREPROCESSED_SIZE];
int origOffset = offset;
int instructionsLength = getInstructionsSize(msg);
int payloadLength = msg.getData().length;
if (payloadLength + instructionsLength + IV_SIZE + 1 + 4 > PREPROCESSED_SIZE) {
int payloadLength = msg.getData().length - msg.getOffset();
if (offset + payloadLength + instructionsLength + IV_SIZE + 1 + 4 > PREPROCESSED_SIZE) {
fragmented = true;
instructionsLength += 4; // messageId
payloadLength = PREPROCESSED_SIZE - IV_SIZE - 1 - 4 - instructionsLength;
payloadLength = PREPROCESSED_SIZE - IV_SIZE - 1 - 4 - instructionsLength - offset;
if (payloadLength <= 0)
throw new RuntimeException("Fragment too small! payloadLen=" + payloadLength
+ " target.length=" + target.length + " offset="+offset
+ " msg.length=" + msg.getData().length + " msg.offset=" + msg.getOffset()
+ " instructionsLength=" + instructionsLength + " for " + msg);
}
int offset = 0;
if (payloadLength <= 0)
throw new RuntimeException("Full size too small! payloadLen=" + payloadLength
+ " target.length=" + target.length + " offset="+offset
+ " msg.length=" + msg.getData().length + " msg.offset=" + msg.getOffset()
+ " instructionsLength=" + instructionsLength + " for " + msg);
// first fragment, or full message
target[offset] = 0x0;
@@ -142,89 +220,21 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
DataHelper.toLong(target, offset, 2, payloadLength);
offset += 2;
//_log.debug("raw data : " + Base64.encode(msg.getData()));
System.arraycopy(msg.getData(), 0, target, offset, payloadLength);
System.arraycopy(msg.getData(), msg.getOffset(), target, offset, payloadLength);
if (_log.shouldLog(Log.DEBUG))
_log.debug("initial fragment[" + msg.getMessageId() + "/" + msg.getFragmentNumber()+ "/"
+ (PREPROCESSED_SIZE - offset - payloadLength) + "/" + payloadLength + "]: "
+ Base64.encode(target, offset, payloadLength));
offset += payloadLength;
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(PREPROCESSED_SIZE);
// payload ready, now H(instructions+payload+IV)
System.arraycopy(iv, 0, target, offset, IV_SIZE);
Hash h = _context.sha().calculateHash(target, 0, offset + IV_SIZE, cache);
//Hash h = _context.sha().calculateHash(target, 0, offset + IV_SIZE);
//_log.debug("before shift: " + Base64.encode(target));
// now shiiiiiift
int distance = PREPROCESSED_SIZE - offset;
System.arraycopy(target, 0, target, distance, offset);
if (_log.shouldLog(Log.DEBUG))
_log.debug(msg.getMessageId() + ": fragments begin at " + distance + " (size="
+ payloadLength + " offset=" + offset +")");
java.util.Arrays.fill(target, 0, distance, (byte)0x0);
//_log.debug("after shift: " + Base64.encode(target));
offset = 0;
System.arraycopy(iv, 0, target, offset, IV_SIZE);
offset += IV_SIZE;
System.arraycopy(h.getData(), 0, target, offset, 4);
offset += 4;
//_log.debug("before pad : " + Base64.encode(target));
_context.sha().cache().release(cache);
_ivCache.release(ivBuf);
if (!fragmented) {
// fits in a single message, so may be smaller than the full size
int numPadBytes = PREPROCESSED_SIZE // max
- IV_SIZE // hmm..
- 4 // 4 bytes of the SHA256
- 1 // the 0x00 after the padding
- payloadLength // the, er, payload
- instructionsLength; // wanna guess?
//_log.debug("# pad bytes: " + numPadBytes + " payloadLength: " + payloadLength + " instructions: " + instructionsLength);
int paddingRemaining = numPadBytes;
while (paddingRemaining > 0) {
byte b = (byte)(_context.random().nextInt() & 0xFF);
if (b != 0x00) {
target[offset] = b;
offset++;
paddingRemaining--;
}
/*
long rnd = _context.random().nextLong();
for (long i = 0; i < 8; i++) {
byte b = (byte)(((rnd >>> i * 8l) & 0xFF));
if (b == 0x00)
continue;
target[offset] = b;
offset++;
paddingRemaining--;
}
*/
}
}
target[offset] = 0x0; // no padding here
offset++;
msg.setOffset(payloadLength);
msg.setOffset(msg.getOffset() + payloadLength);
msg.incrementFragmentNumber();
return target;
return offset;
}
private byte[] preprocessSubsequentFragment(TunnelGateway.Pending msg) {
ByteArray ivBuf = _ivCache.acquire();
protected int writeSubsequentFragment(TunnelGateway.Pending msg, byte target[], int offset) {
boolean isLast = true;
byte iv[] = ivBuf.getData(); // new byte[IV_SIZE];
_context.random().nextBytes(iv);
byte target[] = _dataCache.acquire().getData(); // new byte[PREPROCESSED_SIZE];
int instructionsLength = getInstructionsSize(msg);
int payloadLength = msg.getData().length - msg.getOffset();
@@ -233,8 +243,6 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
payloadLength = PREPROCESSED_SIZE - IV_SIZE - 1 - 4 - instructionsLength;
}
int offset = 0;
// first fragment, or full message
target[offset] = 0x0;
target[offset] |= MASK_IS_SUBSEQUENT;
@@ -259,63 +267,13 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
+ Base64.encode(target, offset, payloadLength));
offset += payloadLength;
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(PREPROCESSED_SIZE);
// payload ready, now H(instructions+payload+IV)
System.arraycopy(iv, 0, target, offset, IV_SIZE);
Hash h = _context.sha().calculateHash(target, 0, offset + IV_SIZE, cache);
//Hash h = _context.sha().calculateHash(target, 0, offset + IV_SIZE);
// now shiiiiiift
int distance = PREPROCESSED_SIZE - offset;
System.arraycopy(target, 0, target, distance, offset);
if (_log.shouldLog(Log.DEBUG))
_log.debug(msg.getMessageId() + ": fragments begin at " + distance + " (size="
+ payloadLength + " offset=" + offset +")");
offset = 0;
System.arraycopy(iv, 0, target, 0, IV_SIZE);
offset += IV_SIZE;
_ivCache.release(ivBuf);
System.arraycopy(h.getData(), 0, target, offset, 4);
offset += 4;
_context.sha().cache().release(cache);
if (isLast) {
// this is the last message, so may be smaller than the full size
int numPadBytes = PREPROCESSED_SIZE // max
- IV_SIZE // hmm..
- 4 // 4 bytes of the SHA256
- 1 // the 0x00 after the padding
- payloadLength // the, er, payload
- instructionsLength; // wanna guess?
for (int i = 0; i < numPadBytes; i++) {
// wouldn't it be nice if random could write to an array?
byte rnd = (byte)_context.random().nextInt();
if (rnd != 0x0) {
target[offset] = rnd;
offset++;
} else {
i--;
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("# pad bytes: " + numPadBytes);
}
target[offset] = 0x0; // end of padding
offset++;
msg.setOffset(msg.getOffset() + payloadLength);
msg.incrementFragmentNumber();
return target;
msg.setOffset(msg.getOffset() + payloadLength);
return offset;
}
private int getInstructionsSize(TunnelGateway.Pending msg) {
protected int getInstructionsSize(TunnelGateway.Pending msg) {
if (msg.getFragmentNumber() > 0)
return 7;
int header = 1;
@@ -324,7 +282,16 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
if (msg.getToRouter() != null)
header += 32;
header += 2;
return header;
}
protected int getInstructionAugmentationSize(TunnelGateway.Pending msg, int offset, int instructionsSize) {
int payloadLength = msg.getData().length - msg.getOffset();
if (offset + payloadLength + instructionsSize + IV_SIZE + 1 + 4 > PREPROCESSED_SIZE) {
// requires fragmentation, so include the messageId
return 4;
}
return 0;
}
}

View File

@@ -2,6 +2,7 @@ package net.i2p.router.tunnel;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import java.text.SimpleDateFormat;
import net.i2p.data.Base64;
@@ -44,6 +45,8 @@ public class TunnelCreatorConfig implements TunnelInfo {
/** how many hops are there in the tunnel? */
public int getLength() { return _config.length; }
public Properties getOptions() { return null; }
/**
* retrieve the config for the given hop. the gateway is
* hop 0.

View File

@@ -106,6 +106,16 @@ public class TunnelDispatcher implements Service {
"How many messages are sent through a participating tunnel?", "Tunnels",
new long[] { 60*10*1000l, 60*60*1000l, 24*60*60*1000l });
}
private TunnelGateway.QueuePreprocessor createPreprocessor() {
return createPreprocessor(null);
}
private TunnelGateway.QueuePreprocessor createPreprocessor(TunnelCreatorConfig cfg) {
if (true)
return new BatchedRouterPreprocessor(_context, cfg);
else
return new TrivialRouterPreprocessor(_context);
}
/**
* We are the outbound gateway - we created this tunnel
@@ -114,7 +124,7 @@ public class TunnelDispatcher implements Service {
if (_log.shouldLog(Log.INFO))
_log.info("Outbound built successfully: " + cfg);
if (cfg.getLength() > 1) {
TunnelGateway.QueuePreprocessor preproc = new TrivialRouterPreprocessor(_context);
TunnelGateway.QueuePreprocessor preproc = createPreprocessor(cfg);
TunnelGateway.Sender sender = new OutboundSender(_context, cfg);
TunnelGateway.Receiver receiver = new OutboundReceiver(_context, cfg);
TunnelGateway gw = new TunnelGateway(_context, preproc, sender, receiver);
@@ -211,7 +221,7 @@ public class TunnelDispatcher implements Service {
public void joinInboundGateway(HopConfig cfg) {
if (_log.shouldLog(Log.INFO))
_log.info("Joining as inbound gateway: " + cfg);
TunnelGateway.QueuePreprocessor preproc = new TrivialRouterPreprocessor(_context);
TunnelGateway.QueuePreprocessor preproc = createPreprocessor();
TunnelGateway.Sender sender = new InboundSender(_context, cfg);
TunnelGateway.Receiver receiver = new InboundGatewayReceiver(_context, cfg);
TunnelGateway gw = new TunnelGateway(_context, preproc, sender, receiver);

View File

@@ -8,6 +8,7 @@ import net.i2p.data.Hash;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.TunnelGatewayMessage;
import net.i2p.router.Router;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
@@ -87,7 +88,7 @@ public class TunnelGateway {
_messagesSent++;
boolean delayedFlush = false;
Pending cur = new Pending(msg, toRouter, toTunnel);
Pending cur = new PendingImpl(msg, toRouter, toTunnel);
synchronized (_queue) {
_queue.add(cur);
delayedFlush = _preprocessor.preprocessQueue(_queue, _sender, _receiver);
@@ -96,7 +97,9 @@ public class TunnelGateway {
// expire any as necessary, even if its framented
for (int i = 0; i < _queue.size(); i++) {
Pending m = (Pending)_queue.get(i);
if (m.getExpiration() < _lastFlush) {
if (m.getExpiration() + Router.CLOCK_FUDGE_FACTOR < _lastFlush) {
if (_log.shouldLog(Log.ERROR))
_log.error("Expire on the queue (size=" + _queue.size() + "): " + m);
_queue.remove(i);
i--;
}
@@ -138,13 +141,13 @@ public class TunnelGateway {
}
public static class Pending {
private Hash _toRouter;
private TunnelId _toTunnel;
protected Hash _toRouter;
protected TunnelId _toTunnel;
private long _messageId;
private long _expiration;
private byte _remaining[];
private int _offset;
private int _fragmentNumber;
protected long _expiration;
protected byte _remaining[];
protected int _offset;
protected int _fragmentNumber;
public Pending(I2NPMessage message, Hash toRouter, TunnelId toTunnel) {
_toRouter = toRouter;
@@ -172,16 +175,48 @@ public class TunnelGateway {
/** ok, fragment sent, increment what the next will be */
public void incrementFragmentNumber() { _fragmentNumber++; }
}
private class PendingImpl extends Pending {
private long _created;
public PendingImpl(I2NPMessage message, Hash toRouter, TunnelId toTunnel) {
super(message, toRouter, toTunnel);
_created = _context.clock().now();
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("Message on ");
buf.append(TunnelGateway.this.toString());
if (_toRouter != null) {
buf.append(" targetting ");
buf.append(_toRouter.toBase64()).append(" ");
if (_toTunnel != null)
buf.append(_toTunnel.getTunnelId());
}
long now = _context.clock().now();
buf.append(" actual lifetime ");
buf.append(now - _created).append("ms");
buf.append(" potential lifetime ");
buf.append(_expiration - _created).append("ms");
buf.append(" size ").append(_remaining.length);
buf.append(" offset ").append(_offset);
buf.append(" frag ").append(_fragmentNumber);
return buf.toString();
}
}
private class DelayedFlush implements SimpleTimer.TimedEvent {
public void timeReached() {
long now = _context.clock().now();
boolean wantRequeue = false;
synchronized (_queue) {
if ( (_queue.size() > 0) && (_lastFlush + _flushFrequency < now) ) {
_preprocessor.preprocessQueue(_queue, _sender, _receiver);
_lastFlush = _context.clock().now();
}
if (_queue.size() > 0)
wantRequeue = _preprocessor.preprocessQueue(_queue, _sender, _receiver);
}
if (wantRequeue)
SimpleTimer.getInstance().addEvent(_delayedFlush, _flushFrequency);
else
_lastFlush = _context.clock().now();
}
}
}

View File

@@ -1,5 +1,6 @@
package net.i2p.router.tunnel.pool;
import java.util.Properties;
import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
import net.i2p.router.tunnel.TunnelCreatorConfig;
@@ -34,6 +35,11 @@ public class PooledTunnelCreatorConfig extends TunnelCreatorConfig {
}
}
public Properties getOptions() {
if (_pool == null) return null;
return _pool.getSettings().getUnknownOptions();
}
/**
* The tunnel failed, so stop using it
*/

View File

@@ -45,7 +45,7 @@ public class RequestTunnelJob extends JobImpl {
private boolean _isFake;
private boolean _isExploratory;
static final int HOP_REQUEST_TIMEOUT = 30*1000;
static final int HOP_REQUEST_TIMEOUT = 20*1000;
private static final int LOOKUP_TIMEOUT = 10*1000;
public RequestTunnelJob(RouterContext ctx, TunnelCreatorConfig cfg, Job onCreated, Job onFailed, int hop, boolean isFake, boolean isExploratory) {

View File

@@ -33,13 +33,15 @@ public class TunnelPool {
private int _buildsThisMinute;
private long _currentMinute;
private RefreshJob _refreshJob;
private TunnelInfo _lastSelected;
private long _lastSelectionPeriod;
/**
* Only 3 builds per minute per pool, even if we have failing tunnels,
* Only 5 builds per minute per pool, even if we have failing tunnels,
* etc. On overflow, the necessary additional tunnels are built by the
* RefreshJob
*/
private static final int MAX_BUILDS_PER_MINUTE = 3;
private static final int MAX_BUILDS_PER_MINUTE = 5;
public TunnelPool(RouterContext ctx, TunnelPoolManager mgr, TunnelPoolSettings settings, TunnelPeerSelector sel, TunnelBuilder builder) {
_context = ctx;
@@ -50,6 +52,8 @@ public class TunnelPool {
_peerSelector = sel;
_builder = builder;
_alive = false;
_lastSelectionPeriod = 0;
_lastSelected = null;
_lifetimeProcessed = 0;
_buildsThisMinute = 0;
_currentMinute = ctx.clock().now();
@@ -77,6 +81,8 @@ public class TunnelPool {
}
public void shutdown() {
_alive = false;
_lastSelectionPeriod = 0;
_lastSelected = null;
}
private int countUsableTunnels() {
@@ -153,6 +159,12 @@ public class TunnelPool {
}
}
/**
* when selecting tunnels, stick with the same one for a brief
* period to allow batching if we can.
*/
private static final long SELECTION_PERIOD = 500;
/**
* Pull a random tunnel out of the pool. If there are none available but
* the pool is configured to allow 0hop tunnels, this builds a fake one
@@ -161,7 +173,18 @@ public class TunnelPool {
*/
public TunnelInfo selectTunnel() { return selectTunnel(true); }
private TunnelInfo selectTunnel(boolean allowRecurseOnFail) {
long period = _context.clock().now();
period -= period % SELECTION_PERIOD;
synchronized (_tunnels) {
if (_lastSelectionPeriod == period) {
if ( (_lastSelected != null) &&
(_lastSelected.getExpiration() > period) &&
(_tunnels.contains(_lastSelected)) )
return _lastSelected;
}
_lastSelectionPeriod = period;
_lastSelected = null;
if (_tunnels.size() <= 0) {
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + ": No tunnels to select from");
@@ -172,6 +195,7 @@ public class TunnelPool {
TunnelInfo info = (TunnelInfo)_tunnels.get(i);
if (info.getExpiration() > _context.clock().now()) {
//_log.debug("Selecting tunnel: " + info + " - " + _tunnels);
_lastSelected = info;
return info;
}
}
@@ -261,6 +285,10 @@ public class TunnelPool {
if (_settings.isInbound() && (_settings.getDestination() != null) )
ls = locked_buildNewLeaseSet();
remaining = _tunnels.size();
if (_lastSelected == info) {
_lastSelected = null;
_lastSelectionPeriod = 0;
}
}
_lifetimeProcessed += info.getProcessedMessagesCount();
@@ -297,6 +325,10 @@ public class TunnelPool {
if (_settings.isInbound() && (_settings.getDestination() != null) )
ls = locked_buildNewLeaseSet();
remaining = _tunnels.size();
if (_lastSelected == cfg) {
_lastSelected = null;
_lastSelectionPeriod = 0;
}
}
_lifetimeProcessed += cfg.getProcessedMessagesCount();
@@ -443,7 +475,7 @@ public class TunnelPool {
int added = refreshBuilders();
if ( (added > 0) && (_log.shouldLog(Log.WARN)) )
_log.warn("Passive rebuilding a tunnel for " + TunnelPool.this.toString());
requeue(60*1000);
requeue(30*1000);
}
}
}

View File

@@ -267,8 +267,13 @@ public class TunnelPoolManager implements TunnelManagerFacade {
*/
int allocateBuilds(int wanted) {
synchronized (this) {
if (_outstandingBuilds >= _maxOutstandingBuilds)
if (_outstandingBuilds >= _maxOutstandingBuilds) {
// ok, as a failsafe, always let one through
// nah, its failsafe for a reason. fix the cause.
//_outstandingBuilds++;
//return 1;
return 0;
}
if (_outstandingBuilds + wanted < _maxOutstandingBuilds) {
_outstandingBuilds += wanted;
return wanted;