java.lang.Object
net.i2p.router.client.ClientConnectionRunner
Direct Known Subclasses:
QueuedClientConnectionRunner

class ClientConnectionRunnerextends Object
Bridge the router and the client - managing state for a client.

As of release 0.9.21, multiple sessions are supported on a single I2CP connection. These sessions share tunnels and some configuration.

  • Field Details

    • _log

      protected final Log _log
      Logger for the runner.
    • _context

      protected final RouterContext _context
      Router context.
    • _manager

      protected final ClientManager _manager
      Client manager managing this runner.
    • _socket

      private final Socket _socket
      Socket for this particular peer connection.
    • _out

      private OutputStream _out
      Output stream of the socket that I2CP messages bound to the client should be written to.
    • _sessions

      The client's sessions, by destination hash.
    • _clientVersion

      private String _clientVersion
      The client's version string.
    • _messages

      private final Map<MessageId, Payload> _messages
      Mapping of MessageId to Payload, storing messages for retrieval. Unused for i2cp.fastReceive = "true" (_dontSendMSMOnRecive = true).
    • _consecutiveLeaseRequestFails

      private int _consecutiveLeaseRequestFails
      Lease set publish failures in a row.
    • _lastLeaseFailTime

      private long _lastLeaseFailTime
      When a lease set publish last failed.
    • _acceptedPending

      private final Set<MessageId> _acceptedPending
      Set of messageIds created but not yet ACCEPTED. Unused for i2cp.messageReliability = "none" (_dontSendMSM = true).
    • _reader

      protected I2CPMessageReader _reader
      Reads and processes incoming I2CP messages from the client.
    • _sessionKeyManager

      private SessionKeyManager _sessionKeyManager
      Used for all sessions, which must all have the same crypto keys.
    • _floodfillNetworkDatabaseFacade

      private FloodfillNetworkDatabaseFacade _floodfillNetworkDatabaseFacade
      Used for leaseSets sent to and received from this client.
    • _alreadyProcessed

      private final List<MessageId> _alreadyProcessed
      This contains the last 10 MessageIds that have had their (non-ack) status delivered to the client (so that we can be sure only to update when necessary).
    • _writer

      private ClientWriterRunner _writer
      The thread writing replies to the client.
    • _dead

      private volatile boolean _dead
      True once the connection has been shut down.
    • _dontSendMSM

      private boolean _dontSendMSM
      For outbound traffic. True if i2cp.messageReliability = "none". @since 0.8.1.
    • _dontSendMSMOnReceive

      private boolean _dontSendMSMOnReceive
      For inbound traffic. True if i2cp.fastReceive = "true". @since 0.9.4.
    • _messageId

      private final AtomicInteger _messageId
      Counter handing out message ids.
    • _encryptedLSHash

      private Hash _encryptedLSHash
      Hash of the client's encrypted lease set.
    • MAX_MESSAGE_ID

      private static final int MAX_MESSAGE_ID
      Largest message id before wrapping.
      See Also:
    • MAX_LEASE_FAILS

      private static final int MAX_LEASE_FAILS
      Disconnect after this many consecutive LeaseSet request failures. Increased from 5 to 10 to tolerate brief I2CP stalls without mass-disconnect; see failLeaseRequest(LeaseRequestState) decay handling.
      See Also:
    • LEASE_FAIL_DECAY_MS

      private static final long LEASE_FAIL_DECAY_MS
      Reset consecutive-fail counter if this much time has elapsed since last failure.
      See Also:
    • BUF_SIZE

      private static final int BUF_SIZE
      Socket buffer size, in bytes.
      See Also:
    • MAX_SESSIONS

      private static final int MAX_SESSIONS
      Sessions allowed per client connection.
      See Also:
    • MAX_BUFFERED_MESSAGES

      private static final int MAX_BUFFERED_MESSAGES
      Maximum number of unclaimed messages to buffer per client connection. Prevents unbounded memory growth if the client is slow to consume.
      See Also:
    • PROP_TAGS

      private static final String PROP_TAGS
      Config keys for tag counts and send options.
      See Also:
    • PROP_THRESH

      private static final String PROP_THRESH
      Config key for the low tag threshold.
      See Also:
    • __id

      private static final AtomicInteger __id
      Counter naming the client connections.
    • REQUEUE_DELAY

      private static final long REQUEUE_DELAY
      If the message hasn't been state=ACCEPTED yet, we shouldn't send an update since the client doesn't know the message id (and we don't know the nonce). So, we just wait REQUEUE_DELAY ms before trying again.
      See Also:
    • MAX_REQUEUE

      private static final int MAX_REQUEUE
      Status update requeues before it is dropped.
      See Also:
  • Constructor Details

    • ClientConnectionRunner

      public ClientConnectionRunner(RouterContext context, ClientManager manager, Socket socket)
      Create a new runner against the given socket.
      Parameters:
      context - the router context
      manager - the client manager
      socket - the I2CP socket
  • Method Details

    • startRunning

      public void startRunning() throws IOException
      Actually run the connection - listen for I2CP messages and respond. This is the main driver for this class, though it gets all its meat from the I2CPMessageReader.
      Throws:
      IOException - if the socket I/O fails
    • createListener

      protected I2CPMessageReader.I2CPMessageEventListener createListener()
      Allow override for testing.
      Returns:
      the message event listener
    • stopRunning

      public void stopRunning()
      Die a horrible death. Cannot be restarted.
    • getAddress

      public InetAddress getAddress()
      The address of the client socket.
      Returns:
      the client's InetAddress
    • getConfig

      public SessionConfig getConfig(Hash h)
      Current client's config, will be null if session not found IS subsession aware.
      Parameters:
      h - the session hash
      Returns:
      the config, or null if session not found
    • getConfig

      public SessionConfig getConfig(SessionId id)
      Current client's config, will be null if session not found IS subsession aware. Returns null if id is null.
      Parameters:
      id - the session id
      Returns:
      the config, or null if not found or id is null
    • getPrimaryConfig

      public SessionConfig getPrimaryConfig()
      Primary client's config, will be null if session not set up.
      Returns:
      the primary config, or null if not set up
    • setClientVersion

      public void setClientVersion(String version)
      The client version.
      Parameters:
      version - the client version string
    • getClientVersion

      public String getClientVersion()
      The client version.
      Returns:
      null if unknown or less than 0.8.7
    • getSessionKeyManager

      public SessionKeyManager getSessionKeyManager()
      The current client's SessionKeyManager. As of 0.9.44, returned implementation varies based on supported encryption types.
      Returns:
      the session key manager
    • getLeaseSet

      public LeaseSet getLeaseSet(Hash h)
      Currently allocated leaseSet. IS subsession aware. Returns primary leaseset only.
      Parameters:
      h - the session hash
      Returns:
      leaseSet or null if not yet set or unknown hash
    • getDestHash

      public Hash getDestHash()
      Equivalent to getConfig(Hash).getDestination().calculateHash(); will be null before session is established Not subsession aware. Returns primary session hash. Don't use if you can help it.
      Returns:
      primary hash or null if not yet set
    • getDestHash

      public Hash getDestHash(SessionId id)
      Return the hash for the given ID.
      Parameters:
      id - the session id
      Returns:
      hash or null if unknown
    • getDestination

      public Destination getDestination(SessionId id)
      Return the dest for the given ID.
      Parameters:
      id - the session id
      Returns:
      dest or null if unknown
    • getSessionId

      SessionId getSessionId(Hash h)
      Subsession aware.
      Parameters:
      h - the local target
      Returns:
      current client's sessionId or null if not yet set or not a valid hash
    • getSessionIds

      List<SessionId> getSessionIds()
      Subsession aware.
      Returns:
      all current client's sessionIds, non-null
    • getDestinations

      List<Destination> getDestinations()
      Subsession aware.
      Returns:
      all current client's destinations, non-null
    • setSessionId

      void setSessionId(Hash hash, SessionId id)
      To be called only by ClientManager.
      Parameters:
      hash - for the session
      id - the session id to set
      Throws:
      IllegalStateException - if already set
    • removeSession

      void removeSession(SessionId id)
      Kill the session. Caller must kill runner if none left. If the session is primary and there are subsessions, this removes all subsessions also.
      Parameters:
      id - the session id to remove
    • getLeaseRequest

      LeaseRequestState getLeaseRequest(Hash h)
      Data for the current leaseRequest, or null if there is no active leaseSet request. Not subsession aware. Returns primary ID only.
      Parameters:
      h - the session hash
      Returns:
      the lease request state, or null if none
    • failLeaseRequest

      public void failLeaseRequest(LeaseRequestState req)
      Fail the given lease request and track consecutive failures. The counter decays after LEASE_FAIL_DECAY_MS of no failures, so a brief I2CP stall does not permanently prime a disconnect. Counter resets to zero on successful LeaseSet receipt. All null states (request, requested LeaseSet, or its destination) are guarded before any dereference; RequestLeaseSetJob calls this with a null LeaseSet or null destination on its early-failure paths.
      Parameters:
      req - the failed request, or null to ignore
    • isDead

      boolean isDead()
      Already closed?
      Returns:
      true if the runner is dead
    • getPayload

      Payload getPayload(MessageId id)
      Only call if _dontSendMSMOnReceive is false, otherwise will always be null.
      Parameters:
      id - the message id
      Returns:
      the payload, or null
    • setPayload

      void setPayload(MessageId id, Payload payload)
      Only call if _dontSendMSMOnReceive is false.
      Parameters:
      id - the message id
      payload - the payload to store
    • removePayload

      void removePayload(MessageId id)
      Only call if _dontSendMSMOnReceive is false.
      Parameters:
      id - the message id to remove
    • sessionEstablished

      public int sessionEstablished(SessionConfig config)
      Caller must send a SessionStatusMessage to the client with the returned code. Caller must call disconnectClient() on failure. Side effect: Sets the session ID.
      Parameters:
      config - the session config
      Returns:
      SessionStatusMessage return code, 1 for success, != 1 for failure
    • updateMessageDeliveryStatus

      void updateMessageDeliveryStatus(Destination dest, MessageId id, long messageNonce, int status)
      Send a notification to the client that their message (id specified) was delivered (or failed delivery) Note that this sends the Guaranteed status codes, even though we only support best effort. Doesn't do anything if i2cp.messageReliability = "none"

      Do not use for status = STATUS_SEND_ACCEPTED; use ackSendMessage() for that.

      Parameters:
      dest - the client whose session is notified; no notification if it has none
      id - the router's ID for this message
      messageNonce - the client's ID for this message, greater than zero
      status - see I2CP MessageStatusMessage for success/failure codes
    • leaseSetCreated

      void leaseSetCreated(LeaseSet ls)
      Called after a new leaseSet is granted by the client and the NetworkDb has been updated. This takes care of all the LeaseRequestState stuff (including firing any jobs).
      Parameters:
      ls - if encrypted, the encrypted LS, not the decrypted one
    • registerEncryptedLS

      public boolean registerEncryptedLS(Hash hash)
      Call after destinationEstablished(), when an encrypted leaseset is created, so we know it's local. Add to the clients list. Check for a dup hash. Caller must call runner.disconnectClient() on failure.
      Parameters:
      hash - the location of the encrypted LS, will change every day
      Returns:
      success, false on dup
    • disconnectClient

      void disconnectClient(String reason)
      Send a DisconnectMessage and log with level Log.ERROR. This is always bad. See ClientMessageEventListener.handleCreateSession(CreateSessionMessage) for why we don't send a SessionStatusMessage when we do this.
      Parameters:
      reason - will be truncated to 255 bytes
    • disconnectClient

      void disconnectClient(String reason, int logLevel)
      Disconnect the client with the given reason and log level.
      Parameters:
      reason - will be truncated to 255 bytes
      logLevel - accepted but never read; the disconnect is logged at WARN
    • distributeMessage

      MessageId distributeMessage(SendMessageMessage message)
      Distribute the message. If the dest is local, it blocks until its passed to the target ClientConnectionRunner (which then fires it into a MessageReceivedJob). If the dest is remote, it blocks until it is added into the ClientMessagePool.
      Parameters:
      message - the message to distribute
      Returns:
      the message id assigned
    • ackSendMessage

      void ackSendMessage(SessionId sid, MessageId id, long nonce)
      Send a notification to the client that their message (id specified) was accepted for delivery (but not necessarily delivered) Doesn't do anything if i2cp.messageReliability = "none" or if the nonce is 0.
      Parameters:
      sid - the session id
      id - OUR id for the message
      nonce - HIS id for the message
    • receiveMessage

      boolean receiveMessage(Destination toDest, Destination fromDest, Payload payload)
      Synchronously deliver the message to the current runner

      Failure indication is available as of 0.9.29. Fails on e.g. queue overflow to client, client dead, etc.

      Parameters:
      toDest - non-null
      fromDest - generally null when from remote, non-null if from local
      payload - the payload to deliver
      Returns:
      true if the client was told about the message
    • receiveMessage

      boolean receiveMessage(Hash toHash, Destination fromDest, Payload payload)
      Synchronously deliver the message to the current runner

      Failure indication is available as of 0.9.29. Fails on e.g. queue overflow to client, client dead, etc.

      Parameters:
      toHash - non-null
      fromDest - generally null when from remote, non-null if from local
      payload - the payload to deliver
      Returns:
      true if a session existed for toHash and the client was told
    • reportAbuse

      public void reportAbuse(Destination dest, String reason, int severity)
      Send async abuse message to the client.
      Parameters:
      dest - the offending destination
      reason - the human-readable description of the abuse
      severity - the severity level
    • rerequestAfterTransientPublishFailure

      boolean rerequestAfterTransientPublishFailure(Destination dest)
      Re-queue the pending lease request after netdb publish() rejected the client's LeaseSet as expired (client clock skew or slow signing). The same LeaseRequestState is re-run so the session slot, callbacks, and deadline stay intact while the job recomputes lease end times against the current clock, instead of leaving the request to wait out the full timeout check. Bounded by LeaseRequestState.MAX_TRANSIENT_PUBLISH_RETRIES so a persistently skewed client cannot re-request forever.
      Parameters:
      dest - the session destination, null for no session
      Returns:
      true if a retry was queued
    • requestLeaseSet

      void requestLeaseSet(Hash h, LeaseSet set, long expirationTime, Job onCreateJob, Job onFailedJob)
      Request that a particular client authorize the Leases contained in the LeaseSet, after which the onCreateJob is queued up. If that doesn't occur within the timeout specified, queue up the onFailedJob. This call does not block.

      Job args are always null, may need some fixups if we start using them.

      Parameters:
      h - the Destination's hash
      set - LeaseSet with requested leases - this object must be updated to contain the signed version (as well as any changed/added/removed Leases) The LeaseSet contains Leases only, it is unsigned. Must be unique for this hash, do not reuse for subsessions.
      expirationTime - ms to wait before failing
      onCreateJob - Job to run after the LeaseSet is authorized, null OK
      onFailedJob - Job to run after the timeout passes without receiving authorization, null OK
    • disconnected

      void disconnected()
      The client was disconnected.
    • getIsDead

      boolean getIsDead()
      Check if the runner is dead.
      Returns:
      true if the runner is dead
    • writeMessage

      void writeMessage(I2CPMessage msg)
      Not thread-safe. Blocking. Only used for external sockets. ClientWriterRunner thread is the only caller. Others must use doSend().
      Parameters:
      msg - the message to write
    • doSend

      void doSend(I2CPMessage msg) throws I2CPMessageException
      Actually send the I2CPMessage to the peer through the socket.
      Parameters:
      msg - the message to send
      Throws:
      I2CPMessageException - if the message could not be sent
    • doSendWait

      void doSendWait(I2CPMessage msg, long timeoutMs) throws I2CPMessageException
      Send a message, allowing the transport up to timeoutMs to accept it.

      For messages whose loss costs more than a short wait. The default is doSend(I2CPMessage): a socket-backed runner hands the message to a writer thread that buffers it regardless, so there is nothing to wait for here. Subclasses that write straight into a bounded queue override this.

      Parameters:
      msg - the message to send
      timeoutMs - how long to wait for the transport to accept the message
      Throws:
      I2CPMessageException - if the message could not be sent
    • getNextMessageId

      public int getNextMessageId()
      Get the next message id.
      Returns:
      the next id
    • alreadyAccepted

      private boolean alreadyAccepted(MessageId id)
      True if the client has already been sent the ACCEPTED state for the given message id, false otherwise.
      Parameters:
      id - the message id whose ACCEPTED notification state is queried
      Returns:
      true if ACCEPTED has already been sent, false if it is still pending or the runner is dead
    • getFloodfillNetworkDatabaseFacade

      public FloodfillNetworkDatabaseFacade getFloodfillNetworkDatabaseFacade()
      The FloodfillNetworkDatabaseFacade for this runner, the client netDb.

      If a session has not been created yet, it will return null.

      Returns:
      the client netdb or null if no session was created yet