/ test / functional / p2p_leak.py
p2p_leak.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2017-2022 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_message(msg_ping())
 88          self.send_message(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_message(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"]):
176              p2p_old_peer.send_message(self.create_old_version(31799))
177              p2p_old_peer.wait_for_disconnect()
178  
179  
180  if __name__ == '__main__':
181      P2PLeakTest().main()