Skip to content

Commit f154071

Browse files
author
MarcoFalke
committed
Merge #19177: test: Fix and clean p2p_invalid_messages functional tests
af2a145 Refactor resource exhaustion test (Troy Giorshev) 5c4648d Fix "invalid message size" test (Troy Giorshev) ff1e7b8 Move size limits to module-global (Troy Giorshev) 57890ab 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](bitcoin/bitcoin#18242) and [AltNet](bitcoin/bitcoin#18989) changes. This should probably go in ahead of #19107, but the two are not strictly related. ACKs for top commit: jnewbery: ACK af2a145 MarcoFalke: re-ACK af2a145 🍦 Tree-SHA512: 9b57561e142c5eaefac5665f7355c8651670400b4db1a89525d2dfdd20e872d6873c4f6175c4222b6f5a8e5210cf5d6a52da69b925b673a2e2ac30a15d670d1c
2 parents 5af16a4 + af2a145 commit f154071

File tree

1 file changed

+28
-131
lines changed

1 file changed

+28
-131
lines changed

test/functional/p2p_invalid_messages.py

Lines changed: 28 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test node responses to invalid network messages."""
66
import asyncio
7-
import struct
8-
import sys
97

108
from test_framework.messages import (
119
CBlockHeader,
@@ -24,6 +22,8 @@
2422
)
2523
from test_framework.test_framework import BitcoinTestFramework
2624

25+
MSG_LIMIT = 4 * 1000 * 1000 # 4MB, per MAX_PROTOCOL_MESSAGE_LENGTH
26+
VALID_DATA_LIMIT = MSG_LIMIT - 5 # Account for the 5-byte length prefix
2727

2828
class msg_unrecognized:
2929
"""Nonsensical message. Modeled after similar types in test_framework.messages."""
@@ -46,114 +46,12 @@ def set_test_params(self):
4646
self.setup_clean_chain = True
4747

4848
def run_test(self):
49-
"""
50-
. Test msg header
51-
0. Send a bunch of large (4MB) messages of an unrecognized type. Check to see
52-
that it isn't an effective DoS against the node.
53-
54-
1. Send an oversized (4MB+) message and check that we're disconnected.
55-
56-
2. Send a few messages with an incorrect data size in the header, ensure the
57-
messages are ignored.
58-
"""
5949
self.test_magic_bytes()
6050
self.test_checksum()
6151
self.test_size()
6252
self.test_msgtype()
6353
self.test_large_inv()
64-
65-
node = self.nodes[0]
66-
self.node = node
67-
node.add_p2p_connection(P2PDataStore())
68-
conn2 = node.add_p2p_connection(P2PDataStore())
69-
70-
msg_limit = 4 * 1000 * 1000 # 4MB, per MAX_PROTOCOL_MESSAGE_LENGTH
71-
valid_data_limit = msg_limit - 5 # Account for the 4-byte length prefix
72-
73-
#
74-
# 0.
75-
#
76-
# Send as large a message as is valid, ensure we aren't disconnected but
77-
# also can't exhaust resources.
78-
#
79-
msg_at_size = msg_unrecognized(str_data="b" * valid_data_limit)
80-
assert len(msg_at_size.serialize()) == msg_limit
81-
82-
self.log.info("Sending a bunch of large, junk messages to test memory exhaustion. May take a bit...")
83-
84-
# Run a bunch of times to test for memory exhaustion.
85-
for _ in range(80):
86-
node.p2p.send_message(msg_at_size)
87-
88-
# Check that, even though the node is being hammered by nonsense from one
89-
# connection, it can still service other peers in a timely way.
90-
for _ in range(20):
91-
conn2.sync_with_ping(timeout=2)
92-
93-
# Peer 1, despite serving up a bunch of nonsense, should still be connected.
94-
self.log.info("Waiting for node to drop junk messages.")
95-
node.p2p.sync_with_ping(timeout=400)
96-
assert node.p2p.is_connected
97-
98-
#
99-
# 1.
100-
#
101-
# Send an oversized message, ensure we're disconnected.
102-
#
103-
# Under macOS this test is skipped due to an unexpected error code
104-
# returned from the closing socket which python/asyncio does not
105-
# yet know how to handle.
106-
#
107-
if sys.platform != 'darwin':
108-
msg_over_size = msg_unrecognized(str_data="b" * (valid_data_limit + 1))
109-
assert len(msg_over_size.serialize()) == (msg_limit + 1)
110-
111-
# An unknown message type (or *any* message type) over
112-
# MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect.
113-
node.p2p.send_message(msg_over_size)
114-
node.p2p.wait_for_disconnect(timeout=4)
115-
116-
node.disconnect_p2ps()
117-
conn = node.add_p2p_connection(P2PDataStore())
118-
conn.wait_for_verack()
119-
else:
120-
self.log.info("Skipping test p2p_invalid_messages/1 (oversized message) under macOS")
121-
122-
#
123-
# 2.
124-
#
125-
# Send messages with an incorrect data size in the header.
126-
#
127-
actual_size = 100
128-
msg = msg_unrecognized(str_data="b" * actual_size)
129-
130-
# TODO: handle larger-than cases. I haven't been able to pin down what behavior to expect.
131-
for wrong_size in (2, 77, 78, 79):
132-
self.log.info("Sending a message with incorrect size of {}".format(wrong_size))
133-
134-
# Unmodified message should submit okay.
135-
node.p2p.send_and_ping(msg)
136-
137-
# A message lying about its data size results in a disconnect when the incorrect
138-
# data size is less than the actual size.
139-
#
140-
# TODO: why does behavior change at 78 bytes?
141-
#
142-
node.p2p.send_raw_message(self._tweak_msg_data_size(msg, wrong_size))
143-
144-
# For some reason unknown to me, we sometimes have to push additional data to the
145-
# peer in order for it to realize a disconnect.
146-
try:
147-
node.p2p.send_message(msg_ping(nonce=123123))
148-
except IOError:
149-
pass
150-
151-
node.p2p.wait_for_disconnect(timeout=10)
152-
node.disconnect_p2ps()
153-
node.add_p2p_connection(P2PDataStore())
154-
155-
# Node is still up.
156-
conn = node.add_p2p_connection(P2PDataStore())
54+
self.test_resource_exhaustion()
15755

15856
def test_magic_bytes(self):
15957
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
@@ -189,13 +87,9 @@ def test_checksum(self):
18987
def test_size(self):
19088
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
19189
with self.nodes[0].assert_debug_log(['']):
192-
msg = conn.build_message(msg_unrecognized(str_data="d"))
193-
cut_len = (
194-
4 + # magic
195-
12 # msgtype
196-
)
197-
# modify len to MAX_SIZE + 1
198-
msg = msg[:cut_len] + struct.pack("<I", 0x02000000 + 1) + msg[cut_len + 4:]
90+
# Create a message with oversized payload
91+
msg = msg_unrecognized(str_data="d"*(VALID_DATA_LIMIT + 1))
92+
msg = conn.build_message(msg)
19993
self.nodes[0].p2p.send_raw_message(msg)
20094
conn.wait_for_disconnect(timeout=1)
20195
self.nodes[0].disconnect_p2ps()
@@ -225,25 +119,28 @@ def test_large_inv(self):
225119
conn.send_and_ping(msg)
226120
self.nodes[0].disconnect_p2ps()
227121

228-
def _tweak_msg_data_size(self, message, wrong_size):
229-
"""
230-
Return a raw message based on another message but with an incorrect data size in
231-
the message header.
232-
"""
233-
raw_msg = self.node.p2p.build_message(message)
234-
235-
bad_size_bytes = struct.pack("<I", wrong_size)
236-
num_header_bytes_before_size = 4 + 12
237-
238-
# Replace the correct data size in the message with an incorrect one.
239-
raw_msg_with_wrong_size = (
240-
raw_msg[:num_header_bytes_before_size] +
241-
bad_size_bytes +
242-
raw_msg[(num_header_bytes_before_size + len(bad_size_bytes)):]
243-
)
244-
assert len(raw_msg) == len(raw_msg_with_wrong_size)
245-
246-
return raw_msg_with_wrong_size
122+
def test_resource_exhaustion(self):
123+
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
124+
conn2 = self.nodes[0].add_p2p_connection(P2PDataStore())
125+
msg_at_size = msg_unrecognized(str_data="b" * VALID_DATA_LIMIT)
126+
assert len(msg_at_size.serialize()) == MSG_LIMIT
127+
128+
self.log.info("Sending a bunch of large, junk messages to test memory exhaustion. May take a bit...")
129+
130+
# Run a bunch of times to test for memory exhaustion.
131+
for _ in range(80):
132+
conn.send_message(msg_at_size)
133+
134+
# Check that, even though the node is being hammered by nonsense from one
135+
# connection, it can still service other peers in a timely way.
136+
for _ in range(20):
137+
conn2.sync_with_ping(timeout=2)
138+
139+
# Peer 1, despite being served up a bunch of nonsense, should still be connected.
140+
self.log.info("Waiting for node to drop junk messages.")
141+
conn.sync_with_ping(timeout=400)
142+
assert conn.is_connected
143+
self.nodes[0].disconnect_p2ps()
247144

248145

249146
if __name__ == '__main__':

0 commit comments

Comments
 (0)