* SessionKeyManager:

- Don't use unacked tagsets after consecutive ack failures
      and revert to full ElGamal if necessary (ticket #574)
    - Synchronize creation of new sessions to prevent dups
    - Don't remove an unacked session until it's really out of tags
    - Failsafe removal of old unacked tagsets
    - Cleanups, final, comments, log tweaks, debug.jsp tweaks, synchronization tweaks
This commit is contained in:
zzz
2012-03-08 17:48:19 +00:00
parent 1e978ea435
commit 97f402be0b
5 changed files with 263 additions and 90 deletions

View File

@ -38,15 +38,28 @@ public class SessionKeyManager {
* Retrieve the session key currently associated with encryption to the target, * Retrieve the session key currently associated with encryption to the target,
* or null if a new session key should be generated. * or null if a new session key should be generated.
* *
* Warning - don't generate a new session if this returns null, it's racy, use getCurrentOrNewKey()
*/ */
public SessionKey getCurrentKey(PublicKey target) { public SessionKey getCurrentKey(PublicKey target) {
return null; return null;
} }
/**
* Retrieve the session key currently associated with encryption to the target.
* Generates a new session and session key if not previously exising.
*
* @return non-null
* @since 0.9
*/
public SessionKey getCurrentOrNewKey(PublicKey target) {
return null;
}
/** /**
* 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.
* *
* @deprecated racy
*/ */
public void createSession(PublicKey target, SessionKey key) { // nop public void createSession(PublicKey target, SessionKey key) { // nop
} }
@ -54,6 +67,7 @@ public class SessionKeyManager {
/** /**
* Generate a new session key and associate it with the specified target. * Generate a new session key and associate it with the specified target.
* *
* @deprecated racy
*/ */
public SessionKey createSession(PublicKey target) { public SessionKey createSession(PublicKey target) {
SessionKey key = KeyGenerator.getInstance().generateSessionKey(); SessionKey key = KeyGenerator.getInstance().generateSessionKey();

View File

@ -22,6 +22,7 @@ import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
@ -74,13 +75,16 @@ import net.i2p.util.SimpleTimer;
* *
*/ */
public class TransientSessionKeyManager extends SessionKeyManager { public class TransientSessionKeyManager extends SessionKeyManager {
private Log _log; private final Log _log;
/** Map allowing us to go from the targeted PublicKey to the OutboundSession used */ /** Map allowing us to go from the targeted PublicKey to the OutboundSession used */
private final Map<PublicKey, OutboundSession> _outboundSessions; private final Map<PublicKey, OutboundSession> _outboundSessions;
/** Map allowing us to go from a SessionTag to the containing TagSet */ /** Map allowing us to go from a SessionTag to the containing TagSet */
private final Map<SessionTag, TagSet> _inboundTagSets; private final Map<SessionTag, TagSet> _inboundTagSets;
protected I2PAppContext _context; protected final I2PAppContext _context;
private volatile boolean _alive; private volatile boolean _alive;
/** for debugging */
private final AtomicInteger _rcvTagSetID = new AtomicInteger();
private final AtomicInteger _sentTagSetID = new AtomicInteger();
/** /**
* Let session tags sit around for 10 minutes before expiring them. We can now have such a large * Let session tags sit around for 10 minutes before expiring them. We can now have such a large
@ -119,7 +123,6 @@ public class TransientSessionKeyManager extends SessionKeyManager {
_alive = true; _alive = true;
SimpleScheduler.getInstance().addEvent(new CleanupEvent(), 60*1000); SimpleScheduler.getInstance().addEvent(new CleanupEvent(), 60*1000);
} }
private TransientSessionKeyManager() { this(null); }
@Override @Override
public void shutdown() { public void shutdown() {
@ -192,6 +195,7 @@ public class TransientSessionKeyManager extends SessionKeyManager {
* Retrieve the session key currently associated with encryption to the target, * Retrieve the session key currently associated with encryption to the target,
* or null if a new session key should be generated. * or null if a new session key should be generated.
* *
* Warning - don't generate a new session if this returns null, it's racy, use getCurrentOrNewKey()
*/ */
@Override @Override
public SessionKey getCurrentKey(PublicKey target) { public SessionKey getCurrentKey(PublicKey target) {
@ -204,16 +208,42 @@ public class TransientSessionKeyManager extends SessionKeyManager {
+ new Date(sess.getEstablishedDate()) + new Date(sess.getEstablishedDate())
+ " but not used for " + " but not used for "
+ (now-sess.getLastUsedDate()) + (now-sess.getLastUsedDate())
+ "ms with target " + target); + "ms with target " + toString(target));
return null; return null;
} }
return sess.getCurrentKey(); return sess.getCurrentKey();
} }
/**
* Retrieve the session key currently associated with encryption to the target.
* Generates a new session and session key if not previously exising.
*
* @return non-null
* @since 0.9
*/
@Override
public SessionKey getCurrentOrNewKey(PublicKey target) {
synchronized (_outboundSessions) {
OutboundSession sess = _outboundSessions.get(target);
if (sess != null) {
long now = _context.clock().now();
if (sess.getLastUsedDate() < now - SESSION_LIFETIME_MAX_MS)
sess = null;
}
if (sess == null) {
SessionKey key = _context.keyGenerator().generateSessionKey();
sess = createAndReturnSession(target, key);
return key;
}
return sess.getCurrentKey();
}
}
/** /**
* 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.
* *
* @deprecated racy
*/ */
@Override @Override
public void createSession(PublicKey target, SessionKey key) { public void createSession(PublicKey target, SessionKey key) {
@ -226,7 +256,9 @@ public class TransientSessionKeyManager extends SessionKeyManager {
* *
*/ */
private OutboundSession createAndReturnSession(PublicKey target, SessionKey key) { private OutboundSession createAndReturnSession(PublicKey target, SessionKey key) {
OutboundSession sess = new OutboundSession(target); if (_log.shouldLog(Log.INFO))
_log.info("New OB session, sesskey: " + key + " target: " + toString(target));
OutboundSession sess = new OutboundSession(_context, _log, target);
sess.setCurrentKey(key); sess.setCurrentKey(key);
addSession(sess); addSession(sess);
return sess; return sess;
@ -243,18 +275,19 @@ public class TransientSessionKeyManager extends SessionKeyManager {
public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key) { public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key) {
OutboundSession sess = getSession(target); OutboundSession sess = getSession(target);
if (sess == null) { if (sess == null) {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.WARN))
_log.debug("No session for " + target); _log.warn("No session for " + toString(target));
return null; return null;
} }
if (sess.getCurrentKey().equals(key)) { if (sess.getCurrentKey().equals(key)) {
SessionTag nxt = sess.consumeNext(); SessionTag nxt = sess.consumeNext();
if (_log.shouldLog(Log.DEBUG)) // logged in OutboundSession
_log.debug("OB Tag consumed: " + nxt + " with: " + key); //if (nxt != null && _log.shouldLog(Log.DEBUG))
// _log.debug("OB Tag consumed: " + nxt + " with: " + key);
return nxt; return nxt;
} }
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.WARN))
_log.debug("Key does not match existing key, no tag"); _log.warn("Key does not match existing key, no tag");
return null; return null;
} }
@ -305,18 +338,20 @@ public class TransientSessionKeyManager extends SessionKeyManager {
*/ */
@Override @Override
public TagSetHandle tagsDelivered(PublicKey target, SessionKey key, Set<SessionTag> sessionTags) { public TagSetHandle tagsDelivered(PublicKey target, SessionKey key, Set<SessionTag> sessionTags) {
if (_log.shouldLog(Log.DEBUG)) { // if this is ever null, this is racy and needs synch
//_log.debug("Tags delivered to set " + set + " on session " + sess);
if (!sessionTags.isEmpty())
_log.debug("Tags delivered: " + sessionTags.size() + " for key: " + key + ": " + sessionTags);
}
OutboundSession sess = getSession(target); OutboundSession sess = getSession(target);
if (sess == null) if (sess == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("No session for delivered TagSet to target: " + toString(target));
sess = createAndReturnSession(target, key); sess = createAndReturnSession(target, key);
else } else {
sess.setCurrentKey(key); sess.setCurrentKey(key);
TagSet set = new TagSet(sessionTags, key, _context.clock().now()); }
TagSet set = new TagSet(sessionTags, key, _context.clock().now(), _sentTagSetID.incrementAndGet());
sess.addTags(set); sess.addTags(set);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Tags delivered: " + set +
" target: " + toString(target) /** + ": " + sessionTags */ );
return set; return set;
} }
@ -339,13 +374,19 @@ public class TransientSessionKeyManager extends SessionKeyManager {
@Override @Override
public void failTags(PublicKey target, SessionKey key, TagSetHandle ts) { public void failTags(PublicKey target, SessionKey key, TagSetHandle ts) {
OutboundSession sess = getSession(target); OutboundSession sess = getSession(target);
if (sess == null) if (sess == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("No session for failed TagSet: " + ts);
return; return;
if(!key.equals(sess.getCurrentKey())) }
if(!key.equals(sess.getCurrentKey())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Wrong session key (wanted " + sess.getCurrentKey() + ") for failed TagSet: " + ts);
return; return;
}
if (_log.shouldLog(Log.WARN))
_log.warn("TagSet failed: " + ts);
sess.failTags((TagSet)ts); sess.failTags((TagSet)ts);
if (_log.shouldLog(Log.DEBUG))
_log.debug("TagSet failed: " + ts);
} }
/** /**
@ -354,13 +395,19 @@ public class TransientSessionKeyManager extends SessionKeyManager {
@Override @Override
public void tagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) { public void tagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) {
OutboundSession sess = getSession(target); OutboundSession sess = getSession(target);
if (sess == null) if (sess == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("No session for acked TagSet: " + ts);
return; return;
if(!key.equals(sess.getCurrentKey())) }
if(!key.equals(sess.getCurrentKey())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Wrong session key (wanted " + sess.getCurrentKey() + ") for acked TagSet: " + ts);
return; return;
sess.ackTags((TagSet)ts); }
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("TagSet acked: " + ts); _log.debug("TagSet acked: " + ts);
sess.ackTags((TagSet)ts);
} }
/** /**
@ -370,13 +417,15 @@ public class TransientSessionKeyManager extends SessionKeyManager {
@Override @Override
public void tagsReceived(SessionKey key, Set<SessionTag> sessionTags) { public void tagsReceived(SessionKey key, Set<SessionTag> sessionTags) {
int overage = 0; int overage = 0;
TagSet tagSet = new TagSet(sessionTags, key, _context.clock().now()); TagSet tagSet = new TagSet(sessionTags, key, _context.clock().now(), _rcvTagSetID.incrementAndGet());
if (_log.shouldLog(Log.INFO))
_log.info("Received " + tagSet);
TagSet old = null; TagSet old = null;
SessionTag dupTag = null; SessionTag dupTag = null;
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 + ": tagSet: " + tagSet); _log.debug("Receiving tag " + tag + " in tagSet: " + tagSet);
synchronized (_inboundTagSets) { synchronized (_inboundTagSets) {
old = _inboundTagSets.put(tag, tagSet); old = _inboundTagSets.put(tag, tagSet);
overage = _inboundTagSets.size() - MAX_INBOUND_SESSION_TAGS; overage = _inboundTagSets.size() - MAX_INBOUND_SESSION_TAGS;
@ -415,8 +464,8 @@ public class TransientSessionKeyManager extends SessionKeyManager {
if (overage > 0) if (overage > 0)
clearExcess(overage); clearExcess(overage);
if ( (sessionTags.isEmpty()) && (_log.shouldLog(Log.DEBUG)) ) //if ( (sessionTags.isEmpty()) && (_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();
} }
@ -494,7 +543,7 @@ public class TransientSessionKeyManager extends SessionKeyManager {
SessionKey key = tagSet.getAssociatedKey(); SessionKey key = tagSet.getAssociatedKey();
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Consuming IB " + tag + " for " + key + " on: " + tagSet); _log.debug("IB Tag consumed: " + tag + " from: " + tagSet);
return key; return key;
} }
} }
@ -607,7 +656,8 @@ public class TransientSessionKeyManager extends SessionKeyManager {
TagSet ts = siter.next(); TagSet ts = siter.next();
int size = ts.getTags().size(); int size = ts.getTags().size();
total += size; total += size;
buf.append("<li><b>Received:</b> ").append(DataHelper.formatDuration(now - ts.getDate())).append(" ago with "); buf.append("<li><b>ID: ").append(ts.getID())
.append(" Received:</b> ").append(DataHelper.formatDuration2(now - ts.getDate())).append(" ago with ");
buf.append(size).append(" tags remaining</li>"); buf.append(size).append(" tags remaining</li>");
} }
buf.append("</ul></td></tr>\n"); buf.append("</ul></td></tr>\n");
@ -615,7 +665,7 @@ public class TransientSessionKeyManager extends SessionKeyManager {
buf.setLength(0); buf.setLength(0);
} }
buf.append("<tr><th colspan=\"2\">Total tags: ").append(total).append(" ("); buf.append("<tr><th colspan=\"2\">Total tags: ").append(total).append(" (");
buf.append(DataHelper.formatSize(32*total)).append("B)</th></tr>\n" + buf.append(DataHelper.formatSize2(32*total)).append("B)</th></tr>\n" +
"</table>" + "</table>" +
"<h2><b>Outbound sessions</b></h2>" + "<h2><b>Outbound sessions</b></h2>" +
"<table>"); "<table>");
@ -625,9 +675,10 @@ public class TransientSessionKeyManager extends SessionKeyManager {
OutboundSession sess = iter.next(); OutboundSession sess = iter.next();
Set<TagSet> sets = new TreeSet(new TagSetComparator()); Set<TagSet> sets = new TreeSet(new TagSetComparator());
sets.addAll(sess.getTagSets()); sets.addAll(sess.getTagSets());
buf.append("<tr><td><b>Target public key:</b> ").append(sess.getTarget().toBase64().substring(0, 20)).append("...<br>" + buf.append("<tr><td><b>Target public key:</b> ").append(toString(sess.getTarget())).append("<br>" +
"<b>Established:</b> ").append(DataHelper.formatDuration(now - sess.getEstablishedDate())).append(" ago<br>" + "<b>Established:</b> ").append(DataHelper.formatDuration2(now - sess.getEstablishedDate())).append(" ago<br>" +
"<b>Last Used:</b> ").append(DataHelper.formatDuration(now - sess.getLastUsedDate())).append(" ago<br>" + "<b>Ack Received?</b> ").append(sess.getAckReceived()).append("<br>" +
"<b>Last Used:</b> ").append(DataHelper.formatDuration2(now - sess.getLastUsedDate())).append(" ago<br>" +
"<b>Session key:</b> ").append(sess.getCurrentKey().toBase64()).append("</td>" + "<b>Session key:</b> ").append(sess.getCurrentKey().toBase64()).append("</td>" +
"<td><b># Sets:</b> ").append(sess.getTagSets().size()).append("</td></tr>" + "<td><b># Sets:</b> ").append(sess.getTagSets().size()).append("</td></tr>" +
"<tr><td colspan=\"2\"><ul>"); "<tr><td colspan=\"2\"><ul>");
@ -635,7 +686,8 @@ public class TransientSessionKeyManager extends SessionKeyManager {
TagSet ts = siter.next(); TagSet ts = siter.next();
int size = ts.getTags().size(); int size = ts.getTags().size();
total += size; total += size;
buf.append("<li><b>Sent:</b> ").append(DataHelper.formatDuration(now - ts.getDate())).append(" ago with "); buf.append("<li><b>ID: ").append(ts.getID())
.append(" Sent:</b> ").append(DataHelper.formatDuration2(now - ts.getDate())).append(" ago with ");
buf.append(size).append(" tags remaining; acked? ").append(ts.getAcked()).append("</li>"); buf.append(size).append(" tags remaining; acked? ").append(ts.getAcked()).append("</li>");
} }
buf.append("</ul></td></tr>\n"); buf.append("</ul></td></tr>\n");
@ -643,12 +695,22 @@ public class TransientSessionKeyManager extends SessionKeyManager {
buf.setLength(0); buf.setLength(0);
} }
buf.append("<tr><th colspan=\"2\">Total tags: ").append(total).append(" ("); buf.append("<tr><th colspan=\"2\">Total tags: ").append(total).append(" (");
buf.append(DataHelper.formatSize(32*total)).append("B)</th></tr>\n" + buf.append(DataHelper.formatSize2(32*total)).append("B)</th></tr>\n" +
"</table>"); "</table>");
out.write(buf.toString()); out.write(buf.toString());
} }
/**
* For debugging
* @since 0.9
*/
private static String toString(PublicKey target) {
if (target == null)
return "null";
return target.toBase64().substring(0, 20) + "...";
}
/** /**
* Just for the HTML method above so we can see what's going on easier * Just for the HTML method above so we can see what's going on easier
* Earliest first * Earliest first
@ -659,28 +721,51 @@ public class TransientSessionKeyManager extends SessionKeyManager {
} }
} }
/** fixme pass in context and change to static */ /**
private class OutboundSession { * The state for a crypto session to a single public key
private PublicKey _target; */
private static class OutboundSession {
private final I2PAppContext _context;
private final Log _log;
private final PublicKey _target;
private SessionKey _currentKey; private SessionKey _currentKey;
private long _established; private final 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. */ * Before the first ack, all tagsets go here. These are never expired, we rely
private /* FIXME final FIXME */ List<TagSet> _unackedTagSets; * on the callers to call failTags() or ackTags() to remove them from this list.
* Actually we now do a failsafe expire.
* Synch on _tagSets to access this.
*/
private final List<TagSet> _unackedTagSets;
/** /**
* As tagsets are acked, they go here. * As tagsets are acked, they go here.
* After the first ack, new tagsets go here (i.e. presumed acked) * After the first ack, new tagsets go here (i.e. presumed acked)
*/ */
private /* FIXME final FIXME */ List<TagSet> _tagSets; private final List<TagSet> _tagSets;
/** set to true after first tagset is acked */ /**
private boolean _acked; * Set to true after first tagset is acked.
* Upon repeated failures, we may revert back to false.
* This prevents us getting "stuck" forever, using tags that weren't acked
* to deliver the next set of tags.
*/
private volatile boolean _acked;
/**
* Fail count
* Synch on _tagSets to access this.
*/
private int _consecutiveFailures;
public OutboundSession(PublicKey target) { private static final int MAX_FAILS = 2;
this(target, null, _context.clock().now(), _context.clock().now(), new ArrayList());
public OutboundSession(I2PAppContext ctx, Log log, PublicKey target) {
this(ctx, log, target, null, ctx.clock().now(), ctx.clock().now(), new ArrayList());
} }
OutboundSession(PublicKey target, SessionKey curKey, long established, long lastUsed, List<TagSet> tagSets) { OutboundSession(I2PAppContext ctx, Log log, PublicKey target, SessionKey curKey,
long established, long lastUsed, List<TagSet> tagSets) {
_context = ctx;
_log = log;
_target = target; _target = target;
_currentKey = curKey; _currentKey = curKey;
_established = established; _established = established;
@ -711,9 +796,17 @@ public class TransientSessionKeyManager extends SessionKeyManager {
void ackTags(TagSet set) { void ackTags(TagSet set) {
synchronized (_tagSets) { synchronized (_tagSets) {
if (_unackedTagSets.remove(set)) { if (_unackedTagSets.remove(set)) {
// we could perhaps use it even if not previuosly in unacked,
// i.e. it was expired already, but _tagSets is a list not a set...
_tagSets.add(set); _tagSets.add(set);
_acked = true; } else if (_log.shouldLog(Log.WARN)) {
if(!_tagSets.contains(set))
_log.warn("Ack of unknown tagset: " + set);
else if (set.getAcked())
_log.warn("Dup ack of tagset: " + set);
} }
_acked = true;
_consecutiveFailures = 0;
} }
set.setAcked(); set.setAcked();
} }
@ -722,7 +815,29 @@ public class TransientSessionKeyManager extends SessionKeyManager {
void failTags(TagSet set) { void failTags(TagSet set) {
synchronized (_tagSets) { synchronized (_tagSets) {
_unackedTagSets.remove(set); _unackedTagSets.remove(set);
_tagSets.remove(set); if (_tagSets.remove(set)) {
if (++_consecutiveFailures >= MAX_FAILS) {
// revert back to non-speculative ack mode,
// and force full ElG next time by reclassifying all tagsets that weren't really acked
_acked = false;
int acked = 0;
int unacked = 0;
for (Iterator<TagSet> iter = _tagSets.iterator(); iter.hasNext(); ) {
TagSet ts = iter.next();
if (!ts.getAcked()) {
iter.remove();
_unackedTagSets.add(ts);
unacked++;
} else {
acked++;
}
}
if (_log.shouldLog(Log.WARN))
_log.warn(_consecutiveFailures + " consecutive failed tagset deliveries to " + _currentKey
+ ": reverting to full ElG and un-acking " + unacked + " unacked tag sets, with "
+ acked + " remaining acked tag sets");
}
}
} }
} }
@ -738,16 +853,18 @@ public class TransientSessionKeyManager extends SessionKeyManager {
_lastUsed = _context.clock().now(); _lastUsed = _context.clock().now();
if (_currentKey != null) { if (_currentKey != null) {
if (!_currentKey.equals(key)) { if (!_currentKey.equals(key)) {
int dropped = 0; synchronized (_tagSets) {
List<TagSet> sets = _tagSets; if (_log.shouldLog(Log.WARN)) {
_tagSets = new ArrayList(); int dropped = 0;
for (int i = 0; i < sets.size(); i++) { for (TagSet set : _tagSets) {
TagSet set = (TagSet) sets.get(i); dropped += set.getTags().size();
dropped += set.getTags().size(); }
_log.warn("Rekeyed from " + _currentKey + " to " + key
+ ": dropping " + dropped + " session tags", new Exception());
}
_acked = false;
_tagSets.clear();
} }
if (_log.shouldLog(Log.INFO))
_log.info("Rekeyed from " + _currentKey + " to " + key
+ ": dropping " + dropped + " session tags");
} }
} }
_currentKey = key; _currentKey = key;
@ -769,14 +886,23 @@ public class TransientSessionKeyManager extends SessionKeyManager {
long now = _context.clock().now(); long now = _context.clock().now();
int removed = 0; int removed = 0;
synchronized (_tagSets) { synchronized (_tagSets) {
for (int i = 0; i < _tagSets.size(); i++) { for (Iterator<TagSet> iter = _tagSets.iterator(); iter.hasNext(); ) {
TagSet set = _tagSets.get(i); TagSet set = iter.next();
if (set.getDate() + SESSION_TAG_DURATION_MS <= now) { if (set.getDate() + SESSION_TAG_DURATION_MS <= now) {
_tagSets.remove(i); iter.remove();
i--;
removed++; removed++;
} }
} }
// failsafe, sometimes these are sticking around, not sure why, so clean them periodically
if ((now & 0x0f) == 0) {
for (Iterator<TagSet> iter = _unackedTagSets.iterator(); iter.hasNext(); ) {
TagSet set = iter.next();
if (set.getDate() + SESSION_TAG_DURATION_MS <= now) {
iter.remove();
removed++;
}
}
}
} }
return removed; return removed;
} }
@ -789,10 +915,16 @@ public class TransientSessionKeyManager extends SessionKeyManager {
TagSet set = _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) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("OB Tag consumed: " + tag + " from: " + set);
return tag;
} else if (_log.shouldLog(Log.INFO)) {
_log.info("Removing empty " + set);
}
} else { } else {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info("TagSet from " + new Date(set.getDate()) + " expired"); _log.info("Expired " + set);
} }
_tagSets.remove(0); _tagSets.remove(0);
} }
@ -812,7 +944,8 @@ public class TransientSessionKeyManager extends SessionKeyManager {
// so tags are sent when the acked tags are below // so tags are sent when the acked tags are below
// 30, 17, and 4. // 30, 17, and 4.
if (!set.getAcked()) if (!set.getAcked())
sz /= 3; // round up so we don't report 0 when we have 1 or 2 remaining and get the session removed
sz = (sz + 2) / 3;
tags += sz; tags += sz;
} }
} }
@ -846,32 +979,36 @@ public class TransientSessionKeyManager extends SessionKeyManager {
*/ */
public void addTags(TagSet set) { public void addTags(TagSet set) {
_lastUsed = _context.clock().now(); _lastUsed = _context.clock().now();
if (_acked) { synchronized (_tagSets) {
synchronized (_tagSets) { if (_acked)
_tagSets.add(set); _tagSets.add(set);
} else
} else {
synchronized (_unackedTagSets) {
_unackedTagSets.add(set); _unackedTagSets.add(set);
}
} }
} }
/** @since 0.9 for debugging */
public boolean getAckReceived() {
return _acked;
}
} }
private static class TagSet implements TagSetHandle { private static class TagSet implements TagSetHandle {
private Set<SessionTag> _sessionTags; private final Set<SessionTag> _sessionTags;
private SessionKey _key; private final SessionKey _key;
private long _date; private final long _date;
private final int _id;
//private Exception _createdBy; //private Exception _createdBy;
/** did we get an ack for this tagset? */ /** did we get an ack for this tagset? Only for outbound tagsets */
private boolean _acked; private boolean _acked;
public TagSet(Set<SessionTag> tags, SessionKey key, long date) { public TagSet(Set<SessionTag> tags, SessionKey key, long date, int id) {
if (key == null) throw new IllegalArgumentException("Missing key"); if (key == null) throw new IllegalArgumentException("Missing key");
if (tags == null) throw new IllegalArgumentException("Missing tags"); if (tags == null) throw new IllegalArgumentException("Missing tags");
_sessionTags = tags; _sessionTags = tags;
_key = key; _key = key;
_date = date; _date = date;
_id = id;
//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 "
@ -885,9 +1022,9 @@ public class TransientSessionKeyManager extends SessionKeyManager {
return _date; return _date;
} }
void setDate(long when) { //void setDate(long when) {
_date = when; // _date = when;
} //}
/** tags still available */ /** tags still available */
public Set<SessionTag> getTags() { public Set<SessionTag> getTags() {
@ -898,15 +1035,24 @@ public class TransientSessionKeyManager extends SessionKeyManager {
return _key; return _key;
} }
/**
* Caller must synch.
*/
public boolean contains(SessionTag tag) { public boolean contains(SessionTag tag) {
return _sessionTags.contains(tag); return _sessionTags.contains(tag);
} }
/**
* Caller must synch.
*/
public void consume(SessionTag tag) { public void consume(SessionTag tag) {
_sessionTags.remove(tag); _sessionTags.remove(tag);
} }
/** let's do this without counting the elements first */ /**
* Let's do this without counting the elements first.
* Caller must synch.
*/
public SessionTag consumeNext() { public SessionTag consumeNext() {
SessionTag first; SessionTag first;
try { try {
@ -943,11 +1089,16 @@ public class TransientSessionKeyManager extends SessionKeyManager {
} }
******/ ******/
/** @since 0.9 for debugging */
public int getID() {
return _id;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder buf = new StringBuilder(256); StringBuilder buf = new StringBuilder(256);
buf.append("TagSet established: ").append(new Date(_date)); buf.append("TagSet #").append(_id).append(" created: ").append(new Date(_date));
buf.append(" Session key: ").append(_key.toBase64()); buf.append(" Session key: ").append(_key);
buf.append(" Size: ").append(_sessionTags.size()); buf.append(" Size: ").append(_sessionTags.size());
buf.append(" Acked? ").append(_acked); buf.append(" Acked? ").append(_acked);
return buf.toString(); return buf.toString();

View File

@ -1,3 +1,13 @@
2012-03-09 zzz
* GarlicConfig: Remove unused reply block methods
* SessionKeyManager:
- Don't use unacked tagsets after consecutive ack failures
and revert to full ElGamal if necessary (ticket #574)
- Synchronize creation of new sessions to prevent dups
- Don't remove an unacked session until it's really out of tags
- Failsafe removal of old unacked tagsets
- Cleanups, final, comments, log tweaks, debug.jsp tweaks, synchronization tweaks
2012-03-06 kytv 2012-03-06 kytv
* German and Spanish translation updates from Transifex * German and Spanish translation updates from Transifex

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 = 6; public final static long BUILD = 7;
/** for example "-test" */ /** for example "-test" */
public final static String EXTRA = ""; public final static String EXTRA = "";

View File

@ -165,10 +165,8 @@ 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 = skm.getCurrentKey(key); SessionKey curKey = skm.getCurrentOrNewKey(key);
SessionTag curTag = null; SessionTag curTag = null;
if (curKey == null)
curKey = skm.createSession(key);
if (!forceElGamal) { if (!forceElGamal) {
curTag = skm.consumeNextAvailableTag(key, curKey); curTag = skm.consumeNextAvailableTag(key, curKey);