dht-rpc 5.0.6 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/index.js +14 -6
- package/lib/io.js +37 -29
- package/package.json +5 -4
- package/test.js +3 -22
package/README.md
CHANGED
|
@@ -6,10 +6,6 @@ Make RPC calls over a [Kademlia](https://pdos.csail.mit.edu/~petar/papers/maymou
|
|
|
6
6
|
npm install dht-rpc
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
## NOTE: v5
|
|
10
|
-
|
|
11
|
-
Note that the latest release is v5. To see the v4 documentation/code go to https://github.com/mafintosh/dht-rpc/tree/v4
|
|
12
|
-
|
|
13
9
|
## Key Features
|
|
14
10
|
|
|
15
11
|
* Remote IP / firewall detection
|
|
@@ -116,12 +112,12 @@ Options include:
|
|
|
116
112
|
{
|
|
117
113
|
// A list of bootstrap nodes
|
|
118
114
|
bootstrap: [ 'bootstrap-node.com:24242', ... ],
|
|
119
|
-
// Optionally pass in your own UDP socket to use.
|
|
120
|
-
socket: udpSocket,
|
|
121
115
|
// Optionally pass in array of { host, port } to add to the routing table if you know any peers
|
|
122
116
|
nodes: [{ host, port }, ...],
|
|
123
117
|
// Optionally pass a port you prefer to bind to instead of a random one
|
|
124
|
-
|
|
118
|
+
port: 0,
|
|
119
|
+
// Optionally pass a UDX instance on which sockets will be created.
|
|
120
|
+
udx,
|
|
125
121
|
// dht-rpc will automatically detect if you are firewalled. If you know that you are not set this to false
|
|
126
122
|
firewalled: true
|
|
127
123
|
}
|
|
@@ -133,7 +129,7 @@ For the vast majority of use-cases you should always use adaptive mode to ensure
|
|
|
133
129
|
|
|
134
130
|
Your DHT routing id is `hash(publicIp + publicPort)` and will be autoconfigured internally.
|
|
135
131
|
|
|
136
|
-
#### `const node = DHT.
|
|
132
|
+
#### `const node = DHT.bootrapper(port, [options])`
|
|
137
133
|
|
|
138
134
|
Sugar for the options needed to run a bootstrap node, ie
|
|
139
135
|
|
|
@@ -144,7 +140,7 @@ Sugar for the options needed to run a bootstrap node, ie
|
|
|
144
140
|
}
|
|
145
141
|
```
|
|
146
142
|
|
|
147
|
-
Additionally since you'll want a known port for a bootstrap node it adds the
|
|
143
|
+
Additionally since you'll want a known port for a bootstrap node it adds the `port` option as a primary argument.
|
|
148
144
|
|
|
149
145
|
#### `await node.ready()`
|
|
150
146
|
|
|
@@ -165,7 +161,7 @@ Emitted when the routing table is fully bootstrapped. Emitted as a conveinience.
|
|
|
165
161
|
|
|
166
162
|
#### `node.on('listening')`
|
|
167
163
|
|
|
168
|
-
Emitted when the underlying
|
|
164
|
+
Emitted when the underlying UDX socket is listening. Emitted as a conveinience.
|
|
169
165
|
|
|
170
166
|
#### `node.on('persistent')`
|
|
171
167
|
|
|
@@ -179,6 +175,10 @@ you are on an open NAT.
|
|
|
179
175
|
Emitted when the node has detected that the computer has gone to sleep. If this happens,
|
|
180
176
|
it will switch from persistent mode to ephemeral again.
|
|
181
177
|
|
|
178
|
+
#### `node.on('network-change', interfaces)`
|
|
179
|
+
|
|
180
|
+
Emitted when the network interfaces of the computer change.
|
|
181
|
+
|
|
182
182
|
#### `node.refresh()`
|
|
183
183
|
|
|
184
184
|
Refresh the routing table by looking up a random node in the background.
|
|
@@ -201,12 +201,12 @@ Boolean indicated if your node is behind a firewall.
|
|
|
201
201
|
This is auto detected by having other node's trying to do a PING to you
|
|
202
202
|
without you contacting them first.
|
|
203
203
|
|
|
204
|
-
#### `const
|
|
204
|
+
#### `const addr = node.address()`
|
|
205
205
|
|
|
206
206
|
Get the local address of the UDP socket bound.
|
|
207
207
|
|
|
208
208
|
Note that if you are in ephemeral mode, this will return a different
|
|
209
|
-
port than the one you provided in the constructor (under
|
|
209
|
+
port than the one you provided in the constructor (under `port`), as ephemeral
|
|
210
210
|
mode always uses a random port.
|
|
211
211
|
|
|
212
212
|
#### `node.on('request', req)`
|
|
@@ -242,7 +242,7 @@ Options include:
|
|
|
242
242
|
```js
|
|
243
243
|
{
|
|
244
244
|
retry: true, // whether the request should retry on timeout
|
|
245
|
-
socket:
|
|
245
|
+
socket: udxSocket // request on this specific socket
|
|
246
246
|
}
|
|
247
247
|
```
|
|
248
248
|
|
package/index.js
CHANGED
|
@@ -2,6 +2,7 @@ const dns = require('dns')
|
|
|
2
2
|
const { EventEmitter } = require('events')
|
|
3
3
|
const Table = require('kademlia-routing-table')
|
|
4
4
|
const TOS = require('time-ordered-set')
|
|
5
|
+
const UDX = require('udx-native')
|
|
5
6
|
const sodium = require('sodium-universal')
|
|
6
7
|
const c = require('compact-encoding')
|
|
7
8
|
const NatSampler = require('nat-sampler')
|
|
@@ -28,7 +29,8 @@ class DHT extends EventEmitter {
|
|
|
28
29
|
this.bootstrapNodes = opts.bootstrap === false ? [] : (opts.bootstrap || []).map(parseNode)
|
|
29
30
|
this.table = new Table(opts.id || randomBytes(32))
|
|
30
31
|
this.nodes = new TOS()
|
|
31
|
-
this.
|
|
32
|
+
this.udx = opts.udx || new UDX()
|
|
33
|
+
this.io = new IO(this.table, this.udx, {
|
|
32
34
|
...opts,
|
|
33
35
|
onrequest: this._onrequest.bind(this),
|
|
34
36
|
onresponse: this._onresponse.bind(this),
|
|
@@ -43,7 +45,7 @@ class DHT extends EventEmitter {
|
|
|
43
45
|
this.destroyed = false
|
|
44
46
|
|
|
45
47
|
this._nat = new NatSampler()
|
|
46
|
-
this.
|
|
48
|
+
this._port = opts.port || 0
|
|
47
49
|
this._quickFirewall = opts.quickFirewall !== false
|
|
48
50
|
this._forcePersistent = opts.ephemeral === false
|
|
49
51
|
this._repinging = 0
|
|
@@ -60,13 +62,15 @@ class DHT extends EventEmitter {
|
|
|
60
62
|
|
|
61
63
|
this.table.on('row', this._onrow)
|
|
62
64
|
|
|
65
|
+
this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))
|
|
66
|
+
|
|
63
67
|
if (opts.nodes) {
|
|
64
68
|
for (const node of opts.nodes) this.addNode(node)
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
static bootstrapper (
|
|
69
|
-
return new this({
|
|
72
|
+
static bootstrapper (port, opts) {
|
|
73
|
+
return new this({ port, firewalled: false, bootstrap: [], ...opts })
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
get id () {
|
|
@@ -329,6 +333,10 @@ class DHT extends EventEmitter {
|
|
|
329
333
|
this._repingAndSwap(newNode, oldest)
|
|
330
334
|
}
|
|
331
335
|
|
|
336
|
+
_onnetworkchange (interfaces) {
|
|
337
|
+
this.emit('network-change', interfaces)
|
|
338
|
+
}
|
|
339
|
+
|
|
332
340
|
_repingAndSwap (newNode, oldNode) {
|
|
333
341
|
const self = this
|
|
334
342
|
const lastSeen = oldNode.seen
|
|
@@ -619,8 +627,8 @@ class DHT extends EventEmitter {
|
|
|
619
627
|
|
|
620
628
|
return false
|
|
621
629
|
|
|
622
|
-
function onmessage (_,
|
|
623
|
-
hosts.push(
|
|
630
|
+
function onmessage (_, { host }) {
|
|
631
|
+
hosts.push(host)
|
|
624
632
|
}
|
|
625
633
|
}
|
|
626
634
|
|
package/lib/io.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const FIFO = require('fast-fifo')
|
|
2
2
|
const sodium = require('sodium-universal')
|
|
3
3
|
const c = require('compact-encoding')
|
|
4
|
-
const bind = require('bind-easy')
|
|
5
4
|
const b4a = require('b4a')
|
|
6
5
|
const peer = require('./peer')
|
|
7
6
|
const errors = require('./errors')
|
|
@@ -13,14 +12,16 @@ const TMP = b4a.alloc(32)
|
|
|
13
12
|
const EMPTY_ARRAY = []
|
|
14
13
|
|
|
15
14
|
module.exports = class IO {
|
|
16
|
-
constructor (table, { maxWindow = 80,
|
|
15
|
+
constructor (table, udx, { maxWindow = 80, port = 0, firewalled = true, onrequest, onresponse = noop, ontimeout = noop } = {}) {
|
|
17
16
|
this.table = table
|
|
17
|
+
this.udx = udx
|
|
18
18
|
this.inflight = []
|
|
19
19
|
this.clientSocket = null
|
|
20
20
|
this.serverSocket = null
|
|
21
21
|
this.firewalled = firewalled !== false
|
|
22
22
|
this.ephemeral = true
|
|
23
23
|
this.congestion = new CongestionWindow(maxWindow)
|
|
24
|
+
this.networkInterfaces = udx.watchNetworkInterfaces()
|
|
24
25
|
|
|
25
26
|
this.onrequest = onrequest
|
|
26
27
|
this.onresponse = onresponse
|
|
@@ -33,13 +34,13 @@ module.exports = class IO {
|
|
|
33
34
|
this._drainInterval = null
|
|
34
35
|
this._destroying = null
|
|
35
36
|
this._binding = null
|
|
36
|
-
this.
|
|
37
|
+
this._port = port
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
onmessage (socket, buffer,
|
|
40
|
-
if (buffer.byteLength < 2 || !(
|
|
40
|
+
onmessage (socket, buffer, { host, port }) {
|
|
41
|
+
if (buffer.byteLength < 2 || !(port > 0 && port < 65536)) return
|
|
41
42
|
|
|
42
|
-
const from = { id: null, host
|
|
43
|
+
const from = { id: null, host, port }
|
|
43
44
|
const state = { start: 1, end: buffer.byteLength, buffer }
|
|
44
45
|
const expectedSocket = this.firewalled ? this.clientSocket : this.serverSocket
|
|
45
46
|
const external = socket !== expectedSocket
|
|
@@ -114,16 +115,11 @@ module.exports = class IO {
|
|
|
114
115
|
req.onerror(errors.createDestroyedError(), req)
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
this._destroying =
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
function done () {
|
|
124
|
-
if (--missing === 0) resolve()
|
|
125
|
-
}
|
|
126
|
-
})
|
|
118
|
+
this._destroying = Promise.allSettled([
|
|
119
|
+
this.serverSocket.close(),
|
|
120
|
+
this.clientSocket.close(),
|
|
121
|
+
this.networkInterfaces.destroy()
|
|
122
|
+
])
|
|
127
123
|
|
|
128
124
|
return this._destroying
|
|
129
125
|
}
|
|
@@ -135,19 +131,32 @@ module.exports = class IO {
|
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
async _bindSockets () {
|
|
138
|
-
const serverSocket =
|
|
134
|
+
const serverSocket = this.udx.createSocket()
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
serverSocket.bind(this._port)
|
|
138
|
+
} catch {
|
|
139
|
+
try {
|
|
140
|
+
serverSocket.bind()
|
|
141
|
+
} catch (err) {
|
|
142
|
+
await serverSocket.close()
|
|
143
|
+
throw err
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const clientSocket = this.udx.createSocket()
|
|
139
148
|
|
|
140
149
|
try {
|
|
141
|
-
|
|
142
|
-
// to avoid it being accidentally opened
|
|
143
|
-
// We'll prop need additional APIs for that
|
|
144
|
-
this.clientSocket = await bind.udp()
|
|
145
|
-
this.serverSocket = serverSocket
|
|
150
|
+
clientSocket.bind()
|
|
146
151
|
} catch (err) {
|
|
147
|
-
await
|
|
152
|
+
await serverSocket.close()
|
|
153
|
+
await clientSocket.close()
|
|
148
154
|
throw err
|
|
149
155
|
}
|
|
150
156
|
|
|
157
|
+
this.clientSocket = clientSocket
|
|
158
|
+
this.serverSocket = serverSocket
|
|
159
|
+
|
|
151
160
|
this.serverSocket.on('message', this.onmessage.bind(this, this.serverSocket))
|
|
152
161
|
this.clientSocket.on('message', this.onmessage.bind(this, this.clientSocket))
|
|
153
162
|
|
|
@@ -242,8 +251,7 @@ class Request {
|
|
|
242
251
|
reply (value, opts = {}) {
|
|
243
252
|
const socket = opts.socket || this.socket
|
|
244
253
|
const to = opts.to || this.from
|
|
245
|
-
|
|
246
|
-
this._sendReply(0, value || null, opts.token !== false, opts.closerNodes !== false, to, socket, onflush)
|
|
254
|
+
this._sendReply(0, value || null, opts.token !== false, opts.closerNodes !== false, to, socket)
|
|
247
255
|
}
|
|
248
256
|
|
|
249
257
|
error (code, opts = {}) {
|
|
@@ -255,7 +263,7 @@ class Request {
|
|
|
255
263
|
relay (value, to, opts) {
|
|
256
264
|
const socket = (opts && opts.socket) || this.socket
|
|
257
265
|
const buffer = this._encodeRequest(null, value, to, socket)
|
|
258
|
-
socket.
|
|
266
|
+
socket.trySend(buffer, to.port, to.host)
|
|
259
267
|
}
|
|
260
268
|
|
|
261
269
|
send (force = false) {
|
|
@@ -280,7 +288,7 @@ class Request {
|
|
|
280
288
|
if (this.destroyed) return
|
|
281
289
|
this.sent++
|
|
282
290
|
this._io.congestion.send()
|
|
283
|
-
this.socket.
|
|
291
|
+
this.socket.trySend(this._buffer, this.to.port, this.to.host)
|
|
284
292
|
if (this._timeout) clearTimeout(this._timeout)
|
|
285
293
|
this._timeout = setTimeout(oncycle, 1000, this)
|
|
286
294
|
}
|
|
@@ -298,7 +306,7 @@ class Request {
|
|
|
298
306
|
this.onerror(err || errors.createDestroyedError(), this)
|
|
299
307
|
}
|
|
300
308
|
|
|
301
|
-
_sendReply (error, value, token, hasCloserNodes, from, socket
|
|
309
|
+
_sendReply (error, value, token, hasCloserNodes, from, socket) {
|
|
302
310
|
if (socket === null || this.destroyed) return
|
|
303
311
|
|
|
304
312
|
const id = this._io.ephemeral === false && socket === this._io.serverSocket
|
|
@@ -324,7 +332,7 @@ class Request {
|
|
|
324
332
|
if (error > 0) c.uint.encode(state, error)
|
|
325
333
|
if (value) c.buffer.encode(state, value)
|
|
326
334
|
|
|
327
|
-
socket.
|
|
335
|
+
socket.trySend(state.buffer, from.port, from.host)
|
|
328
336
|
}
|
|
329
337
|
|
|
330
338
|
_encodeRequest (token, value, to, socket) {
|
package/package.json
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dht-rpc",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Make RPC calls over a Kademlia based DHT",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"b4a": "^1.3.1",
|
|
8
|
-
"bind-easy": "^1.0.0",
|
|
9
8
|
"compact-encoding": "^2.1.0",
|
|
10
9
|
"compact-encoding-net": "^1.0.1",
|
|
10
|
+
"events": "^3.3.0",
|
|
11
11
|
"fast-fifo": "^1.0.0",
|
|
12
12
|
"kademlia-routing-table": "^1.0.0",
|
|
13
13
|
"nat-sampler": "^1.0.1",
|
|
14
14
|
"sodium-universal": "^3.0.4",
|
|
15
15
|
"streamx": "^2.10.3",
|
|
16
|
-
"time-ordered-set": "^1.0.2"
|
|
16
|
+
"time-ordered-set": "^1.0.2",
|
|
17
|
+
"udx-native": "^1.1.0"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
|
-
"brittle": "^
|
|
20
|
+
"brittle": "^2.3.1",
|
|
20
21
|
"standard": "^16.0.3"
|
|
21
22
|
},
|
|
22
23
|
"scripts": {
|
package/test.js
CHANGED
|
@@ -2,14 +2,12 @@ const test = require('brittle')
|
|
|
2
2
|
const dgram = require('dgram')
|
|
3
3
|
const DHT = require('./')
|
|
4
4
|
|
|
5
|
-
test.configure({ serial: true })
|
|
6
|
-
|
|
7
5
|
test('make tiny swarm', async function (t) {
|
|
8
6
|
await makeSwarm(2, t)
|
|
9
7
|
t.pass('could make swarm')
|
|
10
8
|
})
|
|
11
9
|
|
|
12
|
-
test('make bigger swarm', async function (t) {
|
|
10
|
+
test('make bigger swarm', { timeout: 60000 }, async function (t) {
|
|
13
11
|
const swarm = await makeSwarm(500, t)
|
|
14
12
|
|
|
15
13
|
const targetNode = swarm[25]
|
|
@@ -165,23 +163,6 @@ test('request with/without retries', async function (t) {
|
|
|
165
163
|
t.is(tries, 4)
|
|
166
164
|
})
|
|
167
165
|
|
|
168
|
-
test('reply onflush', async function (t) {
|
|
169
|
-
const [, a, b] = await makeSwarm(3, t)
|
|
170
|
-
|
|
171
|
-
let flushed = false
|
|
172
|
-
|
|
173
|
-
b.on('request', function (req) {
|
|
174
|
-
req.reply(null, {
|
|
175
|
-
onflush () {
|
|
176
|
-
flushed = true
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
await a.request({ command: 42 }, { host: '127.0.0.1', port: b.address().port })
|
|
182
|
-
t.ok(flushed)
|
|
183
|
-
})
|
|
184
|
-
|
|
185
166
|
test('shorthand commit', async function (t) {
|
|
186
167
|
const swarm = await makeSwarm(40, t)
|
|
187
168
|
|
|
@@ -290,12 +271,12 @@ test('addNode / nodes option', async function (t) {
|
|
|
290
271
|
test('set bind', async function (t) {
|
|
291
272
|
const port = await freePort()
|
|
292
273
|
|
|
293
|
-
const a = new DHT({
|
|
274
|
+
const a = new DHT({ port, firewalled: false })
|
|
294
275
|
await a.ready()
|
|
295
276
|
|
|
296
277
|
t.alike(a.address().port, port, 'bound to explicit port')
|
|
297
278
|
|
|
298
|
-
const b = new DHT({
|
|
279
|
+
const b = new DHT({ port })
|
|
299
280
|
await b.ready()
|
|
300
281
|
|
|
301
282
|
t.not(b.address().port, port, 'bound to different port as explicit one is taken')
|