├── levin ├── __version__.py ├── __init__.py ├── exceptions.py ├── utils.py ├── section.py ├── writer.py ├── constants.py ├── reader.py ├── bucket.py └── ctypes.py ├── .gitignore ├── LICENSE ├── setup.py ├── peer_retreiver.py └── README.md /levin/__version__.py: -------------------------------------------------------------------------------- 1 | __version__='0.1.0' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *__pycache__* 2 | .cache 3 | .env 4 | htmlcov 5 | .coverage 6 | .idea 7 | .autoenv 8 | .venv 9 | .vscode -------------------------------------------------------------------------------- /levin/__init__.py: -------------------------------------------------------------------------------- 1 | from levin.reader import LevinReader 2 | from levin.writer import LevinWriter 3 | from levin.section import Section 4 | from levin.bucket import Bucket 5 | -------------------------------------------------------------------------------- /levin/exceptions.py: -------------------------------------------------------------------------------- 1 | class BadPortableStorageSignature(Exception): 2 | def __init__(self, msg=None): 3 | super(BadPortableStorageSignature, self).__init__(msg) 4 | 5 | 6 | class BadArgumentException(Exception): 7 | def __init__(self, msg=None): 8 | super(BadArgumentException, self).__init__(msg) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2018 Wownero Inc., a Monero Enterprise Alliance partner company 5 | 6 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 7 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 8 | 9 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /levin/utils.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | import socket 4 | import ipaddress 5 | import struct 6 | from io import BytesIO 7 | 8 | 9 | def ip2int(addr): 10 | return struct.unpack("!I", socket.inet_aton(addr))[0] 11 | 12 | 13 | def int2ip(addr): 14 | return ipaddress.IPv4Address(addr) 15 | 16 | 17 | def rshift(val, n): 18 | # 32bit rightshift 19 | return (val % 0x100000000) >> n 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | 5 | here = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | # Taken from Requests setup.py: https://github.com/psf/requests/blob/master/setup.py#L61 8 | with open(os.path.join(here, 'levin', '__version__.py'), 'r') as f: 9 | __version__ = None 10 | exec(f.read()) 11 | 12 | 13 | setup(name='py-levin', 14 | version=__version__, 15 | description='Levin client', 16 | author='xmrdsc', 17 | url='https://github.com/xmrdsc/py-levin', 18 | packages=['levin'], 19 | license='2018 WTFPL – Do What the Fuck You Want to Public License' 20 | ) -------------------------------------------------------------------------------- /peer_retreiver.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import socket 3 | from levin.section import Section 4 | from levin.bucket import Bucket 5 | from levin.ctypes import * 6 | from levin.constants import P2P_COMMANDS, LEVIN_SIGNATURE 7 | 8 | args = sys.argv 9 | if len(args) != 3: 10 | print('./%s ip port') 11 | sys.exit() 12 | 13 | host, ip = args[1], int(args[2]) 14 | 15 | try: 16 | sock = socket.socket() 17 | sock.connect((host, ip)) 18 | except: 19 | sys.stderr.write("unable to connect to %s:%d\n" % (host, ip)) 20 | sys.exit() 21 | 22 | bucket = Bucket.create_handshake_request() 23 | 24 | sock.send(bucket.header()) 25 | sock.send(bucket.payload()) 26 | 27 | # print(">> sent packet \'%s\'" % P2P_COMMANDS[bucket.command]) 28 | 29 | buckets = [] 30 | 31 | while 1: 32 | buffer = sock.recv(8) 33 | if not buffer: 34 | sys.stderr.write("Invalid response; exiting\n") 35 | break 36 | 37 | if not buffer.startswith(bytes(LEVIN_SIGNATURE)): 38 | sys.stderr.write("Invalid response; exiting\n") 39 | break 40 | 41 | bucket = Bucket.from_buffer(signature=buffer, sock=sock) 42 | buckets.append(bucket) 43 | 44 | if bucket.command == 1001: 45 | peers = bucket.get_peers() or [] 46 | 47 | for peer in peers: 48 | try: 49 | print('%s:%d' % (peer['ip'].ip, peer['port'].value)) 50 | except: 51 | pass 52 | 53 | sock.close() 54 | break 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # py-levin 2 | 3 | > The Levin network protocol is an implementation of peer-to-peer (P2P) communications found in a large number of cryptocurrencies, including basically any cryptocurrency that is a descendant from the CryptoNote project. 4 | 5 | ## Example usage: 6 | 7 | Ask a node for it's peer list, sorted on `last_seen`: 8 | 9 | `python3 peer_retreiver.py 212.83.175.67 18080` 10 | 11 | ```python 12 | >> created packet 'handshake' 13 | >> sent packet 'handshake' 14 | << received packet 'support_flags' 15 | << parsed packet 'support_flags' 16 | << received packet 'handshake' 17 | << parsed packet 'handshake' 18 | 19 | 99.48.XX.XX:18080 20 | 62.24.XX.XX:18080 21 | 93.44.XX.XX:18080 22 | 238.137.XX.XX:18080 23 | 163.86.XX.XX:18080 24 | 250.227.XX.XX:18080 25 | [...] 26 | ``` 27 | 28 | ### Some notes 29 | 30 | A Levin header (33 bytes) looks like this: 31 | 32 | 1. `u_int64` - A levin signature. `01 21 01 01 01 01 01 01` also known as 'Benders nightmare'. 33 | 2. `u_int64` - Payload size as an indicator of how many bytes to read from the network. 34 | 3. `bool` - Recipient should return data. 35 | 4. `u_int32` - The command. `e9 03 00 00` - Possible commands: `1001`, `1007`, etc. See `constants.py`. 36 | 5. `int32` - Return code. 37 | 6. `u_int32` - Flags. 38 | 5. `u_int32` - Protocol version. Usually `01` 39 | 40 | The payload that comes after that is a serialized Boost struct, which can be found 41 | in the Monero codebase, e.g: [cryptonote_protocol_defs.h](https://github.com/monero-project/monero/blob/58ce16d4d91518b674a5d46419809f56f71d9114/src/cryptonote_protocol/cryptonote_protocol_defs.h). 42 | The terminology for a (possible nested collection) of structs is called a `Section()` in this module. 43 | 44 | For the actual serialization from the perspective of Monero, check out [portable_storage_to_bin.h](https://github.com/monero-project/monero/blob/58ce16d4d91518b674a5d46419809f56f71d9114/contrib/epee/include/storages/portable_storage_to_bin.h) 45 | 46 | For deserialization look at `Reader.read_storage_entry()` and `Reader.read()`. 47 | 48 | Data is almost always in little-endian byte order, with the exception of keys (strings) for values in serialized structs. 49 | 50 | Lastly, this module is presented as 'best effort' and, for example, does not guarantee that all Levin data types are supported. 51 | 52 | ### References 53 | - [Monerujo](https://github.com/m2049r/xmrwallet/tree/master/app/src/main/java/com/m2049r/levin) 54 | - [Monero codebase](https://github.com/monero-project/monero) 55 | - [CVE-2018-3972](https://www.talosintelligence.com/reports/TALOS-2018-0637/) 56 | 57 | ### Thanks 58 | 59 | - Big thanks to [m2049r](https://github.com/m2049r/) for his Java implementation of Levin on Monerujo (remote node finder). 60 | - notmike 61 | 62 | ### License 63 | 64 | © 2018 WTFPL – Do What the Fuck You Want to Public License -------------------------------------------------------------------------------- /levin/section.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | import random 3 | from io import BytesIO 4 | from collections import OrderedDict 5 | 6 | from levin.constants import * 7 | from levin.ctypes import * 8 | 9 | 10 | class Section: 11 | def __init__(self): 12 | self.entries = OrderedDict() 13 | 14 | def add(self, key: str, entry: object): 15 | self.entries[key] = entry 16 | 17 | def __len__(self): 18 | return len(self.entries.keys()) 19 | 20 | @classmethod 21 | def from_byte_array(cls, buffer: BytesIO): 22 | from levin.reader import LevinReader 23 | x = LevinReader(buffer) 24 | section = x.read_payload() 25 | return section 26 | 27 | @classmethod 28 | def handshake_request(cls, my_port: int = 0, network_id: bytes = None, peer_id: bytes = None): 29 | if not network_id: 30 | network_id = bytes.fromhex("1230f171610441611731008216a1a110") # mainnet 31 | if not peer_id: 32 | peer_id = random.getrandbits(64) 33 | 34 | section = cls() 35 | node_data = Section() 36 | # node_data.add("local_time", c_uint64(0x4141414141414141)) 37 | node_data.add("local_time", c_uint64(int(time()))) 38 | node_data.add("my_port", c_uint32(my_port)) 39 | node_data.add("network_id", c_string(network_id)) 40 | node_data.add("peer_id", c_uint64(peer_id)) 41 | section.add("node_data", node_data) 42 | 43 | payload_data = Section() 44 | payload_data.add("cumulative_difficulty", c_uint64(1)) 45 | payload_data.add("current_height", c_uint64(1)) 46 | genesis_hash = bytes.fromhex("418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3") # genesis 47 | payload_data.add("top_id", c_string(genesis_hash)) 48 | payload_data.add("top_version", c_ubyte(1)) 49 | section.add("payload_data", payload_data) 50 | return section 51 | 52 | @classmethod 53 | def create_flags_response(cls): 54 | section = cls() 55 | section.add("support_flags", P2P_SUPPORT_FLAGS) 56 | return section 57 | 58 | @classmethod 59 | def stat_info_request(cls, peer_id: bytes = None): 60 | if not peer_id: 61 | peer_id = random.getrandbits(64) 62 | 63 | signature = bytes.fromhex( 64 | "418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3" 65 | ) 66 | 67 | section = cls() 68 | proof_of_trust = Section() 69 | proof_of_trust.add("peer_id", c_uint64(peer_id)) 70 | proof_of_trust.add("time", c_uint64(int(time()))) 71 | proof_of_trust.add("sign", c_string(signature)) 72 | 73 | section.add("proof_of_trust", proof_of_trust) 74 | 75 | return section 76 | 77 | def __bytes__(self): 78 | from levin.writer import LevinWriter 79 | 80 | writer = LevinWriter() 81 | buffer = writer.write_payload(self) 82 | buffer.seek(0) 83 | return buffer.read() 84 | -------------------------------------------------------------------------------- /levin/writer.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | 3 | from levin.section import Section 4 | from levin.exceptions import BadArgumentException 5 | from levin.constants import * 6 | from levin.ctypes import * 7 | 8 | 9 | class LevinWriter: 10 | def __init__(self, buffer: BytesIO = None): 11 | self.buffer = buffer 12 | self._written = 0 13 | if not self.buffer: 14 | self.buffer = BytesIO() 15 | 16 | def write_payload(self, section): 17 | self.write(bytes(PORTABLE_STORAGE_SIGNATUREA)) 18 | self.write(bytes(PORTABLE_STORAGE_SIGNATUREB)) 19 | self.write(bytes(PORTABLE_STORAGE_FORMAT_VER)) 20 | self.put_section(section) 21 | return self.buffer 22 | 23 | def put_section(self, section: Section): 24 | self.write_var_in(len(section)) 25 | for k, v in section.entries.items(): 26 | _k = k.encode('ascii') 27 | self.write(bytes(c_ubyte(len(_k)))) 28 | self.write(_k) 29 | self.serialized_write(v) 30 | 31 | def write_section(self, section): 32 | self.write(bytes(SERIALIZE_TYPE_OBJECT)) 33 | self.put_section(section) 34 | 35 | def serialized_write(self, data): 36 | _types = { 37 | c_uint64: SERIALIZE_TYPE_UINT64, 38 | c_int64: SERIALIZE_TYPE_INT64, 39 | c_uint32: SERIALIZE_TYPE_UINT32, 40 | c_int32: SERIALIZE_TYPE_INT32, 41 | c_uint16: SERIALIZE_TYPE_UINT16, 42 | c_int16: SERIALIZE_TYPE_INT16, 43 | c_string: SERIALIZE_TYPE_STRING, 44 | c_ubyte: SERIALIZE_TYPE_UINT8, 45 | c_byte: SERIALIZE_TYPE_INT8 46 | } 47 | 48 | if type(data) in _types: 49 | self.write(bytes(_types[type(data)])) 50 | 51 | if isinstance(data, c_string): 52 | self.write_var_in(len(data)) 53 | 54 | self.write(bytes(data)) 55 | elif isinstance(data, Section): 56 | self.write_section(data) 57 | else: 58 | raise BadArgumentException("Unable to cast input to serialized data") 59 | 60 | def write_var_in(self, i: int): 61 | # contrib/epee/include/storages/portable_storage_to_bin.h:pack_varint 62 | 63 | if i <= 63: 64 | out = (i << 2) | PORTABLE_RAW_SIZE_MARK_BYTE.value 65 | self.write(bytes(c_ubyte(out))) 66 | elif i <= 16383: 67 | out = (i << 2) | PORTABLE_RAW_SIZE_MARK_WORD.value 68 | self.write(bytes(c_uint16(out))) 69 | elif i <= 1073741823: 70 | out = (i << 2) | PORTABLE_RAW_SIZE_MARK_DWORD.value 71 | self.write(bytes(c_uint32(out))) 72 | else: 73 | if i > 4611686018427387903: 74 | raise BadArgumentException("failed to pack varint - too big amount") 75 | out = (i << 2) | PORTABLE_RAW_SIZE_MARK_INT64.value 76 | self.write(bytes(c_uint64(out))) 77 | 78 | def write(self, data, tt=None): 79 | self.buffer.write(data) 80 | self._written += len(data) 81 | -------------------------------------------------------------------------------- /levin/constants.py: -------------------------------------------------------------------------------- 1 | from levin.ctypes import * 2 | 3 | LEVIN_OK = c_ubyte(0x00) 4 | LEVIN_SIGNATURE = c_uint64(0x0101010101012101) # Bender's nightmare 5 | LEVIN_PACKET_REQUEST = c_uint32(0x00000001) 6 | LEVIN_PACKET_RESPONSE = c_uint32(0x00000002) 7 | LEVIN_DEFAULT_MAX_PACKET_SIZE = 100000000 # 100MB 8 | LEVIN_PROTOCOL_VER_1 = c_uint32(1) 9 | LEVIN_ERROR_CONNECTION = -1 10 | LEVIN_ERROR_CONNECTION_NOT_FOUND = -2 11 | LEVIN_ERROR_CONNECTION_DESTROYED = -3 12 | LEVIN_ERROR_CONNECTION_TIMEDOUT = -4 13 | LEVIN_ERROR_CONNECTION_NO_DUPLEX_PROTOCOL = -5 14 | LEVIN_ERROR_CONNECTION_HANDLER_NOT_DEFINED = -6 15 | LEVIN_ERROR_FORMAT = -7 16 | 17 | P2P_SUPPORT_FLAG_FLUFFY_BLOCKS = c_ubyte(0x01) 18 | P2P_SUPPORT_FLAGS = P2P_SUPPORT_FLAG_FLUFFY_BLOCKS 19 | P2P_COMMANDS_POOL_BASE = c_uint32(1000) 20 | P2P_COMMAND_HANDSHAKE = c_uint32(P2P_COMMANDS_POOL_BASE + 1) # carries payload 21 | P2P_COMMAND_TIMED_SYNC = c_uint32(P2P_COMMANDS_POOL_BASE + 2) # carries payload 22 | P2P_COMMAND_PING = c_uint32(P2P_COMMANDS_POOL_BASE + 3) 23 | P2P_COMMAND_REQUEST_STAT_INFO = c_uint32(P2P_COMMANDS_POOL_BASE + 4) 24 | P2P_COMMAND_REQUEST_NETWORK_STATE = c_uint32(P2P_COMMANDS_POOL_BASE + 5) 25 | P2P_COMMAND_REQUEST_PEER_ID = c_uint32(P2P_COMMANDS_POOL_BASE + 6) 26 | P2P_COMMAND_REQUEST_SUPPORT_FLAGS = c_uint32(P2P_COMMANDS_POOL_BASE + 7) 27 | P2P_COMMANDS = { 28 | P2P_COMMAND_HANDSHAKE: 'handshake', 29 | P2P_COMMAND_TIMED_SYNC: 'timed_sync', 30 | P2P_COMMAND_PING: 'ping', 31 | P2P_COMMAND_REQUEST_SUPPORT_FLAGS: 'support_flags', 32 | P2P_COMMAND_REQUEST_STAT_INFO: 'stat_info', 33 | P2P_COMMAND_REQUEST_NETWORK_STATE: 'network_state', 34 | P2P_COMMAND_REQUEST_PEER_ID: 'peer_id', 35 | } 36 | 37 | PORTABLE_STORAGE_SIGNATUREA = c_uint32(0x01011101) 38 | PORTABLE_STORAGE_SIGNATUREB = c_uint32(0x01020101) 39 | PORTABLE_STORAGE_FORMAT_VER = c_ubyte(1) 40 | PORTABLE_RAW_SIZE_MARK_MASK = c_ubyte(3) 41 | PORTABLE_RAW_SIZE_MARK_BYTE = c_ubyte(0) 42 | PORTABLE_RAW_SIZE_MARK_WORD = c_ubyte(1) 43 | PORTABLE_RAW_SIZE_MARK_DWORD = c_ubyte(2) 44 | PORTABLE_RAW_SIZE_MARK_INT64 = c_ubyte(3) 45 | 46 | MAX_STRING_LEN_POSSIBLE = 2000000000 # do not let string be so big 47 | 48 | # data types (boost serialization) 49 | SERIALIZE_TYPE_INT64 = c_ubyte(1) 50 | SERIALIZE_TYPE_INT32 = c_ubyte(2) 51 | SERIALIZE_TYPE_INT16 = c_ubyte(3) 52 | SERIALIZE_TYPE_INT8 = c_ubyte(4) 53 | SERIALIZE_TYPE_UINT64 = c_ubyte(5) 54 | SERIALIZE_TYPE_UINT32 = c_ubyte(6) 55 | SERIALIZE_TYPE_UINT16 = c_ubyte(7) 56 | SERIALIZE_TYPE_UINT8 = c_ubyte(8) 57 | SERIALIZE_TYPE_DOUBLE = c_ubyte(9) 58 | SERIALIZE_TYPE_STRING = c_ubyte(10) 59 | SERIALIZE_TYPE_BOOL = c_ubyte(11) 60 | SERIALIZE_TYPE_OBJECT = c_ubyte(12) 61 | SERIALIZE_TYPE_ARRAY = c_ubyte(13) 62 | SERIALIZE_FLAG_ARRAY = c_ubyte(0x80) 63 | 64 | # (src/p2p/net_node.inl:715 in node_server::get_ip_seed_nodes) 65 | SEED_NODES = [ 66 | ("212.83.175.67", 18080), 67 | ("212.83.172.165", 18080), 68 | ("176.9.0.187", 18080), 69 | ("88.198.163.90", 18080), 70 | ("95.217.25.101", 18080), 71 | ("136.244.105.131", 18080), 72 | ("104.238.221.81", 18080), 73 | ("66.85.74.134", 18080), 74 | ("88.99.173.38", 18080), 75 | ("51.79.173.165", 18080) 76 | ] 77 | -------------------------------------------------------------------------------- /levin/reader.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from io import BytesIO 3 | 4 | from levin.utils import rshift 5 | from levin.exceptions import BadPortableStorageSignature 6 | from levin.ctypes import * 7 | from levin.constants import * 8 | 9 | 10 | class LevinReader: 11 | def __init__(self, buffer: BytesIO): 12 | if isinstance(buffer, bytes): 13 | buffer = BytesIO(buffer) 14 | self.buffer = buffer 15 | 16 | def read_payload(self): 17 | sig1 = c_uint32.from_buffer(self.buffer) 18 | sig2 = c_uint32.from_buffer(self.buffer) 19 | sig3 = c_ubyte.from_buffer(self.buffer) 20 | 21 | if sig1 != PORTABLE_STORAGE_SIGNATUREA: 22 | raise BadPortableStorageSignature() 23 | elif sig2 != PORTABLE_STORAGE_SIGNATUREB: 24 | raise BadPortableStorageSignature() 25 | elif sig3 != PORTABLE_STORAGE_FORMAT_VER: 26 | raise BadPortableStorageSignature() 27 | 28 | return self.read_section() 29 | 30 | def read_section(self): 31 | from levin.section import Section 32 | section = Section() 33 | count = self.read_var_int() 34 | 35 | while count > 0: 36 | section_name = self.read_section_name() 37 | storage_entry = self.load_storage_entry() 38 | section.add(section_name, storage_entry) 39 | count -= 1 40 | 41 | return section 42 | 43 | def read_section_name(self) -> str: 44 | len_name = c_ubyte.from_buffer(self.buffer) 45 | name = self.buffer.read(len_name.value) 46 | return name.decode('ascii') 47 | 48 | def load_storage_entry(self): 49 | _type = c_ubyte.from_buffer(self.buffer) 50 | 51 | if (_type & SERIALIZE_FLAG_ARRAY) != 0: 52 | return self.load_storage_array_entry(_type) 53 | if _type == SERIALIZE_TYPE_ARRAY: 54 | return self.read_storage_entry_array_entry() 55 | else: 56 | return self.read_storage_entry(_type) 57 | 58 | def read_storage_entry(self, _type: int): 59 | return self.read(_type=_type) 60 | 61 | def load_storage_array_entry(self, _type: int): 62 | _type &= ~SERIALIZE_FLAG_ARRAY.value 63 | return self.read_array_entry(_type) 64 | 65 | def read_storage_entry_array_entry(self): 66 | _type = c_ubyte.from_buffer(self.buffer) 67 | 68 | if (_type & SERIALIZE_FLAG_ARRAY) != 0: 69 | raise IOError("wrong type sequences") 70 | 71 | return self.load_storage_array_entry(_type) 72 | 73 | def read_array_entry(self, _type: int): 74 | data = [] 75 | size = self.read_var_int() 76 | 77 | while size > 0: 78 | data.append(self.read(_type=_type)) 79 | size -= 1 80 | return data 81 | 82 | def read(self, _type: int = None, count: int = None): 83 | if isinstance(count, int): 84 | if count > sys.maxsize: 85 | raise IOError() 86 | _data = self.buffer.read(count) 87 | return _data 88 | 89 | if _type == SERIALIZE_TYPE_UINT64: 90 | return c_uint64.from_buffer(self.buffer) 91 | elif _type == SERIALIZE_TYPE_INT64: 92 | return c_int64.from_buffer(self.buffer) 93 | elif _type == SERIALIZE_TYPE_UINT32: 94 | return c_uint32.from_buffer(self.buffer) 95 | elif _type == SERIALIZE_TYPE_INT32: 96 | return c_int32.from_buffer(self.buffer) 97 | elif _type == SERIALIZE_TYPE_UINT16: 98 | return c_uint16.from_buffer(self.buffer) 99 | elif _type == SERIALIZE_TYPE_INT16: 100 | return c_int16.from_buffer(self.buffer) 101 | elif _type == SERIALIZE_TYPE_UINT8: 102 | return c_ubyte.from_buffer(self.buffer) 103 | elif _type == SERIALIZE_TYPE_INT8: 104 | return c_byte.from_buffer(self.buffer) 105 | elif _type == SERIALIZE_TYPE_OBJECT: 106 | return self.read_section() 107 | elif _type == SERIALIZE_TYPE_STRING: 108 | return self.read_byte_array() 109 | 110 | def read_byte_array(self, count: int = None): 111 | if not isinstance(count, int): 112 | count = self.read_var_int() 113 | return self.read(count=count) 114 | 115 | def read_var_int(self): 116 | b = c_ubyte.from_buffer(self.buffer) 117 | size_mask = b & PORTABLE_RAW_SIZE_MARK_MASK 118 | 119 | if size_mask == PORTABLE_RAW_SIZE_MARK_BYTE: 120 | v = rshift(b, 2) 121 | elif size_mask == PORTABLE_RAW_SIZE_MARK_WORD: 122 | v = rshift(self.read_rest(b, 1), 2) 123 | elif size_mask == PORTABLE_RAW_SIZE_MARK_DWORD: 124 | v = rshift(self.read_rest(b, 3), 2) 125 | elif size_mask == PORTABLE_RAW_SIZE_MARK_INT64: 126 | v = rshift(self.read_rest(b, 7), 2) 127 | else: 128 | raise IOError('invalid var_int') 129 | return v 130 | 131 | def read_rest(self, first_byte: int, _bytes: int): 132 | result = first_byte 133 | for i in range(0, _bytes): 134 | result += (c_ubyte.from_buffer(self.buffer) << 8) 135 | 136 | return result 137 | -------------------------------------------------------------------------------- /levin/bucket.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | import struct 4 | import socket 5 | from io import BytesIO 6 | 7 | from levin.section import Section 8 | from levin.constants import * 9 | from levin.exceptions import BadArgumentException 10 | from levin.ctypes import * 11 | 12 | log = logging.getLogger() 13 | 14 | class Bucket: 15 | def __init__(self): 16 | self.signature = LEVIN_SIGNATURE 17 | if self.signature != LEVIN_SIGNATURE: 18 | raise BadArgumentException('Bender\'s nightmare') 19 | 20 | self.cb = None 21 | self.return_data = None 22 | self.command = None 23 | self.return_code = None 24 | self.flags = None 25 | self.protocol_version = None 26 | self.payload_section = None 27 | 28 | @classmethod 29 | def create_request(cls, command: int, payload: bytes = None, section: Section = None): 30 | bucket = cls() 31 | 32 | bucket.return_data = c_bool(True) 33 | bucket.command = c_uint32(command) 34 | bucket.return_code = c_uint32(0) 35 | bucket.flags = LEVIN_PACKET_REQUEST 36 | bucket.protocol_version = LEVIN_PROTOCOL_VER_1 37 | 38 | if payload: 39 | bucket.cb = c_uint64(len(payload)) 40 | bucket.payload_section = payload 41 | 42 | if section: 43 | bucket.payload_section = bytes(section) 44 | bucket.cb = c_uint64(len(bucket.payload_section)) 45 | 46 | return bucket 47 | 48 | @classmethod 49 | def create_response(cls, command: int, payload: bytes, return_code: int): 50 | bucket = cls() 51 | bucket.cb = c_uint64(len(payload)) 52 | bucket.return_data = c_bool(False) 53 | bucket.command = c_uint32(command) 54 | bucket.return_code = c_uint32(return_code) 55 | bucket.flags = LEVIN_PACKET_RESPONSE 56 | bucket.protocol_version = LEVIN_PROTOCOL_VER_1 57 | 58 | @staticmethod 59 | def create_handshake_request( 60 | my_port: int = 0, 61 | network_id: bytes = None, 62 | peer_id: bytes = b'\x41\x41\x41\x41\x41\x41\x41\x41', 63 | ): 64 | """ 65 | Helper function to create a handshake request. Does not require 66 | parameters but you can use them to impersonate a legit node. 67 | :param my_port: defaults to 0 68 | :param network_id: defaults to mainnet 69 | :param peer_id: 70 | :param verbose: 71 | :return: 72 | """ 73 | handshake_section = Section.handshake_request(peer_id=peer_id, network_id=network_id, my_port=my_port) 74 | bucket = Bucket.create_request(P2P_COMMAND_HANDSHAKE.value, section=handshake_section) 75 | 76 | log.debug(">> created packet '%s'" % P2P_COMMANDS[bucket.command]) 77 | 78 | header = bucket.header() 79 | body = bucket.payload() 80 | return bucket 81 | 82 | @staticmethod 83 | def create_stat_info_request( 84 | peer_id: bytes = b"\x41\x41\x41\x41\x41\x41\x41\x41", 85 | ): 86 | stat_info_section = Section.stat_info_request(peer_id=peer_id) 87 | log.debug(stat_info_section.entries["proof_of_trust"].entries.keys()) 88 | bucket = Bucket.create_request( 89 | P2P_COMMAND_REQUEST_STAT_INFO.value, section=stat_info_section 90 | ) 91 | 92 | log.debug(">> created packet '%s'" % P2P_COMMANDS[bucket.command]) 93 | return bucket 94 | 95 | @classmethod 96 | def from_buffer(cls, signature: c_uint64, sock: socket.socket): 97 | if isinstance(signature, bytes): 98 | signature = c_uint64(signature) 99 | # if isinstance(buffer, bytes): 100 | # buffer = BytesIO(buffer) 101 | bucket = cls() 102 | bucket.signature = signature 103 | bucket.cb = c_uint64.from_buffer(sock) 104 | bucket.return_data = c_bool.from_buffer(sock) 105 | bucket.command = c_uint32.from_buffer(sock) 106 | bucket.return_code = c_int32.from_buffer(sock) 107 | bucket.flags = c_uint32.from_buffer(sock) 108 | bucket.protocol_version = c_uint32.from_buffer(sock) 109 | 110 | if bucket.signature != LEVIN_SIGNATURE: 111 | raise IOError("Bender's nightmare missing") 112 | 113 | if bucket.cb > LEVIN_DEFAULT_MAX_PACKET_SIZE: 114 | raise IOError("payload too large") 115 | 116 | if bucket.command.value not in P2P_COMMANDS: 117 | raise IOError("unregonized command: %d" % bucket.command.value) 118 | 119 | bucket.payload = sock.recv(bucket.cb.value) 120 | 121 | while len(bucket.payload) < bucket.cb.value: 122 | bufsize = 2048 123 | remaining = bucket.cb.value - len(bucket.payload) 124 | if remaining < bufsize: 125 | bufsize = remaining 126 | bucket.payload += sock.recv(bufsize) 127 | 128 | bucket.payload_section = None 129 | 130 | log.debug("<< received packet '%s'" % P2P_COMMANDS[bucket.command]) 131 | 132 | from levin.reader import LevinReader 133 | bucket.payload_section = LevinReader(BytesIO(bucket.payload)).read_payload() 134 | 135 | log.debug("<< parsed packet '%s'" % P2P_COMMANDS[bucket.command]) 136 | return bucket 137 | 138 | def header(self): 139 | return b''.join(( 140 | bytes(LEVIN_SIGNATURE), 141 | bytes(self.cb), 142 | b'\x01' if self.return_data else b'\x00', 143 | bytes(self.command), 144 | bytes(self.return_code), 145 | bytes(self.flags), 146 | bytes(self.protocol_version) 147 | )) 148 | 149 | def payload(self): 150 | return self.payload_section 151 | 152 | def get_peers(self): 153 | # helper function to retreive peerlisting where buckets.command was 1001 154 | if self.command != 1001: 155 | raise Exception("Only handshake has peerlisting") 156 | 157 | peers = [] 158 | 159 | if 'local_peerlist_new' not in self.payload_section.entries: 160 | return 161 | 162 | for peer in [e.entries for e in self.payload_section.entries['local_peerlist_new']]: 163 | if 'adr' not in peer or 'addr' not in peer['adr'].entries: 164 | continue 165 | 166 | addr = peer['adr'].entries['addr'].entries 167 | ipv4 = True if "m_ip" in addr else False 168 | if not ipv4 and not "addr" in addr: 169 | continue 170 | 171 | if ipv4 and len(addr["m_ip"]) == 4: 172 | m_ip, m_port = addr['m_ip'], addr['m_port'] 173 | m_ip = c_uint32(m_ip.to_bytes(), endian='big') 174 | elif len(addr["addr"]) == 16: 175 | m_ip, m_port = addr['addr'], addr["m_port"] 176 | m_ip = c_uint64(m_ip, endian="big") 177 | else: 178 | continue 179 | 180 | peers.append({ 181 | 'ip': m_ip, 182 | 'port': m_port 183 | }) 184 | 185 | return peers 186 | -------------------------------------------------------------------------------- /levin/ctypes.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | import ipaddress 4 | from io import BytesIO 5 | from datetime import datetime 6 | from ctypes import ( 7 | c_ushort as _c_ushort, 8 | c_ubyte as _c_ubyte, 9 | c_uint8 as _c_uint8, 10 | c_uint16 as _c_uint16, 11 | c_uint32 as _c_uint32, 12 | c_uint64 as _c_uint64, 13 | c_short as _c_short, 14 | c_byte as _c_byte, 15 | c_int8 as _c_int8, 16 | c_int16 as _c_int16, 17 | c_int32 as _c_int32, 18 | c_int64 as _c_int64, 19 | ) 20 | 21 | from levin.exceptions import BadArgumentException 22 | 23 | 24 | class _CType: 25 | def __init__(self, value, endian=None): 26 | self.value = value 27 | self.endian = endian 28 | 29 | @classmethod 30 | def from_buffer(cls, buffer, endian: str = 'little', padding: bytes = None): 31 | if isinstance(buffer, BytesIO): 32 | buffer = buffer.read(cls.NBYTES) 33 | elif isinstance(buffer, socket.socket): 34 | buffer = buffer.recv(cls.NBYTES) 35 | 36 | if len(buffer) < cls.NBYTES: 37 | if not padding: 38 | _bytes = 'bytes' if cls.NBYTES > 1 else 'byte' 39 | raise BadArgumentException("requires a buffer of %d %s, received %d" % (cls.NBYTES, _bytes, len(buffer))) 40 | 41 | func_padding = bytes.ljust if endian == 'little' else bytes.rjust 42 | buffer = func_padding(buffer, cls.NBYTES, padding) 43 | 44 | _endian = '<' if endian == 'little' else '>' 45 | type_struct = cls.TYPE_STRUCT if hasattr(cls, 'TYPE_STRUCT') else cls.TYPE._type_ 46 | value = struct.unpack('%s%s' % (_endian, type_struct), buffer)[0] 47 | return cls(value, endian) 48 | 49 | def to_bytes(self): 50 | if isinstance(self.value, (int, bool)): 51 | type_struct = self.TYPE_STRUCT if hasattr(self, 'TYPE_STRUCT') else self.TYPE._type_ 52 | return struct.pack('%s%s' % ('<' if self.endian == 'little' else '>', type_struct), self.value) 53 | elif isinstance(self.value, bytes): 54 | if self.endian == 'little': 55 | return self.value[::-1] 56 | return self.value 57 | 58 | def _overflows(self): 59 | if isinstance(self.value, int): 60 | try: 61 | self.value.to_bytes(self.NBYTES, byteorder=self.endian, signed=self.SIGNED) 62 | except OverflowError as e: 63 | raise OverflowError("Value \'%d\' does not fit in %s." % (self.value, self.__class__.__name__)) 64 | 65 | def __len__(self): 66 | if isinstance(self.value, bytes): 67 | return len(self.value) 68 | if isinstance(self.value, int): 69 | return len(bytes(self)) 70 | 71 | return len(bytes(self.value)) 72 | 73 | def __eq__(self, other): 74 | if isinstance(self.value, int): 75 | return self.value == other 76 | 77 | def __ne__(self, other): 78 | if isinstance(other, _CType): 79 | other = other.value 80 | 81 | if isinstance(self.value, int): 82 | return self.value != other 83 | 84 | def __and__(self, other): 85 | if isinstance(other, _CType): 86 | other = other.value 87 | 88 | if isinstance(self.value, int): 89 | # Bitwise AND 90 | return self.value & other 91 | raise NotImplementedError() 92 | 93 | def __lshift__(self, other): 94 | if isinstance(other, _CType): 95 | other = other.value 96 | 97 | if isinstance(self.value, int): 98 | # Bitwise Left Shift 99 | return self.value << other 100 | raise NotImplementedError() 101 | 102 | def __rshift__(self, other): 103 | if isinstance(other, _CType): 104 | other = other.value 105 | 106 | if isinstance(self.value, int): 107 | # Bitwise Right Shift 108 | return self.value >> other 109 | raise NotImplementedError() 110 | 111 | def __rlshift__(self, other): 112 | if isinstance(other, _CType): 113 | other = other.value 114 | 115 | if isinstance(self.value, int): 116 | # Bitwise Right Shift 117 | return self.value << other 118 | raise NotImplementedError() 119 | 120 | def __rrshift__(self, other): 121 | if isinstance(other, _CType): 122 | other = other.value 123 | 124 | if isinstance(self.value, int): 125 | # Bitwise Left Shift 126 | return self.value >> other 127 | raise NotImplementedError() 128 | 129 | def __or__(self, other): 130 | if isinstance(other, _CType): 131 | other = other.value 132 | 133 | if isinstance(self.value, int): 134 | # Bitwise OR 135 | return self.value | other 136 | raise NotImplementedError() 137 | 138 | def __mod__(self, other): 139 | if isinstance(other, _CType): 140 | other = other.value 141 | 142 | if isinstance(self.value, int): 143 | return self.value % other 144 | 145 | def __add__(self, other): 146 | if isinstance(other, _CType): 147 | other = other.value 148 | 149 | if isinstance(self.value, int): 150 | return self.value + other 151 | 152 | def __radd__(self, other): 153 | if isinstance(other, _CType): 154 | other = other.value 155 | 156 | if isinstance(self.value, int): 157 | return self.value + other 158 | 159 | def __gt__(self, other): 160 | if isinstance(other, _CType): 161 | other = other.value 162 | 163 | if isinstance(self.value, int): 164 | return self.value > other 165 | 166 | def __lt__(self, other): 167 | if isinstance(other, _CType): 168 | other = other.value 169 | 170 | if isinstance(self.value, int): 171 | return self.value < other 172 | 173 | def __bytes__(self): 174 | return self.to_bytes() 175 | 176 | def __hash__(self): 177 | return self.value 178 | 179 | def __int__(self): 180 | return self.value 181 | 182 | 183 | class _IntType(_CType): 184 | def __init__(self, value, endian='little'): 185 | super(_IntType, self).__init__(value, endian) 186 | self._overflows() 187 | 188 | def __repr__(self): 189 | _signed = 'unsigned' if not self.SIGNED else 'signed' 190 | _bytes = 'bytes' if self.NBYTES > 1 else 'byte' 191 | _cls_name = self.__class__.__name__ 192 | return '\'%d\' - %s - %s %d %s' % (self.value, _cls_name, _signed, self.NBYTES, _bytes) 193 | 194 | 195 | class c_int16(_IntType): 196 | TYPE = _c_short 197 | NBYTES = 2 198 | SIGNED = True 199 | 200 | def __init__(self, value, endian='little'): 201 | super(c_int16, self).__init__(value, endian) 202 | 203 | 204 | class c_uint16(_IntType): 205 | TYPE = _c_ushort 206 | NBYTES = 2 207 | SIGNED = False 208 | 209 | def __init__(self, value, endian='little'): 210 | super(c_uint16, self).__init__(value, endian) 211 | 212 | 213 | class c_int32(_IntType): 214 | TYPE = _c_uint32 215 | NBYTES = 4 216 | SIGNED = True 217 | 218 | def __init__(self, value, endian='little'): 219 | super(c_int32, self).__init__(value, endian) 220 | 221 | 222 | class c_uint32(_IntType): 223 | TYPE = _c_uint32 224 | NBYTES = 4 225 | SIGNED = False 226 | 227 | def __init__(self, value, endian='little'): 228 | super(c_uint32, self).__init__(value, endian) 229 | 230 | @property 231 | def ipv4(self) -> str: 232 | return str(ipaddress.IPv4Address(self.value)) 233 | 234 | @property 235 | def ip(self) -> str: 236 | return self.ipv4 237 | 238 | 239 | class c_int64(_IntType): 240 | TYPE = _c_uint64 241 | TYPE_STRUCT = 'q' 242 | NBYTES = 8 243 | SIGNED = True 244 | 245 | def __init__(self, value, endian='little'): 246 | super(c_int64, self).__init__(value, endian) 247 | 248 | @property 249 | def date_utc(self): 250 | return datetime.utcfromtimestamp(self.value) 251 | 252 | 253 | class c_uint64(_IntType): 254 | TYPE = _c_uint64 255 | TYPE_STRUCT = 'Q' 256 | NBYTES = 8 257 | SIGNED = False 258 | 259 | def __init__(self, value, endian='little'): 260 | super(c_uint64, self).__init__(value, endian) 261 | 262 | @property 263 | def ipv6(self) -> str: 264 | val = socket.inet_ntop(socket.AF_INET6, self.value) 265 | if val.startswith("::ffff:"): 266 | val = val[7:] 267 | return val 268 | 269 | @property 270 | def ip(self) -> str: 271 | return self.ipv6 272 | 273 | @property 274 | def date_utc(self): 275 | return datetime.utcfromtimestamp(self.value) 276 | 277 | 278 | class c_byte(_IntType): 279 | TYPE = _c_byte 280 | NBYTES = 1 281 | SIGNED = True 282 | 283 | def __init__(self, value, endian='little'): 284 | super(c_byte, self).__init__(value, endian) 285 | 286 | 287 | class c_bytes(_CType): 288 | def __init__(self, value, endian='little'): 289 | super(c_bytes, self).__init__(value, endian) 290 | 291 | 292 | class c_ubyte(_IntType): 293 | TYPE = _c_ubyte 294 | NBYTES = 1 295 | SIGNED = False 296 | 297 | def __init__(self, value, endian='little'): 298 | super(c_ubyte, self).__init__(value, endian) 299 | 300 | 301 | class c_string(_CType): 302 | ENCODING = 'ascii' 303 | 304 | def __init__(self, value): 305 | super(c_string, self).__init__(value) 306 | if isinstance(value, bytes): 307 | self.NBYTES = len(value) 308 | else: 309 | self.NBYTES = len(self.value.encode(self.ENCODING)) 310 | 311 | 312 | class c_bool(_CType): 313 | TYPE_STRUCT = '?' 314 | NBYTES = 1 315 | 316 | def __init__(self, value, endian=None): 317 | if value not in [True, False]: 318 | raise BadArgumentException('bool') 319 | super(c_bool, self).__init__(value) 320 | --------------------------------------------------------------------------------