mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 20:12:57 +01:00
Merge #19177: test: Fix and clean p2p_invalid_messages functional tests
af2a145e575f23c64909e6cf1fb323c603bda7ca Refactor resource exhaustion test (Troy Giorshev) 5c4648d17ba18e4194959963994cc6b37053f127 Fix "invalid message size" test (Troy Giorshev) ff1e7b884447a5ba10553b2d964625f94e255bdc Move size limits to module-global (Troy Giorshev) 57890abf2c7919eddfec36178b1136cd44ffe883 Remove two unneeded tests (Troy Giorshev) Pull request description: This PR touches only the p2p_invalid_messages.py functional test module. There are two main goals accomplished here. First, it fixes the "invalid message size" test, which previously made a message that was invalid for multiple reasons. Second, it refactors the file into a single consistent style. This file appears to have originally had two authors, with different styles and some test duplication. It should now be easier and quicker to understand this module, anticipating the upcoming [BIP324](https://github.com/bitcoin/bitcoin/pull/18242) and [AltNet](https://github.com/bitcoin/bitcoin/issues/18989) changes. This should probably go in ahead of #19107, but the two are not strictly related. ACKs for top commit: jnewbery: ACK af2a145e575f23c64909e6cf1fb323c603bda7ca MarcoFalke: re-ACK af2a145e57 🍦 Tree-SHA512: 9b57561e142c5eaefac5665f7355c8651670400b4db1a89525d2dfdd20e872d6873c4f6175c4222b6f5a8e5210cf5d6a52da69b925b673a2e2ac30a15d670d1c
This commit is contained in:
parent
54501fb5fb
commit
94680f4ee3
@ -4,9 +4,6 @@
|
|||||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
"""Test node responses to invalid network messages."""
|
"""Test node responses to invalid network messages."""
|
||||||
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
from test_framework.messages import (
|
from test_framework.messages import (
|
||||||
CBlockHeader,
|
CBlockHeader,
|
||||||
@ -27,6 +24,8 @@ from test_framework.util import (
|
|||||||
wait_until,
|
wait_until,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MSG_LIMIT = 3 * 1024 * 1024 # 3MB, per MAX_PROTOCOL_MESSAGE_LENGTH
|
||||||
|
VALID_DATA_LIMIT = MSG_LIMIT - 5 # Account for the 5-byte length prefix
|
||||||
|
|
||||||
class msg_unrecognized:
|
class msg_unrecognized:
|
||||||
"""Nonsensical message. Modeled after similar types in test_framework.messages."""
|
"""Nonsensical message. Modeled after similar types in test_framework.messages."""
|
||||||
@ -49,115 +48,13 @@ class InvalidMessagesTest(BitcoinTestFramework):
|
|||||||
self.setup_clean_chain = True
|
self.setup_clean_chain = True
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
"""
|
|
||||||
. Test msg header
|
|
||||||
0. Send a bunch of large (3MB) messages of an unrecognized type. Check to see
|
|
||||||
that it isn't an effective DoS against the node.
|
|
||||||
|
|
||||||
1. Send an oversized (3MB+) 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_buffer()
|
self.test_buffer()
|
||||||
self.test_magic_bytes()
|
self.test_magic_bytes()
|
||||||
self.test_checksum()
|
self.test_checksum()
|
||||||
self.test_size()
|
self.test_size()
|
||||||
self.test_msgtype()
|
self.test_msgtype()
|
||||||
self.test_large_inv()
|
self.test_large_inv()
|
||||||
|
self.test_resource_exhaustion()
|
||||||
node = self.nodes[0]
|
|
||||||
self.node = node
|
|
||||||
node.add_p2p_connection(P2PDataStore())
|
|
||||||
conn2 = node.add_p2p_connection(P2PDataStore())
|
|
||||||
|
|
||||||
msg_limit = 3 * 1024 * 1024 # 3MB, 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
|
|
||||||
|
|
||||||
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=400)
|
|
||||||
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)
|
|
||||||
|
|
||||||
# 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(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())
|
|
||||||
|
|
||||||
def test_buffer(self):
|
def test_buffer(self):
|
||||||
self.log.info("Test message with header split across two buffers, should be received")
|
self.log.info("Test message with header split across two buffers, should be received")
|
||||||
@ -202,14 +99,10 @@ class InvalidMessagesTest(BitcoinTestFramework):
|
|||||||
|
|
||||||
def test_size(self):
|
def test_size(self):
|
||||||
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
|
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
|
||||||
with self.nodes[0].assert_debug_log(['HEADER ERROR - SIZE (badmsg, 33554433 bytes)']):
|
with self.nodes[0].assert_debug_log(['']):
|
||||||
msg = conn.build_message(msg_unrecognized(str_data="d"))
|
# Create a message with oversized payload
|
||||||
cut_len = (
|
msg = msg_unrecognized(str_data="d"*(VALID_DATA_LIMIT + 1))
|
||||||
4 + # magic
|
msg = conn.build_message(msg)
|
||||||
12 # msgtype
|
|
||||||
)
|
|
||||||
# modify len to MAX_SIZE + 1
|
|
||||||
msg = msg[:cut_len] + struct.pack("<I", 0x02000000 + 1) + msg[cut_len + 4:]
|
|
||||||
self.nodes[0].p2p.send_raw_message(msg)
|
self.nodes[0].p2p.send_raw_message(msg)
|
||||||
conn.wait_for_disconnect(timeout=5)
|
conn.wait_for_disconnect(timeout=5)
|
||||||
self.nodes[0].disconnect_p2ps()
|
self.nodes[0].disconnect_p2ps()
|
||||||
@ -238,25 +131,28 @@ class InvalidMessagesTest(BitcoinTestFramework):
|
|||||||
conn.send_and_ping(msg)
|
conn.send_and_ping(msg)
|
||||||
self.nodes[0].disconnect_p2ps()
|
self.nodes[0].disconnect_p2ps()
|
||||||
|
|
||||||
def _tweak_msg_data_size(self, message, wrong_size):
|
def test_resource_exhaustion(self):
|
||||||
"""
|
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
|
||||||
Return a raw message based on another message but with an incorrect data size in
|
conn2 = self.nodes[0].add_p2p_connection(P2PDataStore())
|
||||||
the message header.
|
msg_at_size = msg_unrecognized(str_data="b" * VALID_DATA_LIMIT)
|
||||||
"""
|
assert len(msg_at_size.serialize()) == MSG_LIMIT
|
||||||
raw_msg = self.node.p2p.build_message(message)
|
|
||||||
|
|
||||||
bad_size_bytes = struct.pack("<I", wrong_size)
|
self.log.info("Sending a bunch of large, junk messages to test memory exhaustion. May take a bit...")
|
||||||
num_header_bytes_before_size = 4 + 12
|
|
||||||
|
|
||||||
# Replace the correct data size in the message with an incorrect one.
|
# Run a bunch of times to test for memory exhaustion.
|
||||||
raw_msg_with_wrong_size = (
|
for _ in range(80):
|
||||||
raw_msg[:num_header_bytes_before_size] +
|
conn.send_message(msg_at_size)
|
||||||
bad_size_bytes +
|
|
||||||
raw_msg[(num_header_bytes_before_size + len(bad_size_bytes)):]
|
|
||||||
)
|
|
||||||
assert len(raw_msg) == len(raw_msg_with_wrong_size)
|
|
||||||
|
|
||||||
return raw_msg_with_wrong_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 being served up a bunch of nonsense, should still be connected.
|
||||||
|
self.log.info("Waiting for node to drop junk messages.")
|
||||||
|
conn.sync_with_ping(timeout=400)
|
||||||
|
assert conn.is_connected
|
||||||
|
self.nodes[0].disconnect_p2ps()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
Reference in New Issue
Block a user