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

Commit de51cbe

Browse files
authored
feat: switch to server mode automatically when addresses change (#473)
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.
1 parent 508023d commit de51cbe

File tree

4 files changed

+143
-1
lines changed

4 files changed

+143
-1
lines changed

src/dual-kad-dht.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import { EventEmitter, CustomEvent } from '@libp2p/interfaces/events'
66
import { logger } from '@libp2p/logger'
77
import drain from 'it-drain'
88
import merge from 'it-merge'
9+
import isPrivate from 'private-ip'
910
import { DefaultKadDHT } from './kad-dht.js'
1011
import { queryErrorEvent } from './query/events.js'
1112
import type { DualKadDHT, KadDHT, KadDHTComponents, KadDHTInit, QueryEvent, QueryOptions } from './index.js'
1213
import type { PeerId } from '@libp2p/interface-peer-id'
1314
import type { PeerInfo } from '@libp2p/interface-peer-info'
15+
import type { Multiaddr } from '@multiformats/multiaddr'
1416
import type { CID } from 'multiformats/cid'
1517

1618
const log = logger('libp2p:kad-dht')
@@ -81,6 +83,44 @@ class DHTPeerRouting implements PeerRouting {
8183
}
8284
}
8385

86+
// see https://github.com/multiformats/multiaddr/blob/master/protocols.csv
87+
const P2P_CIRCUIT_CODE = 290
88+
const DNS4_CODE = 54
89+
const DNS6_CODE = 55
90+
const DNSADDR_CODE = 56
91+
const IP4_CODE = 4
92+
const IP6_CODE = 41
93+
94+
function multiaddrIsPublic (multiaddr: Multiaddr): boolean {
95+
const tuples = multiaddr.stringTuples()
96+
97+
// p2p-circuit should not enable server mode
98+
for (const tuple of tuples) {
99+
if (tuple[0] === P2P_CIRCUIT_CODE) {
100+
return false
101+
}
102+
}
103+
104+
// dns4 or dns6 or dnsaddr
105+
if (tuples[0][0] === DNS4_CODE || tuples[0][0] === DNS6_CODE || tuples[0][0] === DNSADDR_CODE) {
106+
log('%m is public %s', multiaddr, true)
107+
108+
return true
109+
}
110+
111+
// ip4 or ip6
112+
if (tuples[0][0] === IP4_CODE || tuples[0][0] === IP6_CODE) {
113+
const result = isPrivate(`${tuples[0][1]}`)
114+
const isPublic = result == null || !result
115+
116+
log('%m is public %s', multiaddr, isPublic)
117+
118+
return isPublic
119+
}
120+
121+
return false
122+
}
123+
84124
/**
85125
* A DHT implementation modelled after Kademlia with S/Kademlia modifications.
86126
* Original implementation in go: https://github.com/libp2p/go-libp2p-kad-dht.
@@ -123,6 +163,30 @@ export class DefaultDualKadDHT extends EventEmitter<PeerDiscoveryEvents> impleme
123163
detail: evt.detail
124164
}))
125165
})
166+
167+
components.events.addEventListener('self:peer:update', (evt) => {
168+
log('received update of self-peer info')
169+
const hasPublicAddress = evt.detail.peer.addresses
170+
.some(({ multiaddr }) => {
171+
const isPublic = multiaddrIsPublic(multiaddr)
172+
173+
log('%m is public %s', multiaddr, isPublic)
174+
175+
return isPublic
176+
})
177+
178+
this.getMode()
179+
.then(async mode => {
180+
if (hasPublicAddress && mode === 'client') {
181+
await this.setMode('server')
182+
} else if (mode === 'server' && !hasPublicAddress) {
183+
await this.setMode('client')
184+
}
185+
})
186+
.catch(err => {
187+
log.error('error setting dht server mode', err)
188+
})
189+
})
126190
}
127191

128192
readonly [Symbol.toStringTag] = '@libp2p/dual-kad-dht'

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { DefaultDualKadDHT } from './dual-kad-dht.js'
22
import type { ProvidersInit } from './providers.js'
33
import type { AddressManager } from '@libp2p/interface-address-manager'
44
import type { ConnectionManager } from '@libp2p/interface-connection-manager'
5+
import type { Libp2pEvents } from '@libp2p/interface-libp2p'
56
import type { Metrics } from '@libp2p/interface-metrics'
67
import type { PeerId } from '@libp2p/interface-peer-id'
78
import type { PeerInfo } from '@libp2p/interface-peer-info'
89
import type { PeerStore } from '@libp2p/interface-peer-store'
910
import type { Registrar } from '@libp2p/interface-registrar'
1011
import type { AbortOptions } from '@libp2p/interfaces'
12+
import type { EventEmitter } from '@libp2p/interfaces/events'
1113
import type { Datastore } from 'interface-datastore'
1214
import type { CID } from 'multiformats/cid'
1315
import type { ProgressOptions, ProgressEvent } from 'progress-events'
@@ -311,6 +313,7 @@ export interface KadDHTComponents {
311313
metrics?: Metrics
312314
connectionManager: ConnectionManager
313315
datastore: Datastore
316+
events: EventEmitter<Libp2pEvents>
314317
}
315318

316319
export function kadDHT (init?: KadDHTInit): (components: KadDHTComponents) => DualKadDHT {

test/enable-server-mode.spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/* eslint-env mocha */
2+
/* eslint max-nested-callbacks: ["error", 8] */
3+
4+
import { multiaddr } from '@multiformats/multiaddr'
5+
import { expect } from 'aegir/chai'
6+
import delay from 'delay'
7+
import { TestDHT } from './utils/test-dht.js'
8+
9+
const testCases: Array<[string, string, string]> = [
10+
['should enable server mode when public IP4 addresses are found', '/ip4/139.178.91.71/udp/4001/quic', 'server'],
11+
['should enable server mode when public IP6 addresses are found', '/ip6/2604:1380:45e3:6e00::1/udp/4001/quic', 'server'],
12+
['should enable server mode when DNS4 addresses are found', '/dns4/sv15.bootstrap.libp2p.io/tcp/443/wss/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', 'server'],
13+
['should enable server mode when DNS6 addresses are found', '/dns6/sv15.bootstrap.libp2p.io/tcp/443/wss/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', 'server'],
14+
['should enable server mode when DNSADDR addresses are found', '/dnsaddr/sv15.bootstrap.libp2p.io/tcp/443/wss/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', 'server'],
15+
['should not enable server mode when private IP4 addresses are found', '/ip4/127.0.0.1/udp/4001/quic', 'client'],
16+
['should not enable server mode when private IP6 addresses are found', '/ip6/::1/udp/4001/quic', 'client'],
17+
['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']
18+
]
19+
20+
describe('enable server mode', () => {
21+
let tdht: TestDHT
22+
23+
beforeEach(() => {
24+
tdht = new TestDHT()
25+
})
26+
27+
afterEach(async () => {
28+
await tdht.teardown()
29+
})
30+
31+
testCases.forEach(([name, addr, result]) => {
32+
it(name, async function () {
33+
const dht = await tdht.spawn()
34+
35+
await expect(dht.getMode()).to.eventually.equal('client')
36+
37+
dht.components.events.safeDispatchEvent('self:peer:update', {
38+
detail: {
39+
peer: {
40+
addresses: [{
41+
multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4001'),
42+
isCertified: true
43+
}, {
44+
multiaddr: multiaddr('/ip6/::1/tcp/4001'),
45+
isCertified: true
46+
}, {
47+
multiaddr: multiaddr(addr),
48+
isCertified: true
49+
}]
50+
}
51+
}
52+
})
53+
54+
await delay(100)
55+
56+
await expect(dht.getMode()).to.eventually.equal(result, `did not change to "${result}" mode after updating with address ${addr}`)
57+
58+
dht.components.events.safeDispatchEvent('self:peer:update', {
59+
detail: {
60+
peer: {
61+
addresses: [{
62+
multiaddr: multiaddr('/ip4/127.0.0.1/tcp/4001'),
63+
isCertified: true
64+
}]
65+
}
66+
}
67+
})
68+
69+
await delay(100)
70+
71+
await expect(dht.getMode()).to.eventually.equal('client', `did not reset to client mode after updating with address ${addr}`)
72+
})
73+
})
74+
})

test/utils/test-dht.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ export class TestDHT {
3737
// connectionGater: mockConnectionGater(),
3838
addressManager: stubInterface<AddressManager>(),
3939
peerStore: stubInterface<PeerStore>(),
40-
connectionManager: stubInterface<ConnectionManager>()
40+
connectionManager: stubInterface<ConnectionManager>(),
41+
events
4142
}
4243
components.connectionManager = mockConnectionManager({
4344
...components,

0 commit comments

Comments
 (0)