Browse Source

add remoteAddress backed by the nat analyzer

session-estimator
Mathias Buus 4 years ago
parent
commit
2ebf446cb4
  1. 15
      index.js
  2. 108
      lib/nat-analyzer.js
  3. 6
      test.js

15
index.js

@ -1,6 +1,7 @@
const dns = require('dns')
const RPC = require('./lib/rpc')
const Query = require('./lib/query')
const NatAnalyzer = require('./lib/nat-analyzer')
const Table = require('kademlia-routing-table')
const TOS = require('time-ordered-set')
const FIFO = require('fast-fifo/fixed-size')
@ -73,6 +74,7 @@ class DHT extends EventEmitter {
this._refreshTick = this._tick + REFRESH_TICKS
this._stableTick = this._tick + STABLE_TICKS
this._tickInterval = setInterval(this._ontick.bind(this), TICK_INTERVAL)
this._nat = new NatAnalyzer(opts.natSampleSize || 16)
this.table.on('row', (row) => row.on('full', (node) => this._onfullrow(node, row)))
}
@ -341,19 +343,20 @@ class DHT extends EventEmitter {
if (oldNode) {
if (oldNode.port === m.from.port && oldNode.host === m.from.host) {
// refresh it
oldNode.to = m.to
oldNode.seen = this._tick
this.nodes.add(oldNode)
}
return
}
// add a sample of our address from the remote nodes pov
this._nat.add(m.to)
this._addNode({
id: m.nodeId,
token: null,
port: m.from.port,
host: m.from.host,
to: m.to,
added: this._tick,
seen: this._tick,
prev: null,
@ -399,6 +402,10 @@ class DHT extends EventEmitter {
return this.rpc.address()
}
remoteAddress () {
return this._nat.analyze()
}
_reply (rpc, tid, target, status, value, token, to) {
const closerNodes = target ? this.table.closest(target) : null
const persistent = !this.ephemeral && rpc === this.rpc
@ -422,6 +429,10 @@ class DHT extends EventEmitter {
DHT.OK = 0
DHT.UNKNOWN_COMMAND = 1
DHT.BAD_TOKEN = 2
DHT.NAT_UNKNOWN = NatAnalyzer.UNKNOWN
DHT.NAT_PORT_CONSISTENT = NatAnalyzer.PORT_CONSISTENT
DHT.NAT_PORT_INCREMENTING = NatAnalyzer.PORT_INCREMENTING
DHT.NAT_PORT_RANDOMIZED = NatAnalyzer.PORT_RANDOMIZED
module.exports = DHT

108
lib/nat-analyzer.js

@ -0,0 +1,108 @@
// how far can the port median distance be?
const INCREMENTING_THRESHOLD = 200
class NatAnalyzer {
constructor (sampleSize) {
// sampleSize must be 2^n
this.samples = new Array(sampleSize)
this.length = 0
this.top = 0
}
add (addr) {
if (this.length < this.samples.length) this.length++
this.samples[this.top] = { port: addr.port, host: addr.host, dist: 0 }
this.top = (this.top + 1) & (this.samples.length - 1)
}
analyze () {
if (this.length <= 2) return { type: NatAnalyzer.UNKNOWN, host: null, port: 0 }
const samples = this.samples.slice(0, this.length)
const hosts = new Map()
let bestHost = null
let bestHits = 0
for (let i = 0; i < samples.length; i++) {
const host = samples[i].host
const hits = (hosts.get(host) || 0) + 1
hosts.set(host, hits)
if (hits > bestHits) {
bestHits = hits
bestHost = host
}
}
if (bestHits < (samples.length >> 1)) {
return { type: NatAnalyzer.UNKNOWN, host: null, port: 0 }
}
samples.sort(cmpPort)
let start = 0
let end = samples.length
let mid = samples[samples.length >> 1].port
// remove the 3 biggest outliers from the median if we have more than 6 samples
if (samples.length >= 6) {
for (let i = 0; i < 3; i++) {
const s = samples[start]
const e = samples[end - 1]
if (Math.abs(mid - s.port) < Math.abs(mid - e.port)) end--
else start++
}
}
const len = end - start
mid = samples[len >> 1].port
for (let i = 0; i < samples.length; i++) {
samples[i].dist = Math.abs(mid - samples[i].port)
}
// note that still sorts with the outliers which is why we just start=0, end=len-1 below
samples.sort(cmpDist)
mid = samples[len >> 1].dist
if (samples[0].dist === 0 && samples[len - 1].dist === 0) {
return {
type: NatAnalyzer.PORT_CONSISTENT,
host: bestHost,
port: samples[0].port
}
}
if (mid < INCREMENTING_THRESHOLD) {
return {
type: NatAnalyzer.PORT_INCREMENTING,
host: bestHost,
port: 0
}
}
return {
type: NatAnalyzer.PORT_RANDOMIZED,
host: bestHost,
port: 0
}
}
}
NatAnalyzer.UNKNOWN = Symbol.for('NAT_UNKNOWN')
NatAnalyzer.PORT_CONSISTENT = Symbol.for('NAT_PORT_CONSISTENT')
NatAnalyzer.PORT_INCREMENTING = Symbol.for('NAT_PORT_INCREMENTING')
NatAnalyzer.PORT_RANDOMIZED = Symbol.for('NAT_PORT_RANDOM')
module.exports = NatAnalyzer
function cmpDist (a, b) {
return a.dist - b.dist
}
function cmpPort (a, b) {
return a.port - b.port
}

6
test.js

@ -40,6 +40,12 @@ tape('make bigger swarm', async function (t) {
t.ok(found, 'found target again in ' + messages + ' message(s)')
const { type, host, port } = swarm[490].remoteAddress()
t.same(type, DHT.NAT_PORT_CONSISTENT)
t.same(port, swarm[490].address().port)
t.ok(host)
destroy(swarm)
})

Loading…
Cancel
Save