Skip to content

Commit

Permalink
feat: add dns mappings to address manager (#2818)
Browse files Browse the repository at this point in the history
To allow dynamically adding new multiaddrs to the listening-on list
allow adding runtime mappings of ip to domain names.

This allows things like the auto-tls component to affect the returned
list of multiaddrs to include newly minted domain names.
  • Loading branch information
achingbrain authored Nov 12, 2024
1 parent 7626b22 commit 7dcabb8
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 110 deletions.
13 changes: 13 additions & 0 deletions packages/interface-internal/src/address-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,17 @@ export interface AddressManager {
* Get the current node's addresses
*/
getAddresses(): Multiaddr[]

/**
* Adds a mapping between one or more IP addresses and a domain name - when
* `getAddresses` is invoked, where the IP addresses are present in a
* multiaddr, an additional multiaddr will be added with `ip4` and `ip6`
* tuples replaced with `dns4` and `dns6 ones respectively.
*/
addDNSMapping(domain: string, ipAddresses: string[]): void

/**
* Remove a mapping previously added with `addDNSMapping`.
*/
removeDNSMapping(domain: string): void
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { peerIdFromString } from '@libp2p/peer-id'
import { multiaddr } from '@multiformats/multiaddr'
import { debounce } from './utils.js'
import { debounce } from '@libp2p/utils/debounce'
import { multiaddr, protocols } from '@multiformats/multiaddr'
import type { ComponentLogger, Libp2pEvents, Logger, TypedEventTarget, PeerId, PeerStore } from '@libp2p/interface'
import type { TransportManager } from '@libp2p/interface-internal'
import type { AddressManager as AddressManagerInterface, TransportManager } from '@libp2p/interface-internal'
import type { Multiaddr } from '@multiformats/multiaddr'

export interface AddressManagerInit {
Expand All @@ -28,7 +28,7 @@ export interface AddressManagerInit {
noAnnounce?: string[]
}

export interface DefaultAddressManagerComponents {
export interface AddressManagerComponents {
peerId: PeerId
transportManager: TransportManager
peerStore: PeerStore
Expand Down Expand Up @@ -69,29 +69,38 @@ function stripPeerId (ma: Multiaddr, peerId: PeerId): Multiaddr {
return ma
}

export class DefaultAddressManager {
const CODEC_IP4 = 0x04
const CODEC_IP6 = 0x29
const CODEC_DNS4 = 0x36
const CODEC_DNS6 = 0x37

export class AddressManager implements AddressManagerInterface {
private readonly log: Logger
private readonly components: DefaultAddressManagerComponents
private readonly components: AddressManagerComponents
// this is an array to allow for duplicates, e.g. multiples of `/ip4/0.0.0.0/tcp/0`
private readonly listen: string[]
private readonly announce: Set<string>
private readonly observed: Map<string, ObservedAddressMetadata>
private readonly announceFilter: AddressFilter
private readonly ipDomainMappings: Map<string, string>

private readonly where: Error

/**
* Responsible for managing the peer addresses.
* Peers can specify their listen and announce addresses.
* The listen addresses will be used by the libp2p transports to listen for new connections,
* while the announce addresses will be used for the peer addresses' to other peers in the network.
*/
constructor (components: DefaultAddressManagerComponents, init: AddressManagerInit = {}) {
constructor (components: AddressManagerComponents, init: AddressManagerInit = {}) {
const { listen = [], announce = [] } = init

this.components = components
this.log = components.logger.forComponent('libp2p:address-manager')
this.listen = listen.map(ma => ma.toString())
this.announce = new Set(announce.map(ma => ma.toString()))
this.observed = new Map()
this.ipDomainMappings = new Map()
this.announceFilter = init.announceFilter ?? defaultAddressFilter

// this method gets called repeatedly on startup when transports start listening so
Expand All @@ -106,6 +115,8 @@ export class DefaultAddressManager {
components.events.addEventListener('transport:close', () => {
this._updatePeerStoreAddresses()
})

this.where = new Error('where')
}

readonly [Symbol.toStringTag] = '@libp2p/address-manager'
Expand Down Expand Up @@ -200,37 +211,109 @@ export class DefaultAddressManager {
}

getAddresses (): Multiaddr[] {
let addrs = this.getAnnounceAddrs().map(ma => ma.toString())
let multiaddrs = this.getAnnounceAddrs()

if (addrs.length === 0) {
if (multiaddrs.length === 0) {
// no configured announce addrs, add configured listen addresses
addrs = this.components.transportManager.getAddrs().map(ma => ma.toString())
multiaddrs = this.components.transportManager.getAddrs()
}

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

const mappedMultiaddrs: Multiaddr[] = []

// add ip->domain mappings
for (const ma of multiaddrs) {
const tuples = [...ma.stringTuples()]
let mappedIp = false

for (const [ip, domain] of this.ipDomainMappings.entries()) {
for (let i = 0; i < tuples.length; i++) {
if (tuples[i][1] !== ip) {
continue
}

if (tuples[i][0] === CODEC_IP4) {
tuples[i][0] = CODEC_DNS4
tuples[i][1] = domain
mappedIp = true
}

if (tuples[i][0] === CODEC_IP6) {
tuples[i][0] = CODEC_DNS6
tuples[i][1] = domain
mappedIp = true
}
}
}

if (mappedIp) {
mappedMultiaddrs.push(
multiaddr(`/${
tuples.map(tuple => {
return [
protocols(tuple[0]).name,
tuple[1]
].join('/')
}).join('/')
}`)
)
}
}

multiaddrs = multiaddrs.concat(mappedMultiaddrs)

// dedupe multiaddrs
const addrSet = new Set(addrs)
const addrSet = new Set<string>()
multiaddrs = multiaddrs.filter(ma => {
const maStr = ma.toString()

if (addrSet.has(maStr)) {
return false
}

addrSet.add(maStr)

return true
})

// Create advertising list
return this.announceFilter(Array.from(addrSet)
.map(str => multiaddr(str)))
.map(ma => {
// do not append our peer id to a path multiaddr as it will become invalid
if (ma.protos().pop()?.path === true) {
return ma
}
return this.announceFilter(
Array.from(addrSet)
.map(str => {
const ma = multiaddr(str)

// do not append our peer id to a path multiaddr as it will become invalid
if (ma.protos().pop()?.path === true) {
return ma
}

if (ma.getPeerId() === this.components.peerId.toString()) {
return ma
}

return ma.encapsulate(`/p2p/${this.components.peerId.toString()}`)
})
)
}

if (ma.getPeerId() === this.components.peerId.toString()) {
return ma
}
addDNSMapping (domain: string, addresses: string[]): void {
addresses.forEach(ip => {
this.ipDomainMappings.set(ip, domain)
})
}

return ma.encapsulate(`/p2p/${this.components.peerId.toString()}`)
})
removeDNSMapping (domain: string): void {
for (const [key, value] of this.ipDomainMappings.entries()) {
if (value === domain) {
this.ipDomainMappings.delete(key)
}
}
}
}
43 changes: 0 additions & 43 deletions packages/libp2p/src/address-manager/README.md

This file was deleted.

13 changes: 0 additions & 13 deletions packages/libp2p/src/address-manager/utils.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/libp2p/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { generateKeyPair } from '@libp2p/crypto/keys'
import { peerIdFromPrivateKey } from '@libp2p/peer-id'
import { validateConfig } from './config.js'
import { Libp2p as Libp2pClass } from './libp2p.js'
import type { AddressManagerInit, AddressFilter } from './address-manager/index.js'
import type { AddressManagerInit, AddressFilter } from './address-manager.js'
import type { Components } from './components.js'
import type { ConnectionManagerInit } from './connection-manager/index.js'
import type { ConnectionMonitorInit } from './connection-monitor.js'
Expand Down
4 changes: 2 additions & 2 deletions packages/libp2p/src/libp2p.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { isMultiaddr, type Multiaddr } from '@multiformats/multiaddr'
import { MemoryDatastore } from 'datastore-core/memory'
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { DefaultAddressManager } from './address-manager/index.js'
import { AddressManager } from './address-manager.js'
import { checkServiceDependencies, defaultComponents } from './components.js'
import { connectionGater } from './config/connection-gater.js'
import { DefaultConnectionManager } from './connection-manager/index.js'
Expand Down Expand Up @@ -129,7 +129,7 @@ export class Libp2p<T extends ServiceMap = ServiceMap> extends TypedEventEmitter
this.configureComponent('registrar', new DefaultRegistrar(this.components))

// Addresses {listen, announce, noAnnounce}
this.configureComponent('addressManager', new DefaultAddressManager(this.components, init.addresses))
this.configureComponent('addressManager', new AddressManager(this.components, init.addresses))

// Peer routers
const peerRouters: PeerRouting[] = (init.peerRouters ?? []).map((fn, index) => this.configureComponent(`peer-router-${index}`, fn(this.components)))
Expand Down
Loading

0 comments on commit 7dcabb8

Please sign in to comment.