blind-peer 3.5.0 → 3.7.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/index.js +102 -1
- package/lib/top-k.js +2 -2
- package/package.json +9 -4
package/index.js
CHANGED
|
@@ -14,7 +14,11 @@ const ScopeLock = require('scope-lock')
|
|
|
14
14
|
const IdEnc = require('hypercore-id-encoding')
|
|
15
15
|
const ProtomuxRpcClientPool = require('protomux-rpc-client-pool')
|
|
16
16
|
const ProtomuxRpcClient = require('protomux-rpc-client')
|
|
17
|
+
const rrp = require('resolve-reject-promise')
|
|
18
|
+
const IpBanList = require('ip-ban-list')
|
|
17
19
|
const {
|
|
20
|
+
ADMIN_CHANNEL_ID,
|
|
21
|
+
AdminQueryTopKEncoding,
|
|
18
22
|
AddCoreEncoding,
|
|
19
23
|
DeleteCoreEncoding,
|
|
20
24
|
RouterResolvePeersRequest,
|
|
@@ -206,11 +210,14 @@ class BlindPeer extends ReadyResource {
|
|
|
206
210
|
trustedPubKeys,
|
|
207
211
|
routerKey,
|
|
208
212
|
routerPoolOpts,
|
|
213
|
+
ipBanListKeys = [],
|
|
214
|
+
banTimeout = 16_000,
|
|
209
215
|
port,
|
|
210
216
|
announcingInterval = 100,
|
|
211
217
|
wakeupGcTickTime = null,
|
|
212
218
|
replicationLagThreshold = 100,
|
|
213
|
-
topK = {}
|
|
219
|
+
topK = {},
|
|
220
|
+
adminRouter = null
|
|
214
221
|
} = {}
|
|
215
222
|
) {
|
|
216
223
|
super()
|
|
@@ -218,6 +225,10 @@ class BlindPeer extends ReadyResource {
|
|
|
218
225
|
this.rocks = typeof rocks === 'string' ? new RocksDB(rocks) : rocks
|
|
219
226
|
this.store = store || new Corestore(this.rocks, { active: false })
|
|
220
227
|
this.swarm = swarm || null
|
|
228
|
+
const ipBanNs = this.store.namespace('ip-ban-lists')
|
|
229
|
+
this.ipBanLists = ipBanListKeys.map((key) => new IpBanList(ipBanNs, { key }))
|
|
230
|
+
this.banTimeout = banTimeout
|
|
231
|
+
|
|
221
232
|
this._port = port || 0
|
|
222
233
|
this.announcingInterval = announcingInterval
|
|
223
234
|
this.trustedPubKeys = new Set()
|
|
@@ -240,6 +251,7 @@ class BlindPeer extends ReadyResource {
|
|
|
240
251
|
this.routerKey = routerKey || null
|
|
241
252
|
this.routerPoolOpts = routerPoolOpts || {}
|
|
242
253
|
this.routerPool = null
|
|
254
|
+
this.adminRouter = adminRouter
|
|
243
255
|
|
|
244
256
|
this.stats = {
|
|
245
257
|
bytesGcd: 0,
|
|
@@ -261,6 +273,24 @@ class BlindPeer extends ReadyResource {
|
|
|
261
273
|
|
|
262
274
|
this.topKByPeer = new TopKWindow(bucketCount, bucketTime, k, peerThreshold)
|
|
263
275
|
this.topKByReferrer = new TopKWindow(bucketCount, bucketTime, k, referrerThreshold)
|
|
276
|
+
this.topKByIp = new TopKWindow(bucketCount, bucketTime, k, null) // we do not want to expose the ip spike
|
|
277
|
+
|
|
278
|
+
if (this.adminRouter) {
|
|
279
|
+
this.adminRouter.use({
|
|
280
|
+
onrequest: (ctx, next) => {
|
|
281
|
+
if (!this._isTrustedPeer(ctx.connection.remotePublicKey)) {
|
|
282
|
+
throw new Error('Only trusted peers can query top-k')
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return next()
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
this.adminRouter.method(
|
|
289
|
+
'query-top-k',
|
|
290
|
+
AdminQueryTopKEncoding,
|
|
291
|
+
this._onadminquerytopk.bind(this)
|
|
292
|
+
)
|
|
293
|
+
}
|
|
264
294
|
}
|
|
265
295
|
|
|
266
296
|
get encryptionPublicKey() {
|
|
@@ -287,6 +317,15 @@ class BlindPeer extends ReadyResource {
|
|
|
287
317
|
return this.trustedPubKeys.has(IdEnc.normalize(key))
|
|
288
318
|
}
|
|
289
319
|
|
|
320
|
+
_onadminquerytopk() {
|
|
321
|
+
return {
|
|
322
|
+
version: 1,
|
|
323
|
+
ip: this.topKByIp.topK,
|
|
324
|
+
referrer: this.topKByReferrer.topK,
|
|
325
|
+
peerPublicKey: this.topKByPeer.topK
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
290
329
|
async _open() {
|
|
291
330
|
await this.store.ready()
|
|
292
331
|
|
|
@@ -310,6 +349,13 @@ class BlindPeer extends ReadyResource {
|
|
|
310
349
|
}
|
|
311
350
|
this.swarm.on('connection', this._onconnection.bind(this))
|
|
312
351
|
|
|
352
|
+
await Promise.all(
|
|
353
|
+
this.ipBanLists.map(async (banIpList) => {
|
|
354
|
+
await banIpList.ready()
|
|
355
|
+
this.swarm.join(banIpList.discoveryKey, { server: false, client: true })
|
|
356
|
+
})
|
|
357
|
+
)
|
|
358
|
+
|
|
313
359
|
if (this.routerKey) {
|
|
314
360
|
const rpcClient = new ProtomuxRpcClient(this.swarm.dht)
|
|
315
361
|
this.routerPool = new ProtomuxRpcClientPool([this.routerKey], rpcClient, this.routerPoolOpts)
|
|
@@ -317,6 +363,8 @@ class BlindPeer extends ReadyResource {
|
|
|
317
363
|
|
|
318
364
|
await this.topKByPeer.ready()
|
|
319
365
|
await this.topKByReferrer.ready()
|
|
366
|
+
await this.topKByIp.ready()
|
|
367
|
+
if (this.adminRouter) await this.adminRouter.ready()
|
|
320
368
|
|
|
321
369
|
this._announceCores().catch(safetyCatch) // announcing cores asynchronously
|
|
322
370
|
this.flushInterval = setInterval(this.flush.bind(this), 10_000)
|
|
@@ -465,6 +513,7 @@ class BlindPeer extends ReadyResource {
|
|
|
465
513
|
|
|
466
514
|
rpc.respond('add-core', AddCoreEncoding, this._onaddcore.bind(this, conn))
|
|
467
515
|
rpc.respond('delete-core', DeleteCoreEncoding, this._ondeletecore.bind(this, conn))
|
|
516
|
+
if (this.adminRouter) this.adminRouter.handleConnection(conn, ADMIN_CHANNEL_ID)
|
|
468
517
|
|
|
469
518
|
const self = this
|
|
470
519
|
BlindPeerMuxer.pair(conn, function () {
|
|
@@ -484,6 +533,36 @@ class BlindPeer extends ReadyResource {
|
|
|
484
533
|
})
|
|
485
534
|
}
|
|
486
535
|
|
|
536
|
+
_isBlocked(conn) {
|
|
537
|
+
// current behavior:
|
|
538
|
+
// as we do simple check ban
|
|
539
|
+
// bans can run in parallel across lists, but only the original banner can unban
|
|
540
|
+
for (const ipBanList of this.ipBanLists) {
|
|
541
|
+
if (ipBanList.isBanned(conn.rawStream.remoteHost)) {
|
|
542
|
+
return true
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return false
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async _timeoutThenThrow() {
|
|
549
|
+
const { promise, resolve } = rrp()
|
|
550
|
+
|
|
551
|
+
const done = () => {
|
|
552
|
+
clearTimeout(timer)
|
|
553
|
+
this.off('close', done)
|
|
554
|
+
resolve()
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const timer = setTimeout(done, this.banTimeout)
|
|
558
|
+
timer.unref()
|
|
559
|
+
|
|
560
|
+
this.on('close', done)
|
|
561
|
+
|
|
562
|
+
await promise
|
|
563
|
+
throw new Error('Timed out')
|
|
564
|
+
}
|
|
565
|
+
|
|
487
566
|
async _activateCore(stream, record) {
|
|
488
567
|
this.stats.activations++
|
|
489
568
|
|
|
@@ -584,6 +663,10 @@ class BlindPeer extends ReadyResource {
|
|
|
584
663
|
|
|
585
664
|
async _onaddcore(stream, record) {
|
|
586
665
|
if (!this.opened) await this.ready()
|
|
666
|
+
if (this._isBlocked(stream)) {
|
|
667
|
+
this.emit('connection-banned', stream)
|
|
668
|
+
await this._timeoutThenThrow()
|
|
669
|
+
}
|
|
587
670
|
|
|
588
671
|
record.priority = Math.min(record.priority, 1) // 2 is reserved for trusted peers
|
|
589
672
|
if (record.announce !== false && !this._isTrustedPeer(stream.remotePublicKey)) {
|
|
@@ -624,6 +707,10 @@ class BlindPeer extends ReadyResource {
|
|
|
624
707
|
}
|
|
625
708
|
|
|
626
709
|
async _onaddcores(stream, request) {
|
|
710
|
+
if (this._isBlocked(stream)) {
|
|
711
|
+
this.emit('connection-banned', stream)
|
|
712
|
+
throw new Error('Timed out')
|
|
713
|
+
}
|
|
627
714
|
this.stats.addCoresRx++
|
|
628
715
|
|
|
629
716
|
const { cores, referrer } = request
|
|
@@ -631,6 +718,7 @@ class BlindPeer extends ReadyResource {
|
|
|
631
718
|
this.topKByReferrer.hit(IdEnc.normalize(referrer))
|
|
632
719
|
}
|
|
633
720
|
this.topKByPeer.hit(IdEnc.normalize(stream.remotePublicKey))
|
|
721
|
+
this.topKByIp.hit(stream.rawStream.remoteHost)
|
|
634
722
|
|
|
635
723
|
const priority = Math.min(request.priority, 1) // 2 is reserved for trusted peers
|
|
636
724
|
|
|
@@ -731,6 +819,10 @@ class BlindPeer extends ReadyResource {
|
|
|
731
819
|
}
|
|
732
820
|
|
|
733
821
|
async _ondeletecore(stream, { key }) {
|
|
822
|
+
if (this._isBlocked(stream)) {
|
|
823
|
+
this.emit('connection-banned', stream)
|
|
824
|
+
await this._timeoutThenThrow()
|
|
825
|
+
}
|
|
734
826
|
if (!this._isTrustedPeer(stream.remotePublicKey)) {
|
|
735
827
|
this.emit('delete-blocked', stream, { key })
|
|
736
828
|
throw new Error('Only trusted peers can delete cores')
|
|
@@ -780,9 +872,11 @@ class BlindPeer extends ReadyResource {
|
|
|
780
872
|
await this.routerPool.destroy()
|
|
781
873
|
await this.routerPool.statelessRpc.close()
|
|
782
874
|
}
|
|
875
|
+
if (this.adminRouter) await this.adminRouter.close()
|
|
783
876
|
clearInterval(this.flushInterval)
|
|
784
877
|
await this.topKByPeer.close()
|
|
785
878
|
await this.topKByReferrer.close()
|
|
879
|
+
await this.topKByIp.close()
|
|
786
880
|
if (this.ownsWakeup) this.wakeup.destroy()
|
|
787
881
|
if (this.ownsSwarm) await this.swarm.destroy()
|
|
788
882
|
await this.flush()
|
|
@@ -905,6 +999,13 @@ class BlindPeer extends ReadyResource {
|
|
|
905
999
|
this.set(self.topKByReferrer.topKSum())
|
|
906
1000
|
}
|
|
907
1001
|
})
|
|
1002
|
+
new promClient.Gauge({
|
|
1003
|
+
name: 'blind_peer_add_cores_top5_by_remote_ip',
|
|
1004
|
+
help: 'The total number of requests from the top 5 remote IPs in the last minute',
|
|
1005
|
+
collect() {
|
|
1006
|
+
this.set(self.topKByIp.topKSum())
|
|
1007
|
+
}
|
|
1008
|
+
})
|
|
908
1009
|
if (self.rocks.stats) {
|
|
909
1010
|
new promClient.Gauge({
|
|
910
1011
|
// eslint-disable-line no-new
|
package/lib/top-k.js
CHANGED
|
@@ -44,11 +44,11 @@ class TopKWindow extends ReadyResource {
|
|
|
44
44
|
return sum
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
_open() {
|
|
48
48
|
this._timer = setInterval(this._rotate.bind(this), this.bucketTime)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
_close() {
|
|
52
52
|
if (this._timer !== null) {
|
|
53
53
|
clearInterval(this._timer)
|
|
54
54
|
this._timer = null
|
package/package.json
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blind-peer",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "Blind peers help keep hypercores available",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"autobase": "^7.0.18",
|
|
8
8
|
"b4a": "^1.6.7",
|
|
9
|
-
"blind-peer-encodings": "^3.
|
|
9
|
+
"blind-peer-encodings": "^3.3.0",
|
|
10
10
|
"blind-peer-muxer": "^1.0.0",
|
|
11
11
|
"compact-encoding": "^2.16.0",
|
|
12
12
|
"corestore": "^7.4.4",
|
|
13
13
|
"hypercore": "^11.26.0",
|
|
14
14
|
"hypercore-crypto": "^3.5.0",
|
|
15
15
|
"hypercore-id-encoding": "^1.3.0",
|
|
16
|
-
"hyperdb": "^
|
|
16
|
+
"hyperdb": "^6.6.3",
|
|
17
17
|
"hyperdht": "^6.29.0",
|
|
18
18
|
"hyperschema": "^1.10.3",
|
|
19
19
|
"hyperswarm": "^4.13.1",
|
|
20
|
+
"ip-ban-list": "^0.3.0",
|
|
20
21
|
"protomux-rpc": "^1.7.1",
|
|
21
22
|
"protomux-rpc-client": "^2.1.0",
|
|
22
23
|
"protomux-rpc-client-pool": "^2.1.0",
|
|
23
24
|
"protomux-wakeup": "^2.9.0",
|
|
24
25
|
"ready-resource": "^1.1.2",
|
|
25
26
|
"repl-swarm": "^2.3.0",
|
|
27
|
+
"resolve-reject-promise": "^1.0.3",
|
|
26
28
|
"rocksdb-native": "^3.1.6",
|
|
27
29
|
"safety-catch": "^1.0.2",
|
|
28
30
|
"scope-lock": "^1.2.4"
|
|
@@ -32,9 +34,11 @@
|
|
|
32
34
|
"bare-process": "^4.2.2",
|
|
33
35
|
"bare-prom-client": "^15.1.3",
|
|
34
36
|
"blind-peer-router": "^0.2.2",
|
|
35
|
-
"blind-peering": "^2.0
|
|
37
|
+
"blind-peering": "^2.1.0",
|
|
36
38
|
"brittle": "^3.7.0",
|
|
37
39
|
"debounceify": "^1.1.0",
|
|
40
|
+
"graceful-goodbye": "^1.3.3",
|
|
41
|
+
"hyperdht-address": "^1.0.1",
|
|
38
42
|
"lunte": "^1.6.0",
|
|
39
43
|
"prettier": "^3.6.2",
|
|
40
44
|
"prettier-config-holepunch": "^2.0.0",
|
|
@@ -49,6 +53,7 @@
|
|
|
49
53
|
"format": "prettier --write .",
|
|
50
54
|
"format:check": "prettier --check .",
|
|
51
55
|
"test": "npm run format:check && lunte && brittle test/*.js",
|
|
56
|
+
"test:netns": "brittle test-netns/*.test.js",
|
|
52
57
|
"test:bare": "bare test/test.js"
|
|
53
58
|
},
|
|
54
59
|
"repository": {
|