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.
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 priorities at the tunnel OBEP.
For this to work, the minimum tag threshold and tag delivery quantity 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.
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate classPeriodically cleans up stale tag sets.private static classThe state for a crypto session to a single public key.private static classOne set of session tags sent to a peer.private static classJust for the HTML method above so we can see what's going on easier Earliest first.Field Summary
Modifier and TypeFieldDescriptionprivate booleanFalse once the manager has shut down.protected final I2PAppContextThe router context.private final Map<SessionTag, TransientSessionKeyManager.TagSet> Map allowing us to go from a SessionTag to the containing TagSet.private final LogLogger for transient session management.private final intTag count below which more tag sets are sent.private final Map<PublicKey, TransientSessionKeyManager.OutboundSession> Map allowing us to go from the targeted PublicKey to the OutboundSession used.private final AtomicIntegerFor debugging.private final AtomicIntegerCounter handing out sent tag set ids.private final intTags included in each tag set sent.static final intThis was 100 since 0.6.1.10 (50 before that).static final intSame as DEFAULT_RECHECK_INTERVAL.static final intMaximum number of entries in _inboundTagSets.private static final longKeep unused inbound session tags around for this long (a few minutes longer than session tags are used on the outbound side so that no reasonable network lag can cause failed decrypts)private static final longLet outbound session tags sit around for this long before expiring them.private static final longTime to send more if we are this close to expiration.Constructor Summary
ConstructorsConstructorDescriptionTransientSessionKeyManager(I2PAppContext context) The session key manager should only be constructed and accessed through the application context.TransientSessionKeyManager(I2PAppContext context, int tagsToSend, int lowThreshold) Log manager supplies the logger.Method Summary
Modifier and TypeMethodDescriptionprivate voidRegisters a new outbound session.private intAggressively expire inbound tag sets and outbound sessions.private voidclearExcess(int overage) Remove a bunch of arbitrarily selected tags, then drop all of the associated tag sets.(package private) static intcompareTagSets(long leftDate, long rightDate, int leftHash, int rightHash) Orders two tag sets by the two values the debug page sorts on: earliest date first, ties broken by hash.consumeNextAvailableTag(PublicKey target, SessionKey key) Retrieve the next available session tag for identifying the use of the given key when communicating with the target.consumeTag(SessionTag tag) Determine if we have received a session key associated with the given session tag, and if so, discard it (but keep track for frequent dups) and return the decryption key it was received with (via tagsReceived(...)).createAndReturnSession(PublicKey target, SessionKey key) Same as above but for internal use, returns OutboundSession so we don't have to do a subsequentgetSession(PublicKey).voidcreateSession(PublicKey target, SessionKey key) Associate a new session key with the specified target.voidfailTags(PublicKey target, SessionKey key, TagSetHandle ts) Mark these tags as invalid, since the peer has failed to ack them in time.intgetAvailableTags(PublicKey target, SessionKey key) Determine (approximately) how many available session tags for the current target have been confirmed and are available.longgetAvailableTimeLeft(PublicKey target, SessionKey key) Determine how long the available tags will be available for before expiring, in milliseconds.getCurrentKey(PublicKey target) Retrieve the session key currently associated with encryption to the target, or null if a new session key should be generated.getCurrentOrNewKey(PublicKey target) Retrieve the session key currently associated with encryption to the target.private Set<TransientSessionKeyManager.TagSet> Snapshot of all inbound tag sets.private Map<SessionKey, Set<TransientSessionKeyManager.TagSet>> Return a map of session key to a set of inbound TagSets for that SessionKey.intTag count below which new tags are sent.getOrCreateSession(PublicKey target, SessionKey key) The session for a target with its current key brought up to date, or a new one built under key if there is none yet.OutboundSession - used only by HTML.getSession(PublicKey target) Returns the pending session for a target key.intHow many to send, IF we need to.private voidremoveSession(PublicKey target) Drops the pending session for a target key.voidrenderStatusHTML(Writer out) Renders inbound/outbound ElGamal session state as debug HTML tables.booleanshouldSendTags(PublicKey target, SessionKey key, int lowThreshold) Whether to send a new tag set to the target.voidshutdown()Marks the manager dead and drops every tracked session and tag set.voidtagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) Mark these tags as acked, start to use them (if we haven't already) If the set was previously failed, it will be added back in.tagsDelivered(PublicKey target, SessionKey key, Set<SessionTag> sessionTags) Take note of the fact that the given sessionTags associated with the key for encryption to the target have been sent.voidtagsReceived(SessionKey key, Set<SessionTag> sessionTags) Accept the given tags and associate them with the given key for decryption.voidtagsReceived(SessionKey key, Set<SessionTag> sessionTags, long expire) Accept the given tags and associate them with the given key for decryption.private static StringFor debugging.Methods inherited from class SessionKeyManager
createSession, failTags, getDebugStats, shouldSendTagsModifier and TypeMethodDescriptioncreateSession(PublicKey target) Generate a new session key and associate it with the specified target.voidMark all of the tags delivered to the target up to this point as invalid, since the peer has failed to respond when they should have.long[]Debug overview stats as {inbound tags, outbound sessions, sessions created, sessions replaced, inbound sets completed, inbound tag misses}.booleanshouldSendTags(PublicKey target, SessionKey key) Determine if tags should be sent to the target.
Field Details
_log
Logger for transient session management._outboundSessions
Map allowing us to go from the targeted PublicKey to the OutboundSession used._inboundTagSets
Map allowing us to go from a SessionTag to the containing TagSet. Concurrent: a decrypt takes a tag out of it on every inbound message, so the map itself is not a lock. What a TagSet's own contents are guarded by is the TagSet - see consumeTag()._context
The router context._alive
private volatile boolean _aliveFalse once the manager has shut down._rcvTagSetID
For debugging._sentTagSetID
Counter handing out sent tag set ids._tagsToSend
private final int _tagsToSendTags included in each tag set sent._lowThreshold
private final int _lowThresholdTag count below which more tag sets are sent.SESSION_TAG_DURATION_MS
private static final long SESSION_TAG_DURATION_MSLet outbound session tags sit around for this long before expiring them. Inbound tag expiration is set by SESSION_LIFETIME_MAX_MS.- See Also:
SESSION_LIFETIME_MAX_MS
private static final long SESSION_LIFETIME_MAX_MSKeep unused inbound session tags around for this long (a few minutes longer than session tags are used on the outbound side so that no reasonable network lag can cause failed decrypts)This is also the max idle time for an outbound session.
- See Also:
SESSION_TAG_EXPIRATION_WINDOW
private static final long SESSION_TAG_EXPIRATION_WINDOWTime to send more if we are this close to expiration.- See Also:
MAX_INBOUND_SESSION_TAGS
public static final int MAX_INBOUND_SESSION_TAGSMaximum number of entries in _inboundTagSets.This is a memory bound, not a count target. Measured on a 64-bit JVM with compressed oops, an entry costs about 126 bytes - the 32-byte tag and its wrapper, the map node, and the hash table slot - so this cap is worth roughly 96 MB. The hourly cleanup only trims once the cap is already exceeded, so that figure is the steady-state ceiling, not a soft target.
- See Also:
DEFAULT_TAGS
public static final int DEFAULT_TAGSThis was 100 since 0.6.1.10 (50 before that). It's important because:- Tags are 32 bytes. So it previously added 3200 bytes to an initial message. - Too many tags adds a huge overhead to short-duration connections (like http, datagrams, etc.) - Large messages have a much higher chance of being dropped due to one of their 1KB fragments being discarded by a tunnel participant. - This reduces the effective maximum datagram size because the client doesn't know when tags will be bundled, so the tag size must be subtracted from the maximum I2NP size or transport limit.
Issues with too small a value:
- When tags are sent, a reply leaseset (~1KB) is always bundled. Maybe we don't need to bundle more than every minute or so rather than every time? - Does the number of tags (and the threshold of 20) limit the effective streaming lib window size? Should the threshold and the number of sent tags be variable based on the message rate?
We have to be very careful if we implement an adaptive scheme, since the key manager is per-router, not per-local-dest. Or maybe that's a bad idea, and we need to move to a per-dest manager. This needs further investigation.
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.
- See Also:
LOW_THRESHOLD
public static final int LOW_THRESHOLDSame as DEFAULT_RECHECK_INTERVAL.- See Also:
Constructor Details
TransientSessionKeyManager
The session key manager should only be constructed and accessed through the application context. This constructor should only be used by the appropriate application context itself.- Parameters:
context- the application context this manager is a service of, whose log manager supplies the logger
TransientSessionKeyManager
Log manager supplies the logger.- Parameters:
context- the application context this manager is a service of, whosetagsToSend- how many to send at a time, may be lower or higher than lowThreshold. 1-128lowThreshold- below this, send more. 1-128
Method Details
shutdown
public void shutdown()Marks the manager dead and drops every tracked session and tag set.- Overrides:
shutdownin classSessionKeyManager
getInboundTagSets
Snapshot of all inbound tag sets.- Returns:
- a new set, safe to iterate
getOutboundSessions
OutboundSession - used only by HTML.- Returns:
- a snapshot of all outbound sessions
getCurrentKey
Retrieve the session key currently associated with encryption to the target, 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(PublicKey).- Overrides:
getCurrentKeyin classSessionKeyManager- Parameters:
target- the destination whose current session key is wanted- Returns:
- the current key
getCurrentOrNewKey
Retrieve the session key currently associated with encryption to the target. Generates a new session and session key if not previously existing.- Overrides:
getCurrentOrNewKeyin classSessionKeyManager- Parameters:
target- public key to which the data should be encrypted, must be ELGAMAL_2048.- Returns:
- non-null
- Throws:
IllegalArgumentException- on bad target EncType
createSession
Associate a new session key with the specified target. Metrics to determine when to expire that key begin with this call.Racy if called after
getCurrentKey(PublicKey)to check for a current session; use getCurrentOrNewKey() in that case.- Overrides:
createSessionin classSessionKeyManager- Parameters:
target- public key to which the data should be encrypted, must be ELGAMAL_2048.key- the session key to associate, whose expiry metrics start at this call- Throws:
IllegalArgumentException- on bad target EncType
createAndReturnSession
private TransientSessionKeyManager.OutboundSession createAndReturnSession(PublicKey target, SessionKey key) Same as above but for internal use, returns OutboundSession so we don't have to do a subsequentgetSession(PublicKey).- Parameters:
target- the peer's public key, must be ElGamal 2048key- the session key the new session uses- Returns:
- the newly created outbound session
consumeNextAvailableTag
Retrieve the next available session tag for identifying the use of the given key when communicating with the target. If this returns null, no tags are available so ElG should be used with the given key (a new sessionKey should NOT be used).- Overrides:
consumeNextAvailableTagin classSessionKeyManager- Parameters:
target- the destination the tag was sent tokey- the session key whose use the tag identifies- Returns:
- the next available tag, or null
getTagsToSend
public int getTagsToSend()How many to send, IF we need to.- Overrides:
getTagsToSendin classSessionKeyManager- Returns:
- the configured value (not adjusted for current available)
getLowThreshold
public int getLowThreshold()Tag count below which new tags are sent.- Overrides:
getLowThresholdin classSessionKeyManager- Returns:
- the configured value
shouldSendTags
Whether to send a new tag set to the target.- Overrides:
shouldSendTagsin classSessionKeyManager- Parameters:
target- the destination whose tags are being consideredkey- the session keylowThreshold- the low threshold- Returns:
- true if we have less than the threshold or what we have is about to expire
getAvailableTags
Determine (approximately) how many available session tags for the current target have been confirmed and are available.- Overrides:
getAvailableTagsin classSessionKeyManager- Parameters:
target- the destination the tags were sent tokey- the session key those tags were issued under- Returns:
- the available tags
getAvailableTimeLeft
Determine how long the available tags will be available for before expiring, in milliseconds.- Overrides:
getAvailableTimeLeftin classSessionKeyManager- Parameters:
target- the destination the tags were sent tokey- the session key those tags were issued under- Returns:
- the available time left
tagsDelivered
Take note of the fact that the given sessionTags associated with the key for encryption to the target have been sent.Delivered tags are never usable straight away: every tag set starts unacked and only moves into the usable set when the peer acks it through
tagsAcked(PublicKey, SessionKey, TagSetHandle). The ack is what proves the peer received the tags, so the implementation waits for it unconditionally rather than pipelining on how much was sent.If the session does not exist yet it is created here, so the first ack also establishes the session. A tag set delivered under a different session key than the session currently holds is a rekey: the existing tag sets are dropped and the session goes back to waiting for acks.
- Overrides:
tagsDeliveredin classSessionKeyManager- Parameters:
target- public key to which the data is to be encryptedkey- the session key the tags are bound tosessionTags- the tags that were sent- Returns:
- the TagSetHandle, or null if the session could not be created. Caller MUST subsequently call
failTags(PublicKey)or tagsAcked() with this handle.
failTags
Mark these tags as invalid, since the peer has failed to ack them in time.- Overrides:
failTagsin classSessionKeyManager- Parameters:
target- the destination whose tags are being consideredkey- the session keyts- the tag set handle
tagsAcked
Mark these tags as acked, start to use them (if we haven't already) If the set was previously failed, it will be added back in.- Overrides:
tagsAckedin classSessionKeyManager- Parameters:
target- the destination whose tags are being consideredkey- the session keyts- the tag set handle
tagsReceived
Accept the given tags and associate them with the given key for decryption.- Overrides:
tagsReceivedin classSessionKeyManager- Parameters:
key- the session key the tags were received forsessionTags- modifiable; NOT copied
tagsReceived
Accept the given tags and associate them with the given key for decryption.- Overrides:
tagsReceivedin classSessionKeyManager- Parameters:
key- the session key the tags were received forsessionTags- modifiable; NOT copied. Non-null, non-empty.expire- time from now
clearExcess
private void clearExcess(int overage) Remove a bunch of arbitrarily selected tags, then drop all of the associated tag sets. This is very time consuming - iterating across the entire _inboundTagSets map, but it should be very rare, and the stats we can gather can hopefully reduce the frequency of using too many session tags in the future.- Parameters:
overage- the number of tags by which the limit was exceeded, the cleanup removes twice this many
consumeTag
Determine if we have received a session key associated with the given session tag, and if so, discard it (but keep track for frequent dups) and return the decryption key it was received with (via tagsReceived(...)). returns null if no session key matches.- Overrides:
consumeTagin classSessionKeyManager- Parameters:
tag- the session tag whose delivered key is consumed- Returns:
- the session key, or null
getSession
Returns the pending session for a target key.- Parameters:
target- the peer's public key- Returns:
- the session, or null if none is pending
getOrCreateSession
private TransientSessionKeyManager.OutboundSession getOrCreateSession(PublicKey target, SessionKey key) The session for a target with its current key brought up to date, or a new one built under key if there is none yet.Lookup and insert are one step under the session-map monitor, the same shape getCurrentOrNewKey() uses. Taken as a lookup and then a create, two clove threads handing over tags for the same target each found no session and each built one, and the second put discarded the first along with the tag sets already waiting on it.
- Parameters:
target- the peer the session is with, ELGAMAL_2048key- the session key the caller's tags are bound to- Returns:
- the session to attach the tag set to, or null if none could be built
addSession
Registers a new outbound session.- Parameters:
sess- the session to add
removeSession
Drops the pending session for a target key.- Parameters:
target- the peer's public key
aggressiveExpire
private int aggressiveExpire()Aggressively expire inbound tag sets and outbound sessions.- Returns:
- number of tag sets expired (bogus as it overcounts inbound)
getInboundTagSetsBySessionKey
Return a map of session key to a set of inbound TagSets for that SessionKey.- Returns:
- the inbound tag sets by session key
renderStatusHTML
Renders inbound/outbound ElGamal session state as debug HTML tables.- Overrides:
renderStatusHTMLin classSessionKeyManager- Parameters:
out- the writer to render to- Throws:
IOException- if an I/O error occurs
toString
compareTagSets
static int compareTagSets(long leftDate, long rightDate, int leftHash, int rightHash) Orders two tag sets by the two values the debug page sorts on: earliest date first, ties broken by hash.Compares instead of subtracting. A date gap over
Integer.MAX_VALUEms (24.9 days) flips the sign of a truncated subtraction, and a hash is any int, so the tie-break overflows routinely rather than never.- Parameters:
leftDate- the first candidate's tag-set date, in msrightDate- the second candidate's tag-set date, in msleftHash- the first candidate's hash, consulted only to break a date tierightHash- the second candidate's hash- Returns:
- negative, zero or positive as the first sorts before, alongside, or after the second