Skip to content

Commit

Permalink
feat: add autonat support
Browse files Browse the repository at this point in the history
Implements the [autonat](https://github.com/libp2p/specs/blob/master/autonat/README.md)
spec to give us confidence in our external addresses and to open the
door to things like switching DHT server mode on automatically, hole
punching, v2 circuit relay etc.

Depends on:

- [ ] libp2p/js-libp2p-interfaces#269
  • Loading branch information
achingbrain committed Jul 19, 2022
1 parent 627b8bf commit d8a042a
Show file tree
Hide file tree
Showing 15 changed files with 1,512 additions and 45 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"prepublishOnly": "node scripts/update-version.js",
"build": "aegir build",
"generate": "run-s generate:proto:*",
"generate:proto:autonat": "protons ./src/autonat/pb/index.proto",
"generate:proto:circuit": "protons ./src/circuit/pb/index.proto",
"generate:proto:fetch": "protons ./src/fetch/pb/proto.proto",
"generate:proto:identify": "protons ./src/identify/pb/message.proto",
Expand Down
86 changes: 64 additions & 22 deletions src/address-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
import { Multiaddr } from '@multiformats/multiaddr'
import { peerIdFromString } from '@libp2p/peer-id'
import type { Components } from '@libp2p/components'
import type { PeerId } from '@libp2p/interface-peer-id'

export interface AddressManagerInit {
announceFilter?: AddressFilter
Expand All @@ -29,11 +30,31 @@ export interface AddressFilter {

const defaultAddressFilter = (addrs: Multiaddr[]): Multiaddr[] => addrs

interface ObservedAddressMetadata {
confident: boolean
}

function stripPeerId (ma: Multiaddr, peerId: PeerId) {
const observedPeerId = ma.getPeerId()

// strip our peer id if it has been passed
if (observedPeerId != null) {
const peerId = peerIdFromString(observedPeerId)

// use same encoding for comparison
if (peerId.equals(peerId)) {
ma = ma.decapsulate(new Multiaddr(`/p2p/${peerId.toString()}`))
}
}

return ma
}

export class DefaultAddressManager extends EventEmitter<AddressManagerEvents> {
private readonly components: Components
private readonly listen: Set<string>
private readonly announce: Set<string>
private readonly observed: Set<string>
private readonly observed: Map<string, ObservedAddressMetadata>
private readonly announceFilter: AddressFilter

/**
Expand All @@ -50,7 +71,7 @@ export class DefaultAddressManager extends EventEmitter<AddressManagerEvents> {
this.components = components
this.listen = new Set(listen.map(ma => ma.toString()))
this.announce = new Set(announce.map(ma => ma.toString()))
this.observed = new Set()
this.observed = new Map()
this.announceFilter = init.announceFilter ?? defaultAddressFilter
}

Expand All @@ -72,35 +93,51 @@ export class DefaultAddressManager extends EventEmitter<AddressManagerEvents> {
* Get observed multiaddrs
*/
getObservedAddrs (): Multiaddr[] {
return Array.from(this.observed).map((a) => new Multiaddr(a))
return Array.from(this.observed)
.map(([ma]) => new Multiaddr(ma))
}

/**
* Add peer observed addresses
*/
addObservedAddr (addr: string | Multiaddr): void {
let ma = new Multiaddr(addr)
const remotePeer = ma.getPeerId()

// strip our peer id if it has been passed
if (remotePeer != null) {
const remotePeerId = peerIdFromString(remotePeer)

// use same encoding for comparison
if (remotePeerId.equals(this.components.getPeerId())) {
ma = ma.decapsulate(new Multiaddr(`/p2p/${this.components.getPeerId().toString()}`))
}
}

const addrString = ma.toString()
addObservedAddr (addr: Multiaddr): void {
addr = stripPeerId(addr, this.components.getPeerId())
const addrString = addr.toString()

// do not trigger the change:addresses event if we already know about this address
if (this.observed.has(addrString)) {
return
}

this.observed.add(addrString)
this.dispatchEvent(new CustomEvent('change:addresses'))
this.observed.set(addrString, {
confident: false
})
}

confirmObservedAddr (addr: Multiaddr) {
addr = stripPeerId(addr, this.components.getPeerId())
const addrString = addr.toString()

const metadata = this.observed.get(addrString) ?? {
confident: false
}

const startingConfidence = metadata.confident

this.observed.set(addrString, {
confident: true
})

// only trigger the change:addresses event if our confidence in an address has changed
if (startingConfidence === false) {
this.dispatchEvent(new CustomEvent('change:addresses'))
}
}

removeObservedAddr (addr: Multiaddr) {
addr = stripPeerId(addr, this.components.getPeerId())
const addrString = addr.toString()

this.observed.delete(addrString)
}

getAddresses (): Multiaddr[] {
Expand All @@ -111,7 +148,12 @@ export class DefaultAddressManager extends EventEmitter<AddressManagerEvents> {
addrs = this.components.getTransportManager().getAddrs().map(ma => ma.toString())
}

addrs = addrs.concat(this.getObservedAddrs().map(ma => ma.toString()))
// add observed addresses we are confident in
addrs = addrs.concat(
Array.from(this.observed)
.filter(([ma, metadata]) => metadata.confident)
.map(([ma]) => ma)
)

// dedupe multiaddrs
const addrSet = new Set(addrs)
Expand Down
4 changes: 4 additions & 0 deletions src/autonat/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

export const PROTOCOL = '/libp2p/autonat/1.0.0'
export const PROTOCOL_VERSION = '1.0.0'
export const PROTOCOL_NAME = 'autonat'
Loading

0 comments on commit d8a042a

Please sign in to comment.