{% extends "_layout.html" %} {% block title %}Streaming Library{% endblock %} {% block content %} Updated August 2010, current as of router version 0.8
The streaming library is technically part of the "application" layer, as it is not a core router function. In practice, however, it provides a vital function for almost all existing I2P applications, by providing a TCP-like streams over I2P, and allowing existing apps to be easily ported to I2P. The other end-to-end transport library for client communication is the datagram library.
The streaming library is a layer on top of the core I2CP API that allows reliable, in-order, and authenticated streams of messages to operate across an unreliable, unordered, and unauthenticated message layer. Just like the TCP to IP relationship, this streaming functionality has a whole series of tradeoffs and optimizations available, but rather than embed that functionality into the base I2P code, it has been factored off into its own library both to keep the TCP-esque complexities separate and to allow alternative optimized implementations.
In consideration of the relatively high cost of messages, the streaming library's protocol for scheduling and delivering those messages has been optimized to allow individual messages passed to contain as much information as is available. For instance, a small HTTP transaction proxied through the streaming library can be completed in a single round trip - the first messages bundle a SYN, FIN, and the small HTTP request payload, and the reply bundles the SYN, FIN, ACK, and the HTTP response payload. While an additional ACK must be transmitted to tell the HTTP server that the SYN/FIN/ACK has been received, the local HTTP proxy can often deliver the full response to the browser immediately.
The streaming library bears much resemblance to an abstraction of TCP, with its sliding windows, congestion control algorithms (both slow start and congestion avoidance), and general packet behavior (ACK, SYN, FIN, RST, rto calculation, etc).
The streaming library is a robust library which is optimized for operation over I2P. It has a one-phase setup, and it contains a full windowing implementation.
The streaming library API provides a standard socket paradigm to Java applications. The lower-level I2CP API is completely hidden, except that applications may pass I2CP parameters through the streaming library, to be interpreted by I2CP.
The standard interface to the streaming lib is for the application to use the I2PSocketManagerFactory to create an I2PSocketManager. The application then asks the socket manager for an I2PSession, which will cause a connection to the router via I2CP. The application can then setup connections with an I2PSocket or receive connections with an I2PServerSocket.
Here are the full streaming library Javadocs.
For a good example of usage, see the i2psnark code.
The current default values are listed below. Lower case values are streaming lib parameters that can changed on a per-connection basis. These values are tuned for HTTP performance over typical I2P conditions. Other applications such as peer-to-peer services are strongly encouraged to modify as necessary, by setting the options and passing them via the call to I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts). Time values are in ms.
Note that higher-layer APIs, such as SAM, BOB, and I2PTunnel, may override these defaults with their own defaults.
Option | Default |
---|---|
i2p.streaming.connectTimeout | 5*60*1000 |
i2p.streaming.initialReceiveWindow | 1 |
i2p.streaming.initialWindowSize | 12 (if no sharing data available) |
i2p.streaming.maxWindowSize | 128 |
i2p.streaming.maxResends | 8 |
i2p.streaming.profile | 1 (bulk) (2=interactive not supported) |
i2p.streaming.maxMessageSize | 1730 |
i2p.streaming.initialRTT | 10*1000 (if no sharing data available) |
i2p.streaming.initialResendDelay | 1000 |
i2p.streaming.initialAckDelay | 2000 |
i2p.streaming.inactivityTimeout | 90*1000 |
i2p.streaming.inactivityAction | 2 (send) (0=noop, 1=disconnect) |
i2p.streaming.congestionAvoidanceGrowthRateFactor | 1 |
i2p.streaming.slowStartGrowthRateFactor | 1 |
i2p.streaming.answerPings | true |
i2p.streaming.maxConcurrentStreams | -1 (0 or negative value means unlimited) |
i2p.streaming.maxConnsPerMinute | 0 (per peer; 0 means disabled) |
i2p.streaming.maxConnsPerHour | 0 (per peer; 0 means disabled) |
i2p.streaming.maxConnsPerDay | 0 (per peer; 0 means disabled) |
i2p.streaming.maxTotalConnsPerMinute | 0 (all peers; 0 means disabled) |
i2p.streaming.maxTotalConnsPerHour | 0 (all peers; 0 means disabled) |
i2p.streaming.maxTotalConnsPerDay | 0 (all peers; 0 means disabled) |
Here is the format of a single packet transferred as part of a streaming connection.
Field | Length | Contents |
---|---|---|
sendStreamId | 4 byte Integer | Random number selected by the connection recipient and constant for the life of the connection. 0 in the SYN message sent by the originator, and in subsequent messages, until a SYN reply is received, containint the peer's stream ID. |
receiveStreamId | 4 byte Integer | Random number selected by the connection originator and constant for the life of the connection. May be 0 if unknown, for example in a RESET packet. |
sequenceNum | 4 byte Integer | The sequence for this message, starting at 0 in the SYN message, and incremented by 1 in each message except for plain ACKs and retransmissions. If the sequenceNum is 0 and the SYN flag is not set, this is a plain ACK packet that should not be ACKed. |
ackThrough | 4 byte Integer | The highest packet sequence number that was received on the receiveStreamId. This field is ignored on the initial connection packet (where receiveStreamId is the unknown id) or if the NO_ACK flag set. All packets up to and including this sequence number are ACKed, EXCEPT for those listed in NACKs below. |
NACK count | 1 byte Integer | The number of 4-byte NACKs in the next field |
NACKs | n * 4 byte Integers | Sequence numbers less than ackThrough that are not yet received. Two NACKs of a packet is a request for a 'fast retransmit' of that packet. |
resendDelay | 1 byte Integer | How long is the creator of this packet going to wait before resending this packet (if it hasn't yet been ACKed). The value is seconds since the packet was created. Currently ignored on receive. |
flags | 2 byte value | See below. |
option size | 2 byte Integer | The number of bytes in the next field |
option data | 0 or more bytes | As specified by the flags. See below. |
payload | remaining packet size |
The flags field above specifies some metadata about the packet, and in turn may require certain additional data to be included. The flags are as follows. Any data structures specified must be added to the options area in the given order.
Bit order: 15....0 (15 is MSB)
Bit | Flag | Option Data | Function |
---|---|---|---|
0 | SYNCHRONIZE | -- | Similar to TCP SYN. Set in the intial packet and in the first response. |
1 | CLOSE | -- | Similar to TCP FIN. If the response to a SYNCHRONIZE fits in a single message, the response will contain both SYNCHRONIZE and CLOSE. |
2 | RESET | -- | Abnormal close. |
3 | SIGNATURE_INCLUDED | 40 byte DSA Signature | Currently sent only with SYNCHRONIZE and CLOSE, where it is required. The signature uses the Destination's DSA signing keys to sign the entire header and payload with the 40-byte space in the option data field for the signature being set to all zeroes. |
4 | SIGNATURE_REQUESTED | -- | Unused. Requests every packet in the other direction to have SIGNATURE_INCLUDED |
5 | FROM_INCLUDED | 387+ byte Destination | Currently sent only with SYNCHRONIZE, where it is required. |
6 | DELAY_REQUESTED | 2 byte Integer | Optional delay. How many milliseconds the sender of this packet wants the recipient to wait before sending any more data. A value greater than 60000 indicates choking. |
7 | MAX_PACKET_SIZE_INCLUDED | 2 byte Integer | Currently sent with SYNCHRONIZE or with a retransmission; could be optimized to only send with a SYN. |
8 | PROFILE_INTERACTIVE | -- | Unused or ignored; the interactive profile is unimplemented. |
9 | ECHO | -- | Unused except by ping programs |
10 | NO_ACK | -- | This flag simply tells the recipient to ignore the ackThrough field in the header. Currently unused, the ackThrough field is always valid. |
11-15 | unused |
The initiator sends a packet with the SYNCHRONIZE flag set. This packet may contain the initial data as well. The peer replies with a packet with the SYNCHRONIZE flag set. This packet may contain the initial response data as well.
The initiator may send additional data packets, up to the initial window size, before receiving the SYNCHRONIZE response. These packets will also have the send Stream ID field set to 0. Recipients must buffer packets received on unknown streams for a short period of time, as they may arrive out of order, in advance of the SYNCHRONIZE packet.
The maximum message size (also called the MTU / MRU) is negotiated to the lower value supported by the two peers. As tunnel messages are padded to 1KB, a poor MTU selection will lead to a large amount of overhead. The MTU is specified by the option i2p.streaming.maxMessageSize. The current default MTU of 1730 was chosen to fit precisely into two 1K I2NP tunnel messages, including overhead for the typical case.
The first message in a connection includes a 387 byte (typical) Destination added by the streaming layer, and usually a 898 byte (typical) LeaseSet bundled in the Garlic message. Therefore, the goal of fitting a complete HTTP request in a single 1KB I2NP message is not realistic. However, the selection of the MTU, together with careful implementation of fragmentation and batching strategies in the tunnel gateway processor, are important factors in network bandwidth, latency, reliability, and efficiency, especially for long-lived connections.
The streaming lib uses standard slow-start (exponential window growth) and congestion avoidance (linear window growth) phases, with exponential backoff. Windowing and acknowledgements use packet count, not byte count.
Any packet, including one with the SYNCHRONIZE flag set, may have the CLOSE flag sent as well. The connection is not closed until the peer responds with the CLOSE flag. CLOSE packets may contain data as well.
The streaming lib supports "TCP" Control Block sharing. This shares two important streaming lib parameters (window size and round trip time) across connections to the same remote peer. This is used for "temporal" sharing at connection open/close time, not "ensemble" sharing during a connection (See RFC 2140). There is a separate share per ConnectionManager (i.e. per local Destination) so that there is no information leakage to other Destinations on the same router. The share data for a given peer expires after a few minutes.
The streaming library has grown organically for I2P - first mihi implemented the "mini streaming library" as part of I2PTunnel, which was limited to a window size of 1 message (requiring an ACK before sending the next one), and then it was refactored out into a generic streaming interface (mirroring TCP sockets) and the full streaming implementation was deployed with a sliding window protocol and optimizations to take into account the high bandwidth x delay product. Individual streams may adjust the maximum packet size and other options. The default message size is selected to fit precisely in two 1K I2NP tunnel messages, and is a reasonable tradeoff between the bandwidth costs of retransmitting lost messages and the latency of multiple messages.