Exemple #1
0
    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()
Exemple #3
0
    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
Exemple #4
0
    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
Exemple #5
0
 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
Exemple #6
0
 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 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
Exemple #9
0
 def on_version(self, message):
     self.version_received = True
     self.send_message(msg_ping())
     self.send_message(msg_getaddr())
    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()
Exemple #11
0
    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())