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.
O tamanho máximo padrão da janela de transmissão e recepção na implementação Java é de 128 pacotes. Implementações que definem um tamanho máximo da janela de transmissão superior a 128 devem considerar as seguintes questões:
- 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.
O tamanho mínimo recomendado de buffer para implementações de receptor é de 128 pacotes ou 232 KB (aproximadamente 128 * 1812). Devido à latência da rede I2P, perdas de pacotes e o controle de congestionamento resultante, um buffer deste tamanho raramente é preenchido. O overflow é, no entanto, muito mais provável de ocorrer em conexões de alta largura de banda “local loopback” (mesmo router) ou em testes locais.
Para indicar rapidamente e recuperar suavemente de condições de overflow, existe um mecanismo simples de pushback no protocolo de streaming. Se um pacote é recebido com um campo de atraso opcional de valor 60001 ou superior, isso indica “choking” ou uma janela de recepção de zero. Um pacote com um campo de atraso opcional de valor 60000 ou inferior indica “unchoking”. Pacotes sem um campo de atraso opcional não afetam o estado de choke/unchoke.
Após ser choked, não devem ser enviados mais pacotes com dados até que o transmissor seja unchoked, exceto por pacotes de dados de “sonda” ocasionais para compensar possíveis pacotes de unchoke perdidos. O endpoint choked deve iniciar um “persist timer” para controlar a sondagem, como no TCP. O endpoint que faz unchoking deve enviar vários pacotes com este campo definido, ou continuar enviando-os periodicamente até que pacotes de dados sejam recebidos novamente. O tempo máximo para aguardar o unchoking é dependente da implementação. O tamanho da janela do transmissor e a estratégia de controle de congestionamento após ser unchoked é dependente da implementação.
Congestion Control
A biblioteca de streaming usa as fases padrão de slow-start (crescimento exponencial da janela) e evitação de congestionamento (crescimento linear da janela), com backoff exponencial. O controle de janela e reconhecimentos usam contagem de pacotes, não contagem de bytes.
Latency Considerations
Qualquer pacote, incluindo um com a flag SYNCHRONIZE definida, pode ter a flag CLOSE enviada também. A conexão não é fechada até que o peer responda com a flag CLOSE. Pacotes CLOSE também podem conter dados.
Ping / Pong
Não há função de ping na camada I2CP (equivalente ao echo ICMP) ou em datagramas. Esta função é fornecida no streaming. Pings e pongs não podem ser combinados com um pacote de streaming padrão; se a opção ECHO estiver definida, então a maioria das outras flags, opções, ackThrough, sequenceNum, NACKs, etc. são ignoradas.
Um pacote ping deve ter as flags ECHO, SIGNATURE_INCLUDED e FROM_INCLUDED definidas. O sendStreamId deve ser maior que zero, e o receiveStreamId é ignorado. O sendStreamId pode ou não corresponder a uma conexão existente.
Um pacote pong deve ter a flag ECHO definida. O sendStreamId deve ser zero, e o receiveStreamId é o sendStreamId do ping. Antes da versão 0.9.18, o pacote pong não incluía qualquer payload que estava contido no ping.
A partir da versão 0.9.18, pings e pongs podem conter um payload. O payload no ping, até um máximo de 32 bytes, é retornado no pong.
O streaming pode ser configurado para desabilitar o envio de pongs com a configuração i2p.streaming.answerPings=false.
Problemas de 0-RTT
Como observado acima, diferentemente do TCP, o streaming permite entrega de dados 0-RTT ao agrupar dados no pacote SYN. Esta é a implementação preferida. ALÉM DISSO, o streaming permite que pacotes de dados adicionais (até o tamanho da janela inicial) sejam enviados após o SYN, antes que o SYN-ACK seja recebido. Estes pacotes terão um número de sequência diferente de zero, não terão a flag SYN definida e terão um sendStreamID zero.
Os receptores devem projetar para pacotes fora de ordem ou perdidos durante o handshake, incluindo a chegada de pacotes de dados antes do SYN. A implementação preferida é enfileirar, não descartar, pacotes não-SYN para um ID desconhecido, e recuperá-los da fila após o SYN ser recebido.
Na direção reversa, as coisas são similares. O destinatário da conexão (Bob) deve atrasar o envio do SYN-ACK (ACK DELAY) e aguardar um curto período por dados da aplicação. Ao receber dados da aplicação, coloque-os (até o tamanho máximo do pacote) no pacote SYN-ACK e envie-o. Pacotes de dados adicionais, até o tamanho da janela inicial, também podem ser enviados, sem aguardar um ACK do SYN-ACK.
O originador deve armazenar em buffer quaisquer pacotes de dados recebidos antes do SYN-ACK, da mesma forma que o tratamento fora de ordem após o handshake estar completo.
Testando Bibliotecas de Streaming
Para desenvolvedores testando bibliotecas de streaming novas ou modificadas, o Java I2P fornece um utilitário de teste local simples para testes reproduzíveis de condições reais de rede, incluindo latência, perda de pacotes e jitter de atraso. É um pequeno stub que implementa apenas um servidor I2CP para conexões locais.
Os desenvolvedores devem testar com uma ampla gama de parâmetros típicos, incluindo latência de 10ms a pelo menos 15s, e perda de pacotes de 0 a 10%. Adicionar jitter facilita o teste do tratamento fora de ordem.
Esta também é uma boa configuração para testar overflow de buffer (CHOKE/UNCHOKE) suspendendo manualmente uma das duas aplicações.
Do pacote fonte 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 sobre i2p.streaming.profile
Esta opção suporta dois valores; 1=bulk e 2=interactive. A opção fornece uma dica para a biblioteca de streaming e/ou router sobre o padrão de tráfego esperado.
“Bulk” significa otimizar para alta largura de banda, possivelmente às custas da latência. Este é o padrão. “Interactive” significa otimizar para baixa latência, possivelmente às custas da largura de banda ou eficiência. As estratégias de otimização, se houver, dependem da implementação e podem incluir mudanças fora do protocolo de streaming.
Através da versão 0.9.63 da API, o Java I2P retornaria um erro para qualquer valor diferente de 1 (bulk) e o tunnel falharia ao iniciar. A partir da versão 0.9.64 da API, o Java I2P ignora o valor. Através da versão 0.9.63 da API, o i2pd ignorava esta opção; ela foi implementada no i2pd a partir da versão 0.9.64 da API.
Embora o protocolo de streaming inclua um campo de flag para passar a configuração do perfil para a outra extremidade, isso não está implementado em nenhum router conhecido.
Compartilhamento de Bloco de Controle
A biblioteca de streaming suporta compartilhamento de “TCP” Control Block. Isso compartilha três parâmetros importantes da biblioteca de streaming (tamanho da janela, tempo de ida e volta, variância do tempo de ida e volta) entre conexões para o mesmo peer remoto. Isso é usado para compartilhamento “temporal” no momento de abertura/fechamento da conexão, não compartilhamento “ensemble” durante uma conexão (Veja RFC 2140 ). Há um compartilhamento separado por ConnectionManager (ou seja, por Destination local) para que não haja vazamento de informações para outros Destinations no mesmo router. Os dados de compartilhamento para um determinado peer expiram após alguns minutos. Os seguintes parâmetros de Compartilhamento de Control Block podem ser definidos 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.
Outros Parâmetros
Os seguintes parâmetros são padrões recomendados. Os padrões podem variar, dependendo da implementação:
- 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.
História
A biblioteca de streaming cresceu organicamente para o I2P - primeiro o mihi implementou a “mini biblioteca de streaming” como parte do I2PTunnel, que estava limitada a um tamanho de janela de 1 mensagem (exigindo um ACK antes de enviar a próxima), e depois foi refatorada em uma interface de streaming genérica (espelhando sockets TCP) e a implementação completa de streaming foi implantada com um protocolo de janela deslizante e otimizações para levar em conta o alto produto largura de banda x delay. Streams individuais podem ajustar o tamanho máximo de pacote e outras opções. O tamanho padrão de mensagem é selecionado para caber precisamente em duas mensagens I2NP tunnel de 1K, e é um compromisso razoável entre os custos de largura de banda de retransmitir mensagens perdidas, e a latência e overhead de múltiplas mensagens.
Interoperability and Best Practices
O comportamento da biblioteca de streaming tem um impacto profundo no desempenho a nível de aplicação e, como tal, é uma área importante para análise futura.
- 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.