propagate from branch 'i2p.i2p.zzz.test3' (head 459a56e53d8d694591071574e87474b5e95d6494)

to branch 'i2p.i2p' (head ffa1aab7aa8e75d75c183fd6f76140f7d840a6ae)
This commit is contained in:
zzz
2009-10-01 18:18:23 +00:00
61 changed files with 1446 additions and 788 deletions

View File

@ -24,7 +24,7 @@ mkdir -p $PKG
# es: usr/local # es: usr/local
NAME=i2p-base NAME=i2p-base
VERSION=0.0.1 VERSION=0.0.1
BUILD=1sim BUILD=1sponge
ARCH=noarch ARCH=noarch
INSTALL_DIR=opt INSTALL_DIR=opt
cd $PKG cd $PKG
@ -38,5 +38,9 @@ sed "s|directory|/$INSTALL_DIR/i2p/|g" $CWD/doinst.sh > $PKG/install/doinst.sh
cat $CWD/slack-desc > $PKG/install/slack-desc cat $CWD/slack-desc > $PKG/install/slack-desc
cd $PKG cd $PKG
requiredbuilder -v -y -s $CWD $PKG #
# Not really that important to exec this.
#requiredbuilder -v -y -s $CWD $PKG
#
cat $CWD/slack-required > $PKG/install/slack-required
makepkg -l y -c n $CWD/${NAME}-$VERSION-$ARCH-$BUILD.tgz makepkg -l y -c n $CWD/${NAME}-$VERSION-$ARCH-$BUILD.tgz

View File

@ -15,7 +15,7 @@
# It's suggested to subscribe to various dns host, like i2host.i2p # It's suggested to subscribe to various dns host, like i2host.i2p
# For any additional information, visit i2host.i2p and forum.i2p # For any additional information, visit i2host.i2p and forum.i2p
BUILD=1sim BUILD=1sponge
# put here installation dir, without first and last / # put here installation dir, without first and last /
# eg: usr/local # eg: usr/local
@ -113,5 +113,11 @@ sed "s|directory|/$INSTALL_DIR/i2p/|g" $CWD/doinst.sh > $PKG/install/doinst.sh
cat $CWD/slack-desc > $PKG/install/slack-desc cat $CWD/slack-desc > $PKG/install/slack-desc
cd $PKG cd $PKG
requiredbuilder -v -y -s $CWD $PKG #
# requiredbuilder fucks up REALLY bad, and thinks java is perl?!
# It also did not catch the shell requirements! BOOOOOOOOOOO! HISSSSSSSS!
#
#requiredbuilder -v -y -s $CWD $PKG
#
cat $CWD/slack-required > $PKG/install/slack-required
makepkg -l y -c n $CWD/${NAME}-$VERSION-$ARCH-$BUILD.tgz makepkg -l y -c n $CWD/${NAME}-$VERSION-$ARCH-$BUILD.tgz

View File

@ -1,2 +1,4 @@
glibc >= 2.7-i486-17 | glibc-solibs >= 2.7-i486-17 jre >= 5
perl >= 5.10.0-i486-1 i2p-base >= 0.0.1
bash >= 3.1.017

View File

@ -85,7 +85,7 @@ public class I2PSnarkServlet extends HttpServlet {
out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + peerString + "\">\n"); out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + peerString + "\">\n");
out.write(HEADER); out.write(HEADER);
out.write("</head><body>"); out.write("</head><body>");
out.write("<center><div class=\"page\">"); out.write("<center>");
out.write("<div class=\"snarknavbar\"><a href=\"" + req.getRequestURI() + peerString + "\" title=\"Refresh page\" class=\"snarkRefresh\">I2PSnark</a> <a href=\"http://forum.i2p/viewforum.php?f=21\" class=\"snarkRefresh\" target=\"_blank\">Forum</a>\n"); out.write("<div class=\"snarknavbar\"><a href=\"" + req.getRequestURI() + peerString + "\" title=\"Refresh page\" class=\"snarkRefresh\">I2PSnark</a> <a href=\"http://forum.i2p/viewforum.php?f=21\" class=\"snarkRefresh\" target=\"_blank\">Forum</a>\n");
Map trackers = _manager.getTrackers(); Map trackers = _manager.getTrackers();
for (Iterator iter = trackers.entrySet().iterator(); iter.hasNext(); ) { for (Iterator iter = trackers.entrySet().iterator(); iter.hasNext(); ) {
@ -96,10 +96,10 @@ public class I2PSnarkServlet extends HttpServlet {
if (e < 0) if (e < 0)
continue; continue;
baseURL = baseURL.substring(e + 1); baseURL = baseURL.substring(e + 1);
out.write("<a href=\"" + baseURL + "\" class=\"snarkRefresh\" target=\"_blank\">" + name + "</a>"); out.write(" <a href=\"" + baseURL + "\" class=\"snarkRefresh\" target=\"_blank\">" + name + "</a>");
} }
out.write("</div>\n"); out.write("</div>\n");
out.write("<div class=\"mainsection\"><div class=\"snarkMessages\"><table><tr><td align=\"left\"><pre>"); out.write("<div class=\"page\"><div class=\"mainsection\"><div class=\"snarkMessages\"><table><tr><td align=\"left\"><pre>");
List msgs = _manager.getMessages(); List msgs = _manager.getMessages();
for (int i = msgs.size()-1; i >= 0; i--) { for (int i = msgs.size()-1; i >= 0; i--) {
String msg = (String)msgs.get(i); String msg = (String)msgs.get(i);
@ -498,7 +498,7 @@ public class I2PSnarkServlet extends HttpServlet {
if (remaining == 0) if (remaining == 0)
out.write("<a href=\"" + _manager.linkPrefix() + snark.meta.getName() out.write("<a href=\"" + _manager.linkPrefix() + snark.meta.getName()
+ "\" title=\"Click to access completed downloaded..\">"); + "\" title=\"View file\">");
out.write(filename); out.write(filename);
if (remaining == 0) if (remaining == 0)
out.write("</a>"); out.write("</a>");
@ -573,7 +573,7 @@ public class I2PSnarkServlet extends HttpServlet {
out.write("<tr class=\"" + rowClass + "\">"); out.write("<tr class=\"" + rowClass + "\">");
out.write("<td align=\"center\" class=\"snarkTorrentStatus " + rowClass + "\">"); out.write("<td align=\"center\" class=\"snarkTorrentStatus " + rowClass + "\">");
out.write("</td>\n\t"); out.write("</td>\n\t");
out.write("<td align=\"center\" class=\"snarkTorrentStatus " + rowClass + "\">"); out.write("<td align=\"left\" class=\"snarkTorrentStatus " + rowClass + "\">");
String ch = peer.toString().substring(0, 4); String ch = peer.toString().substring(0, 4);
String client; String client;
if ("AwMD".equals(ch)) if ("AwMD".equals(ch))
@ -592,7 +592,7 @@ public class I2PSnarkServlet extends HttpServlet {
client = "Robert"; client = "Robert";
else else
client = "Unknown (" + ch + ')'; client = "Unknown (" + ch + ')';
out.write("<font size=-1>" + client + "</font>&nbsp;&nbsp;" + peer.toString().substring(5, 9) + ""); out.write("" + client + "&nbsp;&nbsp;" + peer.toString().substring(5, 9) + "");
if (showDebug) if (showDebug)
out.write(" inactive " + (peer.getInactiveTime() / 1000) + "s"); out.write(" inactive " + (peer.getInactiveTime() / 1000) + "s");
out.write("</td>\n\t"); out.write("</td>\n\t");
@ -601,12 +601,12 @@ public class I2PSnarkServlet extends HttpServlet {
out.write("<td align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">"); out.write("<td align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
float pct = (float) (100.0 * (float) peer.completed() / snark.meta.getPieces()); float pct = (float) (100.0 * (float) peer.completed() / snark.meta.getPieces());
if (pct == 100.0) if (pct == 100.0)
out.write("<font size=-1>Seed</font>"); out.write("Seed");
else { else {
String ps = String.valueOf(pct); String ps = String.valueOf(pct);
if (ps.length() > 5) if (ps.length() > 5)
ps = ps.substring(0, 5); ps = ps.substring(0, 5);
out.write("<font size=-1>" + ps + "%</font>"); out.write("" + ps + "%");
} }
out.write("</td>\n\t"); out.write("</td>\n\t");
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">"); out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
@ -615,14 +615,14 @@ public class I2PSnarkServlet extends HttpServlet {
if (remaining > 0) { if (remaining > 0) {
if (peer.isInteresting() && !peer.isChoked()) { if (peer.isInteresting() && !peer.isChoked()) {
out.write("<font color=#008000>"); out.write("<font color=#008000>");
out.write("<font size=-1>" + formatSize(peer.getDownloadRate()) + "ps</font></font>"); out.write("" + formatSize(peer.getDownloadRate()) + "ps</font>");
} else { } else {
out.write("<font color=#a00000><font size=-1><a title=\""); out.write("<font color=#a00000><a title=\"");
if (!peer.isInteresting()) if (!peer.isInteresting())
out.write("Uninteresting\">"); out.write("Uninteresting\">");
else else
out.write("Choked\">"); out.write("Choked\">");
out.write(formatSize(peer.getDownloadRate()) + "ps</a></font></font>"); out.write(formatSize(peer.getDownloadRate()) + "ps</a></font>");
} }
} }
out.write("</td>\n\t"); out.write("</td>\n\t");
@ -630,14 +630,14 @@ public class I2PSnarkServlet extends HttpServlet {
if (pct != 100.0) { if (pct != 100.0) {
if (peer.isInterested() && !peer.isChoking()) { if (peer.isInterested() && !peer.isChoking()) {
out.write("<font color=#008000>"); out.write("<font color=#008000>");
out.write("<font size=-1>" + formatSize(peer.getUploadRate()) + "ps</font></font>"); out.write("" + formatSize(peer.getUploadRate()) + "ps</font>");
} else { } else {
out.write("<font color=#a00000><font size=-1><a title=\""); out.write("<font color=#a00000><a title=\"");
if (!peer.isInterested()) if (!peer.isInterested())
out.write("Uninterested\">"); out.write("Uninterested\">");
else else
out.write("Choking\">"); out.write("Choking\">");
out.write(formatSize(peer.getUploadRate()) + "ps</a></font></font>"); out.write(formatSize(peer.getUploadRate()) + "ps</a></font>");
} }
} }
out.write("</td>\n\t"); out.write("</td>\n\t");

View File

@ -13,7 +13,7 @@
%> %>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
<title>I2PTunnel Webmanager - Edit</title> <title>I2P Tunnel Manager - Edit</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" /> <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />

View File

@ -13,7 +13,7 @@
%> %>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
<title>I2PTunnel Webmanager - Edit</title> <title>I2P Tunnel Manager - Edit</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" /> <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />

View File

@ -16,9 +16,9 @@ net.i2p.client.streaming.I2PServerSocket#accept} method, which will provide an
application wants to create a new stream to a peer, it should do so with the application wants to create a new stream to a peer, it should do so with the
appropriate {@link net.i2p.client.streaming.I2PSocketManager#connect} call.</p> appropriate {@link net.i2p.client.streaming.I2PSocketManager#connect} call.</p>
<p>There is a simple pair of demo applications available as well - {@link <p>There is a simple pair of demo applications available as well -
net.i2p.client.streaming.StreamSinkServer} listens to a destination and dumps net.i2p.client.streaming.StreamSinkServer listens to a destination and dumps
the data from all sockets it accepts to individual files, while {@link the data from all sockets it accepts to individual files, while
net.i2p.client.streaming.StreamSinkClient} connects to a particular destination net.i2p.client.streaming.StreamSinkClient connects to a particular destination
and sends a specific amount of random data then disconnects.</p> and sends a specific amount of random data then disconnects.</p>
</body></html> </body></html>

View File

@ -0,0 +1,31 @@
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head><title>I2P Router Console - Debug</title>
<%@include file="css.jsp" %>
</head><body>
<%@include file="summary.jsp" %>
<h1>Router SKM</h1>
<div class="main" id="main">
<%
/*
* Quick and easy place to put debugging stuff
*/
net.i2p.router.RouterContext ctx = (net.i2p.router.RouterContext) net.i2p.I2PAppContext.getGlobalContext();
/*
* Print out the status for all the SessionKeyManagers
*/
ctx.sessionKeyManager().renderStatusHTML(out);
java.util.Set<net.i2p.data.Destination> clients = ctx.clientManager().listClients();
for (net.i2p.data.Destination dest : clients) {
net.i2p.data.Hash h = dest.calculateHash();
net.i2p.crypto.SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(h);
if (skm != null) {
out.print("<h1>" + h.toBase64().substring(0,6) + " SKM</h1>");
skm.renderStatusHTML(out);
}
}
%>
</div></body></html>

View File

@ -12,7 +12,7 @@
} }
// If it can't find the iframe or viewtheme.jsp I wonder if the whole thing blows up... // If it can't find the iframe or viewtheme.jsp I wonder if the whole thing blows up...
%> %>
<html><head><title>I2P Router Console</title> <html><head><title>I2P Router Console - Page Not Found</title>
<%@include file="css.jsp" %> <%@include file="css.jsp" %>
</head><body> </head><body>
<% <%
@ -22,6 +22,7 @@ if (System.getProperty("router.consoleNonce") == null) {
%> %>
<%@include file="summary.jsp" %> <%@include file="summary.jsp" %>
<h1><%=ERROR_CODE%> <%=ERROR_MESSAGE%></h1> <h1><%=ERROR_CODE%> <%=ERROR_MESSAGE%></h1>
<div class="warning" id="warning"> <div class="sorry" id="warning">
The Router Console page <%=ERROR_URI%> was not found. Sorry! You appear to be requesting a non-existent Router Console page or resource.<hr>
Error 404: <%=ERROR_URI%> not found.
</div></body></html> </div></body></html>

View File

@ -176,4 +176,4 @@ client applications can be found on our <a href="http://www.i2p2.i2p/download">d
<p>A more complete list of changes can be found <p>A more complete list of changes can be found
in the history.txt file in your i2p directory. in the history.txt file in your i2p directory.
</p><br></div></body></html> </p><hr></div></body></html>

View File

@ -102,8 +102,8 @@ public abstract class SAMHandler implements Runnable {
} }
static public void writeBytes(ByteBuffer data, SocketChannel out) throws IOException { static public void writeBytes(ByteBuffer data, SocketChannel out) throws IOException {
while (data.hasRemaining()) out.write(data); while (data.hasRemaining()) out.write(data);
out.socket().getOutputStream().flush(); out.socket().getOutputStream().flush();
} }
/** /**
@ -124,9 +124,11 @@ public abstract class SAMHandler implements Runnable {
* @return True if the string was successfully written, false otherwise * @return True if the string was successfully written, false otherwise
*/ */
protected final boolean writeString(String str) { protected final boolean writeString(String str) {
if (_log.shouldLog(Log.DEBUG)) synchronized (socketWLock) {
_log.debug("Sending the client: [" + str + "]"); if (_log.shouldLog(Log.DEBUG))
return writeString(str, socket); _log.debug("Sending the client: [" + str + "]");
return writeString(str, socket);
}
} }
public static boolean writeString(String str, SocketChannel out) public static boolean writeString(String str, SocketChannel out)

View File

@ -354,6 +354,7 @@ public class Connection {
*/ */
} }
/*********
private class PingNotifier implements ConnectionManager.PingNotifier { private class PingNotifier implements ConnectionManager.PingNotifier {
private long _startedPingOn; private long _startedPingOn;
public PingNotifier() { public PingNotifier() {
@ -367,6 +368,7 @@ public class Connection {
_options.updateRTT((int)time*2); _options.updateRTT((int)time*2);
} }
} }
*********/
List ackPackets(long ackThrough, long nacks[]) { List ackPackets(long ackThrough, long nacks[]) {
if (ackThrough < _highestAckedThrough) { if (ackThrough < _highestAckedThrough) {
@ -548,20 +550,21 @@ public class Connection {
killOutstandingPackets(); killOutstandingPackets();
} }
/** ignore tag issues */
private void killOutstandingPackets() { private void killOutstandingPackets() {
boolean tagsCancelled = false; //boolean tagsCancelled = false;
synchronized (_outboundPackets) { synchronized (_outboundPackets) {
for (Iterator iter = _outboundPackets.values().iterator(); iter.hasNext(); ) { for (Iterator iter = _outboundPackets.values().iterator(); iter.hasNext(); ) {
PacketLocal pl = (PacketLocal)iter.next(); PacketLocal pl = (PacketLocal)iter.next();
if ( (pl.getTagsSent() != null) && (pl.getTagsSent().size() > 0) ) //if ( (pl.getTagsSent() != null) && (pl.getTagsSent().size() > 0) )
tagsCancelled = true; // tagsCancelled = true;
pl.cancelled(); pl.cancelled();
} }
_outboundPackets.clear(); _outboundPackets.clear();
_outboundPackets.notifyAll(); _outboundPackets.notifyAll();
} }
if (tagsCancelled) //if (tagsCancelled)
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey()); // _context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
} }
private class DisconnectEvent implements SimpleTimer.TimedEvent { private class DisconnectEvent implements SimpleTimer.TimedEvent {
@ -1140,12 +1143,12 @@ public class Connection {
// in case things really suck, the other side may have lost thier // in case things really suck, the other side may have lost thier
// session tags (e.g. they restarted), so jump back to ElGamal. // session tags (e.g. they restarted), so jump back to ElGamal.
int failTagsAt = _options.getMaxResends() - 2; //int failTagsAt = _options.getMaxResends() - 2;
if ( (newWindowSize == 1) && (numSends == failTagsAt) ) { //if ( (newWindowSize == 1) && (numSends == failTagsAt) ) {
if (_log.shouldLog(Log.WARN)) // if (_log.shouldLog(Log.WARN))
_log.warn("Optimistically failing tags at resend " + numSends); // _log.warn("Optimistically failing tags at resend " + numSends);
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey()); // _context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
} //}
if (numSends - 1 > _options.getMaxResends()) { if (numSends - 1 > _options.getMaxResends()) {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))

View File

@ -349,24 +349,35 @@ public class ConnectionManager {
return new HashSet(_connectionByInboundId.values()); return new HashSet(_connectionByInboundId.values());
} }
} }
/** blocking */
public boolean ping(Destination peer, long timeoutMs) { public boolean ping(Destination peer, long timeoutMs) {
return ping(peer, timeoutMs, true); return ping(peer, timeoutMs, true, null);
} }
public boolean ping(Destination peer, long timeoutMs, boolean blocking) { public boolean ping(Destination peer, long timeoutMs, boolean blocking) {
return ping(peer, timeoutMs, blocking, null, null, null); return ping(peer, timeoutMs, blocking, null);
} }
/**
* @deprecated I2PSession ignores tags, use non-tag variant
* @param keyToUse ignored
* @param tagsToSend ignored
*/
public boolean ping(Destination peer, long timeoutMs, boolean blocking, SessionKey keyToUse, Set tagsToSend, PingNotifier notifier) { public boolean ping(Destination peer, long timeoutMs, boolean blocking, SessionKey keyToUse, Set tagsToSend, PingNotifier notifier) {
return ping(peer, timeoutMs, blocking, notifier);
}
public boolean ping(Destination peer, long timeoutMs, boolean blocking, PingNotifier notifier) {
Long id = new Long(_context.random().nextLong(Packet.MAX_STREAM_ID-1)+1); Long id = new Long(_context.random().nextLong(Packet.MAX_STREAM_ID-1)+1);
PacketLocal packet = new PacketLocal(_context, peer); PacketLocal packet = new PacketLocal(_context, peer);
packet.setSendStreamId(id.longValue()); packet.setSendStreamId(id.longValue());
packet.setFlag(Packet.FLAG_ECHO); packet.setFlag(Packet.FLAG_ECHO);
packet.setFlag(Packet.FLAG_SIGNATURE_INCLUDED); packet.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
packet.setOptionalFrom(_session.getMyDestination()); packet.setOptionalFrom(_session.getMyDestination());
if ( (keyToUse != null) && (tagsToSend != null) ) { //if ( (keyToUse != null) && (tagsToSend != null) ) {
packet.setKeyUsed(keyToUse); // packet.setKeyUsed(keyToUse);
packet.setTagsSent(tagsToSend); // packet.setTagsSent(tagsToSend);
} //}
PingRequest req = new PingRequest(peer, packet, notifier); PingRequest req = new PingRequest(peer, packet, notifier);
@ -435,7 +446,7 @@ public class ConnectionManager {
} }
public void pong() { public void pong() {
_log.debug("Ping successful"); _log.debug("Ping successful");
_context.sessionKeyManager().tagsDelivered(_peer.getPublicKey(), _packet.getKeyUsed(), _packet.getTagsSent()); //_context.sessionKeyManager().tagsDelivered(_peer.getPublicKey(), _packet.getKeyUsed(), _packet.getTagsSent());
synchronized (ConnectionManager.PingRequest.this) { synchronized (ConnectionManager.PingRequest.this) {
_ponged = true; _ponged = true;
ConnectionManager.PingRequest.this.notifyAll(); ConnectionManager.PingRequest.this.notifyAll();

View File

@ -263,12 +263,12 @@ public class ConnectionPacketHandler {
numResends++; numResends++;
// ACK the tags we delivered so we can use them // ACK the tags we delivered so we can use them
if ( (p.getKeyUsed() != null) && (p.getTagsSent() != null) //if ( (p.getKeyUsed() != null) && (p.getTagsSent() != null)
&& (p.getTagsSent().size() > 0) ) { // && (p.getTagsSent().size() > 0) ) {
_context.sessionKeyManager().tagsDelivered(p.getTo().getPublicKey(), // _context.sessionKeyManager().tagsDelivered(p.getTo().getPublicKey(),
p.getKeyUsed(), // p.getKeyUsed(),
p.getTagsSent()); // p.getTagsSent());
} //}
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet acked after " + p.getAckTime() + "ms: " + p); _log.debug("Packet acked after " + p.getAckTime() + "ms: " + p);
} }

View File

@ -47,11 +47,31 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
public Destination getTo() { return _to; } public Destination getTo() { return _to; }
public void setTo(Destination to) { _to = to; } public void setTo(Destination to) { _to = to; }
/**
* @deprecated should always return null
*/
public SessionKey getKeyUsed() { return _keyUsed; } public SessionKey getKeyUsed() { return _keyUsed; }
public void setKeyUsed(SessionKey key) { _keyUsed = key; }
/**
* @deprecated I2PSession throws out the tags
*/
public void setKeyUsed(SessionKey key) {
if (key != null)
_log.error("Who is sending tags thru the streaming lib?");
_keyUsed = key;
}
/**
* @deprecated should always return null or an empty set
*/
public Set getTagsSent() { return _tagsSent; } public Set getTagsSent() { return _tagsSent; }
/**
* @deprecated I2PSession throws out the tags
*/
public void setTagsSent(Set tags) { public void setTagsSent(Set tags) {
if (tags != null && tags.size() > 0)
_log.error("Who is sending tags thru the streaming lib? " + tags.size());
if ( (_tagsSent != null) && (_tagsSent.size() > 0) && (tags.size() > 0) ) { if ( (_tagsSent != null) && (_tagsSent.size() > 0) && (tags.size() > 0) ) {
//int old = _tagsSent.size(); //int old = _tagsSent.size();
//_tagsSent.addAll(tags); //_tagsSent.addAll(tags);

View File

@ -36,16 +36,18 @@ public class PacketQueue {
/** /**
* Add a new packet to be sent out ASAP * Add a new packet to be sent out ASAP
*
* keys and tags disabled since dropped in I2PSession
*/ */
public void enqueue(PacketLocal packet) { public void enqueue(PacketLocal packet) {
packet.prepare(); packet.prepare();
SessionKey keyUsed = packet.getKeyUsed(); //SessionKey keyUsed = packet.getKeyUsed();
if (keyUsed == null) //if (keyUsed == null)
keyUsed = new SessionKey(); // keyUsed = new SessionKey();
Set tagsSent = packet.getTagsSent(); //Set tagsSent = packet.getTagsSent();
if (tagsSent == null) //if (tagsSent == null)
tagsSent = new HashSet(0); // tagsSent = new HashSet(0);
// cache this from before sendMessage // cache this from before sendMessage
String conStr = null; String conStr = null;
@ -92,13 +94,19 @@ public class PacketQueue {
// I2PSessionImpl2 // I2PSessionImpl2
//sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires); //sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires);
// I2PSessionMuxedImpl // I2PSessionMuxedImpl
sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires, //sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, expires,
// I2PSession.PROTO_STREAMING, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
// I2PSessionMuxedImpl no tags
sent = _session.sendMessage(packet.getTo(), buf, 0, size, null, null, expires,
I2PSession.PROTO_STREAMING, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED); I2PSession.PROTO_STREAMING, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
else else
// I2PSessionImpl2 // I2PSessionImpl2
//sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, 0); //sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, 0);
// I2PSessionMuxedImpl // I2PSessionMuxedImpl
sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent, //sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent,
// I2PSession.PROTO_STREAMING, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
// I2PSessionMuxedImpl no tags
sent = _session.sendMessage(packet.getTo(), buf, 0, size, null, null,
I2PSession.PROTO_STREAMING, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED); I2PSession.PROTO_STREAMING, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
end = _context.clock().now(); end = _context.clock().now();
@ -129,13 +137,11 @@ public class PacketQueue {
if (c != null) // handle race on b0rk if (c != null) // handle race on b0rk
c.disconnect(false); c.disconnect(false);
} else { } else {
packet.setKeyUsed(keyUsed); //packet.setKeyUsed(keyUsed);
packet.setTagsSent(tagsSent); //packet.setTagsSent(tagsSent);
packet.incrementSends(); packet.incrementSends();
if (_log.shouldLog(Log.DEBUG)) { if (_log.shouldLog(Log.DEBUG)) {
String msg = "SEND " + packet + (tagsSent.size() > 0 String msg = "SEND " + packet
? " with " + tagsSent.size() + " tags"
: "")
+ " send # " + packet.getNumSends() + " send # " + packet.getNumSends()
+ " sendTime: " + (end-begin) + " sendTime: " + (end-begin)
+ " con: " + conStr; + " con: " + conStr;

View File

@ -73,4 +73,4 @@ Website files to change:
release-x.y.z.html (new) release-x.y.z.html (new)
Sync with mtn.i2p2.i2p Sync with mtn.i2p2.i2p
Announce on #i2p, forum.i2p Announce on #i2p, forum.i2p, freshmeat.net, launchpad.net

View File

@ -15,21 +15,21 @@
mkdir -p lib/ mkdir -p lib/
mkdir -p bin/local mkdir -p bin/local
VER=4.2.4 VER=4.3.1
if [ "$1" != "dynamic" -a ! -d gmp-$VER ] if [ "$1" != "dynamic" -a ! -d gmp-$VER ]
then then
TAR=gmp-$VER.tar.bz2 TAR=gmp-$VER.tar.lzma
if [ ! -f $TAR ] if [ ! -f $TAR ]
then then
echo "GMP tarball $TAR not found. You must download it from http://gmplib.org/" echo "Downloading ftp://ftp.gmplib.org/pub/gmp-4.3.1/gmp-4.3.1.tar.lzma"
exit 1 wget ftp://ftp.gmplib.org/pub/gmp-4.3.1/gmp-4.3.1.tar.lzma
fi fi
echo "Building the jbigi library with GMP Version $VER" echo "Building the jbigi library with GMP Version $VER"
echo "Extracting GMP..." echo "Extracting GMP..."
tar -xjf gmp-$VER.tar.bz2 tar -xf gmp-$VER.tar.lzma --lzma
fi fi
cd bin/local cd bin/local
@ -42,7 +42,7 @@ then
# --with-pic is required for static linking # --with-pic is required for static linking
../../gmp-$VER/configure --with-pic;; ../../gmp-$VER/configure --with-pic;;
*) *)
../../gmp-$VER/configure;; ../../gmp-$VER/configure --with-pic;;
esac esac
make make
sh ../../build_jbigi.sh static sh ../../build_jbigi.sh static
@ -54,7 +54,7 @@ cp *jbigi???* ../../lib/
echo 'Library copied to lib/' echo 'Library copied to lib/'
cd ../.. cd ../..
I2P=~/i2p I2P=~/i2p/i2p
if [ ! -f $I2P/lib/i2p.jar ] if [ ! -f $I2P/lib/i2p.jar ]
then then
echo "I2P installation not found in $I2P - correct \$I2P definition in script to run speed test" echo "I2P installation not found in $I2P - correct \$I2P definition in script to run speed test"

View File

@ -37,13 +37,13 @@ FreeBSD*)
Linux*) Linux*)
COMPILEFLAGS="-fPIC -Wall" COMPILEFLAGS="-fPIC -Wall"
INCLUDES="-I. -Iinclude -I$JAVA_HOME/include -I$JAVA_HOME/include/linux" INCLUDES="-I. -Iinclude -I$JAVA_HOME/include -I$JAVA_HOME/include/linux"
LINKFLAGS="-shared -static -static-libgcc -Wl,-soname,libjcpuid-x86-linux.so" LINKFLAGS="-shared -Wl,-soname,libjcpuid-x86-linux.so"
LIBFILE="lib/freenet/support/CPUInformation/libjcpuid-x86-linux.so";; LIBFILE="lib/freenet/support/CPUInformation/libjcpuid-x86-linux.so";;
esac esac
echo "Compiling C code..." echo "Compiling C code..."
rm -f $LIBFILE rm -f $LIBFILE
$CC $LINKFLAGS $INCLUDES src/*.c -o $LIBFILE $CC $COMPILEFLAGS $LINKFLAGS $INCLUDES src/*.c -o $LIBFILE
strip $LIBFILE strip $LIBFILE
echo Built $LIBFILE echo Built $LIBFILE

View File

@ -388,9 +388,13 @@ public class I2PAppContext {
* The session key manager which coordinates the sessionKey / sessionTag * The session key manager which coordinates the sessionKey / sessionTag
* data. This component allows transparent operation of the * data. This component allows transparent operation of the
* ElGamal/AES+SessionTag algorithm, and contains all of the session tags * ElGamal/AES+SessionTag algorithm, and contains all of the session tags
* for one particular application. If you want to seperate multiple apps * for one particular application.
* to have their own sessionTags and sessionKeys, they should use different *
* I2PAppContexts, and hence, different sessionKeyManagers. * This is deprecated for client use, it should be used only by the router
* as its own key manager. Not that clients are doing end-to-end crypto anyway.
*
* For client crypto within the router,
* use RouterContext.clientManager.getClientSessionKeyManager(dest)
* *
*/ */
public SessionKeyManager sessionKeyManager() { public SessionKeyManager sessionKeyManager() {

View File

@ -93,6 +93,10 @@ class I2CPMessageProducer {
/** /**
* Package up and send the payload to the router for delivery * Package up and send the payload to the router for delivery
* *
* @param tag unused - no end-to-end crypto
* @param tags unused - no end-to-end crypto
* @param key unused - no end-to-end crypto
* @param newKey unused - no end-to-end crypto
*/ */
public void sendMessage(I2PSessionImpl session, Destination dest, long nonce, byte[] payload, SessionTag tag, public void sendMessage(I2PSessionImpl session, Destination dest, long nonce, byte[] payload, SessionTag tag,
SessionKey key, Set tags, SessionKey newKey, long expires) throws I2PSessionException { SessionKey key, Set tags, SessionKey newKey, long expires) throws I2PSessionException {
@ -135,6 +139,10 @@ class I2CPMessageProducer {
/** /**
* Create a new signed payload and send it off to the destination * Create a new signed payload and send it off to the destination
* *
* @param tag unused - no end-to-end crypto
* @param tags unused - no end-to-end crypto
* @param key unused - no end-to-end crypto
* @param newKey unused - no end-to-end crypto
*/ */
private Payload createPayload(Destination dest, byte[] payload, SessionTag tag, SessionKey key, Set tags, private Payload createPayload(Destination dest, byte[] payload, SessionTag tag, SessionKey key, Set tags,
SessionKey newKey) throws I2PSessionException { SessionKey newKey) throws I2PSessionException {

View File

@ -361,17 +361,23 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
*/ */
public abstract boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException; public abstract boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException;
/**
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
*/
public abstract boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, public abstract boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed,
Set tagsSent) throws I2PSessionException; Set tagsSent) throws I2PSessionException;
public abstract void receiveStatus(int msgId, long nonce, int status); public abstract void receiveStatus(int msgId, long nonce, int status);
/****** no end-to-end crypto
protected static final Set createNewTags(int num) { protected static final Set createNewTags(int num) {
Set tags = new HashSet(); Set tags = new HashSet();
for (int i = 0; i < num; i++) for (int i = 0; i < num; i++)
tags.add(new SessionTag(true)); tags.add(new SessionTag(true));
return tags; return tags;
} }
*******/
/** /**
* Recieve a payload message and let the app know its available * Recieve a payload message and let the app know its available

View File

@ -135,14 +135,28 @@ class I2PSessionImpl2 extends I2PSessionImpl {
return sendMessage(dest, payload, offset, size, null, null, 0); return sendMessage(dest, payload, offset, size, null, null, 0);
} }
/**
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
*/
@Override @Override
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException { public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException {
return sendMessage(dest, payload, 0, payload.length, keyUsed, tagsSent, 0); return sendMessage(dest, payload, 0, payload.length, keyUsed, tagsSent, 0);
} }
/**
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent) public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent)
throws I2PSessionException { throws I2PSessionException {
return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0); return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0);
} }
/**
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expires) public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expires)
throws I2PSessionException { throws I2PSessionException {
if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message"); if (_log.shouldLog(Log.DEBUG)) _log.debug("sending message");
@ -198,13 +212,17 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private static final int NUM_TAGS = 50; private static final int NUM_TAGS = 50;
/**
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
*/
protected boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent, long expires) protected boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent, long expires)
throws I2PSessionException { throws I2PSessionException {
SessionKey key = null; //SessionKey key = null;
SessionKey newKey = null; //SessionKey newKey = null;
SessionTag tag = null; //SessionTag tag = null;
Set sentTags = null; //Set sentTags = null;
int oldTags = 0; //int oldTags = 0;
long begin = _context.clock().now(); long begin = _context.clock().now();
/*********** /***********
if (I2CPMessageProducer.END_TO_END_CRYPTO) { if (I2CPMessageProducer.END_TO_END_CRYPTO) {
@ -258,27 +276,27 @@ class I2PSessionImpl2 extends I2PSessionImpl {
long nonce = _context.random().nextInt(Integer.MAX_VALUE); long nonce = _context.random().nextInt(Integer.MAX_VALUE);
if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state"); if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state");
MessageState state = new MessageState(_context, nonce, getPrefix()); MessageState state = new MessageState(_context, nonce, getPrefix());
state.setKey(key); //state.setKey(key);
state.setTags(sentTags); //state.setTags(sentTags);
state.setNewKey(newKey); //state.setNewKey(newKey);
state.setTo(dest); state.setTo(dest);
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Setting key = " + key); //if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Setting key = " + key);
if (keyUsed != null) { //if (keyUsed != null) {
//if (I2CPMessageProducer.END_TO_END_CRYPTO) { //if (I2CPMessageProducer.END_TO_END_CRYPTO) {
// if (newKey != null) // if (newKey != null)
// keyUsed.setData(newKey.getData()); // keyUsed.setData(newKey.getData());
// else // else
// keyUsed.setData(key.getData()); // keyUsed.setData(key.getData());
//} else { //} else {
keyUsed.setData(SessionKey.INVALID_KEY.getData()); // keyUsed.setData(SessionKey.INVALID_KEY.getData());
//} //}
} //}
if (tagsSent != null) { //if (tagsSent != null) {
if (sentTags != null) { // if (sentTags != null) {
tagsSent.addAll(sentTags); // tagsSent.addAll(sentTags);
} // }
} //}
if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state"); if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state");
long beforeSendingSync = _context.clock().now(); long beforeSendingSync = _context.clock().now();
@ -293,7 +311,8 @@ class I2PSessionImpl2 extends I2PSessionImpl {
+ state.getNonce() + " for best effort " + state.getNonce() + " for best effort "
+ " sync took " + (inSendingSync-beforeSendingSync) + " sync took " + (inSendingSync-beforeSendingSync)
+ " add took " + (afterSendingSync-inSendingSync)); + " add took " + (afterSendingSync-inSendingSync));
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey, expires); //_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey, expires);
_producer.sendMessage(this, dest, nonce, payload, null, null, null, null, expires);
// since this is 'best effort', all we're waiting for is a status update // since this is 'best effort', all we're waiting for is a status update
// saying that the router received it - in theory, that should come back // saying that the router received it - in theory, that should come back

View File

@ -128,6 +128,10 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession {
return sendMessage(dest, payload, 0, payload.length, null, null, 0, proto, fromport, toport); return sendMessage(dest, payload, 0, payload.length, null, null, 0, proto, fromport, toport);
} }
/**
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
*/
@Override @Override
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
SessionKey keyUsed, Set tagsSent, long expires) SessionKey keyUsed, Set tagsSent, long expires)
@ -135,6 +139,10 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession {
return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0, PROTO_UNSPECIFIED, PORT_UNSPECIFIED, PORT_UNSPECIFIED); return sendMessage(dest, payload, offset, size, keyUsed, tagsSent, 0, PROTO_UNSPECIFIED, PORT_UNSPECIFIED, PORT_UNSPECIFIED);
} }
/**
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
*/
@Override @Override
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
int proto, int fromport, int toport) throws I2PSessionException { int proto, int fromport, int toport) throws I2PSessionException {
@ -142,6 +150,8 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession {
} }
/** /**
* @param keyUsed unused - no end-to-end crypto
* @param tagsSent unused - no end-to-end crypto
* @param proto 1-254 or 0 for unset; recommended: * @param proto 1-254 or 0 for unset; recommended:
* I2PSession.PROTO_UNSPECIFIED * I2PSession.PROTO_UNSPECIFIED
* I2PSession.PROTO_STREAMING * I2PSession.PROTO_STREAMING

View File

@ -22,6 +22,8 @@ import net.i2p.util.Log;
* of a message by accepting it, decrypting the payload, adding it to the set of * of a message by accepting it, decrypting the payload, adding it to the set of
* recieved messages, and telling the router that it has been recieved correctly. * recieved messages, and telling the router that it has been recieved correctly.
* *
* We don't really decrypt (no more end-to-end crypto)
*
* @author jrandom * @author jrandom
*/ */
class MessagePayloadMessageHandler extends HandlerImpl { class MessagePayloadMessageHandler extends HandlerImpl {
@ -51,21 +53,24 @@ class MessagePayloadMessageHandler extends HandlerImpl {
/** /**
* Decrypt the payload * Decrypt the payload
*
* We don't really decrypt (no more end-to-end crypto)
* If we do, we need to use the correct key manager in the decrypt() call below
*/ */
private Payload decryptPayload(MessagePayloadMessage msg, I2PSessionImpl session) throws DataFormatException { private Payload decryptPayload(MessagePayloadMessage msg, I2PSessionImpl session) throws DataFormatException {
Payload payload = msg.getPayload(); Payload payload = msg.getPayload();
if (!I2CPMessageProducer.END_TO_END_CRYPTO) { //if (!I2CPMessageProducer.END_TO_END_CRYPTO) {
payload.setUnencryptedData(payload.getEncryptedData()); payload.setUnencryptedData(payload.getEncryptedData());
return payload; return payload;
} //}
byte[] data = _context.elGamalAESEngine().decrypt(payload.getEncryptedData(), session.getDecryptionKey()); //byte[] data = _context.elGamalAESEngine().decrypt(payload.getEncryptedData(), session.getDecryptionKey());
if (data == null) { //if (data == null) {
if (_log.shouldLog(Log.WARN)) // if (_log.shouldLog(Log.WARN))
_log.warn("Error decrypting the payload"); // _log.warn("Error decrypting the payload");
throw new DataFormatException("Unable to decrypt the payload"); // throw new DataFormatException("Unable to decrypt the payload");
} //}
payload.setUnencryptedData(data); //payload.setUnencryptedData(data);
return payload; //return payload;
} }
} }

View File

@ -59,14 +59,18 @@ public class ElGamalAESEngine {
} }
/** /**
* Decrypt the message using the given private key using tags from the given key manager. * Decrypt the message using the given private key using tags from the default key manager.
*
* @deprecated specify the key manager!
*/ */
public byte[] decrypt(byte data[], PrivateKey targetPrivateKey) throws DataFormatException { public byte[] decrypt(byte data[], PrivateKey targetPrivateKey) throws DataFormatException {
return decrypt(data, targetPrivateKey, _context.sessionKeyManager()); return decrypt(data, targetPrivateKey, _context.sessionKeyManager());
} }
/** /**
* Decrypt the message using the given private key. This works according to the * Decrypt the message using the given private key
* and using tags from the specified key manager.
* This works according to the
* ElGamal+AES algorithm in the data structure spec. * ElGamal+AES algorithm in the data structure spec.
* *
*/ */

View File

@ -12,7 +12,7 @@ import org.bouncycastle.crypto.macs.I2PHMac;
/** /**
* Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs * Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs
* in {@link org.bouncycastle.crypto.macs.HMac} and * in {@link org.bouncycastle.crypto.macs.I2PHMac} and
* {@link org.bouncycastle.crypto.digests.MD5Digest}. * {@link org.bouncycastle.crypto.digests.MD5Digest}.
* *
*/ */

View File

@ -15,7 +15,7 @@ import org.bouncycastle.crypto.macs.I2PHMac;
/** /**
* Calculate the HMAC-MD5 of a key+message. All the good stuff occurs * Calculate the HMAC-MD5 of a key+message. All the good stuff occurs
* in {@link org.bouncycastle.crypto.macs.HMac} and * in {@link org.bouncycastle.crypto.macs.I2PHMac} and
* {@link org.bouncycastle.crypto.digests.MD5Digest}. * {@link org.bouncycastle.crypto.digests.MD5Digest}.
* *
*/ */

View File

@ -9,6 +9,8 @@ package net.i2p.crypto;
* *
*/ */
import java.io.IOException;
import java.io.Writer;
import java.util.Set; import java.util.Set;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
@ -93,7 +95,8 @@ public class SessionKeyManager {
* method after receiving an ack to a message delivering them) * method after receiving an ack to a message delivering them)
* *
*/ */
public void tagsDelivered(PublicKey target, SessionKey key, Set<SessionTag> sessionTags) { // nop public TagSetHandle tagsDelivered(PublicKey target, SessionKey key, Set<SessionTag> sessionTags) { // nop
return null;
} }
/** /**
@ -130,4 +133,8 @@ public class SessionKeyManager {
*/ */
public void shutdown() { // nop public void shutdown() { // nop
} }
public void renderStatusHTML(Writer out) throws IOException {}
public void failTags(PublicKey target, SessionKey key, TagSetHandle ts) {}
public void tagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) {}
} }

View File

@ -0,0 +1,8 @@
package net.i2p.crypto;
/**
* An opaque handle to a TagSet returned by the SessionKeyManager,
* so that OCMOSJ can report that the tags were later acked, or not.
*
*/
public interface TagSetHandle {}

View File

@ -9,14 +9,19 @@ package net.i2p.crypto;
* *
*/ */
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
@ -32,6 +37,41 @@ import net.i2p.util.SimpleTimer;
* to disk). However, this being java, we cannot guarantee that the keys aren't swapped * to disk). However, this being java, we cannot guarantee that the keys aren't swapped
* out to disk so this should not be considered secure in that sense. * out to disk so this should not be considered secure in that sense.
* *
* The outbound and inbound sides are completely independent, each with
* their own keys and tags.
*
* For a new session, outbound tags are not considered delivered until an ack is received.
* Otherwise, the loss of the first message would render all subsequent messages
* undecryptable. True?
*
* For an existing session, outbound tags are immediately considered delivered, and are
* later revoked if the ack times out. This prevents massive stream slowdown caused by
* repeated tag delivery after the minimum tag threshold is reached. Included tags
* pushes messages above the ideal 1956 size by ~2KB and causes excessive fragmentation
* and padding. As the tags are not seen by the streaming lib, they aren't accounted
* for in the window size, and one or more of a series of large messages is likely to be dropped,
* either due to high fragmentation or drop priorites at the tunnel OBEP.
*
* For this to work, the minimum tag threshold and tag delivery quanitity defined in
* GarlicMessageBuilder must be chosen with streaming lib windows sizes in mind.
* If a single TagSet is not delivered, there will be no stall as long as the
* current window size is smaller than the minimum tag threshold.
* Additional TagSets will be sent before the acked tags completely run out. See below.
* all subsequent messages will fail to decrypt.
* See ConnectionOptions in streaming for more information.
*
* There are large inefficiencies caused by the repeated delivery of tags in a new session.
* With an initial streaming window size of 6 and 40 tags per delivery, a web server
* would deliver up to 240 tags (7680 bytes, not including bundled leaseset, etc.)
* in the first volley of the response.
*
* Could the two directions be linked somehow, such that the initial request could
* contain a key or tags for the response?
*
* Should the tag threshold and quantity be adaptive?
*
* Todo: Switch to ConcurrentHashMaps and ReadWriteLocks, only get write lock during cleanup
*
*/ */
public class TransientSessionKeyManager extends SessionKeyManager { public class TransientSessionKeyManager extends SessionKeyManager {
private Log _log; private Log _log;
@ -122,6 +162,7 @@ public class TransientSessionKeyManager extends SessionKeyManager {
} }
/* FIXME Exporting non-public type through public API */ /* FIXME Exporting non-public type through public API */
/****** leftover from when we had the persistent SKM
protected void setData(Set<TagSet> inboundTagSets, Set<OutboundSession> outboundSessions) { protected void setData(Set<TagSet> inboundTagSets, Set<OutboundSession> outboundSessions) {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info("Loading " + inboundTagSets.size() + " inbound tag sets, and " _log.info("Loading " + inboundTagSets.size() + " inbound tag sets, and "
@ -148,6 +189,7 @@ public class TransientSessionKeyManager extends SessionKeyManager {
_outboundSessions.putAll(sessions); _outboundSessions.putAll(sessions);
} }
} }
******/
/** /**
* Retrieve the session key currently associated with encryption to the target, * Retrieve the session key currently associated with encryption to the target,
@ -175,13 +217,10 @@ public class TransientSessionKeyManager extends SessionKeyManager {
* Associate a new session key with the specified target. Metrics to determine * Associate a new session key with the specified target. Metrics to determine
* when to expire that key begin with this call. * when to expire that key begin with this call.
* *
* Unused except in tests?
*/ */
@Override @Override
public void createSession(PublicKey target, SessionKey key) { public void createSession(PublicKey target, SessionKey key) {
OutboundSession sess = new OutboundSession(target); createAndReturnSession(target, key);
sess.setCurrentKey(key);
addSession(sess);
} }
/** /**
@ -214,7 +253,7 @@ public class TransientSessionKeyManager extends SessionKeyManager {
if (sess.getCurrentKey().equals(key)) { if (sess.getCurrentKey().equals(key)) {
SessionTag nxt = sess.consumeNext(); SessionTag nxt = sess.consumeNext();
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Tag consumed: " + nxt + " with key: " + key.toBase64()); _log.debug("OB Tag consumed: " + nxt + " with: " + key);
return nxt; return nxt;
} }
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
@ -257,23 +296,31 @@ public class TransientSessionKeyManager extends SessionKeyManager {
/** /**
* Take note of the fact that the given sessionTags associated with the key for * Take note of the fact that the given sessionTags associated with the key for
* encryption to the target have definitely been received at the target (aka call this * encryption to the target have been sent. Whether to use the tags immediately
* method after receiving an ack to a message delivering them) * (i.e. assume they will be received) or to wait until an ack, is implementation dependent.
* *
* Here, we wait for the ack if the session is new, otherwise we use right away.
* Will this work???
* If the tags are pipelined sufficiently, it will.
*
* @return the TagSetHandle. Caller MUST subsequently call failTags() or tagsAcked()
* with this handle.
*/ */
@Override @Override
public void tagsDelivered(PublicKey target, SessionKey key, Set sessionTags) { public TagSetHandle tagsDelivered(PublicKey target, SessionKey key, Set<SessionTag> sessionTags) {
if (_log.shouldLog(Log.DEBUG)) { if (_log.shouldLog(Log.DEBUG)) {
//_log.debug("Tags delivered to set " + set + " on session " + sess); //_log.debug("Tags delivered to set " + set + " on session " + sess);
if (sessionTags.size() > 0) if (sessionTags.size() > 0)
_log.debug("Tags delivered: " + sessionTags.size() + " for key: " + key.toBase64() + ": " + sessionTags); _log.debug("Tags delivered: " + sessionTags.size() + " for key: " + key + ": " + sessionTags);
} }
OutboundSession sess = getSession(target); OutboundSession sess = getSession(target);
if (sess == null) if (sess == null)
sess = createAndReturnSession(target, key); sess = createAndReturnSession(target, key);
sess.setCurrentKey(key); else
sess.setCurrentKey(key);
TagSet set = new TagSet(sessionTags, key, _context.clock().now()); TagSet set = new TagSet(sessionTags, key, _context.clock().now());
sess.addTags(set); sess.addTags(set);
return set;
} }
/** /**
@ -281,12 +328,44 @@ public class TransientSessionKeyManager extends SessionKeyManager {
* has failed to respond when they should have. This call essentially lets the system recover * has failed to respond when they should have. This call essentially lets the system recover
* from corrupted tag sets and crashes * from corrupted tag sets and crashes
* *
* @deprecated unused and rather drastic
*/ */
@Override @Override
public void failTags(PublicKey target) { public void failTags(PublicKey target) {
removeSession(target); removeSession(target);
} }
/**
* Mark these tags as invalid, since the peer
* has failed to ack them in time.
*/
@Override
public void failTags(PublicKey target, SessionKey key, TagSetHandle ts) {
OutboundSession sess = getSession(target);
if (sess == null)
return;
if(!key.equals(sess.getCurrentKey()))
return;
sess.failTags((TagSet)ts);
if (_log.shouldLog(Log.DEBUG))
_log.debug("TagSet failed: " + ts);
}
/**
* Mark these tags as acked, start to use them (if we haven't already)
*/
@Override
public void tagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) {
OutboundSession sess = getSession(target);
if (sess == null)
return;
if(!key.equals(sess.getCurrentKey()))
return;
sess.ackTags((TagSet)ts);
if (_log.shouldLog(Log.DEBUG))
_log.debug("TagSet acked: " + ts);
}
/** /**
* Accept the given tags and associate them with the given key for decryption * Accept the given tags and associate them with the given key for decryption
* *
@ -300,9 +379,9 @@ public class TransientSessionKeyManager extends SessionKeyManager {
for (Iterator<SessionTag> iter = sessionTags.iterator(); iter.hasNext();) { for (Iterator<SessionTag> iter = sessionTags.iterator(); iter.hasNext();) {
SessionTag tag = iter.next(); SessionTag tag = iter.next();
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Receiving tag " + tag + " for key " + key.toBase64() + " / " + key.toString() + ": tagSet: " + tagSet); _log.debug("Receiving tag " + tag + " for key " + key + ": tagSet: " + tagSet);
synchronized (_inboundTagSets) { synchronized (_inboundTagSets) {
old = (TagSet)_inboundTagSets.put(tag, tagSet); old = _inboundTagSets.put(tag, tagSet);
overage = _inboundTagSets.size() - MAX_INBOUND_SESSION_TAGS; overage = _inboundTagSets.size() - MAX_INBOUND_SESSION_TAGS;
if (old != null) { if (old != null) {
if (!old.getAssociatedKey().equals(tagSet.getAssociatedKey())) { if (!old.getAssociatedKey().equals(tagSet.getAssociatedKey())) {
@ -330,9 +409,9 @@ public class TransientSessionKeyManager extends SessionKeyManager {
} }
if (_log.shouldLog(Log.WARN)) { if (_log.shouldLog(Log.WARN)) {
_log.warn("Multiple tags matching! tagSet: " + tagSet + " and old tagSet: " + old + " tag: " + dupTag + "/" + dupTag.toBase64()); _log.warn("Multiple tags matching! tagSet: " + tagSet + " and old tagSet: " + old + " tag: " + dupTag + "/" + dupTag);
_log.warn("Earlier tag set creation: " + old + ": key=" + old.getAssociatedKey().toBase64(), old.getCreatedBy()); _log.warn("Earlier tag set creation: " + old + ": key=" + old.getAssociatedKey());
_log.warn("Current tag set creation: " + tagSet + ": key=" + tagSet.getAssociatedKey().toBase64(), tagSet.getCreatedBy()); _log.warn("Current tag set creation: " + tagSet + ": key=" + tagSet.getAssociatedKey());
} }
} }
@ -341,7 +420,7 @@ public class TransientSessionKeyManager extends SessionKeyManager {
if ( (sessionTags.size() <= 0) && (_log.shouldLog(Log.DEBUG)) ) if ( (sessionTags.size() <= 0) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("Received 0 tags for key " + key); _log.debug("Received 0 tags for key " + key);
if (false) aggressiveExpire(); //if (false) aggressiveExpire();
} }
/** /**
@ -406,26 +485,26 @@ public class TransientSessionKeyManager extends SessionKeyManager {
*/ */
@Override @Override
public SessionKey consumeTag(SessionTag tag) { public SessionKey consumeTag(SessionTag tag) {
if (false) aggressiveExpire(); //if (false) aggressiveExpire();
synchronized (_inboundTagSets) { synchronized (_inboundTagSets) {
TagSet tagSet = (TagSet) _inboundTagSets.remove(tag); TagSet tagSet = (TagSet) _inboundTagSets.remove(tag);
if (tagSet == null) { if (tagSet == null) {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Cannot consume tag " + tag + " as it is not known"); _log.debug("Cannot consume IB " + tag + " as it is not known");
return null; return null;
} }
tagSet.consume(tag); tagSet.consume(tag);
SessionKey key = tagSet.getAssociatedKey(); SessionKey key = tagSet.getAssociatedKey();
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Consuming tag " + tag.toString() + " for sessionKey " + key.toBase64() + " / " + key.toString() + " on tagSet: " + tagSet); _log.debug("Consuming IB " + tag + " for " + key + " on: " + tagSet);
return key; return key;
} }
} }
private OutboundSession getSession(PublicKey target) { private OutboundSession getSession(PublicKey target) {
synchronized (_outboundSessions) { synchronized (_outboundSessions) {
return (OutboundSession) _outboundSessions.get(target); return _outboundSessions.get(target);
} }
} }
@ -439,7 +518,7 @@ public class TransientSessionKeyManager extends SessionKeyManager {
if (target == null) return; if (target == null) return;
OutboundSession session = null; OutboundSession session = null;
synchronized (_outboundSessions) { synchronized (_outboundSessions) {
session = (OutboundSession)_outboundSessions.remove(target); session = _outboundSessions.remove(target);
} }
if ( (session != null) && (_log.shouldLog(Log.WARN)) ) if ( (session != null) && (_log.shouldLog(Log.WARN)) )
_log.warn("Removing session tags with " + session.availableTags() + " available for " _log.warn("Removing session tags with " + session.availableTags() + " available for "
@ -457,11 +536,11 @@ public class TransientSessionKeyManager extends SessionKeyManager {
int remaining = 0; int remaining = 0;
long now = _context.clock().now(); long now = _context.clock().now();
StringBuilder buf = null; StringBuilder buf = null;
StringBuilder bufSummary = null; //StringBuilder bufSummary = null;
if (_log.shouldLog(Log.DEBUG)) { if (_log.shouldLog(Log.DEBUG)) {
buf = new StringBuilder(128); buf = new StringBuilder(128);
buf.append("Expiring inbound: "); buf.append("Expiring inbound: ");
bufSummary = new StringBuilder(1024); //bufSummary = new StringBuilder(1024);
} }
synchronized (_inboundTagSets) { synchronized (_inboundTagSets) {
for (Iterator<SessionTag> iter = _inboundTagSets.keySet().iterator(); iter.hasNext();) { for (Iterator<SessionTag> iter = _inboundTagSets.keySet().iterator(); iter.hasNext();) {
@ -473,10 +552,10 @@ public class TransientSessionKeyManager extends SessionKeyManager {
iter.remove(); iter.remove();
removed++; removed++;
if (buf != null) if (buf != null)
buf.append(tag.toString()).append(" @ age ").append(DataHelper.formatDuration(age)); buf.append(tag).append(" @ age ").append(DataHelper.formatDuration(age));
} else if (false && (bufSummary != null) ) { //} else if (false && (bufSummary != null) ) {
bufSummary.append("\nTagSet: " + ts.toString() + ", key: " + ts.getAssociatedKey().toBase64()+"/" + ts.getAssociatedKey().toString() // bufSummary.append("\nTagSet: " + ts + ", key: " + ts.getAssociatedKey()
+ ": tag: " + tag.toString()); // + ": tag: " + tag);
} }
} }
remaining = _inboundTagSets.size(); remaining = _inboundTagSets.size();
@ -484,8 +563,8 @@ public class TransientSessionKeyManager extends SessionKeyManager {
_context.statManager().addRateData("crypto.sessionTagsRemaining", remaining, 0); _context.statManager().addRateData("crypto.sessionTagsRemaining", remaining, 0);
if ( (buf != null) && (removed > 0) ) if ( (buf != null) && (removed > 0) )
_log.debug(buf.toString()); _log.debug(buf.toString());
if (bufSummary != null) //if (bufSummary != null)
_log.debug("Cleaning up with remaining: " + bufSummary.toString()); // _log.debug("Cleaning up with remaining: " + bufSummary.toString());
//_log.warn("Expiring tags: [" + tagsToDrop + "]"); //_log.warn("Expiring tags: [" + tagsToDrop + "]");
@ -494,74 +573,111 @@ public class TransientSessionKeyManager extends SessionKeyManager {
PublicKey key = iter.next(); PublicKey key = iter.next();
OutboundSession sess = _outboundSessions.get(key); OutboundSession sess = _outboundSessions.get(key);
removed += sess.expireTags(); removed += sess.expireTags();
if (sess.availableTags() <= 0) { // don't kill a new session or one that's temporarily out of tags
if (sess.getLastUsedDate() < now - (SESSION_LIFETIME_MAX_MS / 2) &&
sess.availableTags() <= 0) {
iter.remove(); iter.remove();
removed++; removed++; // just to have a non-zero return value?
} }
} }
} }
return removed; return removed;
} }
public String renderStatusHTML() { @Override
public void renderStatusHTML(Writer out) throws IOException {
StringBuilder buf = new StringBuilder(1024); StringBuilder buf = new StringBuilder(1024);
buf.append("<h2>Inbound sessions</h2>"); buf.append("<h2>Inbound sessions</h2>" +
buf.append("<table>"); "<table>");
Set<TagSet> inbound = getInboundTagSets(); Set<TagSet> inbound = getInboundTagSets();
Map<SessionKey, Set<TagSet>> inboundSets = new HashMap(inbound.size()); Map<SessionKey, Set<TagSet>> inboundSets = new HashMap(inbound.size());
// Build a map of the inbound tag sets, grouped by SessionKey
for (Iterator<TagSet> iter = inbound.iterator(); iter.hasNext();) { for (Iterator<TagSet> iter = inbound.iterator(); iter.hasNext();) {
TagSet ts = iter.next(); TagSet ts = iter.next();
if (!inboundSets.containsKey(ts.getAssociatedKey())) inboundSets.put(ts.getAssociatedKey(), new HashSet()); if (!inboundSets.containsKey(ts.getAssociatedKey())) inboundSets.put(ts.getAssociatedKey(), new HashSet());
Set<TagSet> sets = inboundSets.get(ts.getAssociatedKey()); Set<TagSet> sets = inboundSets.get(ts.getAssociatedKey());
sets.add(ts); sets.add(ts);
} }
int total = 0;
long now = _context.clock().now();
for (Iterator<SessionKey> iter = inboundSets.keySet().iterator(); iter.hasNext();) { for (Iterator<SessionKey> iter = inboundSets.keySet().iterator(); iter.hasNext();) {
SessionKey skey = iter.next(); SessionKey skey = iter.next();
Set<TagSet> sets = inboundSets.get(skey); Set<TagSet> sets = new TreeSet(new TagSetComparator());
buf.append("<tr><td><b>Session key</b>: ").append(skey.toBase64()).append("</td>"); sets.addAll(inboundSets.get(skey));
buf.append("<td><b># Sets:</b> ").append(sets.size()).append("</td></tr>"); buf.append("<tr><td><b>Session key</b>: ").append(skey.toBase64()).append("</td>" +
buf.append("<tr><td colspan=\"2\"><ul>"); "<td><b># Sets:</b> ").append(sets.size()).append("</td></tr>" +
"<tr><td colspan=\"2\"><ul>");
for (Iterator<TagSet> siter = sets.iterator(); siter.hasNext();) { for (Iterator<TagSet> siter = sets.iterator(); siter.hasNext();) {
TagSet ts = siter.next(); TagSet ts = siter.next();
buf.append("<li><b>Received on:</b> ").append(new Date(ts.getDate())).append(" with ") int size = ts.getTags().size();
.append(ts.getTags().size()).append(" tags remaining</li>"); total += size;
buf.append("<li><b>Received:</b> ").append(DataHelper.formatDuration(now - ts.getDate())).append(" ago with ");
buf.append(size).append(" tags remaining</li>");
} }
buf.append("</ul></td></tr>"); buf.append("</ul></td></tr>\n");
out.write(buf.toString());
buf.setLength(0);
} }
buf.append("</table>"); buf.append("<tr><th colspan=\"2\">Total tags: ").append(total).append(" (");
buf.append(DataHelper.formatSize(32*total)).append("B)</th></tr>\n" +
buf.append("<h2><b>Outbound sessions</b></h2>"); "</table>" +
"<h2><b>Outbound sessions</b></h2>" +
buf.append("<table>"); "<table>");
total = 0;
Set<OutboundSession> outbound = getOutboundSessions(); Set<OutboundSession> outbound = getOutboundSessions();
for (Iterator<OutboundSession> iter = outbound.iterator(); iter.hasNext();) { for (Iterator<OutboundSession> iter = outbound.iterator(); iter.hasNext();) {
OutboundSession sess = iter.next(); OutboundSession sess = iter.next();
buf.append("<tr><td><b>Target key:</b> ").append(sess.getTarget().toString()).append("<br>"); Set<TagSet> sets = new TreeSet(new TagSetComparator());
buf.append("<b>Established:</b> ").append(new Date(sess.getEstablishedDate())).append("<br>"); sets.addAll(sess.getTagSets());
buf.append("<b>Last Used:</b> ").append(new Date(sess.getLastUsedDate())).append("<br>"); buf.append("<tr><td><b>Target key:</b> ").append(sess.getTarget().toBase64().substring(0, 64)).append("<br>" +
buf.append("<b># Sets:</b> ").append(sess.getTagSets().size()).append("</td></tr>"); "<b>Established:</b> ").append(DataHelper.formatDuration(now - sess.getEstablishedDate())).append(" ago<br>" +
buf.append("<tr><td><b>Session key:</b> ").append(sess.getCurrentKey().toBase64()).append("</td></tr>"); "<b>Last Used:</b> ").append(DataHelper.formatDuration(now - sess.getLastUsedDate())).append(" ago<br>" +
buf.append("<tr><td><ul>"); "<b>Session key:</b> ").append(sess.getCurrentKey().toBase64()).append("</td>" +
for (Iterator<TagSet> siter = sess.getTagSets().iterator(); siter.hasNext();) { "<td><b># Sets:</b> ").append(sess.getTagSets().size()).append("</td></tr>" +
"<tr><td colspan=\"2\"><ul>");
for (Iterator<TagSet> siter = sets.iterator(); siter.hasNext();) {
TagSet ts = siter.next(); TagSet ts = siter.next();
buf.append("<li><b>Sent on:</b> ").append(new Date(ts.getDate())).append(" with ").append( int size = ts.getTags().size();
ts.getTags() total += size;
.size()) buf.append("<li><b>Sent:</b> ").append(DataHelper.formatDuration(now - ts.getDate())).append(" ago with ");
.append(" tags remaining</li>"); buf.append(size).append(" tags remaining; acked? ").append(ts.getAcked()).append("</li>");
} }
buf.append("</ul></td></tr>"); buf.append("</ul></td></tr>\n");
out.write(buf.toString());
buf.setLength(0);
} }
buf.append("</table>"); buf.append("<tr><th colspan=\"2\">Total tags: ").append(total).append(" (");
buf.append(DataHelper.formatSize(32*total)).append("B)</th></tr>\n" +
"</table>");
return buf.toString(); out.write(buf.toString());
} }
class OutboundSession { /**
* Just for the HTML method above so we can see what's going on easier
* Earliest first
*/
private static class TagSetComparator implements Comparator {
public int compare(Object l, Object r) {
return (int) (((TagSet)l).getDate() - ((TagSet)r).getDate());
}
}
private class OutboundSession {
private PublicKey _target; private PublicKey _target;
private SessionKey _currentKey; private SessionKey _currentKey;
private long _established; private long _established;
private long _lastUsed; private long _lastUsed;
/** before the first ack, all tagsets go here. These are never expired, we rely
on the callers to call failTags() or ackTags() to remove them from this list. */
private /* FIXME final FIXME */ List<TagSet> _unackedTagSets;
/**
* As tagsets are acked, they go here.
* After the first ack, new tagsets go here (i.e. presumed acked)
*/
private /* FIXME final FIXME */ List<TagSet> _tagSets; private /* FIXME final FIXME */ List<TagSet> _tagSets;
/** set to true after first tagset is acked */
private boolean _acked;
public OutboundSession(PublicKey target) { public OutboundSession(PublicKey target) {
this(target, null, _context.clock().now(), _context.clock().now(), new ArrayList()); this(target, null, _context.clock().now(), _context.clock().now(), new ArrayList());
@ -572,13 +688,44 @@ public class TransientSessionKeyManager extends SessionKeyManager {
_currentKey = curKey; _currentKey = curKey;
_established = established; _established = established;
_lastUsed = lastUsed; _lastUsed = lastUsed;
_tagSets = tagSets; _unackedTagSets = tagSets;
_tagSets = new ArrayList();
} }
/** list of TagSet objects */ /**
* @return list of TagSet objects
* This is used only by renderStatusHTML().
* It includes both acked and unacked TagSets.
*/
List<TagSet> getTagSets() { List<TagSet> getTagSets() {
List<TagSet> rv;
synchronized (_tagSets) { synchronized (_tagSets) {
return new ArrayList(_tagSets); rv = new ArrayList(_unackedTagSets);
rv.addAll(_tagSets);
}
return rv;
}
/**
* got an ack for these tags
* For tagsets delivered after the session was acked, this is a nop
* because the tagset was originally placed directly on the acked list.
*/
void ackTags(TagSet set) {
synchronized (_tagSets) {
if (_unackedTagSets.remove(set)) {
_tagSets.add(set);
_acked = true;
}
}
set.setAcked();
}
/** didn't get an ack for these tags */
void failTags(TagSet set) {
synchronized (_tagSets) {
_unackedTagSets.remove(set);
_tagSets.remove(set);
} }
} }
@ -626,7 +773,7 @@ public class TransientSessionKeyManager extends SessionKeyManager {
int removed = 0; int removed = 0;
synchronized (_tagSets) { synchronized (_tagSets) {
for (int i = 0; i < _tagSets.size(); i++) { for (int i = 0; i < _tagSets.size(); i++) {
TagSet set = (TagSet) _tagSets.get(i); TagSet set = _tagSets.get(i);
if (set.getDate() + SESSION_TAG_DURATION_MS <= now) { if (set.getDate() + SESSION_TAG_DURATION_MS <= now) {
_tagSets.remove(i); _tagSets.remove(i);
i--; i--;
@ -642,7 +789,7 @@ public class TransientSessionKeyManager extends SessionKeyManager {
_lastUsed = now; _lastUsed = now;
synchronized (_tagSets) { synchronized (_tagSets) {
while (_tagSets.size() > 0) { while (_tagSets.size() > 0) {
TagSet set = (TagSet) _tagSets.get(0); TagSet set = _tagSets.get(0);
if (set.getDate() + SESSION_TAG_DURATION_MS > now) { if (set.getDate() + SESSION_TAG_DURATION_MS > now) {
SessionTag tag = set.consumeNext(); SessionTag tag = set.consumeNext();
if (tag != null) return tag; if (tag != null) return tag;
@ -656,14 +803,21 @@ public class TransientSessionKeyManager extends SessionKeyManager {
return null; return null;
} }
/** @return the total number of tags in acked TagSets */
public int availableTags() { public int availableTags() {
int tags = 0; int tags = 0;
long now = _context.clock().now(); long now = _context.clock().now();
synchronized (_tagSets) { synchronized (_tagSets) {
for (int i = 0; i < _tagSets.size(); i++) { for (int i = 0; i < _tagSets.size(); i++) {
TagSet set = (TagSet) _tagSets.get(i); TagSet set = _tagSets.get(i);
if (set.getDate() + SESSION_TAG_DURATION_MS > now) if (set.getDate() + SESSION_TAG_DURATION_MS > now) {
tags += set.getTags().size(); int sz = set.getTags().size();
// so tags are sent when the acked tags are below
// 30, 17, and 4.
if (!set.getAcked())
sz /= 3;
tags += sz;
}
} }
} }
return tags; return tags;
@ -689,19 +843,31 @@ public class TransientSessionKeyManager extends SessionKeyManager {
return -1; return -1;
} }
/**
* If the session has never been acked, put the TagSet on the unacked list.
* Otherwise, consider it good right away.
*/
public void addTags(TagSet set) { public void addTags(TagSet set) {
_lastUsed = _context.clock().now(); _lastUsed = _context.clock().now();
synchronized (_tagSets) { if (_acked) {
_tagSets.add(set); synchronized (_tagSets) {
_tagSets.add(set);
}
} else {
synchronized (_unackedTagSets) {
_unackedTagSets.add(set);
}
} }
} }
} }
static class TagSet { private static class TagSet implements TagSetHandle {
private Set<SessionTag> _sessionTags; private Set<SessionTag> _sessionTags;
private SessionKey _key; private SessionKey _key;
private long _date; private long _date;
private Exception _createdBy; //private Exception _createdBy;
/** did we get an ack for this tagset? */
private boolean _acked;
public TagSet(Set<SessionTag> tags, SessionKey key, long date) { public TagSet(Set<SessionTag> tags, SessionKey key, long date) {
if (key == null) throw new IllegalArgumentException("Missing key"); if (key == null) throw new IllegalArgumentException("Missing key");
@ -709,12 +875,12 @@ public class TransientSessionKeyManager extends SessionKeyManager {
_sessionTags = tags; _sessionTags = tags;
_key = key; _key = key;
_date = date; _date = date;
if (true) { //if (true) {
long now = I2PAppContext.getGlobalContext().clock().now(); // long now = I2PAppContext.getGlobalContext().clock().now();
_createdBy = new Exception("Created by: key=" + _key.toBase64() + " on " // _createdBy = new Exception("Created by: key=" + _key.toBase64() + " on "
+ new Date(now) + "/" + now // + new Date(now) + "/" + now
+ " via " + Thread.currentThread().getName()); // + " via " + Thread.currentThread().getName());
} //}
} }
/** when the tag set was created */ /** when the tag set was created */
@ -740,27 +906,31 @@ public class TransientSessionKeyManager extends SessionKeyManager {
} }
public void consume(SessionTag tag) { public void consume(SessionTag tag) {
if (contains(tag)) { _sessionTags.remove(tag);
_sessionTags.remove(tag);
}
} }
/** let's do this without counting the elements first */
public SessionTag consumeNext() { public SessionTag consumeNext() {
if (_sessionTags.size() <= 0) { SessionTag first;
try {
first = _sessionTags.iterator().next();
} catch (NoSuchElementException nsee) {
return null; return null;
} }
SessionTag first = (SessionTag) _sessionTags.iterator().next();
_sessionTags.remove(first); _sessionTags.remove(first);
return first; return first;
} }
public Exception getCreatedBy() { return _createdBy; } //public Exception getCreatedBy() { return _createdBy; }
public void setAcked() { _acked = true; }
public boolean getAcked() { return _acked; }
/****** this will return a dup if two in the same ms, so just use java
@Override @Override
public int hashCode() { public int hashCode() {
long rv = 0; long rv = 0;
if (_key != null) rv = rv * 7 + _key.hashCode(); if (_key != null) rv = _key.hashCode();
rv = rv * 7 + _date; rv = rv * 7 + _date;
// no need to hashCode the tags, key + date should be enough // no need to hashCode the tags, key + date should be enough
return (int) rv; return (int) rv;
@ -770,9 +940,20 @@ public class TransientSessionKeyManager extends SessionKeyManager {
public boolean equals(Object o) { public boolean equals(Object o) {
if ((o == null) || !(o instanceof TagSet)) return false; if ((o == null) || !(o instanceof TagSet)) return false;
TagSet ts = (TagSet) o; TagSet ts = (TagSet) o;
return DataHelper.eq(ts.getAssociatedKey(), getAssociatedKey()) return DataHelper.eq(ts.getAssociatedKey(), _key)
//&& DataHelper.eq(ts.getTags(), getTags()) //&& DataHelper.eq(ts.getTags(), getTags())
&& ts.getDate() == getDate(); && ts.getDate() == _date;
}
******/
@Override
public String toString() {
StringBuilder buf = new StringBuilder(256);
buf.append("TagSet established: ").append(new Date(_date));
buf.append(" Session key: ").append(_key.toBase64());
buf.append(" Size: ").append(_sessionTags.size());
buf.append(" Acked? ").append(_acked);
return buf.toString();
} }
} }
} }

View File

@ -89,6 +89,8 @@ public class SessionKey extends DataStructureImpl {
@Override @Override
public String toString() { public String toString() {
return "SessionKey " + toBase64();
/****
if (true) return super.toString(); if (true) return super.toString();
StringBuilder buf = new StringBuilder(64); StringBuilder buf = new StringBuilder(64);
buf.append("[SessionKey: "); buf.append("[SessionKey: ");
@ -103,5 +105,6 @@ public class SessionKey extends DataStructureImpl {
} }
buf.append("]"); buf.append("]");
return buf.toString(); return buf.toString();
****/
} }
} }

View File

@ -58,4 +58,8 @@ public class SessionTag extends ByteArray {
out.write(getData()); out.write(getData());
} }
} @Override
public String toString() {
return "SessionTag " + toBase64();
}
}

View File

@ -1,3 +1,103 @@
2009-09-21 sponge
* fixups to SlackBuilds. requiredbuilder does the wrong thing, and
thinks that java is perl! This isn't really a big deal,
the file format is simple enough and the requirements are known.
2009-09-07 mkvore
* removes a SAM v1&2 bug
2009-09-04 zzz
* SessionKeyManager, OCMOSJ, Garlic:
- Enable per-client SessionKeyManagers for better anonymity
- tagsDelivered() now means tags are sent, not acked.
- OCMOSJ uses the new TagSetHandle object returned from tagsDelivered()
to call tagsAcked() or failTags() as appropriate.
- Assume tags delivered on an established session to
reduce streaming lib stalls caused by massive tag deliveries;
should increase throughput and window sizes on long-lived streams
- Unacked tagsets on a new session are stored on a separate list
- Don't kill an OB Session just because it's temporarily out of tags
- Increase min tag threshold to 30 (was 20) due to new speculative
tags delivered scheme, and to increase effective max window
- More Java 5 and dead code cleanups, and more comments and javadoc,
debug logging cleanups
- Key toString()s for easier debugging
- HandleGarlicMessageJob: cleanup of unused things
* Tunnel TestJob:
- Consume the tag after a failed test so it doesn't
stay in the SKM
- Disable tests with router.disableTunnelTesting=true
* configkeyring.jsp: Add delete and cancel buttons
* Logging: Fix directory for rotated log
* TunnelDispatcher: Cleanup
2009-09-02 sponge
* Small logic fix for dr|z3d
2009-08-28 zzz
* Client: Fail if no date handshake after 30s or no leaseset
after 5m, rather than hanging forever.
* Console:
- Prevent OOMs in NewsFetcher or StatsSummarizer from
killing the router
- Fix favicon (-17)
* Data: Speed up many hashcodes
* DataHelper: Fix byte array hashcode for small arrays
* DecayingBloomFilter:
- Replace with new DecayingHashSet for 3 of 4 uses,
and also in the 4th if the router is low-bandwidth.
Saves 8 MB heap.
* EepGet, I2PSnark:
- New I2PSocketEepGet fetches through existing tunnels
rather than through the proxy
- Use new eepget for i2psnark
- Add a fake user agent for non-proxied fetches
- Cleanups
* NetDb:
- oops, store leaseset locally even when shutting down
(fix -16)
- Java 5 cleanups
* PRNG:
- Rename config option to prng.buffers (was router.prng.buffers)
- Change the default from 16 to 2 for I2PAppContext (saves 3.5MB)
* Tunnel:
- Adjust the random drop probability for the message size
- Concurrentify HashSetIVValidator
* TunnelPool:
- Don't test tunnels when shutting down
- Less rates
- Java 5 cleanups
2009-08-24 zzz
* ClientManager:
- Prevent client destination theft by rejecting duplicates
- Java 5 cleanups
* Console:
- Put favicon on every page
- Make every page UTF-8, ☃ safe for snowmen
- Remove options boxes on configtunnels.jsp
- Fix UTF-8 form submission (i2ptunnel too)
- Throw 403 instead of 404 from flags.jsp and viewstat.jsp
so we don't render error.jsp
* I2CP: Fix the SessionConfig serializer in DataHelper,
so that UTF-8 tunnel names are not corrupted by
I2CP and can be displayed on the console
* Message: Move 2 unused classes out of the router lib (~15KB)
(more SKM prep)
* Message, I2PSession, SessionKeyManager, Console:
Prep for SessionKeyManager work in the router -
Fix up SKM renderStatusHTML(); add debug.jsp to see it;
Redefine getClientSessionKeyManager();
More cleanups
* Ministreaming: Kill deprecation warnings
* profiles.jsp: Bulletproofing, less memory usage
* Streaming, I2PSession:
Prep for SessionKeyManager work in the router -
Comment out, deprecate, and javadoc for unused keys and tags,
they are vestiges of end-to-end crypto
* Updates: Verify zip at startup before extracting
* Wrapper: Take a couple fields out of the log so it's narrower
2009-08-20 zzz 2009-08-20 zzz
* Config files: * Config files:
- Add some path and encoding help - Add some path and encoding help

View File

@ -103,6 +103,28 @@ div.warning hr {
margin: 5px 0; margin: 5px 0;
} }
/* console error messages */
div.sorry {
padding: 20px;
background: #ddf;
margin: -2px 1px 0 195px;
border: 5px solid #bbf;
text-align: justify;
-moz-box-shadow: inset 0px 0px 0px 1px #d00;
word-wrap: break-word;
font-weight: bold;
color: #001;
}
div.sorry hr {
color: #001;
background: #001;
height: 1px;
border: 1px solid #001;
margin: 10px 0;
}
div.toolbar { div.toolbar {
margin: 0em 0em 2em 0em; margin: 0em 0em 2em 0em;
font-weight: bold; font-weight: bold;
@ -123,7 +145,7 @@ div.routersummary {
width: 185px; width: 185px;
color: inherit; color: inherit;
margin: 0; margin: 0;
padding: 7px 1px; padding: 10px 1px 7px 1px;
text-align: center !important; text-align: center !important;
border: 5px solid #bbf; border: 5px solid #bbf;
font-size: 9pt; font-size: 9pt;

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -223,6 +223,34 @@ div.warning {
word-wrap: break-word; word-wrap: break-word;
} }
/* console error messages */
div.sorry {
margin: 5px 15px 10px 220px;
padding: 20px 20px 20px 75px;
background: #005;
border: 1px solid #99f;
-moz-border-radius: 4px;
-khtml-border-radius: 4px;
border-radius: 4px;
text-align: justify;
background-image:url("images/errortriangle.png");
background-position:15px center;
background-repeat:no-repeat;
-moz-box-shadow: inset 0px 0px 0px 1px #d00;
word-wrap: break-word;
font-weight: bold;
color: #eef;
}
div.sorry hr {
color: #eef;
background: #eef;
height: 1px;
border: 1px solid #eef;
margin: 10px 0;
}
div.main { div.main {
margin: 0px 0px 20px 195px; margin: 0px 0px 20px 195px;
padding: 0 15px 15px 25px; padding: 0 15px 15px 25px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,7 +1,7 @@
/* Not yet complete. Subject to flux and change. dr|z3d - 07.25.09 */ /* Not yet complete. Subject to flux and change. dr|z3d - 07.25.09 */
body { body {
margin: 25px 10px 0 5px; margin: 15px 0 0 10px;
padding: 0em; padding: 0em;
text-align: center; text-align: center;
background: #eef; background: #eef;
@ -23,6 +23,7 @@ pre {
text-align: left; text-align: left;
font: 8pt "Lucida Console", "DejaVu Sans Mono", Courier, mono; font: 8pt "Lucida Console", "DejaVu Sans Mono", Courier, mono;
color: #333; color: #333;
margin: 10px;
} }
div.logo { div.logo {
@ -78,7 +79,7 @@ a:active{
div.routersummaryouter { div.routersummaryouter {
float: left; float: left;
width: 215px; width: 215px;
margin: 0 0 10px 20px; margin: 0 0 10px 0px;
padding: 0; padding: 0;
border: 0; border: 0;
clear: left;/* fixes a bug in Opera */ clear: left;/* fixes a bug in Opera */
@ -183,7 +184,6 @@ div.routersummary td {
border: 0 !important; border: 0 !important;
} }
div.routersummary tr:nth-child(even) { div.routersummary tr:nth-child(even) {
background-color: #f60; background-color: #f60;
background-image: none !important; background-image: none !important;
@ -194,6 +194,8 @@ div.routersummarytr:nth-child(odd) {
background-image: none !important; background-image: none !important;
} }
/* proxy error messages */
div.warning { div.warning {
margin: 5px 20px 10px 240px; margin: 5px 20px 10px 240px;
padding: 0px 25px 20px 75px; padding: 0px 25px 20px 75px;
@ -212,8 +214,36 @@ div.warning {
word-wrap: break-word; word-wrap: break-word;
} }
/* console error messages */
div.sorry {
margin: 5px 15px 10px 220px;
padding: 20px 20px 20px 75px;
background: #ffb;
border: 1px solid #002;
-moz-border-radius: 4px;
-khtml-border-radius: 4px;
border-radius: 4px;
text-align: justify;
background-image: url("images/errortriangle.png");
background-position: 15px center;
background-repeat: no-repeat;
-moz-box-shadow: inset 0px 0px 0px 1px #d00;
word-wrap: break-word;
font-weight: bold;
color: #331;
}
div.sorry hr {
color: #552;
background: #552;
height: 1px;
border: 1px solid #552;
margin: 10px 0;
}
div.main { div.main {
margin: 0px 0px 20px 220px; margin: 0px 0px 20px 195px;
padding: 0 15px 15px 25px; padding: 0 15px 15px 25px;
background: #eef; background: #eef;
text-align: left; text-align: left;
@ -237,7 +267,7 @@ div.main textarea {
} }
div.news { div.news {
margin: 0px 15px 20px 245px; margin: 0px 15px 10px 220px;
padding: 20px 30px 20px 30px; padding: 20px 30px 20px 30px;
border: 1px solid #003; border: 1px solid #003;
color: #410; color: #410;
@ -302,7 +332,7 @@ div.news h4 {
div.confignav { div.confignav {
padding: 15px 10px !important; padding: 15px 10px !important;
margin: 0 0 25px 0; margin: 0 0px 15px 0;
background: #ddf url('images/lightbluetile.png'); background: #ddf url('images/lightbluetile.png');
-moz-border-radius: 4px; -moz-border-radius: 4px;
-khtml-border-radius: 4px; -khtml-border-radius: 4px;
@ -316,8 +346,8 @@ div.confignav {
} }
div.configure { div.configure {
padding: 0 15px 15px 15px !important; padding: 0 15px 0px 15px !important;
margin: 10px 0px 25px 0; margin: 0px 0px 15px 0;
background: #ddf url('images/lightbluetile.png'); background: #ddf url('images/lightbluetile.png');
-moz-border-radius: 4px; -moz-border-radius: 4px;
-khtml-border-radius: 4px; -khtml-border-radius: 4px;
@ -327,6 +357,17 @@ div.configure {
min-width: 400px; min-width: 400px;
} }
div.configure h3, div.graphspanel h3 {
border: 1px solid #002;
border-left: 5px solid #002;
padding: 3px 5px 3px 5px;
margin: 15px 0 15px 0;
border-radius: 0 4px 4px 0;
-moz-border-radius: 0 4px 4px 0;
-khtml-border-radius: 0 4px 4px 0;
background: #eef;
}
div.graphspanel { div.graphspanel {
padding: 12px; padding: 12px;
margin: 10px 0px 25px 0; margin: 10px 0px 25px 0;
@ -365,7 +406,7 @@ div.graphspanel form {
div.messages { div.messages {
padding: 10px; padding: 10px;
margin: 10px 0 20px 0; margin: 10px 0 15px 0;
background: #ddf; background: #ddf;
-moz-border-radius: 4px; -moz-border-radius: 4px;
-khtml-border-radius: 4px; -khtml-border-radius: 4px;
@ -397,7 +438,7 @@ table {
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
border: 1px solid #000022; border: 1px solid #000022;
margin: 5px 0px 5px 0px; margin: 10px -15px 5px 0px;
cell-padding: 1px; cell-padding: 1px;
font-size: 7pt; font-size: 7pt;
background: #b4c8ff url('images/tabletitlelight.png') repeat-x; background: #b4c8ff url('images/tabletitlelight.png') repeat-x;
@ -462,7 +503,7 @@ div.main li {
text-align: left; text-align: left;
list-style: square; list-style: square;
margin: 2px 5px 0px 20px; margin: 2px 5px 0px 20px;
padding: 1px 20px 1px 10px; padding: 1px 10px 1px 10px;
line-height: 150%; line-height: 150%;
word-wrap: break-word; word-wrap: break-word;
} }
@ -528,7 +569,7 @@ h1 {
text-align: left; text-align: left;
color: #002; color: #002;
padding: 10px 15px; padding: 10px 15px;
margin: 0 15px 25px 245px; margin: 0 15px 15px 220px;
font: normal bold 16pt/120% "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; font: normal bold 16pt/120% "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif;
letter-spacing: 0.15em; letter-spacing: 0.15em;
text-transform: uppercase; text-transform: uppercase;
@ -554,7 +595,7 @@ h2 {
border-radius: 4px; border-radius: 4px;
-moz-border-radius: 4px; -moz-border-radius: 4px;
-khtml-border-radius: 4px; -khtml-border-radius: 4px;
margin: 25px 0 20px 0 !important; margin: 15px 0px 10px 0 !important;
-moz-box-shadow: inset 0px 0px 1px 0px #002; -moz-box-shadow: inset 0px 0px 1px 0px #002;
word-wrap: break-word; word-wrap: break-word;
} }
@ -571,7 +612,7 @@ h3 {
border: 1px solid #002; border: 1px solid #002;
border-left: 5px solid #002; border-left: 5px solid #002;
padding: 3px 5px 3px 5px; padding: 3px 5px 3px 5px;
margin: 20px 0 15px 0; margin: 10px 0 15px 0;
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
-moz-border-radius: 0 4px 4px 0; -moz-border-radius: 0 4px 4px 0;
-khtml-border-radius: 0 4px 4px 0; -khtml-border-radius: 0 4px 4px 0;
@ -764,8 +805,8 @@ form {}
} }
.joblog { .joblog {
margin: 25px 0 25px 0; margin: 15px 0;
padding: 20px 40px 20px 40px !important; padding: 10px 20px !important;
border: 1px solid #003; border: 1px solid #003;
background-color: #004; background-color: #004;
background: #ddf url('images/lightbluetile.png'); background: #ddf url('images/lightbluetile.png');
@ -786,6 +827,10 @@ form {}
word-wrap: break-word !important; word-wrap: break-word !important;
} }
.joblog table {
margin-top: 10px;
}
.smallhead { .smallhead {
font-size: 7pt font-size: 7pt
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,335 +1,300 @@
/* Not yet complete. Subject to flux and change. dr|z3d - 07.25.09 */ /* Not yet complete. Subject to flux and change. dr|z3d - 07.25.09 */
body { body {
background-color: #eef; background: #eef;
color:#001; color: #001;
font-family:"Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; font: 8pt "Lucida Sans Unicode","Bitstream Vera Sans",Verdana,Tahoma,Helvetica,sans-serif;
font-size: 8pt;
} }
.snarkTitle { .snarkTitle {
font-size: 12pt; font-size: 12pt;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
} }
.snarkRefresh:link, .snarkRefresh:visited { .snarkRefresh:link,.snarkRefresh:visited {
text-decoration: none !important; text-decoration: none !important;
text-transform: uppercase !important; text-transform: uppercase !important;
padding: 0 16px; padding: 0 16px;
letter-spacing: 0.05em; letter-spacing: 0.05em;
font-weight: bold; font-weight: bold;
font-size: 11pt; font-size: 11pt;
color: #005; color: #005;
text-shadow: 0px 0px 1px rgba(0, 0, 148, 0.9); text-shadow: 0px 0px 1px rgba(0,0,148,0.9);
} }
.snarkRefresh:hover{ .snarkRefresh:hover {
text-decoration: none !important; text-decoration: none !important;
text-transform: uppercase !important; text-transform: uppercase !important;
padding: 0 16px; padding: 0 16px;
letter-spacing: 0.05em; letter-spacing: 0.05em;
font-weight: bold; font-weight: bold;
font-size: 11pt; font-size: 11pt;
color: #f60; color: #f60;
border-bottom: 3px solid #f60; border-bottom: 3px solid #f60;
border-top: 3px solid #f60; border-top: 3px solid #f60;
text-shadow: 0px 0px 1px rgba(255, 128, 0, 0.9); text-shadow: 0px 0px 1px rgba(255,128,0,0.9);
} }
.snarkRefresh:active{ .snarkRefresh:active {
text-decoration: none !important; text-decoration: none !important;
text-transform: uppercase !important; text-transform: uppercase !important;
padding: 0 16px; padding: 0 16px;
letter-spacing: 0.05em; letter-spacing: 0.05em;
font-weight: bold; font-weight: bold;
font-size: 11pt; font-size: 11pt;
color: #f30; color: #f30;
border-bottom: 3px solid #f30; border-bottom: 3px solid #f30;
border-top: 3px solid #f30; border-top: 3px solid #f30;
text-shadow: 0px 0px 1px rgba(255, 32, 0, 0.5); text-shadow: 0px 0px 1px rgba(255,32,0,0.5);
} }
.snarkMessages { .snarkMessages {
background-color: #f83; font: bold 9pt "Lucida Console","DejaVu Sans Mono",Courier,mono !important;
font-family: "Lucida Console", "DejaVu Sans Mono", Courier, mono !important; text-align: left;
font-size: 9pt; margin: 0 0px 10px 0px;
font-weight: bold; padding: 0;
text-align: left; border-spacing: 0px;
margin: 0 0px 10px 0px; -moz-border-radius: 4px 0 0 0;
padding: 0; -khtml-border-radius: 4px;
border-spacing: 0px; border-radius: 4px;
-moz-border-radius: 4px 0 0 0; border: 2px solid #930;
-khtml-border-radius: 4px; overflow: auto;
border-radius: 4px; color: #531;
border: 2px solid #930; height: 64px;
text-align: left; width: auto;
overflow: auto; background: #f83 url('../console/images/orangetile.png');
background: #f40 url('../console/images/orangetile.png');
color: #531;
height: 64px;
width: auto;
} }
pre { pre {
font-family: "Lucida Console", "DejaVu Sans Mono", Courier, mono !important; width: 100%;
width: 100%; font: 8pt "Lucida Console","DejaVu Sans Mono",Courier,mono !important;
font-size: 8pt; padding: 0;
padding: 0; text-align: left !important;
text-align: left !important; height: 8px;
height: 8px;
} }
table { table {
margin: 0px 0px 10px 0px; margin: 0px 0px 10px 0px;
border: 0px; border: 0px;
padding: 0px; padding: 0px;
border-width: 0px; border-spacing: 0px;
border-spacing: 0px; border-collapse: collapse;
border-collapse: collapse;
} }
th { th {
padding: 5px; padding: 4px;
font-size: 8pt; font-size: 8pt;
border-top: 1px outset #001; border-top: 1px outset #001;
border-bottom: 1px inset #001; border-bottom: 1px inset #001;
background: #f60 url('/themes/console/images/tabletitleorange.png') repeat-x; background: #f60 url('/themes/console/images/tabletitleorange.png') repeat-x;
/* text-align: right; */ whitespace: nowrap;
whitespace: nowrap;
} }
.SnarkTorrents { .SnarkTorrents {
margin: 0; margin: 0;
border: 1px solid #001; border: 1px solid #001;
background-color: #f9f; background: #f9f;
} }
td { td {
padding: 5px; padding: 4px;
/* text-align: right;*/
} }
.snarkTorrentEven { .snarkTorrentEven {
background-color: #fb1; background: #fb1;
font-size: 7pt; font-size: 7pt;
} }
.snarkTorrentOdd { .snarkTorrentOdd {
background-color: #fa1; background: #fa1;
font-size: 7pt; font-size: 7pt;
} }
.snarkNewTorrent { .snarkNewTorrent {
font-size: 9pt; font-size: 9pt;
} }
.snarkAddInfo { .snarkAddInfo {
font-size: 9pt; font-size: 9pt;
line-height: 130% !important; line-height: 130% !important;
} }
.snarkConfigTitle { .snarkConfigTitle {
font-size: 11pt; font-size: 11pt;
font-weight: bold; font-weight: bold;
text-decoration: underline; text-decoration: underline;
text-transform: uppercase;
text-shadow: 0px 0px 2px rgba(172,172,192,0.9);
} }
.snarkConfig { .snarkConfig {
font-size: 10pt; font-size: 10pt;
width: 100%;
} }
.page { .page {
background-color: #fff; background: #fff;
color:#310; color: #310;
min-width: 800px !important; min-width: 800px !important;
/* max-width: 800px !important; */ margin: 5px 0 0 0;
margin: 5px 0px; padding: 10px 10px 0px 10px;
padding: 10px 10px 0px 10px; -moz-border-radius: 4px;
-moz-border-radius: 4px; -khtml-border-radius: 4px;
-khtml-border-radius: 4px; border-radius: 4px;
border-radius: 4px; border: 1px solid #001;
border: 1px solid #001; font-size: 9pt !important;
font-size: 9pt !important; line-height: 160% !important;
line-height: 160% !important; -moz-box-shadow: inset 0px 0px 1px 0px #002;
-moz-box-shadow: inset 0px 0px 1px 0px #002; text-align: center;
text-align: center; opacity: 1.0;
background: #ddf url('../console/light/images/lightbluetile.png');
opacity: 1.0;
} }
form { form {
line-height: 250% line-height: 250%;
} }
p { p {
line-height: 150% line-height: 150%;
}
a:link {
padding 5px;
} }
hr { hr {
color: #003; color: #003;
background: #003; background: #003;
height: 1px; height: 1px;
border: 0px solid #003; border: 0px solid #003;
width: 100%; width: 100%;
margin: 10px 0 7px 0; margin: 10px 0 7px 0;
text-align: center; text-align: center;
} }
a:link{ a:link {
color: #930; color: #930;
text-decoration: none; text-decoration: none;
font-weight: bold; font-weight: bold;
word-wrap: break-word; word-wrap: break-word;
} }
a:visited{ a:visited {
color: #606; color: #606;
text-decoration: none; text-decoration: none;
font-weight: bold; font-weight: bold;
} }
a:hover{ a:hover {
color: #900; color: #900;
font-weight: bold; font-weight: bold;
} }
input { input {
/* font-family: "Lucida Console", "DejaVu Sans Mono", Courier, mono !important;*/ font-size: 9pt;
font-size: 9pt; font-weight: bold;
font-weight: bold; text-align: left;
text-align: left; padding: 2px;
padding: 2px; }
}
select { select {
font-family:"Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; background: #ffe;
background-color: #ffe; color: #310;
color: #310; font: 9pt "Lucida Sans Unicode","Bitstream Vera Sans",Verdana,Tahoma,Helvetica,sans-serif;
font-size: 9pt;
} }
img { img {
border: none; border: none;
margin: 5px 5px 0px 5px; margin: 5px 5px 0px 5px;
opacity: 1.0; opacity: 1.0;
line-height: 100% line-height: 100%;
} }
img:hover{ img: hover {
border: none; border: none;
margin: 5px 5px 0px 5px; margin: 5px 5px 0px 5px;
opacity: 0.5; opacity: 0.5;
line-height: 100% line-height: 100%;
} }
div.section,div.mainsection {
div.section { margin: 0 0 10px 0;
margin: 0 0 10px 0; padding: 10px;
padding: 10px; border: 1px solid #001;
background: #ffe; color: #001;
border: 1px solid #001; -moz-border-radius: 4px;
text-align: center; -khtml-border-radius: 4px;
color: #001; border-radius: 4px;
-moz-border-radius: 4px; -moz-box-shadow: inset 0px 0px 1px 0px #002;
-khtml-border-radius: 4px; word-wrap: break-word;
border-radius: 4px; text-align: center;
-moz-box-shadow: inset 0px 0px 1px 0px #002; background: #ffe url('../console/light/images/tabletile.png');
word-wrap: break-word; opacity: 1.0;
text-align: center;
background: #ffe url('../console/light/images/tabletile.png');
opacity: 1.0;
}
div.mainsection {
margin: 0 0 10px 0;
padding: 10px;
background: #ffe;
border: 1px solid #001;
text-align: center;
color: #001;
-moz-border-radius: 4px;
-khtml-border-radius: 4px;
border-radius: 4px;
-moz-box-shadow: inset 0px 0px 1px 0px #002;
word-wrap: break-word;
text-align: center;
background: #ffe url('../console/light/images/tabletile.png');
opacity: 1.0;
} }
div.newtorrentsection { div.newtorrentsection {
margin: 0 0 10px 0; margin: 0 0 10px 0;
padding: 10px; padding: 0 10px 10px 10px;
background: #ffe; border: 1px solid #001;
border: 1px solid #001; text-align: center;
text-align: center; color: #001;
color: #001; -moz-border-radius: 4px;
-moz-border-radius: 4px; -khtml-border-radius: 4px;
-khtml-border-radius: 4px; border-radius: 4px;
border-radius: 4px; -moz-box-shadow: inset 0px 0px 1px 0px #002;
-moz-box-shadow: inset 0px 0px 1px 0px #002; word-wrap: break-word;
word-wrap: break-word; background: #bb4 url('../console/images/yellowtile.png');
text-align: center; opacity: 1.0;
background: #ffe url('../console/images/yellowtile.png');
opacity: 1.0;
} }
div.addtorrentsection { div.addtorrentsection {
margin: 0 0 10px 0; margin: 0 0 10px 0;
padding: 10px; padding: 0 10px 10px 10px;
background: #ffe; border: 1px solid #001;
border: 1px solid #001; text-align: center;
text-align: center; color: #001;
color: #001; -moz-border-radius: 4px;
-moz-border-radius: 4px; -khtml-border-radius: 4px;
-khtml-border-radius: 4px; border-radius: 4px;
border-radius: 4px; -moz-box-shadow: inset 0px 0px 1px 0px #002;
-moz-box-shadow: inset 0px 0px 1px 0px #002; word-wrap: break-word;
word-wrap: break-word; background: #7f7 url('../console/images/greentile.png');
text-align: center; opacity: 1.0;
background: #ffe url('../console/images/greentile.png');
opacity: 1.0;
} }
div.configsection { div.configsection {
margin: 0; margin: 0;
padding: 10px; padding: 0 10px 10px 10px;
background: #ffe; border: 1px solid #001;
border: 1px solid #001; color: #ffb;
color: #ffb; -moz-border-radius: 4px;
-moz-border-radius: 4px; -khtml-border-radius: 4px;
-khtml-border-radius: 4px; border-radius: 4px;
border-radius: 4px; -moz-box-shadow: inset 0px 0px 0px 1px #900;
-moz-box-shadow: inset 0px 0px 0px 1px #900; word-wrap: break-word;
word-wrap: break-word; text-align: center;
text-align: center; background: #700 url('../console/light/images/darkbluetile.png');
background: #ffe url('../console/light/images/darkbluetile.png'); font-weight: bold;/* red tile needs bold text! */
font-weight: bold; /* red tile needs bold text! */
} }
div.configsection a{ div.configsection a {
color: #f90; color: #f90;
} }
div.configsection a:hover{ div.configsection a: hover {
color: #f60; color: #f60;
text-decoration: underline; text-decoration: underline;
} }
.snarknavbar { .snarknavbar {
margin: 0 0 10px 0 !important; margin: 0 0 10px 0 !important;
padding: 10px; padding: 10px;
border: 1px solid #001; border: 1px solid #001;
-moz-border-radius: 4px; -moz-border-radius: 4px;
-khtml-border-radius: 4px; -khtml-border-radius: 4px;
border-radius: 4px; border-radius: 4px;
background: #eef; -moz-box-shadow: inset 0px 0px 1px 0px #002;
-moz-box-shadow: inset 0px 0px 1px 0px #002; background: #ddf url('../console/light/images/tabletile.png');
background: #ddf url('../console/light/images/tabletile.png'); text-transform: uppercase !important;
text-transform: uppercase !important; letter-spacing: 0.05em;
letter-spacing: 0.05em; font-weight: bold;
font-weight: bold; font-size: 11pt;
font-size: 11pt; color: #001;
color: #001; text-shadow: 0px 0px 1px rgba(0,0,148,0.9);
text-shadow: 0px 0px 1px rgba(0, 0, 148, 0.9); }
}

View File

@ -85,13 +85,13 @@ public abstract class ClientManagerFacade implements Service {
* *
* @return set of Destination objects * @return set of Destination objects
*/ */
public Set listClients() { return Collections.EMPTY_SET; } public Set<Destination> listClients() { return Collections.EMPTY_SET; }
/** /**
* Return the client's current config, or null if not connected * Return the client's current config, or null if not connected
* *
*/ */
public abstract SessionConfig getClientSessionConfig(Destination dest); public abstract SessionConfig getClientSessionConfig(Destination dest);
public abstract SessionKeyManager getClientSessionKeyManager(Destination dest); public abstract SessionKeyManager getClientSessionKeyManager(Hash dest);
public void renderStatusHTML(Writer out) throws IOException { } public void renderStatusHTML(Writer out) throws IOException { }
} }

View File

@ -41,7 +41,7 @@ public class DummyClientManagerFacade extends ClientManagerFacade {
public void messageDeliveryStatusUpdate(Destination fromDest, MessageId id, boolean delivered) {} public void messageDeliveryStatusUpdate(Destination fromDest, MessageId id, boolean delivered) {}
public SessionConfig getClientSessionConfig(Destination _dest) { return null; } public SessionConfig getClientSessionConfig(Destination _dest) { return null; }
public SessionKeyManager getClientSessionKeyManager(Destination _dest) { return null; } public SessionKeyManager getClientSessionKeyManager(Hash _dest) { return null; }
public void requestLeaseSet(Hash dest, LeaseSet set) {} public void requestLeaseSet(Hash dest, LeaseSet set) {}

View File

@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */ /** deprecated */
public final static String ID = "Monotone"; public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION; public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 16; public final static long BUILD = 20;
/** for example "-test" */ /** for example "-test" */
public final static String EXTRA = ""; public final static String EXTRA = "";
public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA; public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA;

View File

@ -18,6 +18,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.TransientSessionKeyManager;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.Hash; import net.i2p.data.Hash;
import net.i2p.data.LeaseSet; import net.i2p.data.LeaseSet;
@ -188,11 +189,11 @@ public class ClientConnectionRunner {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("SessionEstablished called for destination " + _destHashCache.toBase64()); _log.debug("SessionEstablished called for destination " + _destHashCache.toBase64());
_config = config; _config = config;
// per-dest unimplemented // per-destination session key manager to prevent rather easy correlation
//if (_sessionKeyManager == null) if (_sessionKeyManager == null)
// _sessionKeyManager = new TransientSessionKeyManager(_context); _sessionKeyManager = new TransientSessionKeyManager(_context);
//else else
// _log.error("SessionEstablished called for twice for destination " + _destHashCache.toBase64().substring(0,4)); _log.error("SessionEstablished called for twice for destination " + _destHashCache.toBase64().substring(0,4));
_manager.destinationEstablished(this); _manager.destinationEstablished(this);
} }

View File

@ -42,8 +42,8 @@ import net.i2p.util.Log;
public class ClientManager { public class ClientManager {
private Log _log; private Log _log;
private ClientListenerRunner _listener; private ClientListenerRunner _listener;
private final HashMap _runners; // Destination --> ClientConnectionRunner private final HashMap<Destination, ClientConnectionRunner> _runners; // Destination --> ClientConnectionRunner
private final Set _pendingRunners; // ClientConnectionRunner for clients w/out a Dest yet private final Set<ClientConnectionRunner> _pendingRunners; // ClientConnectionRunner for clients w/out a Dest yet
private RouterContext _ctx; private RouterContext _ctx;
/** ms to wait before rechecking for inbound messages to deliver to clients */ /** ms to wait before rechecking for inbound messages to deliver to clients */
@ -90,21 +90,21 @@ public class ClientManager {
public void shutdown() { public void shutdown() {
_log.info("Shutting down the ClientManager"); _log.info("Shutting down the ClientManager");
_listener.stopListening(); _listener.stopListening();
Set runners = new HashSet(); Set<ClientConnectionRunner> runners = new HashSet();
synchronized (_runners) { synchronized (_runners) {
for (Iterator iter = _runners.values().iterator(); iter.hasNext();) { for (Iterator<ClientConnectionRunner> iter = _runners.values().iterator(); iter.hasNext();) {
ClientConnectionRunner runner = (ClientConnectionRunner)iter.next(); ClientConnectionRunner runner = iter.next();
runners.add(runner); runners.add(runner);
} }
} }
synchronized (_pendingRunners) { synchronized (_pendingRunners) {
for (Iterator iter = _pendingRunners.iterator(); iter.hasNext();) { for (Iterator<ClientConnectionRunner> iter = _pendingRunners.iterator(); iter.hasNext();) {
ClientConnectionRunner runner = (ClientConnectionRunner)iter.next(); ClientConnectionRunner runner = iter.next();
runners.add(runner); runners.add(runner);
} }
} }
for (Iterator iter = runners.iterator(); iter.hasNext(); ) { for (Iterator<ClientConnectionRunner> iter = runners.iterator(); iter.hasNext(); ) {
ClientConnectionRunner runner = (ClientConnectionRunner)iter.next(); ClientConnectionRunner runner = iter.next();
runner.stopRunning(); runner.stopRunning();
} }
} }
@ -131,15 +131,26 @@ public class ClientManager {
} }
} }
/**
* Add to the clients list. Check for a dup destination.
*/
public void destinationEstablished(ClientConnectionRunner runner) { public void destinationEstablished(ClientConnectionRunner runner) {
Destination dest = runner.getConfig().getDestination();
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("DestinationEstablished called for destination " + runner.getConfig().getDestination().calculateHash().toBase64()); _log.debug("DestinationEstablished called for destination " + dest.calculateHash().toBase64());
synchronized (_pendingRunners) { synchronized (_pendingRunners) {
_pendingRunners.remove(runner); _pendingRunners.remove(runner);
} }
boolean fail = false;
synchronized (_runners) { synchronized (_runners) {
_runners.put(runner.getConfig().getDestination(), runner); fail = _runners.containsKey(dest);
if (!fail)
_runners.put(dest, runner);
}
if (fail) {
_log.log(Log.CRIT, "Client attempted to register duplicate destination " + dest.calculateHash().toBase64());
runner.disconnectClient("Duplicate destination");
} }
} }
@ -278,8 +289,8 @@ public class ClientManager {
return true; return true;
} }
public Set listClients() { public Set<Destination> listClients() {
Set rv = new HashSet(); Set<Destination> rv = new HashSet();
synchronized (_runners) { synchronized (_runners) {
rv.addAll(_runners.keySet()); rv.addAll(_runners.keySet());
} }
@ -293,7 +304,7 @@ public class ClientManager {
long inLock = 0; long inLock = 0;
synchronized (_runners) { synchronized (_runners) {
inLock = _ctx.clock().now(); inLock = _ctx.clock().now();
rv = (ClientConnectionRunner)_runners.get(dest); rv = _runners.get(dest);
} }
long afterLock = _ctx.clock().now(); long afterLock = _ctx.clock().now();
if (afterLock - beforeLock > 50) { if (afterLock - beforeLock > 50) {
@ -317,9 +328,10 @@ public class ClientManager {
/** /**
* Return the client's SessionKeyManager * Return the client's SessionKeyManager
* * Use this instead of the RouterContext.sessionKeyManager()
* to prevent correlation attacks across destinations
*/ */
public SessionKeyManager getClientSessionKeyManager(Destination dest) { public SessionKeyManager getClientSessionKeyManager(Hash dest) {
ClientConnectionRunner runner = getRunner(dest); ClientConnectionRunner runner = getRunner(dest);
if (runner != null) if (runner != null)
return runner.getSessionKeyManager(); return runner.getSessionKeyManager();
@ -331,8 +343,8 @@ public class ClientManager {
if (destHash == null) if (destHash == null)
return null; return null;
synchronized (_runners) { synchronized (_runners) {
for (Iterator iter = _runners.values().iterator(); iter.hasNext(); ) { for (Iterator<ClientConnectionRunner> iter = _runners.values().iterator(); iter.hasNext(); ) {
ClientConnectionRunner cur = (ClientConnectionRunner)iter.next(); ClientConnectionRunner cur = iter.next();
if (cur.getDestHash().equals(destHash)) if (cur.getDestHash().equals(destHash))
return cur; return cur;
} }
@ -354,8 +366,8 @@ public class ClientManager {
} }
} }
Set getRunnerDestinations() { Set<Destination> getRunnerDestinations() {
Set dests = new HashSet(); Set<Destination> dests = new HashSet();
long beforeLock = _ctx.clock().now(); long beforeLock = _ctx.clock().now();
long inLock = 0; long inLock = 0;
synchronized (_runners) { synchronized (_runners) {
@ -390,13 +402,13 @@ public class ClientManager {
StringBuilder buf = new StringBuilder(8*1024); StringBuilder buf = new StringBuilder(8*1024);
buf.append("<u><b>Local destinations</b></u><br>"); buf.append("<u><b>Local destinations</b></u><br>");
Map runners = null; Map<Destination, ClientConnectionRunner> runners = null;
synchronized (_runners) { synchronized (_runners) {
runners = (Map)_runners.clone(); runners = (Map)_runners.clone();
} }
for (Iterator iter = runners.keySet().iterator(); iter.hasNext(); ) { for (Iterator<Destination> iter = runners.keySet().iterator(); iter.hasNext(); ) {
Destination dest = (Destination)iter.next(); Destination dest = iter.next();
ClientConnectionRunner runner = (ClientConnectionRunner)runners.get(dest); ClientConnectionRunner runner = runners.get(dest);
buf.append("<b>*</b> ").append(dest.calculateHash().toBase64().substring(0,6)).append("<br>\n"); buf.append("<b>*</b> ").append(dest.calculateHash().toBase64().substring(0,6)).append("<br>\n");
LeaseSet ls = runner.getLeaseSet(); LeaseSet ls = runner.getLeaseSet();
if (ls == null) { if (ls == null) {

View File

@ -194,7 +194,7 @@ public class ClientManagerFacadeImpl extends ClientManagerFacade {
* Return the client's current manager or null if not connected * Return the client's current manager or null if not connected
* *
*/ */
public SessionKeyManager getClientSessionKeyManager(Destination dest) { public SessionKeyManager getClientSessionKeyManager(Hash dest) {
if (_manager != null) if (_manager != null)
return _manager.getClientSessionKeyManager(dest); return _manager.getClientSessionKeyManager(dest);
else { else {
@ -215,7 +215,7 @@ public class ClientManagerFacadeImpl extends ClientManagerFacade {
* @return set of Destination objects * @return set of Destination objects
*/ */
@Override @Override
public Set listClients() { public Set<Destination> listClients() {
if (_manager != null) if (_manager != null)
return _manager.listClients(); return _manager.listClients();
else else

View File

@ -17,7 +17,7 @@ import java.util.Set;
import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Destination; import net.i2p.data.Hash;
import net.i2p.data.PublicKey; import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey; import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag; import net.i2p.data.SessionTag;
@ -59,14 +59,16 @@ public class GarlicMessageBuilder {
* *
* So a value somewhat higher than the low threshold * So a value somewhat higher than the low threshold
* seems appropriate. * seems appropriate.
*
* Use care when adjusting these values. See ConnectionOptions in streaming,
* and TransientSessionKeyManager in crypto, for more information.
*/ */
private static final int DEFAULT_TAGS = 40; private static final int DEFAULT_TAGS = 40;
private static final int LOW_THRESHOLD = 20; private static final int LOW_THRESHOLD = 30;
public static int estimateAvailableTags(RouterContext ctx, PublicKey key, Destination local) { /** @param local non-null; do not use this method for the router's SessionKeyManager */
// per-dest Unimplemented public static int estimateAvailableTags(RouterContext ctx, PublicKey key, Hash local) {
//SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local); SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local);
SessionKeyManager skm = ctx.sessionKeyManager();
if (skm == null) if (skm == null)
return 0; return 0;
SessionKey curKey = skm.getCurrentKey(key); SessionKey curKey = skm.getCurrentKey(key);
@ -75,19 +77,54 @@ public class GarlicMessageBuilder {
return skm.getAvailableTags(key, curKey); return skm.getAvailableTags(key, curKey);
} }
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config) { /**
return buildMessage(ctx, config, new SessionKey(), new HashSet()); * Unused and probably a bad idea.
*
* Used below only on a recursive call if the garlic message contains a garlic message.
* We don't need the SessionKey or SesssionTags returned
* This uses the router's SKM, which is probably not what you want.
* This isn't fully implemented, because the key and tags aren't saved - maybe
* it should force elGamal?
*
* @param ctx scope
* @param config how/what to wrap
*/
private static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config) {
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
log.error("buildMessage 2 args, using router SKM", new Exception("who did it"));
return buildMessage(ctx, config, new SessionKey(), new HashSet(), ctx.sessionKeyManager());
} }
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set wrappedTags) { /**
return buildMessage(ctx, config, wrappedKey, wrappedTags, DEFAULT_TAGS); * called by OCMJH
*
* @param ctx scope
* @param config how/what to wrap
* @param wrappedKey output parameter that will be filled with the sessionKey used
* @param wrappedTags output parameter that will be filled with the sessionTags used
*/
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set<SessionTag> wrappedTags,
SessionKeyManager skm) {
return buildMessage(ctx, config, wrappedKey, wrappedTags, DEFAULT_TAGS, false, skm);
} }
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set wrappedTags, int numTagsToDeliver) { /** unused */
/***
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set wrappedTags,
int numTagsToDeliver) {
return buildMessage(ctx, config, wrappedKey, wrappedTags, numTagsToDeliver, false); return buildMessage(ctx, config, wrappedKey, wrappedTags, numTagsToDeliver, false);
} }
***/
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set wrappedTags, int numTagsToDeliver, boolean forceElGamal) { /**
* @param ctx scope
* @param config how/what to wrap
* @param wrappedKey output parameter that will be filled with the sessionKey used
* @param wrappedTags output parameter that will be filled with the sessionTags used
* @param numTagsToDeliver only if the estimated available tags are below the threshold
*/
private static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set<SessionTag> wrappedTags,
int numTagsToDeliver, boolean forceElGamal, SessionKeyManager skm) {
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class); Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
PublicKey key = config.getRecipientPublicKey(); PublicKey key = config.getRecipientPublicKey();
if (key == null) { if (key == null) {
@ -104,14 +141,14 @@ public class GarlicMessageBuilder {
if (log.shouldLog(Log.INFO)) if (log.shouldLog(Log.INFO))
log.info("Encrypted with public key " + key + " to expire on " + new Date(config.getExpiration())); log.info("Encrypted with public key " + key + " to expire on " + new Date(config.getExpiration()));
SessionKey curKey = ctx.sessionKeyManager().getCurrentKey(key); SessionKey curKey = skm.getCurrentKey(key);
SessionTag curTag = null; SessionTag curTag = null;
if (curKey == null) if (curKey == null)
curKey = ctx.sessionKeyManager().createSession(key); curKey = skm.createSession(key);
if (!forceElGamal) { if (!forceElGamal) {
curTag = ctx.sessionKeyManager().consumeNextAvailableTag(key, curKey); curTag = skm.consumeNextAvailableTag(key, curKey);
int availTags = ctx.sessionKeyManager().getAvailableTags(key, curKey); int availTags = skm.getAvailableTags(key, curKey);
if (log.shouldLog(Log.DEBUG)) if (log.shouldLog(Log.DEBUG))
log.debug("Available tags for encryption to " + key + ": " + availTags); log.debug("Available tags for encryption to " + key + ": " + availTags);
@ -120,7 +157,7 @@ public class GarlicMessageBuilder {
wrappedTags.add(new SessionTag(true)); wrappedTags.add(new SessionTag(true));
if (log.shouldLog(Log.INFO)) if (log.shouldLog(Log.INFO))
log.info("Too few are available (" + availTags + "), so we're including more"); log.info("Too few are available (" + availTags + "), so we're including more");
} else if (ctx.sessionKeyManager().getAvailableTimeLeft(key, curKey) < 60*1000) { } else if (skm.getAvailableTimeLeft(key, curKey) < 60*1000) {
// if we have enough tags, but they expire in under 30 seconds, we want more // if we have enough tags, but they expire in under 30 seconds, we want more
for (int i = 0; i < numTagsToDeliver; i++) for (int i = 0; i < numTagsToDeliver; i++)
wrappedTags.add(new SessionTag(true)); wrappedTags.add(new SessionTag(true));
@ -138,16 +175,19 @@ public class GarlicMessageBuilder {
} }
/** /**
* used by TestJob and directly above
*
* @param ctx scope * @param ctx scope
* @param config how/what to wrap * @param config how/what to wrap
* @param wrappedKey output parameter that will be filled with the sessionKey used * @param wrappedKey unused - why??
* @param wrappedTags output parameter that will be filled with the sessionTags used * @param wrappedTags output parameter that will be filled with the sessionTags used
* @param target public key of the location being garlic routed to (may be null if we * @param target public key of the location being garlic routed to (may be null if we
* know the encryptKey and encryptTag) * know the encryptKey and encryptTag)
* @param encryptKey sessionKey used to encrypt the current message * @param encryptKey sessionKey used to encrypt the current message
* @param encryptTag sessionTag used to encrypt the current message * @param encryptTag sessionTag used to encrypt the current message
*/ */
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set wrappedTags, PublicKey target, SessionKey encryptKey, SessionTag encryptTag) { public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set<SessionTag> wrappedTags,
PublicKey target, SessionKey encryptKey, SessionTag encryptTag) {
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class); Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
if (config == null) if (config == null)
throw new IllegalArgumentException("Null config specified"); throw new IllegalArgumentException("Null config specified");
@ -209,6 +249,7 @@ public class GarlicMessageBuilder {
cloves[i] = buildClove(ctx, (PayloadGarlicConfig)c); cloves[i] = buildClove(ctx, (PayloadGarlicConfig)c);
} else { } else {
log.debug("Subclove IS NOT a payload garlic clove"); log.debug("Subclove IS NOT a payload garlic clove");
// See notes below
cloves[i] = buildClove(ctx, c); cloves[i] = buildClove(ctx, c);
} }
if (cloves[i] == null) if (cloves[i] == null)
@ -242,6 +283,22 @@ public class GarlicMessageBuilder {
return buildCommonClove(ctx, clove, config); return buildCommonClove(ctx, clove, config);
} }
/**
* UNUSED
*
* The Garlic Message we are building contains another garlic message,
* as specified by a GarlicConfig (NOT a PayloadGarlicConfig).
*
* So this calls back to the top, to buildMessage(ctx, config),
* which uses the router's SKM, i.e. the wrong one.
* Unfortunately we've lost the reference to the SessionKeyManager way down here,
* so we can't call buildMessage(ctx, config, key, tags, skm).
*
* If we do ever end up constructing a garlic message that contains a garlic message,
* we'll have to fix this by passing the skm through the last buildMessage,
* through buildCloveSet, to here.
*
*/
private static byte[] buildClove(RouterContext ctx, GarlicConfig config) throws DataFormatException, IOException { private static byte[] buildClove(RouterContext ctx, GarlicConfig config) throws DataFormatException, IOException {
GarlicClove clove = new GarlicClove(ctx); GarlicClove clove = new GarlicClove(ctx);
GarlicMessage msg = buildMessage(ctx, config); GarlicMessage msg = buildMessage(ctx, config);

View File

@ -10,6 +10,7 @@ package net.i2p.router.message;
import java.util.Date; import java.util.Date;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.Certificate; import net.i2p.data.Certificate;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
@ -32,13 +33,14 @@ public class GarlicMessageParser {
_log = _context.logManager().getLog(GarlicMessageParser.class); _log = _context.logManager().getLog(GarlicMessageParser.class);
} }
public CloveSet getGarlicCloves(GarlicMessage message, PrivateKey encryptionKey) { /** @param skm use tags from this session key manager */
public CloveSet getGarlicCloves(GarlicMessage message, PrivateKey encryptionKey, SessionKeyManager skm) {
byte encData[] = message.getData(); byte encData[] = message.getData();
byte decrData[] = null; byte decrData[] = null;
try { try {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Decrypting with private key " + encryptionKey); _log.debug("Decrypting with private key " + encryptionKey);
decrData = _context.elGamalAESEngine().decrypt(encData, encryptionKey); decrData = _context.elGamalAESEngine().decrypt(encData, encryptionKey, skm);
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Error decrypting", dfe); _log.warn("Error decrypting", dfe);

View File

@ -8,6 +8,7 @@ package net.i2p.router.message;
* *
*/ */
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Hash; import net.i2p.data.Hash;
import net.i2p.data.PrivateKey; import net.i2p.data.PrivateKey;
@ -47,13 +48,16 @@ public class GarlicMessageReceiver {
_clientDestination = clientDestination; _clientDestination = clientDestination;
_parser = new GarlicMessageParser(context); _parser = new GarlicMessageParser(context);
_receiver = receiver; _receiver = receiver;
//_log.error("New GMR dest = " + clientDestination);
} }
public void receive(GarlicMessage message) { public void receive(GarlicMessage message) {
PrivateKey decryptionKey = null; PrivateKey decryptionKey = null;
SessionKeyManager skm = null;
if (_clientDestination != null) { if (_clientDestination != null) {
LeaseSetKeys keys = _context.keyManager().getKeys(_clientDestination); LeaseSetKeys keys = _context.keyManager().getKeys(_clientDestination);
if (keys != null) { skm = _context.clientManager().getClientSessionKeyManager(_clientDestination);
if (keys != null && skm != null) {
decryptionKey = keys.getDecryptionKey(); decryptionKey = keys.getDecryptionKey();
} else { } else {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
@ -62,9 +66,10 @@ public class GarlicMessageReceiver {
} }
} else { } else {
decryptionKey = _context.keyManager().getPrivateKey(); decryptionKey = _context.keyManager().getPrivateKey();
skm = _context.sessionKeyManager();
} }
CloveSet set = _parser.getGarlicCloves(message, decryptionKey); CloveSet set = _parser.getGarlicCloves(message, decryptionKey, skm);
if (set != null) { if (set != null) {
for (int i = 0; i < set.getCloveCount(); i++) { for (int i = 0; i < set.getCloveCount(); i++) {
GarlicClove clove = set.getClove(i); GarlicClove clove = set.getClove(i);

View File

@ -31,14 +31,18 @@ import net.i2p.util.Log;
public class HandleGarlicMessageJob extends JobImpl implements GarlicMessageReceiver.CloveReceiver { public class HandleGarlicMessageJob extends JobImpl implements GarlicMessageReceiver.CloveReceiver {
private Log _log; private Log _log;
private GarlicMessage _message; private GarlicMessage _message;
private RouterIdentity _from; //private RouterIdentity _from;
private Hash _fromHash; //private Hash _fromHash;
private Map _cloves; // map of clove Id --> Expiration of cloves we've already seen //private Map _cloves; // map of clove Id --> Expiration of cloves we've already seen
//private MessageHandler _handler; //private MessageHandler _handler;
private GarlicMessageParser _parser; //private GarlicMessageParser _parser;
private final static int FORWARD_PRIORITY = 50; private final static int FORWARD_PRIORITY = 50;
/**
* @param from ignored
* @param fromHash ignored
*/
public HandleGarlicMessageJob(RouterContext context, GarlicMessage msg, RouterIdentity from, Hash fromHash) { public HandleGarlicMessageJob(RouterContext context, GarlicMessage msg, RouterIdentity from, Hash fromHash) {
super(context); super(context);
_log = context.logManager().getLog(HandleGarlicMessageJob.class); _log = context.logManager().getLog(HandleGarlicMessageJob.class);
@ -46,11 +50,11 @@ public class HandleGarlicMessageJob extends JobImpl implements GarlicMessageRece
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("New handle garlicMessageJob called w/ message from [" + from + "]", new Exception("Debug")); _log.debug("New handle garlicMessageJob called w/ message from [" + from + "]", new Exception("Debug"));
_message = msg; _message = msg;
_from = from; //_from = from;
_fromHash = fromHash; //_fromHash = fromHash;
_cloves = new HashMap(); //_cloves = new HashMap();
//_handler = new MessageHandler(context); //_handler = new MessageHandler(context);
_parser = new GarlicMessageParser(context); //_parser = new GarlicMessageParser(context);
} }
public String getName() { return "Handle Inbound Garlic Message"; } public String getName() { return "Handle Inbound Garlic Message"; }

View File

@ -17,6 +17,7 @@ import net.i2p.data.LeaseSet;
import net.i2p.data.Payload; import net.i2p.data.Payload;
import net.i2p.data.PublicKey; import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey; import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.TunnelId; import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DataMessage; import net.i2p.data.i2np.DataMessage;
import net.i2p.data.i2np.DatabaseStoreMessage; import net.i2p.data.i2np.DatabaseStoreMessage;
@ -46,13 +47,15 @@ class OutboundClientMessageJobHelper {
* *
* For now, its just a tunneled DeliveryStatusMessage * For now, its just a tunneled DeliveryStatusMessage
* *
* Unused?
*
* @param bundledReplyLeaseSet if specified, the given LeaseSet will be packaged with the message (allowing * @param bundledReplyLeaseSet if specified, the given LeaseSet will be packaged with the message (allowing
* much faster replies, since their netDb search will return almost instantly) * much faster replies, since their netDb search will return almost instantly)
* @return garlic, or null if no tunnels were found (or other errors) * @return garlic, or null if no tunnels were found (or other errors)
*/ */
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK, static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
Payload data, Hash from, Destination dest, TunnelInfo replyTunnel, Payload data, Hash from, Destination dest, TunnelInfo replyTunnel,
SessionKey wrappedKey, Set wrappedTags, SessionKey wrappedKey, Set<SessionTag> wrappedTags,
boolean requireAck, LeaseSet bundledReplyLeaseSet) { boolean requireAck, LeaseSet bundledReplyLeaseSet) {
PayloadGarlicConfig dataClove = buildDataClove(ctx, data, dest, expiration); PayloadGarlicConfig dataClove = buildDataClove(ctx, data, dest, expiration);
return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, replyTunnel, wrappedKey, return createGarlicMessage(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, replyTunnel, wrappedKey,
@ -62,15 +65,18 @@ class OutboundClientMessageJobHelper {
* Allow the app to specify the data clove directly, which enables OutboundClientMessage to resend the * Allow the app to specify the data clove directly, which enables OutboundClientMessage to resend the
* same payload (including expiration and unique id) in different garlics (down different tunnels) * same payload (including expiration and unique id) in different garlics (down different tunnels)
* *
* This is called from OCMOSJ
*
* @return garlic, or null if no tunnels were found (or other errors) * @return garlic, or null if no tunnels were found (or other errors)
*/ */
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK, static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel, SessionKey wrappedKey, PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel, SessionKey wrappedKey,
Set wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet) { Set<SessionTag> wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet) {
GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, replyTunnel, requireAck, bundledReplyLeaseSet); GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, from, dest, replyTunnel, requireAck, bundledReplyLeaseSet);
if (config == null) if (config == null)
return null; return null;
GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, config, wrappedKey, wrappedTags); GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, config, wrappedKey, wrappedTags,
ctx.clientManager().getClientSessionKeyManager(from));
return msg; return msg;
} }

View File

@ -10,6 +10,8 @@ import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.TagSetHandle;
import net.i2p.data.Base64; import net.i2p.data.Base64;
import net.i2p.data.Certificate; import net.i2p.data.Certificate;
import net.i2p.data.Destination; import net.i2p.data.Destination;
@ -20,6 +22,7 @@ import net.i2p.data.Payload;
import net.i2p.data.PublicKey; import net.i2p.data.PublicKey;
import net.i2p.data.RouterInfo; import net.i2p.data.RouterInfo;
import net.i2p.data.SessionKey; import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.i2cp.MessageId; import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2np.DataMessage; import net.i2p.data.i2np.DataMessage;
import net.i2p.data.i2np.DeliveryInstructions; import net.i2p.data.i2np.DeliveryInstructions;
@ -471,7 +474,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
return; return;
} }
int existingTags = GarlicMessageBuilder.estimateAvailableTags(getContext(), _leaseSet.getEncryptionKey(), _from); int existingTags = GarlicMessageBuilder.estimateAvailableTags(getContext(), _leaseSet.getEncryptionKey(),
_from.calculateHash());
_outTunnel = selectOutboundTunnel(_to); _outTunnel = selectOutboundTunnel(_to);
// boolean wantACK = _wantACK || existingTags <= 30 || getContext().random().nextInt(100) < 5; // boolean wantACK = _wantACK || existingTags <= 30 || getContext().random().nextInt(100) < 5;
// what's the point of 5% random? possible improvements or replacements: // what's the point of 5% random? possible improvements or replacements:
@ -489,7 +493,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
PublicKey key = _leaseSet.getEncryptionKey(); PublicKey key = _leaseSet.getEncryptionKey();
SessionKey sessKey = new SessionKey(); SessionKey sessKey = new SessionKey();
Set tags = new HashSet(); Set<SessionTag> tags = new HashSet();
// If we want an ack, bundle a leaseSet... (so he can get back to us) // If we want an ack, bundle a leaseSet... (so he can get back to us)
LeaseSet replyLeaseSet = getReplyLeaseSet(wantACK); LeaseSet replyLeaseSet = getReplyLeaseSet(wantACK);
// ... and vice versa (so we know he got it) // ... and vice versa (so we know he got it)
@ -531,8 +535,16 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
SendTimeoutJob onFail = null; SendTimeoutJob onFail = null;
ReplySelector selector = null; ReplySelector selector = null;
if (wantACK) { if (wantACK) {
onReply = new SendSuccessJob(getContext(), sessKey, tags); TagSetHandle tsh = null;
onFail = new SendTimeoutJob(getContext()); if ( (sessKey != null) && (tags != null) && (tags.size() > 0) ) {
if (_leaseSet != null) {
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash());
if (skm != null)
tsh = skm.tagsDelivered(_leaseSet.getEncryptionKey(), sessKey, tags);
}
}
onReply = new SendSuccessJob(getContext(), sessKey, tsh);
onFail = new SendTimeoutJob(getContext(), sessKey, tsh);
selector = new ReplySelector(token); selector = new ReplySelector(token);
} }
@ -550,9 +562,9 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
+ _lease.getGateway().toBase64()); + _lease.getGateway().toBase64());
DispatchJob dispatchJob = new DispatchJob(getContext(), msg, selector, onReply, onFail, (int)(_overallExpiration-getContext().clock().now())); DispatchJob dispatchJob = new DispatchJob(getContext(), msg, selector, onReply, onFail, (int)(_overallExpiration-getContext().clock().now()));
if (false) // dispatch may take 100+ms, so toss it in its own job //if (false) // dispatch may take 100+ms, so toss it in its own job
getContext().jobQueue().addJob(dispatchJob); // getContext().jobQueue().addJob(dispatchJob);
else //else
dispatchJob.runJob(); dispatchJob.runJob();
} else { } else {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
@ -848,6 +860,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
/** build the payload clove that will be used for all of the messages, placing the clove in the status structure */ /** build the payload clove that will be used for all of the messages, placing the clove in the status structure */
private boolean buildClove() { private boolean buildClove() {
// FIXME set SKM
PayloadGarlicConfig clove = new PayloadGarlicConfig(); PayloadGarlicConfig clove = new PayloadGarlicConfig();
DeliveryInstructions instructions = new DeliveryInstructions(); DeliveryInstructions instructions = new DeliveryInstructions();
@ -932,14 +945,14 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
*/ */
private class SendSuccessJob extends JobImpl implements ReplyJob { private class SendSuccessJob extends JobImpl implements ReplyJob {
private SessionKey _key; private SessionKey _key;
private Set _tags; private TagSetHandle _tags;
/** /**
* Create a new success job that will be fired when the message encrypted with * Create a new success job that will be fired when the message encrypted with
* the given session key and bearing the specified tags are confirmed delivered. * the given session key and bearing the specified tags are confirmed delivered.
* *
*/ */
public SendSuccessJob(RouterContext enclosingContext, SessionKey key, Set tags) { public SendSuccessJob(RouterContext enclosingContext, SessionKey key, TagSetHandle tags) {
super(enclosingContext); super(enclosingContext);
_key = key; _key = key;
_tags = tags; _tags = tags;
@ -955,10 +968,10 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
+ ": SUCCESS! msg " + _clientMessageId + ": SUCCESS! msg " + _clientMessageId
+ " sent after " + sendTime + "ms"); + " sent after " + sendTime + "ms");
if ( (_key != null) && (_tags != null) && (_tags.size() > 0) ) { if (_key != null && _tags != null && _leaseSet != null) {
if (_leaseSet != null) SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash());
getContext().sessionKeyManager().tagsDelivered(_leaseSet.getEncryptionKey(), if (skm != null)
_key, _tags); skm.tagsAcked(_leaseSet.getEncryptionKey(), _key, _tags);
} }
long dataMsgId = _cloveId; long dataMsgId = _cloveId;
@ -994,8 +1007,13 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
* *
*/ */
private class SendTimeoutJob extends JobImpl { private class SendTimeoutJob extends JobImpl {
public SendTimeoutJob(RouterContext enclosingContext) { private SessionKey _key;
private TagSetHandle _tags;
public SendTimeoutJob(RouterContext enclosingContext, SessionKey key, TagSetHandle tags) {
super(enclosingContext); super(enclosingContext);
_key = key;
_tags = tags;
} }
public String getName() { return "Send client message timed out"; } public String getName() { return "Send client message timed out"; }
@ -1005,6 +1023,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
+ ": Soft timeout through the lease " + _lease); + ": Soft timeout through the lease " + _lease);
_lease.setNumFailure(_lease.getNumFailure()+1); _lease.setNumFailure(_lease.getNumFailure()+1);
if (_key != null && _tags != null && _leaseSet != null) {
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash());
if (skm != null)
skm.failTags(_leaseSet.getEncryptionKey(), _key, _tags);
}
dieFatal(); dieFatal();
} }
} }

View File

@ -0,0 +1,225 @@
package net.i2p.router.networkdb.kademlia;
import java.util.ArrayList;
import java.util.List;
import net.i2p.data.Hash;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.Job;
import net.i2p.router.JobImpl;
import net.i2p.router.MessageSelector;
import net.i2p.router.OutNetMessage;
import net.i2p.router.ReplyJob;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelInfo;
import net.i2p.util.Log;
/**
* Try sending a search to some floodfill peers, but if we don't get a successful
* match within half the allowed lookup time, give up and start querying through
* the normal (kademlia) channels. This should cut down on spurious lookups caused
* by simple delays in responses from floodfill peers
*
*/
public class FloodSearchJob extends JobImpl {
private Log _log;
private FloodfillNetworkDatabaseFacade _facade;
private Hash _key;
private final List _onFind;
private final List _onFailed;
private long _expiration;
private int _timeoutMs;
private long _origExpiration;
private boolean _isLease;
private volatile int _lookupsRemaining;
private volatile boolean _dead;
public FloodSearchJob(RouterContext ctx, FloodfillNetworkDatabaseFacade facade, Hash key, Job onFind, Job onFailed, int timeoutMs, boolean isLease) {
super(ctx);
_log = ctx.logManager().getLog(FloodSearchJob.class);
_facade = facade;
_key = key;
_onFind = new ArrayList();
_onFind.add(onFind);
_onFailed = new ArrayList();
_onFailed.add(onFailed);
int timeout = -1;
timeout = timeoutMs / FLOOD_SEARCH_TIME_FACTOR;
if (timeout < timeoutMs)
timeout = timeoutMs;
_timeoutMs = timeout;
_expiration = timeout + ctx.clock().now();
_origExpiration = timeoutMs + ctx.clock().now();
_isLease = isLease;
_lookupsRemaining = 0;
_dead = false;
}
void addDeferred(Job onFind, Job onFailed, long timeoutMs, boolean isLease) {
if (_dead) {
getContext().jobQueue().addJob(onFailed);
} else {
if (onFind != null) synchronized (_onFind) { _onFind.add(onFind); }
if (onFailed != null) synchronized (_onFailed) { _onFailed.add(onFailed); }
}
}
public long getExpiration() { return _expiration; }
private static final int CONCURRENT_SEARCHES = 2;
private static final int FLOOD_SEARCH_TIME_FACTOR = 2;
private static final int FLOOD_SEARCH_TIME_MIN = 30*1000;
public void runJob() {
// pick some floodfill peers and send out the searches
List floodfillPeers = _facade.getFloodfillPeers();
FloodLookupSelector replySelector = new FloodLookupSelector(getContext(), this);
ReplyJob onReply = new FloodLookupMatchJob(getContext(), this);
Job onTimeout = new FloodLookupTimeoutJob(getContext(), this);
OutNetMessage out = getContext().messageRegistry().registerPending(replySelector, onReply, onTimeout, _timeoutMs);
for (int i = 0; _lookupsRemaining < CONCURRENT_SEARCHES && i < floodfillPeers.size(); i++) {
Hash peer = (Hash)floodfillPeers.get(i);
if (peer.equals(getContext().routerHash()))
continue;
DatabaseLookupMessage dlm = new DatabaseLookupMessage(getContext(), true);
TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundTunnel();
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundTunnel();
if ( (replyTunnel == null) || (outTunnel == null) ) {
_dead = true;
List removed = null;
synchronized (_onFailed) {
removed = new ArrayList(_onFailed);
_onFailed.clear();
}
while (removed.size() > 0)
getContext().jobQueue().addJob((Job)removed.remove(0));
getContext().messageRegistry().unregisterPending(out);
return;
}
dlm.setFrom(replyTunnel.getPeer(0));
dlm.setMessageExpiration(getContext().clock().now()+10*1000);
dlm.setReplyTunnel(replyTunnel.getReceiveTunnelId(0));
dlm.setSearchKey(_key);
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Floodfill search for " + _key.toBase64() + " to " + peer.toBase64());
getContext().tunnelDispatcher().dispatchOutbound(dlm, outTunnel.getSendTunnelId(0), peer);
_lookupsRemaining++;
}
if (_lookupsRemaining <= 0) {
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Floodfill search for " + _key.toBase64() + " had no peers to send to");
// no floodfill peers, go to the normal ones
getContext().messageRegistry().unregisterPending(out);
_facade.searchFull(_key, _onFind, _onFailed, _timeoutMs*FLOOD_SEARCH_TIME_FACTOR, _isLease);
}
}
public String getName() { return "NetDb search (phase 1)"; }
Hash getKey() { return _key; }
void decrementRemaining() { _lookupsRemaining--; }
int getLookupsRemaining() { return _lookupsRemaining; }
void failed() {
if (_dead) return;
_dead = true;
int timeRemaining = (int)(_origExpiration - getContext().clock().now());
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Floodfill search for " + _key.toBase64() + " failed with " + timeRemaining);
if (timeRemaining > 0) {
_facade.searchFull(_key, _onFind, _onFailed, timeRemaining, _isLease);
} else {
List removed = null;
synchronized (_onFailed) {
removed = new ArrayList(_onFailed);
_onFailed.clear();
}
while (removed.size() > 0)
getContext().jobQueue().addJob((Job)removed.remove(0));
}
}
void success() {
if (_dead) return;
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Floodfill search for " + _key.toBase64() + " successful");
_dead = true;
_facade.complete(_key);
List removed = null;
synchronized (_onFind) {
removed = new ArrayList(_onFind);
_onFind.clear();
}
while (removed.size() > 0)
getContext().jobQueue().addJob((Job)removed.remove(0));
}
private static class FloodLookupTimeoutJob extends JobImpl {
private FloodSearchJob _search;
public FloodLookupTimeoutJob(RouterContext ctx, FloodSearchJob job) {
super(ctx);
_search = job;
}
public void runJob() {
_search.decrementRemaining();
if (_search.getLookupsRemaining() <= 0)
_search.failed();
}
public String getName() { return "NetDb search (phase 1) timeout"; }
}
private static class FloodLookupMatchJob extends JobImpl implements ReplyJob {
private Log _log;
private FloodSearchJob _search;
public FloodLookupMatchJob(RouterContext ctx, FloodSearchJob job) {
super(ctx);
_log = ctx.logManager().getLog(FloodLookupMatchJob.class);
_search = job;
}
public void runJob() {
if ( (getContext().netDb().lookupLeaseSetLocally(_search.getKey()) != null) ||
(getContext().netDb().lookupRouterInfoLocally(_search.getKey()) != null) ) {
_search.success();
} else {
int remaining = _search.getLookupsRemaining();
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + "/" + _search.getJobId() + ": got a reply looking for "
+ _search.getKey().toBase64() + ", with " + remaining + " outstanding searches");
// netDb reply pointing us at other people
if (remaining <= 0)
_search.failed();
}
}
public String getName() { return "NetDb search (phase 1) match"; }
public void setMessage(I2NPMessage message) {}
}
private static class FloodLookupSelector implements MessageSelector {
private RouterContext _context;
private FloodSearchJob _search;
public FloodLookupSelector(RouterContext ctx, FloodSearchJob search) {
_context = ctx;
_search = search;
}
public boolean continueMatching() { return _search.getLookupsRemaining() > 0; }
public long getExpiration() { return _search.getExpiration(); }
public boolean isMatch(I2NPMessage message) {
if (message == null) return false;
if (message instanceof DatabaseStoreMessage) {
DatabaseStoreMessage dsm = (DatabaseStoreMessage)message;
// is it worth making sure the reply came in on the right tunnel?
if (_search.getKey().equals(dsm.getKey())) {
_search.decrementRemaining();
return true;
}
} else if (message instanceof DatabaseSearchReplyMessage) {
DatabaseSearchReplyMessage dsrm = (DatabaseSearchReplyMessage)message;
if (_search.getKey().equals(dsrm.getSearchKey())) {
_search.decrementRemaining();
return true;
}
}
return false;
}
}
}

View File

@ -351,210 +351,3 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
} }
} }
} }
/**
* Try sending a search to some floodfill peers, but if we don't get a successful
* match within half the allowed lookup time, give up and start querying through
* the normal (kademlia) channels. This should cut down on spurious lookups caused
* by simple delays in responses from floodfill peers
*
*/
class FloodSearchJob extends JobImpl {
private Log _log;
private FloodfillNetworkDatabaseFacade _facade;
private Hash _key;
private final List _onFind;
private final List _onFailed;
private long _expiration;
private int _timeoutMs;
private long _origExpiration;
private boolean _isLease;
private volatile int _lookupsRemaining;
private volatile boolean _dead;
public FloodSearchJob(RouterContext ctx, FloodfillNetworkDatabaseFacade facade, Hash key, Job onFind, Job onFailed, int timeoutMs, boolean isLease) {
super(ctx);
_log = ctx.logManager().getLog(FloodSearchJob.class);
_facade = facade;
_key = key;
_onFind = new ArrayList();
_onFind.add(onFind);
_onFailed = new ArrayList();
_onFailed.add(onFailed);
int timeout = -1;
timeout = timeoutMs / FLOOD_SEARCH_TIME_FACTOR;
if (timeout < timeoutMs)
timeout = timeoutMs;
_timeoutMs = timeout;
_expiration = timeout + ctx.clock().now();
_origExpiration = timeoutMs + ctx.clock().now();
_isLease = isLease;
_lookupsRemaining = 0;
_dead = false;
}
void addDeferred(Job onFind, Job onFailed, long timeoutMs, boolean isLease) {
if (_dead) {
getContext().jobQueue().addJob(onFailed);
} else {
if (onFind != null) synchronized (_onFind) { _onFind.add(onFind); }
if (onFailed != null) synchronized (_onFailed) { _onFailed.add(onFailed); }
}
}
public long getExpiration() { return _expiration; }
private static final int CONCURRENT_SEARCHES = 2;
private static final int FLOOD_SEARCH_TIME_FACTOR = 2;
private static final int FLOOD_SEARCH_TIME_MIN = 30*1000;
public void runJob() {
// pick some floodfill peers and send out the searches
List floodfillPeers = _facade.getFloodfillPeers();
FloodLookupSelector replySelector = new FloodLookupSelector(getContext(), this);
ReplyJob onReply = new FloodLookupMatchJob(getContext(), this);
Job onTimeout = new FloodLookupTimeoutJob(getContext(), this);
OutNetMessage out = getContext().messageRegistry().registerPending(replySelector, onReply, onTimeout, _timeoutMs);
for (int i = 0; _lookupsRemaining < CONCURRENT_SEARCHES && i < floodfillPeers.size(); i++) {
Hash peer = (Hash)floodfillPeers.get(i);
if (peer.equals(getContext().routerHash()))
continue;
DatabaseLookupMessage dlm = new DatabaseLookupMessage(getContext(), true);
TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundTunnel();
TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundTunnel();
if ( (replyTunnel == null) || (outTunnel == null) ) {
_dead = true;
List removed = null;
synchronized (_onFailed) {
removed = new ArrayList(_onFailed);
_onFailed.clear();
}
while (removed.size() > 0)
getContext().jobQueue().addJob((Job)removed.remove(0));
getContext().messageRegistry().unregisterPending(out);
return;
}
dlm.setFrom(replyTunnel.getPeer(0));
dlm.setMessageExpiration(getContext().clock().now()+10*1000);
dlm.setReplyTunnel(replyTunnel.getReceiveTunnelId(0));
dlm.setSearchKey(_key);
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Floodfill search for " + _key.toBase64() + " to " + peer.toBase64());
getContext().tunnelDispatcher().dispatchOutbound(dlm, outTunnel.getSendTunnelId(0), peer);
_lookupsRemaining++;
}
if (_lookupsRemaining <= 0) {
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Floodfill search for " + _key.toBase64() + " had no peers to send to");
// no floodfill peers, go to the normal ones
getContext().messageRegistry().unregisterPending(out);
_facade.searchFull(_key, _onFind, _onFailed, _timeoutMs*FLOOD_SEARCH_TIME_FACTOR, _isLease);
}
}
public String getName() { return "NetDb search (phase 1)"; }
Hash getKey() { return _key; }
void decrementRemaining() { _lookupsRemaining--; }
int getLookupsRemaining() { return _lookupsRemaining; }
void failed() {
if (_dead) return;
_dead = true;
int timeRemaining = (int)(_origExpiration - getContext().clock().now());
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Floodfill search for " + _key.toBase64() + " failed with " + timeRemaining);
if (timeRemaining > 0) {
_facade.searchFull(_key, _onFind, _onFailed, timeRemaining, _isLease);
} else {
List removed = null;
synchronized (_onFailed) {
removed = new ArrayList(_onFailed);
_onFailed.clear();
}
while (removed.size() > 0)
getContext().jobQueue().addJob((Job)removed.remove(0));
}
}
void success() {
if (_dead) return;
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + ": Floodfill search for " + _key.toBase64() + " successful");
_dead = true;
_facade.complete(_key);
List removed = null;
synchronized (_onFind) {
removed = new ArrayList(_onFind);
_onFind.clear();
}
while (removed.size() > 0)
getContext().jobQueue().addJob((Job)removed.remove(0));
}
}
class FloodLookupTimeoutJob extends JobImpl {
private FloodSearchJob _search;
public FloodLookupTimeoutJob(RouterContext ctx, FloodSearchJob job) {
super(ctx);
_search = job;
}
public void runJob() {
_search.decrementRemaining();
if (_search.getLookupsRemaining() <= 0)
_search.failed();
}
public String getName() { return "NetDb search (phase 1) timeout"; }
}
class FloodLookupMatchJob extends JobImpl implements ReplyJob {
private Log _log;
private FloodSearchJob _search;
public FloodLookupMatchJob(RouterContext ctx, FloodSearchJob job) {
super(ctx);
_log = ctx.logManager().getLog(FloodLookupMatchJob.class);
_search = job;
}
public void runJob() {
if ( (getContext().netDb().lookupLeaseSetLocally(_search.getKey()) != null) ||
(getContext().netDb().lookupRouterInfoLocally(_search.getKey()) != null) ) {
_search.success();
} else {
int remaining = _search.getLookupsRemaining();
if (_log.shouldLog(Log.INFO))
_log.info(getJobId() + "/" + _search.getJobId() + ": got a reply looking for "
+ _search.getKey().toBase64() + ", with " + remaining + " outstanding searches");
// netDb reply pointing us at other people
if (remaining <= 0)
_search.failed();
}
}
public String getName() { return "NetDb search (phase 1) match"; }
public void setMessage(I2NPMessage message) {}
}
class FloodLookupSelector implements MessageSelector {
private RouterContext _context;
private FloodSearchJob _search;
public FloodLookupSelector(RouterContext ctx, FloodSearchJob search) {
_context = ctx;
_search = search;
}
public boolean continueMatching() { return _search.getLookupsRemaining() > 0; }
public long getExpiration() { return _search.getExpiration(); }
public boolean isMatch(I2NPMessage message) {
if (message == null) return false;
if (message instanceof DatabaseStoreMessage) {
DatabaseStoreMessage dsm = (DatabaseStoreMessage)message;
// is it worth making sure the reply came in on the right tunnel?
if (_search.getKey().equals(dsm.getKey())) {
_search.decrementRemaining();
return true;
}
} else if (message instanceof DatabaseSearchReplyMessage) {
DatabaseSearchReplyMessage dsrm = (DatabaseSearchReplyMessage)message;
if (_search.getKey().equals(dsrm.getSearchKey())) {
_search.decrementRemaining();
return true;
}
}
return false;
}
}

View File

@ -1003,7 +1003,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
StringBuilder buf = new StringBuilder(size); StringBuilder buf = new StringBuilder(size);
out.write("<h2>Network Database Contents (<a href=\"netdb.jsp?l=1\">View LeaseSets</a>)</h2>\n"); out.write("<h2>Network Database Contents (<a href=\"netdb.jsp?l=1\">View LeaseSets</a>)</h2>\n");
if (!_initialized) { if (!_initialized) {
buf.append("<i>Not initialized</i>\n"); buf.append("Not initialized\n");
out.write(buf.toString()); out.write(buf.toString());
out.flush(); out.flush();
return; return;
@ -1052,8 +1052,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
buf.append("<tr><th>Version</th><th>Count</th></tr>\n"); buf.append("<tr><th>Version</th><th>Count</th></tr>\n");
for (String routerVersion : versionList) { for (String routerVersion : versionList) {
int num = versions.count(routerVersion); int num = versions.count(routerVersion);
buf.append("<tr><td>").append(DataHelper.stripHTML(routerVersion)); buf.append("<tr><td align=\"center\">").append(DataHelper.stripHTML(routerVersion));
buf.append("</td><td align=\"right\">").append(num).append("</td></tr>\n"); buf.append("</td><td align=\"center\">").append(num).append("</td></tr>\n");
} }
buf.append("</table>\n"); buf.append("</table>\n");
} }
@ -1071,7 +1071,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
buf.append("<tr><td><img height=\"11\" width=\"16\" alt=\"").append(country.toUpperCase()).append("\""); buf.append("<tr><td><img height=\"11\" width=\"16\" alt=\"").append(country.toUpperCase()).append("\"");
buf.append(" src=\"/flags.jsp?c=").append(country).append("\"> "); buf.append(" src=\"/flags.jsp?c=").append(country).append("\"> ");
buf.append(_context.commSystem().getCountryName(country)); buf.append(_context.commSystem().getCountryName(country));
buf.append("</td><td align=\"right\">").append(num).append("</td></tr>\n"); buf.append("</td><td align=\"center\">").append(num).append("</td></tr>\n");
} }
buf.append("</table>\n"); buf.append("</table>\n");
} }
@ -1086,21 +1086,26 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
*/ */
private void renderRouterInfo(StringBuilder buf, RouterInfo info, boolean isUs, boolean full) { private void renderRouterInfo(StringBuilder buf, RouterInfo info, boolean isUs, boolean full) {
String hash = info.getIdentity().getHash().toBase64(); String hash = info.getIdentity().getHash().toBase64();
buf.append("<a name=\"").append(hash.substring(0, 6)).append("\" ></a>"); buf.append("<table><tr><th><a name=\"").append(hash.substring(0, 6)).append("\" ></a>");
if (isUs) { if (isUs) {
buf.append("<a name=\"our-info\" ></a><b>Our info: ").append(hash).append("</b><br>\n"); buf.append("<a name=\"our-info\" ></a><b>Our info: ").append(hash).append("</b></th></tr><tr><td>\n");
} else { } else {
buf.append("<b>Peer info for:</b> ").append(hash).append("<br>\n"); buf.append("<b>Peer info for:</b> ").append(hash).append("\n");
if (full) {
buf.append("[<a href=\"netdb.jsp\" >Back</a>]</th></tr><td>\n");
} else {
buf.append("[<a href=\"netdb.jsp?r=").append(hash.substring(0, 6)).append("\" >Full entry</a>]</th></tr><td>\n");
}
} }
long age = _context.clock().now() - info.getPublished(); long age = _context.clock().now() - info.getPublished();
if (isUs && _context.router().isHidden()) if (isUs && _context.router().isHidden())
buf.append("Hidden, Updated: <i>").append(DataHelper.formatDuration(age)).append(" ago</i><br>\n"); buf.append("<b>Hidden, Updated:</b> ").append(DataHelper.formatDuration(age)).append(" ago<br>\n");
else if (age > 0) else if (age > 0)
buf.append("Published: <i>").append(DataHelper.formatDuration(age)).append(" ago</i><br>\n"); buf.append("<b>Published:</b> ").append(DataHelper.formatDuration(age)).append(" ago<br>\n");
else else
buf.append("Published: <i>in ").append(DataHelper.formatDuration(0-age)).append("???</i><br>\n"); buf.append("<b>Published:</b> in ").append(DataHelper.formatDuration(0-age)).append("???<br>\n");
buf.append("Address(es): <i>"); buf.append("<b>Address(es):</b> ");
String country = _context.commSystem().getCountry(info.getIdentity().getHash()); String country = _context.commSystem().getCountry(info.getIdentity().getHash());
if(country != null) { if(country != null) {
buf.append("<img height=\"11\" width=\"16\" alt=\"").append(country.toUpperCase()).append("\""); buf.append("<img height=\"11\" width=\"16\" alt=\"").append(country.toUpperCase()).append("\"");
@ -1115,19 +1120,18 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
buf.append('[').append(DataHelper.stripHTML(name)).append('=').append(DataHelper.stripHTML(val)).append("] "); buf.append('[').append(DataHelper.stripHTML(name)).append('=').append(DataHelper.stripHTML(val)).append("] ");
} }
} }
buf.append("</i><br>\n"); buf.append("</td></tr>\n");
if (full) { if (full) {
buf.append("Stats: <br><i><code>\n"); buf.append("<tr><td>Stats: <br><code>\n");
for (Iterator iter = info.getOptions().keySet().iterator(); iter.hasNext(); ) { for (Iterator iter = info.getOptions().keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next(); String key = (String)iter.next();
String val = info.getOption(key); String val = info.getOption(key);
buf.append(DataHelper.stripHTML(key)).append(" = ").append(DataHelper.stripHTML(val)).append("<br>\n"); buf.append(DataHelper.stripHTML(key)).append(" = ").append(DataHelper.stripHTML(val)).append("<br>\n");
} }
buf.append("</code></i>\n"); buf.append("</code></td></tr>\n");
} else { } else {
buf.append("<a href=\"netdb.jsp?r=").append(hash.substring(0, 6)).append("\" >Full entry</a>\n");
} }
buf.append("<hr>\n"); buf.append("</td></tr>\n");
} }
} }

View File

@ -3,6 +3,7 @@ package net.i2p.router.tunnel.pool;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.Certificate; import net.i2p.data.Certificate;
import net.i2p.data.SessionKey; import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag; import net.i2p.data.SessionTag;
@ -28,6 +29,8 @@ class TestJob extends JobImpl {
private TunnelInfo _outTunnel; private TunnelInfo _outTunnel;
private TunnelInfo _replyTunnel; private TunnelInfo _replyTunnel;
private PooledTunnelCreatorConfig _otherTunnel; private PooledTunnelCreatorConfig _otherTunnel;
/** save this so we can tell the SKM to kill it if the test fails */
private SessionTag _encryptTag;
/** base to randomize the test delay on */ /** base to randomize the test delay on */
private static final int TEST_DELAY = 30*1000; private static final int TEST_DELAY = 30*1000;
@ -129,6 +132,7 @@ class TestJob extends JobImpl {
SessionKey encryptKey = getContext().keyGenerator().generateSessionKey(); SessionKey encryptKey = getContext().keyGenerator().generateSessionKey();
SessionTag encryptTag = new SessionTag(true); SessionTag encryptTag = new SessionTag(true);
_encryptTag = encryptTag;
SessionKey sentKey = new SessionKey(); SessionKey sentKey = new SessionKey();
Set sentTags = null; Set sentTags = null;
GarlicMessage msg = GarlicMessageBuilder.buildMessage(getContext(), payload, sentKey, sentTags, GarlicMessage msg = GarlicMessageBuilder.buildMessage(getContext(), payload, sentKey, sentTags,
@ -142,7 +146,14 @@ class TestJob extends JobImpl {
} }
Set encryptTags = new HashSet(1); Set encryptTags = new HashSet(1);
encryptTags.add(encryptTag); encryptTags.add(encryptTag);
getContext().sessionKeyManager().tagsReceived(encryptKey, encryptTags); // Register the single tag with the appropriate SKM
if (_cfg.isInbound() && !_pool.getSettings().isExploratory()) {
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_pool.getSettings().getDestination());
if (skm != null)
skm.tagsReceived(encryptKey, encryptTags);
} else {
getContext().sessionKeyManager().tagsReceived(encryptKey, encryptTags);
}
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending garlic test of " + _outTunnel + " / " + _replyTunnel); _log.debug("Sending garlic test of " + _outTunnel + " / " + _replyTunnel);
@ -307,8 +318,17 @@ class TestJob extends JobImpl {
public void runJob() { public void runJob() {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Timeout: found? " + _found, getAddedBy()); _log.warn("Timeout: found? " + _found, getAddedBy());
if (!_found) if (!_found) {
// don't clog up the SKM with old one-tag tagsets
if (_cfg.isInbound() && !_pool.getSettings().isExploratory()) {
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_pool.getSettings().getDestination());
if (skm != null)
skm.consumeTag(_encryptTag);
} else {
getContext().sessionKeyManager().consumeTag(_encryptTag);
}
testFailed(getContext().clock().now() - _started); testFailed(getContext().clock().now() - _started);
}
} }
@Override @Override

View File

@ -309,7 +309,8 @@ public class TunnelPoolManager implements TunnelManagerFacade {
void buildComplete(PooledTunnelCreatorConfig cfg) { void buildComplete(PooledTunnelCreatorConfig cfg) {
//buildComplete(); //buildComplete();
if (cfg.getLength() > 1 && if (cfg.getLength() > 1 &&
!_context.router().gracefulShutdownInProgress()) { (!_context.router().gracefulShutdownInProgress()) &&
!Boolean.valueOf(_context.getProperty("router.disableTunnelTesting")).booleanValue()) {
TunnelPool pool = cfg.getTunnelPool(); TunnelPool pool = cfg.getTunnelPool();
if (pool == null) { if (pool == null) {
// never seen this before, do we reallly need to bother // never seen this before, do we reallly need to bother