|
| 1 | +# -*- coding: binary -*- |
| 2 | + |
| 3 | +require 'rex/proto/kademlia/message' |
| 4 | +require 'rex/proto/kademlia/util' |
| 5 | + |
| 6 | +module Rex |
| 7 | +module Proto |
| 8 | +module Kademlia |
| 9 | + # Opcode for a bootstrap response |
| 10 | + BOOTSTRAP_RESPONSE = 0x09 |
| 11 | + |
| 12 | + # A Kademlia bootstrap response message |
| 13 | + class BootstrapResponse < Message |
| 14 | + # @return [String] the ID of the peer that send the bootstrap response |
| 15 | + attr_reader :peer_id |
| 16 | + # @return [Integer] the TCP port that the responding peer is listening on |
| 17 | + attr_reader :tcp_port |
| 18 | + # @return [Integer] the version of this peer |
| 19 | + attr_reader :version |
| 20 | + # @return [Array<Hash<String, Object>>] the peer ID, IP address, UDP/TCP ports and version of each peer |
| 21 | + attr_reader :peers |
| 22 | + |
| 23 | + # Constructs a new bootstrap response |
| 24 | + # |
| 25 | + # @param peer_id [String] the ID of this peer |
| 26 | + # @param tcp_port [Integer] the TCP port that this peer is listening on |
| 27 | + # @param version [Integer] the version of this peer |
| 28 | + # @param peers [Array<Hash<String, Object>>] the peer ID, IP address, UDP/TCP ports and version of each peer |
| 29 | + def initialize(peer_id, tcp_port, version, peers) |
| 30 | + @peer_id = peer_id |
| 31 | + @tcp_port = tcp_port |
| 32 | + @version = version |
| 33 | + @peers = peers |
| 34 | + end |
| 35 | + |
| 36 | + # The minimum size of a peer in a KADEMLIA2_BOOTSTRAP_RES message: |
| 37 | + # peer ID (16-bytes), IP (4 bytes), UDP port (2 bytes), TCP port (2 bytes) |
| 38 | + # and version (1 byte) |
| 39 | + BOOTSTRAP_PEER_SIZE = 25 |
| 40 | + |
| 41 | + # Builds a bootstrap response from given data |
| 42 | + # |
| 43 | + # @param data [String] the data to decode |
| 44 | + # @return [BootstrapResponse] the bootstrap response if the data is valid, nil otherwise |
| 45 | + def self.from_data(data) |
| 46 | + message = Message.from_data(data) |
| 47 | + # abort if this isn't a valid response |
| 48 | + return unless message |
| 49 | + return unless message.type == BOOTSTRAP_RESPONSE |
| 50 | + return unless message.body.size >= 23 |
| 51 | + bootstrap_peer_id = Rex::Proto::Kademlia.decode_peer_id(message.body.slice!(0, 16)) |
| 52 | + bootstrap_tcp_port, bootstrap_version, num_peers = message.body.slice!(0, 5).unpack('vCv') |
| 53 | + # protocol says there are no peers and the body confirms this, so just return with no peers |
| 54 | + if num_peers == 0 && message.body.blank? |
| 55 | + peers = [] |
| 56 | + else |
| 57 | + peers_data = message.body |
| 58 | + # peers data is too long/short, abort |
| 59 | + return if peers_data.size % BOOTSTRAP_PEER_SIZE != 0 |
| 60 | + peers = [] |
| 61 | + until peers_data.blank? |
| 62 | + peer_data = peers_data.slice!(0, BOOTSTRAP_PEER_SIZE) |
| 63 | + peer_id = Rex::Proto::Kademlia.decode_peer_id(peer_data.slice!(0, 16)) |
| 64 | + ip, udp_port, tcp_port, version = peer_data.unpack('VvvC') |
| 65 | + peers << { |
| 66 | + id: peer_id, |
| 67 | + ip: Rex::Socket.addr_itoa(ip), |
| 68 | + tcp_port: tcp_port, |
| 69 | + udp_port: udp_port, |
| 70 | + version: version |
| 71 | + } |
| 72 | + end |
| 73 | + end |
| 74 | + BootstrapResponse.new(bootstrap_peer_id, bootstrap_tcp_port, bootstrap_version, peers) |
| 75 | + end |
| 76 | + end |
| 77 | +end |
| 78 | +end |
| 79 | +end |
0 commit comments