Overview
The I2P Streaming Library provides reliable, in-order, authenticated transport over I2P’s message layer, similar to TCP over IP. It sits above the I2CP protocol and is used by nearly all interactive I2P applications, including HTTP proxies, IRC, BitTorrent, and email.
This design enables small HTTP requests and responses to complete in a single round-trip. A SYN packet may carry the request payload, while the responder’s SYN/ACK/FIN may contain the full response body.
The Java streaming API mirrors standard Java socket programming:
Full Javadocs are available from the I2P router console or here .
API Basics
You can pass configuration properties when creating a socket manager via:
Newer features since version 0.9.4 include reject log suppression, DSA list support (0.9.21), and mandatory protocol enforcement (0.9.36). Routers since 2.10.0 include post-quantum hybrid encryption (ML-KEM + X25519) at the transport layer.
Core Characteristics
Each stream is identified by a Stream ID. Packets carry control flags similar to TCP: SYNCHRONIZE, ACK, FIN, and RESET. Packets may contain both data and control flags simultaneously, improving efficiency for short-lived connections.
Because I2P tunnels introduce latency and message reordering, the library buffers packets from unknown or early-arriving streams. Buffered messages are stored until synchronization completes, ensuring complete, in-order delivery.
| Option | Default | Notes |
|---|---|---|
| i2cp.accessList | null | Comma- or space-separated list of Base64 peer Hashes used for either access list or blacklist. As of release 0.7.13. |
| i2cp.destination.sigType | DSA_SHA1 | The name or number of the signature type for a transient destination. As of release 0.9.12. |
| i2cp.enableAccessList | false | Use the access list as a whitelist for incoming connections. As of release 0.7.13. |
| i2cp.enableBlackList | false | Use the access list as a blacklist for incoming connections. As of release 0.7.13. |
| i2p.streaming.answerPings | true | Whether to respond to incoming pings |
| i2p.streaming.blacklist | null | Comma- or space-separated list of Base64 peer Hashes to be blacklisted for incoming connections to ALL destinations in the context. This option must be set in the context properties, NOT in the createManager() options argument. Note that setting this in the router context will not affect clients outside the router in a separate JVM and context. As of release 0.9.3. |
| i2p.streaming.bufferSize | 64K | How much transmit data (in bytes) will be accepted that hasn't been written out yet. |
| i2p.streaming.congestionAvoidanceGrowthRateFactor | 1 | When we're in congestion avoidance, we grow the window size at the rate of 1/(windowSize*factor). In standard TCP, window sizes are in bytes, while in I2P, window sizes are in messages. A higher number means slower growth. |
| i2p.streaming.connectDelay | -1 | How long to wait after instantiating a new con before actually attempting to connect. If this is <= 0, connect immediately with no initial data. If greater than 0, wait until the output stream is flushed, the buffer fills, or that many milliseconds pass, and include any initial data with the SYN. |
| i2p.streaming.connectTimeout | 5*60*1000 | How long to block on connect, in milliseconds. Negative means indefinitely. Default is 5 minutes. |
| i2p.streaming.disableRejectLogging | false | Whether to disable warnings in the logs when an incoming connection is rejected due to connection limits. As of release 0.9.4. |
| i2p.streaming.dsalist | null | Comma- or space-separated list of Base64 peer Hashes or host names to be contacted using an alternate DSA destination. Only applies if multisession is enabled and the primary session is non-DSA (generally for shared clients only). This option must be set in the context properties, NOT in the createManager() options argument. Note that setting this in the router context will not affect clients outside the router in a separate JVM and context. As of release 0.9.21. |
| i2p.streaming.enforceProtocol | true | Whether to listen only for the streaming protocol. Setting to true will prohibit communication with Destinations earlier than release 0.7.1 (released March 2009). Set to true if running multiple protocols on this Destination. As of release 0.9.1. Default true as of release 0.9.36. |
| i2p.streaming.inactivityAction | 2 (send) | (0=noop, 1=disconnect) What to do on an inactivity timeout - do nothing, disconnect, or send a duplicate ack. |
| i2p.streaming.inactivityTimeout | 90*1000 | Idle time before sending a keepalive |
| i2p.streaming.initialAckDelay | 750 | Delay before sending an ack |
| i2p.streaming.initialResendDelay | 1000 | The initial value of the resend delay field in the packet header, times 1000. Not fully implemented; see below. |
| i2p.streaming.initialRTO | 9000 | Initial timeout (if no sharing data available). As of release 0.9.8. |
| i2p.streaming.initialRTT | 8000 | Initial round trip time estimate (if no sharing data available). Disabled as of release 0.9.8; uses actual RTT. |
| i2p.streaming.initialWindowSize | 6 | (if no sharing data available) In standard TCP, window sizes are in bytes, while in I2P, window sizes are in messages. |
| i2p.streaming.limitAction | reset | What action to take when an incoming connection exceeds limits. Valid values are: reset (reset the connection); drop (drop the connection); or http (send a hardcoded HTTP 429 response). Any other value is a custom response to be sent. backslash-r and backslash-n will be replaced with CR and LF. As of release 0.9.34. |
| i2p.streaming.maxConcurrentStreams | -1 | (0 or negative value means unlimited) This is a total limit for incoming and outgoing combined. |
| i2p.streaming.maxConnsPerMinute | 0 | Incoming connection limit (per peer; 0 means disabled). As of release 0.7.14. |
| i2p.streaming.maxConnsPerHour | 0 | (per peer; 0 means disabled). As of release 0.7.14. |
| i2p.streaming.maxConnsPerDay | 0 | (per peer; 0 means disabled). As of release 0.7.14. |
| i2p.streaming.maxMessageSize | 1730 | The maximum size of the payload, i.e. the MTU in bytes. |
| i2p.streaming.maxResends | 8 | Maximum number of retransmissions before failure. |
| i2p.streaming.maxTotalConnsPerMinute | 0 | Incoming connection limit (all peers; 0 means disabled). As of release 0.7.14. |
| i2p.streaming.maxTotalConnsPerHour | 0 | (all peers; 0 means disabled) Use with caution as exceeding this will disable a server for a long time. As of release 0.7.14. |
| i2p.streaming.maxTotalConnsPerDay | 0 | (all peers; 0 means disabled) Use with caution as exceeding this will disable a server for a long time. As of release 0.7.14. |
| i2p.streaming.maxWindowSize | 128 | |
| i2p.streaming.profile | 1 (bulk) | 1=bulk; 2=interactive; see important notes below. |
| i2p.streaming.readTimeout | -1 | How long to block on read, in milliseconds. Negative means indefinitely. |
| i2p.streaming.slowStartGrowthRateFactor | 1 | When we're in slow start, we grow the window size at the rate of 1/(factor). In standard TCP, window sizes are in bytes, while in I2P, window sizes are in messages. A higher number means slower growth. |
| i2p.streaming.tcbcache.rttDampening | 0.75 | Ref: RFC 2140. Floating point value. May be set only via context properties, not connection options. As of release 0.9.8. |
| i2p.streaming.tcbcache.rttdevDampening | 0.75 | Ref: RFC 2140. Floating point value. May be set only via context properties, not connection options. As of release 0.9.8. |
| i2p.streaming.tcbcache.wdwDampening | 0.75 | Ref: RFC 2140. Floating point value. May be set only via context properties, not connection options. As of release 0.9.8. |
| i2p.streaming.writeTimeout | -1 | How long to block on write/flush, in milliseconds. Negative means indefinitely. |
The option i2p.streaming.enforceProtocol=true (default since 0.9.36) ensures connections use the correct I2CP protocol number, preventing conflicts between multiple subsystems sharing one destination.
Protocol Details
Key Options
The streaming protocol coexists with the Datagram API, giving developers the choice between connection-oriented and connectionless transports.
Behavior by Workload
Applications can reuse existing tunnels by running as shared clients, allowing multiple services to share the same destination. While this reduces overhead, it increases cross-service correlation risk—use with care.
Because I2P adds several hundred milliseconds of base latency, applications should minimize round-trips. Bundle data with connection setup where possible (e.g., HTTP requests in SYN). Avoid designs relying on many small sequential exchanges.
Performance depends heavily on tunnel configuration: - Short tunnels (1–2 hops) → lower latency, reduced anonymity. - Long tunnels (3+ hops) → higher anonymity, increased RTT.
Connection Lifecycle
Fragmentation and Reordering
Protocol Enforcement
The I2P Streaming Library is the backbone of all reliable communication within I2P. It ensures in-order, authenticated, encrypted message delivery and provides a near drop-in replacement for TCP in anonymous environments.
Shared Clients
To achieve optimal performance: - Minimize round-trips with SYN+payload bundling. - Tune window and timeout parameters for your workload. - Favor shorter tunnels for latency-sensitive applications. - Use congestion-friendly designs to avoid overloading peers.
El tamaño máximo predeterminado de la ventana de transmisión y recepción en la implementación de Java es de 128 paquetes. Las implementaciones que establezcan un tamaño máximo de ventana de transmisión superior a 128 deben considerar los siguientes problemas:
- One-phase connection setup using SYN, ACK, and FIN flags that can be bundled with payload data to reduce round-trips.
- Sliding-window congestion control, with slow start and congestion avoidance tuned for I2P’s high-latency environment.
- Packet compression (default 4KB compressed segments) balancing retransmission cost and fragmentation latency.
- Fully authenticated, encrypted, and reliable channel abstraction between I2P destinations.
El tamaño mínimo recomendado del buffer para implementaciones de receptor es de 128 paquetes o 232 KB (aproximadamente 128 * 1812). Debido a la latencia de la red I2P, la pérdida de paquetes y el control de congestión resultante, un buffer de este tamaño rara vez se llena. Sin embargo, el desbordamiento es mucho más probable que ocurra en conexiones de “local loopback” (mismo router) de alto ancho de banda o en pruebas locales.
Para indicar rápidamente y recuperarse sin problemas de las condiciones de desbordamiento, existe un mecanismo simple para el retroceso en el protocolo de streaming. Si se recibe un paquete con un campo de retraso opcional con un valor de 60001 o superior, eso indica “estrangulamiento” o una ventana de recepción de cero. Un paquete con un campo de retraso opcional con un valor de 60000 o menor indica “desestrangulamiento”. Los paquetes sin un campo de retraso opcional no afectan el estado de estrangulamiento/desestrangulamiento.
Después de ser estrangulado, no se deben enviar más paquetes con datos hasta que el transmisor sea desestrangulado, excepto por paquetes de datos de “sonda” ocasionales para compensar posibles paquetes de desestrangulamiento perdidos. El endpoint estrangulado debe iniciar un “temporizador de persistencia” para controlar el sondeo, como en TCP. El endpoint que desestrangula debe enviar varios paquetes con este campo establecido, o continuar enviándolos periódicamente hasta que se reciban paquetes de datos nuevamente. El tiempo máximo de espera para el desestrangulamiento depende de la implementación. El tamaño de ventana del transmisor y la estrategia de control de congestión después de ser desestrangulado depende de la implementación.
Congestion Control
La biblioteca de streaming utiliza las fases estándar de slow-start (crecimiento exponencial de ventana) y evitación de congestión (crecimiento lineal de ventana), con retroceso exponencial. El manejo de ventanas y confirmaciones utiliza el conteo de paquetes, no el conteo de bytes.
Latency Considerations
Cualquier paquete, incluyendo uno con la bandera SYNCHRONIZE establecida, puede tener también la bandera CLOSE enviada. La conexión no se cierra hasta que el peer responde con la bandera CLOSE. Los paquetes CLOSE también pueden contener datos.
Ping / Pong
No hay función de ping en la capa I2CP (equivalente a echo ICMP) o en datagramas. Esta función se proporciona en streaming. Los pings y pongs no pueden combinarse con un paquete de streaming estándar; si se establece la opción ECHO, entonces la mayoría de otras banderas, opciones, ackThrough, sequenceNum, NACKs, etc. son ignoradas.
Un paquete ping debe tener establecidas las banderas ECHO, SIGNATURE_INCLUDED y FROM_INCLUDED. El sendStreamId debe ser mayor que cero, y el receiveStreamId se ignora. El sendStreamId puede o no corresponder a una conexión existente.
Un paquete pong debe tener configurada la bandera ECHO. El sendStreamId debe ser cero, y el receiveStreamId es el sendStreamId del ping. Antes de la versión 0.9.18, el paquete pong no incluye ninguna carga útil que estuviera contenida en el ping.
A partir de la versión 0.9.18, los pings y pongs pueden contener una carga útil. La carga útil en el ping, hasta un máximo de 32 bytes, se devuelve en el pong.
El streaming puede configurarse para deshabilitar el envío de pongs con la configuración i2p.streaming.answerPings=false.
Problemas de 0-RTT
Como se señaló anteriormente, a diferencia de TCP, el streaming permite la entrega de datos 0-RTT empaquetando datos en el paquete SYN. Esta es la implementación preferida. ADEMÁS, el streaming permite que se envíen paquetes de datos adicionales (hasta el tamaño de ventana inicial) después del SYN, antes de que se reciba el SYN-ACK. Estos paquetes tendrán un número de secuencia distinto de cero, no tendrán la bandera SYN establecida, y tendrán un sendStreamID de cero.
Los receptores deben diseñarse para paquetes fuera de orden o perdidos durante el handshake, incluyendo la llegada de paquetes de datos antes del SYN. La implementación preferida es encolar, no descartar, los paquetes no-SYN para un ID desconocido, y recuperarlos de la cola después de recibir el SYN.
En la dirección inversa, las cosas son similares. El receptor de la conexión (Bob) debería retrasar el envío del SYN-ACK (ACK DELAY) y esperar un corto tiempo para recibir datos de la aplicación. Al recibir datos de la aplicación, los coloca (hasta el tamaño máximo del paquete) en el paquete SYN-ACK y lo envía. También se pueden enviar paquetes de datos adicionales, hasta el tamaño inicial de la ventana, sin esperar un ACK del SYN-ACK.
El originador debe almacenar en buffer cualquier paquete de datos recibido antes del SYN-ACK, de la misma manera que el manejo fuera de orden después de que se complete el handshake.
Probando las Bibliotecas de Streaming
Para desarrolladores que prueban bibliotecas de streaming nuevas o modificadas, Java I2P proporciona una utilidad de prueba local simple para pruebas reproducibles de condiciones de red reales, incluyendo latencia, pérdida de paquetes y fluctuación de retraso. Es un pequeño stub que implementa únicamente un servidor I2CP para conexiones locales.
Los desarrolladores deben probar con una amplia gama de parámetros típicos, incluyendo latencia desde 10ms hasta al menos 15s, y pérdida de paquetes de 0 a 10%. Agregar jitter facilita probar el manejo de paquetes fuera de orden.
Esta también es una buena configuración para probar el desbordamiento del búfer (CHOKE/UNCHOKE) suspendiendo manualmente una de las dos aplicaciones.
Del paquete fuente i2p.i2p:
I2PSocketManagerFactorynegotiates or reuses a router session via I2CP.- If no key is provided, a new destination is automatically generated.
- Developers can pass I2CP options (e.g., tunnel lengths, encryption types, or connection settings) through the
optionsmap. I2PSocketandI2PServerSocketmirror standard JavaSocketinterfaces, making migration straightforward.
Notas de i2p.streaming.profile
Esta opción admite dos valores; 1=bulk y 2=interactive. La opción proporciona una pista a la biblioteca de streaming y/o router sobre el patrón de tráfico que se espera.
“Bulk” significa optimizar para un ancho de banda alto, posiblemente a expensas de la latencia. Esta es la configuración predeterminada. “Interactive” significa optimizar para una latencia baja, posiblemente a expensas del ancho de banda o la eficiencia. Las estrategias de optimización, si las hay, dependen de la implementación y pueden incluir cambios fuera del protocolo de streaming.
Hasta la versión 0.9.63 de la API, Java I2P devolvía un error para cualquier valor distinto de 1 (bulk) y el tunnel fallaría al iniciarse. A partir de la versión 0.9.64 de la API, Java I2P ignora el valor. Hasta la versión 0.9.63 de la API, i2pd ignoraba esta opción; está implementada en i2pd a partir de la versión 0.9.64 de la API.
Aunque el protocolo de streaming incluye un campo de bandera para pasar la configuración del perfil al otro extremo, esto no está implementado en ningún router conocido.
Compartición de Bloques de Control
La biblioteca de streaming soporta el intercambio de “TCP” Control Block. Esto comparte tres parámetros importantes de la biblioteca de streaming (tamaño de ventana, tiempo de ida y vuelta, varianza del tiempo de ida y vuelta) entre conexiones al mismo peer remoto. Esto se utiliza para intercambio “temporal” en el momento de apertura/cierre de conexión, no intercambio “conjunto” durante una conexión (Ver RFC 2140 ). Hay un intercambio separado por ConnectionManager (es decir, por Destination local) para que no haya filtración de información a otros Destinations en el mismo router. Los datos de intercambio para un peer determinado expiran después de unos minutos. Los siguientes parámetros de Control Block Sharing pueden configurarse por router:
- SYN sent — initiator includes optional data.
- SYN/ACK response — responder includes optional data.
- ACK finalization — establishes reliability and session state.
- FIN/RESET — used for orderly closure or abrupt termination.
Otros Parámetros
Los siguientes parámetros son valores predeterminados recomendados. Los valores predeterminados pueden variar, dependiendo de la implementación:
- The streaming layer continuously adapts to network latency and throughput via RTT-based feedback.
- Applications perform best when routers are contributing peers (participating tunnels enabled).
- TCP-like congestion control mechanisms prevent overloading slow peers and help balance bandwidth use across tunnels.
Historia
La biblioteca de streaming ha crecido de forma orgánica para I2P - primero mihi implementó la “mini biblioteca de streaming” como parte de I2PTunnel, que estaba limitada a un tamaño de ventana de 1 mensaje (requiriendo un ACK antes de enviar el siguiente), y luego se refactorizó en una interfaz de streaming genérica (reflejando los sockets TCP) y se implementó la implementación completa de streaming con un protocolo de ventana deslizante y optimizaciones para tener en cuenta el alto producto ancho de banda x retraso. Los streams individuales pueden ajustar el tamaño máximo de paquete y otras opciones. El tamaño de mensaje predeterminado está seleccionado para encajar precisamente en dos mensajes de tunnel I2NP de 1K, y es un equilibrio razonable entre los costos de ancho de banda de retransmitir mensajes perdidos, y la latencia y sobrecarga de múltiples mensajes.
Interoperability and Best Practices
El comportamiento de la biblioteca de streaming tiene un impacto profundo en el rendimiento a nivel de aplicación, y como tal, es un área importante para análisis adicionales.
- Always test against both Java I2P and i2pd to ensure full compatibility.
- Although the protocol is standardized, minor implementation differences may exist.
- Handle older routers gracefully—many peers still run pre-2.0 versions.
- Monitor connection stats using
I2PSocket.getOptions()andgetSession()to read RTT and retransmission metrics.