Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

Commit

Permalink
feat: switch to server mode automatically when addresses change (#473)
Browse files Browse the repository at this point in the history
If the node is in client mode, and the `self:peer:update` event is
emitted, and the event detail contains publicly routable addresses,
switch to server mode.

If the event is emitted and we are in server mode and the event detail
contains only private addresses, switch back to client mode.
  • Loading branch information
achingbrain authored May 9, 2023
1 parent 508023d commit de51cbe
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 1 deletion.
64 changes: 64 additions & 0 deletions src/dual-kad-dht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { EventEmitter, CustomEvent } from '@libp2p/interfaces/events'
import { logger } from '@libp2p/logger'
import drain from 'it-drain'
import merge from 'it-merge'
import isPrivate from 'private-ip'
import { DefaultKadDHT } from './kad-dht.js'
import { queryErrorEvent } from './query/events.js'
import type { DualKadDHT, KadDHT, KadDHTComponents, KadDHTInit, QueryEvent, QueryOptions } from './index.js'
import type { PeerId } from '@libp2p/interface-peer-id'
import type { PeerInfo } from '@libp2p/interface-peer-info'
import type { Multiaddr } from '@multiformats/multiaddr'
import type { CID } from 'multiformats/cid'

const log = logger('libp2p:kad-dht')
Expand Down Expand Up @@ -81,6 +83,44 @@ class DHTPeerRouting implements PeerRouting {
}
}

// see https://github.com/multiformats/multiaddr/blob/master/protocols.csv
const P2P_CIRCUIT_CODE = 290
const DNS4_CODE = 54
const DNS6_CODE = 55
const DNSADDR_CODE = 56
const IP4_CODE = 4
const IP6_CODE = 41

function multiaddrIsPublic (multiaddr: Multiaddr): boolean {
const tuples = multiaddr.stringTuples()

// p2p-circuit should not enable server mode
for (const tuple of tuples) {
if (tuple[0] === P2P_CIRCUIT_CODE) {
return false
}
}

// dns4 or dns6 or dnsaddr
if (tuples[0][0] === DNS4_CODE || tuples[0][0] === DNS6_CODE || tuples[0][0] === DNSADDR_CODE) {
log('%m is public %s', multiaddr, true)

return true
}

// ip4 or ip6
if (tuples[0][0] === IP4_CODE || tuples[0][0] === IP6_CODE) {
const result = isPrivate(`${tuples[0][1]}`)
const isPublic = result == null || !result

log('%m is public %s', multiaddr, isPublic)

return isPublic
}

return false
}

/**
* A DHT implementation modelled after Kademlia with S/Kademlia modifications.
* Original implementation in go: https://github.com/libp2p/go-libp2p-kad-dht.
Expand Down Expand Up @@ -123,6 +163,30 @@ export class DefaultDualKadDHT extends EventEmitter<PeerDiscoveryEvents> impleme
detail: evt.detail
}))
})

components.events.addEventListener('self:peer:update', (evt) => {
log('received update of self-peer info')
const hasPublicAddress = evt.detail.peer.addresses
.some(({ multiaddr }) => {
const isPublic = multiaddrIsPublic(multiaddr)

log('%m is public %s', multiaddr, isPublic)

return isPublic
})

this.getMode()
.then(async mode => {
if (hasPublicAddress && mode === 'client') {
await this.setMode('server')
} else if (mode === 'server' && !hasPublicAddress) {
await this.setMode('client')
}
})
.catch(err => {
log.error('error setting dht server mode', err)
})
})
}

readonly [Symbol.toStringTag] = '@libp2p/dual-kad-dht'
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { DefaultDualKadDHT } from './dual-kad-dht.js'
import type { ProvidersInit } from './providers.js'
import type { AddressManager } from '@libp2p/interface-address-manager'
import type { ConnectionManager } from '@libp2p/interface-connection-manager'
import type { Libp2pEvents } from '@libp2p/interface-libp2p'
import type { Metrics } from '@libp2p/interface-metrics'
import type { PeerId } from '@libp2p/interface-peer-id'
import type { PeerInfo } from '@libp2p/interface-peer-info'
import type { PeerStore } from '@libp2p/interface-peer-store'
import type { Registrar } from '@libp2p/interface-registrar'
import type { AbortOptions } from '@libp2p/interfaces'
import type { EventEmitter } from '@libp2p/interfaces/events'
import type { Datastore } from 'interface-datastore'
import type { CID } from 'multiformats/cid'
import type { ProgressOptions, ProgressEvent } from 'progress-events'
Expand Down Expand Up @@ -311,6 +313,7 @@ export interface KadDHTComponents {
metrics?: Metrics
connectionManager: ConnectionManager
datastore: Datastore
events: EventEmitter<Libp2pEvents>
}

export function kadDHT (init?: KadDHTInit): (components: KadDHTComponents) => DualKadDHT {
Expand Down
74 changes: 74 additions & 0 deletions test/enable-server-mode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* eslint-env mocha */
/* eslint max-nested-callbacks: ["error", 8] */

import { multiaddr } from '@multiformats/multiaddr'
import { expect } from 'aegir/chai'
import delay from 'delay'
import { TestDHT } from './utils/test-dht.js'

const testCases: Array<[string, string, string]> = [
['should enable server mode when public IP4 addresses are found', '/ip4/139.178.91.71/udp/4001/quic', 'server'],
['should enable server mode when public IP6 addresses are found', '/ip6/2604:1380:45e3:6e00::1/udp/4001/quic', 'server'],
['should enable server mode when DNS4 addresses are found', '/dns4/sv15.bootstrap.libp2p.io/tcp/443/wss/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', 'server'],
['should enable server mode when DNS6 addresses are found', '/dns6/sv15.bootstrap.libp2p.io/tcp/443/wss/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', 'server'],
['should enable server mode when DNSADDR addresses are found', '/dnsaddr/sv15.bootstrap.libp2p.io/tcp/443/wss/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', 'server'],
['should not enable server mode when private IP4 addresses are found', '/ip4/127.0.0.1/udp/4001/quic', 'client'],
['should not enable server mode when private IP6 addresses are found', '/ip6/::1/udp/4001/quic', 'client'],
['should not enable server mode when otherwise public circuit relay addresses are found', '/dns4/sv15.bootstrap.libp2p.io/tcp/443/wss/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN/p2p-circuit', 'client']
]

describe('enable server mode', () => {
let tdht: TestDHT

beforeEach(() => {
tdht = new TestDHT()
})

afterEach(async () => {
await tdht.teardown()
})

testCases.forEach(([name, addr, result]) => {
it(name, async function () {
const dht = await tdht.spawn()

await expect(dht.getMode()).to.eventually.equal('client')

dht.components.events.safeDispatchEvent('self:peer:update', {
detail: {
peer: {
addresses: [{
multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4001'),
isCertified: true
}, {
multiaddr: multiaddr('/ip6/::1/tcp/4001'),
isCertified: true
}, {
multiaddr: multiaddr(addr),
isCertified: true
}]
}
}
})

await delay(100)

await expect(dht.getMode()).to.eventually.equal(result, `did not change to "${result}" mode after updating with address ${addr}`)

dht.components.events.safeDispatchEvent('self:peer:update', {
detail: {
peer: {
addresses: [{
multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4001'),
isCertified: true
}]
}
}
})

await delay(100)

await expect(dht.getMode()).to.eventually.equal('client', `did not reset to client mode after updating with address ${addr}`)
})
})
})
3 changes: 2 additions & 1 deletion test/utils/test-dht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export class TestDHT {
// connectionGater: mockConnectionGater(),
addressManager: stubInterface<AddressManager>(),
peerStore: stubInterface<PeerStore>(),
connectionManager: stubInterface<ConnectionManager>()
connectionManager: stubInterface<ConnectionManager>(),
events
}
components.connectionManager = mockConnectionManager({
...components,
Expand Down

0 comments on commit de51cbe

Please sign in to comment.