java.lang.Object
net.i2p.crypto.SessionKeyManager
net.i2p.router.crypto.TransientSessionKeyManager

public class TransientSessionKeyManagerextends SessionKeyManager
Implement the session key management, but keep everything in memory (don't write 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.

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.

  • Field Details

    • _log

      private final Log _log
      Logger for transient session management.
    • _outboundSessions

      private final Map<PublicKey, TransientSessionKeyManager.OutboundSession> _outboundSessions
      Map allowing us to go from the targeted PublicKey to the OutboundSession used.
    • _inboundTagSets

      private final Map<SessionTag, TransientSessionKeyManager.TagSet> _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

      protected final I2PAppContext _context
      The router context.
    • _alive

      private volatile boolean _alive
      False once the manager has shut down.
    • _rcvTagSetID

      private final AtomicInteger _rcvTagSetID
      For debugging.
    • _sentTagSetID

      private final AtomicInteger _sentTagSetID
      Counter handing out sent tag set ids.
    • _tagsToSend

      private final int _tagsToSend
      Tags included in each tag set sent.
    • _lowThreshold

      private final int _lowThreshold
      Tag count below which more tag sets are sent.
    • SESSION_TAG_DURATION_MS

      private static final long SESSION_TAG_DURATION_MS
      Let 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_MS
      Keep 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_WINDOW
      Time to send more if we are this close to expiration.
      See Also:
    • MAX_INBOUND_SESSION_TAGS

      public static final int MAX_INBOUND_SESSION_TAGS
      Maximum 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_TAGS
      This 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_THRESHOLD
      Same as DEFAULT_RECHECK_INTERVAL.
      See Also:
  • Constructor Details

    • TransientSessionKeyManager

      public TransientSessionKeyManager(I2PAppContext context)
      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

      public TransientSessionKeyManager(I2PAppContext context, int tagsToSend, int lowThreshold)
      Log manager supplies the logger.
      Parameters:
      context - the application context this manager is a service of, whose
      tagsToSend - how many to send at a time, may be lower or higher than lowThreshold. 1-128
      lowThreshold - 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:
      shutdown in class SessionKeyManager
    • getInboundTagSets

      private Set<TransientSessionKeyManager.TagSet> getInboundTagSets()
      Snapshot of all inbound tag sets.
      Returns:
      a new set, safe to iterate
    • getOutboundSessions

      private Set<TransientSessionKeyManager.OutboundSession> getOutboundSessions()
      OutboundSession - used only by HTML.
      Returns:
      a snapshot of all outbound sessions
    • getCurrentKey

      public SessionKey getCurrentKey(PublicKey target)
      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:
      getCurrentKey in class SessionKeyManager
      Parameters:
      target - the destination whose current session key is wanted
      Returns:
      the current key
    • getCurrentOrNewKey

      public SessionKey getCurrentOrNewKey(PublicKey target)
      Retrieve the session key currently associated with encryption to the target. Generates a new session and session key if not previously existing.
      Overrides:
      getCurrentOrNewKey in class SessionKeyManager
      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

      public void createSession(PublicKey target, SessionKey key)
      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:
      createSession in class SessionKeyManager
      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 subsequent getSession(PublicKey).
      Parameters:
      target - the peer's public key, must be ElGamal 2048
      key - the session key the new session uses
      Returns:
      the newly created outbound session
    • consumeNextAvailableTag

      public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key)
      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:
      consumeNextAvailableTag in class SessionKeyManager
      Parameters:
      target - the destination the tag was sent to
      key - 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:
      getTagsToSend in class SessionKeyManager
      Returns:
      the configured value (not adjusted for current available)
    • getLowThreshold

      public int getLowThreshold()
      Tag count below which new tags are sent.
      Overrides:
      getLowThreshold in class SessionKeyManager
      Returns:
      the configured value
    • shouldSendTags

      public boolean shouldSendTags(PublicKey target, SessionKey key, int lowThreshold)
      Whether to send a new tag set to the target.
      Overrides:
      shouldSendTags in class SessionKeyManager
      Parameters:
      target - the destination whose tags are being considered
      key - the session key
      lowThreshold - the low threshold
      Returns:
      true if we have less than the threshold or what we have is about to expire
    • getAvailableTags

      public int getAvailableTags(PublicKey target, SessionKey key)
      Determine (approximately) how many available session tags for the current target have been confirmed and are available.
      Overrides:
      getAvailableTags in class SessionKeyManager
      Parameters:
      target - the destination the tags were sent to
      key - the session key those tags were issued under
      Returns:
      the available tags
    • getAvailableTimeLeft

      public long getAvailableTimeLeft(PublicKey target, SessionKey key)
      Determine how long the available tags will be available for before expiring, in milliseconds.
      Overrides:
      getAvailableTimeLeft in class SessionKeyManager
      Parameters:
      target - the destination the tags were sent to
      key - the session key those tags were issued under
      Returns:
      the available time left
    • tagsDelivered

      public TagSetHandle 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.

      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:
      tagsDelivered in class SessionKeyManager
      Parameters:
      target - public key to which the data is to be encrypted
      key - the session key the tags are bound to
      sessionTags - 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

      public void failTags(PublicKey target, SessionKey key, TagSetHandle ts)
      Mark these tags as invalid, since the peer has failed to ack them in time.
      Overrides:
      failTags in class SessionKeyManager
      Parameters:
      target - the destination whose tags are being considered
      key - the session key
      ts - the tag set handle
    • tagsAcked

      public void tagsAcked(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.
      Overrides:
      tagsAcked in class SessionKeyManager
      Parameters:
      target - the destination whose tags are being considered
      key - the session key
      ts - the tag set handle
    • tagsReceived

      public void tagsReceived(SessionKey key, Set<SessionTag> sessionTags)
      Accept the given tags and associate them with the given key for decryption.
      Overrides:
      tagsReceived in class SessionKeyManager
      Parameters:
      key - the session key the tags were received for
      sessionTags - modifiable; NOT copied
    • tagsReceived

      public void tagsReceived(SessionKey key, Set<SessionTag> sessionTags, long expire)
      Accept the given tags and associate them with the given key for decryption.
      Overrides:
      tagsReceived in class SessionKeyManager
      Parameters:
      key - the session key the tags were received for
      sessionTags - 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

      public SessionKey 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(...)). returns null if no session key matches.
      Overrides:
      consumeTag in class SessionKeyManager
      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_2048
      key - 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

      private void addSession(TransientSessionKeyManager.OutboundSession sess)
      Registers a new outbound session.
      Parameters:
      sess - the session to add
    • removeSession

      private void removeSession(PublicKey target)
      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

      private Map<SessionKey, Set<TransientSessionKeyManager.TagSet>> 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

      public void renderStatusHTML(Writer out) throws IOException
      Renders inbound/outbound ElGamal session state as debug HTML tables.
      Overrides:
      renderStatusHTML in class SessionKeyManager
      Parameters:
      out - the writer to render to
      Throws:
      IOException - if an I/O error occurs
    • toString

      private static String toString(PublicKey target)
      For debugging.
      Parameters:
      target - the key to render, null allowed
      Returns:
      the key in Base64, or the string null
    • 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_VALUE ms (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 ms
      rightDate - the second candidate's tag-set date, in ms
      leftHash - the first candidate's hash, consulted only to break a date tie
      rightHash - the second candidate's hash
      Returns:
      negative, zero or positive as the first sorts before, alongside, or after the second