Na série IPFS com o Nabu, o host já sabe escutar. Este post é o que acontece quando o processo chama start: achar outros pares e descobrir se o resto da rede consegue discar de volta.
A proposta
Um nó novo não conhece ninguém. O caderno de endereços começa vazio. Mesmo depois de conhecer alguém, o endereço em que ele escuta pode ser inútil para o mundo. 0.0.0.0 é um bind local, não um destino. Um NAT (Network Address Translation) residencial reescreve o IP (Internet Protocol) e a porta de saída. Os pares de fora precisam de um endereço que de fato atenda. Enquanto isso não existe, o nó só fala com quem ele mesmo disca.
A proposta é uma sequência. Primeiro, alguns pares já conhecidos enchem a tabela. Depois, o nó descobre o próprio endereço externo. Se continuar inalcançável, pede emprestado o caminho de um par que é público e tenta furar o NAT.
A Demonstração
A entrada tem quatro movimentos.
O bootstrap é a lista de pares já conhecidos, em geral os nós públicos do libp2p. Um endereço /dnsaddr/bootstrap.libp2p.io/... ainda não é discável. DNS (Domain Name System) é o sistema que traduz nomes em endereços. O componente dnsaddr consulta o registro TXT (um texto publicado no DNS) de _dnsaddr. mais o domínio e troca aquilo por multiaddrs concretos, com IP, transporte e PeerId. O nó disca esses pares e pede à Kademlia os vizinhos mais próximos. A tabela de roteamento deixa de estar vazia. DHT (Distributed Hash Table) é essa tabela, repartida entre os pares.
O mDNS (multicast DNS) procura pares na rede local, sem passar pela internet. O anúncio usa o nome de serviço _ipfs-discovery._udp.local.. Quem estiver na mesma rede e escutar esse nome responde com o PeerId e os multiaddrs.
O alcance responde à pergunta "alguém de fora consegue me discar?". O identify já entrega o observed address, o endereço que o outro par viu. O AutoNAT pede a um par que tente discar de volta num candidato. Se a discagem volta, o endereço é público. UPnP (Universal Plug and Play) e NAT-PMP (NAT Port Mapping Protocol) pedem ao roteador de casa um mapeamento da porta de swarm para uma porta externa. Swarm é a lista de portas em que o nó aceita conexões. O endereço mapeado entra na lista de candidatos.
Quando nada disso torna o nó discável, o circuit relay empresta o caminho de um par público. O multiaddr ganha o componente /p2p-circuit: primeiro o endereço do relay, depois o PeerId de destino. O DCUtR (Direct Connection Upgrade through Relay) usa essa conexão emprestada para combinar um furo simultâneo. Os dois lados discam um ao outro ao mesmo tempo, e o NAT de cada um aceita o pacote porque acabou de ver saída para aquele destino. Se der certo, a conversa deixa o relay.
flowchart LR
boot[bootstrap] --> dht[routingTable]
mdns[mdns] --> dht
observe[identifyObservedAddr] --> nat[autonatDialback]
upnp[upnpOrNatPmp] --> nat
nat --> relay[circuitRelayIfPrivate]
relay --> dcutr[directUpgrade]
No Nabu
EmbeddedIpfs.start faz a sequência, em src/main/java/org/peergos/EmbeddedIpfs.java. Registra um shutdown hook que chama stop. Sobe o host com node.start(). Registra o identify já com a lista announce convertida em multiaddrs. Se bootstrap está vazio, o log avisa que o nó não entra na DHT global. dht.bootstrapRoutingTable recebe um filtro que descarta qualquer endereço cuja string contém /wss/ (WebSocket seguro, um transporte que este host não registra). Em seguida, se o chamador não pediu bootstrap assíncrono, dht.bootstrap caminha a Kademlia. startBootstrapThread repete o trabalho.
O mDNS usa MDnsDiscovery com o serviço _ipfs-discovery._udp.local. e intervalo de 60 segundos. No handler, o PeerId do anúncio passa por um recorte que o código comenta como ajuste do valor recebido. Se não somos nós mesmos, o nó abre um stream Kademlia com dial, pede closerPeers a partir do nosso PeerId e grava os endereços do anúncio no caderno com addAddrs.
DnsAddr.resolve, em src/main/java/org/peergos/protocol/dnsaddr/DnsAddr.java, só age quando a string começa com /dnsaddr/. O restante da lista de bootstrap passa intacto. O domínio vai para uma consulta TXT em _dnsaddr. mais o nome. O sufixo do multiaddr original é reaplicado, e /ipfs/ também é aceito como /p2p/. bootstrapRoutingTable faz essa resolução em paralelo, grava cada endereço resolvido no caderno e disca com timeout de 5 segundos por futuro. O retorno é a contagem de discagens que completaram.
O redirecionamento de porta é opt-in. enablePortForwarding precisa ser chamado antes de start. O processo Nabu faz isso quando o config traz Addresses.UPnP verdadeiro. startPortForwarders só corre se a flag está ligada e já existem swarmAddresses e ReachabilityManager. PortForwarder.forSwarm abre um mapeador por porta distinta de TCP (Transmission Control Protocol) ou UDP (User Datagram Protocol), com vida de 3600 segundos e nova tentativa a cada 60 segundos se o roteador falhar. O multiaddr externo volta para reachability.addLocalCandidate e também entra no caderno do próprio nó.
O AutoNAT v2 sobe dentro de HostBuilder.build quando o protocolo está na lista, e EmbeddedIpfs.build inclui new AutonatProtocol.Binding() e AutonatV2.protocols(). O cliente monta candidatos em externalAddressCandidates. Entram os endereços que pares já observaram, o IP observado casado com a porta TCP de escuta, os mapeamentos que o ReachabilityManager guardou como candidatos locais, e os endereços de escuta que PeerAddresses.isPublic aceita e que não contêm p2p-circuit. O comentário no código registra uma assimetria. A discagem QUIC reaproveita a porta de escuta, então o observed address do QUIC já é o mapeamento real. A discagem TCP ainda sai de porta efêmera, e o candidato TCP é uma previsão: IP observado mais a porta em que o nó escuta.
Relay e DCUtR também entram em EmbeddedIpfs.build, pelas chamadas enableRelay(Relay.candidateRelaySource(dht)) e enableDcutr(). enableRelay cria um GatingRelayManager.reachabilityGated com limite de 5 reservas simultâneas. Esse manager só concede reserva enquanto o próprio nó está publicamente alcançável, para um nó atrás de NAT não se anunciar como relay. A reserva padrão cabe 1 MiB e dura 120 segundos. O comentário explica o tamanho: o padrão da biblioteca, 4096 bytes, mal completa multistream, Noise e yamux, e não sobra espaço para o DCUtR. O AutoRelay, ainda em HostBuilder.build, escuta o ReachabilityManager. No estado PRIVATE pede duas reservas e chama ensureEnoughCurrentRelays. No estado PUBLIC zera o pedido.
O DCUtR abre o stream /libp2p/dcutr no lado que recebeu a conexão, e só se o endereço remoto contém p2p-circuit. O lado de dentro da conexão relayed envia CONNECT com os endereços discáveis, mede a ida e volta até a resposta e, meio RTT (round-trip time, o tempo de ida e volta) depois, disca. O outro lado disca ao receber SYNC. O resultado do furo volta para ReachabilityManager.recordHolePunchOutcome.
O que fica de fora: o README ainda lista circuit relay, DCUtR e AutoRelay como trabalho futuro. O build do host já liga os três. Não há relay v1.
Nesta série
O anterior é Transportes, criptografia e multiplexação. O próximo é Bitswap, o pedido de um bloco.
Top comments (0)