Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IPv6 DHT support #144

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 35 additions & 34 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function DHT (opts) {
this._tables = LRU({maxAge: ROTATE_INTERVAL, max: opts.maxTables || 1000})
this._values = LRU(opts.maxValues || 1000)
this._peers = new PeerStore(opts.maxPeers || 10000)
this.ipv6 = !!opts.ipv6

this._secrets = null
this._rpc = krpc(opts)
Expand Down Expand Up @@ -83,7 +84,7 @@ DHT.prototype.addNode = function (node) {
if (node.id) {
node.id = toBuffer(node.id)
var old = !!this._rpc.nodes.get(node.id)
this._rpc.nodes.add(node)
this._rpc.addNode(node)
if (!old) this.emit('node', node)
return
}
Expand Down Expand Up @@ -332,7 +333,7 @@ DHT.prototype.lookup = function (infoHash, cb) {

function emit (values, from) {
if (!values) values = self._peers.get(infoHash.toString('hex'))
var peers = decodePeers(values)
var peers = self._decodePeers(values)
for (var i = 0; i < peers.length; i++) {
self.emit('peer', peers[i], infoHash, from || null)
}
Expand All @@ -346,6 +347,29 @@ DHT.prototype.lookup = function (infoHash, cb) {
return function abort () { aborted = true }
}

DHT.prototype._decodePeers = function (buf) {
var peers = []

// According to BEP-0032, we need to be able to handle 'hybrid' values
// lists (lists that contain both IPv4 and IPV6 addresses). However, since they're
// not supposed to be sent, we just ignore items of the wrong type
for (var i = 0; i < buf.length; i++) {
var size = this.ipv6 ? 18 : 6
if (buf[i].length !== size) {
this._debug("Received invalid peer with length %s (presumably an %s peer) when we're using %s. Skipping", buf[i].length, this.ipv6 ? 'IPv4' : 'IPv6', this.ipv6 ? 'IPv6' : 'IPv4')
continue
}
var port = buf[i].readUInt16BE(this.ipv6 ? 16 : 4)
if (!port) continue
peers.push({
host: this._rpc._parseIP(buf[i], 0, this.ipv6),
port: port
})
}

return peers
}

DHT.prototype.address = function () {
return this._rpc.address()
}
Expand Down Expand Up @@ -443,10 +467,18 @@ DHT.prototype._onannouncepeer = function (query, peer) {
}

DHT.prototype._addPeer = function (peer, infoHash, from) {
this._peers.add(infoHash.toString('hex'), encodePeer(peer.host, peer.port))
this._peers.add(infoHash.toString('hex'), this._encodePeer(peer.host, peer.port))
this.emit('announce', peer, infoHash, from)
}

DHT.prototype._encodePeer = function (host, port) {
var buf = Buffer.alloc(this.ipv6 ? (16 + 2) : (4 + 2))

var offset = this._rpc._encodeIP(host, buf, 0, this.ipv6)
buf.writeUInt16BE(port, offset)
return buf
}

DHT.prototype._onget = function (query, peer) {
var host = peer.address || peer.host
var target = query.a.target
Expand Down Expand Up @@ -613,37 +645,6 @@ function createGetResponse (id, token, value) {
return r
}

function encodePeer (host, port) {
var buf = Buffer.allocUnsafe(6)
var ip = host.split('.')
for (var i = 0; i < 4; i++) buf[i] = parseInt(ip[i] || 0, 10)
buf.writeUInt16BE(port, 4)
return buf
}

function decodePeers (buf) {
var peers = []

try {
for (var i = 0; i < buf.length; i++) {
var port = buf[i].readUInt16BE(4)
if (!port) continue
peers.push({
host: parseIp(buf[i], 0),
port: port
})
}
} catch (err) {
// do nothing
}

return peers
}

function parseIp (buf, offset) {
return buf[offset++] + '.' + buf[offset++] + '.' + buf[offset++] + '.' + buf[offset++]
}

function encodeSigData (msg) {
var ref = { seq: msg.seq || 0, v: msg.v }
if (msg.salt) ref.salt = msg.salt
Expand Down
5 changes: 3 additions & 2 deletions test/abort.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ var common = require('./common')
var DHT = require('../')
var test = require('tape')

test('explicitly set nodeId', function (t) {
common.wrapTest(test, 'explicitly set nodeId', function genericTest (t, ipv6) {
var nodeId = common.randomId()

var dht = new DHT({
nodeId: nodeId,
bootstrap: false
bootstrap: false,
ipv6: ipv6
})

common.failOnWarningOrError(t, dht)
Expand Down
21 changes: 11 additions & 10 deletions test/announce.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ var common = require('./common')
var DHT = require('../')
var test = require('tape')

test('`announce` with {host: false}', function (t) {
common.wrapTest(test, '`announce` with {host: false}', function (t, ipv6) {
t.plan(3)
var dht = new DHT({ bootstrap: false, host: false })
var dht = new DHT({ bootstrap: false, host: false, ipv6: ipv6 })
common.failOnWarningOrError(t, dht)

var infoHash = common.randomId()
Expand All @@ -18,9 +18,10 @@ test('`announce` with {host: false}', function (t) {
})
})

test('`announce` with {host: "127.0.0.1"}', function (t) {
common.wrapTest(test, '`announce` with {host: "127.0.0.1"}', function (t, ipv6) {
t.plan(3)
var dht = new DHT({ bootstrap: false, host: '127.0.0.1' })

var dht = new DHT({ bootstrap: false, ipv6: ipv6, host: common.localHost(ipv6, true) })
common.failOnWarningOrError(t, dht)

var infoHash = common.randomId()
Expand All @@ -32,26 +33,26 @@ test('`announce` with {host: "127.0.0.1"}', function (t) {
})

dht.on('peer', function (peer) {
t.deepEqual(peer, { host: '127.0.0.1', port: 6969 })
t.deepEqual(peer, { host: common.localHost(ipv6, true), port: 6969 })
})
})
})

test('announce with implied port', function (t) {
common.wrapTest(test, 'announce with implied port', function (t, ipv6) {
t.plan(2)
var dht1 = new DHT({ bootstrap: false })
var dht1 = new DHT({ bootstrap: false, ipv6: ipv6 })
var infoHash = common.randomId()

dht1.listen(function () {
var dht2 = new DHT({bootstrap: '127.0.0.1:' + dht1.address().port})
var dht2 = new DHT({ipv6: ipv6, bootstrap: (ipv6 ? '[::1]:' : '127.0.0.1:') + dht1.address().port}) // Test parsing port

dht1.on('announce', function (peer) {
t.deepEqual(peer, {host: '127.0.0.1', port: dht2.address().port})
t.deepEqual(peer, {host: common.localHost(ipv6, true), port: dht2.address().port})
})

dht2.announce(infoHash, function () {
dht2.once('peer', function (peer) {
t.deepEqual(peer, {host: '127.0.0.1', port: dht2.address().port})
t.deepEqual(peer, {host: common.localHost(ipv6, true), port: dht2.address().port})
dht1.destroy()
dht2.destroy()
})
Expand Down
46 changes: 24 additions & 22 deletions test/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ var common = require('./common')
var DHT = require('../')
var test = require('tape')

test('explicitly set nodeId', function (t) {
common.wrapTest(test, 'explicitly set nodeId', function (t, ipv6) {
var nodeId = common.randomId()

var dht = new DHT({
nodeId: nodeId,
ipv6: ipv6,
bootstrap: false
})

Expand All @@ -17,29 +18,29 @@ test('explicitly set nodeId', function (t) {
t.end()
})

test('call `addNode` with nodeId argument', function (t) {
common.wrapTest(test, 'call `addNode` with nodeId argument', function (t, ipv6) {
t.plan(3)

var dht = new DHT({ bootstrap: false })
var dht = new DHT({ bootstrap: false, ipv6: ipv6 })
common.failOnWarningOrError(t, dht)

var nodeId = common.randomId()

dht.on('node', function (node) {
t.equal(node.host, '127.0.0.1')
t.equal(node.host, common.localHost(ipv6, true))
t.equal(node.port, 9999)
t.deepEqual(node.id, nodeId)
dht.destroy()
})

dht.addNode({host: '127.0.0.1', port: 9999, id: nodeId})
dht.addNode({host: common.localHost(ipv6, true), port: 9999, id: nodeId})
})

test('call `addNode` without nodeId argument', function (t) {
common.wrapTest(test, 'call `addNode` without nodeId argument', function (t, ipv6) {
t.plan(3)

var dht1 = new DHT({ bootstrap: false })
var dht2 = new DHT({ bootstrap: false })
var dht1 = new DHT({ bootstrap: false, ipv6: ipv6 })
var dht2 = new DHT({ bootstrap: false, ipv6: ipv6 })

common.failOnWarningOrError(t, dht1)
common.failOnWarningOrError(t, dht2)
Expand All @@ -48,10 +49,10 @@ test('call `addNode` without nodeId argument', function (t) {
var port = dht1.address().port

// If `nodeId` is undefined, then the peer will be pinged to learn their node id.
dht2.addNode({host: '127.0.0.1', port: port})
dht2.addNode({host: common.localHost(ipv6, true), port: port})

dht2.on('node', function (node) {
t.equal(node.host, '127.0.0.1')
t.equal(node.host, common.localHost(ipv6, true))
t.equal(node.port, port)
t.deepEqual(node.id, dht1.nodeId)
dht1.destroy()
Expand All @@ -60,15 +61,15 @@ test('call `addNode` without nodeId argument', function (t) {
})
})

test('call `addNode` without nodeId argument, and invalid addr', function (t) {
common.wrapTest(test, 'call `addNode` without nodeId argument, and invalid addr', function (t, ipv6) {
t.plan(1)

var dht = new DHT({ bootstrap: false })
var dht = new DHT({ bootstrap: false, ipv6: ipv6 })
common.failOnWarningOrError(t, dht)

// If `nodeId` is undefined, then the peer will be pinged to learn their node id.
// If the peer DOES NOT RESPOND, the will not be added to the routing table.
dht.addNode({host: '127.0.0.1', port: 9999})
dht.addNode({host: common.localHost(ipv6, true), port: 9999})

dht.on('node', function () {
// No 'node' event should be emitted if the added node does not respond to ping
Expand All @@ -81,20 +82,21 @@ test('call `addNode` without nodeId argument, and invalid addr', function (t) {
}, 2000)
})

test('`addNode` only emits events for new nodes', function (t) {
common.wrapTest(test, '`addNode` only emits events for new nodes', function (t, ipv6) {
t.plan(1)

var dht = new DHT({ bootstrap: false })
var dht = new DHT({ bootstrap: false, ipv6: ipv6 })
common.failOnWarningOrError(t, dht)

dht.on('node', function () {
if (--togo < 0) t.fail()
})

var nodeId = common.randomId()
dht.addNode({host: '127.0.0.1', port: 9999, id: nodeId})
dht.addNode({host: '127.0.0.1', port: 9999, id: nodeId})
dht.addNode({host: '127.0.0.1', port: 9999, id: nodeId})
var addr = common.localHost(ipv6, true)
dht.addNode({host: addr, port: 9999, id: nodeId})
dht.addNode({host: addr, port: 9999, id: nodeId})
dht.addNode({host: addr, port: 9999, id: nodeId})

var togo = 1
setTimeout(function () {
Expand All @@ -103,15 +105,15 @@ test('`addNode` only emits events for new nodes', function (t) {
}, 100)
})

test('send message while binding (listen)', function (t) {
common.wrapTest(test, 'send message while binding (listen)', function (t, ipv6) {
t.plan(1)

var a = new DHT({ bootstrap: false })
var a = new DHT({ bootstrap: false, ipv6: ipv6 })
a.listen(function () {
var port = a.address().port
var b = new DHT({ bootstrap: false })
var b = new DHT({ bootstrap: false, ipv6: ipv6 })
b.listen()
b._sendPing({host: '127.0.0.1', port: port}, function (err) {
b._sendPing({host: common.localHost(ipv6, true), port: port}, function (err) {
t.error(err)
a.destroy()
b.destroy()
Expand Down
4 changes: 2 additions & 2 deletions test/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ var DHT = require('../')
var test = require('tape')

// https://github.com/feross/bittorrent-dht/pull/36
test('bootstrap and listen to custom port', function (t) {
common.wrapTest(test, 'bootstrap and listen to custom port', function (t, ipv6) {
t.plan(4)

var dht = new DHT({ bootstrap: [ '1.2.3.4:1000' ] })
var dht = new DHT({ ipv6: ipv6, bootstrap: [ ipv6 ? '[::2]:1000' : '1.2.3.4:1000' ] })
common.failOnWarningOrError(t, dht)

var port = Math.floor(Math.random() * 60000) + 1024
Expand Down
Loading