Na série IPFS com o Nabu, o par já tem PeerId (o identificador estável) e multiaddr (o endereço em camadas). Este post é a conexão em si: o que atravessa o fio antes de qualquer bloco.
A proposta
Bitswap, Kademlia, identify e HTTP (Hypertext Transfer Protocol) entre pares precisam falar ao mesmo tempo com o mesmo par. Abrir uma conexão TCP (Transmission Control Protocol) por protocolo repetiria o handshake e atravessaria o NAT (Network Address Translation, a tradução de portas do roteador) várias vezes. O fio também é público. Os bytes precisam de sigilo e de autenticação: no fim do handshake, cada lado sabe o PeerId do outro.
A proposta da pilha libp2p é empilhar três trabalhos distintos. Um carrega bytes. Outro cifra e autentica. O terceiro reparte a conexão cifrada em conversas paralelas.
A Demonstração
O transporte carrega bytes. TCP é um fluxo confiável: o que se perde é retransmitido, e a ordem se preserva. UDP (User Datagram Protocol) entrega datagramas, sem essa garantia. QUIC, escrito quic-v1 no multiaddr, anda sobre UDP, traz criptografia própria e já multiplexa várias conversas no mesmo fluxo. O nome QUIC deixou de ser sigla. Nasceu como Quick UDP Internet Connections. O Nabu escuta TCP e QUIC quando o endereço de swarm pede os dois. Swarm, aqui, é a lista de endereços em que o nó aceita conexões de outros pares. Um endereço típico de TCP é /ip4/0.0.0.0/tcp/4001. O de QUIC é /ip4/0.0.0.0/udp/4001/quic-v1.
O canal seguro autentica o PeerId. No TCP, isso é uma camada à parte, porque o TCP sozinho não cifra. O Noise XX faz o handshake, deriva chaves de sessão e amarra a chave pública do par. XX é o padrão do handshake: as duas identidades são transmitidas e cifradas no meio da troca. No QUIC a criptografia já vem no transporte. O host registra ECDSA (Elliptic Curve Digital Signature Algorithm) como o provedor que o QUIC do jvm-libp2p usa nessa pilha.
O muxer reparte uma conexão TCP cifrada em vários streams. Cada protocolo abre o seu, como várias ligações dentro de um único cano. Yamux é o muxer principal. Mplex continua registrado para falar com pares que só oferecem esse. Um stream é barato em relação a uma conexão nova. É por isso que identify, Bitswap e Kademlia convivem depois de um único handshake. A abertura de cada stream negocia um identificador de protocolo, uma string como /ipfs/bitswap/1.2.0. Os dois lados precisam anunciar a mesma string.
flowchart TB
app[bitswapAndDhtAndHttp] --> mux[yamuxOrMplex]
mux --> secure[noiseOnTcp]
secure --> tcp[tcp]
app --> quic[quicV1]
No Nabu
HostBuilder.build monta a pilha em src/main/java/org/peergos/HostBuilder.java. Se ninguém passou muxers, entram yamux e mplex, nessa ordem:
if (muxers.isEmpty())
muxers.addAll(List.of(StreamMuxerProtocol.getYamux(MAX_BUFFERED_CONNECTION_WRITES),
StreamMuxerProtocol.getMplex()));
MAX_BUFFERED_CONNECTION_WRITES vale 128 MiB. O comentário no código explica o número. O yamux limita o buffer de escrita por conexão, não por stream, e um stream que estoura o buffer é resetado em vez de esperar. O padrão de 10 MiB cabia mal uma resposta HTTP de 4 MB. Três respostas grandes na mesma conexão derrubavam umas às outras: numa medida do próprio código, 1053 de 15740 respostas sobreviviam, contra 100% com duas. Stream.writeAndFlush não devolve um future, então o estouro era invisível para quem chamava. 128 MiB é um teto, não uma alocação. A constante fica acima de HttpProtocol.MAX_BODY_SIZE vezes o número de respostas grandes esperadas em voo para um único par.
Dentro do builder do jvm-libp2p, o método olha a lista de multiaddrs já convertidos. QUIC só é registrado se algum endereço tem o componente quic-v1. TCP entra sempre, com TcpTransport::new. O canal seguro acrescentado é NoiseXXSecureChannel. A linha do TLS (Transport Layer Security) está comentada:
if (toListen.stream().anyMatch(a -> a.has(Protocol.QUICV1)))
b.getSecureTransports().add(QuicTransport::ECDSA);
b.getTransports().add(TcpTransport::new);
b.getSecureChannels().add(NoiseXXSecureChannel::new);
// b.getSecureChannels().add(TlsSecureChannel::new);
O README ainda cita TLS com negociação antecipada de muxer via ALPN (Application-Layer Protocol Negotiation, a escolha do próximo protocolo durante o handshake TLS). O host que build devolve hoje cifra TCP com Noise. QUIC cifra por conta própria, pelo transporte registrado acima.
Depois da cifra, o método percorre os ProtocolBinding que EmbeddedIpfs.build juntou: ping, AutoNAT, Bitswap, Kademlia, e o HTTP se houver handler. Cada binding entra em b.getProtocols(). Se o protocolo implementa AddressBookConsumer, recebe o caderno. Se implementa ConnectionHandler, entra na lista de handlers da conexão. Em seguida o host escuta cada string de listenAddrs.
EmbeddedIpfs.build ainda calcula duas flags antes de criar a Kademlia: quicEnabled se algum swarm addr tem QUICV1, tcpEnabled se algum tem TCP. A Kademlia guarda as flags. hasTransportOverlap aceita um endereço se ele tem TCP e este nó fala TCP, ou se tem QUIC sem o componente WebTransport e este nó fala QUIC. O conjunto ainda recusa endereço que contém p2p-circuit. O circuit relay entra por outro caminho, no post seguinte.
O que fica de fora: WebTransport e WebSockets não são registrados. O bootstrap chega a descartar endereços que contêm /wss/. Não há configuração para desligar o Noise e ligar o TLS. Quem quiser só TCP, ou só QUIC, escolhe isso na lista de multiaddrs de escuta.
Nesta série
O anterior é Identidade e endereços. O próximo é Entrar na rede.
Top comments (0)