def run(self): # Loop through the list of peers and send heartbeat messages while True and not self.stop.is_set(): for target, last_beat in list(daemon.peers.items()): if (last_beat + timedelta(seconds=settings.MSG_HB_TTL) ) < datetime.now(): # Check for dead peers with lock: utils.log_message( "Removing dead peer {0}".format(target), utils.Level.MEDIUM) del daemon.peers[target] else: # Send heartbeat with root id and tail id to peers s = socket(AF_INET, SOCK_DGRAM) message_body = { "ledger": daemon.ledger.id, "tail": daemon.ledger.tail.hash } message = Message(settings.MSG_TYPE_HB, message_body).__repr__() s.sendto(message.encode(), (target, settings.BIND_PORT)) utils.log_message("Heartbeat sent to {0}".format(target), utils.Level.LOW) s.close() # Sleep the required time between heartbeats time.sleep(settings.MSG_HB_FREQ)
def __init__(self, ip, port): super(UDPListener, self).__init__() with lock: utils.log_message("Starting UDP Listener Thread") self.daemon = True self._port = port self._ip = ip self.stop = threading.Event()
def _respond(self, message): with lock: utils.log_message("Responded with message to {}".format( self._socket.getsockname())) utils.log_message(message, utils.Level.MEDIUM) self._socket.sendall(message.encode()) self._socket.shutdown(SHUT_WR) self._socket.recv(4096) self._socket.close()
def __init__(self, target, message, timeout=5): super(TCPMessageThread, self).__init__() with lock: utils.log_message( "Sending Message to {0} {1}: {2}{3}".format( target[0], target[1], message[:10], '...'), utils.Level.MEDIUM) self._target = target self.message = message self._timeout = timeout
def __init__(self, ip=settings.BIND_IP, port=settings.BIND_PORT): super(TCPListener, self).__init__() with lock: utils.log_message("Starting TCP Listener Thread") self.daemon = True self._port = port self._ip = ip self.stop = threading.Event() self.stop.clear() self.tcp_server_socket = socket(AF_INET, SOCK_STREAM)
def discover(ip='<broadcast>', port=settings.BIND_PORT, timeout=settings.DISCOVERY_TIMEOUT): utils.log_message("Starting Discovery for {} seconds".format(timeout)) results = dict() # Get our IP address - I don't like this hack but it works # https://stackoverflow.com/questions/24196932/how-can-i-get-the-ip-address-of-eth0-in-python/24196955 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.connect((ip, port)) ip_self = s.getsockname()[0] s.close() # Send out discovery query s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) message = messaging.Message(settings.MSG_TYPE_DISCOVER).__repr__() s.sendto(message.encode(), (ip, port)) try: # Listen for responses for 10 seconds s.settimeout(timeout) while True: data, address = s.recvfrom(4096) try: message = json.loads(data.decode(), object_hook=utils.message_decoder) if message.msg_type == settings.MSG_TYPE_SUCCESS: utils.log_message( "Discovered ledger {0} at {1}".format( message.msg, address), utils.Level.MEDIUM) # Received response # Is the response our own ledger? if address[0] == ip_self: continue # Is the hash already in our list? if message.msg not in results: # If hash isn't in the list, create a new set and add address to it results[message.msg] = set() # Since there's already a set for our hash, we add to it results[message.msg].add(address) except: utils.log_message("Malformed response from {0}: {1}".format( data, address)) except OSError as e: utils.log_message("Exception: {0}".format(e)) finally: s.close() return results
def do_init(self, args): """Initialize the ledger with a provided Root of Trust (RSA Public Key) Arguments: gen: Generate an RSA key locally - to save to disk, follow with save path private_key: Provide a PEM private key string path: Path to PEM private key """ # Give error with no key, use default key, or provide if len(args) == 0: print("Please provide an RSA key as your new Root of Trust.") return else: args_list = args.split() if args_list[0].lower() == "gen": # Generate an RSA key if len(args_list) == 1: # Generate a RSA key in memory privkey = utils.gen_privkey() else: # Generate and save RSA key privkey = utils.gen_privkey(True, args_list[0]) else: # Try to import provided key privkey = utils.get_key(args) if privkey is None: print("Could not import the provided key") return # If we made it this far we have a valid key # Store generated key in our daemon for now daemon.create_ledger(privkey) hash = daemon.ledger.id print("\nPublic Key Hash: {0}".format(hash)) utils.log_message( "Added key ({0}) as a new Root of Trust".format(hash), utils.Level.FORCE) self.update_prompt()
def peer_sync(target): utils.log_message("Requesting peers from {0}".format(target), utils.Level.MEDIUM) ledger_message = Message(settings.MSG_TYPE_PEER, None).prep_tcp() thread = TCPMessageThread(target, ledger_message) thread.start() thread.join() message = json.loads(thread.message, object_hook=utils.message_decoder) for peer in message.msg: daemon.peers[peer] = datetime.now() daemon.peers[target[0]] = datetime.now() utils.log_message( "Successfully synchronized {} peer(s) from {}".format( len(message.msg), target), utils.Level.MEDIUM)
def run(self): # Listen for ledger client connection requests with lock: utils.log_message( "Listening for ledger messages on port {0}".format(self._port)) try: self.tcp_server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) self.tcp_server_socket.setblocking(False) self.tcp_server_socket.bind((self._ip, self._port)) self.tcp_server_socket.listen(5) # List for managing spawned threads socket_threads = [] # Non-blocking socket loop that can be interrupted with a signal/event while True and not self.stop.is_set(): try: client_socket, address = self.tcp_server_socket.accept() # Spawn thread client_thread = TCPConnectionThread(client_socket) client_thread.start() socket_threads.append(client_thread) except Exception as e: continue # Clean up all the threads for thread in socket_threads: thread.join() except Exception as e: print("Could not bind to port: {0}".format(e)) finally: self.tcp_server_socket.close()
def join_ledger(public_key_hash, member): global ledger # Check to make sure we aren't part of a ledger yet if joined(): print("You are already a member of a ledger") return utils.log_message("Spawning TCP Connection Thread to {0}".format(member)) join_message = messaging.Message(settings.MSG_TYPE_JOIN, public_key_hash).prep_tcp() thread = messaging.TCPMessageThread(member, join_message) thread.start() thread.join() # If the message is a success, import the key try: message = json.loads(thread.message, object_hook=utils.message_decoder) if message.msg_type == settings.MSG_TYPE_SUCCESS: key = utils.get_key(message.msg) key_hash = utils.gen_hash(utils.encode_key(key)) if public_key_hash == key_hash: # Hooray! We have a match utils.log_message("Joined ledger {}".format(public_key_hash), utils.Level.FORCE) # Sync Ledger messaging.block_sync(member) # Request peers messaging.peer_sync(member) # Start Listeners ledger_listeners(True) else: raise ValueError( 'Public key returned does not match requested hash: {0}'. format(key_hash)) else: raise ValueError('Response was not as expected: {0}'.format( message.msg_type)) except (ValueError, TypeError) as e: utils.log_message("Not a valid response from {0}: {1}".format( member, e))
def run(self): tcp_message_socket = socket(AF_INET, SOCK_STREAM) tcp_message_socket.settimeout(self._timeout) try: tcp_message_socket.connect(self._target) tcp_message_socket.sendall(self.message.encode()) # Get response self.message = '' message_size = None while True: if message_size is None: data = tcp_message_socket.recv(settings.MSG_SIZE_BYTES) # Convert first message size to an integer message_size = int(data.decode()) elif len(self.message) < message_size: data = tcp_message_socket.recv(4096) self.message += data.decode() else: break except ValueError as e: with lock: utils.log_message('Received invalid response from {0}'.format( tcp_message_socket.getsockname())) except Exception as e: with lock: utils.log_message( 'Could not send or receive message to or from the ledger at {0}:\n{1}\n{2}' .format(tcp_message_socket.getsockname()[0], self.message, e)) else: with lock: utils.log_message( "Received Response from {0} {1}: {2}{3}".format( self._target[0], self._target[1], self.message[:10], '...'), utils.Level.MEDIUM) finally: tcp_message_socket.close()
def ledger_listeners(start): global _udp_thread, _udp_hb_thread, _tcp_thread if start: # Spawn UDP Persistent Listener thread _udp_thread = messaging.UDPListener(settings.BIND_IP, settings.BIND_PORT) _udp_thread.start() # Spawn TCP Listener thread _tcp_thread = messaging.TCPListener(settings.BIND_IP, settings.BIND_PORT) _tcp_thread.start() # Spawn UDP Heartbeat thread _udp_hb_thread = messaging.UDPHeartbeat() _udp_hb_thread.start() else: # Kill udp listener thread if _udp_thread is not None: utils.log_message("Killing UDP Listening Thread...") _udp_thread.stop.set() _udp_thread.join() _udp_thread = None # Kill tcp listener thread if _tcp_thread is not None: utils.log_message("Killing TCP Listening Thread...") _tcp_thread.stop.set() _tcp_thread.join() _tcp_thread = None # Kill udp hb thread if _udp_hb_thread is not None: utils.log_message("Killing Heartbeat Thread...") _udp_hb_thread.stop.set() _udp_hb_thread.join() _udp_hb_thread = None
def block_sync(target, block_hash=None): utils.log_message("Requesting blocks from {0}".format(target), utils.Level.MEDIUM) ledger_message = Message(settings.MSG_TYPE_LEDGER, block_hash).prep_tcp() thread = TCPMessageThread(target, ledger_message) thread.start() thread.join() message = json.loads(thread.message, object_hook=utils.message_decoder) # Add received blocks to our ledger if daemon.ledger is None: daemon.ledger = ledger.Ledger() try: for block in message.msg: daemon.ledger.append(block) except ValueError as e: utils.log_message(e) utils.log_message( "Successfully synchronized {} block(s) from {}".format( len(message.msg), target), utils.Level.HIGH)
def do_discover(self, args): """Attempt to discover other ledgers. Arguments: peers: include to discover peers on the same ledger and add them to your peer list cached: include to utilize the cache ip: provide an ip address otherwise the local broadcast will be used. """ args = args.lower().strip().split() peers = False cached = False ip = '<broadcast>' # Parse the arguments if 'peers' in args: # Check that we're even a member of a ledger if daemon.ledger is None or daemon.ledger.id is None: print( "You may not search for peers without being a member of a ledger." ) return peers = True args.remove('peers') if 'cached' in args: cached = len(daemon.disc_peers) > 0 if peers else len( daemon.disc_ledgers) args.remove('cached') utils.log_message("Using cached results" if cached else "Bypassing cache because no results in cache.") if len(args) > 0: # If we still have arguments, we assume it's an IP address ip = args[0] # Check for a valid IP try: socket.inet_pton(socket.AF_INET, ip) except socket.error: print("You entered an invalid IP address") return # Get results of discovery and process if not cached: daemon.disc_ledgers = daemon.discover(ip) if peers: # If we're looking for peers, we're only looking for ledger ids that match ours daemon.disc_peers = daemon.disc_ledgers.get( daemon.ledger.id, set()) # Display the results if peers: print("Found {} peers".format(str(len(daemon.disc_peers)))) if len(daemon.disc_peers) > 0: added_peer_count = 0 for idx, addr in enumerate(daemon.disc_peers): is_peer = addr[0] in daemon.peers print("{} | {}{}".format(idx + 1, '(peer) ' if is_peer else '', addr[0])) # Add non-peers to peer list if not is_peer: daemon.peers[addr[0]] = datetime.now() added_peer_count += 1 print( "Added {} peers to peer list".format(added_peer_count)) else: print("Found {} available ledgers".format( str(len(daemon.disc_ledgers)))) if len(daemon.disc_ledgers) > 0: member = '' for idx, ledger in enumerate(daemon.disc_ledgers): if daemon.ledger is not None and daemon.ledger.id == ledger.strip( ): member = '(peer)' else: member = '' print("{0} | {4}: ({1} members) {2} {3}".format( idx + 1, len(daemon.disc_ledgers[ledger]), ledger, member, list(daemon.disc_ledgers[ledger])[0][0]))
def __init__(self): super(UDPHeartbeat, self).__init__() with lock: utils.log_message("Starting UDP Heartbeat Thread") self.daemon = True self.stop = threading.Event()
def run(self): # Listen for ledger client connection requests with lock: utils.log_message( "Listening for ledger discovery queries on port {0}".format( self._port), utils.Level.MEDIUM) discovery_socket = socket(AF_INET, SOCK_DGRAM) discovery_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) discovery_socket.bind((self._ip, self._port)) discovery_socket.setblocking(False) # Non-blocking socket loop that can be interrupted with a signal/event while True and not self.stop.is_set(): try: data, addr = discovery_socket.recvfrom(1024) except OSError as e: continue else: message = json.loads(data.decode(), object_hook=utils.message_decoder) # Decode Message Type if message.msg_type == settings.MSG_TYPE_DISCOVER: # Discovery Message with lock: utils.log_message( "Received discovery inquiry from {0}, responding..." .format(addr), utils.Level.MEDIUM) response = Message(settings.MSG_TYPE_SUCCESS, daemon.ledger.id).__repr__() discovery_socket.sendto(response.encode(), addr) elif message.msg_type == settings.MSG_TYPE_HB: # Heartbeat Message if "ledger" in message.msg and message.msg[ "ledger"] == daemon.ledger.id: # Add the source address and port to our list of peers and update the date daemon.peers[addr[0]] = datetime.now() # Possible Scenarios: # Heartbeat tail is same as local tail: Do nothing (in sync) # Heartbeat tail is in our ledger: Do nothing (out of sync) # Heartbeat tail is not in our ledger: Synchronize with peer (out of sync) if "tail" in message.msg: tail = message.msg["tail"] # Check for presence of heartbeat tail in our ledger idx, blocks = daemon.ledger.search(tail) # If heartbeat tail is in our ledger, do nothing # If heartbeat tail isn't in our ledger, synchronize with peer if len(idx) <= 0: block_sync((addr[0], settings.BIND_PORT), daemon.ledger.tail.hash) with lock: utils.log_message( "Received heartbeat from {0}".format(addr), utils.Level.LOW) discovery_socket.close()
def __init__(self, socket): super(TCPConnectionThread, self).__init__() with lock: utils.log_message("Spawning TCP Connection Thread from {0}".format( socket.getsockname())) self._socket = socket
def run(self): # Get message message = '' message_size = None try: while True: if message_size is None: data = self._socket.recv(settings.MSG_SIZE_BYTES) # Convert first message size to an integer message_size = int(data.decode()) elif len(message) < message_size: data = self._socket.recv(4096) message += data.decode() else: break except ValueError as e: utils.log_message('Received invalid packet from {0}'.format( self._socket.getsockname())) return with lock: utils.log_message( "Received message from {0}:\n{1}".format( self._socket.getsockname(), message), utils.Level.MEDIUM) message = json.loads(message, object_hook=utils.message_decoder) # JOIN LEDGER if message.msg_type == settings.MSG_TYPE_JOIN: if message.msg == daemon.ledger.id: # Respond with success and the root key response = Message(settings.MSG_TYPE_SUCCESS, daemon.ledger.root.message).prep_tcp() self._respond(response) return else: self._respond_error() return elif message.msg_type == settings.MSG_TYPE_PEER: # Respond with list of peers peer_list = list(daemon.peers.keys()) target = self._socket.getsockname() if target in peer_list: peer_list.remove(target) response = Message(settings.MSG_TYPE_SUCCESS, peer_list).prep_tcp() self._respond(response) return elif message.msg_type == settings.MSG_TYPE_LEDGER: # Respond with the ledger ledger_list = daemon.ledger.slice_ledger(message.msg) if ledger_list is None: self._respond_error() return response = Message(settings.MSG_TYPE_SUCCESS, ledger_list).prep_tcp() self._respond(response) return # No response, send error status else: self._respond_error() return