forked from I2P_Developers/i2p.i2p
Compare commits
45 Commits
i2p_0_5_0_
...
i2p_0_5_0_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80c6290b89 | ||
|
|
6492ad165a | ||
|
|
f0d1b1a40e | ||
|
|
17f044e6cd | ||
|
|
63f3a9cd7b | ||
|
|
b8ddbf13b4 | ||
|
|
be9bdbfe0f | ||
|
|
bc74bf1402 | ||
|
|
5c2a57f95a | ||
|
|
9cd8cc692e | ||
|
|
ebac4df2d3 | ||
|
|
0626f714c6 | ||
|
|
21842291e9 | ||
|
|
d461c295f6 | ||
|
|
85b3450525 | ||
|
|
75d7c81b7c | ||
|
|
1433e20f73 | ||
|
|
e614a2f726 | ||
|
|
32be7f1fd8 | ||
|
|
66e1d95a2a | ||
|
|
ff03be217e | ||
|
|
a52f8b89dc | ||
|
|
21c7c043b3 | ||
|
|
45e6608ad3 | ||
|
|
28978e3680 | ||
|
|
904f755c8c | ||
|
|
a2c309ddd3 | ||
|
|
677eeac8f7 | ||
|
|
b232cc0f24 | ||
|
|
18bbae1d1e | ||
|
|
08ee62b52c | ||
|
|
5b83aed719 | ||
|
|
b5875ca07b | ||
|
|
3f9bf28382 | ||
|
|
a2bd71c75b | ||
|
|
89509490c5 | ||
|
|
a997a46040 | ||
|
|
538dd07e7b | ||
|
|
046778404e | ||
|
|
766f83d653 | ||
|
|
b20aee6753 | ||
|
|
f9aa3aef18 | ||
|
|
d74aa6e53d | ||
|
|
ea6fbc7835 | ||
|
|
536e604b8e |
@@ -12,7 +12,8 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.ByteCache;
|
||||
@@ -68,19 +69,6 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* filter any headers (adding or removing as necessary), and tweak
|
||||
* the first response line as necessary.
|
||||
*
|
||||
* @return response line ("200 OK", etc)
|
||||
*/
|
||||
protected String filterHeaders(String responseLine, Properties props) {
|
||||
props.setProperty("Connection", "close");
|
||||
props.setProperty("Proxy-Connection", "close");
|
||||
return responseLine;
|
||||
}
|
||||
|
||||
|
||||
/** grow (and free) the buffer as necessary */
|
||||
private void ensureCapacity() {
|
||||
if (_headerBuffer.getValid() + 1 >= _headerBuffer.getData().length) {
|
||||
@@ -105,26 +93,52 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
(isNL(first) && isNL(third)); // \n\r\n
|
||||
}
|
||||
|
||||
/**
|
||||
* Tweak that first HTTP response line (HTTP 200 OK, etc)
|
||||
*
|
||||
*/
|
||||
protected String filterResponseLine(String line) {
|
||||
return line;
|
||||
}
|
||||
|
||||
/** we ignore any potential \r, since we trim it on write anyway */
|
||||
private static final byte NL = '\n';
|
||||
private boolean isNL(byte b) { return (b == NL); }
|
||||
|
||||
/** ok, received, now munge & write it */
|
||||
private void writeHeader() throws IOException {
|
||||
Properties props = new Properties();
|
||||
String responseLine = null;
|
||||
|
||||
boolean connectionSent = false;
|
||||
boolean proxyConnectionSent = false;
|
||||
|
||||
int lastEnd = -1;
|
||||
for (int i = 0; i < _headerBuffer.getValid(); i++) {
|
||||
if (isNL(_headerBuffer.getData()[i])) {
|
||||
if (lastEnd == -1) {
|
||||
responseLine = new String(_headerBuffer.getData(), 0, i+1); // includes NL
|
||||
responseLine = filterResponseLine(responseLine);
|
||||
responseLine = (responseLine.trim() + "\n");
|
||||
out.write(responseLine.getBytes());
|
||||
} else {
|
||||
for (int j = lastEnd+1; j < i; j++) {
|
||||
if (_headerBuffer.getData()[j] == ':') {
|
||||
String key = new String(_headerBuffer.getData(), lastEnd+1, j-(lastEnd+1));
|
||||
String val = new String(_headerBuffer.getData(), j+2, i-(j+2));
|
||||
props.setProperty(key, val);
|
||||
int keyLen = j-(lastEnd+1);
|
||||
int valLen = i-(j+2);
|
||||
if ( (keyLen <= 0) || (valLen <= 0) )
|
||||
throw new IOException("Invalid header @ " + j);
|
||||
String key = new String(_headerBuffer.getData(), lastEnd+1, keyLen);
|
||||
String val = new String(_headerBuffer.getData(), j+2, valLen);
|
||||
|
||||
if ("Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Connection: close\n".getBytes());
|
||||
connectionSent = true;
|
||||
} else if ("Proxy-Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
proxyConnectionSent = true;
|
||||
} else {
|
||||
out.write((key.trim() + ": " + val.trim() + "\n").getBytes());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -133,34 +147,11 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
}
|
||||
}
|
||||
|
||||
if (responseLine == null)
|
||||
throw new IOException("No HTTP response line, with props=" + props);
|
||||
|
||||
responseLine = filterHeaders(responseLine, props);
|
||||
responseLine = (responseLine.trim() + "\n");
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
StringBuffer msg = new StringBuffer(responseLine.length() + props.size() * 64);
|
||||
msg.append("HTTP response: first line [").append(responseLine.trim());
|
||||
msg.append("] options: \n");
|
||||
if (!connectionSent)
|
||||
out.write("Connection: close\n".getBytes());
|
||||
if (!proxyConnectionSent)
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
|
||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = props.getProperty(key);
|
||||
msg.append('[').append(key.trim()).append("]=[").append(val.trim()).append("]\n");
|
||||
}
|
||||
|
||||
_log.debug(msg.toString());
|
||||
}
|
||||
|
||||
out.write(responseLine.getBytes());
|
||||
|
||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = props.getProperty(key);
|
||||
String line = key.trim() + ": " + val.trim() + "\n";
|
||||
out.write(line.getBytes());
|
||||
}
|
||||
out.write("\n".getBytes()); // end of the headers
|
||||
|
||||
// done, shove off
|
||||
@@ -198,6 +189,17 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
String invalid2 = "HTTP/1.1 200 OK";
|
||||
String invalid3 = "HTTP 200 OK\r\n";
|
||||
String invalid4 = "HTTP 200 OK\r";
|
||||
String invalid5 = "HTTP/1.1 200 OK\r\n" +
|
||||
"I am broken, and I smell\r\n" +
|
||||
"\r\n";
|
||||
String invalid6 = "HTTP/1.1 200 OK\r\n" +
|
||||
":I am broken, and I smell\r\n" +
|
||||
"\r\n";
|
||||
String invalid7 = "HTTP/1.1 200 OK\n" +
|
||||
"I am broken, and I smell:\n" +
|
||||
":asdf\n" +
|
||||
":\n" +
|
||||
"\n";
|
||||
String large = "HTTP/1.1 200 OK\n" +
|
||||
"Last-modified: Tue, 25 Nov 2003 12:05:38 GMT\n" +
|
||||
"Expires: Tue, 25 Nov 2003 12:05:38 GMT\n" +
|
||||
@@ -205,20 +207,23 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
"\n" +
|
||||
"hi ho, this is the body";
|
||||
/* */
|
||||
test("Simple", simple);
|
||||
test("Filtered", filtered);
|
||||
test("Filtered windows", winfilter);
|
||||
test("Minimal", minimal);
|
||||
test("Windows", winmin);
|
||||
test("Large", large);
|
||||
test("Invalid (short headers)", invalid1);
|
||||
test("Invalid (no headers)", invalid2);
|
||||
test("Invalid (windows with short headers)", invalid3);
|
||||
test("Invalid (windows no headers)", invalid4);
|
||||
test("Simple", simple, true);
|
||||
test("Filtered", filtered, true);
|
||||
test("Filtered windows", winfilter, true);
|
||||
test("Minimal", minimal, true);
|
||||
test("Windows", winmin, true);
|
||||
test("Large", large, true);
|
||||
test("Invalid (short headers)", invalid1, true);
|
||||
test("Invalid (no headers)", invalid2, true);
|
||||
test("Invalid (windows with short headers)", invalid3, true);
|
||||
test("Invalid (windows no headers)", invalid4, true);
|
||||
test("Invalid (bad headers)", invalid5, true);
|
||||
test("Invalid (bad headers2)", invalid6, false);
|
||||
test("Invalid (bad headers3)", invalid7, false);
|
||||
/* */
|
||||
}
|
||||
|
||||
private static void test(String name, String orig) {
|
||||
private static void test(String name, String orig, boolean shouldPass) {
|
||||
System.out.println("====Testing: " + name + "\n" + orig + "\n------------");
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
|
||||
@@ -228,7 +233,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
String received = new String(baos.toByteArray());
|
||||
System.out.println(received);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
if (shouldPass)
|
||||
e.printStackTrace();
|
||||
else
|
||||
System.out.println("Properly fails with " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,9 +194,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
|
||||
|
||||
if (line.startsWith("Connection: ") ||
|
||||
line.startsWith("Keep-Alive: ") ||
|
||||
line.startsWith("Proxy-Connection: "))
|
||||
String lowercaseLine = line.toLowerCase();
|
||||
if (lowercaseLine.startsWith("connection: ") ||
|
||||
lowercaseLine.startsWith("keep-alive: ") ||
|
||||
lowercaseLine.startsWith("proxy-connection: "))
|
||||
continue;
|
||||
|
||||
if (method == null) { // first line (GET /base64/realaddr)
|
||||
@@ -335,29 +336,29 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
} else {
|
||||
if (line.startsWith("Host: ") && !usingWWWProxy) {
|
||||
if (lowercaseLine.startsWith("host: ") && !usingWWWProxy) {
|
||||
line = "Host: " + host;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Setting host = " + host);
|
||||
} else if (line.startsWith("User-Agent: ")) {
|
||||
} else if (lowercaseLine.startsWith("user-agent: ")) {
|
||||
// always stripped, added back at the end
|
||||
line = null;
|
||||
continue;
|
||||
} else if (line.startsWith("Accept")) {
|
||||
} else if (lowercaseLine.startsWith("accept")) {
|
||||
// strip the accept-blah headers, as they vary dramatically from
|
||||
// browser to browser
|
||||
line = null;
|
||||
continue;
|
||||
} else if (line.startsWith("Referer: ")) {
|
||||
} else if (lowercaseLine.startsWith("referer: ")) {
|
||||
// Shouldn't we be more specific, like accepting in-site referers ?
|
||||
//line = "Referer: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (line.startsWith("Via: ")) {
|
||||
} else if (lowercaseLine.startsWith("via: ")) {
|
||||
//line = "Via: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (line.startsWith("From: ")) {
|
||||
} else if (lowercaseLine.startsWith("from: ")) {
|
||||
//line = "From: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
225
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
Normal file
225
apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
Normal file
@@ -0,0 +1,225 @@
|
||||
package net.i2p.i2ptunnel.web;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2005 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Ugly little accessor for the edit page
|
||||
*/
|
||||
public class EditBean extends IndexBean {
|
||||
public EditBean() { super(); }
|
||||
|
||||
public static boolean staticIsClient(int tunnel) {
|
||||
TunnelControllerGroup group = TunnelControllerGroup.getInstance();
|
||||
List controllers = group.getControllers();
|
||||
if (controllers.size() > tunnel) {
|
||||
TunnelController cur = (TunnelController)controllers.get(tunnel);
|
||||
if (cur == null) return false;
|
||||
return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getInternalType(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getType();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getTargetHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getTargetHost();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
public String getTargetPort(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getTargetPort();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
public String getSpoofedHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getSpoofedHost();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
public String getPrivateKeyFile(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getPrivKeyFile();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean startAutomatically(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getStartOnLoad();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean shouldDelay(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String delay = opts.getProperty("i2p.streaming.connectDelay");
|
||||
if ( (delay == null) || ("0".equals(delay)) )
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInteractive(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String wsiz = opts.getProperty("i2p.streaming.maxWindowSize");
|
||||
if ( (wsiz == null) || (!"1".equals(wsiz)) )
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTunnelDepth(int tunnel, int defaultLength) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String len = opts.getProperty("inbound.length");
|
||||
if (len == null) return defaultLength;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultLength;
|
||||
}
|
||||
} else {
|
||||
return defaultLength;
|
||||
}
|
||||
} else {
|
||||
return defaultLength;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTunnelCount(int tunnel, int defaultCount) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts != null) {
|
||||
String len = opts.getProperty("inbound.quantity");
|
||||
if (len == null) return defaultCount;
|
||||
try {
|
||||
return Integer.parseInt(len);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultCount;
|
||||
}
|
||||
} else {
|
||||
return defaultCount;
|
||||
}
|
||||
} else {
|
||||
return defaultCount;
|
||||
}
|
||||
}
|
||||
|
||||
public String getI2CPHost(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getI2CPHost();
|
||||
else
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
public String getI2CPPort(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getI2CPPort();
|
||||
else
|
||||
return "7654";
|
||||
}
|
||||
|
||||
public String getCustomOptions(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null) {
|
||||
Properties opts = getOptions(tun);
|
||||
if (opts == null) return "";
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
int i = 0;
|
||||
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = opts.getProperty(key);
|
||||
if ("inbound.length".equals(key)) continue;
|
||||
if ("outbound.length".equals(key)) continue;
|
||||
if ("inbound.quantity".equals(key)) continue;
|
||||
if ("outbound.quantity".equals(key)) continue;
|
||||
if ("inbound.nickname".equals(key)) continue;
|
||||
if ("outbound.nickname".equals(key)) continue;
|
||||
if ("i2p.streaming.connectDelay".equals(key)) continue;
|
||||
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
|
||||
if (i != 0) buf.append(' ');
|
||||
buf.append(key).append('=').append(val);
|
||||
i++;
|
||||
}
|
||||
return buf.toString();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the client options from the tunnel
|
||||
*
|
||||
* @return map of name=val to be used as I2P session options
|
||||
*/
|
||||
private static Properties getOptions(TunnelController controller) {
|
||||
if (controller == null) return null;
|
||||
String opts = controller.getClientOptions();
|
||||
StringTokenizer tok = new StringTokenizer(opts);
|
||||
Properties props = new Properties();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int eq = pair.indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= pair.length()) )
|
||||
continue;
|
||||
String key = pair.substring(0, eq);
|
||||
String val = pair.substring(eq+1);
|
||||
props.setProperty(key, val);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,38 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
package net.i2p.i2ptunnel.web;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2005 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* UUUUuuuuuugly glue code to handle bean interaction from the web, process
|
||||
* that data, and spit out the results (or the form requested). The basic
|
||||
* usage is to set any of the fields with data then query the bean via
|
||||
* getActionResults() which triggers the request processing (taking all the
|
||||
* provided data, doing what needs to be done) and returns the results of those
|
||||
* activites. Then a subsequent call to getEditForm() generates the HTML form
|
||||
* to either edit the currently selected tunnel (if specified) or add a new one.
|
||||
* This functionality is delegated to the WebEditPageFormGenerator.
|
||||
* Simple accessor for exposing tunnel info, but also an ugly form handler
|
||||
*
|
||||
*/
|
||||
public class WebEditPageHelper {
|
||||
private Log _log;
|
||||
public class IndexBean {
|
||||
protected I2PAppContext _context;
|
||||
protected Log _log;
|
||||
protected TunnelControllerGroup _group;
|
||||
private String _action;
|
||||
private int _tunnel;
|
||||
private long _prevNonce;
|
||||
private long _curNonce;
|
||||
private long _nextNonce;
|
||||
private String _passphrase;
|
||||
|
||||
private String _type;
|
||||
private String _id;
|
||||
private String _name;
|
||||
private String _description;
|
||||
private String _i2cpHost;
|
||||
@@ -44,30 +54,306 @@ public class WebEditPageHelper {
|
||||
private boolean _startOnLoad;
|
||||
private boolean _privKeyGenerate;
|
||||
private boolean _removeConfirmed;
|
||||
private long _nonce;
|
||||
|
||||
public WebEditPageHelper() {
|
||||
public static final int RUNNING = 1;
|
||||
public static final int STARTING = 2;
|
||||
public static final int NOT_RUNNING = 3;
|
||||
|
||||
public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase";
|
||||
static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
|
||||
static final String CLIENT_NICKNAME = "shared clients";
|
||||
|
||||
public IndexBean() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(IndexBean.class);
|
||||
_group = TunnelControllerGroup.getInstance();
|
||||
_action = null;
|
||||
_type = null;
|
||||
_id = null;
|
||||
_removeConfirmed = false;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(WebEditPageHelper.class);
|
||||
_tunnel = -1;
|
||||
_curNonce = -1;
|
||||
_prevNonce = -1;
|
||||
try {
|
||||
String nonce = System.getProperty(PROP_NONCE);
|
||||
if (nonce != null)
|
||||
_prevNonce = Long.parseLong(nonce);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
_nextNonce = _context.random().nextLong();
|
||||
System.setProperty(PROP_NONCE, Long.toString(_nextNonce));
|
||||
}
|
||||
|
||||
public long getNextNonce() { return _nextNonce; }
|
||||
public void setNonce(String nonce) {
|
||||
if (nonce != null) {
|
||||
try {
|
||||
_nonce = Long.parseLong(nonce);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
if ( (nonce == null) || (nonce.trim().length() <= 0) ) return;
|
||||
try {
|
||||
_curNonce = Long.parseLong(nonce);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_curNonce = -1;
|
||||
}
|
||||
}
|
||||
public void setPassphrase(String phrase) {
|
||||
_passphrase = phrase;
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
if ( (action == null) || (action.trim().length() <= 0) ) return;
|
||||
_action = action;
|
||||
}
|
||||
public void setTunnel(String tunnel) {
|
||||
if ( (tunnel == null) || (tunnel.trim().length() <= 0) ) return;
|
||||
try {
|
||||
_tunnel = Integer.parseInt(tunnel);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_tunnel = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for form submit - either "Save" or Remove"
|
||||
*/
|
||||
public void setAction(String action) {
|
||||
_action = (action != null ? action.trim() : null);
|
||||
private boolean validPassphrase(String proposed) {
|
||||
if (proposed == null) return false;
|
||||
String pass = _context.getProperty(PROP_TUNNEL_PASSPHRASE);
|
||||
if ( (pass != null) && (pass.trim().length() > 0) )
|
||||
return pass.trim().equals(proposed.trim());
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private String processAction() {
|
||||
if ( (_action == null) || (_action.trim().length() <= 0) )
|
||||
return "";
|
||||
if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) )
|
||||
return "Invalid nonce, are you being spoofed?";
|
||||
if ("Stop all tunnels".equals(_action))
|
||||
return stopAll();
|
||||
else if ("Start all tunnels".equals(_action))
|
||||
return startAll();
|
||||
else if ("Restart all".equals(_action))
|
||||
return restartAll();
|
||||
else if ("Reload config".equals(_action))
|
||||
return reloadConfig();
|
||||
else if ("stop".equals(_action))
|
||||
return stop();
|
||||
else if ("start".equals(_action))
|
||||
return start();
|
||||
else if ("Save changes".equals(_action))
|
||||
return saveChanges();
|
||||
else if ("Delete this proxy".equals(_action))
|
||||
return deleteTunnel();
|
||||
else
|
||||
return "Action " + _action + " unknown";
|
||||
}
|
||||
private String stopAll() {
|
||||
if (_group == null) return "";
|
||||
List msgs = _group.stopAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String startAll() {
|
||||
if (_group == null) return "";
|
||||
List msgs = _group.startAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String restartAll() {
|
||||
if (_group == null) return "";
|
||||
List msgs = _group.restartAllControllers();
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private String reloadConfig() {
|
||||
if (_group == null) return "";
|
||||
|
||||
_group.reloadControllers();
|
||||
return "Config reloaded";
|
||||
}
|
||||
private String start() {
|
||||
if (_tunnel < 0) return "Invalid tunnel";
|
||||
|
||||
List controllers = _group.getControllers();
|
||||
if (_tunnel >= controllers.size()) return "Invalid tunnel";
|
||||
TunnelController controller = (TunnelController)controllers.get(_tunnel);
|
||||
controller.startTunnelBackground();
|
||||
return "";
|
||||
}
|
||||
|
||||
private String stop() {
|
||||
if (_tunnel < 0) return "Invalid tunnel";
|
||||
|
||||
List controllers = _group.getControllers();
|
||||
if (_tunnel >= controllers.size()) return "Invalid tunnel";
|
||||
TunnelController controller = (TunnelController)controllers.get(_tunnel);
|
||||
controller.stopTunnel();
|
||||
return "";
|
||||
}
|
||||
|
||||
private String saveChanges() {
|
||||
TunnelController cur = getController(_tunnel);
|
||||
|
||||
Properties config = getConfig();
|
||||
if (config == null)
|
||||
return "Invalid params";
|
||||
|
||||
if (cur == null) {
|
||||
// creating new
|
||||
cur = new TunnelController(config, "", true);
|
||||
_group.addController(cur);
|
||||
if (cur.getStartOnLoad())
|
||||
cur.startTunnelBackground();
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same
|
||||
// I2CP options
|
||||
List controllers = _group.getControllers();
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelController c = (TunnelController)controllers.get(i);
|
||||
if (c == cur) continue;
|
||||
if ("httpclient".equals(c.getType()) || "client".equals(c.getType())) {
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelCount != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
cOpt.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
}
|
||||
if (_tunnelDepth != null) {
|
||||
cOpt.setProperty("option.inbound.length", _tunnelDepth);
|
||||
cOpt.setProperty("option.outbound.length", _tunnelDepth);
|
||||
}
|
||||
cOpt.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
cOpt.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
|
||||
c.setConfig(cOpt, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List msgs = doSave();
|
||||
msgs.add(0, "Changes saved");
|
||||
return getMessages(msgs);
|
||||
}
|
||||
private List doSave() {
|
||||
_group.saveConfig();
|
||||
return _group.clearAllMessages();
|
||||
}
|
||||
private String deleteTunnel() {
|
||||
if (!_removeConfirmed)
|
||||
return "Please confirm removal";
|
||||
|
||||
TunnelController cur = getController(_tunnel);
|
||||
if (cur == null)
|
||||
return "Invalid tunnel number";
|
||||
|
||||
List msgs = _group.removeController(cur);
|
||||
msgs.addAll(doSave());
|
||||
return getMessages(msgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes any action requested (start/stop/etc) and dump out the
|
||||
* messages.
|
||||
*
|
||||
*/
|
||||
public String getMessages() {
|
||||
if (_group == null)
|
||||
return "";
|
||||
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
if (_action != null) {
|
||||
try {
|
||||
buf.append(processAction()).append("\n");
|
||||
} catch (Exception e) {
|
||||
_log.log(Log.CRIT, "Error processing " + _action, e);
|
||||
}
|
||||
}
|
||||
getMessages(_group.clearAllMessages(), buf);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
////
|
||||
// The remaining methods are simple bean props for the jsp to query
|
||||
////
|
||||
|
||||
public int getTunnelCount() {
|
||||
if (_group == null) return 0;
|
||||
return _group.getControllers().size();
|
||||
}
|
||||
|
||||
public boolean isClient(int tunnelNum) {
|
||||
TunnelController cur = getController(tunnelNum);
|
||||
if (cur == null) return false;
|
||||
return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
|
||||
}
|
||||
|
||||
public String getTunnelName(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getName();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getClientPort(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getListenPort();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getTunnelType(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return getTypeName(tun.getType());
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getTypeName(String internalType) {
|
||||
if ("client".equals(internalType)) return "Client proxy";
|
||||
else if ("httpclient".equals(internalType)) return "HTTP proxy";
|
||||
else if ("server".equals(internalType)) return "Server";
|
||||
else if ("httpserver".equals(internalType)) return "HTTP server";
|
||||
else return internalType;
|
||||
}
|
||||
|
||||
public String getClientInterface(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getListenOnInterface();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public int getTunnelStatus(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun == null) return NOT_RUNNING;
|
||||
if (tun.getIsRunning()) return RUNNING;
|
||||
else if (tun.getIsStarting()) return STARTING;
|
||||
else return NOT_RUNNING;
|
||||
}
|
||||
|
||||
public String getTunnelDescription(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getDescription();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getClientDestination(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun == null) return "";
|
||||
if ("client".equals(tun.getType())) return tun.getTargetDestination();
|
||||
else return tun.getProxyList();
|
||||
}
|
||||
|
||||
public String getServerTarget(int tunnel) {
|
||||
TunnelController tun = getController(tunnel);
|
||||
if (tun != null)
|
||||
return tun.getTargetHost() + ':' + tun.getTargetPort();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
///
|
||||
/// bean props for form submission
|
||||
///
|
||||
|
||||
/**
|
||||
* What type of tunnel (httpclient, client, or server). This is
|
||||
* required when adding a new tunnel.
|
||||
@@ -76,17 +362,7 @@ public class WebEditPageHelper {
|
||||
public void setType(String type) {
|
||||
_type = (type != null ? type.trim() : null);
|
||||
}
|
||||
/**
|
||||
* Which particular tunnel should be edited (index into the current
|
||||
* TunnelControllerGroup's getControllers() list). This is required
|
||||
* when editing a tunnel, but not when adding a new one.
|
||||
*
|
||||
*/
|
||||
public void setNum(String id) {
|
||||
_id = (id != null ? id.trim() : null);
|
||||
}
|
||||
String getType() { return _type; }
|
||||
String getNum() { return _id; }
|
||||
|
||||
/** Short name of the tunnel */
|
||||
public void setName(String name) {
|
||||
@@ -158,14 +434,6 @@ public class WebEditPageHelper {
|
||||
public void setPrivKeyFile(String file) {
|
||||
_privKeyFile = (file != null ? file.trim() : null);
|
||||
}
|
||||
/**
|
||||
* If called with any value, we want to generate a new destination
|
||||
* for this server tunnel. This won't cause any existing private keys
|
||||
* to be overwritten, however.
|
||||
*/
|
||||
public void setPrivKeyGenerate(String moo) {
|
||||
_privKeyGenerate = true;
|
||||
}
|
||||
/**
|
||||
* If called with any value (and the form submitted with action=Remove),
|
||||
* we really do want to stop and remove the tunnel.
|
||||
@@ -186,145 +454,7 @@ public class WebEditPageHelper {
|
||||
public void setProfile(String profile) {
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the form and display any resulting messages
|
||||
*
|
||||
*/
|
||||
public String getActionResults() {
|
||||
try {
|
||||
return processAction();
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Internal error processing request", t);
|
||||
return "Internal error - " + t.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an HTML form to edit / create a tunnel according to the
|
||||
* specified fields
|
||||
*/
|
||||
public String getEditForm() {
|
||||
try {
|
||||
return WebEditPageFormGenerator.getForm(this);
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Internal error retrieving edit form", t);
|
||||
return "Internal error - " + t.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the tunnel pointed to by the current id
|
||||
*
|
||||
*/
|
||||
TunnelController getTunnelController() {
|
||||
if (_id == null) return null;
|
||||
int id = -1;
|
||||
try {
|
||||
id = Integer.parseInt(_id);
|
||||
List controllers = TunnelControllerGroup.getInstance().getControllers();
|
||||
if ( (id < 0) || (id >= controllers.size()) )
|
||||
return null;
|
||||
else
|
||||
return (TunnelController)controllers.get(id);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid tunnel id [" + _id + "]", nfe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String processAction() {
|
||||
if ( (_action == null) || (_action.trim().length() <= 0) )
|
||||
return "";
|
||||
String expected = System.getProperty(getClass().getName() + ".nonce");
|
||||
if ( (expected == null) || (!expected.equals(Long.toString(_nonce))) )
|
||||
return "<b>Invalid nonce, are you being spoofed?</b>";
|
||||
if ("Save".equals(_action))
|
||||
return save();
|
||||
else if ("Remove".equals(_action))
|
||||
return remove();
|
||||
else
|
||||
return "Action <i>" + _action + "</i> unknown";
|
||||
}
|
||||
|
||||
private String remove() {
|
||||
if (!_removeConfirmed)
|
||||
return "Please confirm removal";
|
||||
|
||||
TunnelController cur = getTunnelController();
|
||||
if (cur == null)
|
||||
return "Invalid tunnel number";
|
||||
|
||||
List msgs = TunnelControllerGroup.getInstance().removeController(cur);
|
||||
msgs.addAll(doSave());
|
||||
return getMessages(msgs);
|
||||
}
|
||||
|
||||
private String save() {
|
||||
if (_type == null)
|
||||
return "<b>Invalid form submission (no type?)</b>";
|
||||
Properties config = getConfig();
|
||||
if (config == null)
|
||||
return "<b>Invalid params</b>";
|
||||
|
||||
TunnelController cur = getTunnelController();
|
||||
if (cur == null) {
|
||||
// creating new
|
||||
cur = new TunnelController(config, "", _privKeyGenerate);
|
||||
TunnelControllerGroup.getInstance().addController(cur);
|
||||
if (cur.getStartOnLoad())
|
||||
cur.startTunnelBackground();
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same
|
||||
// I2CP options
|
||||
List controllers = TunnelControllerGroup.getInstance().getControllers();
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelController c = (TunnelController)controllers.get(i);
|
||||
if (c == cur) continue;
|
||||
if ("httpclient".equals(c.getType()) || "client".equals(c.getType())) {
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelCount != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelCount);
|
||||
cOpt.setProperty("option.outbound.quantity", _tunnelCount);
|
||||
}
|
||||
if (_tunnelDepth != null) {
|
||||
cOpt.setProperty("option.inbound.length", _tunnelDepth);
|
||||
cOpt.setProperty("option.outbound.length", _tunnelDepth);
|
||||
}
|
||||
// these are per-proxy settings, not per-session settings, and
|
||||
// as such don't need to be shared. the values are propogated
|
||||
// to the current tunnel's settings via cur.setConfig above
|
||||
/*
|
||||
if (_connectDelay)
|
||||
cOpt.setProperty("option.i2p.streaming.connectDelay", "1000");
|
||||
else
|
||||
cOpt.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
if ("interactive".equals(_profile))
|
||||
cOpt.setProperty("option.i2p.streaming.maxWindowSize", "1");
|
||||
else
|
||||
cOpt.remove("option.i2p.streaming.maxWindowSize");
|
||||
*/
|
||||
if (_name != null) {
|
||||
cOpt.setProperty("option.inbound.nickname", _name);
|
||||
cOpt.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
c.setConfig(cOpt, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getMessages(doSave());
|
||||
}
|
||||
private List doSave() {
|
||||
TunnelControllerGroup.getInstance().saveConfig();
|
||||
return TunnelControllerGroup.getInstance().clearAllMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on all provided data, create a set of configuration parameters
|
||||
* suitable for use in a TunnelController. This will replace (not add to)
|
||||
@@ -344,6 +474,9 @@ public class WebEditPageHelper {
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_proxyList != null)
|
||||
config.setProperty("proxyList", _proxyList);
|
||||
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
} else if ("client".equals(_type)) {
|
||||
if (_port != null)
|
||||
config.setProperty("listenPort", _port);
|
||||
@@ -353,6 +486,9 @@ public class WebEditPageHelper {
|
||||
config.setProperty("interface", _reachableBy);
|
||||
if (_targetDestination != null)
|
||||
config.setProperty("targetDestination", _targetDestination);
|
||||
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
} else if ("server".equals(_type)) {
|
||||
if (_targetHost != null)
|
||||
config.setProperty("targetHost", _targetHost);
|
||||
@@ -422,32 +558,43 @@ public class WebEditPageHelper {
|
||||
else
|
||||
config.setProperty("option.i2p.streaming.connectDelay", "0");
|
||||
if (_name != null) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
}
|
||||
if ( (!"client".equals(_type)) && (!"httpclient".equals(_type)) ) {
|
||||
config.setProperty("option.inbound.nickname", _name);
|
||||
config.setProperty("option.outbound.nickname", _name);
|
||||
} else {
|
||||
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
|
||||
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
|
||||
}
|
||||
}
|
||||
if ("interactive".equals(_profile))
|
||||
config.setProperty("option.i2p.streaming.maxWindowSize", "1");
|
||||
else
|
||||
config.remove("option.i2p.streaming.maxWindowSize");
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty print the messages provided
|
||||
*
|
||||
*/
|
||||
///
|
||||
///
|
||||
///
|
||||
|
||||
protected TunnelController getController(int tunnel) {
|
||||
if (tunnel < 0) return null;
|
||||
if (_group == null) return null;
|
||||
List controllers = _group.getControllers();
|
||||
if (controllers.size() > tunnel)
|
||||
return (TunnelController)controllers.get(tunnel);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getMessages(List msgs) {
|
||||
if (msgs == null) return "";
|
||||
int num = msgs.size();
|
||||
switch (num) {
|
||||
case 0: return "";
|
||||
case 1: return (String)msgs.get(0);
|
||||
default:
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("<ul>");
|
||||
for (int i = 0; i < num; i++)
|
||||
buf.append("<li>").append((String)msgs.get(i)).append("</li>\n");
|
||||
buf.append("</ul>\n");
|
||||
return buf.toString();
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
getMessages(msgs, buf);
|
||||
return buf.toString();
|
||||
}
|
||||
private void getMessages(List msgs, StringBuffer buf) {
|
||||
if (msgs == null) return;
|
||||
for (int i = 0; i < msgs.size(); i++) {
|
||||
buf.append((String)msgs.get(i)).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<%
|
||||
}
|
||||
}
|
||||
%>
|
||||
280
apps/i2ptunnel/jsp/editClient.jsp
Normal file
280
apps/i2ptunnel/jsp/editClient.jsp
Normal 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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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" />
|
||||
<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>
|
||||
229
apps/i2ptunnel/jsp/editServer.jsp
Normal file
229
apps/i2ptunnel/jsp/editServer.jsp
Normal file
@@ -0,0 +1,229 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
|
||||
<% String tun = request.getParameter("tunnel");
|
||||
int curTunnel = -1;
|
||||
if (tun != null) {
|
||||
try {
|
||||
curTunnel = Integer.parseInt(tun);
|
||||
} catch (NumberFormatException nfe) {
|
||||
curTunnel = -1;
|
||||
}
|
||||
}
|
||||
%>
|
||||
<html>
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="index.jsp">
|
||||
<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
|
||||
<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
|
||||
<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<% if (curTunnel >= 0) { %>
|
||||
<b>Edit server settings</b>
|
||||
<% } else { %>
|
||||
<b>New server settings</b>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Type: </b>
|
||||
<td><%
|
||||
if (curTunnel >= 0) {
|
||||
%><%=editBean.getTunnelType(curTunnel)%>
|
||||
<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
|
||||
<%
|
||||
} else {
|
||||
%><%=editBean.getTypeName(request.getParameter("type"))%>
|
||||
<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
|
||||
<%
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Description: </b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Start automatically?:</b>
|
||||
</td>
|
||||
<td>
|
||||
<% if (editBean.startAutomatically(curTunnel)) { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" checked="true" />
|
||||
<% } else { %>
|
||||
<input value="1" type="checkbox" name="startOnLoad" />
|
||||
<% } %>
|
||||
<i>(Check the Box for 'YES')</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <b>Target:</b>
|
||||
</td>
|
||||
<td>
|
||||
Host: <input type="text" size="20" name="targetHost" value="<%=editBean.getTargetHost(curTunnel)%>" />
|
||||
Port: <input type="text" size="4" maxlength="4" name="targetPort" value="<%=editBean.getTargetPort(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<% String curType = editBean.getInternalType(curTunnel);
|
||||
if ( (curType == null) || (curType.trim().length() <= 0) )
|
||||
curType = request.getParameter("type");
|
||||
if ("httpserver".equals(curType)) { %>
|
||||
<tr>
|
||||
<td><b>Website name:</b></td>
|
||||
<td><input type="text" size="20" name="spoofedHost" value="<%=editBean.getSpoofedHost(curTunnel)%>" />
|
||||
</td></tr>
|
||||
<% } %>
|
||||
<tr>
|
||||
<td><b>Private key file:</b>
|
||||
</td>
|
||||
<td><input type="text" size="30" name="privKeyFile" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Profile:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="profile">
|
||||
<% if (editBean.isInteractive(curTunnel)) { %>
|
||||
<option value="interactive" selected="true">interactive connection </option>
|
||||
<option value="bulk">bulk connection (downloads/websites/BT) </option>
|
||||
<% } else { %>
|
||||
<option value="interactive">interactive connection </option>
|
||||
<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<b><hr size="1">
|
||||
Advanced networking options<br />
|
||||
</b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Tunnel depth:</b>
|
||||
</td>
|
||||
<td><select name="tunnelDepth">
|
||||
<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
|
||||
switch (tunnelDepth) {
|
||||
case 0: %>
|
||||
<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 1: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Tunnel count:</b>
|
||||
</td>
|
||||
<td>
|
||||
<select name="tunnelCount">
|
||||
<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
|
||||
switch (tunnelCount) {
|
||||
case 1: %>
|
||||
<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 2: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
case 3: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% break;
|
||||
default: %>
|
||||
<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
<tr>
|
||||
<td><b>I2CP host:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>I2CP port:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Custom options:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<hr size="1">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Save:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Save changes" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Delete?</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" name="action" value="Delete this proxy" />
|
||||
<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>
|
||||
@@ -1,26 +1,181 @@
|
||||
<%@page contentType="text/html" %>
|
||||
<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean" %>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.web.IndexBean" id="indexBean" scope="request" />
|
||||
<jsp:setProperty name="indexBean" property="*" />
|
||||
|
||||
<html><head>
|
||||
<title>I2PTunnel status</title>
|
||||
</head><body>
|
||||
<html>
|
||||
<head>
|
||||
<title>I2PTunnel Webmanager</title>
|
||||
</head>
|
||||
<body style="font-family: Verdana, Tahoma, Helvetica, sans-serif;font-size:12px;">
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td nowrap="true"><b>New Messages: </b><br />
|
||||
<a href="index.jsp">refresh</a>
|
||||
</td>
|
||||
<td>
|
||||
<textarea rows="3" cols="60" readonly="true"><jsp:getProperty name="indexBean" property="messages" /></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.WebStatusPageHelper" id="helper" scope="request" />
|
||||
<jsp:setProperty name="helper" property="*" />
|
||||
<h2>Messages since last page load:</h2>
|
||||
<b><jsp:getProperty name="helper" property="actionResults" /></b>
|
||||
|
||||
<jsp:getProperty name="helper" property="summaryList" />
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="7" align="center" valign="middle" style="font-size:14px;">
|
||||
<b>Your Client Tunnels:</b><br />
|
||||
<hr size="1" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="15%"><b>Name:</b></td>
|
||||
<td><b>Port:</b></td>
|
||||
<td><b>Type:</b></td>
|
||||
<td><b>Interface:</b></td>
|
||||
<td><b>Status:</b></td>
|
||||
</tr>
|
||||
<% for (int curClient = 0; curClient < indexBean.getTunnelCount(); curClient++) {
|
||||
if (!indexBean.isClient(curClient)) continue; %>
|
||||
<tr>
|
||||
<td valign="top" align="left">
|
||||
<b><a href="edit.jsp?tunnel=<%=curClient%>"><%=indexBean.getTunnelName(curClient) %></a></b></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientPort(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getTunnelType(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%=indexBean.getClientInterface(curClient) %></td>
|
||||
<td valign="top" align="left" nowrap="true"><%
|
||||
switch (indexBean.getTunnelStatus(curClient)) {
|
||||
case IndexBean.STARTING:
|
||||
%><b><span style="color:#339933">Starting...</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
|
||||
break;
|
||||
case IndexBean.RUNNING:
|
||||
%><b><span style="color:#00dd00">Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
|
||||
break;
|
||||
case IndexBean.NOT_RUNNING:
|
||||
%><b><span style="color:#dd0000">Not Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curClient%>">[START]</a><%
|
||||
break;
|
||||
}
|
||||
%></td>
|
||||
</tr>
|
||||
<tr><td align="right" valign="top">Destination:</td>
|
||||
<td colspan="4"><input align="left" size="40" valign="top" style="overflow: hidden" readonly="true" value="<%=indexBean.getClientDestination(curClient) %>" /></td></tr>
|
||||
<tr>
|
||||
<td valign="top" align="right">Description:</td>
|
||||
<td valign="top" align="left" colspan="4"><%=indexBean.getTunnelDescription(curClient) %></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="5" align="center" valign="middle" style="font-size:14px;">
|
||||
<b>Your Server Tunnels:</b><br />
|
||||
<hr size="1" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="15%"><b>Name: </b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Points at:</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Status:</b>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<% for (int curServer = 0; curServer < indexBean.getTunnelCount(); curServer++) {
|
||||
if (indexBean.isClient(curServer)) continue; %>
|
||||
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<b><a href="edit.jsp?tunnel=<%=curServer%>"><%=indexBean.getTunnelName(curServer)%></a></b>
|
||||
</td>
|
||||
<td valign="top"><%=indexBean.getServerTarget(curServer)%></td>
|
||||
<td valign="top" nowrap="true"><%
|
||||
switch (indexBean.getTunnelStatus(curServer)) {
|
||||
case IndexBean.RUNNING:
|
||||
%><b><span style="color:#00dd00">Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
|
||||
break;
|
||||
case IndexBean.NOT_RUNNING:
|
||||
%><b><span style="color:#dd0000">Not Running</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curServer%>">[START]</a><%
|
||||
break;
|
||||
case IndexBean.STARTING:
|
||||
%>
|
||||
<b><span style="color:#339933">Starting...</span></b>
|
||||
<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
|
||||
break;
|
||||
}
|
||||
%>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td valign="top" align="right">Description:</td>
|
||||
<td valign="top" align="left" colspan="2"><%=indexBean.getTunnelDescription(curServer)%></td></tr>
|
||||
<% } %>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
|
||||
<tr>
|
||||
<td style="background-color:#000">
|
||||
<div style="background-color:#ffffed">
|
||||
<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
|
||||
<tr>
|
||||
<td colspan="2" align="center" valign="middle">
|
||||
<b>Operations Menu - Please chose from below!</b><br /><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<form action="index.jsp" method="GET">
|
||||
<td >
|
||||
<input type="hidden" name="nonce" value="<%=indexBean.getNextNonce()%>" />
|
||||
<input type="submit" name="action" value="Stop all tunnels" />
|
||||
<input type="submit" name="action" value="Start all tunnels" />
|
||||
<input type="submit" name="action" value="Restart all" />
|
||||
<input type="submit" name="action" value="Reload config" />
|
||||
</td>
|
||||
</form>
|
||||
<form action="edit.jsp">
|
||||
<td >
|
||||
<b>Add new:</b>
|
||||
<select name="type">
|
||||
<select name="type">
|
||||
<option value="httpclient">HTTP proxy</option>
|
||||
<option value="client">Client tunnel</option>
|
||||
<option value="server">Server tunnel</option>
|
||||
<option value="httpserver">HTTP server tunnel</option>
|
||||
</select> <input type="submit" value="GO" />
|
||||
<option value="httpserver">HTTP server tunnel</option>
|
||||
</select> <input type="submit" value="Create" />
|
||||
</td>
|
||||
</form>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -36,18 +36,27 @@ public class I2PSocketManagerFactory {
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager() {
|
||||
String i2cpHost = System.getProperty(I2PClient.PROP_TCP_HOST, "localhost");
|
||||
int i2cpPort = 7654;
|
||||
String i2cpPortStr = System.getProperty(I2PClient.PROP_TCP_PORT);
|
||||
if (i2cpPortStr != null) {
|
||||
try {
|
||||
i2cpPort = Integer.parseInt(i2cpPortStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
return createManager(getHost(), getPort(), System.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the local machine on the default port (7654).
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(Properties opts) {
|
||||
return createManager(getHost(), getPort(), opts);
|
||||
}
|
||||
|
||||
return createManager(i2cpHost, i2cpPort, System.getProperties());
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the specified host and port
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(String host, int port) {
|
||||
return createManager(host, port, System.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,6 +81,26 @@ public class I2PSocketManagerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using the destination loaded from the given private key
|
||||
* stream and connected to the default I2CP host and port.
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(InputStream myPrivateKeyStream) {
|
||||
return createManager(myPrivateKeyStream, getHost(), getPort(), System.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using the destination loaded from the given private key
|
||||
* stream and connected to the default I2CP host and port.
|
||||
*
|
||||
* @return the newly created socket manager, or null if there were errors
|
||||
*/
|
||||
public static I2PSocketManager createManager(InputStream myPrivateKeyStream, Properties opts) {
|
||||
return createManager(myPrivateKeyStream, getHost(), getPort(), opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket manager using the destination loaded from the given private key
|
||||
* stream and connected to the I2CP router on the specified machine on the given
|
||||
@@ -154,4 +183,20 @@ public class I2PSocketManagerFactory {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getHost() {
|
||||
return System.getProperty(I2PClient.PROP_TCP_HOST, "localhost");
|
||||
}
|
||||
private static int getPort() {
|
||||
int i2cpPort = 7654;
|
||||
String i2cpPortStr = System.getProperty(I2PClient.PROP_TCP_PORT);
|
||||
if (i2cpPortStr != null) {
|
||||
try {
|
||||
i2cpPort = Integer.parseInt(i2cpPortStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
return i2cpPort;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
255
apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
Normal file
255
apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
Normal file
@@ -0,0 +1,255 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Task to periodically look for updates to the news.xml, and to keep
|
||||
* track of whether that has an announcement for a new version.
|
||||
*/
|
||||
public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private boolean _updateAvailable;
|
||||
private long _lastFetch;
|
||||
private static NewsFetcher _instance;
|
||||
public static final NewsFetcher getInstance() { return _instance; }
|
||||
|
||||
private static final String NEWS_FILE = "docs/news.xml";
|
||||
private static final String TEMP_NEWS_FILE = "docs/news.xml.temp";
|
||||
|
||||
public NewsFetcher(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(NewsFetcher.class);
|
||||
_instance = this;
|
||||
File news = new File(NEWS_FILE);
|
||||
if (news.exists())
|
||||
_lastFetch = news.lastModified();
|
||||
else
|
||||
_lastFetch = 0;
|
||||
}
|
||||
|
||||
public boolean updateAvailable() { return _updateAvailable; }
|
||||
|
||||
public void run() {
|
||||
try { Thread.sleep(_context.random().nextLong(5*60*1000)); } catch (InterruptedException ie) {}
|
||||
while (true) {
|
||||
if (!_updateAvailable) checkForUpdates();
|
||||
if (shouldFetchNews())
|
||||
fetchNews();
|
||||
try { Thread.sleep(10*60*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldInstall() {
|
||||
String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY);
|
||||
return ("install".equals(policy));
|
||||
}
|
||||
|
||||
private boolean shouldFetchNews() {
|
||||
String freq = _context.getProperty(ConfigUpdateHandler.PROP_REFRESH_FREQUENCY);
|
||||
if (freq == null)
|
||||
freq = ConfigUpdateHandler.DEFAULT_REFRESH_FREQUENCY;
|
||||
try {
|
||||
long ms = Long.parseLong(freq);
|
||||
if (ms <= 0)
|
||||
return false;
|
||||
|
||||
if (_lastFetch + ms < _context.clock().now()) {
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Last fetched " + DataHelper.formatDuration(_context.clock().now() - _lastFetch) + " ago");
|
||||
return false;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Invalid refresh frequency: " + freq);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private void fetchNews() {
|
||||
String newsURL = _context.getProperty(ConfigUpdateHandler.PROP_NEWS_URL, ConfigUpdateHandler.DEFAULT_NEWS_URL);
|
||||
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
String port = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT);
|
||||
File tempFile = new File(TEMP_NEWS_FILE);
|
||||
if (tempFile.exists())
|
||||
tempFile.delete();
|
||||
|
||||
int proxyPort = -1;
|
||||
try {
|
||||
proxyPort = Integer.parseInt(port);
|
||||
EepGet get = null;
|
||||
if (shouldProxy)
|
||||
get = new EepGet(_context, proxyHost, proxyPort, 10, TEMP_NEWS_FILE, newsURL);
|
||||
else
|
||||
get = new EepGet(_context, 10, TEMP_NEWS_FILE, newsURL);
|
||||
get.addStatusListener(this);
|
||||
get.fetch();
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error fetching the news", t);
|
||||
}
|
||||
|
||||
_lastFetch = _context.clock().now();
|
||||
}
|
||||
|
||||
private static final String VERSION_STRING = "version=\"" + RouterVersion.VERSION + "\"";
|
||||
private static final String VERSION_PREFIX = "version=\"";
|
||||
private void checkForUpdates() {
|
||||
File news = new File(NEWS_FILE);
|
||||
if (!news.exists()) return;
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(news);
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
while (DataHelper.readLine(in, buf)) {
|
||||
int index = buf.indexOf(VERSION_PREFIX);
|
||||
if (index == -1) {
|
||||
// skip
|
||||
} else {
|
||||
int end = buf.indexOf("\"", index + VERSION_PREFIX.length());
|
||||
if (end > index) {
|
||||
String ver = buf.substring(index+VERSION_PREFIX.length(), end);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Found version: [" + ver + "]");
|
||||
if (needsUpdate(ver)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Our version is out of date, update!");
|
||||
break;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Our version is current");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (buf.indexOf(VERSION_STRING) != -1) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Our version found, no need to update: " + buf.toString());
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("No match in " + buf.toString());
|
||||
}
|
||||
buf.setLength(0);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error checking the news for an update", ioe);
|
||||
return;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
// could not find version="0.5.0.1", so there must be an update ;)
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Our version was NOT found (" + RouterVersion.VERSION + "), update needed");
|
||||
_updateAvailable = true;
|
||||
|
||||
if (shouldInstall()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Policy requests update, so we update");
|
||||
UpdateHandler handler = null;
|
||||
if (_context instanceof RouterContext) {
|
||||
handler = new UpdateHandler((RouterContext)_context);
|
||||
} else {
|
||||
List contexts = RouterContext.listContexts();
|
||||
if (contexts.size() > 0)
|
||||
handler = new UpdateHandler((RouterContext)contexts.get(0));
|
||||
else
|
||||
_log.log(Log.CRIT, "No router context to update with?");
|
||||
}
|
||||
if (handler != null)
|
||||
handler.update();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Policy requests manual update, so we do nothing");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean needsUpdate(String version) {
|
||||
StringTokenizer newTok = new StringTokenizer(sanitize(version), ".");
|
||||
StringTokenizer ourTok = new StringTokenizer(sanitize(RouterVersion.VERSION), ".");
|
||||
|
||||
while (newTok.hasMoreTokens() && ourTok.hasMoreTokens()) {
|
||||
String newVer = newTok.nextToken();
|
||||
String oldVer = ourTok.nextToken();
|
||||
switch (compare(newVer, oldVer)) {
|
||||
case -1: // newVer is smaller
|
||||
return false;
|
||||
case 0: // eq
|
||||
break;
|
||||
case 1: // newVer is larger
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (newTok.hasMoreTokens() && !ourTok.hasMoreTokens())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final String VALID = "0123456789.";
|
||||
private static final String sanitize(String str) {
|
||||
StringBuffer buf = new StringBuffer(str);
|
||||
for (int i = 0; i < buf.length(); i++) {
|
||||
if (VALID.indexOf(buf.charAt(i)) == -1) {
|
||||
buf.deleteCharAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final int compare(String lhs, String rhs) {
|
||||
try {
|
||||
int left = Integer.parseInt(lhs);
|
||||
int right = Integer.parseInt(rhs);
|
||||
if (left < right)
|
||||
return -1;
|
||||
else if (left == right)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
} catch (NumberFormatException nfe) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||
// ignore
|
||||
}
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
// ignore
|
||||
}
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("News fetched from " + url);
|
||||
|
||||
File temp = new File(TEMP_NEWS_FILE);
|
||||
if (temp.exists()) {
|
||||
boolean copied = FileUtil.copy(TEMP_NEWS_FILE, NEWS_FILE, true);
|
||||
if (copied)
|
||||
temp.delete();
|
||||
}
|
||||
checkForUpdates();
|
||||
}
|
||||
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Failed to fetch the news from " + url);
|
||||
File temp = new File(TEMP_NEWS_FILE);
|
||||
temp.delete();
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,11 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.apps.systray.SysTray;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import org.mortbay.jetty.Server;
|
||||
import org.mortbay.jetty.servlet.WebApplicationContext;
|
||||
@@ -70,6 +72,10 @@ public class RouterConsoleRunner {
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
I2PThread t = new I2PThread(new NewsFetcher(I2PAppContext.getGlobalContext()), "NewsFetcher");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void initialize(WebApplicationContext context) {
|
||||
|
||||
@@ -84,7 +84,7 @@ public class SummaryHelper {
|
||||
|
||||
if (ms < 60 * 1000) {
|
||||
return now + " (" + (ms / 1000) + "s)";
|
||||
} else if (ms < 60 * 1000) {
|
||||
} else if (ms < 60 * 60 * 1000) {
|
||||
return now + " (" + (ms / (60 * 1000)) + "m)";
|
||||
} else if (ms < 24 * 60 * 60 * 1000) {
|
||||
return now + " (" + (ms / (60 * 60 * 1000)) + "h)";
|
||||
@@ -96,7 +96,7 @@ public class SummaryHelper {
|
||||
public boolean allowReseed() {
|
||||
return (_context.netDb().getKnownRouters() < 10);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve amount of used memory.
|
||||
*
|
||||
@@ -467,4 +467,8 @@ public class SummaryHelper {
|
||||
|
||||
return _context.throttle().getTunnelLag() + "ms";
|
||||
}
|
||||
|
||||
public boolean updateAvailable() {
|
||||
return NewsFetcher.getInstance().updateAvailable();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
51
apps/routerconsole/jsp/configupdate.jsp
Normal file
51
apps/routerconsole/jsp/configupdate.jsp
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
12
apps/routerconsole/jsp/verifyupdate.jsp
Normal file
12
apps/routerconsole/jsp/verifyupdate.jsp
Normal 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>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -71,7 +71,7 @@ public class Connection {
|
||||
private long _lifetimeDupMessageSent;
|
||||
private long _lifetimeDupMessageReceived;
|
||||
|
||||
public static final long MAX_RESEND_DELAY = 60*1000;
|
||||
public static final long MAX_RESEND_DELAY = 20*1000;
|
||||
public static final long MIN_RESEND_DELAY = 10*1000;
|
||||
|
||||
/** wait up to 5 minutes after disconnection so we can ack/close packets */
|
||||
@@ -258,7 +258,7 @@ public class Connection {
|
||||
}
|
||||
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
|
||||
long timeout = (_options.getRTT() < MIN_RESEND_DELAY ? MIN_RESEND_DELAY : _options.getRTT());
|
||||
long timeout = _options.getRTT() + MIN_RESEND_DELAY;
|
||||
if (timeout > MAX_RESEND_DELAY)
|
||||
timeout = MAX_RESEND_DELAY;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -892,13 +892,15 @@ public class Connection {
|
||||
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Resend packet " + _packet + " time " + numSends +
|
||||
" activeResends: " + _activeResends +
|
||||
" (wsize "
|
||||
+ newWindowSize + " lifetime "
|
||||
+ (_context.clock().now() - _packet.getCreatedOn()) + "ms)");
|
||||
_outboundQueue.enqueue(_packet);
|
||||
if (numSends - 1 <= _options.getMaxResends()) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Resend packet " + _packet + " time " + numSends +
|
||||
" activeResends: " + _activeResends +
|
||||
" (wsize "
|
||||
+ newWindowSize + " lifetime "
|
||||
+ (_context.clock().now() - _packet.getCreatedOn()) + "ms)");
|
||||
_outboundQueue.enqueue(_packet);
|
||||
}
|
||||
|
||||
_lastSendTime = _context.clock().now();
|
||||
|
||||
@@ -911,7 +913,7 @@ public class Connection {
|
||||
return;
|
||||
}
|
||||
|
||||
if (numSends > _options.getMaxResends()) {
|
||||
if (numSends - 1 > _options.getMaxResends()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Too many resends");
|
||||
_packet.cancelled();
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -552,6 +552,11 @@ public class Packet {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer str = formatAsString();
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
protected StringBuffer formatAsString() {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
buf.append(toId(_sendStreamId));
|
||||
//buf.append("<-->");
|
||||
@@ -570,7 +575,7 @@ public class Packet {
|
||||
}
|
||||
if ( (_payload != null) && (_payload.getValid() > 0) )
|
||||
buf.append(" data: ").append(_payload.getValid());
|
||||
return buf.toString();
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static final String toId(byte id[]) {
|
||||
|
||||
@@ -107,11 +107,15 @@ public class PacketHandler {
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss.SSS");
|
||||
void displayPacket(Packet packet, String prefix, String suffix) {
|
||||
if (!_log.shouldLog(Log.DEBUG)) return;
|
||||
String msg = null;
|
||||
StringBuffer buf = new StringBuffer(256);
|
||||
synchronized (_fmt) {
|
||||
msg = _fmt.format(new Date()) + ": " + prefix + " " + packet.toString() + (suffix != null ? " " + suffix : "");
|
||||
buf.append(_fmt.format(new Date()));
|
||||
}
|
||||
System.out.println(msg);
|
||||
buf.append(": ").append(prefix).append(" ");
|
||||
buf.append(packet.toString());
|
||||
if (suffix != null)
|
||||
buf.append(" ").append(suffix);
|
||||
System.out.println(buf.toString());
|
||||
}
|
||||
|
||||
private void receiveKnownCon(Connection con, Packet packet) {
|
||||
|
||||
@@ -114,16 +114,44 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
|
||||
public void setResendPacketEvent(SimpleTimer.TimedEvent evt) { _resendEvent = evt; }
|
||||
|
||||
public String toString() {
|
||||
String str = super.toString();
|
||||
public StringBuffer formatAsString() {
|
||||
StringBuffer buf = super.formatAsString();
|
||||
|
||||
Connection con = _connection;
|
||||
if (con != null)
|
||||
buf.append(" rtt ").append(con.getOptions().getRTT());
|
||||
|
||||
if ( (_tagsSent != null) && (_tagsSent.size() > 0) )
|
||||
str = str + " with tags";
|
||||
buf.append(" with tags");
|
||||
|
||||
if (_ackOn > 0)
|
||||
return str + " ack after " + getAckTime() + (_numSends <= 1 ? "" : " sent " + _numSends + " times");
|
||||
else
|
||||
return str + (_numSends <= 1 ? "" : " sent " + _numSends + " times");
|
||||
buf.append(" ack after ").append(getAckTime());
|
||||
|
||||
if (_numSends > 1)
|
||||
buf.append(" sent ").append(_numSends).append(" times");
|
||||
|
||||
if (isFlagSet(Packet.FLAG_SYNCHRONIZE) ||
|
||||
isFlagSet(Packet.FLAG_CLOSE) ||
|
||||
isFlagSet(Packet.FLAG_RESET)) {
|
||||
|
||||
if (con != null) {
|
||||
buf.append(" from ");
|
||||
Destination local = con.getSession().getMyDestination();
|
||||
if (local != null)
|
||||
buf.append(local.calculateHash().toBase64().substring(0,4));
|
||||
else
|
||||
buf.append("unknown");
|
||||
|
||||
buf.append(" to ");
|
||||
Destination remote = con.getRemotePeer();
|
||||
if (remote != null)
|
||||
buf.append(remote.calculateHash().toBase64().substring(0,4));
|
||||
else
|
||||
buf.append("unknown");
|
||||
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
public void waitForAccept(int maxWaitMs) {
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -14,8 +14,8 @@ package net.i2p;
|
||||
*
|
||||
*/
|
||||
public class CoreVersion {
|
||||
public final static String ID = "$Revision: 1.29 $ $Date: 2005/02/23 00:00:52 $";
|
||||
public final static String VERSION = "0.5.0.2";
|
||||
public final static String ID = "$Revision: 1.32 $ $Date: 2005/03/24 02:29:28 $";
|
||||
public final static String VERSION = "0.5.0.5";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Core version: " + VERSION);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,13 +52,17 @@ class I2CPMessageProducer {
|
||||
CreateSessionMessage msg = new CreateSessionMessage();
|
||||
SessionConfig cfg = new SessionConfig(session.getMyDestination());
|
||||
cfg.setOptions(session.getOptions());
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("config created");
|
||||
try {
|
||||
cfg.signSessionConfig(session.getPrivateKey());
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2PSessionException("Unable to sign the session config", dfe);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("config signed");
|
||||
msg.setSessionConfig(cfg);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("config loaded into message");
|
||||
session.sendMessage(msg);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("config message sent");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -504,12 +504,15 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
|
||||
long beforeSync = _context.clock().now();
|
||||
long inSync = 0;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync to write");
|
||||
try {
|
||||
synchronized (_out) {
|
||||
inSync = _context.clock().now();
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("before writeMessage");
|
||||
message.writeMessage(_out);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("after writeMessage");
|
||||
_out.flush();
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("after flush");
|
||||
}
|
||||
} catch (I2CPMessageException ime) {
|
||||
throw new I2PSessionException(getPrefix() + "Error writing out the message", ime);
|
||||
|
||||
@@ -73,10 +73,11 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
}
|
||||
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent)
|
||||
throws I2PSessionException {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message");
|
||||
if (isClosed()) throw new I2PSessionException("Already closed");
|
||||
if (SHOULD_COMPRESS) payload = DataHelper.compress(payload, offset, size);
|
||||
else throw new IllegalStateException("we need to update sendGuaranteed to support partial send");
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("message compressed");
|
||||
// we always send as guaranteed (so we get the session keys/tags acked),
|
||||
// but only block until the appropriate event has been reached (guaranteed
|
||||
// success or accepted). we may want to break this out into a seperate
|
||||
@@ -111,10 +112,12 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent)
|
||||
throws I2PSessionException {
|
||||
long begin = _context.clock().now();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("begin sendBestEffort");
|
||||
SessionKey key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("key fetched");
|
||||
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
|
||||
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("tag consumed");
|
||||
Set sentTags = null;
|
||||
int oldTags = _context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key);
|
||||
long availTimeLeft = _context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key);
|
||||
@@ -151,7 +154,10 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
sentTags.addAll(tagsSent);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("before creating nonce");
|
||||
|
||||
long nonce = _context.random().nextInt(Integer.MAX_VALUE);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state");
|
||||
MessageState state = new MessageState(nonce, getPrefix());
|
||||
state.setKey(key);
|
||||
state.setTags(sentTags);
|
||||
@@ -171,6 +177,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state");
|
||||
long beforeSendingSync = _context.clock().now();
|
||||
long inSendingSync = 0;
|
||||
synchronized (_sendingStates) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,101 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
|
||||
/**
|
||||
* Calculate the HMAC-SHA256 of a key+message. Currently FAKE - returns a stupid
|
||||
* kludgy hash: H(H(key) XOR H(data)). Fix me!
|
||||
* Calculate the HMAC-SHA256 of a key+message.
|
||||
*
|
||||
*/
|
||||
public class HMACSHA256Generator {
|
||||
public HMACSHA256Generator(I2PAppContext context) { // nop
|
||||
private I2PAppContext _context;
|
||||
public HMACSHA256Generator(I2PAppContext context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public static HMACSHA256Generator getInstance() {
|
||||
return I2PAppContext.getGlobalContext().hmac();
|
||||
}
|
||||
|
||||
private static final int PAD_LENGTH = 64;
|
||||
|
||||
private static final byte[] _IPAD = new byte[PAD_LENGTH];
|
||||
private static final byte[] _OPAD = new byte[PAD_LENGTH];
|
||||
static {
|
||||
for (int i = 0; i < _IPAD.length; i++) {
|
||||
_IPAD[i] = 0x36;
|
||||
_OPAD[i] = 0x5C;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Buffer createBuffer(int dataLen) { return new Buffer(dataLen); }
|
||||
|
||||
public class Buffer {
|
||||
private byte padded[];
|
||||
private byte innerBuf[];
|
||||
private SHA256EntryCache.CacheEntry innerEntry;
|
||||
private byte rv[];
|
||||
private byte outerBuf[];
|
||||
private SHA256EntryCache.CacheEntry outerEntry;
|
||||
|
||||
public Buffer(int dataLength) {
|
||||
padded = new byte[PAD_LENGTH];
|
||||
innerBuf = new byte[dataLength + PAD_LENGTH];
|
||||
innerEntry = _context.sha().cache().acquire(innerBuf.length);
|
||||
rv = new byte[Hash.HASH_LENGTH];
|
||||
outerBuf = new byte[Hash.HASH_LENGTH + PAD_LENGTH];
|
||||
outerEntry = _context.sha().cache().acquire(outerBuf.length);
|
||||
}
|
||||
|
||||
public void releaseCached() {
|
||||
_context.sha().cache().release(innerEntry);
|
||||
_context.sha().cache().release(outerEntry);
|
||||
}
|
||||
|
||||
public byte[] getHash() { return rv; }
|
||||
}
|
||||
|
||||
/**
|
||||
* This should calculate the HMAC/SHA256, but it DOESNT. Its just a kludge.
|
||||
* Fix me.
|
||||
* Calculate the HMAC of the data with the given key
|
||||
*/
|
||||
public Hash calculate(SessionKey key, byte data[]) {
|
||||
if ((key == null) || (key.getData() == null) || (data == null))
|
||||
throw new NullPointerException("Null arguments for HMAC");
|
||||
|
||||
Hash hkey = SHA256Generator.getInstance().calculateHash(key.getData());
|
||||
Hash hdata = SHA256Generator.getInstance().calculateHash(data);
|
||||
return SHA256Generator.getInstance().calculateHash(DataHelper.xor(hkey.getData(), hdata.getData()));
|
||||
|
||||
Buffer buf = new Buffer(data.length);
|
||||
calculate(key, data, buf);
|
||||
Hash rv = new Hash(buf.rv);
|
||||
buf.releaseCached();
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the HMAC of the data with the given key
|
||||
*/
|
||||
public void calculate(SessionKey key, byte data[], Buffer buf) {
|
||||
// inner hash
|
||||
padKey(key.getData(), _IPAD, buf.padded);
|
||||
System.arraycopy(buf.padded, 0, buf.innerBuf, 0, PAD_LENGTH);
|
||||
System.arraycopy(data, 0, buf.innerBuf, PAD_LENGTH, data.length);
|
||||
|
||||
Hash h = _context.sha().calculateHash(buf.innerBuf, buf.innerEntry);
|
||||
|
||||
// outer hash
|
||||
padKey(key.getData(), _OPAD, buf.padded);
|
||||
System.arraycopy(buf.padded, 0, buf.outerBuf, 0, PAD_LENGTH);
|
||||
System.arraycopy(h.getData(), 0, buf.outerBuf, PAD_LENGTH, Hash.HASH_LENGTH);
|
||||
|
||||
h = _context.sha().calculateHash(buf.outerBuf, buf.outerEntry);
|
||||
System.arraycopy(h.getData(), 0, buf.rv, 0, Hash.HASH_LENGTH);
|
||||
}
|
||||
|
||||
private static final void padKey(byte key[], byte pad[], byte out[]) {
|
||||
for (int i = 0; i < SessionKey.KEYSIZE_BYTES; i++)
|
||||
out[i] = (byte) (key[i] ^ pad[i]);
|
||||
Arrays.fill(out, SessionKey.KEYSIZE_BYTES, PAD_LENGTH, pad[0]);
|
||||
}
|
||||
}
|
||||
697
core/java/src/net/i2p/crypto/SHA1.java
Normal file
697
core/java/src/net/i2p/crypto/SHA1.java
Normal 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);
|
||||
}
|
||||
}
|
||||
191
core/java/src/net/i2p/crypto/SHA1Test.java
Normal file
191
core/java/src/net/i2p/crypto/SHA1Test.java
Normal 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";
|
||||
}
|
||||
380
core/java/src/net/i2p/crypto/TrustedUpdate.java
Normal file
380
core/java/src/net/i2p/crypto/TrustedUpdate.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -35,6 +35,15 @@ public class Destination extends DataStructureImpl {
|
||||
__calculatedHash = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* alternative constructor which takes a base64 string representation
|
||||
* @param s a Base64 representation of the destination, as (eg) is used in hosts.txt
|
||||
*/
|
||||
public Destination(String s) throws DataFormatException {
|
||||
this();
|
||||
fromBase64(s);
|
||||
}
|
||||
|
||||
public Certificate getCertificate() {
|
||||
return _certificate;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -158,21 +158,18 @@ public class Timestamper implements Runnable {
|
||||
*/
|
||||
private boolean queryTime(String serverList[]) throws IllegalArgumentException {
|
||||
long found[] = new long[_concurringServers];
|
||||
long localTime = -1;
|
||||
long now = -1;
|
||||
long expectedDelta = 0;
|
||||
for (int i = 0; i < _concurringServers; i++) {
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
localTime = _context.clock().now();
|
||||
now = NtpClient.currentTime(serverList);
|
||||
|
||||
long delta = now - localTime;
|
||||
long delta = now - _context.clock().now();
|
||||
found[i] = delta;
|
||||
if (i == 0) {
|
||||
if (Math.abs(delta) < MAX_VARIANCE) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("a single SNTP query was within the tolerance (" + delta + "ms)");
|
||||
return true;
|
||||
break;
|
||||
} else {
|
||||
// outside the tolerance, lets iterate across the concurring queries
|
||||
expectedDelta = delta;
|
||||
|
||||
554
core/java/src/net/i2p/util/EepGet.java
Normal file
554
core/java/src/net/i2p/util/EepGet.java
Normal file
@@ -0,0 +1,554 @@
|
||||
package net.i2p.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* EepGet [-p localhost:4444]
|
||||
* [-n #retries]
|
||||
* [-o outputFile]
|
||||
* [-m markSize lineLen]
|
||||
* url
|
||||
*/
|
||||
public class EepGet {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private boolean _shouldProxy;
|
||||
private String _proxyHost;
|
||||
private int _proxyPort;
|
||||
private int _numRetries;
|
||||
private String _outputFile;
|
||||
private String _url;
|
||||
private List _listeners;
|
||||
|
||||
private boolean _keepFetching;
|
||||
private Socket _proxy;
|
||||
private OutputStream _proxyOut;
|
||||
private InputStream _proxyIn;
|
||||
private OutputStream _out;
|
||||
private long _alreadyTransferred;
|
||||
private long _bytesTransferred;
|
||||
private long _bytesRemaining;
|
||||
private int _currentAttempt;
|
||||
private String _etag;
|
||||
private boolean _encodingChunked;
|
||||
|
||||
public EepGet(I2PAppContext ctx, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
|
||||
this(ctx, true, proxyHost, proxyPort, numRetries, outputFile, url);
|
||||
}
|
||||
public EepGet(I2PAppContext ctx, int numRetries, String outputFile, String url) {
|
||||
this(ctx, false, null, -1, numRetries, outputFile, url);
|
||||
}
|
||||
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(EepGet.class);
|
||||
_shouldProxy = shouldProxy;
|
||||
_proxyHost = proxyHost;
|
||||
_proxyPort = proxyPort;
|
||||
_numRetries = numRetries;
|
||||
_outputFile = outputFile;
|
||||
_url = url;
|
||||
_alreadyTransferred = 0;
|
||||
_bytesTransferred = 0;
|
||||
_bytesRemaining = -1;
|
||||
_currentAttempt = 0;
|
||||
_listeners = new ArrayList(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* EepGet [-p localhost:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] url
|
||||
*
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String proxyHost = "localhost";
|
||||
int proxyPort = 4444;
|
||||
int numRetries = 5;
|
||||
int markSize = 1024;
|
||||
int lineLen = 40;
|
||||
String saveAs = null;
|
||||
String url = null;
|
||||
try {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].equals("-p")) {
|
||||
proxyHost = args[i+1].substring(0, args[i+1].indexOf(':'));
|
||||
String port = args[i+1].substring(args[i+1].indexOf(':')+1);
|
||||
proxyPort = Integer.parseInt(port);
|
||||
i++;
|
||||
} else if (args[i].equals("-n")) {
|
||||
numRetries = Integer.parseInt(args[i+1]);
|
||||
i++;
|
||||
} else if (args[i].equals("-o")) {
|
||||
saveAs = args[i+1];
|
||||
i++;
|
||||
} else if (args[i].equals("-m")) {
|
||||
markSize = Integer.parseInt(args[i+1]);
|
||||
lineLen = Integer.parseInt(args[i+2]);
|
||||
i += 2;
|
||||
} else {
|
||||
url = args[i];
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (url == null) {
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
if (saveAs == null)
|
||||
saveAs = suggestName(url);
|
||||
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), proxyHost, proxyPort, numRetries, saveAs, url);
|
||||
get.addStatusListener(get.new CLIStatusListener(markSize, lineLen));
|
||||
get.fetch();
|
||||
}
|
||||
|
||||
public static String suggestName(String url) {
|
||||
String name = null;
|
||||
if (url.lastIndexOf('/') >= 0)
|
||||
name = sanitize(url.substring(url.lastIndexOf('/')+1));
|
||||
if (name != null)
|
||||
return name;
|
||||
else
|
||||
return sanitize(url);
|
||||
}
|
||||
|
||||
private static final String _safeChars = "abcdefghijklmnopqrstuvwxyz" +
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
"01234567890.,_=@#:";
|
||||
private static String sanitize(String name) {
|
||||
name = name.replace('/', '_');
|
||||
StringBuffer buf = new StringBuffer(name);
|
||||
for (int i = 0; i < name.length(); i++)
|
||||
if (_safeChars.indexOf(buf.charAt(i)) == -1)
|
||||
buf.setCharAt(i, '_');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.err.println("EepGet [-p localhost:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] url");
|
||||
}
|
||||
|
||||
public static interface StatusListener {
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url);
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile);
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause);
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt);
|
||||
}
|
||||
private class CLIStatusListener implements StatusListener {
|
||||
private int _markSize;
|
||||
private int _lineSize;
|
||||
private long _startedOn;
|
||||
private long _written;
|
||||
private long _lastComplete;
|
||||
private DecimalFormat _pct = new DecimalFormat("00.0%");
|
||||
private DecimalFormat _kbps = new DecimalFormat("###,000.00");
|
||||
public CLIStatusListener() {
|
||||
this(1024, 40);
|
||||
}
|
||||
public CLIStatusListener(int markSize, int lineSize) {
|
||||
_markSize = markSize;
|
||||
_lineSize = lineSize;
|
||||
_written = 0;
|
||||
_lastComplete = _context.clock().now();
|
||||
_startedOn = _lastComplete;
|
||||
}
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
for (int i = 0; i < currentWrite; i++) {
|
||||
_written++;
|
||||
if ( (_markSize > 0) && (_written % _markSize == 0) ) {
|
||||
System.out.print("#");
|
||||
|
||||
if ( (_lineSize > 0) && (_written % ((long)_markSize*(long)_lineSize) == 0l) ) {
|
||||
long now = _context.clock().now();
|
||||
long timeToSend = now - _lastComplete;
|
||||
if (timeToSend > 0) {
|
||||
StringBuffer buf = new StringBuffer(50);
|
||||
buf.append(" ");
|
||||
double pct = ((double)alreadyTransferred + (double)_written) / ((double)alreadyTransferred + (double)bytesRemaining);
|
||||
synchronized (_pct) {
|
||||
buf.append(_pct.format(pct));
|
||||
}
|
||||
buf.append(": ");
|
||||
buf.append(_written+alreadyTransferred);
|
||||
buf.append(" @ ");
|
||||
double lineKBytes = ((double)_markSize * (double)_lineSize)/1024.0d;
|
||||
double kbps = lineKBytes/((double)timeToSend/1000.0d);
|
||||
synchronized (_kbps) {
|
||||
buf.append(_kbps.format(kbps));
|
||||
}
|
||||
buf.append("KBps");
|
||||
|
||||
buf.append(" / ");
|
||||
long lifetime = _context.clock().now() - _startedOn;
|
||||
double lifetimeKBps = (1000.0d*(double)(_written+alreadyTransferred)/((double)lifetime*1024.0d));
|
||||
synchronized (_kbps) {
|
||||
buf.append(_kbps.format(lifetimeKBps));
|
||||
}
|
||||
buf.append("KBps");
|
||||
System.out.println(buf.toString());
|
||||
}
|
||||
_lastComplete = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
|
||||
System.out.println();
|
||||
System.out.println("== " + new Date());
|
||||
System.out.println("== Transfer of " + url + " completed with " + (alreadyTransferred+bytesTransferred)
|
||||
+ " and " + (bytesRemaining - bytesTransferred) + " remaining");
|
||||
System.out.println("== Output saved to " + outputFile);
|
||||
long timeToSend = _context.clock().now() - _startedOn;
|
||||
System.out.println("== Transfer time: " + DataHelper.formatDuration(timeToSend));
|
||||
StringBuffer buf = new StringBuffer(50);
|
||||
buf.append("== Transfer rate: ");
|
||||
double kbps = (1000.0d*(double)(_written)/((double)timeToSend*1024.0d));
|
||||
synchronized (_kbps) {
|
||||
buf.append(_kbps.format(kbps));
|
||||
}
|
||||
buf.append("KBps");
|
||||
System.out.println(buf.toString());
|
||||
}
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||
System.out.println();
|
||||
System.out.println("** " + new Date());
|
||||
System.out.println("** Attempt " + currentAttempt + " of " + url + " failed");
|
||||
System.out.println("** Transfered " + bytesTransferred
|
||||
+ " with " + (bytesRemaining < 0 ? "unknown" : ""+bytesRemaining) + " remaining");
|
||||
System.out.println("** " + cause.getMessage());
|
||||
_written = 0;
|
||||
}
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
System.out.println("== " + new Date());
|
||||
System.out.println("== Transfer of " + url + " failed after " + currentAttempt + " attempts");
|
||||
System.out.println("== Transfer size: " + bytesTransferred + " with "
|
||||
+ (bytesRemaining < 0 ? "unknown" : ""+bytesRemaining) + " remaining");
|
||||
long timeToSend = _context.clock().now() - _startedOn;
|
||||
System.out.println("== Transfer time: " + DataHelper.formatDuration(timeToSend));
|
||||
double kbps = (timeToSend > 0 ? (1000.0d*(double)(bytesTransferred)/((double)timeToSend*1024.0d)) : 0);
|
||||
StringBuffer buf = new StringBuffer(50);
|
||||
buf.append("== Transfer rate: ");
|
||||
synchronized (_kbps) {
|
||||
buf.append(_kbps.format(kbps));
|
||||
}
|
||||
buf.append("KBps");
|
||||
System.out.println(buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void addStatusListener(StatusListener lsnr) {
|
||||
synchronized (_listeners) { _listeners.add(lsnr); }
|
||||
}
|
||||
|
||||
public void stopFetching() { _keepFetching = false; }
|
||||
public void fetch() {
|
||||
_keepFetching = true;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fetching (proxied? " + _shouldProxy + ") url=" + _url);
|
||||
while (_keepFetching) {
|
||||
try {
|
||||
sendRequest();
|
||||
doFetch();
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
|
||||
} finally {
|
||||
if (_out != null) {
|
||||
try {
|
||||
_out.close();
|
||||
} catch (IOException cioe) {}
|
||||
_out = null;
|
||||
}
|
||||
if (_proxy != null) {
|
||||
try {
|
||||
_proxy.close();
|
||||
_proxy = null;
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
_currentAttempt++;
|
||||
if (_currentAttempt > _numRetries)
|
||||
break;
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt);
|
||||
}
|
||||
|
||||
/** return true if the URL was completely retrieved */
|
||||
private void doFetch() throws IOException {
|
||||
readHeaders();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Headers read completely, reading " + _bytesRemaining);
|
||||
|
||||
int remaining = (int)_bytesRemaining;
|
||||
byte buf[] = new byte[1024];
|
||||
while (_keepFetching && remaining > 0) {
|
||||
int toRead = buf.length;
|
||||
int read = _proxyIn.read(buf, 0, (buf.length > remaining ? remaining : buf.length));
|
||||
if (read == -1)
|
||||
break;
|
||||
_out.write(buf, 0, read);
|
||||
_bytesTransferred += read;
|
||||
remaining -= read;
|
||||
if (read > 0)
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).bytesTransferred(_alreadyTransferred, read, _bytesTransferred, _bytesRemaining, _url);
|
||||
}
|
||||
|
||||
if (_out != null)
|
||||
_out.close();
|
||||
_out = null;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Done transferring " + _bytesTransferred);
|
||||
|
||||
if ( (_bytesRemaining == -1) || (remaining == 0) ){
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).transferComplete(_alreadyTransferred, _bytesTransferred, _bytesRemaining, _url, _outputFile);
|
||||
} else {
|
||||
throw new IOException("Disconnection on attempt " + _currentAttempt + " after " + _bytesTransferred);
|
||||
}
|
||||
}
|
||||
|
||||
private void readHeaders() throws IOException {
|
||||
String key = null;
|
||||
StringBuffer buf = new StringBuffer(32);
|
||||
|
||||
boolean read = DataHelper.readLine(_proxyIn, buf);
|
||||
if (!read) throw new IOException("Unable to read the first line");
|
||||
int responseCode = handleStatus(buf.toString());
|
||||
|
||||
boolean rcOk = false;
|
||||
switch (responseCode) {
|
||||
case 200: // full
|
||||
_out = new FileOutputStream(_outputFile, false);
|
||||
rcOk = true;
|
||||
break;
|
||||
case 206: // partial
|
||||
_out = new FileOutputStream(_outputFile, true);
|
||||
rcOk = true;
|
||||
break;
|
||||
case 416: // completed (or range out of reach)
|
||||
_bytesRemaining = 0;
|
||||
_keepFetching = false;
|
||||
return;
|
||||
default:
|
||||
rcOk = false;
|
||||
}
|
||||
|
||||
byte lookahead[] = new byte[3];
|
||||
while (true) {
|
||||
int cur = _proxyIn.read();
|
||||
switch (cur) {
|
||||
case -1:
|
||||
throw new IOException("Headers ended too soon");
|
||||
case ':':
|
||||
if (key == null) {
|
||||
key = buf.toString();
|
||||
buf.setLength(0);
|
||||
increment(lookahead, cur);
|
||||
break;
|
||||
} else {
|
||||
buf.append((char)cur);
|
||||
increment(lookahead, cur);
|
||||
break;
|
||||
}
|
||||
case '\n':
|
||||
case '\r':
|
||||
if (key != null)
|
||||
handle(key, buf.toString());
|
||||
|
||||
buf.setLength(0);
|
||||
key = null;
|
||||
increment(lookahead, cur);
|
||||
if (isEndOfHeaders(lookahead)) {
|
||||
if (!rcOk)
|
||||
throw new IOException("Invalid HTTP response code: " + responseCode);
|
||||
if (_encodingChunked) {
|
||||
readChunkLength();
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
buf.append((char)cur);
|
||||
increment(lookahead, cur);
|
||||
}
|
||||
|
||||
if (buf.length() > 1024)
|
||||
throw new IOException("Header line too long: " + buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void readChunkLength() throws IOException {
|
||||
StringBuffer buf = new StringBuffer(8);
|
||||
int nl = 0;
|
||||
while (true) {
|
||||
int cur = _proxyIn.read();
|
||||
switch (cur) {
|
||||
case -1:
|
||||
throw new IOException("Chunk ended too soon");
|
||||
case '\n':
|
||||
case '\r':
|
||||
nl++;
|
||||
default:
|
||||
buf.append((char)cur);
|
||||
}
|
||||
|
||||
if (nl >= 2)
|
||||
break;
|
||||
}
|
||||
|
||||
String len = buf.toString().trim();
|
||||
try {
|
||||
long bytes = Long.parseLong(len, 16);
|
||||
_bytesRemaining = bytes;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Chunked length: " + bytes);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IOException("Invalid chunk length [" + len + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse the first status line and grab the response code.
|
||||
* e.g. "HTTP/1.1 206 OK" vs "HTTP/1.1 200 OK" vs
|
||||
* "HTTP/1.1 404 NOT FOUND", etc.
|
||||
*
|
||||
* @return HTTP response code (200, 206, other)
|
||||
*/
|
||||
private int handleStatus(String line) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Status line: [" + line + "]");
|
||||
StringTokenizer tok = new StringTokenizer(line, " ");
|
||||
if (!tok.hasMoreTokens()) {
|
||||
System.err.println("ERR: status "+ line);
|
||||
return -1;
|
||||
}
|
||||
String protocol = tok.nextToken(); // ignored
|
||||
if (!tok.hasMoreTokens()) {
|
||||
System.err.println("ERR: status "+ line);
|
||||
return -1;
|
||||
}
|
||||
String rc = tok.nextToken();
|
||||
try {
|
||||
return Integer.parseInt(rc);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(String key, String val) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Header line: [" + key + "] = [" + val + "]");
|
||||
if (key.equalsIgnoreCase("Content-length")) {
|
||||
try {
|
||||
_bytesRemaining = Long.parseLong(val.trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
}
|
||||
} else if (key.equalsIgnoreCase("ETag")) {
|
||||
_etag = val.trim();
|
||||
} else if (key.equalsIgnoreCase("Transfer-encoding")) {
|
||||
if (val.indexOf("chunked") != -1)
|
||||
_encodingChunked = true;
|
||||
} else {
|
||||
// ignore the rest
|
||||
}
|
||||
}
|
||||
|
||||
private void increment(byte[] lookahead, int cur) {
|
||||
lookahead[0] = lookahead[1];
|
||||
lookahead[1] = lookahead[2];
|
||||
lookahead[2] = (byte)cur;
|
||||
}
|
||||
private boolean isEndOfHeaders(byte lookahead[]) {
|
||||
byte first = lookahead[0];
|
||||
byte second = lookahead[1];
|
||||
byte third = lookahead[2];
|
||||
return (isNL(second) && isNL(third)) || // \n\n
|
||||
(isNL(first) && isNL(third)); // \n\r\n
|
||||
}
|
||||
|
||||
/** we ignore any potential \r, since we trim it on write anyway */
|
||||
private static final byte NL = '\n';
|
||||
private boolean isNL(byte b) { return (b == NL); }
|
||||
|
||||
private void sendRequest() throws IOException {
|
||||
File outFile = new File(_outputFile);
|
||||
if (outFile.exists())
|
||||
_alreadyTransferred = outFile.length();
|
||||
|
||||
String req = getRequest();
|
||||
|
||||
if (_shouldProxy) {
|
||||
_proxy = new Socket(_proxyHost, _proxyPort);
|
||||
} else {
|
||||
try {
|
||||
URL url = new URL(_url);
|
||||
String host = url.getHost();
|
||||
int port = url.getPort();
|
||||
if (port == -1)
|
||||
port = 80;
|
||||
_proxy = new Socket(host, port);
|
||||
} catch (MalformedURLException mue) {
|
||||
throw new IOException("Request URL is invalid");
|
||||
}
|
||||
}
|
||||
_proxyIn = _proxy.getInputStream();
|
||||
_proxyOut = _proxy.getOutputStream();
|
||||
|
||||
_proxyOut.write(req.toString().getBytes());
|
||||
_proxyOut.flush();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Request flushed");
|
||||
}
|
||||
|
||||
private String getRequest() {
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("GET ").append(_url).append(" HTTP/1.1\n");
|
||||
try {
|
||||
URL url = new URL(_url);
|
||||
buf.append("Host: ").append(url.getHost()).append("\n");
|
||||
} catch (MalformedURLException mue) {
|
||||
mue.printStackTrace();
|
||||
}
|
||||
if (_alreadyTransferred > 0) {
|
||||
buf.append("Range: bytes=");
|
||||
buf.append(_alreadyTransferred);
|
||||
buf.append("-\n");
|
||||
}
|
||||
buf.append("Accept-Encoding: identity;q=1, *;q=0\n");
|
||||
buf.append("Connection: close\n\n");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Request: [" + buf.toString() + "]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public class RandomSource extends SecureRandom {
|
||||
* thats what it has been used for.
|
||||
*
|
||||
*/
|
||||
public int nextInt(int n) {
|
||||
public synchronized int nextInt(int n) {
|
||||
if (n == 0) return 0;
|
||||
int val = super.nextInt(n);
|
||||
if (val < 0) val = 0 - val;
|
||||
@@ -54,19 +54,48 @@ public class RandomSource extends SecureRandom {
|
||||
* Like the modified nextInt, nextLong(n) returns a random number from 0 through n,
|
||||
* including 0, excluding n.
|
||||
*/
|
||||
public long nextLong(long n) {
|
||||
public synchronized long nextLong(long n) {
|
||||
long v = super.nextLong();
|
||||
if (v < 0) v = 0 - v;
|
||||
if (v >= n) v = v % n;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** synchronized for older versions of kaffe */
|
||||
public void nextBytes(byte bytes[]) {
|
||||
synchronized (this) {
|
||||
super.nextBytes(bytes);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* override as synchronized, for those JVMs that don't always pull via
|
||||
* nextBytes (cough ibm)
|
||||
*/
|
||||
public synchronized boolean nextBoolean() { return super.nextBoolean(); }
|
||||
/**
|
||||
* override as synchronized, for those JVMs that don't always pull via
|
||||
* nextBytes (cough ibm)
|
||||
*/
|
||||
public synchronized void nextBytes(byte buf[]) { super.nextBytes(buf); }
|
||||
/**
|
||||
* override as synchronized, for those JVMs that don't always pull via
|
||||
* nextBytes (cough ibm)
|
||||
*/
|
||||
public synchronized double nextDouble() { return super.nextDouble(); }
|
||||
/**
|
||||
* override as synchronized, for those JVMs that don't always pull via
|
||||
* nextBytes (cough ibm)
|
||||
*/
|
||||
public synchronized float nextFloat() { return super.nextFloat(); }
|
||||
/**
|
||||
* override as synchronized, for those JVMs that don't always pull via
|
||||
* nextBytes (cough ibm)
|
||||
*/
|
||||
public synchronized double nextGaussian() { return super.nextGaussian(); }
|
||||
/**
|
||||
* override as synchronized, for those JVMs that don't always pull via
|
||||
* nextBytes (cough ibm)
|
||||
*/
|
||||
public synchronized int nextInt() { return super.nextInt(); }
|
||||
/**
|
||||
* override as synchronized, for those JVMs that don't always pull via
|
||||
* nextBytes (cough ibm)
|
||||
*/
|
||||
public synchronized long nextLong() { return super.nextLong(); }
|
||||
|
||||
public EntropyHarvester harvester() { return _entropyHarvester; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
112
core/java/test/net/i2p/crypto/HMACSHA256Bench.java
Normal file
112
core/java/test/net/i2p/crypto/HMACSHA256Bench.java
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
172
history.txt
172
history.txt
@@ -1,4 +1,174 @@
|
||||
$Id: history.txt,v 1.165 2005/03/06 19:07:27 jrandom Exp $
|
||||
$Id: history.txt,v 1.182 2005/03/26 02:13:38 jrandom Exp $
|
||||
|
||||
* 2005-03-29 0.5.0.5 released
|
||||
|
||||
2005-03-29 jrandom
|
||||
* Decreased the initial RTT estimate to 10s to allow more retries.
|
||||
* Increased the default netDb store replication factor from 2 to 6 to take
|
||||
into consideration tunnel failures.
|
||||
* Address some statistical anonymity attacks against the netDb that could
|
||||
be mounted by an active internal adversary by only answering lookups for
|
||||
leaseSets we received through an unsolicited store.
|
||||
* Don't throttle lookup responses (we throttle enough elsewhere)
|
||||
* Fix the NewsFetcher so that it doesn't incorrectly resume midway through
|
||||
the file (thanks nickster!)
|
||||
* Updated the I2PTunnel HTML (thanks postman!)
|
||||
* Added support to the I2PTunnel pages for the URL parameter "passphrase",
|
||||
which, if matched against the router.config "i2ptunnel.passphrase" value,
|
||||
skips the nonce check. If the config prop doesn't exist or is blank, no
|
||||
passphrase is accepted.
|
||||
* Implemented HMAC-SHA256.
|
||||
* Enable the tunnel batching with a 500ms delay by default
|
||||
* Dropped compatability with 0.5.0.3 and earlier releases
|
||||
|
||||
2005-03-26 jrandom
|
||||
* Added some error handling and fairly safe to cache data to the streaming
|
||||
lib (good call Tom!)
|
||||
|
||||
2005-03-25 jrandom
|
||||
* Fixed up building dependencies for the routerconsole on some more
|
||||
aggressive compilers (thanks polecat!)
|
||||
|
||||
* 2005-03-24 0.5.0.4 released
|
||||
|
||||
2005-03-23 jrandom
|
||||
* Added more intelligent version checking in news.xml, in case we have a
|
||||
version newer than the one specified.
|
||||
|
||||
2005-03-23 jrandom
|
||||
* Added support for Transfer-Encoding: chunked to the EepGet, so that the
|
||||
cvsweb.cgi doesn't puke on us.
|
||||
|
||||
2005-03-23 Connelly
|
||||
* Fixed Bugzilla Bug #99 in the SAM Bridge, which caused pending
|
||||
stream send data to not be sent if STREAM CLOSE is issued too fast.
|
||||
|
||||
2005-03-23 jrandom
|
||||
* Implemented the news fetch / update policy code, as configurated on
|
||||
/configupdate.jsp. Defaults are to grab the news every 24h (or if it
|
||||
doesn't exist yet, on startup). No action is taken however, though if
|
||||
the news.xml specifies that a new release is available, an option to
|
||||
update will be shown on the router console.
|
||||
* New initialNews.xml delivered with new installs, and moved news.xml out
|
||||
of the i2pwww module and into the i2p module so that we can bundle it
|
||||
within each update.
|
||||
|
||||
2005-03-23 jrandom
|
||||
* New /configupdate.jsp page for controlling the update / notification
|
||||
process, as well as various minor related updates. Note that not all
|
||||
options are exposed yet, and the update detection code isn't in place
|
||||
in this commit - it currently says there is always an update available.
|
||||
* New EepGet component for reliable downloading, with a CLI exposed in
|
||||
java -cp lib/i2p.jar net.i2p.util.EepGet url
|
||||
* Added a default signing key to the TrustedUpdate component to be used
|
||||
for verifying updates. This signing key can be authenticated via
|
||||
gpg --verify i2p/core/java/src/net/i2p/crypto/TrustedUpdate.java
|
||||
* New public domain SHA1 implementation for the DSA code so that we can
|
||||
handle signing streams of arbitrary size without excess memory usage
|
||||
(thanks P.Verdy!)
|
||||
* Added some helpers to the TrustedUpdate to work off streams and to offer
|
||||
a minimal CLI:
|
||||
TrustedUpdate keygen pubKeyFile privKeyFile
|
||||
TrustedUpdate sign origFile signedFile privKeyFile
|
||||
TrustedUpdate verify signedFile
|
||||
|
||||
2005-03-22 smeghead
|
||||
* New TrustedUpdate component for signing/verifying files with a DSA
|
||||
signature.
|
||||
|
||||
2005-03-21 jrandom
|
||||
* Fixed the tunnel fragmentation handler to deal with multiple fragments
|
||||
in a single message properly (rather than release the buffer into the
|
||||
cache after processing the first one) (duh!)
|
||||
* Added the batching preprocessor which will bundle together multiple
|
||||
small messages inside a single tunnel message by delaying their delivery
|
||||
up to .5s, or whenever the pending data will fill a full message,
|
||||
whichever comes first. This is disabled at the moment, since without the
|
||||
above bugfix widely deployed, lots and lots of messages would fail.
|
||||
* Within each tunnel pool, stick with a randomly selected peer for up to
|
||||
.5s before randomizing and selecting again, instead of randomizing the
|
||||
pool each time a tunnel is needed.
|
||||
|
||||
* 2005-03-18 0.5.0.3 released
|
||||
|
||||
2005-03-18 jrandom
|
||||
* Minor tweak to the timestamper to help reduce small skews
|
||||
* Adjust the stats published to include only the relevent ones
|
||||
* Only show the currently used speed calculation on the profile page
|
||||
* Allow the full max # resends to be sent, rather than piggybacking the
|
||||
RESET packet along side the final resend (duh)
|
||||
* Add irc.postman.i2p to the default list of IRC servers for new installs
|
||||
* Drop support for routers running 0.5 or 0.5.0.1 while maintaining
|
||||
backwards compatability for users running 0.5.0.2.
|
||||
|
||||
2005-03-18 jrandom
|
||||
* Eepproxy Fix for corrupted HTTP headers (thanks nickster!)
|
||||
* Fixed case sensitivity issues on the HTTP headers (thanks duck!)
|
||||
|
||||
2005-03-17 jrandom
|
||||
* Update the old speed calculator and associated profile data points to
|
||||
use a non-tiered moving average of the tunnel test time, avoiding the
|
||||
freshness issues of the old tiered speed stats.
|
||||
* Explicitly synchronize all of the methods on the PRNG, rather than just
|
||||
the feeder methods (sun and kaffe only need the feeder, but it seems ibm
|
||||
needs all of them synchronized).
|
||||
* Properly use the tunnel tests as part of the profile stats.
|
||||
* Don't flood the jobqueue with sequential persist profile tasks, but
|
||||
instead, inject a brief scheduling delay between them.
|
||||
* Reduce the TCP connection establishment timeout to 20s (which is still
|
||||
absurdly excessive)
|
||||
* Reduced the max resend delay to 30s so we can get some resends in when
|
||||
dealing with client apps that hang up early (e.g. wget)
|
||||
* Added more alternative socketManager factories (good call aum!)
|
||||
|
||||
2005-03-16 jrandom
|
||||
* Adjust the old speed calculator to include end to end RTT data in its
|
||||
estimates, and use that as the primary speed calculator again.
|
||||
* Use the mean of the high capacity speeds to determine the fast
|
||||
threshold, rather than the median. Perhaps we should use the mean of
|
||||
all active non-failing peers?
|
||||
* Updated the profile page to sort by tier, then alphabetically.
|
||||
* Added some alternative socketManager factories (good call aum!)
|
||||
|
||||
2005-03-14 jrandom
|
||||
* New strict speed calculator that goes off the actual number of messages
|
||||
verifiably sent through the peer by way of tunnels. Initially, this only
|
||||
contains the successful message count on inbound tunnels, but may be
|
||||
augmented later to include verified outbound messages, peers queried in
|
||||
the netDb, etc. The speed calculation decays quickly, but should give
|
||||
a better differential than the previous stat (both values are shown on
|
||||
the /profiles.jsp page)
|
||||
|
||||
2005-03-11 jrandom
|
||||
* Rather than the fixed resend timeout floor (10s), use 10s+RTT as the
|
||||
minimum (increased on resends as before, of course).
|
||||
* Always prod the clock update listeners, even if just to tell them that
|
||||
the time hasn't changed much.
|
||||
* Added support for explicit peer selection for individual tunnel pools,
|
||||
which will be useful in debugging but not recommended for use by normal
|
||||
end users.
|
||||
* More aggressively search for the next hop's routerInfo on tunnel join.
|
||||
* Give messages received via inbound tunnels that are bound to remote
|
||||
locations sufficient time (taking into account clock skew).
|
||||
* Give alternate direct send messages sufficient time (10s min, not 5s)
|
||||
* Always give the end to end data message the explicit timeout (though the
|
||||
old default was sufficient before)
|
||||
* No need to give end to end messages an insane expiration (+2m), as we
|
||||
are already handling skew on the receiving side.
|
||||
* Don't complain too loudly about expired TunnelCreateMessages (at least,
|
||||
not until after all those 0.5 and 0.5.0.1 users upgrade ;)
|
||||
* Properly keep the sendBps stat
|
||||
* When running the router with router.keepHistory=true, log more data to
|
||||
messageHistory.txt
|
||||
* Logging updates
|
||||
* Minor formatting updates
|
||||
|
||||
2005-03-08 jrandom
|
||||
* More aggressively adjust the clock
|
||||
|
||||
2005-03-07 jrandom
|
||||
* Fix the HTTP response header filter to allow multiple headers with the
|
||||
same name (thanks duck and spotteri!)
|
||||
|
||||
* 2005-03-06 0.5.0.2 released
|
||||
|
||||
|
||||
29
hosts.txt
29
hosts.txt
@@ -1,6 +1,15 @@
|
||||
; TC's hosts.txt guaranteed freshness
|
||||
; $Id: hosts.txt,v 1.128 2005/03/04 18:37:39 duck Exp $
|
||||
; $Id: hosts.txt,v 1.136 2005/03/29 20:49:49 cervantes Exp $
|
||||
; changelog:
|
||||
; (1.158) added tracker.fr.i2p
|
||||
; (1.157) added v2mail.i2p, complication.i2p
|
||||
; (1.156) added lazyguy.i2p
|
||||
; (1.155) added confessions.i2p, rsync.thetower.i2p, redzara.i2p, gaytorrents.i2p
|
||||
; (1.154) added arkan.i2p, search.i2p, floureszination.i2p, antipiratbyran.i2p
|
||||
; asylum.i2p, templar.i2p
|
||||
; (1.153) added feedspace.i2p
|
||||
; (1.152) added wiki.fr.i2p
|
||||
; (1.151) added septu.i2p
|
||||
; (1.150) added music.i2p, rotten.i2p, wintermute.i2p, kaji2.i2p, aspnet.i2p,
|
||||
; gaming.i2p, nntp.i2p
|
||||
; (1.149) added cowsay.i2p
|
||||
@@ -347,5 +356,21 @@ kaji2.i2p=kbW8N9ZMxbpLjR4EjJuc7GYlKIp3Z4rXpMwAU07-CoyndW3YNUftvKi8PTyg1FMVrf-7IT
|
||||
wintermute.i2p=o6uDc3O3Esz0RizDwJ9ty5j4j3XFJhuh~d9zFvQqLojjq0c4a5nrXsop105DRBwFJ9h3OpOoWA8wXZUtdgqcvXoQXH-LmiApfyzrNLnbBzlwZGmd1LEWZEUMHo~EiU7JsEkE8BylLEfYXXrcqaMC3NxrM3gIwIieCRuxfIOzlYN-hIORs9-rqBurSdS4aBuIr3pz4H7pH43w1RgxiLKaFN5C9qbaDVDzhGKr35J-frRDApmlsL-foUYuFM7olmyleyG-C0DBskvJIoIfAPRvm25LtatOqr79dIA62gJOLo3SuifndMSwLcblNkzlh~H8kD4Cl~o5qCyE0sx96I7d-QkURVvKnbTJKc-uc6J3fgA2KByAXWmDfJfXb1ArlTcrE598FbjPfAGrmd8bvDAQZlABneuy190z08Ati1YGKId7Y-9MrmCcTTAFC08J6rgmhoeIA1mpE68WGz~xMuU0zwvT4K2czLWq4IRlmZb2o2RsxEGulDZuzH4ozxXq~CiRAAAA
|
||||
rotten.i2p=ECnru16NdLMkgYzmtMgz1~mg8eIo~5FJ8x2LOj9f7NLx-8Me3lgyAMpcYuoohN08hNJYI74rvvS0mS67f5tw8j9CkFc0mPqUQN0g-RyHK9QWq8kghstnoexr5P2TjusQ3n99YLFujlbUDBTsStP-OjRxSGO8Ho173STIZeVlkPILkttm6cfHrFEcrd1MZrYFRlbrY-yv8ipcEh2ymx0bdII~8Htc1~hLIZyeD4G7FMVWyIxSQwL7Wgv3JB0BcBkq-1oB699uTVXemGizq4VTjywzktR~gj98gvyPrff3KnNfL2RYxruxb3uWoVRHlBQadk9W-5pZyVsaPsn8LuCluB2ax6KdxL-Uj98zavn35YiMIsr3g~aq3aX885lV9Oc7Dndq68zfuXsMo5yF~158XylkykWR-fykTcR-abVgScSNa1AFgHVXZnbOEA5NXUG6C0AYBbOjQ~rFXNZKnxQOljWJgPIWOsZYI2CLTbaz9tAUXZ6t1qXBJOlxAQCW62DfAAAA
|
||||
music.i2p=sE8lmVzZgjq6bLbS89c8OCrsJXbV6qmxtBIb1289NZTw9DnwNDosmgfQO9g9txB28FZVLJ09aTgRbDm~D4BQNMRfF1kKgIyxSr0x-VTMwX58-9bB~0a3eGrHHT6OS7jSuOgHji-uqylLCc2JiB5sIApO0wgQ4GGas8mKyGyqUExiBhoNybL8y1Ks3qC97x3x-7U8ZMzU-7~V64sfAWdN7qSK0UdHK20kyFBCD8KcMKG3Ctl3SX3B8NYPRHnFq~l8UpXpTY2dWHmjceSRUY4FrXJNju1nfogfX2gv~r121VD7Hn3tyNX-Fbt0vOIsUJut1d9RjVCwie7dCiudf3Rf-S7nMEVmQzyaQzgbFZl27MRmEV5KHjOHVZXpwOFAes8uXh50BqjSmkTb2QfHxe~9Qb-mw~~oF9G95EQHe3H135d7YXJMepjUcju8UUoOzX94nSRWq5XCJdDrd-5K~oMIQs7GoIJxDzWLV9P7baSHbvRlRnZ7jCeyda4kDFbB0UTQAAAA
|
||||
|
||||
septu.i2p=PAMLJCc4LMNO3lULWpWYQR6sLwLuRdDmBxCLrv7GN32hfsMq3y18VJ9RdTmk8YZjuF1be6udYsadkgzXVyAUJ2yB~ZcuJiVr4crYNhGcQQ582ZLrqyye6rfNMxkKmg1JdZhOilJRn8-N-9Y90hWcc77ClNrFHmYE3y0ZSffMQuoPNXx7Ra03xBXfXxcYmIyslBNrbbBqZaqQktKXK9HL5PVlRoGCw1xNvMU9lCxZfdpKJSogljsg0sledwVl2p5DwhWhAGVzDYihcZ~PY6QAyATVhmWIpPk691geBPfpleKauwuj9~ZneRbMN5Lh0qsz1i-XG1zvGn9NDTbkdVAXu4Uop-DC-gN5aYYI-bVQtHVTSl9N2eCWorPYTEhsi6plOR4KXnoD3s3yzu4pphQGv0r8xLCdX4~wIcXJnIXW2W7e5hEzYqSe7EzuUw~PUEwskYqspXOLNzGzsK4r8k2X2fNB7LYLHqwWkKozkPmce3wti1XxyqoD0Ndq~JPJQzTcAAAA
|
||||
wiki.fr.i2p=zuKLG6BVohlrgsNhIhG-SPxhsiaXz049s0GC9bnFAaKiWaXEOXH75m4wSF1e8kJkXfSvK~PHaREj7ZQxql-wtaFM9IXIQX3Wk2rfkdfGj-eWuMlh7OxRpqftDhWk36EDFua6Hs17d58uluipy~BI3i55LLgiKz23qprunABjL843zAWD7Oq3NHxe7jorIz7c5Sx5D8~jxbQOxwhMKVrPpOs~cQU2sqftPX0nrkSh5pBJxhGbGKIRdzHCAlSgGy~Ydf5DsBH1k0HpHMks49JFX~JzXJHtCEJxaMVrSnDCh9nXUJF5gGpCXhI9aXTFE6LzCRkxsazbPy0gHqyHNZ2nupt8LqN~ZJNxAJxtPyC79ikWTxE4akk3CrEGDYEgHGxrEATrKpMfMx9f2MqDKbV-8BJVdkpFk4EtkLhfVJjYgc1qxH7kFEG9S6cm53Oopaz7CZW-0PR0vbZzCMfyJrR0ExkqmmbiHrXnryrKvB2bJ8emsmTBvguvqO7qUiAwzIQPAAAA
|
||||
feedspace.i2p=KuW5sR2iGCfnnuwdslHbFsNyNCsoZnoIwAmHeypOV-s8OQxokBpdNazksBrhoQum9nv81vprl6k15Mhcd~KWE4OajjmdU7v2fjqps7QK3KmLv4UTrX-ihSIUdhb5B9FLh2XEFEQ4-8guFTVxBRqQQE~c058AL6~uZpuFpLtEOg0HEZ6BydndOhx-FCDm8ip12pPwZ3a5O86l1UoATZBXxoctGafTjnUlx64jyQs6y0WB811l36wVrc~~dqEcanxab0yfg8dJ~1M4EUNrXcHT-PwYYrr3GgpimuF4oUtYjkeDKlq5WjfMAa8bE73HFgquxq99fuW5aI1JbLPxnTLHi00-2On0dSDwJxSP08HOhKFKMNzykI9Asg8CywzNO6kWpbX9yaML36ohCJF0iaLvvDyhS4a2B65crSJRJPVkbxIvsyyUyYMGi31EK593ijOLjOvugglxJoRizUehvD8c6KCpLLSjCc~ASoMbitolOXq8HCeJ5C9uSgUxKhZVk4~iAAAA
|
||||
antipiratbyran.i2p=K~sebW76Q4eiNalKMrYrAs6S8Ctnfs-MVmMVfGeo1-9fMI7YhSBBZ9SI9zkNiur5MGBlqZvIY21-nP6RObwj4GGI9OxUXJXQ9gO~FMuuluGelDrb47AfJwhb0XPFyUQWXz2yYcsJkUQRikuNG~gy-vm~yP0847dw7nBsLOtnaiyD3LndwNKO7KLFV8lDWniu7cHENJ-srQrdxy9DfbTc-M1GjzL2qKrg8SF9DSahkNGp9rG~7rykzFlqisJkaepcBnB0MEAb8kzFXiOIz1HScf91CDdyS0nUClA0GLVF7WGftA~1Coih0I7bWLl27UyfoV4DA4-JhwB6yf8CL8k2hn9vWg6zBK1AifaG-ha8tmZvd1ZCoar670WSb2~dtILxMHvTeDEEuKyzTkDZIkJ8bqstB0guYwjkH98GANeUQCD23CRpmm8JW2tY80N6zt~KAlIq436m6SkhOgZ2cOPs454KgtDsmEV7vqlL0xS-0jlb9HHwouNga50Q~txhcUXTAAAA
|
||||
floureszination.i2p=XnaY8f3pEycYWVdtYH21H~PgT3StiASiMvCHwDIQAVVnAswUY6L8sHr2nGSyCSjEWspgCj8kjwO0zwzgdq5IYLkwzPVTY~quYv3xBFVkWXDz37WM2cJ8rmaI0AVBmalh5v3WFdCKpMsn78TL0cF-JvnAw24SPbDfVTCG5AwaWQ1dBCscfVLkP1JI1mckSp-7Ld9S~AN7Uajw4NobAkaaSTjqHK3adeuOekHqi8iwGY-DEcmuSNAbJ4OVZlLLZeg~Mqp-jukEQPbcxhOlEM~JI-8nfjt1fTvxmGT7LtNVYpC2qoofZ6CEYPqOZRduacHiW0mGQWdm7BrcGmjMWIr~eUk8XabgpOb9BHCjciZdiTrgtx~VmwETSgKceNfFGkHNTkNwrqmHz5rQhTPx29~mp5GNS6XG4dhgNlhYXeEOhQnPPuTo~Wa4giu83sNq8OZJZqcWeTSa~P2NLvxqdMXFfHcD5jjZjB5VyEHdWXk5Vja97bWHCVhxaK0HZ~wcGoafAAAA
|
||||
search.i2p=9GIzUtJ7aQTTPsQtMFQ-RFzKENhODuftOcD9ZyQig6bkuYND2MNMPmcSO8YjtN74MKBjpOc2XyZIj3wy869ChLbwSVXj5H3ZKVY6H3Fxq-8BH8gFU91EyNIKvFQbASqfDBDIvMAWYQItq5oicXyb6T-hRY5nNnRO7XtxIDn94S46jqWVocb2YEaO2kKqABwRBv1HEoWCNGG3nuUAIcYZkeCuEgvsL6q2Us3te6RAz~Dpqv5EIVbsiEA-uxYUmJHVFP4NwchV53~TLQMXBZv6C5y89N~pFoO-7MGeWl76tI0i~FDlbuAvZWHIeHEIgMCZV45f~QVHGz0LR1PW51KobDpyrjzgSvvWxhPCCCpj0fKA0~wf4dXLbKo9XzgLKsspEH6-MJx6D0LJBjzsEtllzYQ96JpfG-yrqAHhz1pbL-2mfuJEBBu1d0VxypQkMNN9U-ZZL3~04CNidL1XPgU7Z50NsSxdJAj6lHqzt4b8RweXSm7zv3NsJuBR2btuyp6GAAAA
|
||||
arkan.i2p=wxq-X1NM2u~K7w9tSjbOq6QNvvXJERI8rDq0qSj1yCgytNnb52RuHPoWsTbkZ6joJF1WS3Xm8WfwF7ceYg-uvuTWhW1uytJZBFbvkhP6~Z~OzUUMppiP2AWboq0awD5Dw3JQN9RWSRClEDgzxUSJ2U0Y3MZTQdCyPNF1-gIIMrppxFtLzTbTDY7XBnDd5yvYEXNm1ftp~EGiEl5za1Y3txTLTwC~oNoSWD901WttKYU9DiL35ZZFhJU~1cbK4R9~uIRYhow3rV~8IpCtmSjwE942-qcS5rDdgD06L~QqHbE64KieEm-WBAm8wgh4EX5VlXZTgp1bcCwrn-Vn2DTQ1AUJNLVBiOc90irsN4WXMgc0Subuk9QaWrqk~bc6aPvM02m-Muw1MKaXdMgSsflS9di2OX4kmGwwiO8eUdsH0laqOnV1Jdg1CM~CmXz1pgkV8SsqkGNKHiNDMlfyqEiOrOsGg9T9Kcm7fdXdlCM-KV3xTpaT-lnKY0mO1yKklh7TAAAA
|
||||
templar.i2p=VlRjolSuETmcCSiSEan-NxU5uZAbyn~BSt2mqKhdiF6ppV-dtPZnay5PmzpOULJoMOqjTZGNd4Sf7-zMKtZBB9UI7aBIQG9LsfeGF-JlK1jVkAkuw291E66RlrYQdG4nD~4mn3wXiwkaswb8rHleruTpCa6~tJx-gfK6YVclro5DA1s1AvLZYcgQ3bv7S7n5jNXVWLvvzJSnT~OJ3x9tHOaRdJPdFduVXBn2zchj7KEPZya9XJgwbsOzJtuZArP2Pxtodtta1C8AgS09r2dy0EO52Neb1sHS-wnG~ITcPnqbgGqM0hiP0nYk7Kg-PfWH5jaP2w0yhmHh2OEOMEtWTWAGQW9f77I-MBOG0nLASKQNhFZkKLxdf76Ab-xEX3AVdc~rPqmxuB0osCTAfUnAYtE7PxfPP1jt1cVw4lA6n-~eJTTwE-6-fBcclt1ptoCwXXxZWcX9umtkg8TsvPkUveI8K6kNwcnPU57m0cjb3dnj-62CQh5VuWsBxVyAPpaKAAAA
|
||||
asylum.i2p=97-3nsICymUlwgSQTEhO6JQowiQ5IcY82DPQ3lrxaf4Y1VigIDAd0Ny36Fen0XfEC3R80WxOdLAALNiS-nh8XWhY0flmvTNt9Wd7Oz~KHXyjxiPQnC82oD2mRYq2euUcJm~GZ1NU3QzB6-RBAxTj4zLGbeJ9e3fRoijwWL2prAdgV19MCurUfQ346BbmZG9e~EfAE9ZLKVRa7eILV0GWiyuvVMA~Z8bJReJdwaHzkCsKybC9a-HvwAjTnDq9fywC43yEGOTBI~4nmDz77pNKvgUykN-MLZfqqRzgrbmf91kLxlJ0Mj36cs-yx8ZTyfTh1mti3Ys0nEqnxyxD~1jsFD09y~6A00khU1-ptepKQugfxxJHL264yGTd4dy7dL9r5AqPiS14CvvwMS9QpdoUgOD1xazquB217kYVpO6tPPt3aqZ2h~w1WgrztG0se3uI7UE9bl0JkfZtgpGblVzIh2lBx7uSh9b9d7-rnYT4d-buV~Va1T9mKQQRQBc1Tp48AAAA
|
||||
confessions.i2p=hgEwASKmmhKk0a0U9Wu2dVno7h6eZVqC3wdqWvul~Td47taUBEL6N6H5uQ2~nFcwcEYrCgJfhDoaBBUDyE6kpwCJU2xTC96F4YcBCIQhM3JVZqvKNB6QZGJd1oyGOv5JWq87K8ItSnXBazczdRDFknL0xa-f2oaDddTp9lz00BU1hpgvd9K93SMeZR7Bfq7VtfyvxyBA5S10cfNljbjk6RVVEqesHV-SqSyL7jSavzGVPIXERokb5xahosRkf7iGFsh5e14Wu9wPapsxWlKGCKiAhBqU-rVH-skENq-0se0sMcItilGaIeGeS2horymamv8VpXNZkIB0ikiEN2z83jb2Om6W33r81MXsE~gM3XQQ534ausN1LF7KM3~QWs5e1ElycRhBjmyvkZkwjR-jqZPjAFuJxmX-xC9QqI9pkOhPPD6exlptdEx6L6cJzJJSqFZHE9wGw8aCiKKHsH7eS9O85DEQB58FT~3TxHnTBYRwbBMfnP~Wj89AFwPXVpX7AAAA
|
||||
rsync.thetower.i2p=FGylQtqVT-2~~aXTCtTR6sKURHvuT7fcUKW2eETpUHU~yahtXDamexxJY4fZQoSLqHIQC1RCmrixohxKjYLjXJtDlIX0uqADcEEqHZ1odc01xKUm1gHvxUgnxLQdlFHj0cH5de2fOT9Fiqkl54svaJyKbGi3WN7T0jRQqqH6ZuG5XQA90c6cfmLOpdK4hXdebm6YXrVJKxFirZ3rAKKlaFjeD4Wvgp4cTGxTXOzwdO75ZUmebRE6rpL1z0927MGg~hyOFbeqG7naje7RBe64PjTwRj46DH4EdKTT8NlRn5M7-qqTb8A2GnXYKrGJOpfx9HGSMxBSf0P2Usrpp7BamHJuBmLU0SH~J19SLq-SiOlWwrNgQzgKJoJ2J6zSOgZqFhBNnNtySj-zkOeC6ODN9QXxzHSZWzF9m4XwGKcrdLsZ8pxVIXMBqQY5BfZd0bigupwENVjEbMGPxSEDtffFeEKjoLybRUEcLB1jcn~FlnhWXL0tUqIEdfgDQ7VxAytTAAAA
|
||||
redzara.i2p=X11wIDxPBZ5p08Eifivuv3-xDW~k4CMyjBky2xIyOgGNsNfktxSGWNQ-cjhVYY1-FLZheX~8SdUBEa8FGb20jKtkfsKBmejbkZFhpQ53oHUITj5kN5WPcr06ORB~Ofska637DSzJs5ZVLQb13wNVXqtuKc-JDJa~kanQPsc60-D~Vu7LKKRYMKsUJx5xz9knr2TaLdDmGlzjOcbII4dw-7WBhydLEm9DhEHuaCReuoT3nSv5aCRyXuzf~PcBrT1gmoTLEAf-GxxZdGqNf5GWQ48IzZUVoR3NR1lKS4JbQNBfhC6S~~FrY-2~u5RRwF6HrNQfYEdKmnQqf6euahOpkD0pEMIqQdkWG2L2DZP9BWZBebxQxpv~4FNeRj-7ltpeipiut2yx3QSDLXrvWtkRRRcx2WugIp8svaR98e5yZ1nztx4H-LE7BbuNeIlITjo7lG-ddRspoJ29G1neRoQdYMhpFHFaNZfNWvPDu9DQ7w1Ryn-n1YZWvfFUYaMUeOYOAAAA
|
||||
gaytorrents.i2p=uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA
|
||||
lazyguy.i2p=jVkB2blHl6nNSI6rLxNcim5mPrwR5vnMFBFrZRcsZxKFRjbbNBxFqJd5aZmJyykUbpYYj0HZ3acGIL880Klfp4ufhDof1oBR1SObm~qF7ussK7ME9-mB3rO9XS7KHFLgr~Nr95uTqCn~PECE-kMEOb42ZXzYvKOhnTQS4xjrZVP8hE-8MowjINVglAexk7yAVWda3gU-008N5ycuD-H3x2vlK~izZmfj62UbgVhmydJ7bERYKkA1221LbyAGUMA-bS1oe7~-mJugz3o1mlLbn-vWqY5-GeByiuip1ollddYxk62yYUG0jugH708~21NUw6SutxKleDOEvkHmJDoO1B6rEFT~N7MH-V4g0wetQdaAZrVb4tdDko9K4Lmq69vln3su9egPx5d8gBLgKOtMEhoGvuNM8t-88TTGGaAvRjVDnP4QpiubKYCPs2l4GzeLTXspaRrk~M5Kwbjg9IgDrNXclZdBGApC-oF8di~ZHOuxmMv8lXTBX3KyNRb-xwZqAAAA
|
||||
v2mail.i2p=FE~x8chFTUEGogpe8aaDBYES0gnm072OCMJ2M9F9Dt2MZgLsogZH8UDSPdu59QHW695-qZR5CTzSc3IZNTNlPJQRb5EjMXAIYkrvVcghaJ5-QUVhhIE8iXNwcogJclQBP-rDK~CrdXp0FloAczd~WSk48vOjApRVhZFte~GZw~rL85ZZw3-27DFZh77wc57E1y~7MKXNm5PuEF24VMowp0Qnb9WPcennJmTEqNMPSs3-vNRytAASkVN6~Ign7tApUHsvHpFcRkTiERBRDuHOpUAjBGZmBW1mQxPYqueMEKC7nZu3Pxgp3Pzxld-AOrl0wDAbg~uggoWz-edyTKylj3V9JT~4lsBbKnf2hK2LMJ4XOCqv9svlpu7E6bvQopvWy70hHzhest52OcyqhGxIkRf~jozHAYH88rRoOAW~LRtqOvCrqbkSTkT8uAvRKcZDDQGBHU8S~KBzp2Cejk1gljmQdBKCeuLJzr8uq3IgrL1J5GKcrnFENaJNd~BbLvDhAAAA
|
||||
complication.i2p=oGGGEsxv4TDMS-hCGHDbzDJC6f0R~4NbrPHxPdemxBAJ8JQQR34R6NsY4ocLc-lUqftIzDs7EAR~Qtkj6ixyGgrN4S7~XdoJZQPUCtC2zfiUSeehArzDNGBe~rCF2UzT-bCCejbSG57vE4i4QzI84-c9CEUww56veR2VdCULktv32P156LWlnDO26nyZsWj88sAgcBTwxZC4Lh8ms2YnQLyVJwIRlzX1wUwh4H8nlm0buUHx3tr9raIm6Ru0ORk2LGQss1g3rEVX5hm48S2vbvG9OT5ggghfYrjyJ6CZeIJmPTTYyMJfEJAvEzdlg0YvROd4LjQ9T5DaO36cHtFUYSJPuFeCvR9AWBoGbreZfrN1Q3mJ1g7TnYFbaLYYOC2Aj9DOnZyGaGoFCt4iMP9n6kZ~ktqOrwT-b6evLucxAxfMX6uLCsnwjY5GHywT9opsxtp-XXG4j8cbtz43SBQKurI1NWBrUwLvFqLqJMzJwMLtGrPwt-mH6ysvhzqOTEIoAAAA
|
||||
tracker.fr.i2p=608f0LeGqSCul8ppxGNMROY5zndsC99Xrm9x5HsySsqusoDw~7UWo41UkOM71qJpFE5XyphFmKNTtiHp7TvED-QFgRyDVQ656R3R8nMuxIO5NSkk-J7ibSYkoDI0qB0~-sWzyC0dDmoZ313ko0szuc9ORzxcejHKXftP4D4a35Q82TxWNn2jeyCf3YlkPSH3YMkJJ-2EvQxBp98vUpmWU-pN~eQdowHBIhkTxhh3jIpnv5ShHD6YeFYYEZlEHmYHHm85C9f1CryuKS2qq5owrafPUpF4ZLTuT6bOE0go8m0FYfWg0XpcP95felXN20~Fq2JQJFqz3Z9YYhfQPScsDQ4bwFH2GihltoegoNSdBp7VTxja4H-lCOQAOGW1xWTrvGuknTfJK2fSPrSbds2KgWdgqzP50ooxblBytAGWjzbkm89VtG7dn3xJBXoIjpSBp4IpIbER83pbfccvdJE9y24AOXQK-VDwmpP6ndq0QnQDNwsrxy-daAk1DRZLbGrSAAAA
|
||||
|
||||
|
||||
14
initialNews.xml
Normal file
14
initialNews.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<i2p.news date="$Date: 2005/03/24 02:29:27 $">
|
||||
<i2p.release version="0.5.0.5" date="2005/03/29" minVersion="0.5.0.4"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
|
||||
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000652.html"
|
||||
publicannouncement="http://dev.i2p.net/pipermail/i2p/March-2005/000662.html" />
|
||||
<i2p.notes date="2005/03/29"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000661.html"
|
||||
publicurl="http://dev.i2p.net/pipermail/i2p/March-2005/000661.html"
|
||||
anonlogs="http://i2p/Nf3ab-ZFkmI-LyMt7GjgT-jfvZ3zKDl0L96pmGQXF1B82W2Bfjf0n7~288vafocjFLnQnVcmZd~-p0-Oolfo9aW2Rm-AhyqxnxyLlPBqGxsJBXjPhm1JBT4Ia8FB-VXt0BuY0fMKdAfWwN61-tj4zIcQWRxv3DFquwEf035K~Ra4SWOqiuJgTRJu7~o~DzHVljVgWIzwf8Z84cz0X33pv-mdG~~y0Bsc2qJVnYwjjR178YMcRSmNE0FVMcs6f17c6zqhMw-11qjKpY~EJfHYCx4lBWF37CD0obbWqTNUIbL~78vxqZRT3dgAgnLixog9nqTO-0Rh~NpVUZnoUi7fNR~awW5U3Cf7rU7nNEKKobLue78hjvRcWn7upHUF45QqTDuaM3yZa7OsjbcH-I909DOub2Q0Dno6vIwuA7yrysccN1sbnkwZbKlf4T6~iDdhaSLJd97QCyPOlbyUfYy9QLNExlRqKgNVJcMJRrIual~Lb1CLbnzt0uvobM57UpqSAAAA/meeting135"
|
||||
publiclogs="http://www.i2p.net/meeting135" />
|
||||
<h1>Congratulations on getting I2P installed!</h1>
|
||||
</i2p.news>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<info>
|
||||
<appname>i2p</appname>
|
||||
<appversion>0.5</appversion>
|
||||
<appversion>0.5.0.5</appversion>
|
||||
<authors>
|
||||
<author name="I2P" email="support@i2p.net"/>
|
||||
</authors>
|
||||
|
||||
@@ -18,7 +18,7 @@ tunnel.1.description=IRC proxy to access the anonymous irc net
|
||||
tunnel.1.type=client
|
||||
tunnel.1.interface=127.0.0.1
|
||||
tunnel.1.listenPort=6668
|
||||
tunnel.1.targetDestination=irc.duck.i2p,irc.baffled.i2p
|
||||
tunnel.1.targetDestination=irc.duck.i2p,irc.baffled.i2p,irc.postman.i2p
|
||||
tunnel.1.i2cpHost=127.0.0.1
|
||||
tunnel.1.i2cpPort=7654
|
||||
tunnel.1.option.inbound.nickname=ircProxy
|
||||
|
||||
40
news.xml
Normal file
40
news.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<!-- this comment is filler to work around a bug in the 0.5.0.4 updater, where
|
||||
** it would try to *resume* instead of overwriting the old news file. oops.
|
||||
** so, to get around this, i need to babble on a bit, otherwise the
|
||||
** oh-so-interesting text below wouldn't be seen by the router. still, i
|
||||
** think the updater might not get the updated announcement, but perhaps it
|
||||
** will. Or maybe i should put in a message telling people to delete their
|
||||
** news.xml file (at least, until 0.5.0.5 is out)? hmm...
|
||||
**
|
||||
** ok, this is getting a bit tedious. come hither, ^C^V!
|
||||
|
||||
<i2p.news date="$Date: 2005/03/24 02:29:27 $">
|
||||
<i2p.release version="0.5.0.5" date="2005/03/29" minVersion="0.5.0.4"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
|
||||
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000652.html"
|
||||
publicannouncement="http://dev.i2p.net/pipermail/i2p/March-2005/000662.html" />
|
||||
<i2p.notes date="2005/03/29"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000661.html"
|
||||
publicurl="http://dev.i2p.net/pipermail/i2p/March-2005/000661.html"
|
||||
anonlogs="http://i2p/Nf3ab-ZFkmI-LyMt7GjgT-jfvZ3zKDl0L96pmGQXF1B82W2Bfjf0n7~288vafocjFLnQnVcmZd~-p0-Oolfo9aW2Rm-AhyqxnxyLlPBqGxsJBXjPhm1JBT4Ia8FB-VXt0BuY0fMKdAfWwN61-tj4zIcQWRxv3DFquwEf035K~Ra4SWOqiuJgTRJu7~o~DzHVljVgWIzwf8Z84cz0X33pv-mdG~~y0Bsc2qJVnYwjjR178YMcRSmNE0FVMcs6f17c6zqhMw-11qjKpY~EJfHYCx4lBWF37CD0obbWqTNUIbL~78vxqZRT3dgAgnLixog9nqTO-0Rh~NpVUZnoUi7fNR~awW5U3Cf7rU7nNEKKobLue78hjvRcWn7upHUF45QqTDuaM3yZa7OsjbcH-I909DOub2Q0Dno6vIwuA7yrysccN1sbnkwZbKlf4T6~iDdhaSLJd97QCyPOlbyUfYy9QLNExlRqKgNVJcMJRrIual~Lb1CLbnzt0uvobM57UpqSAAAA/meeting135"
|
||||
publiclogs="http://www.i2p.net/meeting135" />
|
||||
|
||||
** Bah, enough
|
||||
-->
|
||||
<i2p.news date="$Date: 2005/03/24 02:29:27 $">
|
||||
<i2p.release version="0.5.0.5" date="2005/03/29" minVersion="0.5.0.4"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
|
||||
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000652.html"
|
||||
publicannouncement="http://dev.i2p.net/pipermail/i2p/March-2005/000662.html" />
|
||||
<i2p.notes date="2005/03/29"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/March-2005/000661.html"
|
||||
publicurl="http://dev.i2p.net/pipermail/i2p/March-2005/000661.html"
|
||||
anonlogs="http://i2p/Nf3ab-ZFkmI-LyMt7GjgT-jfvZ3zKDl0L96pmGQXF1B82W2Bfjf0n7~288vafocjFLnQnVcmZd~-p0-Oolfo9aW2Rm-AhyqxnxyLlPBqGxsJBXjPhm1JBT4Ia8FB-VXt0BuY0fMKdAfWwN61-tj4zIcQWRxv3DFquwEf035K~Ra4SWOqiuJgTRJu7~o~DzHVljVgWIzwf8Z84cz0X33pv-mdG~~y0Bsc2qJVnYwjjR178YMcRSmNE0FVMcs6f17c6zqhMw-11qjKpY~EJfHYCx4lBWF37CD0obbWqTNUIbL~78vxqZRT3dgAgnLixog9nqTO-0Rh~NpVUZnoUi7fNR~awW5U3Cf7rU7nNEKKobLue78hjvRcWn7upHUF45QqTDuaM3yZa7OsjbcH-I909DOub2Q0Dno6vIwuA7yrysccN1sbnkwZbKlf4T6~iDdhaSLJd97QCyPOlbyUfYy9QLNExlRqKgNVJcMJRrIual~Lb1CLbnzt0uvobM57UpqSAAAA/meeting135"
|
||||
publiclogs="http://www.i2p.net/meeting135" />
|
||||
<b>I2P news online!</b> For those still on 0.5.0.4, please update ASAP. If you
|
||||
don't see a link to update with, delete the docs/news.xml file and wait 10
|
||||
minutes. Otherwise, swing on by the website to download it.<br />
|
||||
</i2p.news>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
<h1>Congratulations on getting I2P installed!</h1>
|
||||
|
||||
<p>If this is your first time running I2P, you will see a link on the left hand
|
||||
side telling you to "reseed" - click that to get connected to the network (you
|
||||
only need to do it if that link shows up). Within 5 minutes, you should see
|
||||
|
||||
515
router/doc/udp.html
Normal file
515
router/doc/udp.html
Normal file
@@ -0,0 +1,515 @@
|
||||
<code>$Id: udp.html,v 1.6 2005/03/27 17:08:16 jrandom Exp $</code>
|
||||
|
||||
<h1>Secure Semireliable UDP (SSU)</h1>
|
||||
<b>DRAFT</b>
|
||||
|
||||
<p>
|
||||
The goal of this protocol is to provide secure, authenticated,
|
||||
semireliable, and unordered message delivery, exposing only a minimal
|
||||
amount of data easily discernible to third parties. It should
|
||||
support high degree communication as well as TCP-friendly congestion
|
||||
control, and may include PMTU detection. It should be capable of
|
||||
efficiently moving bulk data at rates sufficient for home users.
|
||||
In addition, it should support techniques for addressing network
|
||||
obstacles, like most NATs or firewalls.</p>
|
||||
|
||||
<h2><a name="addressing">Addressing and introduction</a></h2>
|
||||
|
||||
<p>To contact an SSU peer, one of two sets of information is necessary:
|
||||
a direct address, for when the peer is publicly reachable, or an
|
||||
indirect address, for using a third party to introduce the peer.
|
||||
There is no restriction on the number of addresses a peer may have.</p>
|
||||
|
||||
<pre>
|
||||
Direct: udp://host:port/introKey
|
||||
Indirect: udp://tag@relayhost:port/relayIntroKey/targetIntroKey
|
||||
</pre>
|
||||
|
||||
<p>These introduction keys are delivered through an external channel
|
||||
and must be used when establishing a session key. For the indirect
|
||||
address, the peer must first contact the relayhost and ask them for
|
||||
an introduction to the peer known at that relayhost under the given
|
||||
tag. If possible, the relayhost sends a message to the addressed
|
||||
peer telling them to contact the requesting peer, and also gives
|
||||
the requesting peer the IP and port on which the addressed peer is
|
||||
located. In addition, the peer establishing the connection must
|
||||
already know the public keys of the peer they are connecting to (but
|
||||
not necessary to any intermediary relay peer).</p>
|
||||
|
||||
<h2><a name="header">Header</a></h2>
|
||||
|
||||
<p>All UDP datagrams begin with a MAC and an IV, followed by a variable
|
||||
size payload encrypted with the appropriate key. The MAC used is
|
||||
HMAC-SHA256, truncated to 16 bytes, while the key is a full AES256
|
||||
key. The specific construct of the MAC is the first 16 bytes from:</p>
|
||||
<pre>
|
||||
HMAC-SHA256(payload || IV || payloadLength, macKey)
|
||||
</pre>
|
||||
|
||||
<p>The payload itself is AES256/CBC encrypted with the IV and the
|
||||
sessionKey, with replay prevention addressed within its body,
|
||||
explained below.</p>
|
||||
|
||||
<h2><a name="payload">Payload</a></h2>
|
||||
|
||||
<p>Within the AES encrypted payload, there is a minimal common structure
|
||||
to the various messages - a one byte flag and a four byte sending
|
||||
timestamp (*seconds* since the unix epoch). The flag byte contains
|
||||
the following bitfields:</p>
|
||||
<pre>
|
||||
bits 0-3: payload type
|
||||
bit 4: rekey?
|
||||
bit 5: extended options included
|
||||
bits 6-7: reserved
|
||||
</pre>
|
||||
|
||||
<p>If the rekey flag is set, 64 bytes of keying material follow the
|
||||
timestamp. If the extended options flag is set, a one byte option
|
||||
size value is appended to, followed by that many extended option
|
||||
bytes, which are currently uninterpreted.</p>
|
||||
|
||||
<p>When rekeying, the first 32 bytes of the keying material is fed
|
||||
into a SHA256 to produce the new MAC key, and the next 32 bytes are
|
||||
fed into a SHA256 to produce the new session key, though the keys are
|
||||
not immediately used. The other side should also reply with the
|
||||
rekey flag set and that same keying material. Once both sides have
|
||||
sent and received those values, the new keys should be used and the
|
||||
previous keys discarded. It may be useful to keep the old keys
|
||||
around briefly, to address packet loss and reordering.</p>
|
||||
|
||||
<pre>
|
||||
Header: 37+ bytes
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| MAC |
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| IV |
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|flag| time | (optionally |
|
||||
+----+----+----+----+----+ |
|
||||
| this may have 64 byte keying material |
|
||||
| and/or a one+N byte extended options) |
|
||||
+---------------------------------------|
|
||||
</pre>
|
||||
|
||||
<h2><a name="messages">Messages</a></h2>
|
||||
|
||||
<h3><a name="sessionRequest">SessionRequest (type 0)</a></h3>
|
||||
<table border="1">
|
||||
<tr><td align="right" valign="top"><b>Peer:</b></td>
|
||||
<td>Alice to Bob</td></tr>
|
||||
<tr><td align="right" valign="top"><b>Data:</b></td>
|
||||
<td><ul>
|
||||
<li>256 byte X, to begin the DH agreement</li>
|
||||
<li>1 byte IP address size</li>
|
||||
<li>that many byte representation of Bob's IP address</li>
|
||||
<li>N bytes, currently uninterpreted (later, for challenges)</li>
|
||||
</ul></td></tr>
|
||||
<tr><td align="right" valign="top"><b>Key used:</b></td>
|
||||
<td>introKey</td></tr>
|
||||
</table>
|
||||
|
||||
<pre>
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| X, as calculated from DH |
|
||||
| |
|
||||
. . .
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|size| that many byte IP address (4-16) |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| arbitrary amount |
|
||||
| of uninterpreted data |
|
||||
. . .
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
</pre>
|
||||
|
||||
<h3><a name="sessionCreated">SessionCreated (type 1)</a></h3>
|
||||
<table border="1">
|
||||
<tr><td align="right" valign="top"><b>Peer:</b></td>
|
||||
<td>Bob to Alice</td></tr>
|
||||
<tr><td align="right" valign="top"><b>Data:</b></td>
|
||||
<td><ul>
|
||||
<li>256 byte Y, to complete the DH agreement</li>
|
||||
<li>1 byte IP address size</li>
|
||||
<li>that many byte representation of Alice's IP address</li>
|
||||
<li>2 byte port number (unsigned, big endian 2s complement)</li>
|
||||
<li>0-15 pad bytes to reach the 16 byte boundary</li>
|
||||
<li>4 byte relay tag which Alice can publish (else 0x0)</li>
|
||||
<li>40 byte DSA signature of the critical exchanged data</li>
|
||||
<li>N bytes, currently uninterpreted (later, for challenges)</li>
|
||||
</ul></td></tr>
|
||||
<tr><td align="right" valign="top"><b>Key used:</b></td>
|
||||
<td>introKey for the data through the pad bytes, and the
|
||||
sessionKey for the DSA signature</td></tr>
|
||||
</table>
|
||||
|
||||
<pre>
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| Y, as calculated from DH |
|
||||
| |
|
||||
. . .
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|size| that many byte IP address (4-16) |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| Port (A)| (pad to 16 byte boundary) |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| public relay tag | DSA signature |
|
||||
+----+----+----+----+ |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
+ +----+----+----+----+
|
||||
| | arbitrary amount |
|
||||
+----+----+----+----+ |
|
||||
| of uninterpreted data |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
</pre>
|
||||
|
||||
<h3><a name="sessionConfirmed">SessionConfirmed (type 2)</a></h3>
|
||||
<table border="1">
|
||||
<tr><td align="right" valign="top"><b>Peer:</b></td>
|
||||
<td>Bob to Alice</td></tr>
|
||||
<tr><td align="right" valign="top"><b>Data:</b></td>
|
||||
<td><ul>
|
||||
<li>1 byte identity fragment info:<pre>
|
||||
bits 0-3: current identity fragment #
|
||||
bits 4-7: total identity fragments</pre></li>
|
||||
<li>N byte fragment of Alice's identity, sent over a number
|
||||
of messages.</li>
|
||||
<li>on the last identity fragment, the last 40 bytes contain
|
||||
the DSA signature of the critical exchanged data</li>
|
||||
</ul></td></tr>
|
||||
<tr><td align="right" valign="top"><b>Key used:</b></td>
|
||||
<td>sessionKey</td></tr>
|
||||
</table>
|
||||
|
||||
<pre>
|
||||
<b>Fragment 1 through N-1</b>
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|info| fragment of Alice's full |
|
||||
+----+ |
|
||||
| identity keys |
|
||||
. . .
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|
||||
<b>Fragment N:</b>
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|info| fragment of Alice's full |
|
||||
+----+ |
|
||||
| identity keys |
|
||||
. . .
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| arbitrary amount of uninterpreted |
|
||||
| data, up from the end of the |
|
||||
| identity key to 40 bytes prior to |
|
||||
| end of the current packet |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| DSA signature |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
</pre>
|
||||
|
||||
<h3><a name="relayRequest">RelayRequest (type 3)</a></h3>
|
||||
<table border="1">
|
||||
<tr><td align="right" valign="top"><b>Peer:</b></td>
|
||||
<td>Alice to Bob</td></tr>
|
||||
<tr><td align="right" valign="top"><b>Data:</b></td>
|
||||
<td><ul>
|
||||
<li>4 byte relay tag</li>
|
||||
<li>1 byte IP address size</li>
|
||||
<li>that many byte representation of Bob's IP address</li>
|
||||
<li>1 byte IP address size</li>
|
||||
<li>that many byte representation of Alice's IP address</li>
|
||||
<li>2 byte port number (of Alice)</li>
|
||||
<li>1 byte challenge size</li>
|
||||
<li>that many bytes to be relayed to Charlie in the intro</li>
|
||||
<li>N bytes, currently uninterpreted</li>
|
||||
</ul></td></tr>
|
||||
<tr><td align="right" valign="top"><b>Key used:</b></td>
|
||||
<td>introKey (or sessionKey, if Alice/Bob is established)</td></tr>
|
||||
</table>
|
||||
|
||||
<pre>
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| relay tag |size| that many |
|
||||
+----+----+----+----+----+ +----|
|
||||
| bytes making up Bob's IP address |size|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| that many bytes making up Alice's IP |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| Port (A)|size| that many challenge |
|
||||
+----+----+----+ |
|
||||
| bytes to be delivered to Charlie |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| arbitrary amount of uninterpreted data|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
</pre>
|
||||
|
||||
<h3><a name="relayResponse">RelayResponse (type 4)</a></h3>
|
||||
<table border="1">
|
||||
<tr><td align="right" valign="top"><b>Peer:</b></td>
|
||||
<td>Bob to Alice</td></tr>
|
||||
<tr><td align="right" valign="top"><b>Data:</b></td>
|
||||
<td><ul>
|
||||
<li>1 byte IP address size</li>
|
||||
<li>that many byte representation of Charlie's IP address</li>
|
||||
<li>2 byte port number</li>
|
||||
<li>1 byte IP address size</li>
|
||||
<li>that many byte representation of Alice's IP address</li>
|
||||
<li>2 byte port number</li>
|
||||
<li>N bytes, currently uninterpreted</li>
|
||||
</ul></td></tr>
|
||||
<tr><td align="right" valign="top"><b>Key used:</b></td>
|
||||
<td>introKey (or sessionKey, if Alice/Bob is established)</td></tr>
|
||||
</table>
|
||||
|
||||
<pre>
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|size| that many bytes making up |
|
||||
+----+ +----+----+
|
||||
| Charlie's IP address | Port (C)|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|size| that many bytes making up |
|
||||
+----+ +----+----+
|
||||
| Alice's IP address | Port (A)|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| arbitrary amount of uninterpreted data|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
</pre>
|
||||
|
||||
<h3><a name="relayIntro">RelayIntro (type 5)</a></h3>
|
||||
<table border="1">
|
||||
<tr><td align="right" valign="top"><b>Peer:</b></td>
|
||||
<td>Bob to Charlie</td></tr>
|
||||
<tr><td align="right" valign="top"><b>Data:</b></td>
|
||||
<td><ul>
|
||||
<li>1 byte IP address size</li>
|
||||
<li>that many byte representation of Alice's IP address</li>
|
||||
<li>2 byte port number (of Alice)</li>
|
||||
<li>1 byte challenge size</li>
|
||||
<li>that many bytes relayed from Alice</li>
|
||||
<li>N bytes, currently uninterpreted</li>
|
||||
</ul></td></tr>
|
||||
<tr><td align="right" valign="top"><b>Key used:</b></td>
|
||||
<td>sessionKey</td></tr>
|
||||
</table>
|
||||
|
||||
<pre>
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|size| that many bytes making up |
|
||||
+----+ +----+----+
|
||||
| Charlie's IP address | Port (C)|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|size| that many bytes of challenge |
|
||||
+----+ |
|
||||
| data relayed from Alice |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| arbitrary amount of uninterpreted data|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
</pre>
|
||||
|
||||
<h3><a name="data">Data (type 6)</a></h3>
|
||||
<table border="1">
|
||||
<tr><td align="right" valign="top"><b>Peer:</b></td>
|
||||
<td>Any</td></tr>
|
||||
<tr><td align="right" valign="top"><b>Data:</b></td>
|
||||
<td><ul>
|
||||
<li>1 byte flags:<pre>
|
||||
bit 0: explicit ACKs included
|
||||
bit 1: explicit NACKs included
|
||||
bit 2: numACKs included
|
||||
bits 3-4: reserved for congestion control
|
||||
bit 5: want reply
|
||||
bits 6-7: reserved</pre></li>
|
||||
<li>if explicit ACKs are included:<ul>
|
||||
<li>a 1 byte number of ACKs</li>
|
||||
<li>that many 4 byte MessageIds being fully ACKed</li>
|
||||
</ul></li>
|
||||
<li>if explicit NACKs are included:<ul>
|
||||
<li>a 1 byte number of NACKs</li>
|
||||
<li>that many 4 byte MessageIds + 1 byte fragmentNum NACKs</li>
|
||||
</ul></li>
|
||||
<li>if numACKs included:<ul>
|
||||
<li>a 2 byte number for how many messages were fully
|
||||
received in the last minute.</li></ul></li>
|
||||
<li>1 byte number of fragments</li>
|
||||
<li>that many message fragments:<ul>
|
||||
<li>4 byte messageId</li>
|
||||
<li>1 byte fragment info:<pre>
|
||||
bits 0-4: fragment #
|
||||
bit 5: isLast (1 = true)
|
||||
bits 6-7: unused</pre></li>
|
||||
<li>2 byte fragment size</li>
|
||||
<li>that many bytes</li>
|
||||
<li>1 byte fragment size</li></ul>
|
||||
<li>N bytes padding, uninterpreted</li>
|
||||
</ul></td></tr>
|
||||
<tr><td align="right" valign="top"><b>Key used:</b></td>
|
||||
<td>sessionKey</td></tr>
|
||||
</table>
|
||||
|
||||
<pre>
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|flag| (additional headers, determined |
|
||||
+----+ |
|
||||
| by the flags, such as ACKs, NACKs, or |
|
||||
| simple rate of full ACKs) |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|#frg| messageId |info|fragSize |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| that many bytes of fragment data |
|
||||
. . .
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| messageId |info|fragSize | |
|
||||
+----+----+----+----+----+----+----+ |
|
||||
| that many bytes of fragment data |
|
||||
. . .
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| messageId |info|fragSize | |
|
||||
+----+----+----+----+----+----+----+ |
|
||||
| that many bytes of fragment data |
|
||||
. . .
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| arbitrary amount of uninterpreted data|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
</pre>
|
||||
|
||||
<h2><a name="keys">Keys</a></h2>
|
||||
|
||||
<p>All encryption used is AES256/CBC with 32 byte keys and 16 byte IVs.
|
||||
The MAC and session keys are negotiated as part of the DH exchange, used
|
||||
for the HMAC and encryption, respectively. Prior to the DH exchange,
|
||||
the publicly knowable introKey is used for the MAC and encryption.</p>
|
||||
|
||||
<p>When using the introKey, both the initial message and any subsequent
|
||||
reply use the introKey of the responder (Bob) - the responder does
|
||||
not need to know the introKey of the requestor (Alice). The DSA
|
||||
signing key used by Bob should already be known to Alice when she
|
||||
contacts him, though Alice's DSA key may not already be known by
|
||||
Bob.</p>
|
||||
|
||||
<p>Upon receiving a message, the receiver checks the from IP address
|
||||
with any established sessions - if there is one or more matches,
|
||||
those session's MAC keys are tested sequentially in the HMAC. If none
|
||||
of those verify or if there are no matching IP addresses, the
|
||||
receiver tries their introKey in the MAC. If that does not verify,
|
||||
the packet is dropped. If it does verify, it is interpreted
|
||||
according to the message type, though if the receiver is overloaded,
|
||||
it may be dropped anyway.</p>
|
||||
|
||||
<p>If Alice and Bob have an established session, but Alice loses the
|
||||
keys for some reason and she wants to contact Bob, she may at any
|
||||
time simply establish a new session through the SessionRequest and
|
||||
related messages. If Bob has lost the key but Alice does not know
|
||||
that, she will first attempt to prod him to reply, by sending a
|
||||
DataMessage with the wantReply flag set, and if Bob continually
|
||||
fails to reply, she will assume the key is lost and reestablish a
|
||||
new one.</p>
|
||||
|
||||
<p>For the DH key agreement,
|
||||
<a href="http://www.faqs.org/rfcs/rfc3526.html">RFC3526</a> 2048bit
|
||||
MODP group (#14) is used:</p>
|
||||
<pre>
|
||||
p = 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }
|
||||
g = 2
|
||||
</pre>
|
||||
|
||||
<p>The DSA p, q, and g are shared according to the scope of the
|
||||
identity which created them.</p>
|
||||
|
||||
<h2><a name="replay">Replay prevention</a></h2>
|
||||
|
||||
<p>Replay prevention at the SSU layer occurs by rejecting packets
|
||||
with exceedingly old timestamps or those which reuse an IV. To
|
||||
detect duplicate IVs, a sequence of Bloom filters are employed to
|
||||
"decay" periodically so that only recently added IVs are detected.</p>
|
||||
|
||||
<p>The messageIds used in DataMessages are defined at layers above
|
||||
the SSU transport and are passed through transparently. These IDs
|
||||
are not in any particular order - in fact, they are likely to be
|
||||
entirely random. The SSU layer makes no attempt at messageId
|
||||
replay prevention - higher layers should take that into account.</p>
|
||||
|
||||
<h2><a name="messageSequences">Message sequences</a></h2>
|
||||
|
||||
<h3><a name="establishDirect">Connection establishment (direct)</a></h3>
|
||||
|
||||
<pre>
|
||||
Alice Bob
|
||||
SessionRequest--------------------->
|
||||
<---------------------SessionCreated
|
||||
SessionConfirmed------------------->
|
||||
SessionConfirmed------------------->
|
||||
SessionConfirmed------------------->
|
||||
SessionConfirmed------------------->
|
||||
<--------------------------Data
|
||||
</pre>
|
||||
|
||||
<h3><a name="establishIndirect">Connection establishment (indirect)</a></h3>
|
||||
|
||||
<pre>
|
||||
Alice Bob Charlie
|
||||
RelayRequest ---------------------->
|
||||
<--------------RelayResponse RelayIntro----------->
|
||||
<--------------------------------------------Data (ignored)
|
||||
SessionRequest-------------------------------------------->
|
||||
<--------------------------------------------SessionCreated
|
||||
SessionConfirmed------------------------------------------>
|
||||
SessionConfirmed------------------------------------------>
|
||||
SessionConfirmed------------------------------------------>
|
||||
SessionConfirmed------------------------------------------>
|
||||
<---------------------------------------------------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>
|
||||
@@ -67,6 +67,8 @@ public interface I2NPMessage extends DataStructure {
|
||||
*
|
||||
*/
|
||||
public long getMessageExpiration();
|
||||
public void setMessageExpiration(long exp);
|
||||
|
||||
|
||||
/** How large the message is, including any checksums */
|
||||
public int getMessageSize();
|
||||
|
||||
@@ -17,6 +17,7 @@ import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.TunnelCreateMessage;
|
||||
import net.i2p.data.i2np.TunnelCreateStatusMessage;
|
||||
import net.i2p.data.i2np.TunnelDataMessage;
|
||||
import net.i2p.data.i2np.TunnelGatewayMessage;
|
||||
@@ -103,11 +104,15 @@ public class InNetMessagePool implements Service {
|
||||
if (messageBody instanceof TunnelDataMessage) {
|
||||
// do not validate the message with the validator - the IV validator is sufficient
|
||||
} else {
|
||||
boolean valid = _context.messageValidator().validateMessage(messageBody.getUniqueId(), exp);
|
||||
if (!valid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Duplicate message received [" + messageBody.getUniqueId()
|
||||
+ " expiring on " + exp + "]: " + messageBody.getClass().getName());
|
||||
String invalidReason = _context.messageValidator().validateMessage(messageBody.getUniqueId(), exp);
|
||||
if (invalidReason != null) {
|
||||
int level = Log.WARN;
|
||||
if (messageBody instanceof TunnelCreateMessage)
|
||||
level = Log.INFO;
|
||||
if (_log.shouldLog(level))
|
||||
_log.log(level, "Duplicate message received [" + messageBody.getUniqueId()
|
||||
+ " expiring on " + exp + "]: " + messageBody.getClass().getName() + ": " + invalidReason
|
||||
+ ": " + messageBody);
|
||||
_context.statManager().addRateData("inNetPool.dropped", 1, 0);
|
||||
_context.statManager().addRateData("inNetPool.duplicate", 1, 0);
|
||||
_context.messageHistory().droppedOtherMessage(messageBody);
|
||||
|
||||
@@ -318,7 +318,7 @@ public class MessageHistory {
|
||||
*/
|
||||
public void sendMessage(String messageType, long messageId, long expiration, Hash peer, boolean sentOk) {
|
||||
if (!_doLog) return;
|
||||
if (true) return;
|
||||
if (false) return;
|
||||
StringBuffer buf = new StringBuffer(256);
|
||||
buf.append(getPrefix());
|
||||
buf.append("send [").append(messageType).append("] message [").append(messageId).append("] ");
|
||||
@@ -344,7 +344,7 @@ public class MessageHistory {
|
||||
*/
|
||||
public void receiveMessage(String messageType, long messageId, long expiration, Hash from, boolean isValid) {
|
||||
if (!_doLog) return;
|
||||
if (true) return;
|
||||
if (false) return;
|
||||
StringBuffer buf = new StringBuffer(256);
|
||||
buf.append(getPrefix());
|
||||
buf.append("receive [").append(messageType).append("] with id [").append(messageId).append("] ");
|
||||
|
||||
@@ -31,20 +31,20 @@ public class MessageValidator {
|
||||
/**
|
||||
* Determine if this message should be accepted as valid (not expired, not a duplicate)
|
||||
*
|
||||
* @return true if the message should be accepted as valid, false otherwise
|
||||
* @return reason why the message is invalid (or null if the message is valid)
|
||||
*/
|
||||
public boolean validateMessage(long messageId, long expiration) {
|
||||
public String validateMessage(long messageId, long expiration) {
|
||||
long now = _context.clock().now();
|
||||
if (now - Router.CLOCK_FUDGE_FACTOR >= expiration) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Rejecting message " + messageId + " because it expired " + (now-expiration) + "ms ago");
|
||||
_context.statManager().addRateData("router.invalidMessageTime", (now-expiration), 0);
|
||||
return false;
|
||||
return "expired " + (now-expiration) + "ms ago";
|
||||
} else if (now + 4*Router.CLOCK_FUDGE_FACTOR < expiration) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Rejecting message " + messageId + " because it will expire too far in the future (" + (expiration-now) + "ms)");
|
||||
_context.statManager().addRateData("router.invalidMessageTime", (now-expiration), 0);
|
||||
return false;
|
||||
return "expire too far in the future (" + (expiration-now) + "ms)";
|
||||
}
|
||||
|
||||
boolean isDuplicate = noteReception(messageId, expiration);
|
||||
@@ -52,11 +52,11 @@ public class MessageValidator {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Rejecting message " + messageId + " because it is a duplicate", new Exception("Duplicate origin"));
|
||||
_context.statManager().addRateData("router.duplicateMessageId", 1, 0);
|
||||
return false;
|
||||
return "duplicate";
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Accepting message " + messageId + " because it is NOT a duplicate", new Exception("Original origin"));
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public class OutNetMessage {
|
||||
*/
|
||||
public long timestamp(String eventName) {
|
||||
long now = _context.clock().now();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
// only timestamp if we are debugging
|
||||
synchronized (this) {
|
||||
locked_initTimestamps();
|
||||
@@ -103,7 +103,7 @@ public class OutNetMessage {
|
||||
return now - _created;
|
||||
}
|
||||
public Map getTimestamps() {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
synchronized (this) {
|
||||
locked_initTimestamps();
|
||||
return (Map)_timestamps.clone();
|
||||
@@ -112,7 +112,7 @@ public class OutNetMessage {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
public Long getTimestamp(String eventName) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
synchronized (this) {
|
||||
locked_initTimestamps();
|
||||
return (Long)_timestamps.get(eventName);
|
||||
@@ -301,7 +301,7 @@ public class OutNetMessage {
|
||||
}
|
||||
|
||||
private void renderTimestamps(StringBuffer buf) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
synchronized (this) {
|
||||
long lastWhen = -1;
|
||||
for (int i = 0; i < _timestampOrder.size(); i++) {
|
||||
|
||||
@@ -371,7 +371,7 @@ public class Router {
|
||||
|
||||
RateStat sendRate = _context.statManager().getRate("transport.sendMessageSize");
|
||||
if (sendRate != null) {
|
||||
Rate rate = receiveRate.getRate(60*1000);
|
||||
Rate rate = sendRate.getRate(60*1000);
|
||||
if (rate != null) {
|
||||
double bytes = rate.getLastTotalValue();
|
||||
double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d);
|
||||
|
||||
@@ -18,6 +18,7 @@ import net.i2p.router.peermanager.ProfileManagerImpl;
|
||||
import net.i2p.router.peermanager.ProfileOrganizer;
|
||||
import net.i2p.router.peermanager.ReliabilityCalculator;
|
||||
import net.i2p.router.peermanager.SpeedCalculator;
|
||||
import net.i2p.router.peermanager.StrictSpeedCalculator;
|
||||
import net.i2p.router.transport.CommSystemFacadeImpl;
|
||||
import net.i2p.router.transport.FIFOBandwidthLimiter;
|
||||
import net.i2p.router.transport.OutboundMessageRegistry;
|
||||
@@ -62,6 +63,7 @@ public class RouterContext extends I2PAppContext {
|
||||
private Calculator _speedCalc;
|
||||
private Calculator _reliabilityCalc;
|
||||
private Calculator _capacityCalc;
|
||||
private Calculator _oldSpeedCalc;
|
||||
|
||||
private static List _contexts = new ArrayList(1);
|
||||
|
||||
@@ -115,6 +117,7 @@ public class RouterContext extends I2PAppContext {
|
||||
_isFailingCalc = new IsFailingCalculator(this);
|
||||
_integrationCalc = new IntegrationCalculator(this);
|
||||
_speedCalc = new SpeedCalculator(this);
|
||||
_oldSpeedCalc = new StrictSpeedCalculator(this);
|
||||
_reliabilityCalc = new ReliabilityCalculator(this);
|
||||
_capacityCalc = new CapacityCalculator(this);
|
||||
}
|
||||
@@ -248,6 +251,7 @@ public class RouterContext extends I2PAppContext {
|
||||
public Calculator integrationCalculator() { return _integrationCalc; }
|
||||
/** how do we rank the speed of profiles? */
|
||||
public Calculator speedCalculator() { return _speedCalc; }
|
||||
public Calculator oldSpeedCalculator() { return _oldSpeedCalc; }
|
||||
/** how do we rank the reliability of profiles? */
|
||||
public Calculator reliabilityCalculator() { return _reliabilityCalc; }
|
||||
/** how do we rank the capacity of profiles? */
|
||||
|
||||
@@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
|
||||
*
|
||||
*/
|
||||
public class RouterVersion {
|
||||
public final static String ID = "$Revision: 1.159 $ $Date: 2005/03/04 21:54:43 $";
|
||||
public final static String VERSION = "0.5.0.2";
|
||||
public final static String ID = "$Revision: 1.175 $ $Date: 2005/03/26 02:13:38 $";
|
||||
public final static String VERSION = "0.5.0.5";
|
||||
public final static long BUILD = 0;
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Router version: " + VERSION);
|
||||
|
||||
@@ -102,24 +102,27 @@ public class StatisticsManager implements Service {
|
||||
stats.putAll(_context.profileManager().summarizePeers(_publishedStats));
|
||||
|
||||
includeThroughput(stats);
|
||||
includeRate("router.invalidMessageTime", stats, new long[] { 10*60*1000, 3*60*60*1000 });
|
||||
includeRate("router.invalidMessageTime", stats, new long[] { 10*60*1000 });
|
||||
includeRate("router.duplicateMessageId", stats, new long[] { 24*60*60*1000 });
|
||||
includeRate("tunnel.duplicateIV", stats, new long[] { 24*60*60*1000 });
|
||||
includeRate("tunnel.fragmentedComplete", stats, new long[] { 10*60*1000, 3*60*60*1000 });
|
||||
includeRate("tunnel.fragmentedDropped", stats, new long[] { 10*60*1000, 3*60*60*1000 });
|
||||
includeRate("tunnel.fullFragments", stats, new long[] { 10*60*1000, 3*60*60*1000 });
|
||||
includeRate("tunnel.smallFragments", stats, new long[] { 10*60*1000, 3*60*60*1000 });
|
||||
includeRate("tunnel.testFailedTime", stats, new long[] { 60*60*1000, 3*60*60*1000 });
|
||||
includeRate("tunnel.dispatchOutboundTime", stats, new long[] { 60*60*1000 });
|
||||
includeRate("tunnel.dispatchGatewayTime", stats, new long[] { 60*60*1000 });
|
||||
includeRate("tunnel.dispatchDataTime", stats, new long[] { 60*60*1000 });
|
||||
includeRate("tunnel.buildFailure", stats, new long[] { 10*60*1000, 60*60*1000 });
|
||||
includeRate("tunnel.buildSuccess", stats, new long[] { 10*60*1000, 60*60*1000 });
|
||||
includeRate("tunnel.testFailedTime", stats, new long[] { 60*60*1000 });
|
||||
|
||||
includeRate("tunnel.buildFailure", stats, new long[] { 60*60*1000 });
|
||||
includeRate("tunnel.buildSuccess", stats, new long[] { 60*60*1000 });
|
||||
|
||||
//includeRate("tunnel.batchDelaySent", stats, new long[] { 10*60*1000, 60*60*1000 });
|
||||
includeRate("tunnel.batchMultipleCount", stats, new long[] { 10*60*1000, 60*60*1000 });
|
||||
includeRate("tunnel.corruptMessage", stats, new long[] { 60*60*1000l, 3*60*60*1000l });
|
||||
|
||||
includeRate("router.throttleTunnelProbTestSlow", stats, new long[] { 60*60*1000 });
|
||||
includeRate("router.throttleTunnelProbTooFast", stats, new long[] { 60*60*1000 });
|
||||
includeRate("router.throttleTunnelProcessingTime1m", stats, new long[] { 60*60*1000 });
|
||||
|
||||
|
||||
includeRate("router.fastPeers", stats, new long[] { 60*60*1000 });
|
||||
|
||||
includeRate("clock.skew", stats, new long[] { 10*60*1000, 3*60*60*1000, 24*60*60*1000 });
|
||||
|
||||
includeRate("transport.sendProcessingTime", stats, new long[] { 60*60*1000 });
|
||||
|
||||
@@ -109,6 +109,10 @@ public class GarlicMessageBuilder {
|
||||
msg.setData(encData);
|
||||
msg.setMessageExpiration(config.getExpiration());
|
||||
|
||||
long timeFromNow = config.getExpiration() - ctx.clock().now();
|
||||
if (timeFromNow < 10*1000)
|
||||
log.error("Building a message expiring in " + timeFromNow + "ms: " + config, new Exception("created by"));
|
||||
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("CloveSet size for message " + msg.getUniqueId() + " is " + cloveSet.length
|
||||
+ " and encrypted message data is " + encData.length);
|
||||
|
||||
@@ -99,13 +99,13 @@ public class GarlicMessageReceiver {
|
||||
}
|
||||
|
||||
private boolean isValid(GarlicClove clove) {
|
||||
boolean valid = _context.messageValidator().validateMessage(clove.getCloveId(),
|
||||
clove.getExpiration().getTime());
|
||||
if (!valid) {
|
||||
String invalidReason = _context.messageValidator().validateMessage(clove.getCloveId(),
|
||||
clove.getExpiration().getTime());
|
||||
if (invalidReason != null) {
|
||||
String howLongAgo = DataHelper.formatDuration(_context.clock().now()-clove.getExpiration().getTime());
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Clove is NOT valid: id=" + clove.getCloveId()
|
||||
+ " expiration " + howLongAgo + " ago");
|
||||
+ " expiration " + howLongAgo + " ago: " + invalidReason + ": " + clove);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Clove is NOT valid: id=" + clove.getCloveId()
|
||||
+ " expiration " + howLongAgo + " ago", new Exception("Invalid within..."));
|
||||
@@ -113,6 +113,6 @@ public class GarlicMessageReceiver {
|
||||
clove.getData().getClass().getName(),
|
||||
"Clove is not valid (expiration " + howLongAgo + " ago)");
|
||||
}
|
||||
return valid;
|
||||
return (invalidReason == null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,10 +52,11 @@ class OutboundClientMessageJobHelper {
|
||||
* @return garlic, or null if no tunnels were found (or other errors)
|
||||
*/
|
||||
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
|
||||
Payload data, Hash from, Destination dest, SessionKey wrappedKey, Set wrappedTags,
|
||||
Payload data, Hash from, Destination dest, TunnelInfo replyTunnel,
|
||||
SessionKey wrappedKey, Set wrappedTags,
|
||||
boolean requireAck, LeaseSet bundledReplyLeaseSet) {
|
||||
PayloadGarlicConfig dataClove = buildDataClove(ctx, data, dest, expiration);
|
||||
return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, wrappedKey,
|
||||
return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, replyTunnel, wrappedKey,
|
||||
wrappedTags, requireAck, bundledReplyLeaseSet);
|
||||
}
|
||||
/**
|
||||
@@ -65,9 +66,9 @@ class OutboundClientMessageJobHelper {
|
||||
* @return garlic, or null if no tunnels were found (or other errors)
|
||||
*/
|
||||
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
|
||||
PayloadGarlicConfig dataClove, Hash from, Destination dest, SessionKey wrappedKey,
|
||||
PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel, SessionKey wrappedKey,
|
||||
Set wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet) {
|
||||
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, requireAck, bundledReplyLeaseSet);
|
||||
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, replyTunnel, requireAck, bundledReplyLeaseSet);
|
||||
if (config == null)
|
||||
return null;
|
||||
GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, config, wrappedKey, wrappedTags);
|
||||
@@ -75,7 +76,7 @@ class OutboundClientMessageJobHelper {
|
||||
}
|
||||
|
||||
private static GarlicConfig createGarlicConfig(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
|
||||
PayloadGarlicConfig dataClove, Hash from, Destination dest, boolean requireAck,
|
||||
PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel, boolean requireAck,
|
||||
LeaseSet bundledReplyLeaseSet) {
|
||||
Log log = ctx.logManager().getLog(OutboundClientMessageJobHelper.class);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
@@ -85,7 +86,7 @@ class OutboundClientMessageJobHelper {
|
||||
config.addClove(dataClove);
|
||||
|
||||
if (requireAck) {
|
||||
PayloadGarlicConfig ackClove = buildAckClove(ctx, from, replyToken, expiration);
|
||||
PayloadGarlicConfig ackClove = buildAckClove(ctx, from, replyTunnel, replyToken, expiration);
|
||||
if (ackClove == null)
|
||||
return null; // no tunnels
|
||||
config.addClove(ackClove);
|
||||
@@ -108,7 +109,7 @@ class OutboundClientMessageJobHelper {
|
||||
config.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
config.setDeliveryInstructions(instructions);
|
||||
config.setId(ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE));
|
||||
config.setExpiration(expiration+2*Router.CLOCK_FUDGE_FACTOR);
|
||||
config.setExpiration(expiration); // +2*Router.CLOCK_FUDGE_FACTOR);
|
||||
config.setRecipientPublicKey(recipientPK);
|
||||
config.setRequestAck(false);
|
||||
|
||||
@@ -122,14 +123,13 @@ class OutboundClientMessageJobHelper {
|
||||
/**
|
||||
* Build a clove that sends a DeliveryStatusMessage to us
|
||||
*/
|
||||
private static PayloadGarlicConfig buildAckClove(RouterContext ctx, Hash from, long replyToken, long expiration) {
|
||||
private static PayloadGarlicConfig buildAckClove(RouterContext ctx, Hash from, TunnelInfo replyToTunnel, long replyToken, long expiration) {
|
||||
Log log = ctx.logManager().getLog(OutboundClientMessageJobHelper.class);
|
||||
PayloadGarlicConfig ackClove = new PayloadGarlicConfig();
|
||||
|
||||
Hash replyToTunnelRouter = null; // inbound tunnel gateway
|
||||
TunnelId replyToTunnelId = null; // tunnel id on that gateway
|
||||
|
||||
TunnelInfo replyToTunnel = ctx.tunnelManager().selectInboundTunnel(from);
|
||||
if (replyToTunnel == null) {
|
||||
if (log.shouldLog(Log.ERROR))
|
||||
log.error("Unable to send client message from " + from.toBase64()
|
||||
|
||||
@@ -51,6 +51,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
private int _clientMessageSize;
|
||||
private Destination _from;
|
||||
private Destination _to;
|
||||
private String _toString;
|
||||
/** target destination's leaseSet, if known */
|
||||
private LeaseSet _leaseSet;
|
||||
/** Actual lease the message is being routed through */
|
||||
@@ -60,6 +61,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
private long _start;
|
||||
private boolean _finished;
|
||||
private long _leaseSetLookupBegin;
|
||||
private TunnelInfo _outTunnel;
|
||||
private TunnelInfo _inTunnel;
|
||||
|
||||
/**
|
||||
* final timeout (in milliseconds) that the outbound message will fail in.
|
||||
@@ -117,6 +120,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
_clientMessageSize = msg.getPayload().getSize();
|
||||
_from = msg.getFromDestination();
|
||||
_to = msg.getDestination();
|
||||
_toString = _to.calculateHash().toBase64().substring(0,4);
|
||||
_leaseSetLookupBegin = -1;
|
||||
|
||||
String param = msg.getSenderConfig().getOptions().getProperty(OVERALL_TIMEOUT_MS_PARAM);
|
||||
@@ -146,22 +150,24 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
_log.debug(getJobId() + ": Send outbound client message job beginning");
|
||||
buildClove();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Clove built");
|
||||
_log.debug(getJobId() + ": Clove built to " + _toString);
|
||||
long timeoutMs = _overallExpiration - getContext().clock().now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": preparing to search for the leaseSet");
|
||||
_log.debug(getJobId() + ": preparing to search for the leaseSet for " + _toString);
|
||||
Hash key = _to.calculateHash();
|
||||
SendJob success = new SendJob(getContext());
|
||||
LookupLeaseSetFailedJob failed = new LookupLeaseSetFailedJob(getContext());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Send outbound client message - sending off leaseSet lookup job");
|
||||
LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(key);
|
||||
if (ls != null) {
|
||||
getContext().statManager().addRateData("client.leaseSetFoundLocally", 1, 0);
|
||||
_leaseSetLookupBegin = -1;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Send outbound client message - leaseSet found locally for " + _toString);
|
||||
success.runJob();
|
||||
} else {
|
||||
_leaseSetLookupBegin = getContext().clock().now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Send outbound client message - sending off leaseSet lookup job for " + _toString);
|
||||
getContext().netDb().lookupLeaseSet(key, success, failed, timeoutMs);
|
||||
}
|
||||
}
|
||||
@@ -203,8 +209,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
boolean ok = getNextLease();
|
||||
if (ok)
|
||||
send();
|
||||
else
|
||||
else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to send on a random lease, as getNext returned null (to=" + _toString + ")");
|
||||
dieFatal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +227,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
_leaseSet = getContext().netDb().lookupLeaseSetLocally(_to.calculateHash());
|
||||
if (_leaseSet == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getJobId() + ": Lookup locally didn't find the leaseSet");
|
||||
_log.warn(getJobId() + ": Lookup locally didn't find the leaseSet for " + _toString);
|
||||
return false;
|
||||
}
|
||||
long now = getContext().clock().now();
|
||||
@@ -229,7 +238,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
Lease lease = _leaseSet.getLease(i);
|
||||
if (lease.isExpired(Router.CLOCK_FUDGE_FACTOR)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getJobId() + ": getNextLease() - expired lease! - " + lease);
|
||||
_log.warn(getJobId() + ": getNextLease() - expired lease! - " + lease + " for " + _toString);
|
||||
continue;
|
||||
} else {
|
||||
leases.add(lease);
|
||||
@@ -278,6 +287,9 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
long lookupTime = getContext().clock().now() - _leaseSetLookupBegin;
|
||||
getContext().statManager().addRateData("client.leaseSetFailedRemoteTime", lookupTime, lookupTime);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to send to " + _toString + " because we couldn't find their leaseSet");
|
||||
|
||||
dieFatal();
|
||||
}
|
||||
@@ -302,22 +314,26 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
replyLeaseSet = getContext().netDb().lookupLeaseSetLocally(_from.calculateHash());
|
||||
}
|
||||
|
||||
_inTunnel = selectInboundTunnel();
|
||||
|
||||
GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token,
|
||||
_overallExpiration, key,
|
||||
_clove, _from.calculateHash(),
|
||||
_to,
|
||||
_to, _inTunnel,
|
||||
sessKey, tags,
|
||||
true, replyLeaseSet);
|
||||
if (msg == null) {
|
||||
// set to null if there are no tunnels to ack the reply back through
|
||||
// (should we always fail for this? or should we send it anyway, even if
|
||||
// we dont receive the reply? hmm...)
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(getJobId() + ": Unable to create the garlic message (no tunnels left) to " + _toString);
|
||||
dieFatal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": send() - token expected " + token);
|
||||
_log.debug(getJobId() + ": send() - token expected " + token + " to " + _toString);
|
||||
|
||||
SendSuccessJob onReply = new SendSuccessJob(getContext(), sessKey, tags);
|
||||
SendTimeoutJob onFail = new SendTimeoutJob(getContext());
|
||||
@@ -325,18 +341,20 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Placing GarlicMessage into the new tunnel message bound for "
|
||||
+ _toString + " at "
|
||||
+ _lease.getTunnelId() + " on "
|
||||
+ _lease.getGateway().toBase64());
|
||||
|
||||
TunnelInfo outTunnel = selectOutboundTunnel();
|
||||
if (outTunnel != null) {
|
||||
_outTunnel = selectOutboundTunnel();
|
||||
if (_outTunnel != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Sending tunnel message out " + outTunnel.getSendTunnelId(0) + " to "
|
||||
_log.debug(getJobId() + ": Sending tunnel message out " + _outTunnel.getSendTunnelId(0) + " to "
|
||||
+ _toString + " at "
|
||||
+ _lease.getTunnelId() + " on "
|
||||
+ _lease.getGateway().toBase64());
|
||||
|
||||
// dispatch may take 100+ms, so toss it in its own job
|
||||
getContext().jobQueue().addJob(new DispatchJob(getContext(), msg, outTunnel, selector, onReply, onFail, (int)(_overallExpiration-getContext().clock().now())));
|
||||
getContext().jobQueue().addJob(new DispatchJob(getContext(), msg, selector, onReply, onFail, (int)(_overallExpiration-getContext().clock().now())));
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(getJobId() + ": Could not find any outbound tunnels to send the payload through... wtf?");
|
||||
@@ -348,15 +366,13 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
|
||||
private class DispatchJob extends JobImpl {
|
||||
private GarlicMessage _msg;
|
||||
private TunnelInfo _outTunnel;
|
||||
private ReplySelector _selector;
|
||||
private SendSuccessJob _replyFound;
|
||||
private SendTimeoutJob _replyTimeout;
|
||||
private int _timeoutMs;
|
||||
public DispatchJob(RouterContext ctx, GarlicMessage msg, TunnelInfo out, ReplySelector sel, SendSuccessJob success, SendTimeoutJob timeout, int timeoutMs) {
|
||||
public DispatchJob(RouterContext ctx, GarlicMessage msg, ReplySelector sel, SendSuccessJob success, SendTimeoutJob timeout, int timeoutMs) {
|
||||
super(ctx);
|
||||
_msg = msg;
|
||||
_outTunnel = out;
|
||||
_selector = sel;
|
||||
_replyFound = success;
|
||||
_replyTimeout = timeout;
|
||||
@@ -365,7 +381,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
public String getName() { return "Dispatch outbound client message"; }
|
||||
public void runJob() {
|
||||
getContext().messageRegistry().registerPending(_selector, _replyFound, _replyTimeout, _timeoutMs);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Dispatching message to " + _toString + ": " + _msg);
|
||||
getContext().tunnelDispatcher().dispatchOutbound(_msg, _outTunnel.getSendTunnelId(0), _lease.getTunnelId(), _lease.getGateway());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Dispatching message to " + _toString + " complete");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -378,6 +398,13 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
private TunnelInfo selectOutboundTunnel() {
|
||||
return getContext().tunnelManager().selectOutboundTunnel(_from.calculateHash());
|
||||
}
|
||||
/**
|
||||
* Pick an arbitrary outbound tunnel for any deliveryStatusMessage to come back in
|
||||
*
|
||||
*/
|
||||
private TunnelInfo selectInboundTunnel() {
|
||||
return getContext().tunnelManager().selectInboundTunnel(_from.calculateHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* give up the ghost, this message just aint going through. tell the client to fuck off.
|
||||
@@ -391,7 +418,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
long sendTime = getContext().clock().now() - _start;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getJobId() + ": Failed to send the message " + _clientMessageId + " after "
|
||||
+ sendTime + "ms", new Exception("Message send failure"));
|
||||
+ sendTime + "ms");
|
||||
|
||||
long messageDelay = getContext().throttle().getMessageDelay();
|
||||
long tunnelLag = getContext().throttle().getTunnelLag();
|
||||
@@ -427,6 +454,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
|
||||
DataMessage msg = new DataMessage(getContext());
|
||||
msg.setData(_clientMessage.getPayload().getEncryptedData());
|
||||
msg.setMessageExpiration(_overallExpiration);
|
||||
|
||||
clove.setPayload(msg);
|
||||
clove.setRecipientPublicKey(null);
|
||||
@@ -471,6 +499,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "sending " + _toString + " waiting for token " + _pendingToken
|
||||
+ " for cloveId " + _cloveId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -516,8 +549,14 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
|
||||
getContext().statManager().addRateData("client.sendAckTime", sendTime, 0);
|
||||
getContext().statManager().addRateData("client.sendMessageSize", _clientMessageSize, sendTime);
|
||||
if (_outTunnel != null)
|
||||
for (int i = 0; i < _outTunnel.getLength(); i++)
|
||||
getContext().profileManager().tunnelTestSucceeded(_outTunnel.getPeer(i), sendTime);
|
||||
if (_inTunnel != null)
|
||||
for (int i = 0; i < _inTunnel.getLength(); i++)
|
||||
getContext().profileManager().tunnelTestSucceeded(_inTunnel.getPeer(i), sendTime);
|
||||
}
|
||||
|
||||
|
||||
public void setMessage(I2NPMessage msg) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@ public class SendMessageDirectJob extends JobImpl {
|
||||
_message = message;
|
||||
_targetHash = toPeer;
|
||||
_router = null;
|
||||
if (timeoutMs < 5*1000) {
|
||||
if (timeoutMs < 10*1000) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Very little time given [" + timeoutMs + "], resetting to 5s", new Exception("stingy bastard"));
|
||||
_expiration = ctx.clock().now() + 5*1000;
|
||||
_expiration = ctx.clock().now() + 10*1000;
|
||||
} else {
|
||||
_expiration = timeoutMs + ctx.clock().now();
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -264,8 +264,9 @@ class SearchJob extends JobImpl {
|
||||
_log.error(getJobId() + ": Dont send search to ourselves - why did we try?");
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Send search to " + router.getIdentity().getHash().toBase64());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Send search to " + router.getIdentity().getHash().toBase64()
|
||||
+ " for " + _state.getTarget().toBase64());
|
||||
}
|
||||
|
||||
getContext().statManager().addRateData("netDb.searchMessageCount", 1, 0);
|
||||
@@ -330,8 +331,8 @@ class SearchJob extends JobImpl {
|
||||
|
||||
DatabaseLookupMessage msg = buildMessage(expiration);
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Sending router search to " + router.getIdentity().getHash().toBase64()
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Sending router search to " + router.getIdentity().getHash().toBase64()
|
||||
+ " for " + msg.getSearchKey().toBase64() + " w/ replies to us ["
|
||||
+ msg.getFrom().toBase64() + "]");
|
||||
SearchMessageSelector sel = new SearchMessageSelector(getContext(), router, _expiration, _state);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,6 +19,7 @@ public class PeerProfile {
|
||||
private long _lastSentToSuccessfully;
|
||||
private long _lastFailedSend;
|
||||
private long _lastHeardFrom;
|
||||
private double _tunnelTestResponseTimeAvg;
|
||||
// periodic rates
|
||||
private RateStat _sendSuccessSize = null;
|
||||
private RateStat _sendFailureSize = null;
|
||||
@@ -26,6 +27,7 @@ public class PeerProfile {
|
||||
private RateStat _dbResponseTime = null;
|
||||
private RateStat _tunnelCreateResponseTime = null;
|
||||
private RateStat _tunnelTestResponseTime = null;
|
||||
private RateStat _tunnelTestResponseTimeSlow = null;
|
||||
private RateStat _commError = null;
|
||||
private RateStat _dbIntroduction = null;
|
||||
// calculation bonuses
|
||||
@@ -38,6 +40,7 @@ public class PeerProfile {
|
||||
private double _reliabilityValue;
|
||||
private double _capacityValue;
|
||||
private double _integrationValue;
|
||||
private double _oldSpeedValue;
|
||||
private boolean _isFailing;
|
||||
// good vs bad behavior
|
||||
private TunnelHistory _tunnelHistory;
|
||||
@@ -59,6 +62,7 @@ public class PeerProfile {
|
||||
_integrationValue = 0;
|
||||
_isFailing = false;
|
||||
_consecutiveShitlists = 0;
|
||||
_tunnelTestResponseTimeAvg = 0.0d;
|
||||
_peer = peer;
|
||||
if (expand)
|
||||
expandProfile();
|
||||
@@ -142,6 +146,8 @@ public class PeerProfile {
|
||||
public RateStat getTunnelCreateResponseTime() { return _tunnelCreateResponseTime; }
|
||||
/** how long it takes to successfully test a tunnel this peer participates in (in milliseconds), calculated over a 10 minute, 1 hour, and 1 day period */
|
||||
public RateStat getTunnelTestResponseTime() { return _tunnelTestResponseTime; }
|
||||
/** how long it takes to successfully test the peer (in milliseconds) when the time exceeds 5s */
|
||||
public RateStat getTunnelTestResponseTimeSlow() { return _tunnelTestResponseTimeSlow; }
|
||||
/** how long between communication errors with the peer (disconnection, etc), calculated over a 1 minute, 1 hour, and 1 day period */
|
||||
public RateStat getCommError() { return _commError; }
|
||||
/** how many new peers we get from dbSearchReplyMessages or dbStore messages, calculated over a 1 hour, 1 day, and 1 week period */
|
||||
@@ -186,6 +192,7 @@ public class PeerProfile {
|
||||
*
|
||||
*/
|
||||
public double getSpeedValue() { return _speedValue; }
|
||||
public double getOldSpeedValue() { return _oldSpeedValue; }
|
||||
/**
|
||||
* How likely are they to stay up and pass on messages over the next few minutes.
|
||||
* Positive numbers means more likely, negative numbers means its probably not
|
||||
@@ -208,6 +215,24 @@ public class PeerProfile {
|
||||
* is this peer actively failing (aka not worth touching)?
|
||||
*/
|
||||
public boolean getIsFailing() { return _isFailing; }
|
||||
|
||||
public double getTunnelTestTimeAverage() { return _tunnelTestResponseTimeAvg; }
|
||||
void setTunnelTestTimeAverage(double avg) { _tunnelTestResponseTimeAvg = avg; }
|
||||
|
||||
void updateTunnelTestTimeAverage(long ms) {
|
||||
if (_tunnelTestResponseTimeAvg <= 0)
|
||||
_tunnelTestResponseTimeAvg = 30*1000; // should we instead start at $ms?
|
||||
|
||||
// weighted since we want to let the average grow quickly and shrink slowly
|
||||
if (ms < _tunnelTestResponseTimeAvg)
|
||||
_tunnelTestResponseTimeAvg = 0.95*_tunnelTestResponseTimeAvg + .05*(double)ms;
|
||||
else
|
||||
_tunnelTestResponseTimeAvg = 0.75*_tunnelTestResponseTimeAvg + .25*(double)ms;
|
||||
|
||||
if ( (_peer != null) && (_log.shouldLog(Log.INFO)) )
|
||||
_log.info("Updating tunnel test time for " + _peer.toBase64().substring(0,6)
|
||||
+ " to " + _tunnelTestResponseTimeAvg + " via " + ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* when the given peer is performing so poorly that we don't want to bother keeping
|
||||
@@ -222,6 +247,7 @@ public class PeerProfile {
|
||||
_dbResponseTime = null;
|
||||
_tunnelCreateResponseTime = null;
|
||||
_tunnelTestResponseTime = null;
|
||||
_tunnelTestResponseTimeSlow = null;
|
||||
_commError = null;
|
||||
_dbIntroduction = null;
|
||||
_tunnelHistory = null;
|
||||
@@ -250,7 +276,9 @@ public class PeerProfile {
|
||||
if (_tunnelCreateResponseTime == null)
|
||||
_tunnelCreateResponseTime = new RateStat("tunnelCreateResponseTime", "how long it takes to get a tunnel create response from the peer (in milliseconds)", group, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_tunnelTestResponseTime == null)
|
||||
_tunnelTestResponseTime = new RateStat("tunnelTestResponseTime", "how long it takes to successfully test a tunnel this peer participates in (in milliseconds)", group, new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
_tunnelTestResponseTime = new RateStat("tunnelTestResponseTime", "how long it takes to successfully test a tunnel this peer participates in (in milliseconds)", group, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 } );
|
||||
if (_tunnelTestResponseTimeSlow == null)
|
||||
_tunnelTestResponseTimeSlow = new RateStat("tunnelTestResponseTimeSlow", "how long it takes to successfully test a peer when the time exceeds 5s", group, new long[] { 10*60*1000l, 30*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l, });
|
||||
if (_commError == null)
|
||||
_commError = new RateStat("commErrorRate", "how long between communication errors with the peer (e.g. disconnection)", group, new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000 } );
|
||||
if (_dbIntroduction == null)
|
||||
@@ -267,6 +295,7 @@ public class PeerProfile {
|
||||
_dbResponseTime.setStatLog(_context.statManager().getStatLog());
|
||||
_tunnelCreateResponseTime.setStatLog(_context.statManager().getStatLog());
|
||||
_tunnelTestResponseTime.setStatLog(_context.statManager().getStatLog());
|
||||
_tunnelTestResponseTimeSlow.setStatLog(_context.statManager().getStatLog());
|
||||
_commError.setStatLog(_context.statManager().getStatLog());
|
||||
_dbIntroduction.setStatLog(_context.statManager().getStatLog());
|
||||
_expanded = true;
|
||||
@@ -283,10 +312,12 @@ public class PeerProfile {
|
||||
_sendSuccessSize.coalesceStats();
|
||||
_tunnelCreateResponseTime.coalesceStats();
|
||||
_tunnelTestResponseTime.coalesceStats();
|
||||
_tunnelTestResponseTimeSlow.coalesceStats();
|
||||
_dbHistory.coalesceStats();
|
||||
_tunnelHistory.coalesceStats();
|
||||
|
||||
_speedValue = calculateSpeed();
|
||||
_oldSpeedValue = calculateOldSpeed();
|
||||
_reliabilityValue = calculateReliability();
|
||||
_capacityValue = calculateCapacity();
|
||||
_integrationValue = calculateIntegration();
|
||||
@@ -297,6 +328,7 @@ public class PeerProfile {
|
||||
}
|
||||
|
||||
private double calculateSpeed() { return _context.speedCalculator().calc(this); }
|
||||
private double calculateOldSpeed() { return _context.oldSpeedCalculator().calc(this); }
|
||||
private double calculateReliability() { return _context.reliabilityCalculator().calc(this); }
|
||||
private double calculateCapacity() { return _context.capacityCalculator().calc(this); }
|
||||
private double calculateIntegration() { return _context.integrationCalculator().calc(this); }
|
||||
|
||||
@@ -46,7 +46,7 @@ class PersistProfilesJob extends JobImpl {
|
||||
PersistProfilesJob.this.getContext().jobQueue().addJob(PersistProfilesJob.this);
|
||||
} else {
|
||||
// we've got peers left to persist, so requeue the persist profile job
|
||||
PersistProfilesJob.this.getContext().jobQueue().addJob(PersistProfileJob.this);
|
||||
PersistProfilesJob.PersistProfileJob.this.requeue(1000);
|
||||
}
|
||||
}
|
||||
public String getName() { return "Persist profile"; }
|
||||
|
||||
@@ -111,7 +111,15 @@ public class ProfileManagerImpl implements ProfileManager {
|
||||
public void tunnelTestSucceeded(Hash peer, long responseTimeMs) {
|
||||
PeerProfile data = getProfile(peer);
|
||||
if (data == null) return;
|
||||
data.updateTunnelTestTimeAverage(responseTimeMs);
|
||||
data.getTunnelTestResponseTime().addData(responseTimeMs, responseTimeMs);
|
||||
if (responseTimeMs > getSlowThreshold())
|
||||
data.getTunnelTestResponseTimeSlow().addData(responseTimeMs, responseTimeMs);
|
||||
}
|
||||
|
||||
private int getSlowThreshold() {
|
||||
// perhaps we should have this compare vs. tunnel.testSuccessTime?
|
||||
return 5*1000;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -357,7 +357,7 @@ public class ProfileOrganizer {
|
||||
// we dont want the good peers, just random ones
|
||||
continue;
|
||||
} else {
|
||||
if (isOk(cur))
|
||||
if (isSelectable(cur))
|
||||
selected.add(cur);
|
||||
}
|
||||
}
|
||||
@@ -474,7 +474,7 @@ public class ProfileOrganizer {
|
||||
for (Iterator iter = _strictCapacityOrder.iterator(); iter.hasNext(); ) {
|
||||
PeerProfile cur = (PeerProfile)iter.next();
|
||||
if ( (!_fastPeers.containsKey(cur.getPeer())) && (!cur.getIsFailing()) ) {
|
||||
if (!isOk(cur.getPeer())) {
|
||||
if (!isSelectable(cur.getPeer())) {
|
||||
// skip peers we dont have in the netDb
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("skip unknown peer from fast promotion: " + cur.getPeer().toBase64());
|
||||
@@ -644,6 +644,10 @@ public class ProfileOrganizer {
|
||||
* (highest first) for active nonfailing peers
|
||||
*/
|
||||
private void locked_calculateSpeedThreshold(Set reordered) {
|
||||
if (true) {
|
||||
locked_calculateSpeedThresholdMean(reordered);
|
||||
return;
|
||||
}
|
||||
Set speeds = new TreeSet();
|
||||
for (Iterator iter = reordered.iterator(); iter.hasNext(); ) {
|
||||
PeerProfile profile = (PeerProfile)iter.next();
|
||||
@@ -669,6 +673,28 @@ public class ProfileOrganizer {
|
||||
_log.info("Threshold value for speed: " + _thresholdSpeedValue + " out of speeds: " + speeds);
|
||||
}
|
||||
|
||||
private void locked_calculateSpeedThresholdMean(Set reordered) {
|
||||
double total = 0;
|
||||
int count = 0;
|
||||
for (Iterator iter = reordered.iterator(); iter.hasNext(); ) {
|
||||
PeerProfile profile = (PeerProfile)iter.next();
|
||||
if (profile.getCapacityValue() >= _thresholdCapacityValue) {
|
||||
// duplicates being clobbered is fine by us
|
||||
total += profile.getSpeedValue();
|
||||
count++;
|
||||
} else {
|
||||
// its ordered
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
_thresholdSpeedValue = total / count;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Threshold value for speed: " + _thresholdSpeedValue + " out of speeds: " + count);
|
||||
}
|
||||
|
||||
|
||||
/** simple average, or 0 if NaN */
|
||||
private final static double avg(double total, double quantity) {
|
||||
if ( (total > 0) && (quantity > 0) )
|
||||
@@ -701,7 +727,7 @@ public class ProfileOrganizer {
|
||||
Collections.shuffle(all, _random);
|
||||
for (int i = 0; (matches.size() < howMany) && (i < all.size()); i++) {
|
||||
Hash peer = (Hash)all.get(i);
|
||||
boolean ok = isOk(peer);
|
||||
boolean ok = isSelectable(peer);
|
||||
if (ok)
|
||||
matches.add(peer);
|
||||
else
|
||||
@@ -709,7 +735,7 @@ public class ProfileOrganizer {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOk(Hash peer) {
|
||||
public boolean isSelectable(Hash peer) {
|
||||
NetworkDatabaseFacade netDb = _context.netDb();
|
||||
// the CLI shouldn't depend upon the netDb
|
||||
if (netDb == null) return true;
|
||||
@@ -755,7 +781,7 @@ public class ProfileOrganizer {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("High capacity: \t" + profile.getPeer().toBase64());
|
||||
if (_thresholdSpeedValue <= profile.getSpeedValue()) {
|
||||
if (!isOk(profile.getPeer())) {
|
||||
if (!isSelectable(profile.getPeer())) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Skipping fast mark [!ok] for " + profile.getPeer().toBase64());
|
||||
} else if (!profile.getIsActive()) {
|
||||
|
||||
@@ -6,10 +6,11 @@ import java.io.Writer;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.router.RouterContext;
|
||||
@@ -21,23 +22,25 @@ import net.i2p.router.RouterContext;
|
||||
class ProfileOrganizerRenderer {
|
||||
private RouterContext _context;
|
||||
private ProfileOrganizer _organizer;
|
||||
private ProfileComparator _comparator;
|
||||
|
||||
public ProfileOrganizerRenderer(ProfileOrganizer organizer, RouterContext context) {
|
||||
_context = context;
|
||||
_organizer = organizer;
|
||||
_comparator = new ProfileComparator();
|
||||
}
|
||||
public void renderStatusHTML(Writer out) throws IOException {
|
||||
Set peers = _organizer.selectAllPeers();
|
||||
|
||||
long hideBefore = _context.clock().now() - 3*60*60*1000;
|
||||
|
||||
TreeMap order = new TreeMap();
|
||||
TreeSet order = new TreeSet(_comparator);
|
||||
for (Iterator iter = peers.iterator(); iter.hasNext();) {
|
||||
Hash peer = (Hash)iter.next();
|
||||
if (_organizer.getUs().equals(peer)) continue;
|
||||
PeerProfile prof = _organizer.getProfile(peer);
|
||||
if (prof.getLastSendSuccessful() <= hideBefore) continue;
|
||||
order.put(peer.toBase64(), prof);
|
||||
order.add(prof);
|
||||
}
|
||||
|
||||
int fast = 0;
|
||||
@@ -56,11 +59,35 @@ class ProfileOrganizerRenderer {
|
||||
buf.append("<td><b>Failing?</b></td>");
|
||||
buf.append("<td> </td>");
|
||||
buf.append("</tr>");
|
||||
for (Iterator iter = order.keySet().iterator(); iter.hasNext();) {
|
||||
String name = (String)iter.next();
|
||||
PeerProfile prof = (PeerProfile)order.get(name);
|
||||
int prevTier = 1;
|
||||
for (Iterator iter = order.iterator(); iter.hasNext();) {
|
||||
PeerProfile prof = (PeerProfile)iter.next();
|
||||
Hash peer = prof.getPeer();
|
||||
|
||||
int tier = 0;
|
||||
boolean isIntegrated = false;
|
||||
if (_organizer.isFast(peer)) {
|
||||
tier = 1;
|
||||
fast++;
|
||||
reliable++;
|
||||
} else if (_organizer.isHighCapacity(peer)) {
|
||||
tier = 2;
|
||||
reliable++;
|
||||
} else if (_organizer.isFailing(peer)) {
|
||||
failing++;
|
||||
} else {
|
||||
tier = 3;
|
||||
}
|
||||
|
||||
if (_organizer.isWellIntegrated(peer)) {
|
||||
isIntegrated = true;
|
||||
integrated++;
|
||||
}
|
||||
|
||||
if (tier != prevTier)
|
||||
buf.append("<tr><td colspan=\"7\"><hr /></td></tr>\n");
|
||||
prevTier = tier;
|
||||
|
||||
buf.append("<tr>");
|
||||
buf.append("<td><code>");
|
||||
if (prof.getIsFailing()) {
|
||||
@@ -74,25 +101,6 @@ class ProfileOrganizerRenderer {
|
||||
}
|
||||
buf.append("</code></td>");
|
||||
buf.append("<td>");
|
||||
int tier = 0;
|
||||
boolean isIntegrated = false;
|
||||
if (_organizer.isFast(peer)) {
|
||||
tier = 1;
|
||||
fast++;
|
||||
reliable++;
|
||||
} else if (_organizer.isHighCapacity(peer)) {
|
||||
tier = 2;
|
||||
reliable++;
|
||||
} else if (_organizer.isFailing(peer)) {
|
||||
failing++;
|
||||
} else {
|
||||
tier = 3;
|
||||
}
|
||||
|
||||
if (_organizer.isWellIntegrated(peer)) {
|
||||
isIntegrated = true;
|
||||
integrated++;
|
||||
}
|
||||
|
||||
switch (tier) {
|
||||
case 1: buf.append("Fast"); break;
|
||||
@@ -102,7 +110,9 @@ class ProfileOrganizerRenderer {
|
||||
}
|
||||
if (isIntegrated) buf.append(", Integrated");
|
||||
|
||||
buf.append("<td align=\"right\">").append(num(prof.getSpeedValue())).append("</td>");
|
||||
buf.append("<td align=\"right\">").append(num(prof.getSpeedValue()));
|
||||
//buf.append('/').append(num(prof.getOldSpeedValue()));
|
||||
buf.append("</td>");
|
||||
buf.append("<td align=\"right\">").append(num(prof.getCapacityValue())).append("</td>");
|
||||
buf.append("<td align=\"right\">").append(num(prof.getIntegrationValue())).append("</td>");
|
||||
buf.append("<td align=\"right\">").append(prof.getIsFailing()).append("</td>");
|
||||
@@ -130,6 +140,56 @@ class ProfileOrganizerRenderer {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private class ProfileComparator implements Comparator {
|
||||
public int compare(Object lhs, Object rhs) {
|
||||
if ( (lhs == null) || (rhs == null) )
|
||||
throw new NullPointerException("lhs=" + lhs + " rhs=" + rhs);
|
||||
if ( !(lhs instanceof PeerProfile) || !(rhs instanceof PeerProfile) )
|
||||
throw new ClassCastException("lhs=" + lhs.getClass().getName() + " rhs=" + rhs.getClass().getName());
|
||||
|
||||
PeerProfile left = (PeerProfile)lhs;
|
||||
PeerProfile right = (PeerProfile)rhs;
|
||||
|
||||
if (_context.profileOrganizer().isFast(left.getPeer())) {
|
||||
if (_context.profileOrganizer().isFast(right.getPeer())) {
|
||||
return compareHashes(left, right);
|
||||
} else {
|
||||
return -1; // fast comes first
|
||||
}
|
||||
} else if (_context.profileOrganizer().isHighCapacity(left.getPeer())) {
|
||||
if (_context.profileOrganizer().isFast(right.getPeer())) {
|
||||
return 1;
|
||||
} else if (_context.profileOrganizer().isHighCapacity(right.getPeer())) {
|
||||
return compareHashes(left, right);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (_context.profileOrganizer().isFailing(left.getPeer())) {
|
||||
if (_context.profileOrganizer().isFailing(right.getPeer())) {
|
||||
return compareHashes(left, right);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
// left is not failing
|
||||
if (_context.profileOrganizer().isFast(right.getPeer())) {
|
||||
return 1;
|
||||
} else if (_context.profileOrganizer().isHighCapacity(right.getPeer())) {
|
||||
return 1;
|
||||
} else if (_context.profileOrganizer().isFailing(right.getPeer())) {
|
||||
return -1;
|
||||
} else {
|
||||
return compareHashes(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int compareHashes(PeerProfile left, PeerProfile right) {
|
||||
return left.getPeer().toBase64().compareTo(right.getPeer().toBase64());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final static DecimalFormat _fmt = new DecimalFormat("###,##0.00", new DecimalFormatSymbols(Locale.UK));
|
||||
private final static String num(double num) { synchronized (_fmt) { return _fmt.format(num); } }
|
||||
}
|
||||
|
||||
@@ -109,6 +109,8 @@ class ProfilePersistenceHelper {
|
||||
buf.append("lastFailedSend=").append(profile.getLastSendFailed()).append(NL);
|
||||
buf.append("# Last heard from: when did we last get a message from the peer? (milliseconds from the epoch)").append(NL);
|
||||
buf.append("lastHeardFrom=").append(profile.getLastHeardFrom()).append(NL);
|
||||
buf.append("# moving average as to how fast the peer replies").append(NL);
|
||||
buf.append("tunnelTestTimeAverage=").append(profile.getTunnelTestTimeAverage()).append(NL);
|
||||
buf.append(NL);
|
||||
|
||||
out.write(buf.toString().getBytes());
|
||||
@@ -126,6 +128,7 @@ class ProfilePersistenceHelper {
|
||||
profile.getSendSuccessSize().store(out, "sendSuccessSize");
|
||||
profile.getTunnelCreateResponseTime().store(out, "tunnelCreateResponseTime");
|
||||
profile.getTunnelTestResponseTime().store(out, "tunnelTestResponseTime");
|
||||
profile.getTunnelTestResponseTimeSlow().store(out, "tunnelTestResponseTimeSlow");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +180,7 @@ class ProfilePersistenceHelper {
|
||||
profile.setLastSendSuccessful(getLong(props, "lastSentToSuccessfully"));
|
||||
profile.setLastSendFailed(getLong(props, "lastFailedSend"));
|
||||
profile.setLastHeardFrom(getLong(props, "lastHeardFrom"));
|
||||
profile.setTunnelTestTimeAverage(getDouble(props, "tunnelTestTimeAverage"));
|
||||
|
||||
profile.getTunnelHistory().load(props);
|
||||
profile.getDBHistory().load(props);
|
||||
@@ -189,6 +193,7 @@ class ProfilePersistenceHelper {
|
||||
profile.getSendSuccessSize().load(props, "sendSuccessSize", true);
|
||||
profile.getTunnelCreateResponseTime().load(props, "tunnelCreateResponseTime", true);
|
||||
profile.getTunnelTestResponseTime().load(props, "tunnelTestResponseTime", true);
|
||||
profile.getTunnelTestResponseTimeSlow().load(props, "tunnelTestResponseTimeSlow", true);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Loaded the profile for " + peer.toBase64() + " from " + file.getName());
|
||||
@@ -212,6 +217,18 @@ class ProfilePersistenceHelper {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private final static double getDouble(Properties props, String key) {
|
||||
String val = props.getProperty(key);
|
||||
if (val != null) {
|
||||
try {
|
||||
return Double.parseDouble(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private void loadProps(Properties props, File file) {
|
||||
try {
|
||||
|
||||
@@ -38,6 +38,7 @@ public class SpeedCalculator extends Calculator {
|
||||
}
|
||||
|
||||
public double calc(PeerProfile profile) {
|
||||
if (true) return calcAverage(profile);
|
||||
long threshold = getEventThreshold();
|
||||
boolean tunnelTestOnly = getUseTunnelTestOnly();
|
||||
|
||||
@@ -72,10 +73,35 @@ public class SpeedCalculator extends Calculator {
|
||||
double estimateFactor = getEstimateFactor(threshold, events);
|
||||
double rv = (1-estimateFactor)*measuredRTPerMinute + (estimateFactor)*estimatedRTPerMinute;
|
||||
|
||||
long slowCount = 0;
|
||||
RateStat rs = profile.getTunnelTestResponseTimeSlow();
|
||||
if (rs != null) {
|
||||
Rate r = rs.getRate(period);
|
||||
if (r != null)
|
||||
slowCount = r.getCurrentEventCount();
|
||||
}
|
||||
long fastCount = 0;
|
||||
rs = profile.getTunnelTestResponseTime();
|
||||
if (rs != null) {
|
||||
Rate r = rs.getRate(period);
|
||||
if (r != null)
|
||||
fastCount = r.getCurrentEventCount();
|
||||
}
|
||||
double slowPct = 0;
|
||||
if (fastCount > 0)
|
||||
slowPct = slowCount / fastCount;
|
||||
else
|
||||
rv /= 100; // if there are no tunnel tests, weigh against it
|
||||
if (slowPct > 0.01) // if 1% of the traffic is dogshit slow, the peer probably sucks
|
||||
rv /= 100.0*slowPct;
|
||||
|
||||
rv = adjust(period, rv);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("\n\nrv: " + rv + " events: " + events + " threshold: " + threshold + " period: " + period + " useTunnelTestOnly? " + tunnelTestOnly + "\n"
|
||||
+ "measuredRTT: " + measuredRoundTripTime + " measured events per minute: " + measuredRTPerMinute + "\n"
|
||||
+ "estimateRTT: " + estimatedRoundTripTime + " estimated events per minute: " + estimatedRTPerMinute + "\n"
|
||||
+ "slow count: " + slowCount + " fast count: " + fastCount + "\n"
|
||||
+ "estimateFactor: " + estimateFactor + "\n"
|
||||
+ "for peer: " + profile.getPeer().toBase64());
|
||||
}
|
||||
@@ -83,6 +109,27 @@ public class SpeedCalculator extends Calculator {
|
||||
rv += profile.getSpeedBonus();
|
||||
return rv;
|
||||
}
|
||||
|
||||
private double calcAverage(PeerProfile profile) {
|
||||
double avg = profile.getTunnelTestTimeAverage();
|
||||
if (avg == 0)
|
||||
return 0.0;
|
||||
else
|
||||
return (60.0*1000.0) / avg;
|
||||
}
|
||||
|
||||
private double adjust(long period, double value) {
|
||||
switch ((int)period) {
|
||||
case 10*60*1000:
|
||||
return value;
|
||||
case 60*60*1000:
|
||||
return value * 0.75;
|
||||
case 24*60*60*1000:
|
||||
return value * 0.1;
|
||||
default:
|
||||
return value * 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How much do we want to prefer the measured values more than the estimated
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package net.i2p.router.peermanager;
|
||||
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple speed calculator that just counts how many messages go through the
|
||||
* tunnel.
|
||||
*
|
||||
*/
|
||||
public class StrictSpeedCalculator extends Calculator {
|
||||
private Log _log;
|
||||
private RouterContext _context;
|
||||
|
||||
public StrictSpeedCalculator(RouterContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(StrictSpeedCalculator.class);
|
||||
}
|
||||
|
||||
public double calc(PeerProfile profile) {
|
||||
return countSuccesses(profile);
|
||||
}
|
||||
private double countSuccesses(PeerProfile profile) {
|
||||
RateStat success = profile.getTunnelHistory().getProcessSuccessRate();
|
||||
RateStat failure = profile.getTunnelHistory().getProcessFailureRate();
|
||||
return messagesPerMinute(success, failure);
|
||||
}
|
||||
private double messagesPerMinute(RateStat success, RateStat failure) {
|
||||
double rv = 0.0d;
|
||||
if (success != null) {
|
||||
Rate rate = null;
|
||||
long periods[] = success.getPeriods();
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
rate = success.getRate(periods[i]);
|
||||
if ( (rate != null) && (rate.getCurrentTotalValue() > 0) )
|
||||
break;
|
||||
}
|
||||
|
||||
double value = rate.getCurrentTotalValue();
|
||||
value += rate.getLastTotalValue();
|
||||
rv = value * 10.0d * 60.0d * 1000.0d / (double)rate.getPeriod();
|
||||
|
||||
// if any of the messages are getting fragmented and cannot be
|
||||
// handled, penalize like crazy
|
||||
Rate fail = failure.getRate(rate.getPeriod());
|
||||
if (fail.getCurrentTotalValue() > 0)
|
||||
rv /= fail.getCurrentTotalValue();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
public double calc(PeerProfile profile) {
|
||||
double successCount = countSuccesses(profile);
|
||||
double failureCount = countFailures(profile);
|
||||
|
||||
double rv = successCount - 5*failureCount;
|
||||
if (rv < 0)
|
||||
rv = 0;
|
||||
return rv;
|
||||
}
|
||||
private double countSuccesses(PeerProfile profile) {
|
||||
RateStat success = profile.getTunnelHistory().getProcessSuccessRate();
|
||||
return messagesPerMinute(success);
|
||||
}
|
||||
private double countFailures(PeerProfile profile) {
|
||||
RateStat failure = profile.getTunnelHistory().getProcessFailureRate();
|
||||
return messagesPerMinute(failure);
|
||||
}
|
||||
private double messagesPerMinute(RateStat stat) {
|
||||
double rv = 0.0d;
|
||||
if (stat != null) {
|
||||
Rate rate = null;
|
||||
long periods[] = stat.getPeriods();
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
rate = stat.getRate(periods[i]);
|
||||
if ( (rate != null) && (rate.getCurrentTotalValue() > 0) )
|
||||
break;
|
||||
}
|
||||
|
||||
double value = rate.getCurrentTotalValue();
|
||||
value += rate.getLastTotalValue();
|
||||
rv = value * 60.0d * 1000.0d / (double)rate.getPeriod();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -26,6 +26,8 @@ public class TunnelHistory {
|
||||
private volatile long _lastFailed;
|
||||
private RateStat _rejectRate;
|
||||
private RateStat _failRate;
|
||||
private RateStat _processSuccessRate;
|
||||
private RateStat _processFailureRate;
|
||||
private String _statGroup;
|
||||
|
||||
/** probabalistic tunnel rejection due to a flood of requests */
|
||||
@@ -47,8 +49,12 @@ public class TunnelHistory {
|
||||
private void createRates(String statGroup) {
|
||||
_rejectRate = new RateStat("tunnelHistory.rejectRate", "How often does this peer reject a tunnel request?", statGroup, new long[] { 60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
_failRate = new RateStat("tunnelHistory.failRate", "How often do tunnels this peer accepts fail?", statGroup, new long[] { 60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
_processSuccessRate = new RateStat("tunnelHistory.processSuccessRate", "How many messages does a tunnel process?", statGroup, new long[] { 5*60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
_processFailureRate = new RateStat("tunnelHistory.processfailureRate", "How many messages does a tunnel fail?", statGroup, new long[] { 5*60*1000l, 10*60*1000l, 30*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
_rejectRate.setStatLog(_context.statManager().getStatLog());
|
||||
_failRate.setStatLog(_context.statManager().getStatLog());
|
||||
_processSuccessRate.setStatLog(_context.statManager().getStatLog());
|
||||
_processFailureRate.setStatLog(_context.statManager().getStatLog());
|
||||
}
|
||||
|
||||
/** total tunnels the peer has agreed to participate in */
|
||||
@@ -70,6 +76,13 @@ public class TunnelHistory {
|
||||
/** when the last tunnel the peer participated in failed */
|
||||
public long getLastFailed() { return _lastFailed; }
|
||||
|
||||
public void incrementProcessed(int processedSuccessfully, int failedProcessing) {
|
||||
if (processedSuccessfully > 0)
|
||||
_processSuccessRate.addData(processedSuccessfully, 0);
|
||||
if (failedProcessing > 0)
|
||||
_processFailureRate.addData(failedProcessing, 0);
|
||||
}
|
||||
|
||||
public void incrementAgreedTo() {
|
||||
_lifetimeAgreedTo++;
|
||||
_lastAgreedTo = _context.clock().now();
|
||||
@@ -113,12 +126,16 @@ public class TunnelHistory {
|
||||
|
||||
public RateStat getRejectionRate() { return _rejectRate; }
|
||||
public RateStat getFailedRate() { return _failRate; }
|
||||
public RateStat getProcessSuccessRate() { return _processSuccessRate; }
|
||||
public RateStat getProcessFailureRate() { return _processFailureRate; }
|
||||
|
||||
public void coalesceStats() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Coallescing stats");
|
||||
_rejectRate.coalesceStats();
|
||||
_failRate.coalesceStats();
|
||||
_processFailureRate.coalesceStats();
|
||||
_processSuccessRate.coalesceStats();
|
||||
}
|
||||
|
||||
private final static String NL = System.getProperty("line.separator");
|
||||
@@ -140,7 +157,9 @@ public class TunnelHistory {
|
||||
add(buf, "lifetimeRejected", _lifetimeRejected, "How many tunnels has the peer ever refused to participate in?");
|
||||
out.write(buf.toString().getBytes());
|
||||
_rejectRate.store(out, "tunnelHistory.rejectRate");
|
||||
_rejectRate.store(out, "tunnelHistory.failRate");
|
||||
_failRate.store(out, "tunnelHistory.failRate");
|
||||
_processSuccessRate.store(out, "tunnelHistory.processSuccessRate");
|
||||
_processFailureRate.store(out, "tunnelHistory.processFailureRate");
|
||||
}
|
||||
|
||||
private void add(StringBuffer buf, String name, long val, String description) {
|
||||
@@ -162,9 +181,15 @@ public class TunnelHistory {
|
||||
_rejectRate.load(props, "tunnelHistory.rejectRate", true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Loading tunnelHistory.rejectRate");
|
||||
_rejectRate.load(props, "tunnelHistory.failRate", true);
|
||||
_failRate.load(props, "tunnelHistory.failRate", true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Loading tunnelHistory.failRate");
|
||||
_processFailureRate.load(props, "tunnelHistory.processFailureRate", true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Loading tunnelHistory.processFailureRate");
|
||||
_processSuccessRate.load(props, "tunnelHistory.processSuccessRate", true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Loading tunnelHistory.processSuccessRate");
|
||||
} catch (IllegalArgumentException iae) {
|
||||
_log.warn("TunnelHistory rates are corrupt, resetting", iae);
|
||||
createRates(_statGroup);
|
||||
|
||||
@@ -76,7 +76,7 @@ public class ConnectionBuilder {
|
||||
private String _error;
|
||||
|
||||
/** If the connection hasn't been built in 30 seconds, give up */
|
||||
public static final int CONNECTION_TIMEOUT = 30*1000;
|
||||
public static final int CONNECTION_TIMEOUT = 20*1000;
|
||||
|
||||
public static final int WRITE_BUFFER_SIZE = 2*1024;
|
||||
|
||||
|
||||
@@ -87,9 +87,7 @@ public class TCPTransport extends TransportImpl {
|
||||
public static final int DEFAULT_ESTABLISHERS = 3;
|
||||
|
||||
/** Ordered list of supported I2NP protocols */
|
||||
public static final int[] SUPPORTED_PROTOCOLS = new int[] { 2
|
||||
, 3 // forward compat, so we can drop 0.5 builds after 0.5.0.2
|
||||
};
|
||||
public static final int[] SUPPORTED_PROTOCOLS = new int[] { 4 }; // drop <= 0.5.0.3
|
||||
/** blah, people shouldnt use defaults... */
|
||||
public static final int DEFAULT_LISTEN_PORT = 8887;
|
||||
|
||||
@@ -196,6 +194,7 @@ public class TCPTransport extends TransportImpl {
|
||||
newPeer = true;
|
||||
}
|
||||
msgs.add(msg);
|
||||
msg.timestamp("TCPTransport.outboundMessageReady queued behind " +(msgs.size()-1));
|
||||
|
||||
if (newPeer)
|
||||
_connectionLock.notifyAll();
|
||||
@@ -305,17 +304,18 @@ public class TCPTransport extends TransportImpl {
|
||||
void connectionClosed(TCPConnection con) {
|
||||
synchronized (_connectionLock) {
|
||||
TCPConnection cur = (TCPConnection)_connectionsByIdent.remove(con.getRemoteRouterIdentity().getHash());
|
||||
if (cur != con)
|
||||
if ( (cur != null) && (cur != con) )
|
||||
_connectionsByIdent.put(cur.getRemoteRouterIdentity().getHash(), cur);
|
||||
cur = (TCPConnection)_connectionsByAddress.remove(con.getRemoteAddress().toString());
|
||||
if (cur != con)
|
||||
if ( (cur != null) && (cur != con) )
|
||||
_connectionsByAddress.put(cur.getRemoteAddress().toString(), cur);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
StringBuffer buf = new StringBuffer(256);
|
||||
buf.append("\nCLOSING ").append(con.getRemoteRouterIdentity().getHash().toBase64().substring(0,6));
|
||||
buf.append(".");
|
||||
buf.append("\nconnectionsByAddress: (cur=").append(con.getRemoteAddress().toString()).append(") ");
|
||||
if (cur != null)
|
||||
buf.append("\nconnectionsByAddress: (cur=").append(con.getRemoteAddress().toString()).append(") ");
|
||||
for (Iterator iter = _connectionsByAddress.keySet().iterator(); iter.hasNext(); ) {
|
||||
String addr = (String)iter.next();
|
||||
buf.append(addr).append(" ");
|
||||
|
||||
184
router/java/src/net/i2p/router/tunnel/BatchedFragmentTest.java
Normal file
184
router/java/src/net/i2p/router/tunnel/BatchedFragmentTest.java
Normal 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();
|
||||
}
|
||||
}
|
||||
203
router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
Normal file
203
router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,13 @@ public class FragmentHandler {
|
||||
private Log _log;
|
||||
private Map _fragmentedMessages;
|
||||
private DefragmentedReceiver _receiver;
|
||||
private int _completed;
|
||||
private int _failed;
|
||||
|
||||
/** don't wait more than 60s to defragment the partial message */
|
||||
private static final long MAX_DEFRAGMENT_TIME = 60*1000;
|
||||
|
||||
static long MAX_DEFRAGMENT_TIME = 60*1000;
|
||||
private static final ByteCache _cache = ByteCache.getInstance(512, TrivialPreprocessor.PREPROCESSED_SIZE);
|
||||
|
||||
public FragmentHandler(I2PAppContext context, DefragmentedReceiver receiver) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(FragmentHandler.class);
|
||||
@@ -46,6 +49,8 @@ public class FragmentHandler {
|
||||
"Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("tunnel.fragmentedDropped", "How many fragments were in a partially received yet failed message?",
|
||||
"Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("tunnel.corruptMessage", "How many corrupted messages arrived?",
|
||||
"Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,8 +63,11 @@ public class FragmentHandler {
|
||||
public void receiveTunnelMessage(byte preprocessed[], int offset, int length) {
|
||||
boolean ok = verifyPreprocessed(preprocessed, offset, length);
|
||||
if (!ok) {
|
||||
_log.error("Unable to verify preprocessed data (pre.length=" + preprocessed.length
|
||||
+ " off=" +offset + " len=" + length, new Exception("failed"));
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to verify preprocessed data (pre.length="
|
||||
+ preprocessed.length + " off=" +offset + " len=" + length);
|
||||
_cache.release(new ByteArray(preprocessed));
|
||||
_context.statManager().addRateData("tunnel.corruptMessage", 1, 1);
|
||||
return;
|
||||
}
|
||||
offset += HopProcessor.IV_LENGTH; // skip the IV
|
||||
@@ -80,10 +88,19 @@ public class FragmentHandler {
|
||||
} catch (RuntimeException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Corrupt fragment received: offset = " + offset, e);
|
||||
_context.statManager().addRateData("tunnel.corruptMessage", 1, 1);
|
||||
throw e;
|
||||
} finally {
|
||||
// each of the FragmentedMessages populated make a copy out of the
|
||||
// payload, which they release separately, so we can release
|
||||
// immediately
|
||||
_cache.release(new ByteArray(preprocessed));
|
||||
}
|
||||
}
|
||||
|
||||
public int getCompleteCount() { return _completed; }
|
||||
public int getFailedCount() { return _failed; }
|
||||
|
||||
private static final ByteCache _validateCache = ByteCache.getInstance(512, TrivialPreprocessor.PREPROCESSED_SIZE);
|
||||
|
||||
/**
|
||||
@@ -98,8 +115,19 @@ public class FragmentHandler {
|
||||
private boolean verifyPreprocessed(byte preprocessed[], int offset, int length) {
|
||||
// now we need to verify that the message was received correctly
|
||||
int paddingEnd = HopProcessor.IV_LENGTH + 4;
|
||||
while (preprocessed[offset+paddingEnd] != (byte)0x00)
|
||||
while (preprocessed[offset+paddingEnd] != (byte)0x00) {
|
||||
paddingEnd++;
|
||||
if (offset+paddingEnd >= length) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Corrupt tunnel message padding");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("cannot verify, going past the end [off="
|
||||
+ offset + " len=" + length + " paddingEnd="
|
||||
+ paddingEnd + " data:\n"
|
||||
+ Base64.encode(preprocessed, offset, length));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
paddingEnd++; // skip the last
|
||||
|
||||
ByteArray ba = _validateCache.acquire(); // larger than necessary, but always sufficient
|
||||
@@ -117,10 +145,11 @@ public class FragmentHandler {
|
||||
boolean eq = DataHelper.eq(v.getData(), 0, preprocessed, offset + HopProcessor.IV_LENGTH, 4);
|
||||
if (!eq) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Endpoint data doesn't match: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("nomatching endpoint: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1) + "\n"
|
||||
+ Base64.encode(preprocessed, offset + paddingEnd, preV.length-HopProcessor.IV_LENGTH));
|
||||
_log.error("Corrupt tunnel message - verification fails");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("nomatching endpoint: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1) + "\n"
|
||||
+ " offset=" + offset + " length=" + length + " paddingEnd=" + paddingEnd
|
||||
+ Base64.encode(preprocessed, offset, length));
|
||||
}
|
||||
|
||||
_context.sha().cache().release(cache);
|
||||
@@ -249,6 +278,9 @@ public class FragmentHandler {
|
||||
}
|
||||
|
||||
offset += size;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Handling finished message " + msg.getMessageId() + " at offset " + offset);
|
||||
return offset;
|
||||
}
|
||||
|
||||
@@ -312,6 +344,7 @@ public class FragmentHandler {
|
||||
}
|
||||
|
||||
private void receiveComplete(FragmentedMessage msg) {
|
||||
_completed++;
|
||||
try {
|
||||
byte data[] = msg.toByteArray();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -362,6 +395,7 @@ public class FragmentHandler {
|
||||
removed = (null != _fragmentedMessages.remove(new Long(_msg.getMessageId())));
|
||||
}
|
||||
if (removed && !_msg.getReleased()) {
|
||||
_failed++;
|
||||
noteFailure(_msg.getMessageId());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropped failed fragmented message: " + _msg);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + "]: "
|
||||
|
||||
@@ -90,6 +90,8 @@ public class InboundMessageDistributor implements GarlicMessageReceiver.CloveRec
|
||||
+ " failing to distribute " + msg);
|
||||
return;
|
||||
}
|
||||
if (msg.getMessageExpiration() < _context.clock().now() + 10*1000)
|
||||
msg.setMessageExpiration(_context.clock().now() + 10*1000);
|
||||
_context.tunnelDispatcher().dispatchOutbound(msg, outId, tunnel, target);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user