propagate from branch 'i2p.i2p' (head 60a9a2297abeaf042645e3f0bc8d106f1ff585bf)

to branch 'i2p.i2p.zzz.test2' (head 6ff6f0bcee835d32aad62449a37f5171afde915a)
This commit is contained in:
zzz
2014-09-13 14:50:11 +00:00
158 changed files with 2982 additions and 947 deletions

View File

@@ -100,15 +100,15 @@
<target name="war" depends="jar, bundle, warUpToDate, listChangedFiles" unless="war.uptodate" >
<!-- set if unset -->
<property name="workspace.changes.tr" value="" />
<copy todir="build/icons/.icons" >
<fileset dir="../icons/" />
<copy todir="build/resources/.resources" >
<fileset dir="../resources/" />
</copy>
<!-- mime.properties must be in with the classes -->
<copy file="../mime.properties" todir="build/obj/org/klomp/snark/web" />
<war destfile="../i2psnark.war" webxml="../web.xml" >
<!-- include only the web stuff, as of 0.7.12 the router will add i2psnark.jar to the classpath for the war -->
<classes dir="./build/obj" includes="**/web/*" />
<fileset dir="build/icons/" />
<fileset dir="build/resources/" />
<manifest>
<attribute name="Implementation-Version" value="${full.version}" />
<attribute name="Built-By" value="${build.built-by}" />
@@ -121,7 +121,7 @@
<target name="warUpToDate">
<uptodate property="war.uptodate" targetfile="../i2psnark.war" >
<srcfiles dir= "." includes="build/obj/org/klomp/snark/web/*.class ../icons/* ../web.xml" />
<srcfiles dir= "." includes="build/obj/org/klomp/snark/web/*.class ../resources/**/* ../web.xml" />
</uptodate>
</target>

View File

@@ -57,8 +57,8 @@ public class Peer implements Comparable<Peer>
private DataOutputStream dout;
/** running counters */
private long downloaded;
private long uploaded;
private final AtomicLong downloaded = new AtomicLong();
private final AtomicLong uploaded = new AtomicLong();
// Keeps state for in/out connections. Non-null when the handshake
// was successful, the connection setup and runs
@@ -618,7 +618,7 @@ public class Peer implements Comparable<Peer>
* @since 0.8.4
*/
public void downloaded(int size) {
downloaded += size;
downloaded.addAndGet(size);
}
/**
@@ -626,7 +626,7 @@ public class Peer implements Comparable<Peer>
* @since 0.8.4
*/
public void uploaded(int size) {
uploaded += size;
uploaded.addAndGet(size);
}
/**
@@ -635,7 +635,7 @@ public class Peer implements Comparable<Peer>
*/
public long getDownloaded()
{
return downloaded;
return downloaded.get();
}
/**
@@ -644,7 +644,7 @@ public class Peer implements Comparable<Peer>
*/
public long getUploaded()
{
return uploaded;
return uploaded.get();
}
/**
@@ -652,8 +652,8 @@ public class Peer implements Comparable<Peer>
*/
public void resetCounters()
{
downloaded = 0;
uploaded = 0;
downloaded.set(0);
uploaded.set(0);
}
public long getInactiveTime() {

View File

@@ -27,7 +27,6 @@ import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
@@ -245,16 +244,19 @@ public class Snark
*
* @deprecated unused
*/
/****
Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener) {
this(util, torrent, ip, user_port, slistener, clistener, null, null, null, true, ".");
}
****/
/**
* single torrent - via router
*
* @deprecated unused
*/
/****
public Snark(I2PAppContext ctx, Properties opts, String torrent,
StorageListener slistener, boolean start, String rootDir) {
this(new I2PSnarkUtil(ctx), torrent, null, -1, slistener, null, null, null, null, false, rootDir);
@@ -284,6 +286,7 @@ public class Snark
if (start)
this.startTorrent();
}
****/
/**
* multitorrent
@@ -515,18 +518,13 @@ public class Snark
// Create a new ID and fill it with something random. First nine
// zeros bytes, then three bytes filled with snark and then
// sixteen random bytes.
// eight random bytes.
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
byte[] rv = new byte[20];
Random random = I2PAppContext.getGlobalContext().random();
int i;
for (i = 0; i < 9; i++)
rv[i] = 0;
rv[i++] = snark;
rv[i++] = snark;
rv[i++] = snark;
while (i < 20)
rv[i++] = (byte)random.nextInt(256);
rv[9] = snark;
rv[10] = snark;
rv[11] = snark;
I2PAppContext.getGlobalContext().random().nextBytes(rv, 12, 8);
return rv;
}
@@ -958,6 +956,7 @@ public class Snark
* non-valid argument list. The given listeners will be
* passed to all components that take one.
*/
/****
private static Snark parseArguments(String[] args,
StorageListener slistener,
CoordinatorListener clistener)
@@ -972,6 +971,7 @@ public class Snark
int i = 0;
while (i < args.length)
{
****/
/*
if (args[i].equals("--debug"))
{
@@ -993,7 +993,9 @@ public class Snark
catch (NumberFormatException nfe) { }
}
}
else */ if (args[i].equals("--port"))
else */
/****
if (args[i].equals("--port"))
{
if (args.length - 1 < i + 1)
usage("--port needs port number to listen on");
@@ -1099,6 +1101,7 @@ public class Snark
System.out.println
(" \tor (with --share) a file to share.");
}
****/
/**
* Aborts program abnormally.

View File

@@ -61,7 +61,7 @@ public class I2PSnarkServlet extends BasicServlet {
private static final String DEFAULT_NAME = "i2psnark";
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
private static final String WARBASE = "/.icons/";
private static final String WARBASE = "/.resources/";
private static final char HELLIP = '\u2026';
public I2PSnarkServlet() {
@@ -194,22 +194,8 @@ public class I2PSnarkServlet extends BasicServlet {
resp.setHeader("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'");
resp.setHeader("X-XSS-Protection", "1; mode=block");
String peerParam = req.getParameter("p");
String stParam = req.getParameter("st");
String peerString;
if (peerParam == null || (!_manager.util().connected()) ||
peerParam.replaceAll("[a-zA-Z0-9~=-]", "").length() > 0) { // XSS
peerString = "";
} else {
peerString = "?p=" + DataHelper.stripHTML(peerParam);
}
if (stParam != null && !stParam.equals("0")) {
stParam = DataHelper.stripHTML(stParam);
if (peerString.length() > 0)
peerString += "&amp;st=" + stParam;
else
peerString = "?st="+ stParam;
}
String pOverride = _manager.util().connected() ? null : "";
String peerString = getQueryString(req, pOverride, null, null);
// AJAX for mainsection
if ("/.ajax/xhr1.html".equals(path)) {
@@ -292,6 +278,7 @@ public class I2PSnarkServlet extends BasicServlet {
out.write(_("Configuration"));
else
out.write(_("Anonymous BitTorrent Client"));
String peerParam = req.getParameter("p");
if ("2".equals(peerParam))
out.write(" | Debug Mode");
out.write("</title>\n");
@@ -413,13 +400,7 @@ public class I2PSnarkServlet extends BasicServlet {
boolean isForm = _manager.util().connected() || !snarks.isEmpty();
if (isForm) {
out.write("<form action=\"_post\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n");
// don't lose peer setting
if (peerParam != null)
out.write("<input type=\"hidden\" name=\"p\" value=\"" + peerParam + "\" >\n");
// ...or st setting
if (stParam != null)
out.write("<input type=\"hidden\" name=\"st\" value=\"" + stParam + "\" >\n");
writeHiddenInputs(out, req, null);
}
out.write(TABLE_HEADER);
@@ -441,18 +422,29 @@ public class I2PSnarkServlet extends BasicServlet {
}
int pageSize = Math.max(_manager.getPageSize(), 5);
out.write("<tr><th><img border=\"0\" src=\"" + _imgPath + "status.png\" title=\"");
String currentSort = req.getParameter("sort");
boolean showSort = total > 1;
out.write("<tr><th>");
String sort = ("2".equals(currentSort)) ? "-2" : "2";
if (showSort) {
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "status.png\" title=\"");
if (showSort)
out.write(_("Sort by {0}", _("Status")));
else
out.write(_("Status"));
out.write("\" alt=\"");
out.write(_("Status"));
out.write("\"></th>\n<th>");
out.write("\">");
if (showSort)
out.write("</a>");
out.write("</th>\n<th>");
if (_manager.util().connected() && !snarks.isEmpty()) {
out.write(" <a href=\"" + _contextPath + '/');
if (peerParam != null) {
if (stParam != null) {
out.write("?st=");
out.write(stParam);
}
// disable peer view
out.write("\">");
out.write("<img border=\"0\" src=\"" + _imgPath + "hidepeers.png\" title=\"");
out.write(_("Hide Peers"));
@@ -460,11 +452,8 @@ public class I2PSnarkServlet extends BasicServlet {
out.write(_("Hide Peers"));
out.write("\">");
} else {
out.write("?p=1");
if (stParam != null) {
out.write("&amp;st=");
out.write(stParam);
}
// enable peer view
out.write(getQueryString(req, "1", null, null));
out.write("\">");
out.write("<img border=\"0\" src=\"" + _imgPath + "showpeers.png\" title=\"");
out.write(_("Show Peers"));
@@ -475,56 +464,158 @@ public class I2PSnarkServlet extends BasicServlet {
out.write("</a><br>\n");
}
out.write("</th>\n<th colspan=\"2\" align=\"left\">");
// cycle through sort by name or type
boolean isTypeSort = false;
if (showSort) {
if (currentSort == null || "0".equals(currentSort) || "1".equals(currentSort)) {
sort = "-1";
} else if ("-1".equals(currentSort)) {
sort = "12";
isTypeSort = true;
} else if ("12".equals(currentSort)) {
sort = "-12";
isTypeSort = true;
} else {
sort = "";
}
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "torrent.png\" title=\"");
if (showSort)
out.write(_("Sort by {0}", (isTypeSort ? _("File type") : _("Torrent"))));
else
out.write(_("Torrent"));
out.write("\" alt=\"");
out.write(_("Torrent"));
out.write("\"></th>\n<th align=\"center\">");
out.write("\">");
if (showSort)
out.write("</a>");
out.write("</th>\n<th align=\"center\">");
if (total > 0 && (start > 0 || total > pageSize)) {
writePageNav(out, start, pageSize, total, peerParam, noThinsp);
writePageNav(out, req, start, pageSize, total, noThinsp);
}
out.write("</th>\n<th align=\"right\">");
if (_manager.util().connected() && !snarks.isEmpty()) {
if (showSort) {
sort = ("4".equals(currentSort)) ? "-4" : "4";
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "eta.png\" title=\"");
if (showSort)
out.write(_("Sort by {0}", _("Estimated time remaining")));
else
out.write(_("Estimated time remaining"));
out.write("\" alt=\"");
// Translators: Please keep short or translate as " "
out.write(_("ETA"));
out.write("\">");
if (showSort)
out.write("</a>");
}
out.write("</th>\n<th align=\"right\">");
// cycle through sort by size or downloaded
boolean isDlSort = false;
if (showSort) {
if ("5".equals(currentSort)) {
sort = "-5";
} else if ("-5".equals(currentSort)) {
sort = "6";
isDlSort = true;
} else if ("6".equals(currentSort)) {
sort = "-6";
isDlSort = true;
} else {
sort = "5";
}
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "head_rx.png\" title=\"");
if (showSort)
out.write(_("Sort by {0}", (isDlSort ? _("Downloaded") : _("Size"))));
else
out.write(_("Downloaded"));
out.write("\" alt=\"");
// Translators: Please keep short or translate as " "
out.write(_("RX"));
out.write("\">");
if (showSort)
out.write("</a>");
out.write("</th>\n<th align=\"right\">");
boolean isRatSort = false;
if (!snarks.isEmpty()) {
// cycle through sort by uploaded or ratio
boolean nextRatSort = false;
if (showSort) {
if ("7".equals(currentSort)) {
sort = "-7";
} else if ("-7".equals(currentSort)) {
sort = "11";
nextRatSort = true;
} else if ("11".equals(currentSort)) {
sort = "-11";
nextRatSort = true;
isRatSort = true;
} else if ("-11".equals(currentSort)) {
sort = "7";
isRatSort = true;
} else {
sort = "7";
}
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "head_tx.png\" title=\"");
if (showSort)
out.write(_("Sort by {0}", (nextRatSort ? _("Upload ratio") : _("Uploaded"))));
else
out.write(_("Uploaded"));
out.write("\" alt=\"");
// Translators: Please keep short or translate as " "
out.write(_("TX"));
out.write("\">");
if (showSort)
out.write("</a>");
}
out.write("</th>\n<th align=\"right\">");
if (_manager.util().connected() && !snarks.isEmpty()) {
if (showSort) {
sort = ("8".equals(currentSort)) ? "-8" : "8";
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "head_rxspeed.png\" title=\"");
if (showSort)
out.write(_("Sort by {0}", _("Down Rate")));
else
out.write(_("Down Rate"));
out.write("\" alt=\"");
// Translators: Please keep short or translate as " "
out.write(_("RX Rate"));
out.write(" \">");
out.write("\">");
if (showSort)
out.write("</a>");
}
out.write("</th>\n<th align=\"right\">");
if (_manager.util().connected() && !snarks.isEmpty()) {
if (showSort) {
sort = ("9".equals(currentSort)) ? "-9" : "9";
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "head_txspeed.png\" title=\"");
if (showSort)
out.write(_("Sort by {0}", _("Up Rate")));
else
out.write(_("Up Rate"));
out.write("\" alt=\"");
// Translators: Please keep short or translate as " "
out.write(_("TX Rate"));
out.write(" \">");
out.write("\">");
if (showSort)
out.write("</a>");
}
out.write("</th>\n<th align=\"center\">");
@@ -580,12 +671,11 @@ public class I2PSnarkServlet extends BasicServlet {
String uri = _contextPath + '/';
boolean showDebug = "2".equals(peerParam);
String stParamStr = stParam == null ? "" : "&amp;st=" + stParam;
for (int i = 0; i < total; i++) {
Snark snark = snarks.get(i);
boolean showPeers = showDebug || "1".equals(peerParam) || Base64.encode(snark.getInfoHash()).equals(peerParam);
boolean hide = i < start || i >= start + pageSize;
displaySnark(out, snark, uri, i, stats, showPeers, isDegraded, noThinsp, showDebug, hide, stParamStr);
displaySnark(out, req, snark, uri, i, stats, showPeers, isDegraded, noThinsp, showDebug, hide, isRatSort);
}
if (total == 0) {
@@ -636,17 +726,105 @@ public class I2PSnarkServlet extends BasicServlet {
return start == 0;
}
/**
* hidden inputs for nonce and paramters p, st, and sort
*
* @param out writes to it
* @param action if non-null, add it as the action
* @since 0.9.16
*/
private void writeHiddenInputs(PrintWriter out, HttpServletRequest req, String action) {
StringBuilder buf = new StringBuilder(256);
writeHiddenInputs(buf, req, action);
out.write(buf.toString());
}
/**
* hidden inputs for nonce and paramters p, st, and sort
*
* @param out appends to it
* @param action if non-null, add it as the action
* @since 0.9.16
*/
private void writeHiddenInputs(StringBuilder buf, HttpServletRequest req, String action) {
buf.append("<input type=\"hidden\" name=\"nonce\" value=\"")
.append(_nonce).append("\" >\n");
String peerParam = req.getParameter("p");
if (peerParam != null) {
buf.append("<input type=\"hidden\" name=\"p\" value=\"")
.append(DataHelper.stripHTML(peerParam)).append("\" >\n");
}
String stParam = req.getParameter("st");
if (stParam != null) {
buf.append("<input type=\"hidden\" name=\"st\" value=\"")
.append(DataHelper.stripHTML(stParam)).append("\" >\n");
}
String soParam = req.getParameter("sort");
if (soParam != null) {
buf.append("<input type=\"hidden\" name=\"sort\" value=\"")
.append(DataHelper.stripHTML(soParam)).append("\" >\n");
}
if (action != null) {
buf.append("<input type=\"hidden\" name=\"action\" value=\"")
.append(action).append("\" >\n");
}
}
/**
* Build HTML-escaped and stripped query string
*
* @param p override or "" for default or null to keep the same as in req
* @param st override or "" for default or null to keep the same as in req
* @param so override or "" for default or null to keep the same as in req
* @return non-null, possibly empty
* @since 0.9.16
*/
private static String getQueryString(HttpServletRequest req, String p, String st, String so) {
StringBuilder buf = new StringBuilder(64);
if (p == null) {
p = req.getParameter("p");
if (p != null)
p = DataHelper.stripHTML(p);
}
if (p != null && !p.equals(""))
buf.append("?p=").append(p);
if (so == null) {
so = req.getParameter("sort");
if (so != null)
so = DataHelper.stripHTML(so);
}
if (so != null && !so.equals("")) {
if (buf.length() <= 0)
buf.append("?sort=");
else
buf.append("&amp;sort=");
buf.append(so);
}
if (st == null) {
st = req.getParameter("st");
if (st != null)
st = DataHelper.stripHTML(st);
}
if (st != null && !st.equals("")) {
if (buf.length() <= 0)
buf.append("?st=");
else
buf.append("&amp;st=");
buf.append(st);
}
return buf.toString();
}
/**
* @since 0.9.6
*/
private void writePageNav(PrintWriter out, int start, int pageSize, int total,
String peerParam, boolean noThinsp) {
private void writePageNav(PrintWriter out, HttpServletRequest req, int start, int pageSize, int total,
boolean noThinsp) {
// Page nav
if (start > 0) {
// First
out.write("<a href=\"" + _contextPath);
if (peerParam != null)
out.write("?p=" + peerParam);
out.write(getQueryString(req, null, "", null));
out.write("\">" +
"<img alt=\"" + _("First") + "\" title=\"" + _("First page") + "\" border=\"0\" src=\"" +
_imgPath + "control_rewind_blue.png\">" +
@@ -655,9 +833,9 @@ public class I2PSnarkServlet extends BasicServlet {
//if (prev > 0) {
if (true) {
// Back
out.write("&nbsp;<a href=\"" + _contextPath + "?st=" + prev);
if (peerParam != null)
out.write("&amp;p=" + peerParam);
out.write("&nbsp;<a href=\"" + _contextPath);
String sprev = (prev > 0) ? Integer.toString(prev) : "";
out.write(getQueryString(req, null, sprev, null));
out.write("\">" +
"<img alt=\"" + _("Prev") + "\" title=\"" + _("Previous page") + "\" border=\"0\" src=\"" +
_imgPath + "control_back_blue.png\">" +
@@ -690,9 +868,8 @@ public class I2PSnarkServlet extends BasicServlet {
//if (next + pageSize < total) {
if (true) {
// Next
out.write("&nbsp;<a href=\"" + _contextPath + "?st=" + next);
if (peerParam != null)
out.write("&amp;p=" + peerParam);
out.write("&nbsp;<a href=\"" + _contextPath);
out.write(getQueryString(req, null, Integer.toString(next), null));
out.write("\">" +
"<img alt=\"" + _("Next") + "\" title=\"" + _("Next page") + "\" border=\"0\" src=\"" +
_imgPath + "control_play_blue.png\">" +
@@ -700,9 +877,8 @@ public class I2PSnarkServlet extends BasicServlet {
}
// Last
int last = ((total - 1) / pageSize) * pageSize;
out.write("&nbsp;<a href=\"" + _contextPath + "?st=" + last);
if (peerParam != null)
out.write("&amp;p=" + peerParam);
out.write("&nbsp;<a href=\"" + _contextPath);
out.write(getQueryString(req, null, Integer.toString(last), null));
out.write("\">" +
"<img alt=\"" + _("Last") + "\" title=\"" + _("Last page") + "\" border=\"0\" src=\"" +
_imgPath + "control_fastforward_blue.png\">" +
@@ -1190,34 +1366,22 @@ public class I2PSnarkServlet extends BasicServlet {
return buf.toString();
}
/**
* Sort alphabetically in current locale, ignore case, ignore leading "the "
* (I guess this is worth it, a lot of torrents start with "The "
* @since 0.7.14
*/
private static class TorrentNameComparator implements Comparator<Snark>, Serializable {
public int compare(Snark l, Snark r) {
// put downloads and magnets first
if (l.getStorage() == null && r.getStorage() != null)
return -1;
if (l.getStorage() != null && r.getStorage() == null)
return 1;
String ls = l.getBaseName();
String llc = ls.toLowerCase(Locale.US);
if (llc.startsWith("the ") || llc.startsWith("the.") || llc.startsWith("the_"))
ls = ls.substring(4);
String rs = r.getBaseName();
String rlc = rs.toLowerCase(Locale.US);
if (rlc.startsWith("the ") || rlc.startsWith("the.") || rlc.startsWith("the_"))
rs = rs.substring(4);
return Collator.getInstance().compare(ls, rs);
}
}
private List<Snark> getSortedSnarks(HttpServletRequest req) {
ArrayList<Snark> rv = new ArrayList<Snark>(_manager.getTorrents());
Collections.sort(rv, new TorrentNameComparator());
if (rv.size() > 1) {
int sort = 0;
String ssort = req.getParameter("sort");
if (ssort != null) {
try {
sort = Integer.parseInt(ssort);
} catch (NumberFormatException nfe) {}
}
try {
Collections.sort(rv, Sorters.getComparator(sort, this));
} catch (IllegalArgumentException iae) {
// Java 7 TimSort - may be unstable
}
}
return rv;
}
@@ -1229,11 +1393,11 @@ public class I2PSnarkServlet extends BasicServlet {
*
* @param stats in/out param (totals)
* @param statsOnly if true, output nothing, update stats only
* @param stParam non null; empty or e.g. &amp;st=10
*/
private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers,
private void displaySnark(PrintWriter out, HttpServletRequest req,
Snark snark, String uri, int row, long stats[], boolean showPeers,
boolean isDegraded, boolean noThinsp, boolean showDebug, boolean statsOnly,
String stParam) throws IOException {
boolean showRatios) throws IOException {
// stats
long uploaded = snark.getUploaded();
stats[0] += snark.getDownloaded();
@@ -1335,7 +1499,7 @@ public class I2PSnarkServlet extends BasicServlet {
if (curPeers > 0 && !showPeers)
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + img + ".png\" title=\"" + txt + "\"></td>" +
"<td class=\"snarkTorrentStatus\">" + txt +
": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + stParam + "\">" +
": <a href=\"" + uri + getQueryString(req, Base64.encode(snark.getInfoHash()), null, null) + "\">" +
curPeers + thinsp(noThinsp) +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
else
@@ -1351,7 +1515,7 @@ public class I2PSnarkServlet extends BasicServlet {
if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "downloading.png\" title=\"" + _("OK") + "\"></td>" +
"<td class=\"snarkTorrentStatus\">" + _("OK") +
": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + stParam + "\">" +
": <a href=\"" + uri + getQueryString(req, Base64.encode(snark.getInfoHash()), null, null) + "\">" +
curPeers + thinsp(noThinsp) +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
else if (isRunning && curPeers > 0 && downBps > 0)
@@ -1362,7 +1526,7 @@ public class I2PSnarkServlet extends BasicServlet {
else if (isRunning && curPeers > 0 && !showPeers)
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "stalled.png\" title=\"" + _("Stalled") + "\"></td>" +
"<td class=\"snarkTorrentStatus\">" + _("Stalled") +
": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + stParam + "\">" +
": <a href=\"" + uri + getQueryString(req, Base64.encode(snark.getInfoHash()), null, null) + "\">" +
curPeers + thinsp(noThinsp) +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
else if (isRunning && curPeers > 0)
@@ -1466,8 +1630,17 @@ public class I2PSnarkServlet extends BasicServlet {
// out.write("??"); // no meta size yet
out.write("</td>\n\t");
out.write("<td align=\"right\" class=\"snarkTorrentUploaded\">");
if (isValid && uploaded > 0)
if (isValid) {
if (showRatios) {
if (total > 0) {
double ratio = uploaded / ((double) total);
out.write((new DecimalFormat("0.000")).format(ratio));
out.write("&nbsp;x");
}
} else if (uploaded > 0) {
out.write(formatSize(uploaded));
}
}
out.write("</td>\n\t");
out.write("<td align=\"right\" class=\"snarkTorrentRateDown\">");
if (isRunning && needed > 0)
@@ -1484,7 +1657,8 @@ public class I2PSnarkServlet extends BasicServlet {
} else if (isRunning) {
// Stop Button
if (isDegraded)
out.write("<a href=\"" + _contextPath + "/?action=Stop_" + b64 + "&amp;nonce=" + _nonce + stParam + "\"><img title=\"");
out.write("<a href=\"" + _contextPath + "/?action=Stop_" + b64 + "&amp;nonce=" + _nonce +
getQueryString(req, "", null, null).replace("?", "&amp;") + "\"><img title=\"");
else
out.write("<input type=\"image\" name=\"action_Stop_" + b64 + "\" value=\"foo\" title=\"");
out.write(_("Stop the torrent"));
@@ -1498,7 +1672,8 @@ public class I2PSnarkServlet extends BasicServlet {
// Start Button
// This works in Opera but it's displayed a little differently, so use noThinsp here too so all 3 icons are consistent
if (noThinsp)
out.write("<a href=\"" + _contextPath + "/?action=Start_" + b64 + "&amp;nonce=" + _nonce + stParam + "\"><img title=\"");
out.write("<a href=\"" + _contextPath + "/?action=Start_" + b64 + "&amp;nonce=" + _nonce +
getQueryString(req, "", null, null).replace("?", "&amp;") + "\"><img title=\"");
else
out.write("<input type=\"image\" name=\"action_Start_" + b64 + "\" value=\"foo\" title=\"");
out.write(_("Start the torrent"));
@@ -1512,7 +1687,8 @@ public class I2PSnarkServlet extends BasicServlet {
// Remove Button
// Doesnt work with Opera so use noThinsp instead of isDegraded
if (noThinsp)
out.write("<a href=\"" + _contextPath + "/?action=Remove_" + b64 + "&amp;nonce=" + _nonce + stParam + "\"><img title=\"");
out.write("<a href=\"" + _contextPath + "/?action=Remove_" + b64 + "&amp;nonce=" + _nonce +
getQueryString(req, "", null, null).replace("?", "&amp;") + "\"><img title=\"");
else
out.write("<input type=\"image\" name=\"action_Remove_" + b64 + "\" value=\"foo\" title=\"");
out.write(_("Remove the torrent from the active list, deleting the .torrent file"));
@@ -1533,7 +1709,8 @@ public class I2PSnarkServlet extends BasicServlet {
// Delete Button
// Doesnt work with Opera so use noThinsp instead of isDegraded
if (noThinsp)
out.write("<a href=\"" + _contextPath + "/?action=Delete_" + b64 + "&amp;nonce=" + _nonce + stParam + "\"><img title=\"");
out.write("<a href=\"" + _contextPath + "/?action=Delete_" + b64 + "&amp;nonce=" + _nonce +
getQueryString(req, "", null, null).replace("?", "&amp;") + "\"><img title=\"");
else
out.write("<input type=\"image\" name=\"action_Delete_" + b64 + "\" value=\"foo\" title=\"");
out.write(_("Delete the .torrent file and the associated data file(s)"));
@@ -1842,12 +2019,7 @@ public class I2PSnarkServlet extends BasicServlet {
out.write("<div class=\"snarkNewTorrent\">\n");
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
out.write("<form action=\"_post\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n");
out.write("<input type=\"hidden\" name=\"action\" value=\"Add\" >\n");
// don't lose peer setting
String peerParam = req.getParameter("p");
if (peerParam != null)
out.write("<input type=\"hidden\" name=\"p\" value=\"" + DataHelper.stripHTML(peerParam) + "\" >\n");
writeHiddenInputs(out, req, "Add");
out.write("<div class=\"addtorrentsection\"><span class=\"snarkConfigTitle\">");
out.write("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "add.png\"> ");
out.write(_("Add Torrent"));
@@ -1874,12 +2046,7 @@ public class I2PSnarkServlet extends BasicServlet {
out.write("<a name=\"add\"></a><div class=\"newtorrentsection\"><div class=\"snarkNewTorrent\">\n");
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
out.write("<form action=\"_post\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n");
out.write("<input type=\"hidden\" name=\"action\" value=\"Create\" >\n");
// don't lose peer setting
String peerParam = req.getParameter("p");
if (peerParam != null)
out.write("<input type=\"hidden\" name=\"p\" value=\"" + DataHelper.stripHTML(peerParam) + "\" >\n");
writeHiddenInputs(out, req, "Create");
out.write("<span class=\"snarkConfigTitle\">");
out.write("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "create.png\"> ");
out.write(_("Create Torrent"));
@@ -1945,10 +2112,9 @@ public class I2PSnarkServlet extends BasicServlet {
//int seedPct = 0;
out.write("<form action=\"" + _contextPath + "/configure\" method=\"POST\">\n" +
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n" +
"<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n" +
"<input type=\"hidden\" name=\"action\" value=\"Save\" >\n" +
"<span class=\"snarkConfigTitle\">" +
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n");
writeHiddenInputs(out, req, "Save");
out.write("<span class=\"snarkConfigTitle\">" +
"<img alt=\"\" border=\"0\" src=\"" + _imgPath + "config.png\"> ");
out.write(_("Configuration"));
out.write("</span><hr>\n" +
@@ -2131,10 +2297,9 @@ public class I2PSnarkServlet extends BasicServlet {
private void writeTrackerForm(PrintWriter out, HttpServletRequest req) throws IOException {
StringBuilder buf = new StringBuilder(1024);
buf.append("<form action=\"" + _contextPath + "/configure\" method=\"POST\">\n" +
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n" +
"<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n" +
"<input type=\"hidden\" name=\"action\" value=\"Save2\" >\n" +
"<span class=\"snarkConfigTitle\">" +
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n");
writeHiddenInputs(buf, req, "Save2");
buf.append("<span class=\"snarkConfigTitle\">" +
"<img alt=\"\" border=\"0\" src=\"" + _imgPath + "config.png\"> ");
buf.append(_("Trackers"));
buf.append("</span><hr>\n" +
@@ -2435,6 +2600,10 @@ public class I2PSnarkServlet extends BasicServlet {
// dummy
r = new File("");
}
boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete() &&
r.isDirectory();
StringBuilder buf=new StringBuilder(4096);
buf.append(DOCTYPE).append("<HTML><HEAD><TITLE>");
if (title.endsWith("/"))
@@ -2442,8 +2611,14 @@ public class I2PSnarkServlet extends BasicServlet {
String directory = title;
title = _("Torrent") + ": " + DataHelper.escapeHTML(title);
buf.append(title);
buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" +
"</HEAD><BODY>\n<center><div class=\"snarknavbar\"><a href=\"").append(_contextPath).append("/\" title=\"Torrents\"");
buf.append("</TITLE>\n").append(HEADER_A).append(_themePath).append(HEADER_B)
.append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">\n");
if (showPriority)
buf.append("<script src=\"").append(_contextPath).append(WARBASE + "js/folder.js\" type=\"text/javascript\"></script>\n");
buf.append("</HEAD><BODY");
if (showPriority)
buf.append(" onload=\"setupbuttons()\"");
buf.append(">\n<center><div class=\"snarknavbar\"><a href=\"").append(_contextPath).append("/\" title=\"Torrents\"");
buf.append(" class=\"snarkRefresh\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("arrow_refresh.png\">&nbsp;&nbsp;");
if (_contextName.equals(DEFAULT_NAME))
buf.append(_("I2PSnark"));
@@ -2453,8 +2628,6 @@ public class I2PSnarkServlet extends BasicServlet {
if (parent) // always true
buf.append("<div class=\"page\"><div class=\"mainsection\">");
boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete() &&
r.isDirectory();
if (showPriority) {
buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
buf.append("<input type=\"hidden\" name=\"nonce\" value=\"").append(_nonce).append("\" >\n");
@@ -2594,9 +2767,20 @@ public class I2PSnarkServlet extends BasicServlet {
.append(":</b> ")
.append((new DecimalFormat("0.00%")).format(completion));
else
buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;")
.append(_("Complete"));
// else unknown
buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;<b>")
.append(_("Complete")).append("</b>");
// up ratio
buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_tx.png\" >&nbsp;<b>")
.append(_("Upload ratio"))
.append(":</b> ");
long uploaded = snark.getUploaded();
if (uploaded > 0) {
double ratio = uploaded / ((double) snark.getTotalLength());
buf.append((new DecimalFormat("0.000")).format(ratio));
buf.append("&nbsp;x");
} else {
buf.append('0');
}
long needed = snark.getNeededLength();
if (needed > 0)
buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;<b>")
@@ -2781,19 +2965,19 @@ public class I2PSnarkServlet extends BasicServlet {
if (showPriority) {
buf.append("<td class=\"priority\">");
if ((!complete) && (!item.isDirectory())) {
buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(fileIndex).append("\" ");
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"prihigh\" value=\"5\" name=\"pri.").append(fileIndex).append("\" ");
if (priority > 0)
buf.append("checked=\"true\"");
buf.append("checked=\"checked\"");
buf.append('>').append(_("High"));
buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(fileIndex).append("\" ");
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"prinorm\" value=\"0\" name=\"pri.").append(fileIndex).append("\" ");
if (priority == 0)
buf.append("checked=\"true\"");
buf.append("checked=\"checked\"");
buf.append('>').append(_("Normal"));
buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(fileIndex).append("\" ");
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"priskip\" value=\"-9\" name=\"pri.").append(fileIndex).append("\" ");
if (priority < 0)
buf.append("checked=\"true\"");
buf.append("checked=\"checked\"");
buf.append('>').append(_("Skip"));
showSaveButton = true;
}
@@ -2802,9 +2986,16 @@ public class I2PSnarkServlet extends BasicServlet {
buf.append("</TR>\n");
}
if (showSaveButton) {
buf.append("<thead><tr><th colspan=\"4\">&nbsp;</th><th class=\"headerpriority\"><input type=\"submit\" value=\"");
buf.append(_("Save priorities"));
buf.append("\" name=\"foo\" ></th></tr></thead>\n");
buf.append("<thead><tr><th colspan=\"4\">&nbsp;</th><th class=\"headerpriority\">" +
"<a class=\"control\" id=\"setallhigh\" href=\"javascript:void(null);\" onclick=\"setallhigh();\">")
.append(toImg("clock_red")).append(_("Set all high")).append("</a>\n" +
"<a class=\"control\" id=\"setallnorm\" href=\"javascript:void(null);\" onclick=\"setallnorm();\">")
.append(toImg("clock")).append(_("Set all normal")).append("</a>\n" +
"<a class=\"control\" id=\"setallskip\" href=\"javascript:void(null);\" onclick=\"setallskip();\">")
.append(toImg("cancel")).append(_("Skip all")).append("</a>\n" +
"<br><br><input type=\"submit\" class=\"accept\" value=\"").append(_("Save priorities"))
.append("\" name=\"savepri\" >\n" +
"</th></tr></thead>\n");
}
buf.append("</table>\n");
if (showPriority)
@@ -2814,7 +3005,12 @@ public class I2PSnarkServlet extends BasicServlet {
return buf.toString();
}
/** @since 0.7.14 */
/**
* Pick an icon; try to catch the common types in an i2p environment.
*
* @return file name not including ".png"
* @since 0.7.14
*/
private String toIcon(File item) {
if (item.isDirectory())
return "folder";
@@ -2823,10 +3019,12 @@ public class I2PSnarkServlet extends BasicServlet {
/**
* Pick an icon; try to catch the common types in an i2p environment
* Pkg private for FileTypeSorter.
*
* @return file name not including ".png"
* @since 0.7.14
*/
private String toIcon(String path) {
String toIcon(String path) {
String icon;
// Note that for this to work well, our custom mime.properties file must be loaded.
String plc = path.toLowerCase(Locale.US);
@@ -2873,12 +3071,12 @@ public class I2PSnarkServlet extends BasicServlet {
/** @since 0.7.14 */
private String toImg(String icon) {
return "<img alt=\"\" height=\"16\" width=\"16\" src=\"" + _contextPath + "/.icons/" + icon + ".png\">";
return toImg(icon, "");
}
/** @since 0.8.2 */
private String toImg(String icon, String altText) {
return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"" + _contextPath + "/.icons/" + icon + ".png\">";
return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"" + _contextPath + WARBASE + "icons/" + icon + ".png\">";
}
/** @since 0.8.1 */

View File

@@ -0,0 +1,341 @@
package org.klomp.snark.web;
import java.io.Serializable;
import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Snark;
/**
* Comparators for various columns
*
* @since 0.9.16 from TorrentNameComparator, moved from I2PSnarkservlet
*/
class Sorters {
/**
* Negative is reverse
*
*<ul>
*<li>0, 1: Name
*<li>2: Status
*<li>3: Peers
*<li>4: ETA
*<li>5: Size
*<li>6: Downloaded
*<li>7: Uploaded
*<li>8: Down rate
*<li>9: Up rate
*<li>10: Remaining (needed)
*<li>11: Upload ratio
*<li>11: File type
*</ul>
*
* @param servlet for file type callback only
*/
public static Comparator<Snark> getComparator(int type, I2PSnarkServlet servlet) {
boolean rev = type < 0;
Comparator<Snark> rv;
switch (type) {
case -1:
case 0:
case 1:
default:
rv = new TorrentNameComparator();
if (rev)
rv = Collections.reverseOrder(rv);
break;
case -2:
case 2:
rv = new StatusComparator(rev);
break;
case -3:
case 3:
rv = new PeersComparator(rev);
break;
case -4:
case 4:
rv = new ETAComparator(rev);
break;
case -5:
case 5:
rv = new SizeComparator(rev);
break;
case -6:
case 6:
rv = new DownloadedComparator(rev);
break;
case -7:
case 7:
rv = new UploadedComparator(rev);
break;
case -8:
case 8:
rv = new DownRateComparator(rev);
break;
case -9:
case 9:
rv = new UpRateComparator(rev);
break;
case -10:
case 10:
rv = new RemainingComparator(rev);
break;
case -11:
case 11:
rv = new RatioComparator(rev);
break;
case -12:
case 12:
rv = new FileTypeComparator(rev, servlet);
break;
}
return rv;
}
/**
* Sort alphabetically in current locale, ignore case, ignore leading "the "
* (I guess this is worth it, a lot of torrents start with "The "
* @since 0.7.14
*/
private static class TorrentNameComparator implements Comparator<Snark>, Serializable {
public int compare(Snark l, Snark r) {
return comp(l, r);
}
public static int comp(Snark l, Snark r) {
// put downloads and magnets first
if (l.getStorage() == null && r.getStorage() != null)
return -1;
if (l.getStorage() != null && r.getStorage() == null)
return 1;
String ls = l.getBaseName();
String llc = ls.toLowerCase(Locale.US);
if (llc.startsWith("the ") || llc.startsWith("the.") || llc.startsWith("the_"))
ls = ls.substring(4);
String rs = r.getBaseName();
String rlc = rs.toLowerCase(Locale.US);
if (rlc.startsWith("the ") || rlc.startsWith("the.") || rlc.startsWith("the_"))
rs = rs.substring(4);
return Collator.getInstance().compare(ls, rs);
}
}
/**
* Forward or reverse sort, but the fallback is always forward
*/
private static abstract class Sort implements Comparator<Snark>, Serializable {
private final boolean _rev;
public Sort(boolean rev) {
_rev = rev;
}
public int compare(Snark l, Snark r) {
int rv = compareIt(l, r);
if (rv != 0)
return _rev ? 0 - rv : rv;
return TorrentNameComparator.comp(l, r);
}
protected abstract int compareIt(Snark l, Snark r);
protected static int compLong(long l, long r) {
if (l < r)
return -1;
if (l > r)
return 1;
return 0;
}
}
private static class StatusComparator extends Sort {
private StatusComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
int rv = getStatus(l) - getStatus(r);
if (rv != 0)
return rv;
// use reverse remaining as first tie break
return compLong(r.getNeededLength(), l.getNeededLength());
}
private static int getStatus(Snark snark) {
long remaining = snark.getRemainingLength();
long needed = snark.getNeededLength();
if (snark.isStopped()) {
if (remaining < 0)
return 0;
if (remaining > 0)
return 5;
return 10;
}
if (snark.isStarting())
return 15;
if (snark.isAllocating())
return 20;
if (remaining < 0)
return 15; // magnet
if (remaining == 0)
return 100;
if (snark.isChecking())
return 95;
if (snark.getNeededLength() <= 0)
return 90;
if (snark.getPeerCount() <= 0)
return 40;
if (snark.getDownloadRate() <= 0)
return 50;
return 60;
}
}
private static class PeersComparator extends Sort {
public PeersComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return l.getPeerCount() - r.getPeerCount();
}
}
private static class RemainingComparator extends Sort {
public RemainingComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getNeededLength(), r.getNeededLength());
}
}
private static class ETAComparator extends Sort {
public ETAComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(eta(l), eta(r));
}
private static long eta(Snark snark) {
long needed = snark.getNeededLength();
if (needed <= 0)
return 0;
long total = snark.getTotalLength();
if (needed > total)
needed = total;
long downBps = snark.getDownloadRate();
if (downBps > 0)
return needed / downBps;
return Long.MAX_VALUE;
}
}
private static class SizeComparator extends Sort {
public SizeComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getTotalLength(), r.getTotalLength());
}
}
private static class DownloadedComparator extends Sort {
public DownloadedComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
long ld = l.getTotalLength() - l.getRemainingLength();
long rd = r.getTotalLength() - r.getRemainingLength();
return compLong(ld, rd);
}
}
private static class UploadedComparator extends Sort {
public UploadedComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getUploaded(), r.getUploaded());
}
}
private static class DownRateComparator extends Sort {
public DownRateComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getDownloadRate(), r.getDownloadRate());
}
}
private static class UpRateComparator extends Sort {
public UpRateComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getUploadRate(), r.getUploadRate());
}
}
private static class RatioComparator extends Sort {
private static final long M = 128 * 1024 * 1024;
public RatioComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
long lt = l.getTotalLength();
long ld = lt > 0 ? ((M * l.getUploaded()) / lt) : 0;
long rt = r.getTotalLength();
long rd = rt > 0 ? ((M * r.getUploaded()) / rt) : 0;
return compLong(ld, rd);
}
}
private static class FileTypeComparator extends Sort {
private final I2PSnarkServlet servlet;
public FileTypeComparator(boolean rev, I2PSnarkServlet servlet) {
super(rev);
this.servlet = servlet;
}
public int compareIt(Snark l, Snark r) {
String ls = toName(l);
String rs = toName(r);
return ls.compareTo(rs);
}
private String toName(Snark snark) {
MetaInfo meta = snark.getMetaInfo();
if (meta == null)
return "0";
if (meta.getFiles() != null)
return "1";
// arbitrary sort based on icon name
return servlet.toIcon(meta.getName());
}
}
}

View File

@@ -8,6 +8,7 @@ epub = application/epub+zip
flac = audio/flac
flv = video/x-flv
iso = application/x-iso9660-image
js = text/javascript
m4a = audio/mp4a-latm
m4v = video/x-m4v
mkv = video/x-matroska

View File

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 464 B

View File

Before

Width:  |  Height:  |  Size: 733 B

After

Width:  |  Height:  |  Size: 733 B

View File

Before

Width:  |  Height:  |  Size: 587 B

After

Width:  |  Height:  |  Size: 587 B

View File

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 673 B

View File

Before

Width:  |  Height:  |  Size: 882 B

After

Width:  |  Height:  |  Size: 882 B

View File

Before

Width:  |  Height:  |  Size: 889 B

After

Width:  |  Height:  |  Size: 889 B

View File

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 766 B

View File

Before

Width:  |  Height:  |  Size: 653 B

After

Width:  |  Height:  |  Size: 653 B

View File

Before

Width:  |  Height:  |  Size: 537 B

After

Width:  |  Height:  |  Size: 537 B

View File

Before

Width:  |  Height:  |  Size: 578 B

After

Width:  |  Height:  |  Size: 578 B

View File

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

View File

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 385 B

View File

Before

Width:  |  Height:  |  Size: 853 B

After

Width:  |  Height:  |  Size: 853 B

View File

Before

Width:  |  Height:  |  Size: 635 B

After

Width:  |  Height:  |  Size: 635 B

View File

Before

Width:  |  Height:  |  Size: 294 B

After

Width:  |  Height:  |  Size: 294 B

View File

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

View File

Before

Width:  |  Height:  |  Size: 589 B

After

Width:  |  Height:  |  Size: 589 B

View File

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

View File

Before

Width:  |  Height:  |  Size: 537 B

After

Width:  |  Height:  |  Size: 537 B

View File

@@ -0,0 +1,93 @@
function setupbuttons() {
updatesetallbuttons();
var form = document.forms[0];
form.savepri.disabled = true;
form.savepri.className = 'disabled';
}
function priorityclicked() {
updatesetallbuttons();
var form = document.forms[0];
form.savepri.disabled = false;
form.savepri.className = 'accept';
}
function updatesetallbuttons() {
var notNorm = false;
var notHigh = false;
var notSkip = false;
var form = document.forms[0];
for(i = 0; i < form.elements.length; i++) {
var elem = form.elements[i];
if (elem.type == 'radio') {
if (!elem.checked) {
if (elem.className == 'prinorm')
notNorm = true;
else if (elem.className == 'prihigh')
notHigh = true;
else
notSkip = true;
}
}
}
if (notNorm)
document.getElementById('setallnorm').className = 'control';
else
document.getElementById('setallnorm').className = 'controld';
if (notHigh)
document.getElementById('setallhigh').className = 'control';
else
document.getElementById('setallhigh').className = 'controld';
if (notSkip)
document.getElementById('setallskip').className = 'control';
else
document.getElementById('setallskip').className = 'controld';
}
function setallnorm() {
var form = document.forms[0];
for(i = 0; i < form.elements.length; i++) {
var elem = form.elements[i];
if (elem.type == 'radio') {
if (elem.className === 'prinorm')
elem.checked = true;
}
}
document.getElementById('setallnorm').className = 'controld';
document.getElementById('setallhigh').className = 'control';
document.getElementById('setallskip').className = 'control';
form.savepri.disabled = false;
form.savepri.className = 'accept';
}
function setallhigh() {
var form = document.forms[0];
for(i = 0; i < form.elements.length; i++) {
var elem = form.elements[i];
if (elem.type == 'radio') {
if (elem.className === 'prihigh')
elem.checked = true;
}
}
document.getElementById('setallnorm').className = 'control';
document.getElementById('setallhigh').className = 'controld';
document.getElementById('setallskip').className = 'control';
form.savepri.disabled = false;
form.savepri.className = 'accept';
}
function setallskip() {
var form = document.forms[0];
for(i = 0; i < form.elements.length; i++) {
var elem = form.elements[i];
if (elem.type == 'radio') {
if (elem.className === 'priskip')
elem.checked = true;
}
}
document.getElementById('setallnorm').className = 'control';
document.getElementById('setallhigh').className = 'control';
document.getElementById('setallskip').className = 'controld';
form.savepri.disabled = false;
form.savepri.className = 'accept';
}

View File

@@ -601,9 +601,12 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
return;
int status = ise != null ? ise.getStatus() : -1;
String error;
//TODO MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION
if (status == MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET) {
// We won't get this one unless it is treated as a hard failure
// in streaming. See PacketQueue.java
error = usingWWWProxy ? "nolsp" : "nols";
} else if (status == MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION) {
error = usingWWWProxy ? "encp" : "enc";
} else {
error = usingWWWProxy ? "dnfp" : "dnf";
}

View File

@@ -188,8 +188,7 @@ public class EditBean extends IndexBean {
/** @since 0.9.12 */
public boolean isSigTypeAvailable(int code) {
SigType type = SigType.getByCode(code);
return type != null && type.isAvailable();
return SigType.isAvailable(code);
}
/** @since 0.8.9 */

View File

@@ -5,7 +5,7 @@ import java.util.HashSet;
import java.util.Set;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterAddress;
import net.i2p.data.router.RouterAddress;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.Router;
import net.i2p.router.transport.TransportManager;

View File

@@ -29,8 +29,8 @@ import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelPoolSettings;
import net.i2p.router.util.HashDistance; // debug
@@ -415,7 +415,9 @@ public class NetDbRenderer {
// shouldnt happen
buf.append("<b>" + _("Published") + ":</b> in ").append(DataHelper.formatDuration2(0-age)).append("???<br>\n");
}
buf.append("<b>" + _("Address(es)") + ":</b> ");
buf.append("<b>").append(_("Signing Key")).append(":</b> ")
.append(info.getIdentity().getSigningPublicKey().getType().toString());
buf.append("<br>\n<b>" + _("Address(es)") + ":</b> ");
String country = _context.commSystem().getCountry(info.getIdentity().getHash());
if(country != null) {
buf.append("<img height=\"11\" width=\"16\" alt=\"").append(country.toUpperCase(Locale.US)).append('\"');

View File

@@ -10,7 +10,7 @@ import java.util.TreeSet;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.router.peermanager.DBHistory;
import net.i2p.router.peermanager.PeerProfile;

View File

@@ -3,8 +3,8 @@ package net.i2p.router.web;
import java.util.Date;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.Signature;
/**

View File

@@ -15,8 +15,8 @@ import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;

View File

@@ -11,7 +11,7 @@ import java.util.Map;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.TunnelId;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;

View File

@@ -44,7 +44,7 @@ class PacketQueue implements SendMessageStatusListener {
private static final int FINAL_TAGS_TO_SEND = 4;
private static final int FINAL_TAG_THRESHOLD = 2;
private static final long REMOVE_EXPIRED_TIME = 67*1000;
private static final boolean ENABLE_STATUS_LISTEN = false;
private static final boolean ENABLE_STATUS_LISTEN = true;
public PacketQueue(I2PAppContext context, I2PSession session, ConnectionManager mgr) {
_context = context;
@@ -267,6 +267,20 @@ class PacketQueue implements SendMessageStatusListener {
_messageStatusMap.remove(id);
break;
case MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET:
// Ideally we would like to make this a hard failure,
// but it caused far too many fast-fails that were then
// resolved by the user clicking reload in his browser.
// Until the LS fetch is faster and more reliable,
// or we increase the timeout for it,
// we can't treat this one as a hard fail.
// Let the streaming retransmission paper over the problem.
if (_log.shouldLog(Log.WARN))
_log.warn("LS lookup (soft) failure for msg " + msgId + " on " + con);
_messageStatusMap.remove(id);
break;
case MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL:
case MessageStatusMessage.STATUS_SEND_FAILURE_ROUTER:
case MessageStatusMessage.STATUS_SEND_FAILURE_NETWORK:
@@ -280,7 +294,6 @@ class PacketQueue implements SendMessageStatusListener {
case MessageStatusMessage.STATUS_SEND_FAILURE_DESTINATION:
case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_LEASESET:
case MessageStatusMessage.STATUS_SEND_FAILURE_EXPIRED_LEASESET:
case MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET:
case SendMessageStatusListener.STATUS_CANCELLED:
if (con.getHighestAckedThrough() >= 0) {
// a retxed SYN succeeded before the first SYN failed

View File

@@ -548,7 +548,7 @@
windowtitle="I2P Anonymous Network - Java Documentation - Version ${release.number}">
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:org.bouncycastle.oldcrypto:org.bouncycastle.oldcrypto.*:gnu.crypto.*:gnu.getopt:gnu.gettext:com.nettgryppa.security:net.metanotion:net.metanotion.*" />
<group title="Streaming Library" packages="net.i2p.client.streaming:net.i2p.client.streaming.impl" />
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters" />
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:net.i2p.data.router:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters" />
<group title="Router Console" packages="net.i2p.router.web:net.i2p.router.update" />
<!-- apps and bridges starting here, alphabetical please -->
<group title="Addressbook Application" packages="net.i2p.addressbook" />

View File

@@ -0,0 +1,135 @@
package net.i2p.crypto;
import java.math.BigInteger;
import java.security.spec.ECField;
import java.security.spec.ECFieldFp;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import net.i2p.util.NativeBigInteger;
/**
* Used by KeyGenerator.getSigningPublicKey()
*
* Modified from
* http://stackoverflow.com/questions/15727147/scalar-multiplication-of-point-over-elliptic-curve
* Apparently public domain.
* Supported P-192 only.
* Added curve parameters to support all curves.
*
* @since 0.9.16
*/
class ECUtil {
private static final BigInteger TWO = new BigInteger("2");
private static final BigInteger THREE = new BigInteger("3");
public static ECPoint scalarMult(ECPoint p, BigInteger kin, EllipticCurve curve) {
ECPoint r = ECPoint.POINT_INFINITY;
BigInteger prime = ((ECFieldFp) curve.getField()).getP();
BigInteger k = kin.mod(prime);
int length = k.bitLength();
byte[] binarray = new byte[length];
for (int i = 0; i <= length-1; i++) {
binarray[i] = k.mod(TWO).byteValue();
k = k.divide(TWO);
}
for (int i = length-1; i >= 0; i--) {
// i should start at length-1 not -2 because the MSB of binarry may not be 1
r = doublePoint(r, curve);
if (binarray[i] == 1)
r = addPoint(r, p, curve);
}
return r;
}
private static ECPoint addPoint(ECPoint r, ECPoint s, EllipticCurve curve) {
if (r.equals(s))
return doublePoint(r, curve);
else if (r.equals(ECPoint.POINT_INFINITY))
return s;
else if (s.equals(ECPoint.POINT_INFINITY))
return r;
BigInteger prime = ((ECFieldFp) curve.getField()).getP();
BigInteger slope = (r.getAffineY().subtract(s.getAffineY())).multiply(r.getAffineX().subtract(s.getAffineX()).modInverse(prime)).mod(prime);
slope = new NativeBigInteger(slope);
BigInteger xOut = (slope.modPow(TWO, prime).subtract(r.getAffineX())).subtract(s.getAffineX()).mod(prime);
BigInteger yOut = s.getAffineY().negate().mod(prime);
yOut = yOut.add(slope.multiply(s.getAffineX().subtract(xOut))).mod(prime);
ECPoint out = new ECPoint(xOut, yOut);
return out;
}
private static ECPoint doublePoint(ECPoint r, EllipticCurve curve) {
if (r.equals(ECPoint.POINT_INFINITY))
return r;
BigInteger slope = (r.getAffineX().pow(2)).multiply(THREE);
slope = slope.add(curve.getA());
BigInteger prime = ((ECFieldFp) curve.getField()).getP();
slope = slope.multiply((r.getAffineY().multiply(TWO)).modInverse(prime));
BigInteger xOut = slope.pow(2).subtract(r.getAffineX().multiply(TWO)).mod(prime);
BigInteger yOut = (r.getAffineY().negate()).add(slope.multiply(r.getAffineX().subtract(xOut))).mod(prime);
ECPoint out = new ECPoint(xOut, yOut);
return out;
}
/**
* P-192 test only.
* See KeyGenerator.main() for a test of all supported curves.
*/
/****
public static void main(String[] args) {
EllipticCurve P192 = ECConstants.P192_SPEC.getCurve();
BigInteger xs = new BigInteger("d458e7d127ae671b0c330266d246769353a012073e97acf8", 16);
BigInteger ys = new BigInteger("325930500d851f336bddc050cf7fb11b5673a1645086df3b", 16);
BigInteger xt = new BigInteger("f22c4395213e9ebe67ddecdd87fdbd01be16fb059b9753a4", 16);
BigInteger yt = new BigInteger("264424096af2b3597796db48f8dfb41fa9cecc97691a9c79", 16);
ECPoint S = new ECPoint(xs,ys);
ECPoint T = new ECPoint(xt,yt);
// Verifying addition
ECPoint Rst = addPoint(S, T, P192);
BigInteger xst = new BigInteger("48e1e4096b9b8e5ca9d0f1f077b8abf58e843894de4d0290", 16); // Specified value of x of point R for addition in NIST Routine example
System.out.println("x-coordinate of point Rst is : " + Rst.getAffineX());
System.out.println("y-coordinate of point Rst is : " + Rst.getAffineY());
if (Rst.getAffineX().equals(xst))
System.out.println("Adding is correct");
else
System.out.println("Adding FAIL");
//Verifying Doubling
BigInteger xr = new BigInteger("30c5bc6b8c7da25354b373dc14dd8a0eba42d25a3f6e6962", 16); // Specified value of x of point R for doubling in NIST Routine example
BigInteger yr = new BigInteger("0dde14bc4249a721c407aedbf011e2ddbbcb2968c9d889cf", 16);
ECPoint R2s = new ECPoint(xr, yr); // Specified value of y of point R for doubling in NIST Routine example
System.out.println("x-coordinate of point R2s is : " + R2s.getAffineX());
System.out.println("y-coordinate of point R2s is : " + R2s.getAffineY());
System.out.println("x-coordinate of calculated point is : " + doublePoint(S, P192).getAffineX());
System.out.println("y-coordinate of calculated point is : " + doublePoint(S, P192).getAffineY());
if (R2s.getAffineX().equals(doublePoint(S, P192).getAffineX()) &&
R2s.getAffineY().equals(doublePoint(S, P192).getAffineY()))
System.out.println("Doubling is correct");
else
System.out.println("Doubling FAIL");
xr = new BigInteger("1faee4205a4f669d2d0a8f25e3bcec9a62a6952965bf6d31", 16); // Specified value of x of point R for scalar Multiplication in NIST Routine example
yr = new BigInteger("5ff2cdfa508a2581892367087c696f179e7a4d7e8260fb06", 16); // Specified value of y of point R for scalar Multiplication in NIST Routine example
ECPoint Rds = new ECPoint(xr, yr);
BigInteger d = new BigInteger("a78a236d60baec0c5dd41b33a542463a8255391af64c74ee", 16);
ECPoint Rs = scalarMult(S, d, P192);
System.out.println("x-coordinate of point Rds is : " + Rds.getAffineX());
System.out.println("y-coordinate of point Rds is : " + Rds.getAffineY());
System.out.println("x-coordinate of calculated point is : " + Rs.getAffineX());
System.out.println("y-coordinate of calculated point is : " + Rs.getAffineY());
if (Rds.getAffineX().equals(Rs.getAffineX()) &&
Rds.getAffineY().equals(Rs.getAffineY()))
System.out.println("Scalar Multiplication is correct");
else
System.out.println("Scalar Multiplication FAIL");
}
****/
}

View File

@@ -12,11 +12,25 @@ package net.i2p.crypto;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.ProviderException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import net.i2p.I2PAppContext;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
@@ -268,24 +282,56 @@ public class KeyGenerator {
}
/** Convert a SigningPrivateKey to a SigningPublicKey.
* DSA-SHA1 only.
* As of 0.9.16, supports all key types.
*
* @param priv a SigningPrivateKey object
* @return a SigningPublicKey object
* @throws IllegalArgumentException on bad key
* @throws IllegalArgumentException on bad key or unknown type
*/
public static SigningPublicKey getSigningPublicKey(SigningPrivateKey priv) {
if (priv.getType() != SigType.DSA_SHA1)
throw new IllegalArgumentException();
SigType type = priv.getType();
if (type == null)
throw new IllegalArgumentException("Unknown type");
try {
switch (type.getBaseAlgorithm()) {
case DSA:
BigInteger x = new NativeBigInteger(1, priv.toByteArray());
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
SigningPublicKey pub = new SigningPublicKey();
try {
pub.setData(SigUtil.rectify(y, SigningPublicKey.KEYSIZE_BYTES));
} catch (InvalidKeyException ike) {
throw new IllegalArgumentException(ike);
}
return pub;
case EC:
ECPrivateKey ecpriv = SigUtil.toJavaECKey(priv);
BigInteger s = ecpriv.getS();
ECParameterSpec spec = (ECParameterSpec) type.getParams();
EllipticCurve curve = spec.getCurve();
ECPoint g = spec.getGenerator();
ECPoint w = ECUtil.scalarMult(g, s, curve);
ECPublicKeySpec ecks = new ECPublicKeySpec(w, ecpriv.getParams());
KeyFactory eckf = KeyFactory.getInstance("EC");
ECPublicKey ecpub = (ECPublicKey) eckf.generatePublic(ecks);
return SigUtil.fromJavaKey(ecpub, type);
case RSA:
RSAPrivateKey rsapriv = SigUtil.toJavaRSAKey(priv);
BigInteger exp = ((RSAKeyGenParameterSpec)type.getParams()).getPublicExponent();
RSAPublicKeySpec rsaks = new RSAPublicKeySpec(rsapriv.getModulus(), exp);
KeyFactory rsakf = KeyFactory.getInstance("RSA");
RSAPublicKey rsapub = (RSAPublicKey) rsakf.generatePublic(rsaks);
return SigUtil.fromJavaKey(rsapub, type);
case EdDSA:
EdDSAPrivateKey epriv = SigUtil.toJavaEdDSAKey(priv);
EdDSAPublicKey epub = new EdDSAPublicKey(new EdDSAPublicKeySpec(epriv.getA(), epriv.getParams()));
return SigUtil.fromJavaKey(epub, type);
default:
throw new IllegalArgumentException("Unsupported algorithm");
}
} catch (GeneralSecurityException gse) {
throw new IllegalArgumentException("Conversion failed", gse);
}
}
public static void main(String args[]) {
@@ -322,14 +368,20 @@ public class KeyGenerator {
long stime = 0;
long vtime = 0;
SimpleDataStructure keys[] = KeyGenerator.getInstance().generateSigningKeys(type);
//System.out.println("pubkey " + keys[0]);
SigningPublicKey pubkey = (SigningPublicKey) keys[0];
SigningPrivateKey privkey = (SigningPrivateKey) keys[1];
SigningPublicKey pubkey2 = getSigningPublicKey(privkey);
if (pubkey.equals(pubkey2))
System.out.println(type + " private-to-public test PASSED");
else
System.out.println(type + " private-to-public test FAILED");
//System.out.println("privkey " + keys[1]);
for (int i = 0; i < runs; i++) {
RandomSource.getInstance().nextBytes(src);
long start = System.nanoTime();
Signature sig = DSAEngine.getInstance().sign(src, (SigningPrivateKey) keys[1]);
Signature sig = DSAEngine.getInstance().sign(src, privkey);
long mid = System.nanoTime();
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, (SigningPublicKey) keys[0]);
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, pubkey);
long end = System.nanoTime();
stime += mid - start;
vtime += end - mid;

View File

@@ -331,7 +331,7 @@ public class SigUtil {
}
/**
* @deprecated unused
*
*/
public static RSAPrivateKey toJavaRSAKey(SigningPrivateKey pk)
throws GeneralSecurityException {
@@ -344,7 +344,7 @@ public class SigUtil {
}
/**
* @deprecated unused
*
*/
public static SigningPublicKey fromJavaKey(RSAPublicKey pk, SigType type)
throws GeneralSecurityException {

View File

@@ -24,20 +24,15 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
@@ -638,13 +633,17 @@ public class DataHelper {
* Integers are a fixed number of bytes (numBytes), stored as unsigned integers in network byte order.
* @param value value to write out, non-negative
* @param rawStream stream to write to
* @param numBytes number of bytes to write the number into (padding as necessary)
* @throws DataFormatException if value is negative
* @param numBytes number of bytes to write the number into, 1-8 (padding as necessary)
* @throws DataFormatException if value is negative or if numBytes not 1-8
* @throws IOException if there is an IO error writing to the stream
*/
public static void writeLong(OutputStream rawStream, int numBytes, long value)
throws DataFormatException, IOException {
if (value < 0) throw new DataFormatException("Value is negative (" + value + ")");
if (numBytes <= 0 || numBytes > 8)
// probably got the args backwards
throw new DataFormatException("Bad byte count " + numBytes);
if (value < 0)
throw new DataFormatException("Value is negative (" + value + ")");
for (int i = (numBytes - 1) * 8; i >= 0; i -= 8) {
byte cur = (byte) (value >> i);
rawStream.write(cur);
@@ -1425,58 +1424,6 @@ public class DataHelper {
out.write(data);
}
/**
* Sort based on the Hash of the DataStructure.
* Warning - relatively slow.
* WARNING - this sort order must be consistent network-wide, so while the order is arbitrary,
* it cannot be changed.
* Why? Just because it has to be consistent so signing will work.
* How to spec as returning the same type as the param?
* DEPRECATED - Only used by RouterInfo.
*
* @return a new list
*/
public static List<? extends DataStructure> sortStructures(Collection<? extends DataStructure> dataStructures) {
if (dataStructures == null) return Collections.emptyList();
// This used to use Hash.toString(), which is insane, since a change to toString()
// would break the whole network. Now use Hash.toBase64().
// Note that the Base64 sort order is NOT the same as the raw byte sort order,
// despite what you may read elsewhere.
//ArrayList<DataStructure> rv = new ArrayList(dataStructures.size());
//TreeMap<String, DataStructure> tm = new TreeMap();
//for (DataStructure struct : dataStructures) {
// tm.put(struct.calculateHash().toString(), struct);
//}
//for (DataStructure struct : tm.values()) {
// rv.add(struct);
//}
ArrayList<DataStructure> rv = new ArrayList<DataStructure>(dataStructures);
sortStructureList(rv);
return rv;
}
/**
* See above.
* DEPRECATED - Only used by RouterInfo.
*
* @since 0.9
*/
static void sortStructureList(List<? extends DataStructure> dataStructures) {
Collections.sort(dataStructures, new DataStructureComparator());
}
/**
* See sortStructures() comments.
* @since 0.8.3
*/
private static class DataStructureComparator implements Comparator<DataStructure>, Serializable {
public int compare(DataStructure l, DataStructure r) {
return l.calculateHash().toBase64().compareTo(r.calculateHash().toBase64());
}
}
/**
* NOTE: formatDuration2() recommended in most cases for readability
*/

View File

@@ -77,6 +77,13 @@ public class KeysAndCert extends DataStructureImpl {
_signingKey = key;
}
/**
* @since 0.9.16
*/
public byte[] getPadding() {
return _padding;
}
/**
* @throws IllegalStateException if was already set
* @since 0.9.12
@@ -114,6 +121,8 @@ public class KeysAndCert extends DataStructureImpl {
_publicKey.writeBytes(out);
if (_padding != null)
out.write(_padding);
else if (_signingKey.length() < SigningPublicKey.KEYSIZE_BYTES)
throw new DataFormatException("No padding set");
_signingKey.writeTruncatedBytes(out);
_certificate.writeBytes(out);
}

View File

@@ -50,6 +50,7 @@ public class PrivateKey extends SimpleDataStructure {
/** derives a new PublicKey object derived from the secret contents
* of this PrivateKey
* @return a PublicKey object
* @throws IllegalArgumentException on bad key
*/
public PublicKey toPublic() {
return KeyGenerator.getPublicKey(this);

View File

@@ -1,11 +1,13 @@
package net.i2p.data;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.Locale;
import java.util.Map;
@@ -24,6 +26,7 @@ import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SigType;
import net.i2p.util.RandomSource;
import net.i2p.util.SecureFileOutputStream;
/**
* This helper class reads and writes files in the
@@ -48,11 +51,11 @@ public class PrivateKeyFile {
private static final int HASH_EFFORT = VerifiedDestination.MIN_HASHCASH_EFFORT;
private final File file;
protected final File file;
private final I2PClient client;
private Destination dest;
private PrivateKey privKey;
private SigningPrivateKey signingPrivKey;
protected Destination dest;
protected PrivateKey privKey;
protected SigningPrivateKey signingPrivKey;
/**
* Create a new PrivateKeyFile, or modify an existing one, with various
@@ -224,6 +227,16 @@ public class PrivateKeyFile {
*/
public PrivateKeyFile(File file, PublicKey pubkey, SigningPublicKey spubkey, Certificate cert,
PrivateKey pk, SigningPrivateKey spk) {
this(file, pubkey, spubkey, cert, pk, spk, null);
}
/**
* @param padding null OK, must be non-null if spubkey length < 128
* @throws IllegalArgumentException on mismatch of spubkey and spk types
* @since 0.9.16
*/
public PrivateKeyFile(File file, PublicKey pubkey, SigningPublicKey spubkey, Certificate cert,
PrivateKey pk, SigningPrivateKey spk, byte[] padding) {
if (spubkey.getType() != spk.getType())
throw new IllegalArgumentException("Signing key type mismatch");
this.file = file;
@@ -232,6 +245,8 @@ public class PrivateKeyFile {
this.dest.setPublicKey(pubkey);
this.dest.setSigningPublicKey(spubkey);
this.dest.setCertificate(cert);
if (padding != null)
this.dest.setPadding(padding);
this.privKey = pk;
this.signingPrivKey = spk;
}
@@ -241,9 +256,9 @@ public class PrivateKeyFile {
*/
public Destination createIfAbsent() throws I2PException, IOException, DataFormatException {
if(!this.file.exists()) {
FileOutputStream out = null;
OutputStream out = null;
try {
out = new FileOutputStream(this.file);
out = new SecureFileOutputStream(this.file);
if (this.client != null)
this.client.createDestination(out);
else
@@ -257,7 +272,10 @@ public class PrivateKeyFile {
return getDestination();
}
/** Also sets the local privKey and signingPrivKey */
/**
* If the destination is not set, read it in from the file.
* Also sets the local privKey and signingPrivKey.
*/
public Destination getDestination() throws I2PSessionException, IOException, DataFormatException {
if (dest == null) {
I2PSession s = open();
@@ -408,9 +426,9 @@ public class PrivateKeyFile {
}
public I2PSession open(Properties opts) throws I2PSessionException, IOException {
FileInputStream in = null;
InputStream in = null;
try {
in = new FileInputStream(this.file);
in = new BufferedInputStream(new FileInputStream(this.file));
I2PSession s = this.client.createSession(in, opts);
return s;
} finally {
@@ -424,13 +442,12 @@ public class PrivateKeyFile {
* Copied from I2PClientImpl.createDestination()
*/
public void write() throws IOException, DataFormatException {
FileOutputStream out = null;
OutputStream out = null;
try {
out = new FileOutputStream(this.file);
out = new SecureFileOutputStream(this.file);
this.dest.writeBytes(out);
this.privKey.writeBytes(out);
this.signingPrivKey.writeBytes(out);
out.flush();
} finally {
if (out != null) {
try { out.close(); } catch (IOException ioe) {}
@@ -438,6 +455,23 @@ public class PrivateKeyFile {
}
}
/**
* Verify that the PublicKey matches the PrivateKey, and
* the SigningPublicKey matches the SigningPrivateKey.
*
* @return success
* @since 0.9.16
*/
public boolean validateKeyPairs() {
try {
if (!dest.getPublicKey().equals(KeyGenerator.getPublicKey(privKey)))
return false;
return dest.getSigningPublicKey().equals(KeyGenerator.getSigningPublicKey(signingPrivKey));
} catch (IllegalArgumentException iae) {
return false;
}
}
@Override
public String toString() {
StringBuilder s = new StringBuilder(128);

View File

@@ -75,8 +75,12 @@ public class SigningPrivateKey extends SimpleDataStructure {
return _type;
}
/** converts this signing private key to its public equivalent
/**
* Converts this signing private key to its public equivalent.
* As of 0.9.16, supports all key types.
*
* @return a SigningPublicKey object derived from this private key
* @throws IllegalArgumentException on bad key or unknown or unsupported type
*/
public SigningPublicKey toPublic() {
return KeyGenerator.getSigningPublicKey(this);

View File

@@ -0,0 +1,24 @@
HTTP/1.1 504 Gateway Timeout
Content-Type: text/html; charset=UTF-8
Cache-control: no-cache
Connection: close
Proxy-Connection: close
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>_("Warning: Eepsite Unreachable")</title>
<link rel="shortcut icon" href="http://proxy.i2p/themes/console/images/favicon.ico">
<link href="http://proxy.i2p/themes/console/default/console.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="logo">
<a href="http://127.0.0.1:7657/" title="_("Router Console")"><img src="http://proxy.i2p/themes/console/images/i2plogo.png" alt="_("I2P Router Console")" border="0"></a><hr>
<a href="http://127.0.0.1:7657/config.jsp">_("Configuration")</a> <a href="http://127.0.0.1:7657/help.jsp">_("Help")</a> <a href="http://127.0.0.1:7657/susidns/index">_("Addressbook")</a>
</div>
<div class="warning" id="warning">
<h3>_("Warning: Eepsite Unreachable")</h3>
<p>
_("The eepsite was not reachable, because it uses encryption options that are not supported by your I2P or Java version.")
<hr>
<p><b>_("Could not connect to the following destination:")</b>
</p>

View File

@@ -0,0 +1,25 @@
HTTP/1.1 504 Gateway Timeout
Content-Type: text/html; charset=UTF-8
Cache-control: no-cache
Connection: close
Proxy-Connection: close
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>_("Warning: Outproxy Unreachable")</title>
<link rel="shortcut icon" href="http://proxy.i2p/themes/console/images/favicon.ico">
<link href="http://proxy.i2p/themes/console/default/console.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="logo">
<a href="http://127.0.0.1:7657/" title="_("Router Console")"><img src="http://proxy.i2p/themes/console/images/i2plogo.png" alt="_("I2P Router Console")" border="0"></a><hr>
<a href="http://127.0.0.1:7657/config.jsp">_("Configuration")</a> <a href="http://127.0.0.1:7657/help.jsp">_("Help")</a> <a href="http://127.0.0.1:7657/susidns/index">_("Addressbook")</a>
</div>
<div class="warning" id="warning">
<h3>_("Warning: Outproxy Unreachable")</h3>
<p>
_("The HTTP outproxy was not reachable, because it uses encryption options that are not supported by your I2P or Java version.")
_("You may want to {0}retry{1} as this will randomly reselect an outproxy from the pool you have defined {2}here{3} (if you have more than one configured).", "<a href=\"javascript:parent.window.location.reload()\">", "</a>", "<a href=\"http://127.0.0.1:7657/i2ptunnel/index.jsp\">", "</a>")
_("If you continue to have trouble you may want to edit your outproxy list {0}here{1}.", "<a href=\"http://127.0.0.1:7657/i2ptunnel/edit.jsp?tunnel=0\">", "</a>")
</p>
<hr><p><b>_("Could not connect to the following destination:")</b></p>

View File

@@ -526,6 +526,40 @@ a:hover {
font-weight: bold;
}
a.control, a.controld {
background: #989;
border: 1px inset #bbb;
border-radius: 4px;
color: #000;
font-weight: bold;
margin: 2px 4px;
padding: 2px;
text-shadow: 0px 0px #410;
white-space: nowrap;
}
a.controld {
color: #444;
font-weight: normal;
}
a.controld img {
display: none;
}
a.control:hover {
background-color: #f60;
border: 1px outset #bbb;
color: #fff;
text-shadow: 0px 1px 5px #f00;
}
a.control:active {
background: #000 !important;
color: #f60 !important;
text-shadow: 0 !important;
}
input {
font-size: 8.5pt;
font-weight: bold;
@@ -598,6 +632,14 @@ input[type=radio] {
input.default { width: 1px; height: 1px; visibility: hidden; }
input.disabled, input.disabled:hover {
background-color: #989;
border: 1px inset #bbb;
color: #444;
font-weight: normal;
text-shadow: 0px 0px 0px #444;
}
input.accept {
background: #989 url('../../console/images/accept.png') no-repeat 2px center;
padding: 2px 3px 2px 20px !important;

View File

@@ -146,10 +146,10 @@ public class BuildRequestRecord {
return (_data.getData()[_data.getOffset() + OFF_FLAG] & FLAG_OUTBOUND_ENDPOINT) != 0;
}
/**
* Time that the request was sent, truncated to the nearest hour
* Time that the request was sent (ms), truncated to the nearest hour
*/
public long readRequestTime() {
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * 60l * 60l * 1000l;
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * (60 * 60 * 1000L);
}
/**
* What message ID should we send the request to the next hop with. If this is the outbound tunnel endpoint,
@@ -250,6 +250,8 @@ public class BuildRequestRecord {
else if (isOutEndpoint)
buf[OFF_FLAG] |= FLAG_OUTBOUND_ENDPOINT;
long truncatedHour = ctx.clock().now();
// prevent hop identification at top of the hour
truncatedHour -= ctx.random().nextInt(90*1000);
truncatedHour /= (60l*60l*1000l);
DataHelper.toLong(buf, OFF_REQ_TIME, 4, truncatedHour);
DataHelper.toLong(buf, OFF_SEND_MSG_ID, 4, nextMsgId);

View File

@@ -17,7 +17,7 @@ import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.TunnelId;

View File

@@ -18,7 +18,7 @@ import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.TunnelId;
/**

View File

@@ -1,4 +1,4 @@
package net.i2p.data;
package net.i2p.data.router;
/*
* free (adj.): unencumbered; not under the control of others
@@ -17,6 +17,9 @@ import java.util.Date;
import java.util.Map;
import java.util.Properties;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.DataStructureImpl;
import net.i2p.util.Addresses;
import net.i2p.util.OrderedProperties;
@@ -36,6 +39,7 @@ import net.i2p.util.OrderedProperties;
* several releases for the change to propagate as it is backwards-incompatible.
* Restored as of 0.9.12.
*
* @since 0.9.16 moved from net.i2p.data
* @author jrandom
*/
public class RouterAddress extends DataStructureImpl {

View File

@@ -1,4 +1,7 @@
package net.i2p.data;
package net.i2p.data.router;
import net.i2p.data.Certificate;
import net.i2p.data.KeysAndCert;
/*
* free (adj.): unencumbered; not under the control of others
@@ -16,6 +19,7 @@ package net.i2p.data;
* As of 0.9.9 this data structure is immutable after the two keys and the certificate
* are set; attempts to change them will throw an IllegalStateException.
*
* @since 0.9.16 moved from net.i2p.data
* @author jrandom
*/
public class RouterIdentity extends KeysAndCert {

View File

@@ -1,4 +1,4 @@
package net.i2p.data;
package net.i2p.data.router;
/*
* free (adj.): unencumbered; not under the control of others
@@ -31,6 +31,13 @@ import net.i2p.crypto.SHA1;
import net.i2p.crypto.SHA1Hash;
import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.SigType;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.KeysAndCert;
import net.i2p.data.Signature;
import net.i2p.data.SimpleDataStructure;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
@@ -47,6 +54,7 @@ import net.i2p.util.SystemVersion;
* To ensure integrity of the RouterInfo, methods that change an element of the
* RouterInfo will throw an IllegalStateException after the RouterInfo is signed.
*
* @since 0.9.16 moved from net.i2p.data
* @author jrandom
*/
public class RouterInfo extends DatabaseEntry {
@@ -190,7 +198,7 @@ public class RouterInfo extends DatabaseEntry {
// WARNING this sort algorithm cannot be changed, as it must be consistent
// network-wide. The signature is not checked at readin time, but only
// later, and the addresses are stored in a Set, not a List.
DataHelper.sortStructureList(_addresses);
SortHelper.sortStructureList(_addresses);
}
}
}
@@ -308,7 +316,7 @@ public class RouterInfo extends DatabaseEntry {
// WARNING this sort algorithm cannot be changed, as it must be consistent
// network-wide. The signature is not checked at readin time, but only
// later, and the hashes are stored in a Set, not a List.
peers = (Collection<Hash>) DataHelper.sortStructures(peers);
peers = (Collection<Hash>) SortHelper.sortStructures(peers);
for (Hash peerHash : peers) {
peerHash.writeBytes(out);
}

View File

@@ -0,0 +1,62 @@
package net.i2p.data.router;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKey;
import net.i2p.data.PrivateKeyFile;
import net.i2p.data.SigningPrivateKey;
/**
* Same format as super, simply adds a method to
* treat it as a RouterIdentity instead of a Destination.
*
* @since 0.9.16
*/
public class RouterPrivateKeyFile extends PrivateKeyFile {
public RouterPrivateKeyFile(File file) {
super(file);
}
/**
* Read it in from the file.
* Also sets the local privKey and signingPrivKey.
*/
public RouterIdentity getRouterIdentity() throws IOException, DataFormatException {
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(this.file));
RouterIdentity ri = new RouterIdentity();
ri.readBytes(in);
privKey = new PrivateKey();
privKey.readBytes(in);
SigType type = ri.getSigningPublicKey().getType();
if (type == null)
throw new DataFormatException("Unknown sig type");
signingPrivKey = new SigningPrivateKey(type);
signingPrivKey.readBytes(in);
// set it a Destination, so we may call validateKeyPairs()
// or other methods
dest = new Destination();
dest.setPublicKey(ri.getPublicKey());
dest.setSigningPublicKey(ri.getSigningPublicKey());
dest.setCertificate(ri.getCertificate());
dest.setPadding(ri.getPadding());
return ri;
} finally {
if (in != null) {
try { in.close(); } catch (IOException ioe) {}
}
}
}
}

View File

@@ -0,0 +1,79 @@
package net.i2p.data.router;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import net.i2p.data.DataStructure;
/**
* The sorting of addresses in RIs
*
* @since 0.9.16 moved from DataHelper
*/
class SortHelper {
/**
* Sort based on the Hash of the DataStructure.
* Warning - relatively slow.
* WARNING - this sort order must be consistent network-wide, so while the order is arbitrary,
* it cannot be changed.
* Why? Just because it has to be consistent so signing will work.
* How to spec as returning the same type as the param?
* DEPRECATED - Only used by RouterInfo.
*
* @return a new list
*/
public static List<? extends DataStructure> sortStructures(Collection<? extends DataStructure> dataStructures) {
if (dataStructures == null) return Collections.emptyList();
// This used to use Hash.toString(), which is insane, since a change to toString()
// would break the whole network. Now use Hash.toBase64().
// Note that the Base64 sort order is NOT the same as the raw byte sort order,
// despite what you may read elsewhere.
//ArrayList<DataStructure> rv = new ArrayList(dataStructures.size());
//TreeMap<String, DataStructure> tm = new TreeMap();
//for (DataStructure struct : dataStructures) {
// tm.put(struct.calculateHash().toString(), struct);
//}
//for (DataStructure struct : tm.values()) {
// rv.add(struct);
//}
ArrayList<DataStructure> rv = new ArrayList<DataStructure>(dataStructures);
sortStructureList(rv);
return rv;
}
/**
* See above.
* DEPRECATED - Only used by RouterInfo.
*
* @since 0.9
*/
static void sortStructureList(List<? extends DataStructure> dataStructures) {
Collections.sort(dataStructures, new DataStructureComparator());
}
/**
* See sortStructures() comments.
* @since 0.8.3
*/
private static class DataStructureComparator implements Comparator<DataStructure>, Serializable {
public int compare(DataStructure l, DataStructure r) {
return l.calculateHash().toBase64().compareTo(r.calculateHash().toBase64());
}
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Classes formerly in net.i2p.data but moved here as they are only used by the router.
</p>
</body>
</html>

View File

@@ -28,8 +28,8 @@ import java.util.TreeSet;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.util.Addresses;
import net.i2p.util.ConcurrentHashSet;

View File

@@ -13,7 +13,7 @@ import java.io.Writer;
import java.util.Collections;
import java.util.List;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.router.RouterAddress;
/**
* Manages the communication subsystem between peers, including connections,

View File

@@ -9,7 +9,7 @@ package net.i2p.router;
*/
import net.i2p.data.Hash;
import net.i2p.data.RouterIdentity;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.i2np.I2NPMessage;
/**

View File

@@ -14,7 +14,7 @@ import java.util.Date;
import java.util.List;
import net.i2p.data.Hash;
import net.i2p.data.RouterIdentity;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
import net.i2p.data.i2np.DeliveryStatusMessage;

View File

@@ -18,6 +18,7 @@ import java.io.OutputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataStructure;
import net.i2p.data.Destination;
@@ -26,6 +27,7 @@ import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.router.startup.CreateRouterInfoJob;
import net.i2p.util.Log;
import net.i2p.util.SecureDirectory;
import net.i2p.util.SecureFileOutputStream;
@@ -47,10 +49,10 @@ public class KeyManager {
public final static String PROP_KEYDIR = "router.keyBackupDir";
public final static String DEFAULT_KEYDIR = "keyBackup";
private final static String KEYFILE_PRIVATE_ENC = "privateEncryption.key";
private final static String KEYFILE_PUBLIC_ENC = "publicEncryption.key";
private final static String KEYFILE_PRIVATE_SIGNING = "privateSigning.key";
private final static String KEYFILE_PUBLIC_SIGNING = "publicSigning.key";
public final static String KEYFILE_PRIVATE_ENC = "privateEncryption.key";
public final static String KEYFILE_PUBLIC_ENC = "publicEncryption.key";
public final static String KEYFILE_PRIVATE_SIGNING = "privateSigning.key";
public final static String KEYFILE_PUBLIC_SIGNING = "publicSigning.key";
public KeyManager(RouterContext context) {
_context = context;
@@ -151,8 +153,9 @@ public class KeyManager {
private void syncKeys(File keyDir) {
syncPrivateKey(keyDir);
syncPublicKey(keyDir);
syncSigningKey(keyDir);
syncVerificationKey(keyDir);
SigType type = CreateRouterInfoJob.getSigTypeConfig(getContext());
syncSigningKey(keyDir, type);
syncVerificationKey(keyDir, type);
}
private void syncPrivateKey(File keyDir) {
@@ -181,27 +184,33 @@ public class KeyManager {
_publicKey = (PublicKey) readin;
}
private void syncSigningKey(File keyDir) {
/**
* @param type the SigType to expect on read-in, ignored on write
*/
private void syncSigningKey(File keyDir, SigType type) {
DataStructure ds;
File keyFile = new File(keyDir, KEYFILE_PRIVATE_SIGNING);
boolean exists = (_signingPrivateKey != null);
if (exists)
ds = _signingPrivateKey;
else
ds = new SigningPrivateKey();
ds = new SigningPrivateKey(type);
DataStructure readin = syncKey(keyFile, ds, exists);
if (readin != null && !exists)
_signingPrivateKey = (SigningPrivateKey) readin;
}
private void syncVerificationKey(File keyDir) {
/**
* @param type the SigType to expect on read-in, ignored on write
*/
private void syncVerificationKey(File keyDir, SigType type) {
DataStructure ds;
File keyFile = new File(keyDir, KEYFILE_PUBLIC_SIGNING);
boolean exists = (_signingPublicKey != null);
if (exists)
ds = _signingPublicKey;
else
ds = new SigningPublicKey();
ds = new SigningPublicKey(type);
DataStructure readin = syncKey(keyFile, ds, exists);
if (readin != null && !exists)
_signingPublicKey = (SigningPublicKey) readin;

View File

@@ -40,7 +40,7 @@ public class LeaseSetKeys {
/**
* Key with which a LeaseSet can be revoked (by republishing it with no Leases)
*
* @deprecated unused
* Deprecated, unused
*/
public SigningPrivateKey getRevocationKey() { return _revocationKey; }

View File

@@ -10,7 +10,7 @@ import java.util.Scanner;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.Router;
/**

View File

@@ -14,9 +14,10 @@ import java.util.Collections;
import java.util.Set;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.networkdb.reseed.ReseedChecker;
/**
@@ -51,18 +52,51 @@ public abstract class NetworkDatabaseFacade implements Service {
public abstract LeaseSet lookupLeaseSetLocally(Hash key);
public abstract void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs);
public abstract RouterInfo lookupRouterInfoLocally(Hash key);
/**
* return the leaseSet if another leaseSet already existed at that key
* Lookup using the client's tunnels
* Succeeds even if LS validation fails due to unsupported sig type
*
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
* @since 0.9.16
*/
public abstract void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest);
/**
* Lookup locally in netDB and in badDest cache
* Succeeds even if LS validation failed due to unsupported sig type
*
* @since 0.9.16
*/
public abstract Destination lookupDestinationLocally(Hash key);
/**
* @return the leaseSet if another leaseSet already existed at that key
*
* @throws IllegalArgumentException if the data is not valid
*/
public abstract LeaseSet store(Hash key, LeaseSet leaseSet) throws IllegalArgumentException;
/**
* return the routerInfo if another router already existed at that key
* @return the routerInfo if another router already existed at that key
*
* @throws IllegalArgumentException if the data is not valid
*/
public abstract RouterInfo store(Hash key, RouterInfo routerInfo) throws IllegalArgumentException;
/**
* @return the old entry if it already existed at that key
* @throws IllegalArgumentException if the data is not valid
* @since 0.9.16
*/
public DatabaseEntry store(Hash key, DatabaseEntry entry) throws IllegalArgumentException {
if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO)
return store(key, (RouterInfo) entry);
if (entry.getType() == DatabaseEntry.KEY_TYPE_LEASESET)
return store(key, (LeaseSet) entry);
throw new IllegalArgumentException("unknown type");
}
/**
* @throws IllegalArgumentException if the local router is not valid
*/
@@ -101,4 +135,12 @@ public abstract class NetworkDatabaseFacade implements Service {
* @since IPv6
*/
public boolean floodfillEnabled() { return false; };
/**
* Is it permanently negative cached?
*
* @param key only for Destinations; for RouterIdentities, see Banlist
* @since 0.9.16
*/
public boolean isNegativeCachedForever(Hash key) { return false; }
}

View File

@@ -18,7 +18,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.util.CDPQEntry;
import net.i2p.util.Log;

View File

@@ -70,9 +70,8 @@ public class PersistentKeyRing extends KeyRing {
Hash h = e.getKey();
buf.append(h.toBase64().substring(0, 6)).append("&hellip;");
buf.append("<td>");
LeaseSet ls = _ctx.netDb().lookupLeaseSetLocally(h);
if (ls != null) {
Destination dest = ls.getDestination();
Destination dest = _ctx.netDb().lookupDestinationLocally(h);
if (dest != null) {
if (_ctx.clientManager().isLocal(dest)) {
TunnelPoolSettings in = _ctx.tunnelManager().getInboundSettings(h);
if (in != null && in.getDestinationNickname() != null)

View File

@@ -29,11 +29,12 @@ import net.i2p.data.Certificate;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.i2np.GarlicMessage;
import net.i2p.router.message.GarlicMessageHandler;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.startup.CreateRouterInfoJob;
import net.i2p.router.startup.StartupJob;
import net.i2p.router.startup.WorkingDir;
import net.i2p.router.tasks.*;
@@ -98,10 +99,6 @@ public class Router implements RouterClock.ClockShiftListener {
/** this does not put an 'H' in your routerInfo **/
public final static String PROP_HIDDEN_HIDDEN = "router.isHidden";
public final static String PROP_DYNAMIC_KEYS = "router.dynamicKeys";
public final static String PROP_INFO_FILENAME = "router.info.location";
public final static String PROP_INFO_FILENAME_DEFAULT = "router.info";
public final static String PROP_KEYS_FILENAME = "router.keys.location";
public final static String PROP_KEYS_FILENAME_DEFAULT = "router.keys";
public final static String PROP_SHUTDOWN_IN_PROGRESS = "__shutdownInProgress";
public final static String DNS_CACHE_TIME = "" + (5*60);
private static final String EVENTLOG = "eventlog.txt";
@@ -673,20 +670,6 @@ public class Router implements RouterClock.ClockShiftListener {
return _context.commSystem().isInBadCountry();
}
/**
* Only called at startup via LoadRouterInfoJob and RebuildRouterInfoJob.
* Not called by periodic RepublishLocalRouterInfoJob.
* We don't want to change the cert on the fly as it changes the router hash.
* RouterInfo.isHidden() checks the capability, but RouterIdentity.isHidden() checks the cert.
* There's no reason to ever add a hidden cert?
* @return the certificate for a new RouterInfo - probably a null cert.
*/
public Certificate createCertificate() {
if (_context.getBooleanProperty(PROP_HIDDEN))
return new Certificate(Certificate.CERTIFICATE_TYPE_HIDDEN, null);
return Certificate.NULL_CERT;
}
/**
* @since 0.9.3
*/
@@ -698,14 +681,16 @@ public class Router implements RouterClock.ClockShiftListener {
* Ugly list of files that we need to kill if we are building a new identity
*
*/
private static final String _rebuildFiles[] = new String[] { "router.info",
"router.keys",
private static final String _rebuildFiles[] = new String[] {
CreateRouterInfoJob.INFO_FILENAME,
CreateRouterInfoJob.KEYS_FILENAME,
CreateRouterInfoJob.KEYS2_FILENAME,
"netDb/my.info", // no longer used
"connectionTag.keys", // never used?
"keyBackup/privateEncryption.key",
"keyBackup/privateSigning.key",
"keyBackup/publicEncryption.key",
"keyBackup/publicSigning.key",
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PRIVATE_ENC,
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PUBLIC_ENC,
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PRIVATE_SIGNING,
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PUBLIC_SIGNING,
"sessionKeys.dat" // no longer used
};

View File

@@ -10,7 +10,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import net.i2p.I2PAppContext;
import net.i2p.app.ClientAppManager;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.internal.InternalClientManager;
import net.i2p.router.client.ClientManagerFacadeImpl;
import net.i2p.router.crypto.TransientSessionKeyManager;

View File

@@ -1,7 +1,7 @@
package net.i2p.router;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.peermanager.TunnelHistory;
import net.i2p.stat.Rate;
import net.i2p.stat.RateAverages;

View File

@@ -12,8 +12,10 @@ import java.util.Properties;
import net.i2p.CoreVersion;
import net.i2p.crypto.SigType;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.Payload;
import net.i2p.data.PublicKey;
import net.i2p.data.i2cp.BandwidthLimitsMessage;
import net.i2p.data.i2cp.CreateLeaseSetMessage;
import net.i2p.data.i2cp.CreateSessionMessage;
@@ -37,6 +39,7 @@ import net.i2p.data.i2cp.SessionId;
import net.i2p.data.i2cp.SessionStatusMessage;
import net.i2p.data.i2cp.SetDateMessage;
import net.i2p.router.ClientTunnelSettings;
import net.i2p.router.LeaseSetKeys;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
import net.i2p.util.PasswordManager;
@@ -81,8 +84,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
_log.debug("Message received: \n" + message);
int type = message.getType();
if (!_authorized) {
// TODO change to default true
boolean strict = _context.getBooleanProperty(PROP_AUTH_STRICT);
// Default true as of 0.9.16
boolean strict = _context.getBooleanPropertyDefaultTrue(PROP_AUTH_STRICT);
if ((strict && type != GetDateMessage.MESSAGE_TYPE) ||
(type != CreateSessionMessage.MESSAGE_TYPE &&
type != GetDateMessage.MESSAGE_TYPE &&
@@ -367,8 +370,41 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
_runner.disconnectClient("Invalid CreateLeaseSetMessage");
return;
}
_context.keyManager().registerKeys(message.getLeaseSet().getDestination(), message.getSigningPrivateKey(), message.getPrivateKey());
Destination dest = _runner.getConfig().getDestination();
Destination ndest = message.getLeaseSet().getDestination();
if (!dest.equals(ndest)) {
if (_log.shouldLog(Log.ERROR))
_log.error("Different destination in LS");
_runner.disconnectClient("Different destination in LS");
return;
}
LeaseSetKeys keys = _context.keyManager().getKeys(dest);
if (keys == null ||
!message.getPrivateKey().equals(keys.getDecryptionKey())) {
// Verify and register crypto keys if new or if changed
// Private crypto key should never change, and if it does,
// one of the checks below will fail
PublicKey pk;
try {
pk = message.getPrivateKey().toPublic();
} catch (IllegalArgumentException iae) {
if (_log.shouldLog(Log.ERROR))
_log.error("Bad private key in LS");
_runner.disconnectClient("Bad private key in LS");
return;
}
if (!pk.equals(message.getLeaseSet().getEncryptionKey())) {
if (_log.shouldLog(Log.ERROR))
_log.error("Private/public crypto key mismatch in LS");
_runner.disconnectClient("Private/public crypto key mismatch in LS");
return;
}
// just register new SPK, don't verify, unused
_context.keyManager().registerKeys(dest, message.getSigningPrivateKey(), message.getPrivateKey());
} else if (!message.getSigningPrivateKey().equals(keys.getRevocationKey())) {
// just register new SPK, don't verify, unused
_context.keyManager().registerKeys(dest, message.getSigningPrivateKey(), message.getPrivateKey());
}
try {
_context.netDb().publish(message.getLeaseSet());
} catch (IllegalArgumentException iae) {

View File

@@ -38,7 +38,11 @@ class LookupDestJob extends JobImpl {
}
/**
* One of h or name non-null
* One of h or name non-null.
*
* For hash or b32 name, the dest will be returned if the LS can be found,
* even if the dest uses unsupported crypto.
*
* @param reqID must be >= 0 if name != null
* @param sessID must non-null if reqID >= 0
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
@@ -88,7 +92,7 @@ class LookupDestJob extends JobImpl {
returnFail();
} else {
DoneJob done = new DoneJob(getContext());
getContext().netDb().lookupLeaseSet(_hash, done, done, _timeout, _fromLocalDest);
getContext().netDb().lookupDestination(_hash, done, _timeout, _fromLocalDest);
}
}
@@ -98,9 +102,9 @@ class LookupDestJob extends JobImpl {
}
public String getName() { return "LeaseSet Lookup Reply to Client"; }
public void runJob() {
LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(_hash);
if (ls != null)
returnDest(ls.getDestination());
Destination dest = getContext().netDb().lookupDestinationLocally(_hash);
if (dest != null)
returnDest(dest);
else
returnFail();
}

View File

@@ -15,16 +15,17 @@ import java.util.Map;
import java.util.Set;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.Job;
import net.i2p.router.NetworkDatabaseFacade;
import net.i2p.router.RouterContext;
public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
private Map<Hash, RouterInfo> _routers;
private RouterContext _context;
private final Map<Hash, RouterInfo> _routers;
private final RouterContext _context;
public DummyNetworkDatabaseFacade(RouterContext ctx) {
_routers = Collections.synchronizedMap(new HashMap<Hash, RouterInfo>());
@@ -42,6 +43,11 @@ public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {}
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, Hash fromLocalDest) {}
public LeaseSet lookupLeaseSetLocally(Hash key) { return null; }
public void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest) {}
public Destination lookupDestinationLocally(Hash key) { return null; }
public void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
RouterInfo info = lookupRouterInfoLocally(key);
if (info == null)
@@ -50,13 +56,16 @@ public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
_context.jobQueue().addJob(onFindJob);
}
public RouterInfo lookupRouterInfoLocally(Hash key) { return _routers.get(key); }
public void publish(LeaseSet localLeaseSet) {}
public void publish(RouterInfo localRouterInfo) {}
public LeaseSet store(Hash key, LeaseSet leaseSet) { return leaseSet; }
public RouterInfo store(Hash key, RouterInfo routerInfo) {
RouterInfo rv = _routers.put(key, routerInfo);
return rv;
}
public void unpublish(LeaseSet localLeaseSet) {}
public void fail(Hash dbEntry) {
_routers.remove(dbEntry);

View File

@@ -14,7 +14,7 @@ import java.util.List;
import net.i2p.data.Certificate;
import net.i2p.data.PublicKey;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.i2np.DeliveryInstructions;
/**

View File

@@ -9,7 +9,7 @@ package net.i2p.router.message;
*/
import net.i2p.data.Hash;
import net.i2p.data.RouterIdentity;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.i2np.GarlicMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.HandlerJobBuilder;

View File

@@ -9,7 +9,7 @@ package net.i2p.router.message;
*/
import net.i2p.data.Hash;
import net.i2p.data.RouterIdentity;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.i2np.DeliveryInstructions;
import net.i2p.data.i2np.GarlicMessage;
import net.i2p.data.i2np.I2NPMessage;

View File

@@ -18,7 +18,7 @@ import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
import net.i2p.data.Payload;
import net.i2p.data.PublicKey;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.i2cp.MessageId;
@@ -425,12 +425,19 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
getContext().statManager().addRateData("client.leaseSetFailedRemoteTime", lookupTime);
}
//if (_finished == Result.NONE) {
int cause;
if (getContext().netDb().isNegativeCachedForever(_to.calculateHash())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to send to " + _toString + " because the sig type is unsupported");
cause = MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to send to " + _toString + " because we couldn't find their leaseSet");
//}
cause = MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET;
}
dieFatal(MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET);
dieFatal(cause);
}
}

View File

@@ -11,7 +11,7 @@ package net.i2p.router.message;
import java.util.Date;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.Job;
import net.i2p.router.JobImpl;

View File

@@ -14,8 +14,8 @@ import java.util.Set;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterIdentity;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DatabaseLookupMessage;

View File

@@ -12,7 +12,7 @@ import java.util.Date;
import java.util.Properties;
import net.i2p.data.DataFormatException;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SigningPrivateKey;
import net.i2p.router.JobImpl;
import net.i2p.router.Router;

View File

@@ -25,8 +25,8 @@ import gnu.getopt.Getopt;
import net.i2p.I2PAppContext;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.transport.BadCountries;
import net.i2p.router.transport.GeoIP;
import net.i2p.util.FileUtil;

View File

@@ -12,7 +12,7 @@ import java.util.Set;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;

View File

@@ -15,6 +15,8 @@ import java.util.Set;
import net.i2p.data.Hash;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.router.RouterInfo;
import net.i2p.kademlia.KBucketSet;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
@@ -71,15 +73,15 @@ class ExploreJob extends SearchJob {
* and PeerSelector doesn't include the floodfill peers,
* so we add the ff peers ourselves and then use the regular PeerSelector.
*
* TODO should we encrypt this also like we do for normal lookups?
* Could the OBEP capture it and reply with a reference to a hostile peer?
*
* @param replyTunnelId tunnel to receive replies through
* @param replyGateway gateway for the reply tunnel
* @param expiration when the search should stop
* @param peer the peer to send it to
*
* @return a DatabaseLookupMessage or GarlicMessage
*/
@Override
protected DatabaseLookupMessage buildMessage(TunnelId replyTunnelId, Hash replyGateway, long expiration) {
protected I2NPMessage buildMessage(TunnelId replyTunnelId, Hash replyGateway, long expiration, RouterInfo peer) {
DatabaseLookupMessage msg = new DatabaseLookupMessage(getContext(), true);
msg.setSearchKey(getState().getTarget());
msg.setFrom(replyGateway);
@@ -127,7 +129,27 @@ class ExploreJob extends SearchJob {
_log.debug("Peers we don't want to hear about: " + dontIncludePeers);
msg.setDontIncludePeers(dontIncludePeers);
return msg;
// Now encrypt if we can
I2NPMessage outMsg;
if (getContext().getProperty(IterativeSearchJob.PROP_ENCRYPT_RI, IterativeSearchJob.DEFAULT_ENCRYPT_RI)) {
// request encrypted reply?
if (DatabaseLookupMessage.supportsEncryptedReplies(peer)) {
MessageWrapper.OneTimeSession sess;
sess = MessageWrapper.generateSession(getContext());
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Requesting encrypted reply from " + peer.getIdentity().calculateHash() +
' ' + sess.key + ' ' + sess.tag);
msg.setReplySession(sess.key, sess.tag);
}
outMsg = MessageWrapper.wrap(getContext(), msg, peer);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getJobId() + ": Encrypted exploratory DLM for " + getState().getTarget() + " to " +
peer.getIdentity().calculateHash());
} else {
outMsg = msg;
}
return outMsg;
}
/** max # of concurrent searches */

View File

@@ -2,10 +2,10 @@ package net.i2p.router.networkdb.kademlia;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterInfo;
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.JobImpl;
import net.i2p.router.ReplyJob;
import net.i2p.router.RouterContext;
@@ -62,6 +62,9 @@ class FloodOnlyLookupMatchJob extends JobImpl implements ReplyJob {
} else {
getContext().netDb().store(dsm.getKey(), (RouterInfo) dsm.getEntry());
}
} catch (UnsupportedCryptoException uce) {
_search.failed();
return;
} catch (IllegalArgumentException iae) {
if (_log.shouldLog(Log.WARN))
_log.warn(_search.getJobId() + ": Received an invalid store reply", iae);

View File

@@ -9,7 +9,7 @@ package net.i2p.router.networkdb.kademlia;
*/
import net.i2p.data.Hash;
import net.i2p.data.RouterIdentity;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.HandlerJobBuilder;

View File

@@ -9,7 +9,7 @@ package net.i2p.router.networkdb.kademlia;
*/
import net.i2p.data.Hash;
import net.i2p.data.RouterIdentity;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.HandlerJobBuilder;

View File

@@ -3,8 +3,8 @@ package net.i2p.router.networkdb.kademlia;
import java.util.List;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.JobImpl;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;

View File

@@ -7,11 +7,12 @@ import java.util.Map;
import java.util.Set;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.Job;
import net.i2p.router.JobImpl;
import net.i2p.router.OutNetMessage;
@@ -31,7 +32,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
private final Set<Hash> _verifiesInProgress;
private FloodThrottler _floodThrottler;
private LookupThrottler _lookupThrottler;
private NegativeLookupCache _negativeCache;
/**
* This is the flood redundancy. Entries are
@@ -65,7 +65,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
_context.statManager().createRateStat("netDb.searchReplyNotValidated", "How many search replies we get that we are NOT able to validate (fetch)", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("netDb.searchReplyValidationSkipped", "How many search replies we get from unreliable peers that we skip?", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("netDb.republishQuantity", "How many peers do we need to send a found leaseSet to?", "NetworkDatabase", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("netDb.negativeCache", "Aborted lookup, already cached", "NetworkDatabase", new long[] { 60*60*1000l });
}
@Override
@@ -73,7 +72,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
super.startup();
_context.jobQueue().addJob(new FloodfillMonitorJob(_context, this));
_lookupThrottler = new LookupThrottler();
_negativeCache = new NegativeLookupCache();
// refresh old routers
Job rrj = new RefreshRoutersJob(_context, this);
@@ -171,25 +169,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
return _lookupThrottler.shouldThrottle(from, id);
}
/**
* Increment in the negative lookup cache
* @since 0.9.4
*/
void lookupFailed(Hash key) {
_negativeCache.lookupFailed(key);
}
/**
* Is the key in the negative lookup cache?
* @since 0.9.4
*/
boolean isNegativeCached(Hash key) {
boolean rv = _negativeCache.isCached(key);
if (rv)
_context.statManager().addRateData("netDb.negativeCache", 1);
return rv;
}
/**
* Send to a subset of all floodfill peers.
* We do this to implement Kademlia within the floodfills, i.e.
@@ -301,7 +280,9 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
}
/**
* Lookup using exploratory tunnels
* Lookup using exploratory tunnels.
*
* Caller should check negative cache and/or banlist before calling.
*
* Begin a kademlia style search for the key specified, which can take up to timeoutMs and
* will fire the appropriate jobs on success or timeout (or if the kademlia search completes
@@ -315,7 +296,10 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
}
/**
* Lookup using the client's tunnels
* Lookup using the client's tunnels.
*
* Caller should check negative cache and/or banlist before calling.
*
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
* @return null always
* @since 0.9.10
@@ -473,6 +457,7 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
// should we skip the search?
if (_floodfillEnabled ||
_context.jobQueue().getMaxLag() > 500 ||
_context.banlist().isBanlistedForever(peer) ||
getKBucketSetSize() > MAX_DB_BEFORE_SKIPPING_SEARCH) {
// don't try to overload ourselves (e.g. failing 3000 router refs at
// once, and then firing off 3000 netDb lookup tasks)

View File

@@ -18,8 +18,8 @@ import java.util.Set;
import java.util.TreeSet;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.kademlia.KBucketSet;
import net.i2p.kademlia.SelectionCollector;
import net.i2p.kademlia.XORComparator;

View File

@@ -6,9 +6,10 @@ import java.util.Set;
import net.i2p.data.Certificate;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
@@ -173,9 +174,9 @@ class FloodfillVerifyStoreJob extends JobImpl {
FloodfillPeerSelector sel = (FloodfillPeerSelector)_facade.getPeerSelector();
Certificate keyCert = null;
if (!_isRouterInfo) {
LeaseSet ls = _facade.lookupLeaseSetLocally(_key);
if (ls != null) {
Certificate cert = ls.getDestination().getCertificate();
Destination dest = _facade.lookupDestinationLocally(_key);
if (dest != null) {
Certificate cert = dest.getCertificate();
if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY)
keyCert = cert;
}

View File

@@ -11,8 +11,8 @@ package net.i2p.router.networkdb.kademlia;
import java.util.Set;
import net.i2p.data.Hash;
import net.i2p.data.RouterIdentity;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.DatabaseLookupMessage;

View File

@@ -14,9 +14,9 @@ import java.util.Date;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterIdentity;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.DeliveryStatusMessage;
import net.i2p.router.JobImpl;
@@ -51,6 +51,8 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
long recvBegin = System.currentTimeMillis();
String invalidMessage = null;
// set if invalid store but not his fault
boolean dontBlamePeer = false;
boolean wasNew = false;
RouterInfo prevNetDb = null;
Hash key = _message.getKey();
@@ -72,6 +74,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
if (getContext().clientManager().isLocal(key)) {
//getContext().statManager().addRateData("netDb.storeLocalLeaseSetAttempt", 1, 0);
// throw rather than return, so that we send the ack below (prevent easy attack)
dontBlamePeer = true;
throw new IllegalArgumentException("Peer attempted to store local leaseSet: " +
key.toBase64().substring(0, 4));
}
@@ -114,6 +117,9 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
//if (!ls.getReceivedAsReply())
// match.setReceivedAsPublished(true);
}
} catch (UnsupportedCryptoException uce) {
invalidMessage = uce.getMessage();
dontBlamePeer = true;
} catch (IllegalArgumentException iae) {
invalidMessage = iae.getMessage();
}
@@ -131,8 +137,10 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
if (getContext().routerHash().equals(key)) {
//getContext().statManager().addRateData("netDb.storeLocalRouterInfoAttempt", 1, 0);
// throw rather than return, so that we send the ack below (prevent easy attack)
dontBlamePeer = true;
throw new IllegalArgumentException("Peer attempted to store our RouterInfo");
}
getContext().profileManager().heardAbout(key);
prevNetDb = getContext().netDb().store(key, ri);
wasNew = ((null == prevNetDb) || (prevNetDb.getPublished() < ri.getPublished()));
// Check new routerinfo address against blocklist
@@ -152,7 +160,9 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
_log.warn("New address received, Blocklisting old peer " + key + ' ' + ri);
}
}
getContext().profileManager().heardAbout(key);
} catch (UnsupportedCryptoException uce) {
invalidMessage = uce.getMessage();
dontBlamePeer = true;
} catch (IllegalArgumentException iae) {
invalidMessage = iae.getMessage();
}
@@ -165,6 +175,8 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
long recvEnd = System.currentTimeMillis();
getContext().statManager().addRateData("netDb.storeRecvTime", recvEnd-recvBegin);
// ack even if invalid or unsupported
// TODO any cases where we shouldn't?
if (_message.getReplyToken() > 0)
sendAck();
long ackEnd = System.currentTimeMillis();
@@ -172,7 +184,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
if (_from != null)
_fromHash = _from.getHash();
if (_fromHash != null) {
if (invalidMessage == null) {
if (invalidMessage == null || dontBlamePeer) {
getContext().profileManager().dbStoreReceived(_fromHash, wasNew);
getContext().statManager().addRateData("netDb.storeHandled", ackEnd-recvEnd);
} else {
@@ -180,7 +192,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
if (_log.shouldLog(Log.WARN))
_log.warn("Peer " + _fromHash.toBase64() + " sent bad data: " + invalidMessage);
}
} else if (invalidMessage != null) {
} else if (invalidMessage != null && !dontBlamePeer) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unknown peer sent bad data: " + invalidMessage);
}

View File

@@ -7,7 +7,7 @@ import java.util.Set;
import java.util.TreeMap;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.router.JobImpl;
import net.i2p.router.OutNetMessage;

View File

@@ -1,7 +1,7 @@
package net.i2p.router.networkdb.kademlia;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
import net.i2p.util.Log;
import net.i2p.router.JobImpl;

View File

@@ -13,9 +13,9 @@ import java.util.concurrent.ConcurrentHashMap;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.router.RouterInfo;
import net.i2p.kademlia.KBucketSet;
import net.i2p.kademlia.XORComparator;
import net.i2p.router.CommSystemFacade;
@@ -28,6 +28,8 @@ import net.i2p.router.TunnelInfo;
import net.i2p.router.TunnelManagerFacade;
import net.i2p.router.util.RandomIterator;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.SystemVersion;
/**
* A traditional Kademlia search that continues to search
@@ -88,8 +90,13 @@ class IterativeSearchJob extends FloodSearchJob {
*/
private static final int MAX_CONCURRENT = 1;
/** testing */
private static final String PROP_ENCRYPT_RI = "router.encryptRouterLookups";
public static final String PROP_ENCRYPT_RI = "router.encryptRouterLookups";
/** only on fast boxes, for now */
public static final boolean DEFAULT_ENCRYPT_RI =
SystemVersion.isX86() && SystemVersion.is64Bit() &&
!SystemVersion.isApache() && !SystemVersion.isGNU() &&
NativeBigInteger.isNative();
/**
* Lookup using exploratory tunnels
@@ -315,7 +322,7 @@ class IterativeSearchJob extends FloodSearchJob {
_sentTime.put(peer, Long.valueOf(now));
I2NPMessage outMsg = null;
if (_isLease || getContext().getBooleanProperty(PROP_ENCRYPT_RI)) {
if (_isLease || getContext().getProperty(PROP_ENCRYPT_RI, DEFAULT_ENCRYPT_RI)) {
// Full ElG is fairly expensive so only do it for LS lookups
// if we have the ff RI, garlic encrypt it
RouterInfo ri = getContext().netDb().lookupRouterInfoLocally(peer);

View File

@@ -19,14 +19,20 @@ import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.i2p.crypto.SigType;
import net.i2p.data.Certificate;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.KeyCertificate;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
import net.i2p.kademlia.KBucketSet;
import net.i2p.kademlia.RejectTrimmer;
import net.i2p.kademlia.SelectionCollector;
@@ -63,6 +69,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
protected final RouterContext _context;
private final ReseedChecker _reseedChecker;
private volatile long _lastRIPublishTime;
private NegativeLookupCache _negativeCache;
/**
* Map of Hash to RepublishLeaseSetJob for leases we'realready managing.
@@ -155,6 +162,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_reseedChecker = new ReseedChecker(context);
context.statManager().createRateStat("netDb.lookupDeferred", "how many lookups are deferred?", "NetworkDatabase", new long[] { 60*60*1000 });
context.statManager().createRateStat("netDb.exploreKeySet", "how many keys are queued for exploration?", "NetworkDatabase", new long[] { 60*60*1000 });
context.statManager().createRateStat("netDb.negativeCache", "Aborted lookup, already cached", "NetworkDatabase", new long[] { 60*60*1000l });
// following are for StoreJob
context.statManager().createRateStat("netDb.storeRouterInfoSent", "How many routerInfo store messages have we sent?", "NetworkDatabase", new long[] { 60*60*1000l });
context.statManager().createRateStat("netDb.storeLeaseSetSent", "How many leaseSet store messages have we sent?", "NetworkDatabase", new long[] { 60*60*1000l });
@@ -223,6 +231,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
//_ds = null;
_exploreKeys.clear(); // hope this doesn't cause an explosion, it shouldn't.
// _exploreKeys = null;
_negativeCache.clear();
}
public synchronized void restart() {
@@ -262,6 +271,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
//_ds = new TransientDataStore();
// _exploreKeys = new HashSet(64);
_dbDir = dbDir;
_negativeCache = new NegativeLookupCache();
createHandlers();
@@ -480,7 +490,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
}
/**
* Lookup using exploratory tunnels
* Lookup using exploratory tunnels.
* Use lookupDestination() if you don't need the LS or need it validated.
*/
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
lookupLeaseSet(key, onFindJob, onFailedLookupJob, timeoutMs, null);
@@ -488,6 +499,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
/**
* Lookup using the client's tunnels
* Use lookupDestination() if you don't need the LS or need it validated.
*
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
* @since 0.9.10
*/
@@ -500,6 +513,11 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_log.debug("leaseSet found locally, firing " + onFindJob);
if (onFindJob != null)
_context.jobQueue().addJob(onFindJob);
} else if (isNegativeCached(key)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Negative cached, not searching: " + key);
if (onFailedLookupJob != null)
_context.jobQueue().addJob(onFailedLookupJob);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("leaseSet not found locally, running search");
@@ -509,6 +527,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_log.debug("after lookupLeaseSet");
}
/**
* Use lookupDestination() if you don't need the LS or need it validated.
*/
public LeaseSet lookupLeaseSetLocally(Hash key) {
if (!_initialized) return null;
DatabaseEntry ds = _ds.get(key);
@@ -532,12 +553,56 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
}
}
/**
* Lookup using the client's tunnels
* Succeeds even if LS validation and store fails due to unsupported sig type, expired, etc.
*
* Note that there are not separate success and fail jobs. Caller must call
* lookupDestinationLocally() in the job to determine success.
*
* @param onFinishedJob non-null
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
* @since 0.9.16
*/
public void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest) {
if (!_initialized) return;
Destination d = lookupDestinationLocally(key);
if (d != null) {
_context.jobQueue().addJob(onFinishedJob);
} else {
search(key, onFinishedJob, onFinishedJob, timeoutMs, true, fromLocalDest);
}
}
/**
* Lookup locally in netDB and in badDest cache
* Succeeds even if LS validation fails due to unsupported sig type, expired, etc.
*
* @since 0.9.16
*/
public Destination lookupDestinationLocally(Hash key) {
if (!_initialized) return null;
DatabaseEntry ds = _ds.get(key);
if (ds != null) {
if (ds.getType() == DatabaseEntry.KEY_TYPE_LEASESET) {
LeaseSet ls = (LeaseSet)ds;
return ls.getDestination();
}
} else {
return _negativeCache.getBadDest(key);
}
return null;
}
public void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
if (!_initialized) return;
RouterInfo ri = lookupRouterInfoLocally(key);
if (ri != null) {
if (onFindJob != null)
_context.jobQueue().addJob(onFindJob);
} else if (_context.banlist().isBanlistedForever(key)) {
if (onFailedLookupJob != null)
_context.jobQueue().addJob(onFailedLookupJob);
} else {
search(key, onFindJob, onFailedLookupJob, timeoutMs, false);
}
@@ -694,9 +759,10 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
* Unlike for RouterInfos, this is only called once, when stored.
* After that, LeaseSet.isCurrent() is used.
*
* @throws UnsupportedCryptoException if that's why it failed.
* @return reason why the entry is not valid, or null if it is valid
*/
private String validate(Hash key, LeaseSet leaseSet) {
private String validate(Hash key, LeaseSet leaseSet) throws UnsupportedCryptoException {
if (!key.equals(leaseSet.getDestination().calculateHash())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid store attempt! key does not match leaseSet.destination! key = "
@@ -704,9 +770,11 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
return "Key does not match leaseSet.destination - " + key.toBase64();
}
if (!leaseSet.verifySignature()) {
// throws UnsupportedCryptoException
processStoreFailure(key, leaseSet);
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid leaseSet signature! leaseSet = " + leaseSet);
return "Invalid leaseSet signature on " + leaseSet.getDestination().calculateHash().toBase64();
_log.warn("Invalid leaseSet signature! " + leaseSet);
return "Invalid leaseSet signature on " + key;
}
long earliest = leaseSet.getEarliestLeaseDate();
long latest = leaseSet.getLatestLeaseDate();
@@ -722,7 +790,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
+ " first exp. " + new Date(earliest)
+ " last exp. " + new Date(latest),
new Exception("Rejecting store"));
return "Expired leaseSet for " + leaseSet.getDestination().calculateHash().toBase64()
return "Expired leaseSet for " + leaseSet.getDestination().calculateHash()
+ " expired " + DataHelper.formatDuration(age) + " ago";
}
if (latest > now + (Router.CLOCK_FUDGE_FACTOR + MAX_LEASE_FUTURE)) {
@@ -739,9 +807,13 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
}
/**
* Store the leaseSet
* Store the leaseSet.
*
* If the store fails due to unsupported crypto, it will negative cache
* the hash until restart.
*
* @throws IllegalArgumentException if the leaseSet is not valid
* @throws UnsupportedCryptoException if that's why it failed.
* @return previous entry or null
*/
public LeaseSet store(Hash key, LeaseSet leaseSet) throws IllegalArgumentException {
@@ -798,6 +870,10 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
*
* Call this only on first store, to check the key and signature once
*
* If the store fails due to unsupported crypto, it will banlist
* the router hash until restart and then throw UnsupportedCrytpoException.
*
* @throws UnsupportedCryptoException if that's why it failed.
* @return reason why the entry is not valid, or null if it is valid
*/
private String validate(Hash key, RouterInfo routerInfo) throws IllegalArgumentException {
@@ -807,6 +883,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
return "Key does not match routerInfo.identity";
}
if (!routerInfo.isValid()) {
// throws UnsupportedCryptoException
processStoreFailure(key, routerInfo);
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo);
return "Invalid routerInfo signature";
@@ -892,15 +970,29 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
}
/**
* store the routerInfo
* Store the routerInfo.
*
* If the store fails due to unsupported crypto, it will banlist
* the router hash until restart and then throw UnsupportedCrytpoException.
*
* @throws IllegalArgumentException if the routerInfo is not valid
* @throws UnsupportedCryptoException if that's why it failed.
* @return previous entry or null
*/
public RouterInfo store(Hash key, RouterInfo routerInfo) throws IllegalArgumentException {
return store(key, routerInfo, true);
}
/**
* Store the routerInfo.
*
* If the store fails due to unsupported crypto, it will banlist
* the router hash until restart and then throw UnsupportedCrytpoException.
*
* @throws IllegalArgumentException if the routerInfo is not valid
* @throws UnsupportedCryptoException if that's why it failed.
* @return previous entry or null
*/
RouterInfo store(Hash key, RouterInfo routerInfo, boolean persist) throws IllegalArgumentException {
if (!_initialized) return null;
@@ -935,6 +1027,59 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
return rv;
}
/**
* If the validate fails, call this
* to determine if it was because of unsupported crypto.
*
* If so, this will banlist-forever the router hash or permanently negative cache the dest hash,
* and then throw the exception. Otherwise it does nothing.
*
* @throws UnsupportedCryptoException if that's why it failed.
* @since 0.9.16
*/
private void processStoreFailure(Hash h, DatabaseEntry entry) throws UnsupportedCryptoException {
if (entry.getHash().equals(h)) {
if (entry.getType() == DatabaseEntry.KEY_TYPE_LEASESET) {
LeaseSet ls = (LeaseSet) entry;
Destination d = ls.getDestination();
Certificate c = d.getCertificate();
if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
try {
KeyCertificate kc = c.toKeyCertificate();
SigType type = kc.getSigType();
if (type == null || !type.isAvailable()) {
failPermanently(d);
String stype = (type != null) ? type.toString() : Integer.toString(kc.getSigTypeCode());
if (_log.shouldLog(Log.WARN))
_log.warn("Unsupported sig type " + stype + " for destination " + h);
throw new UnsupportedCryptoException("Sig type " + stype);
}
} catch (DataFormatException dfe) {}
}
} else if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
RouterInfo ri = (RouterInfo) entry;
RouterIdentity id = ri.getIdentity();
Certificate c = id.getCertificate();
if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
try {
KeyCertificate kc = c.toKeyCertificate();
SigType type = kc.getSigType();
if (type == null || !type.isAvailable()) {
String stype = (type != null) ? type.toString() : Integer.toString(kc.getSigTypeCode());
_context.banlist().banlistRouterForever(h, "Unsupported signature type " + stype);
if (_log.shouldLog(Log.WARN))
_log.warn("Unsupported sig type " + stype + " for router " + h);
throw new UnsupportedCryptoException("Sig type " + stype);
}
} catch (DataFormatException dfe) {}
}
}
}
if (_log.shouldLog(Log.WARN))
_log.warn("Verify fail, cause unknown: " + entry);
}
/**
* Final remove for a leaseset.
* For a router info, will look up in the network before dropping.
@@ -1005,8 +1150,12 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
* without any match)
*
* Unused - called only by FNDF.searchFull() from FloodSearchJob which is overridden - don't use this.
*
* @throws UnsupportedOperationException always
*/
SearchJob search(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, boolean isLease) {
throw new UnsupportedOperationException();
/****
if (!_initialized) return null;
boolean isNew = true;
SearchJob searchJob = null;
@@ -1031,6 +1180,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_context.statManager().addRateData("netDb.lookupDeferred", deferred, searchJob.getExpiration()-_context.clock().now());
}
return searchJob;
****/
}
/**
@@ -1102,6 +1252,47 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_context.jobQueue().addJob(new StoreJob(_context, this, key, ds, onSuccess, onFailure, sendTimeout, toIgnore));
}
/**
* Increment in the negative lookup cache
*
* @param key for Destinations or RouterIdentities
* @since 0.9.4 moved from FNDF to KNDF in 0.9.16
*/
void lookupFailed(Hash key) {
_negativeCache.lookupFailed(key);
}
/**
* Is the key in the negative lookup cache?
*&
* @param key for Destinations or RouterIdentities
* @since 0.9.4 moved from FNDF to KNDF in 0.9.16
*/
boolean isNegativeCached(Hash key) {
boolean rv = _negativeCache.isCached(key);
if (rv)
_context.statManager().addRateData("netDb.negativeCache", 1);
return rv;
}
/**
* Negative cache until restart
* @since 0.9.16
*/
void failPermanently(Destination dest) {
_negativeCache.failPermanently(dest);
}
/**
* Is it permanently negative cached?
*
* @param key only for Destinations; for RouterIdentities, see Banlist
* @since 0.9.16
*/
public boolean isNegativeCachedForever(Hash key) {
return _negativeCache.getBadDest(key) != null;
}
/**
* Debug info, HTML formatted
* @since 0.9.10

View File

@@ -8,7 +8,7 @@ import net.i2p.crypto.TagSetHandle;
import net.i2p.data.Certificate;
import net.i2p.data.Hash;
import net.i2p.data.PublicKey;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.i2np.DeliveryInstructions;

View File

@@ -1,6 +1,9 @@
package net.i2p.router.networkdb.kademlia;
import java.util.Map;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.LHMCache;
import net.i2p.util.ObjectCounter;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
@@ -12,11 +15,15 @@ import net.i2p.util.SimpleTimer;
*/
class NegativeLookupCache {
private final ObjectCounter<Hash> counter;
private final Map<Hash, Destination> badDests;
private static final int MAX_FAILS = 3;
private static final int MAX_BAD_DESTS = 128;
private static final long CLEAN_TIME = 2*60*1000;
public NegativeLookupCache() {
this.counter = new ObjectCounter<Hash>();
this.badDests = new LHMCache<Hash, Destination>(MAX_BAD_DESTS);
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
}
@@ -25,7 +32,46 @@ class NegativeLookupCache {
}
public boolean isCached(Hash h) {
return this.counter.count(h) >= MAX_FAILS;
if (counter.count(h) >= MAX_FAILS)
return true;
synchronized(badDests) {
return badDests.get(h) != null;
}
}
/**
* Negative cache the hash until restart,
* but cache the destination.
*
* @since 0.9.16
*/
public void failPermanently(Destination dest) {
Hash h = dest.calculateHash();
synchronized(badDests) {
badDests.put(h, dest);
}
}
/**
* Get an unsupported but cached Destination
*
* @return dest or null if not cached
* @since 0.9.16
*/
public Destination getBadDest(Hash h) {
synchronized(badDests) {
return badDests.get(h);
}
}
/**
* @since 0.9.16
*/
public void clear() {
counter.clear();
synchronized(badDests) {
badDests.clear();
}
}
private class Cleaner implements SimpleTimer.TimedEvent {

View File

@@ -16,7 +16,7 @@ import java.util.Set;
import java.util.TreeMap;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.kademlia.KBucketSet;
import net.i2p.kademlia.SelectionCollector;
import net.i2p.router.RouterContext;

View File

@@ -29,7 +29,7 @@ import net.i2p.data.Base64;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.DataFormatException;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.JobImpl;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;

View File

@@ -5,7 +5,7 @@ import java.util.List;
import java.util.Set;
import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;

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