YARD and spec cleanup

bug/bundler_fix
Jon Hart 2014-11-18 13:27:59 -08:00
parent b6b288ae3a
commit 94e5ba13a4
3 changed files with 114 additions and 63 deletions

View File

@ -17,70 +17,100 @@ module Proto
#
##
module Kademlia
# The header that non-compressed Kad messages use
STANDARD_PACKET = 0xE4
# TODO: support this format
# The header that compressed Kad messages use, which is currently unsupported
COMPRESSED_PACKET = 0xE5
# Opcode for a BOOTSTRAP request
BOOTSTRAP_REQ = 0x01
# Opcode for a BOOTSTRAP response
BOOTSTRAP_RES = 0x09
# Opcode for a PING request
PING = 0x60
# Opcode for a PING response
PONG = 0x61
# The minimum size of a peer in a KADEMLIA2_BOOTSTRAP_RES message:
# peer ID (16-bytes), IP (4 bytes), UDP port (2 bytes), TCP port (2 bytes)
# and version (1 byte)
BOOTSTRAP_PEER_SIZE = 25
# Decodes a Kademlia message.
#
# @param message [String] the message to decode
# @return [Array] the message type and body if valid, nil otherwise
def decode_message(message)
# minimum size is header (1) + opcode (1) + stuff (0+)
# minimum size is header (1) + type (1) + stuff (0+)
return if message.length < 2
header, opcode = message.unpack('CC')
header, type = message.unpack('CC')
if header == COMPRESSED_PACKET
fail NotImplementedError, "Unable to handle compressed #{message.length}-byte compressed Kademlia message"
fail NotImplementedError, "Unable to handle #{message.length}-byte compressed Kademlia message"
end
return if header != STANDARD_PACKET
[opcode, message[2, message.length]]
[type, message[2, message.length]]
end
def encode_message(type, payload = '')
[STANDARD_PACKET, type].pack('CC') + payload
# Encodes a Kademlia message.
#
# @param type [String] the message type
# @param body [String] the message body
# @return [String] the encoded Kademlia message
def encode_message(type, body = '')
[STANDARD_PACKET, type].pack('CC') + body
end
# Builds a BOOTSTRAP request
#
# @return [String] a BOOTSTRAP request
def bootstrap
encode_message(BOOTSTRAP_REQ)
end
def decode_bootstrap_res(message)
opcode, payload = decode_message(message)
# Decodes a BOOTSTRAP response
#
# @param response [String] the response to decode
# @return [Array] the discovered peer ID, TCP port, version and a list of peers
# if the response if valid, nil otherwise
def decode_bootstrap_res(response)
type, body = decode_message(response)
# abort if this isn't a valid response
return nil unless opcode = BOOTSTRAP_RES
return nil unless payload.size >= 23
peer_id = decode_peer_id(payload.slice!(0,16))
tcp_port, version, num_peers = payload.slice!(0,5).unpack('vCv')
# protocol says there are no peers and the payload confirms this, so just return with no peers
return [ tcp_port, version, []] if num_peers == 0 && payload.blank?
peers = decode_bootstrap_peers(payload)
return nil unless type = BOOTSTRAP_RES
return nil unless body.size >= 23
peer_id = decode_peer_id(body.slice!(0,16))
tcp_port, version, num_peers = body.slice!(0,5).unpack('vCv')
# protocol says there are no peers and the body confirms this, so just return with no peers
return [ tcp_port, version, []] if num_peers == 0 && body.blank?
peers = decode_bootstrap_peers(body)
# abort if the peer data was invalid
return nil unless peers
[ peer_id, tcp_port, version, peers ]
end
# Returns a PING message
# Builds a PING request
#
# @return [String] a PING request
def ping
encode_message(PING)
end
# Decodes a PONG message, returning the port used by the peer
def decode_pong(message)
opcode, port = decode_message(message)
# Decode a PING response, PONG
#
# @param response [String] the response to decode
# @return [Integer] the source port from the PING response if the response is valid, nil otherwise
def decode_pong(response)
type, port = decode_message(response)
# abort if this isn't a pong
return nil unless opcode == PONG
return nil unless type == PONG
# abort if the response is too large/small
return nil unless port && port.size == 2
# this should always be equivalent to the source port from which the PING was received
port.unpack('v')[0]
end
# Decode a list of peers from a BOOTSTRAP response
#
# @param peers_data [String] the peers data from a BOOTSTRAP response
# @return [Array] a list of the peers and their associated metadata extracted
# from the response if valid, nil otherwise
def decode_bootstrap_peers(peers_data)
# sanity check total size
return nil unless peers_data.size % BOOTSTRAP_PEER_SIZE == 0
@ -91,6 +121,10 @@ module Kademlia
peers
end
# Decodes a single set of peer data from a BOOTSTRAP reseponse
#
# @param peer-data [String] the peer data for one peer from a BOOSTRAP response
# @return [Array] the peer ID, IPv4 addresss, UDP port, TCP port and version of this peer
def decode_bootstrap_peer(peer_data)
# sanity check the size of this peer's data
return nil unless peer_data.size == BOOTSTRAP_PEER_SIZE
@ -100,7 +134,10 @@ module Kademlia
[ decode_peer_id(peer_id), Rex::Socket.addr_itoa(ip), udp_port, tcp_port, version ]
end
# Decodes an on-the-wire representation of a Kademlia peer to its 16-character hex equivalent
#
# @param bytes [String] the on-the-wire representation of a Kademlia peer
# @return [String] the peer ID if valid, nil otherwise
def decode_peer_id(bytes)
peer_id = 0
return nil unless bytes.size == 16
@ -108,9 +145,9 @@ module Kademlia
peer_id.to_s(16).upcase
end
# TODO?
def encode_peer_id(id)
end
# TODO
# def encode_peer_id(id)
# end
end
end
end
end

View File

@ -22,7 +22,7 @@ class Metasploit3 < Msf::Auxiliary
typically belonging to eMule/eDonkey/BitTorrent servers or other P2P
applications.
),
'Author' => 'Jon Hart <jon_hart[at]rapid7.com',
'Author' => 'Jon Hart <jon_hart[at]rapid7.com>',
'References' =>
[
# There are lots of academic papers on the protocol but they tend to lack usable

View File

@ -3,69 +3,80 @@ require 'spec_helper'
require 'rex/proto/kademlia/message'
describe Rex::Proto::Kademlia do
subject do
subject(:kad) do
mod = Module.new
mod.extend described_class
mod
end
describe '#encode_message' do
it 'should properly encode messages' do
expect(subject.encode_message(1)).to eq("\xE4\x01")
expect(subject.encode_message(1, 'p2p')).to eq("\xE4\x01p2p")
it 'properly encodes messages without a body' do
expect(kad.encode_message(1)).to eq("\xE4\x01")
end
it 'properly encodes messages with a body' do
expect(kad.encode_message(1, 'p2p')).to eq("\xE4\x01p2p")
end
end
describe '#decode_message' do
it 'should not decode overly short messages' do
expect(subject.decode_message('f')).to eq(nil)
it 'does not decode overly short messages' do
expect(kad.decode_message('f')).to eq(nil)
end
it 'should not decode unknown messages' do
expect(subject.decode_message("this is not kademlia")).to eq(nil)
it 'does not decode unknown messages' do
expect(kad.decode_message("this is not kademlia")).to eq(nil)
end
it 'should raise on compressed messages' do
it 'raises on compressed messages' do
expect do
subject.decode_message("\xE5\x01blahblah")
kad.decode_message("\xE5\x01blahblah")
end.to raise_error(NotImplementedError)
end
it 'should properly decode valid messages' do
type, payload = subject.decode_message("\xE4\xFF")
it 'properly decodes valid messages without a body' do
type, payload = kad.decode_message("\xE4\xFF")
expect(type).to eq(0xFF)
expect(payload).to eq('')
end
_, payload = subject.decode_message("\xE4\xFFtesttesttest")
it 'properly decodes valid messages wth a body' do
type, payload = kad.decode_message("\xE4\xFFtesttesttest")
expect(type).to eq(0xFF)
expect(payload).to eq('testtesttest')
end
end
describe '#decode_pong' do
it 'should not decode overly large/small pongs' do
expect(subject.decode_pong("\xE4\x61\x01")).to eq(nil)
expect(subject.decode_pong("\xE4\x61\x01\x02\x03")).to eq(nil)
it 'does not decode overly small pongs' do
expect(kad.decode_pong("\xE4\x61\x01")).to eq(nil)
end
it 'should properly decode valid pongs' do
expect(subject.decode_pong("\xE4\x61\x9E\x86")).to eq(34462)
it 'does not decode overly large pongs' do
expect(kad.decode_pong("\xE4\x61\x01\x02\x03")).to eq(nil)
end
it 'properly decodes valid pongs' do
expect(kad.decode_pong("\xE4\x61\x9E\x86")).to eq(34462)
end
end
describe '#decode_bootstrap_peer' do
it 'should not decode overly large/small peer' do
expect(subject.decode_bootstrap_peer("this is too small")).to eq(nil)
expect(subject.decode_bootstrap_peer("this is much, much, much too large")).to eq(nil)
it 'does not decode overly small peer responses' do
expect(kad.decode_bootstrap_peer("this is too small")).to eq(nil)
end
it 'should properly extract peer info' do
it 'does not decode overly large peer responses' do
expect(kad.decode_bootstrap_peer("this is much, much, much too large")).to eq(nil)
end
it 'properly extracts peer info' do
data =
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + # peer ID
"\x04\x28\xA8\xC0" + # 192.168.40.4
"\x31\xd4" + # UDP port 54321
"\x39\x30" + # TCP port 12345
"\x08" # peer type
peer_id, ip, udp_port, tcp_port, type = subject.decode_bootstrap_peer(data)
peer_id, ip, udp_port, tcp_port, type = kad.decode_bootstrap_peer(data)
expect(peer_id).to eq('3020100070605040B0A09080F0E0D0C')
expect(ip).to eq('192.168.40.4')
expect(udp_port).to eq(54321)
@ -75,12 +86,15 @@ describe Rex::Proto::Kademlia do
end
describe '#decode_bootstrap_peers' do
it 'should not decode overly small peers' do
expect(subject.decode_bootstrap_peer("this is too small")).to eq(nil)
expect(subject.decode_bootstrap_peer("this is large enough but truncated")).to eq(nil)
it 'does not decode overly small bootstrap responses' do
expect(kad.decode_bootstrap_peer("this is too small")).to eq(nil)
end
it 'should properly extract peers info' do
it 'does not decode overly large bootstrap responses' do
expect(kad.decode_bootstrap_peer("this is large enough but truncated")).to eq(nil)
end
it 'properly extracts peers info' do
data =
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + # peer ID
"\x04\x28\xA8\xC0" + # 192.168.40.4
@ -92,7 +106,7 @@ describe Rex::Proto::Kademlia do
"\x5c\x11" + # UDP port 4444
"\xb3\x15" + # TCP port 5555
"\x09" # peer type
peers = subject.decode_bootstrap_peers(data)
peers = kad.decode_bootstrap_peers(data)
expect(peers.size).to eq(2)
peer1_id, peer1_ip, peer1_udp, peer1_tcp, peer1_type = peers.first
expect(peer1_id).to eq('3020100070605040B0A09080F0E0D0C')
@ -110,9 +124,9 @@ describe Rex::Proto::Kademlia do
end
describe '#decode_bootstrap_res' do
it 'should properly decode valid bootstrap responses' do
it 'properly decodes valid bootstrap responses' do
data = IO.read(File.join(File.dirname(__FILE__), 'kademlia_bootstrap_res.bin'))
peer_id, tcp, version, peers = subject.decode_bootstrap_res(data)
peer_id, tcp, version, peers = kad.decode_bootstrap_res(data)
expect(peer_id).to eq('B54A83462529B21EF51FD54B956B07B0')
expect(tcp).to eq(4662)
expect(version).to eq(8)
@ -122,18 +136,18 @@ describe Rex::Proto::Kademlia do
end
describe '#decode_peer_id' do
it 'should decode a peer ID properly' do
it 'decodes a peer ID properly' do
bytes = "\x00\x60\x89\x9B\x0A\x0B\xBE\xAE\x45\x35\xCB\x0E\x07\xA1\x77\x71"
peer_id = "9B896000AEBE0B0A0ECB35457177A107"
expect(subject.decode_peer_id(bytes)).to eq(peer_id)
expect(kad.decode_peer_id(bytes)).to eq(peer_id)
end
end
describe '#encode_peer' do
skip 'should encode a peer ID properly' do
skip 'encodes a peer ID properly' do
bytes = "\x00\x60\x89\x9B\x0A\x0B\xBE\xAE\x45\x35\xCB\x0E\x07\xA1\x77\x71"
peer_id = "9B896000AEBE0B0A0ECB35457177A107"
expect(subject.encode_peer_id(peer_id)).to eq(bytes)
expect(kad.encode_peer_id(peer_id)).to eq(bytes)
end
end
end