java.lang.Object
net.i2p.router.JobImpl
net.i2p.router.message.OutboundClientMessageOneShotJob
All Implemented Interfaces:
Job

public class OutboundClientMessageOneShotJobextends JobImpl
Send a client message out an outbound tunnel and into an inbound tunnel on the target leaseSet. This also (sometimes) bundles the sender's leaseSet and a DeliveryStatusMessage (for ACKing any sessionTags used in the garlic).

This class is where we make several important decisions about what to send and what path to send it over. These decisions will dramatically affect:

  • Local performance and outbound bandwidth usage
  • Streaming performance and reliability
  • Overall network performance and connection congestion

For the outbound message, we build and encrypt a garlic message, after making the following decisions:

  • Whether to bundle our leaseset
  • Whether to bundle session tags, and if so, how many
  • Whether to bundle an encrypted DeliveryStatusMessage to be returned to us as an acknowledgement

Also, we make the following path selection decisions:

  • What outbound client tunnel of ours to use send the message out
  • What inbound client tunnel of his (i.e. lease, chosen from his leaseset) to use to send the message in
  • If a DeliveryStatusMessage is bundled, What inbound client tunnel of ours do we specify to receive it

Note that the 4th tunnel in the DeliveryStatusMessage's round trip (his outbound tunnel) is not selected by us, it is chosen by the recipient.

If a DeliveryStatusMessage is sent, a listener is registered to wait for its reply. When a reply is received, or the timeout is reached, this is noted and will influence subsequent bundling and path selection decisions.

Path selection decisions are cached and reused if still valid and if previous deliveries were apparently successful. This significantly reduces out-of-order delivery and network connection congestion. Caching is based on the local/remote destination pair.

Bundling decisions, and both messaging and reply expiration times, are generally set here but may be overridden by the client on a per-message basis. Within clients, there may be overall settings or per-message settings. The streaming lib also overrides defaults for some messages. A datagram-based DHT application may need significantly different settings than a streaming application. For an application such as a bittorrent client that sends both types of traffic on the same tunnels, it is important to tune the settings for efficiency and performance. The per-session and per-message overrides are set via I2CP.

  • Field Details

    • _log

      private final Log _log
      Logger for this send attempt.
    • _cache

      private final OutboundCache _cache
      Cache consulted for the target's lease set.
    • _overallExpiration

      private final long _overallExpiration
      When this send attempt gives up entirely.
    • _clientMessage

      private final ClientMessage _clientMessage
      The message being sent.
    • _clientMessageId

      private final MessageId _clientMessageId
      Id of the message being sent.
    • _clientMessageSize

      private final int _clientMessageSize
      Size of the message in bytes.
    • _from

      private final Destination _from
      The sending client's destination.
    • _to

      private final Destination _to
      The target destination.
    • _toString

      private final String _toString
      Base32 prefix of the target, for the logs.
    • _leaseSet

      private LeaseSet _leaseSet
      The target's lease set, if known.
    • _lease

      private Lease _lease
      The actual lease the message is being routed through.
    • _encryptionKey

      private PublicKey _encryptionKey
      Actual target encryption key from the LS being used.
    • _start

      private final long _start
      When the job started.
    • _finished

      The result reported when the job finishes.
    • _leaseSetLookupBegin

      private long _leaseSetLookupBegin
      When the lease set lookup started.
    • _outTunnel

      private TunnelInfo _outTunnel
      The outbound tunnel the message was sent on.
    • _inTunnel

      private TunnelInfo _inTunnel
      The inbound tunnel the reply arrived on.
    • _wantACK

      private boolean _wantACK
      Whether the client asked for an ack.
    • _hashPair

      private final OutboundCache.HashPair _hashPair
      Key used to cache things with, based on source + dest.
    • OVERALL_TIMEOUT_MS_PARAM

      public static final String OVERALL_TIMEOUT_MS_PARAM
      Final timeout (in milliseconds) that the outbound message will fail in. This can be overridden in the router.config or the client's session config (the client's session config takes precedence).
      See Also:
    • OVERALL_TIMEOUT_MS_DEFAULT

      private static final long OVERALL_TIMEOUT_MS_DEFAULT
      Default overall send timeout, one minute.
      See Also:
    • OVERALL_TIMEOUT_MS_MIN

      private static final long OVERALL_TIMEOUT_MS_MIN
      Shortest overall send timeout allowed.
      See Also:
    • OVERALL_TIMEOUT_MS_MAX

      private static final long OVERALL_TIMEOUT_MS_MAX
      Longest overall send timeout allowed.
      See Also:
    • LS_LOOKUP_TIMEOUT

      private static final long LS_LOOKUP_TIMEOUT
      Timeout for the lease set lookup.
      See Also:
    • OVERALL_TIMEOUT_NOLS_MIN

      private static final long OVERALL_TIMEOUT_NOLS_MIN
      Minimum overall timeout when a lookup is needed.
      See Also:
    • REPLY_TIMEOUT_MS_MIN

      private static final long REPLY_TIMEOUT_MS_MIN
      Minimum time left for the reply after sending.
      See Also:
    • RATCHET_REPLY_TIMEOUT_MS_MIN

      private static final long RATCHET_REPLY_TIMEOUT_MS_MIN
      Least time left for the ratchet reply callback, longer than the send timeout so a success after a failure still counts.
      See Also:
    • BUNDLE_REPLY_LEASESET

      public static final String BUNDLE_REPLY_LEASESET
      NOTE: Changed as of 0.9.2.

      Defaults to true.

      If the client's config specifies shouldBundleReplyInfo=true, messages sent from that client to any peers will periodically include the sending destination's current LeaseSet (allowing the recipient to reply without having to do a full netDb lookup). This should improve performance during the initial negotiations.

      For clients that do not publish their LeaseSet, this option must be true for any reply to be possible.

      Setting to "false" may save significant outbound bandwidth, especially if the client is configured with a large number of inbound tunnels (Leases). If replies are still required, this may shift the bandwidth burden to the far-end client and the floodfill.

      There are several cases where "false" is may be appropriate:

      • Unidirectional communication, no reply required
      • LeaseSet is published and higher reply latency is acceptable
      • LeaseSet is published, client is a "server", all connections are inbound so the connecting far-end destination obviously has the leaseset already. Connections are either short, or it is acceptable for latency on a long-lived connection to temporarily increase while the other end re-fetches the LeaseSet after expiration. HTTP servers may fit these requirements.
      See Also:
    • REPLY_REQUEST_INTERVAL

      private static final int REPLY_REQUEST_INTERVAL
      Interval between cached reply requests.
      See Also:
    • MIN_TUNNEL_USAGE

      private static final long MIN_TUNNEL_USAGE
      Minimum time in ms to keep using the same outbound tunnel for a given source-destination pair, to prevent rapid tunnel switching that causes out-of-order delivery and unnecessary NACKs in the streaming layer.
      See Also:
    • MAX_ROTATION_CANDIDATES

      private static final int MAX_ROTATION_CANDIDATES
      How many pool picks to sample when gathering rotation candidates for a fresh connection. With the default client pool of two tunnels a single extra pick almost always yields the alternative. A small bound keeps the hot path cheap.
      See Also:
    • RATES

      private static final long[] RATES
      Rate stat period for the send statistics.
    • STALL_ROTATION_COOLDOWN_MS

      private static final long STALL_ROTATION_COOLDOWN_MS
      Default cooldown a stall mark stays in effect: how long the router prefers another outbound tunnel for a destination that stopped delivering. Long enough to ride out a bad path, short enough that a briefly-bad tunnel is not withheld from a small pool for long.
      See Also:
  • Constructor Details

    • OutboundClientMessageOneShotJob

      public OutboundClientMessageOneShotJob(RouterContext ctx, OutboundCache cache, ClientMessage msg)
      Send it.
      Parameters:
      ctx - router context supplying the clock, log, and client netdb
      cache - outbound cache holding the LeaseSet cooldown and last-valid state
      msg - may have a null payload for ratchet-layer acks
  • Method Details

    • init

      public static void init(RouterContext ctx)
      Called once at router startup.
      Parameters:
      ctx - router context whose stat manager receives the statistics
    • getName

      public String getName()
      Description copied from interface: Job
      Descriptive name of the task.
      Returns:
      the descriptive name of the task
    • runJob

      public void runJob()
      Description copied from interface: Job
      Actually perform the task. This call blocks until the Job is complete.
    • getDestNickname

      private String getDestNickname(Hash dest)
      Resolve a human-readable nickname for a destination, if available.
      Parameters:
      dest - the destination hash to look up
      Returns:
      nickname or null
    • getReplyLeaseSet

      private LeaseSet getReplyLeaseSet(boolean force)
      Reply lease set, checking the cache first.
      Parameters:
      force - to force including a reply lease set
      Returns:
      lease set or null if we should not send the lease set
    • getNextLease

      private int getNextLease()
      Choose a lease from his leaseset to send the message to.

      Side effects: Sets _lease. Sets _wantACK if it's new or changed. Sets _encryptionKey.

      Does several checks to see if we can actually send to this leaseset, and returns nonzero failure code if unable to.

      Returns:
      0 on success, or a MessageStatusMessage failure code
    • send

      private void send()
      Send the message to the specified tunnel by creating a new garlic message containing the (already created) payload clove as well as a new delivery status message. This garlic message is sent out one of our tunnels, destined for the lease (tunnel+router) specified, and the delivery status message is targeting one of our free inbound tunnels as well. We use a new reply selector to keep an eye out for that delivery status message's token.
    • clearCaches

      private void clearCaches()
      Called on failure to give us a better chance of success next time. Of course this is probably 60s too late. And we could pick the bad ones at random again. Or remove entries that were sent and succeeded after this was sent but before this failed. But it's a start.
    • selectOutboundTunnel

      private TunnelInfo selectOutboundTunnel(Destination _to)
      Choose our outbound tunnel to send the message through. Sets _wantACK if it's new or changed.
      Parameters:
      _to - the destination we are sending to
      Returns:
      the tunnel or null on failure
    • selectOutboundTunnel

      private TunnelInfo selectOutboundTunnel()
      Pick an arbitrary outbound tunnel to send the message through, or null if there aren't any around

      Rather than pick one at random, pick the "closest" to the lease, to minimize network OBEP - IBGW connections? This would also eliminate a connection when OBEP == IBGW. Anonymity issues?

      Returns:
      an outbound tunnel, or null if none are available
    • selectFreshConnectionTunnel

      private TunnelInfo selectFreshConnectionTunnel()
      Try to pick a different outbound tunnel than the one the previous connection to this destination used. Only called for the first message of a new connection (fresh streaming SYN), so nothing is in flight on that connection yet and the swap is transparent. The chosen tunnel is recorded as the cached tunnel with a fresh start time, so all subsequent messages of the stream stay on it (no per-packet flapping).

      Called with _cache.tunnelCache locked.

      Returns:
      the rotated tunnel, or null when there is no cached tunnel or it is no longer valid - the caller then falls through to normal selection logic. When rotation is saturated (the pool offered no tunnel distinguishable from the previously-used one), the cached entry is evicted before returning null, so the fall-through re-selects fresh instead of riding the tunnel that stalled the previous connection.
    • selectInboundTunnel

      private TunnelInfo selectInboundTunnel()
      Pick an arbitrary inbound tunnel for any deliveryStatusMessage to come back in.
      Returns:
      an inbound tunnel, or null if none are available
    • dieFatal

      private void dieFatal(int status)
      Give up the ghost, this message just aint going through. tell the client. This is safe to call multiple times (only tells the client once). We may still succeed later.
      Parameters:
      status - the MessageStatusMessage.STATUS_SEND_FAILURE_* code to report to the client
    • isTunnelRelatedFailure

      static boolean isTunnelRelatedFailure(int status)
      Whether the I2CP failure status indicates the outbound tunnel itself rejected the message during dispatch, as opposed to a timeout, destination problem, or reply-path issue.

      Hard dispatch failures are reported to the pool at the normal removal bar. Statuses 14 (expired) and 16 (no tunnels) are local queue / pool-empty conditions, not a fault of this specific tunnel.

      Parameters:
      status - the I2CP MessageStatusMessage failure code
      Returns:
      true only for hard outbound-dispatch failures
    • isPoolStarvationFailure

      static boolean isPoolStarvationFailure(int status)
      Whether the failure status means the destination's pools were empty or starved when the send needed them: 14 (expired while queued — nothing carried it before the deadline) and 16 (no tunnels available). These do not blame the outbound tunnel (see isTunnelRelatedFailure(int)). They trigger a pool-level ensure nudge instead (see dieFatal).
      Parameters:
      status - the I2CP MessageStatusMessage failure code
      Returns:
      true for pool-starvation statuses only
    • isStalledForRotation

      private boolean isStalledForRotation()
      Whether the current (source, destination) pair is inside a stall cooldown, and rotation is enabled.

      Always a hint. When true the caller prefers another tunnel but must still return one. A destination whose every tunnel is inside a cooldown has to keep using one of them, or the send fails outright and a slow path becomes a dead one.

      Returns:
      true if a different tunnel should be preferred
    • starvationNudgeKey

      static Hash starvationNudgeKey(Hash sourceHash, Hash destHash)
      Which hash a pool-starvation nudge must be addressed to. The tunnel pool maps are keyed by the local client (source) destination hash — TunnelPoolManager registers pools under client.calculateHash() for local destinations — so the nudge always goes to the source hash. Passing the remote destination (the pre-fix behavior) finds no pool for every non-local target and the starvation signal silently evaporates.
      Parameters:
      sourceHash - the local sending destination's hash, or null when unknown
      destHash - the remote destination's hash (unused — kept so the source-vs-dest choice is pinned by tests rather than implicit)
      Returns:
      the pool-map key to nudge, or null when the source is unknown
    • isSoftSendFailure

      static boolean isSoftSendFailure(int status)
      Whether the status is a best-effort send timeout (3) after a successful dispatch. Reported to the pool at a higher removal bar so a congested destination or slow reply path cannot cascade-kill healthy tunnels, while a tunnel that times out repeatedly still rotates out before peers accumulate dozens of unreported failures.
      Parameters:
      status - the I2CP MessageStatusMessage failure code
      Returns:
      true for soft send timeouts only
    • shouldBlameOutboundTunnel

      static boolean shouldBlameOutboundTunnel(int status, boolean locallyHosted)
      Whether a failed send should be charged to the outbound tunnel.

      Pure, so the blame rule is testable without a router.

      A destination this router hosts produces its own reply, so a missing one is our own fault — a saturated handler pool, an aborted body, a slow upstream — and says nothing about the tunnel that delivered the request. Charging those failures to the tunnel makes the pool condemn its own healthy egress. The resulting starvation guarantees the next request fails too, so the service can never recover.

      Parameters:
      status - the I2CP MessageStatusMessage failure code
      locallyHosted - true if this router hosts the destination
      Returns:
      true if the outbound tunnel should be reported as failing
    • isLocallyHostedDestination

      private boolean isLocallyHostedDestination(Destination to)
      Whether this router hosts the given destination.

      The registry lookup is not pure, so it is kept out of shouldBlameOutboundTunnel(int, boolean) and pinned by tests at the decision level instead.

      Parameters:
      to - destination the message was sent to; may be null
      Returns:
      true if the destination is a local service
    • buildClove

      private PayloadGarlicConfig buildClove()
      Build the payload clove that will be used for all of the messages, placing the clove in the status structure.
      Returns:
      null on failure