From tunnel-alt.html.
When the gateway wants to deliver data through the tunnel, it first gathers zero or more I2NP messages, selects how much padding will be used, fragments it across the necessary number of 1KB tunnel messages, and decides how each I2NP message should be handled by the tunnel endpoint, encoding that data into the raw tunnel payload:
- The 4 byte Tunnel ID
- The 16 byte IV
- the first 4 bytes of the SHA256 of (the remaining preprocessed data concatenated with the IV), using the IV as will be seen on the tunnel endpoint (for outbound tunnels), or the IV as was seen on the tunnel gateway (for inbound tunnels) (see below for IV processing).
- 0 or more bytes containing random nonzero integers
- 1 byte containing 0x00
- a series of zero or more { instructions, message } pairs
Note that the padding, if any, must be before the instruction/message pairs. there is no provision for padding at the end.
The instructions are encoded with a single control byte, followed by any necessary additional information. The first bit in that control byte determines how the remainder of the header is interpreted - if it is not set, the message is either not fragmented or this is the first fragment in the message. If it is set, this is a follow on fragment.
With the first (leftmost or MSB) bit being 0, the instructions are:
- 1 byte control byte:
bit 7: is follow on fragment? (1 = true, 0 = false, must be 0) bits 6-5: delivery type (0x0 = LOCAL, 0x01 = TUNNEL, 0x02 = ROUTER) bit 4: delay included? (1 = true, 0 = false) (unimplemented) bit 3: fragmented? (1 = true, 0 = false) bit 2: extended options? (1 = true, 0 = false) (unimplemented) bits 1-0: reserved - if the delivery type was TUNNEL, a 4 byte tunnel ID
- if the delivery type was TUNNEL or ROUTER, a 32 byte router hash
- if the delay included flag is true, a 1 byte value (unimplemented):
bit 7: type (0 = strict, 1 = randomized) bits 0-6: delay exponent (2^value minutes) - if the fragmented flag is true, a 4 byte message ID
- if the extended options flag is true (unimplemented):
= a 1 byte option size (in bytes) = that many bytes
- 2 byte size of the I2NP message or this fragment
If the first bit being 1, the instructions are:
- 1 byte control byte:
bit 7: is follow on fragment? (1 = true, 0 = false, must be 1) bits 6-1: fragment number bit 0: is last? (1 = true, 0 = false) - 4 byte message ID (same one defined in the first fragment)
- 2 byte size of this fragment
The I2NP message is encoded in its standard form, and the preprocessed payload must be padded to a multiple of 16 bytes. The total size, including the tunnel ID and IV, is 1028 bytes.
Nested Class Summary
Modifier and TypeClassDescriptionstatic interfaceReceive messages out of the tunnel endpoint.private classRemoves a failed message after a delay.Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final ByteCacheThe cache supplying the write buffers.private final AtomicIntegerMessages fully sent or received.protected final RouterContextThe router context.private final AtomicIntegerMessages that failed to send.private final Map<Integer, FragmentedMessage> The messages being fragmented, by id.private final ThreadLocal<I2NPMessageHandler> Per-thread I2NP message reader, used for inbound data only.private final booleanTrue for the inbound handler.protected final LogThe log.private final FragmentHandler.DefragmentedReceiverThe receiver of the defragmented messages.(package private) static final byteAre there follow-up headers? UNIMPLEMENTED.private static final intFor subsequent fragments, which bits contain the fragment number?(package private) static final byteIs this the first fragment of a fragmented message?(package private) static final byteIs this a follow-up byte?(package private) static final byteHow this should be delivered.(package private) static longDon't wait more than this long to completely receive a fragmented message.(package private) static final intMaximum number of partially reassembled messages held at once.(package private) static final shortLocal delivery.(package private) static final shortRouter delivery.(package private) static final shortTunnel delivery.(package private) static final shortUndefined delivery.Constructor Summary
ConstructorsConstructorDescriptionFragmentHandler(RouterContext context, FragmentHandler.DefragmentedReceiver receiver) For unit tests only, others use 3-arg constructor.FragmentHandler(RouterContext context, FragmentHandler.DefragmentedReceiver receiver, boolean isInbound) Binds the given context and receiver, storing the inbound flag.Method Summary
Modifier and TypeMethodDescriptionintFragments received and complete.intFragments that failed.(package private) FragmentedMessagegetOrCreate(int messageId, long fMessageId) Get the message being reassembled for the given ID, starting it if this is its first fragment.(package private) static int[]hashRanges(int offset, int validLength, int paddingEnd) The two byte ranges the inbound verifier must hash, in the order they must be fed to the digest: {payloadOffset, payloadLength, ivOffset, ivLength}.(package private) static booleanmayStartNewFragmentedMessage(int activeCount) Determine whether another message may begin reassembling given how many are already in progress.private voidreceiveComplete(byte[] data, int offset, int len, Hash router, TunnelId tunnelId) Zero-copy reception of an unfragmented message.private voidHandles a fully received message.private intreceiveFragment(byte[] preprocessed, int offset, int length) The offset for the next byte after the received fragment, or -1 on error.private intreceiveInitialFragment(byte[] preprocessed, int offset, int length) Handle the initial fragment in a message (or a full message, if it fits).private intreceiveSubsequentFragment(byte[] preprocessed, int offset, int length) Handle a fragment beyond the initial fragment in a message.booleanreceiveTunnelMessage(byte[] preprocessed, int offset, int length) Receive the raw preprocessed message at the endpoint, parsing out each of the fragments, using those to fill various FragmentedMessages, and sending the resulting I2NPMessages where necessary.private booleanverifyPreprocessed(byte[] preprocessed, int offset, int length) Verify that the preprocessed data hasn't been modified by checking the H(payload+IV)[0:3] vs preprocessed[16:19], where payload is the data after the padding.
Field Details
_context
The router context._log
The log._fragmentedMessages
The messages being fragmented, by id._receiver
The receiver of the defragmented messages._completed
Messages fully sent or received._failed
Messages that failed to send._isInbound
private final boolean _isInboundTrue for the inbound handler._inboundHandler
Per-thread I2NP message reader, used for inbound data only.I2NPMessageHandler is NOT threadsafe because it caches the message it last parsed in an instance field that readMessage() and lastRead() share, and a single FragmentHandler is called by every UDPHandle thread reading tunnel messages. One handler per thread keeps that state confined to the thread that wrote it, and costs an allocation per thread rather than per message.
MAX_DEFRAGMENT_TIME
static long MAX_DEFRAGMENT_TIMEDon't wait more than this long to completely receive a fragmented message.MAX_FRAGMENTED_MESSAGES
static final int MAX_FRAGMENTED_MESSAGESMaximum number of partially reassembled messages held at once. Entries expire after MAX_DEFRAGMENT_TIME, but a peer that sends fragments for many distinct message IDs faster than they expire would otherwise grow the map without bound, so new messages are refused while the map is at the limit and the remaining fragments of those messages are dropped.- See Also:
_cache
The cache supplying the write buffers.MASK_IS_SUBSEQUENT
MASK_TYPE
static final byte MASK_TYPEHow this should be delivered. Shift right 5 to get TYPE_*.- See Also:
MASK_FRAGMENTED
static final byte MASK_FRAGMENTEDIs this the first fragment of a fragmented message?- See Also:
MASK_EXTENDED
static final byte MASK_EXTENDEDAre there follow-up headers? UNIMPLEMENTED.- See Also:
MASK_FRAGMENT_NUM
private static final int MASK_FRAGMENT_NUMFor subsequent fragments, which bits contain the fragment number?- See Also:
TYPE_LOCAL
TYPE_TUNNEL
TYPE_ROUTER
TYPE_UNDEF
Constructor Details
FragmentHandler
For unit tests only, others use 3-arg constructor.- Parameters:
context- the router contextreceiver- callback that receives each fully reassembled message
FragmentHandler
public FragmentHandler(RouterContext context, FragmentHandler.DefragmentedReceiver receiver, boolean isInbound) Binds the given context and receiver, storing the inbound flag.- Parameters:
context- the router contextreceiver- the defragmented receiverisInbound- true for IBEP, false for OBEP
Method Details
receiveTunnelMessage
public boolean receiveTunnelMessage(byte[] preprocessed, int offset, int length) Receive the raw preprocessed message at the endpoint, parsing out each of the fragments, using those to fill various FragmentedMessages, and sending the resulting I2NPMessages where necessary. The received fragments are all verified.- Parameters:
preprocessed- the preprocessed message as read from the transportoffset- index in the buffer at which the message startslength- number of bytes of the message to process- Returns:
- ok (false if corrupt)
getCompleteCount
public int getCompleteCount()Fragments received and complete.- Returns:
- the number of fragments that completed
getFailedCount
public int getFailedCount()Fragments that failed.- Returns:
- the number of fragments that failed
hashRanges
static int[] hashRanges(int offset, int validLength, int paddingEnd) The two byte ranges the inbound verifier must hash, in the order they must be fed to the digest: {payloadOffset, payloadLength, ivOffset, ivLength}.The preprocessed buffer holds the IV at the front, but the spec hashes the payload followed by the IV, so the IV range is returned second even though it comes first in the buffer. Hashing IV first would yield a different digest and fail every message, so the order here is load-bearing.
- Parameters:
offset- where this message's data starts within the buffervalidLength- total number of bytes hashed, payload plus the IVpaddingEnd- one past the final padding byte, i.e. where the payload starts- Returns:
- {payloadOffset, payloadLength, ivOffset, ivLength} in hash order
verifyPreprocessed
private boolean verifyPreprocessed(byte[] preprocessed, int offset, int length) Verify that the preprocessed data hasn't been modified by checking the H(payload+IV)[0:3] vs preprocessed[16:19], where payload is the data after the padding. Remember, the preprocessed data is formatted as { IV + H[0:3] + padding + {instructions, fragment}* }. The IV and the payload are therefore not adjacent in the buffer, so the two ranges are fed to the digest separately, payload first, avoiding a copy into a contiguous staging buffer.- Parameters:
preprocessed- the preprocessed buffer holding this message's IV, checksum, padding and fragment dataoffset- where this message's data starts within the bufferlength- one past the last valid byte in the buffer- Returns:
- true if the recomputed H(payload+IV) matches the checksum stored in the buffer, false if it differs or the padding runs past the end
- Throws:
RuntimeException- if the SHA-256 digest computation fails
receiveFragment
private int receiveFragment(byte[] preprocessed, int offset, int length) The offset for the next byte after the received fragment, or -1 on error.- Parameters:
preprocessed- the preprocessed buffer holding the fragmentoffset- where the fragment starts within the bufferlength- one past the last valid byte in the buffer- Returns:
- the offset for the next byte after the received fragment or -1 on error
- Throws:
RuntimeException- if a subsequent fragment carries a negative message id
receiveInitialFragment
private int receiveInitialFragment(byte[] preprocessed, int offset, int length) Handle the initial fragment in a message (or a full message, if it fits).- Parameters:
preprocessed- the preprocessed buffer holding the fragmentoffset- where the fragment starts within the bufferlength- one past the last valid byte in the buffer- Returns:
- offset after reading the full fragment or -1 on error
mayStartNewFragmentedMessage
static boolean mayStartNewFragmentedMessage(int activeCount) Determine whether another message may begin reassembling given how many are already in progress. Refused messages are dropped until older ones complete or expire, which bounds the memory a single peer can pin.- Parameters:
activeCount- messages currently being reassembled- Returns:
- true if a new message may be started
getOrCreate
Get the message being reassembled for the given ID, starting it if this is its first fragment. Refuses to start a new message while the map is at its cap; fragments of messages already in progress are always accepted so an existing reassembly can still finish.- Parameters:
messageId- the message IDfMessageId- the message ID as a long, for the new message- Returns:
- the message, or null if a new message may not be started
receiveSubsequentFragment
private int receiveSubsequentFragment(byte[] preprocessed, int offset, int length) Handle a fragment beyond the initial fragment in a message.- Parameters:
preprocessed- the preprocessed buffer holding the fragmentoffset- where the fragment starts within the bufferlength- one past the last valid byte in the buffer- Returns:
- offset after reading the full fragment or -1 on error
- Throws:
RuntimeException- if the fragment's message id is negative
receiveComplete
Handles a fully received message.- Parameters:
msg- the complete message
receiveComplete
Zero-copy reception of an unfragmented message.- Parameters:
data- the buffer holding the message payload, bad bounds drop itoffset- where the payload starts in data, negative drops itlen- the payload length in bytes, 0 or less drops itrouter- the destination router hash from the delivery instructions, null if nonetunnelId- the destination tunnel id, null if none