This issue distills findings from a systematic cross-implementation evaluation of AutoNAT v2, conducted by ProbeLab. The final report of the investigation is available in the following link.
Severity:
Low — this is an API completeness / developer ergonomics gap. No runtime crash or incorrect reachability verdict; the information is tracked internally but not surfaced to applications.
Description:
What I did. Registered the autoNATv2() service on a js-libp2p Node.js node and attempted to subscribe to a reachability event from the service so the application could react to per-address REACHABLE / UNREACHABLE verdicts.
What happened. No event fires. @libp2p/autonat-v2 tracks per-address outcomes internally in a dialResults Map but does not emit any event. No dispatchEvent / safeDispatchEvent calls exist anywhere in packages/protocol-autonat-v2/.
What I expected. Per-address reachability changes surfaced as events on the service, consistent with the rest of js-libp2p, where TypedEventEmitter is used extensively:
Libp2p emits peer:connect, peer:disconnect, peer:discovery, connection:open/close.
- PeerStore emits
self:peer:update and peer:update.
- Identify emits
peer:identify.
- Circuit Relay v2 emits
relay:created-reservation, relay:removed, relay:found-enough-relays.
AutoNAT v2 is the outlier with no event surface. Concrete consequences: applications cannot directly observe reachability changes (they can only infer from downstream PeerStore updates, and the per-address REACHABLE/UNREACHABLE granularity is lost); for nodes behind symmetric NAT running AutoNAT v2 over QUIC, every dial-back fails, the address is silently removed via removeObservedAddr() after 8 failures, and the application sees the address disappear without ever receiving an explicit UNREACHABLE signal.
AutoNAT v2 already influences downstream behavior indirectly: observed addresses are added by Identify as unverified, and only elevated to the PeerStore entry after AutoNAT v2 calls confirmObservedAddr() on the AddressManager (client.ts:592). DHT mode switching (via self:peer:update) therefore already consumes an AutoNAT-verified address list. What is missing is only the API-level event surface for applications.
Proposal
Expose v2 probe results via TypedEventEmitter, consistent with the rest of the library:
- Per-address event:
reachability:changed { addr, state, confidence } where state is REACHABLE | UNREACHABLE | UNKNOWN.
- Optional aggregated host-level event equivalent to go-libp2p's
EvtLocalReachabilityChanged, derived from a per-address reducer.
This issue distills findings from a systematic cross-implementation evaluation of AutoNAT v2, conducted by ProbeLab. The final report of the investigation is available in the following link.
Severity:
Low — this is an API completeness / developer ergonomics gap. No runtime crash or incorrect reachability verdict; the information is tracked internally but not surfaced to applications.
Description:
What I did. Registered the
autoNATv2()service on a js-libp2p Node.js node and attempted to subscribe to a reachability event from the service so the application could react to per-address REACHABLE / UNREACHABLE verdicts.What happened. No event fires.
@libp2p/autonat-v2tracks per-address outcomes internally in adialResultsMap but does not emit any event. NodispatchEvent/safeDispatchEventcalls exist anywhere inpackages/protocol-autonat-v2/.What I expected. Per-address reachability changes surfaced as events on the service, consistent with the rest of js-libp2p, where
TypedEventEmitteris used extensively:Libp2pemitspeer:connect,peer:disconnect,peer:discovery,connection:open/close.self:peer:updateandpeer:update.peer:identify.relay:created-reservation,relay:removed,relay:found-enough-relays.AutoNAT v2 is the outlier with no event surface. Concrete consequences: applications cannot directly observe reachability changes (they can only infer from downstream PeerStore updates, and the per-address REACHABLE/UNREACHABLE granularity is lost); for nodes behind symmetric NAT running AutoNAT v2 over QUIC, every dial-back fails, the address is silently removed via
removeObservedAddr()after 8 failures, and the application sees the address disappear without ever receiving an explicit UNREACHABLE signal.AutoNAT v2 already influences downstream behavior indirectly: observed addresses are added by Identify as unverified, and only elevated to the PeerStore entry after AutoNAT v2 calls
confirmObservedAddr()on the AddressManager (client.ts:592). DHT mode switching (viaself:peer:update) therefore already consumes an AutoNAT-verified address list. What is missing is only the API-level event surface for applications.Proposal
Expose v2 probe results via
TypedEventEmitter, consistent with the rest of the library:reachability:changed { addr, state, confidence }where state isREACHABLE | UNREACHABLE | UNKNOWN.EvtLocalReachabilityChanged, derived from a per-address reducer.