-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path2-discover-nodes.js
130 lines (98 loc) · 3.82 KB
/
2-discover-nodes.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
const crypto = require('crypto')
const dgram = require('dgram')
const {decode, encode} = require('bencode')
const udp = dgram.createSocket('udp4')
const nodeId = crypto.randomBytes(20)
const bootstrapNodes = [{host: 'router.bittorrent.com', port: 6881}, {host: 'router.utorrent.com', port: 6881}]
const magnetId = '-- ENTER MAGNET ID --'
const peers = {}
const queries = {}
let tCount = 0 // transaction id counter
const intToBuffer = (d) => Buffer.from(d.toString(16).padStart(4, '0'), 'hex')
const bufferToInt = (b) => parseInt(b.toString('hex'), 16)
const splitBuffer = (b, n) => Array.from({length: b.length / n}).map((_, i) => b.subarray(n * i, n * (i + 1)))
const getIpPortFromBuffer = (buffer) => {
const address = buffer.slice(0, 4).join('.')
const port = parseInt(buffer.slice(4).toString('hex'), 16)
return { address, port }
}
const queryOutgoing = async (query, args, address, port) => {
// save pending query
const t = tCount.toString()
queries[tCount.toString()] = {}
tCount++
// send message
const message = { t: intToBuffer(t), y: 'q', q: query, a: {...args, id: nodeId} }
udp.send(encode(message), port, address)
// wait response or timeout
await new Promise(r => {
let i = 0
setInterval(() => { if (queries[t].r || i > 20) r(); else i++ }, 250)
})
return queries[t].r
}
const queryResponse = async (type, message, rinfo) => {
// check incorrect types
if (type === 'r' && !(message.r && message.r.id)) return
if (type === 'e' && !message.e) return
// check if own request
const t = bufferToInt(message.t).toString()
if (!Object.keys(queries).includes(t)) return
// update query response
queries[t].r = message.r
// response to query
if (type === 'r') {
const peerId = message.r.id.toString('hex')
peers[peerId] = {address: rinfo.address, port: rinfo.port}
} else if (type === 'e') {
console.log('something went wrong')
}
}
const getPeers = async (address, port, magnetId, parentIds = []) => {
const response = await queryOutgoing('get_peers', { info_hash: Buffer.from(magnetId, 'hex') }, address, port)
if (response?.values) {
const values = response.values.map(v => ({...getIpPortFromBuffer(v), path: parentIds.concat([{id: response.id.toString('hex'), address, port}])}))
values.forEach(v => console.log(`${v.address}:${v.port} (nodeId=${response.id.toString('hex')})`))
return values
} else if (response?.nodes) {
const promises = splitBuffer(response.nodes, 26)
.map((c) => {
const {address, port} = getIpPortFromBuffer(c.subarray(20, 26))
return getPeers(address, port, Buffer.from(magnetId, 'hex'), parentIds.concat([{id: response.id.toString('hex'), address, port}]))
})
const rest = await Promise.all(promises)
return rest.flat()
} else {
return []
}
}
;(async () => {
udp.bind(6881)
udp.once('listening', async () => {
// log messages
console.log(`udp server listening on port => ${udp.address().port}`)
console.log()
// get bootlocs
console.log(`booting from => ${bootstrapNodes[0].host}`)
console.log(`node id => ${nodeId.toString('hex')}`)
console.log(`magnet id => ${magnetId}`)
console.log()
// find magnet peers
console.log('closest to magnet =>')
const bootstrapPeers = bootstrapNodes.map((l) => getPeers(l.host, l.port, magnetId))
const peersFound = await Promise.all(Array(2).fill().map(() => bootstrapPeers).flat()) // replication = 2
console.log(`Found ${[...new Set(peersFound)].flat().length} peers`)
console.log()
})
udp.on('message', async (message, rinfo) => {
try {
const decoded = decode(message)
const type = decoded?.y?.toString()
if (type === 'r' || type === 'e') {
await queryResponse(type, decoded, rinfo)
}
} catch (e) {
console.log('error', e)
}
})
})()