* 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
This commit is contained in:
zzz
2009-08-30 16:27:03 +00:00
parent 15f0cda41f
commit e0f1047d72
8 changed files with 346 additions and 100 deletions

View File

@ -17,7 +17,7 @@ import java.util.Set;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
@ -59,14 +59,16 @@ public class GarlicMessageBuilder {
*
* So a value somewhat higher than the low threshold
* 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 LOW_THRESHOLD = 20;
private static final int LOW_THRESHOLD = 30;
public static int estimateAvailableTags(RouterContext ctx, PublicKey key, Destination local) {
// per-dest Unimplemented
//SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local);
SessionKeyManager skm = ctx.sessionKeyManager();
/** @param local non-null; do not use this method for the router's SessionKeyManager */
public static int estimateAvailableTags(RouterContext ctx, PublicKey key, Hash local) {
SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local);
if (skm == null)
return 0;
SessionKey curKey = skm.getCurrentKey(key);
@ -75,19 +77,54 @@ public class GarlicMessageBuilder {
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);
}
***/
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);
PublicKey key = config.getRecipientPublicKey();
if (key == null) {
@ -104,14 +141,14 @@ public class GarlicMessageBuilder {
if (log.shouldLog(Log.INFO))
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;
if (curKey == null)
curKey = ctx.sessionKeyManager().createSession(key);
curKey = skm.createSession(key);
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))
log.debug("Available tags for encryption to " + key + ": " + availTags);
@ -120,7 +157,7 @@ public class GarlicMessageBuilder {
wrappedTags.add(new SessionTag(true));
if (log.shouldLog(Log.INFO))
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
for (int i = 0; i < numTagsToDeliver; i++)
wrappedTags.add(new SessionTag(true));
@ -138,16 +175,19 @@ public class GarlicMessageBuilder {
}
/**
* used by TestJob and directly above
*
* @param ctx scope
* @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 target public key of the location being garlic routed to (may be null if we
* know the encryptKey and encryptTag)
* @param encryptKey sessionKey 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);
if (config == null)
throw new IllegalArgumentException("Null config specified");
@ -209,6 +249,7 @@ public class GarlicMessageBuilder {
cloves[i] = buildClove(ctx, (PayloadGarlicConfig)c);
} else {
log.debug("Subclove IS NOT a payload garlic clove");
// See notes below
cloves[i] = buildClove(ctx, c);
}
if (cloves[i] == null)
@ -242,6 +283,22 @@ public class GarlicMessageBuilder {
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 {
GarlicClove clove = new GarlicClove(ctx);
GarlicMessage msg = buildMessage(ctx, config);

View File

@ -10,6 +10,7 @@ package net.i2p.router.message;
import java.util.Date;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.Certificate;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
@ -32,13 +33,14 @@ public class GarlicMessageParser {
_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 decrData[] = null;
try {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Decrypting with private key " + encryptionKey);
decrData = _context.elGamalAESEngine().decrypt(encData, encryptionKey);
decrData = _context.elGamalAESEngine().decrypt(encData, encryptionKey, skm);
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN))
_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.Hash;
import net.i2p.data.PrivateKey;
@ -47,13 +48,16 @@ public class GarlicMessageReceiver {
_clientDestination = clientDestination;
_parser = new GarlicMessageParser(context);
_receiver = receiver;
//_log.error("New GMR dest = " + clientDestination);
}
public void receive(GarlicMessage message) {
PrivateKey decryptionKey = null;
SessionKeyManager skm = null;
if (_clientDestination != null) {
LeaseSetKeys keys = _context.keyManager().getKeys(_clientDestination);
if (keys != null) {
skm = _context.clientManager().getClientSessionKeyManager(_clientDestination);
if (keys != null && skm != null) {
decryptionKey = keys.getDecryptionKey();
} else {
if (_log.shouldLog(Log.WARN))
@ -62,9 +66,10 @@ public class GarlicMessageReceiver {
}
} else {
decryptionKey = _context.keyManager().getPrivateKey();
skm = _context.sessionKeyManager();
}
CloveSet set = _parser.getGarlicCloves(message, decryptionKey);
CloveSet set = _parser.getGarlicCloves(message, decryptionKey, skm);
if (set != null) {
for (int i = 0; i < set.getCloveCount(); i++) {
GarlicClove clove = set.getClove(i);

View File

@ -17,6 +17,7 @@ import net.i2p.data.LeaseSet;
import net.i2p.data.Payload;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DataMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
@ -46,13 +47,15 @@ class OutboundClientMessageJobHelper {
*
* For now, its just a tunneled DeliveryStatusMessage
*
* Unused?
*
* @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)
* @return garlic, or null if no tunnels were found (or other errors)
*/
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
Payload data, Hash from, Destination dest, TunnelInfo replyTunnel,
SessionKey wrappedKey, Set wrappedTags,
SessionKey wrappedKey, Set<SessionTag> wrappedTags,
boolean requireAck, LeaseSet bundledReplyLeaseSet) {
PayloadGarlicConfig dataClove = buildDataClove(ctx, data, dest, expiration);
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
* 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)
*/
static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK,
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);
if (config == 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;
}

View File

@ -10,6 +10,8 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.TagSetHandle;
import net.i2p.data.Base64;
import net.i2p.data.Certificate;
import net.i2p.data.Destination;
@ -20,6 +22,7 @@ import net.i2p.data.Payload;
import net.i2p.data.PublicKey;
import net.i2p.data.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2np.DataMessage;
import net.i2p.data.i2np.DeliveryInstructions;
@ -471,7 +474,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
return;
}
int existingTags = GarlicMessageBuilder.estimateAvailableTags(getContext(), _leaseSet.getEncryptionKey(), _from);
int existingTags = GarlicMessageBuilder.estimateAvailableTags(getContext(), _leaseSet.getEncryptionKey(),
_from.calculateHash());
_outTunnel = selectOutboundTunnel(_to);
// boolean wantACK = _wantACK || existingTags <= 30 || getContext().random().nextInt(100) < 5;
// what's the point of 5% random? possible improvements or replacements:
@ -489,7 +493,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
PublicKey key = _leaseSet.getEncryptionKey();
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)
LeaseSet replyLeaseSet = getReplyLeaseSet(wantACK);
// ... and vice versa (so we know he got it)
@ -531,8 +535,16 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
SendTimeoutJob onFail = null;
ReplySelector selector = null;
if (wantACK) {
onReply = new SendSuccessJob(getContext(), sessKey, tags);
onFail = new SendTimeoutJob(getContext());
TagSetHandle tsh = null;
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);
}
@ -550,9 +562,9 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
+ _lease.getGateway().toBase64());
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
getContext().jobQueue().addJob(dispatchJob);
else
//if (false) // dispatch may take 100+ms, so toss it in its own job
// getContext().jobQueue().addJob(dispatchJob);
//else
dispatchJob.runJob();
} else {
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 */
private boolean buildClove() {
// FIXME set SKM
PayloadGarlicConfig clove = new PayloadGarlicConfig();
DeliveryInstructions instructions = new DeliveryInstructions();
@ -932,14 +945,14 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
*/
private class SendSuccessJob extends JobImpl implements ReplyJob {
private SessionKey _key;
private Set _tags;
private TagSetHandle _tags;
/**
* 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.
*
*/
public SendSuccessJob(RouterContext enclosingContext, SessionKey key, Set tags) {
public SendSuccessJob(RouterContext enclosingContext, SessionKey key, TagSetHandle tags) {
super(enclosingContext);
_key = key;
_tags = tags;
@ -955,10 +968,10 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
+ ": SUCCESS! msg " + _clientMessageId
+ " sent after " + sendTime + "ms");
if ( (_key != null) && (_tags != null) && (_tags.size() > 0) ) {
if (_leaseSet != null)
getContext().sessionKeyManager().tagsDelivered(_leaseSet.getEncryptionKey(),
_key, _tags);
if (_key != null && _tags != null && _leaseSet != null) {
SessionKeyManager skm = getContext().clientManager().getClientSessionKeyManager(_from.calculateHash());
if (skm != null)
skm.tagsAcked(_leaseSet.getEncryptionKey(), _key, _tags);
}
long dataMsgId = _cloveId;
@ -994,8 +1007,13 @@ public class OutboundClientMessageOneShotJob 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);
_key = key;
_tags = tags;
}
public String getName() { return "Send client message timed out"; }
@ -1005,6 +1023,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
+ ": Soft timeout through the lease " + _lease);
_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();
}
}