p2p_leak.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2017-present The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 """Test message sending before handshake completion. 6 7 Before receiving a VERACK, a node should not send anything but VERSION/VERACK 8 and feature negotiation messages (WTXIDRELAY, SENDADDRV2). 9 10 This test connects to a node and sends it a few messages, trying to entice it 11 into sending us something it shouldn't.""" 12 13 import time 14 15 from test_framework.messages import ( 16 msg_getaddr, 17 msg_ping, 18 msg_version, 19 ) 20 from test_framework.p2p import ( 21 P2PInterface, 22 P2P_SUBVERSION, 23 P2P_SERVICES, 24 P2P_VERSION_RELAY, 25 ) 26 from test_framework.test_framework import BitcoinTestFramework 27 from test_framework.util import ( 28 assert_equal, 29 assert_greater_than_or_equal, 30 ) 31 32 PEER_TIMEOUT = 3 33 34 35 class LazyPeer(P2PInterface): 36 def __init__(self): 37 super().__init__() 38 self.unexpected_msg = False 39 self.ever_connected = False 40 self.got_wtxidrelay = False 41 self.got_sendaddrv2 = False 42 43 def bad_message(self, message): 44 self.unexpected_msg = True 45 print("should not have received message: %s" % message.msgtype) 46 47 def on_open(self): 48 self.ever_connected = True 49 50 # Does not respond to "version" with "verack" 51 def on_version(self, message): self.bad_message(message) 52 def on_verack(self, message): self.bad_message(message) 53 def on_inv(self, message): self.bad_message(message) 54 def on_addr(self, message): self.bad_message(message) 55 def on_getdata(self, message): self.bad_message(message) 56 def on_getblocks(self, message): self.bad_message(message) 57 def on_tx(self, message): self.bad_message(message) 58 def on_block(self, message): self.bad_message(message) 59 def on_getaddr(self, message): self.bad_message(message) 60 def on_headers(self, message): self.bad_message(message) 61 def on_getheaders(self, message): self.bad_message(message) 62 def on_ping(self, message): self.bad_message(message) 63 def on_mempool(self, message): self.bad_message(message) 64 def on_pong(self, message): self.bad_message(message) 65 def on_feefilter(self, message): self.bad_message(message) 66 def on_sendheaders(self, message): self.bad_message(message) 67 def on_sendcmpct(self, message): self.bad_message(message) 68 def on_cmpctblock(self, message): self.bad_message(message) 69 def on_getblocktxn(self, message): self.bad_message(message) 70 def on_blocktxn(self, message): self.bad_message(message) 71 def on_wtxidrelay(self, message): self.got_wtxidrelay = True 72 def on_sendaddrv2(self, message): self.got_sendaddrv2 = True 73 74 75 # Peer that sends a version but not a verack. 76 class NoVerackIdlePeer(LazyPeer): 77 def __init__(self): 78 self.version_received = False 79 super().__init__() 80 81 def on_verack(self, message): pass 82 # When version is received, don't reply with a verack. Instead, see if the 83 # node will give us a message that it shouldn't. This is not an exhaustive 84 # list! 85 def on_version(self, message): 86 self.version_received = True 87 self.send_without_ping(msg_ping()) 88 self.send_without_ping(msg_getaddr()) 89 90 91 class P2PVersionStore(P2PInterface): 92 version_received = None 93 94 def on_version(self, msg): 95 # Responds with an appropriate verack 96 super().on_version(msg) 97 self.version_received = msg 98 99 100 class P2PLeakTest(BitcoinTestFramework): 101 def set_test_params(self): 102 self.num_nodes = 1 103 self.extra_args = [[f"-peertimeout={PEER_TIMEOUT}"]] 104 105 def create_old_version(self, nversion): 106 old_version_msg = msg_version() 107 old_version_msg.nVersion = nversion 108 old_version_msg.strSubVer = P2P_SUBVERSION 109 old_version_msg.nServices = P2P_SERVICES 110 old_version_msg.relay = P2P_VERSION_RELAY 111 return old_version_msg 112 113 def run_test(self): 114 self.log.info('Check that the node doesn\'t send unexpected messages before handshake completion') 115 # Peer that never sends a version, nor any other messages. It shouldn't receive anything from the node. 116 no_version_idle_peer = self.nodes[0].add_p2p_connection(LazyPeer(), send_version=False, wait_for_verack=False) 117 118 # Peer that sends a version but not a verack. 119 no_verack_idle_peer = self.nodes[0].add_p2p_connection(NoVerackIdlePeer(), wait_for_verack=False) 120 121 # Pre-wtxidRelay peer that sends a version but not a verack and does not support feature negotiation 122 # messages which start at nVersion == 70016 123 pre_wtxidrelay_peer = self.nodes[0].add_p2p_connection(NoVerackIdlePeer(), send_version=False, wait_for_verack=False) 124 pre_wtxidrelay_peer.send_without_ping(self.create_old_version(70015)) 125 126 # Wait until the peer gets the verack in response to the version. Though, don't wait for the node to receive the 127 # verack, since the peer never sent one 128 no_verack_idle_peer.wait_for_verack() 129 pre_wtxidrelay_peer.wait_for_verack() 130 131 no_version_idle_peer.wait_until(lambda: no_version_idle_peer.ever_connected) 132 no_verack_idle_peer.wait_until(lambda: no_verack_idle_peer.version_received) 133 pre_wtxidrelay_peer.wait_until(lambda: pre_wtxidrelay_peer.version_received) 134 135 # Mine a block and make sure that it's not sent to the connected peers 136 self.generate(self.nodes[0], nblocks=1) 137 138 # Give the node enough time to possibly leak out a message 139 time.sleep(PEER_TIMEOUT + 2) 140 141 self.log.info("Connect peer to ensure the net thread runs the disconnect logic at least once") 142 self.nodes[0].add_p2p_connection(P2PInterface()) 143 144 # Make sure only expected messages came in 145 assert not no_version_idle_peer.unexpected_msg 146 assert not no_version_idle_peer.got_wtxidrelay 147 assert not no_version_idle_peer.got_sendaddrv2 148 149 assert not no_verack_idle_peer.unexpected_msg 150 assert no_verack_idle_peer.got_wtxidrelay 151 assert no_verack_idle_peer.got_sendaddrv2 152 153 assert not pre_wtxidrelay_peer.unexpected_msg 154 assert not pre_wtxidrelay_peer.got_wtxidrelay 155 assert not pre_wtxidrelay_peer.got_sendaddrv2 156 157 # Expect peers to be disconnected due to timeout 158 assert not no_version_idle_peer.is_connected 159 assert not no_verack_idle_peer.is_connected 160 assert not pre_wtxidrelay_peer.is_connected 161 162 self.log.info('Check that the version message does not leak the local address of the node') 163 p2p_version_store = self.nodes[0].add_p2p_connection(P2PVersionStore()) 164 ver = p2p_version_store.version_received 165 # Check that received time is within one hour of now 166 assert_greater_than_or_equal(ver.nTime, time.time() - 3600) 167 assert_greater_than_or_equal(time.time() + 3600, ver.nTime) 168 assert_equal(ver.addrFrom.port, 0) 169 assert_equal(ver.addrFrom.ip, '0.0.0.0') 170 assert_equal(ver.nStartingHeight, 201) 171 assert_equal(ver.relay, 1) 172 173 self.log.info('Check that old peers are disconnected') 174 p2p_old_peer = self.nodes[0].add_p2p_connection(P2PInterface(), send_version=False, wait_for_verack=False) 175 with self.nodes[0].assert_debug_log(["using obsolete version 31799, disconnecting peer=5"]): 176 p2p_old_peer.send_without_ping(self.create_old_version(31799)) 177 p2p_old_peer.wait_for_disconnect() 178 179 180 if __name__ == '__main__': 181 P2PLeakTest(__file__).main()