You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
6.1 KiB

import logging
import sys
import struct
import socket
from io import BytesIO
from levin.section import Section
from levin.constants import *
from levin.exceptions import BadArgumentException
from levin.ctypes import *
log = logging.getLogger()
class Bucket:
def __init__(self):
self.signature = LEVIN_SIGNATURE
if self.signature != LEVIN_SIGNATURE:
raise BadArgumentException('Bender\'s nightmare')
self.cb = None
self.return_data = None
self.command = None
self.return_code = None
self.flags = None
self.protocol_version = None
self.payload_section = None
@classmethod
def create_request(cls, command: int, payload: bytes = None, section: Section = None):
bucket = cls()
bucket.return_data = c_bool(True)
bucket.command = c_uint32(command)
bucket.return_code = c_uint32(0)
bucket.flags = LEVIN_PACKET_REQUEST
bucket.protocol_version = LEVIN_PROTOCOL_VER_1
if payload:
bucket.cb = c_uint64(len(payload))
bucket.payload_section = payload
if section:
bucket.payload_section = bytes(section)
bucket.cb = c_uint64(len(bucket.payload_section))
return bucket
@classmethod
def create_response(cls, command: int, payload: bytes, return_code: int):
bucket = cls()
bucket.cb = c_uint64(len(payload))
bucket.return_data = c_bool(False)
bucket.command = c_uint32(command)
bucket.return_code = c_uint32(return_code)
bucket.flags = LEVIN_PACKET_RESPONSE
bucket.protocol_version = LEVIN_PROTOCOL_VER_1
@staticmethod
def create_handshake_request(
my_port: int = 0,
network_id: bytes = None,
peer_id: bytes = b'\x41\x41\x41\x41\x41\x41\x41\x41',
):
"""
Helper function to create a handshake request. Does not require
parameters but you can use them to impersonate a legit node.
:param my_port: defaults to 0
:param network_id: defaults to mainnet
:param peer_id:
:param verbose:
:return:
"""
handshake_section = Section.handshake_request(peer_id=peer_id, network_id=network_id, my_port=my_port)
bucket = Bucket.create_request(P2P_COMMAND_HANDSHAKE.value, section=handshake_section)
log.debug(">> created packet '%s'" % P2P_COMMANDS[bucket.command])
header = bucket.header()
body = bucket.payload()
return bucket
@staticmethod
def create_stat_info_request(
peer_id: bytes = b"\x41\x41\x41\x41\x41\x41\x41\x41",
):
stat_info_section = Section.stat_info_request(peer_id=peer_id)
log.debug(stat_info_section.entries["proof_of_trust"].entries.keys())
bucket = Bucket.create_request(
P2P_COMMAND_REQUEST_STAT_INFO.value, section=stat_info_section
)
log.debug(">> created packet '%s'" % P2P_COMMANDS[bucket.command])
return bucket
@classmethod
def from_buffer(cls, signature: c_uint64, sock: socket.socket):
if isinstance(signature, bytes):
signature = c_uint64(signature)
# if isinstance(buffer, bytes):
# buffer = BytesIO(buffer)
bucket = cls()
bucket.signature = signature
bucket.cb = c_uint64.from_buffer(sock)
bucket.return_data = c_bool.from_buffer(sock)
bucket.command = c_uint32.from_buffer(sock)
bucket.return_code = c_int32.from_buffer(sock)
bucket.flags = c_uint32.from_buffer(sock)
bucket.protocol_version = c_uint32.from_buffer(sock)
if bucket.signature != LEVIN_SIGNATURE:
raise IOError("Bender's nightmare missing")
if bucket.cb > LEVIN_DEFAULT_MAX_PACKET_SIZE:
raise IOError("payload too large")
if bucket.command.value not in P2P_COMMANDS:
raise IOError("unregonized command: %d" % bucket.command.value)
bucket.payload = sock.recv(bucket.cb.value)
while len(bucket.payload) < bucket.cb.value:
bufsize = 2048
remaining = bucket.cb.value - len(bucket.payload)
if remaining < bufsize:
bufsize = remaining
bucket.payload += sock.recv(bufsize)
bucket.payload_section = None
log.debug("<< received packet '%s'" % P2P_COMMANDS[bucket.command])
from levin.reader import LevinReader
bucket.payload_section = LevinReader(BytesIO(bucket.payload)).read_payload()
log.debug("<< parsed packet '%s'" % P2P_COMMANDS[bucket.command])
return bucket
def header(self):
return b''.join((
bytes(LEVIN_SIGNATURE),
bytes(self.cb),
b'\x01' if self.return_data else b'\x00',
bytes(self.command),
bytes(self.return_code),
bytes(self.flags),
bytes(self.protocol_version)
))
def payload(self):
return self.payload_section
def get_peers(self):
# helper function to retreive peerlisting where buckets.command was 1001
if self.command != 1001:
raise Exception("Only handshake has peerlisting")
peers = []
if 'local_peerlist_new' not in self.payload_section.entries:
return
for peer in [e.entries for e in self.payload_section.entries['local_peerlist_new']]:
if 'adr' not in peer or 'addr' not in peer['adr'].entries:
continue
addr = peer['adr'].entries['addr'].entries
ipv4 = True if "m_ip" in addr else False
if not ipv4 and not "addr" in addr:
continue
if ipv4 and len(addr["m_ip"]) == 4:
m_ip, m_port = addr['m_ip'], addr['m_port']
m_ip = c_uint32(m_ip.to_bytes(), endian='big')
elif len(addr["addr"]) == 16:
m_ip, m_port = addr['addr'], addr["m_port"]
m_ip = c_uint64(m_ip, endian="big")
else:
continue
peers.append({
'ip': m_ip,
'port': m_port
})
return peers