def run_test(self): # Setup the p2p connections no_verack_node = self.nodes[0].add_p2p_connection(TestP2PConn()) no_version_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False, wait_for_verack=False) no_send_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False, wait_for_verack=False) sleep(1) assert no_verack_node.is_connected assert no_version_node.is_connected assert no_send_node.is_connected no_verack_node.send_message(msg_ping()) no_version_node.send_message(msg_ping()) sleep(30) assert "version" in no_verack_node.last_message assert no_verack_node.is_connected assert no_version_node.is_connected assert no_send_node.is_connected no_verack_node.send_message(msg_ping()) no_version_node.send_message(msg_ping()) sleep(31) assert not no_verack_node.is_connected assert not no_version_node.is_connected assert not no_send_node.is_connected
def test_magic_bytes(self): conn = self.nodes[0].add_p2p_connection(P2PDataStore()) conn._on_data = lambda: None # Need to ignore all incoming messages from now, since they come with "invalid" magic bytes conn.magic_bytes = b'\x00\x11\x22\x32' with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART ping']): conn.send_message(messages.msg_ping(nonce=0xff)) conn.wait_for_disconnect(timeout=1) self.nodes[0].disconnect_p2ps()
def run_test(self): # Setup the p2p connections no_verack_node = self.nodes[0].add_p2p_connection(TestP2PConn()) no_version_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False, wait_for_verack=False) no_send_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False, wait_for_verack=False) sleep(1) assert no_verack_node.is_connected assert no_version_node.is_connected assert no_send_node.is_connected no_verack_node.send_message(msg_ping()) no_version_node.send_message(msg_ping()) sleep(1) assert "version" in no_verack_node.last_message assert no_verack_node.is_connected assert no_version_node.is_connected assert no_send_node.is_connected no_verack_node.send_message(msg_ping()) no_version_node.send_message(msg_ping()) expected_timeout_logs = [ "version handshake timeout from 0", "socket no message in first 3 seconds, 1 0 from 1", "socket no message in first 3 seconds, 0 0 from 2", ] with self.nodes[0].assert_debug_log(expected_msgs=expected_timeout_logs): sleep(3) # By now, we waited a total of 5 seconds. Off-by-two for two # reasons: # * The internal precision is one second # * Account for network delay assert not no_verack_node.is_connected assert not no_version_node.is_connected assert not no_send_node.is_connected
def run_test(self): # Setup the p2p connections no_verack_node = self.nodes[0].add_p2p_connection(TestP2PConn()) no_version_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False, wait_for_verack=False) no_send_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False, wait_for_verack=False) sleep(1) assert no_verack_node.is_connected assert no_version_node.is_connected assert no_send_node.is_connected no_verack_node.send_message(msg_ping()) no_version_node.send_message(msg_ping()) sleep(1) assert "version" in no_verack_node.last_message assert no_verack_node.is_connected assert no_version_node.is_connected assert no_send_node.is_connected no_verack_node.send_message(msg_ping()) no_version_node.send_message(msg_ping()) expected_timeout_logs = [ "version handshake timeout from 0", "socket no message in first 3 seconds, 1 0 from 1", "socket no message in first 3 seconds, 0 0 from 2", ] with self.nodes[0].assert_debug_log(expected_msgs=expected_timeout_logs): sleep(2) assert not no_verack_node.is_connected assert not no_version_node.is_connected assert not no_send_node.is_connected
def sync_with_ping(self, timeout=60): self.send_message(msg_ping(nonce=self.ping_counter)) test_function = lambda: self.last_message.get("pong") and self.last_message["pong"].nonce == self.ping_counter wait_until(test_function, timeout=timeout, lock=mininode_lock) self.ping_counter += 1
def on_version(self, message): self.version_received = True self.send_message(msg_ping()) self.send_message(msg_getaddr())
def run_test(self): """ 0. Send a bunch of large (4MB) messages of an unrecognized type. Check to see that it isn't an effective DoS against the node. 1. Send an oversized (4MB+) message and check that we're disconnected. 2. Send a few messages with an incorrect data size in the header, ensure the messages are ignored. 3. Send an unrecognized message with a command name longer than 12 characters. """ node = self.nodes[0] self.node = node node.add_p2p_connection(P2PDataStore()) conn2 = node.add_p2p_connection(P2PDataStore()) msg_limit = 4 * 1000 * 1000 # 4MB, per MAX_PROTOCOL_MESSAGE_LENGTH valid_data_limit = msg_limit - 5 # Account for the 4-byte length prefix # # 0. # # Send as large a message as is valid, ensure we aren't disconnected but # also can't exhaust resources. # msg_at_size = msg_unrecognized("b" * valid_data_limit) assert len(msg_at_size.serialize()) == msg_limit increase_allowed = 0.5 if [s for s in os.environ.get("BITCOIN_CONFIG", "").split(" ") if "--with-sanitizers" in s and "address" in s]: increase_allowed = 3.5 with node.assert_memory_usage_stable(increase_allowed=increase_allowed): self.log.info( "Sending a bunch of large, junk messages to test " "memory exhaustion. May take a bit...") # Run a bunch of times to test for memory exhaustion. for _ in range(80): node.p2p.send_message(msg_at_size) # Check that, even though the node is being hammered by nonsense from one # connection, it can still service other peers in a timely way. for _ in range(20): conn2.sync_with_ping(timeout=2) # Peer 1, despite serving up a bunch of nonsense, should still be connected. self.log.info("Waiting for node to drop junk messages.") node.p2p.sync_with_ping(timeout=30) assert node.p2p.is_connected # # 1. # # Send an oversized message, ensure we're disconnected. # msg_over_size = msg_unrecognized("b" * (valid_data_limit + 1)) assert len(msg_over_size.serialize()) == (msg_limit + 1) with node.assert_debug_log(["Oversized message from peer=0, disconnecting"]): # An unknown message type (or *any* message type) over # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. node.p2p.send_message(msg_over_size) node.p2p.wait_for_disconnect(timeout=4) node.disconnect_p2ps() conn = node.add_p2p_connection(P2PDataStore()) conn.wait_for_verack() # # 2. # # Send messages with an incorrect data size in the header. # actual_size = 100 msg = msg_unrecognized("b" * actual_size) # TODO: handle larger-than cases. I haven't been able to pin down what behavior to expect. for wrong_size in (2, 77, 78, 79): self.log.info("Sending a message with incorrect size of {}".format(wrong_size)) # Unmodified message should submit okay. node.p2p.send_and_ping(msg) # A message lying about its data size results in a disconnect when the incorrect # data size is less than the actual size. # # TODO: why does behavior change at 78 bytes? # node.p2p.send_raw_message(self._tweak_msg_data_size(msg, wrong_size)) # For some reason unknown to me, we sometimes have to push additional data to the # peer in order for it to realize a disconnect. try: node.p2p.send_message(messages.msg_ping(nonce=123123)) except IOError: pass node.p2p.wait_for_disconnect(timeout=10) node.disconnect_p2ps() node.add_p2p_connection(P2PDataStore()) # # 3. # # Send a message with a too-long command name. # node.p2p.send_message(msg_nametoolong("foobar")) node.p2p.wait_for_disconnect(timeout=4) # Node is still up. conn = node.add_p2p_connection(P2PDataStore()) conn.sync_with_ping()
def run_test(self): """ . Test msg header 0. Send a bunch of large (4MB) messages of an unrecognized type. Check to see that it isn't an effective DoS against the node. 1. Send an oversized (4MB+) message and check that we're disconnected. 2. Send a few messages with an incorrect data size in the header, ensure the messages are ignored. """ self.test_magic_bytes() self.test_checksum() self.test_size() self.test_command() node = self.nodes[0] self.node = node node.add_p2p_connection(P2PDataStore()) conn2 = node.add_p2p_connection(P2PDataStore()) msg_limit = 4 * 1000 * 1000 # 4MB, per MAX_PROTOCOL_MESSAGE_LENGTH valid_data_limit = msg_limit - 5 # Account for the 4-byte length prefix # # 0. # # Send as large a message as is valid, ensure we aren't disconnected but # also can't exhaust resources. # msg_at_size = msg_unrecognized(str_data="b" * valid_data_limit) assert len(msg_at_size.serialize()) == msg_limit increase_allowed = 0.5 if [s for s in os.environ.get("BITCOIN_CONFIG", "").split(" ") if "--with-sanitizers" in s and "address" in s]: increase_allowed = 3.5 with node.assert_memory_usage_stable(increase_allowed=increase_allowed): self.log.info( "Sending a bunch of large, junk messages to test " "memory exhaustion. May take a bit...") # Run a bunch of times to test for memory exhaustion. for _ in range(80): node.p2p.send_message(msg_at_size) # Check that, even though the node is being hammered by nonsense from one # connection, it can still service other peers in a timely way. for _ in range(20): conn2.sync_with_ping(timeout=2) # Peer 1, despite serving up a bunch of nonsense, should still be connected. self.log.info("Waiting for node to drop junk messages.") node.p2p.sync_with_ping(timeout=120) assert node.p2p.is_connected # # 1. # # Send an oversized message, ensure we're disconnected. # # Under macOS this test is skipped due to an unexpected error code # returned from the closing socket which python/asyncio does not # yet know how to handle. # if sys.platform != 'darwin': msg_over_size = msg_unrecognized(str_data="b" * (valid_data_limit + 1)) assert len(msg_over_size.serialize()) == (msg_limit + 1) with node.assert_debug_log(["Oversized message from peer=4, disconnecting"]): # An unknown message type (or *any* message type) over # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. node.p2p.send_message(msg_over_size) node.p2p.wait_for_disconnect(timeout=4) node.disconnect_p2ps() conn = node.add_p2p_connection(P2PDataStore()) conn.wait_for_verack() else: self.log.info("Skipping test p2p_invalid_messages/1 (oversized message) under macOS") # # 2. # # Send messages with an incorrect data size in the header. # actual_size = 100 msg = msg_unrecognized(str_data="b" * actual_size) # TODO: handle larger-than cases. I haven't been able to pin down what behavior to expect. for wrong_size in (2, 77, 78, 79): self.log.info("Sending a message with incorrect size of {}".format(wrong_size)) # Unmodified message should submit okay. node.p2p.send_and_ping(msg) # A message lying about its data size results in a disconnect when the incorrect # data size is less than the actual size. # # TODO: why does behavior change at 78 bytes? # node.p2p.send_raw_message(self._tweak_msg_data_size(msg, wrong_size)) # For some reason unknown to me, we sometimes have to push additional data to the # peer in order for it to realize a disconnect. try: node.p2p.send_message(messages.msg_ping(nonce=123123)) except IOError: pass node.p2p.wait_for_disconnect(timeout=10) node.disconnect_p2ps() node.add_p2p_connection(P2PDataStore()) # Node is still up. conn = node.add_p2p_connection(P2PDataStore()) conn.sync_with_ping()
def run_test(self): # Peer that never sends a version. We will send a bunch of messages # from this peer anyway and verify eventual disconnection. no_version_disconnect_peer = self.nodes[0].add_p2p_connection( LazyPeer(), send_version=False, wait_for_verack=False) # Another peer that never sends a version, nor any other messages. It shouldn't receive anything from the node. no_version_idle_peer = self.nodes[0].add_p2p_connection( LazyPeer(), send_version=False, wait_for_verack=False) # Peer that sends a version but not a verack. no_verack_idle_peer = self.nodes[0].add_p2p_connection( NoVerackIdlePeer(), wait_for_verack=False) # Send enough ping messages (any non-version message will do) prior to sending # version to reach the peer discouragement threshold. This should get us disconnected. for _ in range(DISCOURAGEMENT_THRESHOLD): no_version_disconnect_peer.send_message(msg_ping()) # Wait until we got the verack in response to the version. Though, don't wait for the node to receive the # verack, since we never sent one no_verack_idle_peer.wait_for_verack() no_version_disconnect_peer.wait_until( lambda: no_version_disconnect_peer.ever_connected, check_connected=False) no_version_idle_peer.wait_until( lambda: no_version_idle_peer.ever_connected) no_verack_idle_peer.wait_until( lambda: no_verack_idle_peer.version_received) # Mine a block and make sure that it's not sent to the connected peers self.nodes[0].generate(nblocks=1) #Give the node enough time to possibly leak out a message time.sleep(5) # Expect this peer to be disconnected for misbehavior assert not no_version_disconnect_peer.is_connected self.nodes[0].disconnect_p2ps() # Make sure no unexpected messages came in assert no_version_disconnect_peer.unexpected_msg == False assert no_version_idle_peer.unexpected_msg == False assert no_verack_idle_peer.unexpected_msg == False self.log.info( 'Check that the version message does not leak the local address of the node' ) p2p_version_store = self.nodes[0].add_p2p_connection(P2PVersionStore()) ver = p2p_version_store.version_received # Check that received time is within one hour of now assert_greater_than_or_equal(ver.nTime, time.time() - 3600) assert_greater_than_or_equal(time.time() + 3600, ver.nTime) assert_equal(ver.addrFrom.port, 0) assert_equal(ver.addrFrom.ip, '0.0.0.0') assert_equal(ver.nStartingHeight, 201) assert_equal(ver.nRelay, 1) self.log.info('Check that old peers are disconnected') p2p_old_peer = self.nodes[0].add_p2p_connection(P2PInterface(), send_version=False, wait_for_verack=False) old_version_msg = msg_version() old_version_msg.nVersion = 31799 with self.nodes[0].assert_debug_log( ['peer=4 using obsolete version 31799; disconnecting']): p2p_old_peer.send_message(old_version_msg) p2p_old_peer.wait_for_disconnect()
def run_test(self): """ . Test msg header 0. Send a bunch of large (2MB) messages of an unrecognized type. Check to see that it isn't an effective DoS against the node. 1. Send an oversized (2MB+) message and check that we're disconnected. 2. Send a few messages with an incorrect data size in the header, ensure the messages are ignored. """ self.test_magic_bytes() self.test_checksum() self.test_size() self.test_command() node = self.nodes[0] self.node = node node.add_p2p_connection(P2PDataStore()) conn2 = node.add_p2p_connection(P2PDataStore()) # 2MB, per MAX_PROTOCOL_MESSAGE_LENGTH msg_limit = 2 * 1024 * 1024 # Account for the 4-byte length prefix valid_data_limit = msg_limit - 5 # # 0. # # Send as large a message as is valid, ensure we aren't disconnected but # also can't exhaust resources. # msg_at_size = msg_unrecognized(str_data="b" * valid_data_limit) assert len(msg_at_size.serialize()) == msg_limit self.log.info( "Sending a bunch of large, junk messages to test memory exhaustion. May take a bit..." ) # Run a bunch of times to test for memory exhaustion. for _ in range(80): node.p2p.send_message(msg_at_size) # Check that, even though the node is being hammered by nonsense from one # connection, it can still service other peers in a timely way. for _ in range(20): conn2.sync_with_ping(timeout=2) # Peer 1, despite serving up a bunch of nonsense, should still be # connected. self.log.info("Waiting for node to drop junk messages.") node.p2p.sync_with_ping(timeout=320) assert node.p2p.is_connected # # 1. # # Send an oversized message, ensure we're disconnected. # msg_over_size = msg_unrecognized(str_data="b" * (valid_data_limit + 1)) assert len(msg_over_size.serialize()) == (msg_limit + 1) with node.assert_debug_log(["Oversized header detected"]): # An unknown message type (or *any* message type) over # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. node.p2p.send_message(msg_over_size) node.p2p.wait_for_disconnect(timeout=4) node.disconnect_p2ps() conn = node.add_p2p_connection(P2PDataStore()) conn.wait_for_verack() # # 2. # # Send messages with an incorrect data size in the header. # actual_size = 100 msg = msg_unrecognized(str_data="b" * actual_size) # TODO: handle larger-than cases. I haven't been able to pin down what # behavior to expect. for wrong_size in (2, 77, 78, 79): self.log.info("Sending a message with incorrect size of {}".format( wrong_size)) # Unmodified message should submit okay. node.p2p.send_and_ping(msg) # A message lying about its data size results in a disconnect when the incorrect # data size is less than the actual size. # # TODO: why does behavior change at 78 bytes? # node.p2p.send_raw_message( self._tweak_msg_data_size(msg, wrong_size)) # For some reason unknown to me, we sometimes have to push additional data to the # peer in order for it to realize a disconnect. try: node.p2p.send_message(messages.msg_ping(nonce=123123)) except IOError: pass node.p2p.wait_for_disconnect(timeout=10) node.disconnect_p2ps() node.add_p2p_connection(P2PDataStore()) # Node is still up. conn = node.add_p2p_connection(P2PDataStore())