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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
|
---
-- A Vuze DHT protocol implementation based on the following documentation:
-- o http://wiki.vuze.com/w/Distributed_hash_table
--
-- It currently supports the PING and FIND_NODE requests and parses the
-- responses. The following main classes are used by the library:
--
-- o Request - the request class containing all of the request classes. It
-- currently contains the Header, PING and FIND_NODE classes.
--
-- o Response - the response class containing all of the response classes. It
-- currently contains the Header, PING, FIND_NODE and ERROR
-- class.
--
-- o Session - a class containing "session state" such as the transaction- and
-- instance ID's.
--
-- o Helper - The helper class that serves as the main interface between
-- scripts and the library.
--
-- @author Patrik Karlsson <patrik@cqure.net>
--
local ipOps = require "ipOps"
local math = require "math"
local nmap = require "nmap"
local os = require "os"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local rand = require "rand"
_ENV = stdnse.module("vuzedht", stdnse.seeall)
Request = {
Actions = {
ACTION_PING = 1024,
FIND_NODE = 1028,
},
-- The request Header class shared by all Requests classes
Header = {
-- Creates a new Header instance
-- @param action number containing the request action
-- @param session instance of Session
-- @return o new instance of Header
new = function(self, action, session)
local o = {
conn_id = string.char(255) .. rand.random_string(7),
-- we need to handle this one like this, due to a bug in nsedoc
-- it used to be action = action, but that breaks parsing
["action"] = action,
trans_id = session:getTransactionId(),
proto_version = 0x32,
vendor_id = 0,
network_id = 0,
local_proto_version = 0x32,
address = session:getAddress(),
port = session:getPort(),
instance_id = session:getInstanceId(),
time = os.time(),
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts the header to a string
__tostring = function(self)
local lhost = ipOps.ip_to_str(self.address)
return self.conn_id .. string.pack( ">I4 I4 BB I4 B s1 I2 I4 I8 ", self.action, self.trans_id,
self.proto_version, self.vendor_id, self.network_id, self.local_proto_version,
lhost, self.port, self.instance_id, self.time )
end,
},
-- The PING Request class
Ping = {
-- Creates a new Ping instance
-- @param session instance of Session
-- @return o new instance of Ping
new = function(self, session)
local o = {
header = Request.Header:new(Request.Actions.ACTION_PING, session)
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts a Ping Request to a string
__tostring = function(self)
return tostring(self.header)
end,
},
-- The FIND_NODES Request class
FindNode = {
-- Creates a new FindNode instance
-- @param session instance of Session
-- @return o new instance of FindNode
new = function(self, session)
local o = {
header = Request.Header:new(Request.Actions.FIND_NODE, session),
node_id = '\xA7' .. rand.random_string(19),
status = 0xFFFFFFFF,
dht_size = 0,
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts a FindNode Request to a string
__tostring = function(self)
local data = tostring(self.header)
.. string.pack(">s1 I4I4", self.node_id, self.status, self.dht_size)
return data
end,
}
}
Response = {
-- A table of currently supported Actions (Responses)
-- It's used in the fromString method to determine which class to create.
Actions = {
ACTION_PING = 1025,
FIND_NODE = 1029,
ERROR = 1032,
},
-- Creates an address record based on received data
-- @param data containing an address record [C][I|H][S] where
-- [C] is the length of the address (4 or 16)
-- [I|H] is the binary address
-- [S] is the port number as a short
-- @return o Address instance on success, nil on failure
Address = {
new = function(self, data)
local o = { data = data }
setmetatable(o, self)
self.__index = self
if ( o:parse() ) then
return o
end
end,
-- Parses the received data
-- @return true on success, false on failure
parse = function(self)
local ip, err
ip, self.port = string.unpack(">s1 I2", self.data)
self.ip, err = ipOps.str_to_ip(ip)
if not self.ip then
stdnse.debug1("Unknown address type (length: %d)", #ip)
return false, "Unknown address type"
end
return true
end
},
-- The response header, present in all packets
Header = {
Vendors = {
[0] = "Azureus",
[1] = "ShareNet",
[255] = "Unknown", -- to be honest, we report all except 0 and 1 as unknown
},
Networks = {
[0] = "Stable",
[1] = "CVS"
},
-- Creates a new Header instance
-- @param data string containing the received data
-- @return o instance of Header
new = function(self, data)
local o = { data = data }
setmetatable(o, self)
self.__index = self
o:parse()
return o
end,
-- parses the header
parse = function(self)
local pos
self.action, self.trans_id, self.conn_id,
self.proto_version, self.vendor_id, self.network_id,
self.instance_id, pos = string.unpack(">I4 I4 c8 BB I4 I4 ", self.data)
end,
-- Converts the header to a suitable string representation
__tostring = function(self)
local result = {}
table.insert(result, ("Transaction id: %d"):format(self.trans_id))
table.insert(result, ("Connection id: 0x%s"):format(stdnse.tohex(self.conn_id)))
table.insert(result, ("Protocol version: %d"):format(self.proto_version))
table.insert(result, ("Vendor id: %s (%d)"):format(
Response.Header.Vendors[self.vendor_id] or "Unknown", self.vendor_id))
table.insert(result, ("Network id: %s (%d)"):format(
Response.Header.Networks[self.network_id] or "Unknown", self.network_id))
table.insert(result, ("Instance id: %d"):format(self.instance_id))
return stdnse.format_output(true, result)
end,
},
-- The PING response
PING = {
-- Creates a new instance of PING
-- @param data string containing the received data
-- @return o new PING instance
new = function(self, data)
local o = {
header = Response.Header:new(data)
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Creates a new PING instance based on received data
-- @param data string containing received data
-- @return status true on success, false on failure
-- @return new instance of PING on success, error message on failure
fromString = function(data)
local ping = Response.PING:new(data)
if ( ping ) then
return true, ping
end
return false, "Failed to parse PING response"
end,
-- Converts the PING response to a response suitable for script output
-- @return result formatted script output
__tostring = function(self)
return tostring(self.header)
end,
},
-- A class to process the response from a FIND_NODE query
FIND_NODE = {
-- Creates a new FIND_NODE instance
-- @param data string containing the received data
-- @return o new instance of FIND_NODE
new = function(self, data)
local o = {
header = Response.Header:new(data),
data = data:sub(27)
}
setmetatable(o, self)
self.__index = self
o:parse()
return o
end,
-- Parses the FIND_NODE response
parse = function(self)
local pos
self.spoof_id, self.node_type, self.dht_size,
self.network_coords, pos = string.unpack(">I4 I4 I4 c20", self.data)
local contact_count
contact_count, pos = string.unpack("B", self.data, pos)
self.contacts = {}
for i=1, contact_count do
local contact = {}
local address
contact.type, contact.proto_version, address, contact.port, pos = string.unpack(
">BBs1I2", self.data, pos)
contact.address = ipOps.str_to_ip(address)
table.insert(self.contacts, contact)
end
end,
-- Creates a new instance of FIND_NODE based on received data
-- @param data string containing received data
-- @return status true on success, false on failure
-- @return new instance of FIND_NODE on success, error message on failure
fromString = function(data)
local find = Response.FIND_NODE:new(data)
if ( find.header.proto_version < 13 ) then
stdnse.debug1("ERROR: Unsupported version %d", find.header.proto_version)
return false
end
return true, find
end,
-- Convert the FIND_NODE response to formatted string data, suitable
-- for script output.
-- @return string with formatted FIND_NODE data
__tostring = function(self)
if ( not(self.contacts) ) then
return ""
end
local result = {}
for _, contact in ipairs(self.contacts) do
local address = contact.address
if address:find(":") then
address = ("[%s]"):format(address)
end
table.insert(result, ("%s:%d"):format(address, contact.port))
end
return stdnse.format_output(true, result)
end
},
-- The ERROR action
ERROR = {
-- Creates a new ERROR instance based on received socket data
-- @return o new ERROR instance on success, nil on failure
new = function(self, data)
local o = {
header = Response.Header:new(data),
data = data:sub(27)
}
setmetatable(o, self)
self.__index = self
if ( o:parse() ) then
return o
end
end,
-- parses the received data and attempts to create an ERROR response
-- @return true on success, false on failure
parse = function(self)
local err_type, pos = string.unpack(">I4", self.data)
if ( 1 == err_type ) then
self.addr = Response.Address:new(self.data:sub(pos))
return true
end
return false
end,
-- creates a new ERROR instance based on the received data
-- @return true on success, false on failure
fromString = function(data)
local err = Response.ERROR:new(data)
if ( err ) then
return true, err
end
return false
end,
-- Converts the ERROR action to a formatted response
-- @return string containing the formatted response
__tostring = function(self)
return ("Wrong address, expected: %s"):format(self.addr.ip)
end,
},
-- creates a suitable Response class based on the Action received
-- @return true on success, false on failure
-- @return response instance of suitable Response class on success,
-- err string error message if status is false
fromString = function(data)
local action, pos = string.unpack(">I4", data)
if ( action == Response.Actions.ACTION_PING ) then
return Response.PING.fromString(data)
elseif ( action == Response.Actions.FIND_NODE ) then
return Response.FIND_NODE.fromString(data)
elseif ( action == Response.Actions.ERROR ) then
return Response.ERROR.fromString(data)
end
stdnse.debug1("ERROR: Unknown response received from server")
return false, "Failed to parse response"
end,
}
-- The Session
Session = {
-- Creates a new Session instance to keep track on some of the protocol
-- stuff, such as transaction- and instance- identities.
-- @param address the local address to pass in the requests to the server
-- this could be either the local address or the IP of the router
-- depending on if NAT is used or not.
-- @param port the local port to pass in the requests to the server
-- @return o new instance of Session
new = function(self, address, port)
local o = {
trans_id = math.random(12345678),
instance_id = math.random(12345678),
address = address,
port = port,
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Gets the next transaction ID
-- @return trans_id number
getTransactionId = function(self)
self.trans_id = self.trans_id + 1
return self.trans_id
end,
-- Gets the next instance ID
-- @return instance_id number
getInstanceId = function(self)
self.instance_id = self.instance_id + 1
return self.instance_id
end,
-- Gets the stored local address used to create the session
-- @return string containing the IP passed to the session
getAddress = function(self)
return self.address
end,
-- Get the stored local port used to create the session
-- @return number containing the local port
getPort = function(self)
return self.port
end
}
-- The Helper class, used as main interface between the scripts and the library
Helper = {
-- Creates a new instance of the Helper class
-- @param host table as passed to the action method
-- @param port table as passed to the action method
-- @param lhost [optional] used if an alternate local address is to be
-- passed in the requests to the remote node (ie. NAT is in play).
-- @param lport [optional] used if an alternate port is to be passed in
-- the requests to the remote node.
-- @return o new instance of Helper
new = function(self, host, port, lhost, lport)
local o = {
host = host,
port = port,
lhost = lhost,
lport = lport
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Connects to the remote Vuze Node
-- @return true on success, false on failure
-- @return err string error message if status is false
connect = function(self)
local lhost = tonumber(self.lhost or stdnse.get_script_args('vuzedht.lhost'))
local lport = tonumber(self.lport or stdnse.get_script_args('vuzedht.lport'))
self.socket = nmap.new_socket()
if ( lport ) then
self.socket:bind(nil, lport)
end
local status, err = self.socket:connect(self.host, self.port)
if ( not(status) ) then
return false, "Failed to connect to server"
end
if ( not(lhost) or not(lport) ) then
local status, lh, lp, _, _ = self.socket:get_info()
if ( not(status) ) then
return false, "Failed to get socket information"
end
lhost = lhost or lh
lport = lport or lp
end
self.session = Session:new(lhost, lport)
return true
end,
-- Sends a Vuze PING request to the server and parses the response
-- @return status true on success, false on failure
-- @return response PING response instance on success,
-- err string containing the error message on failure
ping = function(self)
local ping = Request.Ping:new(self.session)
local status, err = self.socket:send(tostring(ping))
if ( not(status) ) then
return false, "Failed to send PING request to server"
end
local data
status, data = self.socket:receive()
if ( not(status) ) then
return false, "Failed to receive PING response from server"
end
local response
status, response = Response.fromString(data)
if ( not(status) ) then
return false, "Failed to parse PING response from server"
end
return true, response
end,
-- Requests a list of known nodes by sending the FIND_NODES request
-- to the remote node and parses the response.
-- @return status true on success, false on failure
-- @return response FIND_NODE response instance on success
-- err string containing the error message on failure
findNodes = function(self)
local find = Request.FindNode:new(self.session)
local status, err = self.socket:send(tostring(find))
if ( not(status) ) then
return false, "Failed to send FIND_NODE request to server"
end
local data
status, data = self.socket:receive()
local response
status, response = Response.fromString(data)
if ( not(status) ) then
return false, "Failed to parse FIND_NODE response from server"
end
return true, response
end,
-- Closes the socket connect to the remote node
close = function(self)
self.socket:close()
end,
}
return _ENV;
|