net: advertise support for ADDRv2 via new message

Introduce a new message `sendaddrv2` to signal support for ADDRv2.
Send the new message immediately after sending the `VERACK` message.

Add support for receiving and parsing ADDRv2 messages.

Send ADDRv2 messages (instead of ADDR) to a peer if he has
advertised support for it.

Co-authored-by: Carl Dong <contact@carldong.me>
This commit is contained in:
Vasil Dimov
2020-05-20 12:05:18 +02:00
parent 201a4596d9
commit 353a3fdaad
12 changed files with 392 additions and 23 deletions

View File

@ -868,6 +868,11 @@ public:
bool m_legacyWhitelisted{false}; bool m_legacyWhitelisted{false};
bool fClient{false}; // set by version message bool fClient{false}; // set by version message
bool m_limited_node{false}; //after BIP159, set by version message bool m_limited_node{false}; //after BIP159, set by version message
/**
* Whether the peer has signaled support for receiving ADDRv2 (BIP155)
* messages, implying a preference to receive ADDRv2 instead of ADDR ones.
*/
std::atomic_bool m_wants_addrv2{false};
std::atomic_bool fSuccessfullyConnected{false}; std::atomic_bool fSuccessfullyConnected{false};
// Setting fDisconnect to true will cause the node to be disconnected the // Setting fDisconnect to true will cause the node to be disconnected the
// next time DisconnectNodes() runs // next time DisconnectNodes() runs
@ -1115,11 +1120,16 @@ public:
void PushAddress(const CAddress& _addr, FastRandomContext &insecure_rand) void PushAddress(const CAddress& _addr, FastRandomContext &insecure_rand)
{ {
// Whether the peer supports the address in `_addr`. For example,
// nodes that do not implement BIP155 cannot receive Tor v3 addresses
// because they require ADDRv2 (BIP155) encoding.
const bool addr_format_supported = m_wants_addrv2 || _addr.IsAddrV1Compatible();
// Known checking here is only to save space from duplicates. // Known checking here is only to save space from duplicates.
// SendMessages will filter it again for knowns that were added // SendMessages will filter it again for knowns that were added
// after addresses were pushed. // after addresses were pushed.
assert(m_addr_known); assert(m_addr_known);
if (_addr.IsValid() && !m_addr_known->contains(_addr.GetKey())) { if (_addr.IsValid() && !m_addr_known->contains(_addr.GetKey()) && addr_format_supported) {
if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) { if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) {
vAddrToSend[insecure_rand.randrange(vAddrToSend.size())] = _addr; vAddrToSend[insecure_rand.randrange(vAddrToSend.size())] = _addr;
} else { } else {

View File

@ -23,6 +23,7 @@
#include <random.h> #include <random.h>
#include <reverse_iterator.h> #include <reverse_iterator.h>
#include <scheduler.h> #include <scheduler.h>
#include <streams.h>
#include <tinyformat.h> #include <tinyformat.h>
#include <txmempool.h> #include <txmempool.h>
#include <util/check.h> // For NDEBUG compile time check #include <util/check.h> // For NDEBUG compile time check
@ -2408,11 +2409,16 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
pfrom.SetCommonVersion(greatest_common_version); pfrom.SetCommonVersion(greatest_common_version);
pfrom.nVersion = nVersion; pfrom.nVersion = nVersion;
const CNetMsgMaker msg_maker(greatest_common_version);
if (greatest_common_version >= WTXID_RELAY_VERSION) { if (greatest_common_version >= WTXID_RELAY_VERSION) {
m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::WTXIDRELAY)); m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::WTXIDRELAY));
} }
m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::VERACK)); m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
// Signal ADDRv2 support (BIP155).
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDADDRV2));
pfrom.nServices = nServices; pfrom.nServices = nServices;
pfrom.SetAddrLocal(addrMe); pfrom.SetAddrLocal(addrMe);
@ -2582,16 +2588,25 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
return; return;
} }
if (msg_type == NetMsgType::ADDR) { if (msg_type == NetMsgType::ADDR || msg_type == NetMsgType::ADDRV2) {
int stream_version = vRecv.GetVersion();
if (msg_type == NetMsgType::ADDRV2) {
// Add ADDRV2_FORMAT to the version so that the CNetAddr and CAddress
// unserialize methods know that an address in v2 format is coming.
stream_version |= ADDRV2_FORMAT;
}
OverrideStream<CDataStream> s(&vRecv, vRecv.GetType(), stream_version);
std::vector<CAddress> vAddr; std::vector<CAddress> vAddr;
vRecv >> vAddr;
s >> vAddr;
if (!pfrom.RelayAddrsWithConn()) { if (!pfrom.RelayAddrsWithConn()) {
return; return;
} }
if (vAddr.size() > MAX_ADDR_TO_SEND) if (vAddr.size() > MAX_ADDR_TO_SEND)
{ {
Misbehaving(pfrom.GetId(), 20, strprintf("addr message size = %u", vAddr.size())); Misbehaving(pfrom.GetId(), 20, strprintf("%s message size = %u", msg_type, vAddr.size()));
return; return;
} }
@ -2635,6 +2650,11 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
return; return;
} }
if (msg_type == NetMsgType::SENDADDRV2) {
pfrom.m_wants_addrv2 = true;
return;
}
if (msg_type == NetMsgType::SENDHEADERS) { if (msg_type == NetMsgType::SENDHEADERS) {
LOCK(cs_main); LOCK(cs_main);
State(pfrom.GetId())->fPreferHeaders = true; State(pfrom.GetId())->fPreferHeaders = true;
@ -4095,6 +4115,17 @@ bool PeerManager::SendMessages(CNode* pto)
std::vector<CAddress> vAddr; std::vector<CAddress> vAddr;
vAddr.reserve(pto->vAddrToSend.size()); vAddr.reserve(pto->vAddrToSend.size());
assert(pto->m_addr_known); assert(pto->m_addr_known);
const char* msg_type;
int make_flags;
if (pto->m_wants_addrv2) {
msg_type = NetMsgType::ADDRV2;
make_flags = ADDRV2_FORMAT;
} else {
msg_type = NetMsgType::ADDR;
make_flags = 0;
}
for (const CAddress& addr : pto->vAddrToSend) for (const CAddress& addr : pto->vAddrToSend)
{ {
if (!pto->m_addr_known->contains(addr.GetKey())) if (!pto->m_addr_known->contains(addr.GetKey()))
@ -4104,14 +4135,14 @@ bool PeerManager::SendMessages(CNode* pto)
// receiver rejects addr messages larger than MAX_ADDR_TO_SEND // receiver rejects addr messages larger than MAX_ADDR_TO_SEND
if (vAddr.size() >= MAX_ADDR_TO_SEND) if (vAddr.size() >= MAX_ADDR_TO_SEND)
{ {
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::ADDR, vAddr)); m_connman.PushMessage(pto, msgMaker.Make(make_flags, msg_type, vAddr));
vAddr.clear(); vAddr.clear();
} }
} }
} }
pto->vAddrToSend.clear(); pto->vAddrToSend.clear();
if (!vAddr.empty()) if (!vAddr.empty())
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::ADDR, vAddr)); m_connman.PushMessage(pto, msgMaker.Make(make_flags, msg_type, vAddr));
// we only send the big addr message once // we only send the big addr message once
if (pto->vAddrToSend.capacity() > 40) if (pto->vAddrToSend.capacity() > 40)
pto->vAddrToSend.shrink_to_fit(); pto->vAddrToSend.shrink_to_fit();

View File

@ -474,6 +474,26 @@ bool CNetAddr::IsInternal() const
return m_net == NET_INTERNAL; return m_net == NET_INTERNAL;
} }
bool CNetAddr::IsAddrV1Compatible() const
{
switch (m_net) {
case NET_IPV4:
case NET_IPV6:
case NET_INTERNAL:
return true;
case NET_ONION:
return m_addr.size() == ADDR_TORV2_SIZE;
case NET_I2P:
case NET_CJDNS:
return false;
case NET_UNROUTABLE: // m_net is never and should not be set to NET_UNROUTABLE
case NET_MAX: // m_net is never and should not be set to NET_MAX
assert(false);
} // no default case, so the compiler can warn about missing cases
assert(false);
}
enum Network CNetAddr::GetNetwork() const enum Network CNetAddr::GetNetwork() const
{ {
if (IsInternal()) if (IsInternal())
@ -744,9 +764,12 @@ std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) co
std::vector<unsigned char> CNetAddr::GetAddrBytes() const std::vector<unsigned char> CNetAddr::GetAddrBytes() const
{ {
uint8_t serialized[V1_SERIALIZATION_SIZE]; if (IsAddrV1Compatible()) {
SerializeV1Array(serialized); uint8_t serialized[V1_SERIALIZATION_SIZE];
return {std::begin(serialized), std::end(serialized)}; SerializeV1Array(serialized);
return {std::begin(serialized), std::end(serialized)};
}
return std::vector<unsigned char>(m_addr.begin(), m_addr.end());
} }
uint64_t CNetAddr::GetHash() const uint64_t CNetAddr::GetHash() const

View File

@ -173,6 +173,12 @@ class CNetAddr
bool IsRoutable() const; bool IsRoutable() const;
bool IsInternal() const; bool IsInternal() const;
bool IsValid() const; bool IsValid() const;
/**
* Check if the current object can be serialized in pre-ADDRv2/BIP155 format.
*/
bool IsAddrV1Compatible() const;
enum Network GetNetwork() const; enum Network GetNetwork() const;
std::string ToString() const; std::string ToString() const;
std::string ToStringIP() const; std::string ToStringIP() const;

View File

@ -14,6 +14,8 @@ namespace NetMsgType {
const char *VERSION="version"; const char *VERSION="version";
const char *VERACK="verack"; const char *VERACK="verack";
const char *ADDR="addr"; const char *ADDR="addr";
const char *ADDRV2="addrv2";
const char *SENDADDRV2="sendaddrv2";
const char *INV="inv"; const char *INV="inv";
const char *GETDATA="getdata"; const char *GETDATA="getdata";
const char *MERKLEBLOCK="merkleblock"; const char *MERKLEBLOCK="merkleblock";
@ -52,6 +54,8 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::VERSION, NetMsgType::VERSION,
NetMsgType::VERACK, NetMsgType::VERACK,
NetMsgType::ADDR, NetMsgType::ADDR,
NetMsgType::ADDRV2,
NetMsgType::SENDADDRV2,
NetMsgType::INV, NetMsgType::INV,
NetMsgType::GETDATA, NetMsgType::GETDATA,
NetMsgType::MERKLEBLOCK, NetMsgType::MERKLEBLOCK,

View File

@ -76,6 +76,18 @@ extern const char* VERACK;
* network. * network.
*/ */
extern const char* ADDR; extern const char* ADDR;
/**
* The addrv2 message relays connection information for peers on the network just
* like the addr message, but is extended to allow gossiping of longer node
* addresses (see BIP155).
*/
extern const char *ADDRV2;
/**
* The sendaddrv2 message signals support for receiving ADDRV2 messages (BIP155).
* It also implies that its sender can encode as ADDRV2 and would send ADDRV2
* instead of ADDR to a peer that has signaled ADDRV2 support by sending SENDADDRV2.
*/
extern const char *SENDADDRV2;
/** /**
* The inv message (inventory message) transmits one or more inventories of * The inv message (inventory message) transmits one or more inventories of
* objects known to the transmitting peer. * objects known to the transmitting peer.

View File

@ -212,6 +212,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic)
BOOST_REQUIRE(addr.IsIPv4()); BOOST_REQUIRE(addr.IsIPv4());
BOOST_CHECK(addr.IsBindAny()); BOOST_CHECK(addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "0.0.0.0"); BOOST_CHECK_EQUAL(addr.ToString(), "0.0.0.0");
// IPv4, INADDR_NONE // IPv4, INADDR_NONE
@ -220,6 +221,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic)
BOOST_REQUIRE(addr.IsIPv4()); BOOST_REQUIRE(addr.IsIPv4());
BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "255.255.255.255"); BOOST_CHECK_EQUAL(addr.ToString(), "255.255.255.255");
// IPv4, casual // IPv4, casual
@ -228,6 +230,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic)
BOOST_REQUIRE(addr.IsIPv4()); BOOST_REQUIRE(addr.IsIPv4());
BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "12.34.56.78"); BOOST_CHECK_EQUAL(addr.ToString(), "12.34.56.78");
// IPv6, in6addr_any // IPv6, in6addr_any
@ -236,6 +239,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic)
BOOST_REQUIRE(addr.IsIPv6()); BOOST_REQUIRE(addr.IsIPv6());
BOOST_CHECK(addr.IsBindAny()); BOOST_CHECK(addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "::"); BOOST_CHECK_EQUAL(addr.ToString(), "::");
// IPv6, casual // IPv6, casual
@ -244,6 +248,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic)
BOOST_REQUIRE(addr.IsIPv6()); BOOST_REQUIRE(addr.IsIPv6());
BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "1122:3344:5566:7788:9900:aabb:ccdd:eeff"); BOOST_CHECK_EQUAL(addr.ToString(), "1122:3344:5566:7788:9900:aabb:ccdd:eeff");
// TORv2 // TORv2
@ -252,6 +257,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic)
BOOST_REQUIRE(addr.IsTor()); BOOST_REQUIRE(addr.IsTor());
BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion"); BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion");
// TORv3 // TORv3
@ -261,6 +267,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic)
BOOST_REQUIRE(addr.IsTor()); BOOST_REQUIRE(addr.IsTor());
BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(!addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), torv3_addr); BOOST_CHECK_EQUAL(addr.ToString(), torv3_addr);
// TORv3, broken, with wrong checksum // TORv3, broken, with wrong checksum
@ -285,6 +292,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic)
BOOST_REQUIRE(addr.IsInternal()); BOOST_REQUIRE(addr.IsInternal());
BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "esffpvrt3wpeaygy.internal"); BOOST_CHECK_EQUAL(addr.ToString(), "esffpvrt3wpeaygy.internal");
// Totally bogus // Totally bogus
@ -379,6 +387,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2)
s >> addr; s >> addr;
BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsIPv4()); BOOST_CHECK(addr.IsIPv4());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "1.2.3.4"); BOOST_CHECK_EQUAL(addr.ToString(), "1.2.3.4");
BOOST_REQUIRE(s.empty()); BOOST_REQUIRE(s.empty());
@ -415,6 +424,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2)
s >> addr; s >> addr;
BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsIPv6()); BOOST_CHECK(addr.IsIPv6());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "102:304:506:708:90a:b0c:d0e:f10"); BOOST_CHECK_EQUAL(addr.ToString(), "102:304:506:708:90a:b0c:d0e:f10");
BOOST_REQUIRE(s.empty()); BOOST_REQUIRE(s.empty());
@ -426,6 +436,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2)
// sha256(name)[0:10] // sha256(name)[0:10]
s >> addr; s >> addr;
BOOST_CHECK(addr.IsInternal()); BOOST_CHECK(addr.IsInternal());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "zklycewkdo64v6wc.internal"); BOOST_CHECK_EQUAL(addr.ToString(), "zklycewkdo64v6wc.internal");
BOOST_REQUIRE(s.empty()); BOOST_REQUIRE(s.empty());
@ -461,6 +472,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2)
s >> addr; s >> addr;
BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsTor()); BOOST_CHECK(addr.IsTor());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion"); BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion");
BOOST_REQUIRE(s.empty()); BOOST_REQUIRE(s.empty());
@ -482,6 +494,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2)
s >> addr; s >> addr;
BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsTor()); BOOST_CHECK(addr.IsTor());
BOOST_CHECK(!addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), BOOST_CHECK_EQUAL(addr.ToString(),
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"); "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion");
BOOST_REQUIRE(s.empty()); BOOST_REQUIRE(s.empty());
@ -503,6 +516,8 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2)
"f98232ae42d4b6fd2fa81952dfe36a87")); "f98232ae42d4b6fd2fa81952dfe36a87"));
s >> addr; s >> addr;
BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsI2P());
BOOST_CHECK(!addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), BOOST_CHECK_EQUAL(addr.ToString(),
"ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p"); "ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p");
BOOST_REQUIRE(s.empty()); BOOST_REQUIRE(s.empty());
@ -524,6 +539,8 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2)
)); ));
s >> addr; s >> addr;
BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsCJDNS());
BOOST_CHECK(!addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "fc00:1:2:3:4:5:6:7"); BOOST_CHECK_EQUAL(addr.ToString(), "fc00:1:2:3:4:5:6:7");
BOOST_REQUIRE(s.empty()); BOOST_REQUIRE(s.empty());

View File

@ -0,0 +1,79 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Test addrv2 relay
"""
import time
from test_framework.messages import (
CAddress,
msg_addrv2,
NODE_NETWORK,
NODE_WITNESS,
)
from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
ADDRS = []
for i in range(10):
addr = CAddress()
addr.time = int(time.time()) + i
addr.nServices = NODE_NETWORK | NODE_WITNESS
addr.ip = "123.123.123.{}".format(i % 256)
addr.port = 8333 + i
ADDRS.append(addr)
class AddrReceiver(P2PInterface):
addrv2_received_and_checked = False
def __init__(self):
super().__init__(support_addrv2 = True)
def on_addrv2(self, message):
for addr in message.addrs:
assert_equal(addr.nServices, 9)
assert addr.ip.startswith('123.123.123.')
assert (8333 <= addr.port < 8343)
self.addrv2_received_and_checked = True
def wait_for_addrv2(self):
self.wait_until(lambda: "addrv2" in self.last_message)
class AddrTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def run_test(self):
self.log.info('Create connection that sends addrv2 messages')
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
msg = msg_addrv2()
self.log.info('Send too-large addrv2 message')
msg.addrs = ADDRS * 101
with self.nodes[0].assert_debug_log(['addrv2 message size = 1010']):
addr_source.send_and_ping(msg)
self.log.info('Check that addrv2 message content is relayed and added to addrman')
addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
msg.addrs = ADDRS
with self.nodes[0].assert_debug_log([
'Added 10 addresses from 127.0.0.1: 0 tried',
'received: addrv2 (131 bytes) peer=0',
'sending addrv2 (131 bytes) peer=1',
]):
addr_source.send_and_ping(msg)
self.nodes[0].setmocktime(int(time.time()) + 30 * 60)
addr_receiver.wait_for_addrv2()
assert addr_receiver.addrv2_received_and_checked
if __name__ == '__main__':
AddrTest().main()

View File

@ -4,6 +4,9 @@
# 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 time
from test_framework.messages import ( from test_framework.messages import (
CBlockHeader, CBlockHeader,
CInv, CInv,
@ -22,7 +25,10 @@ from test_framework.p2p import (
P2PInterface, P2PInterface,
) )
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal from test_framework.util import (
assert_equal,
hex_str_to_bytes,
)
VALID_DATA_LIMIT = MAX_PROTOCOL_MESSAGE_LENGTH - 5 # Account for the 5-byte length prefix VALID_DATA_LIMIT = MAX_PROTOCOL_MESSAGE_LENGTH - 5 # Account for the 5-byte length prefix
@ -42,6 +48,11 @@ class msg_unrecognized:
return "{}(data={})".format(self.msgtype, self.str_data) return "{}(data={})".format(self.msgtype, self.str_data)
class SenderOfAddrV2(P2PInterface):
def wait_for_sendaddrv2(self):
self.wait_until(lambda: 'sendaddrv2' in self.last_message)
class InvalidMessagesTest(BitcoinTestFramework): class InvalidMessagesTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.num_nodes = 1 self.num_nodes = 1
@ -53,6 +64,10 @@ class InvalidMessagesTest(BitcoinTestFramework):
self.test_checksum() self.test_checksum()
self.test_size() self.test_size()
self.test_msgtype() self.test_msgtype()
self.test_addrv2_empty()
self.test_addrv2_no_addresses()
self.test_addrv2_too_long_address()
self.test_addrv2_unrecognized_network()
self.test_oversized_inv_msg() self.test_oversized_inv_msg()
self.test_oversized_getdata_msg() self.test_oversized_getdata_msg()
self.test_oversized_headers_msg() self.test_oversized_headers_msg()
@ -127,6 +142,84 @@ class InvalidMessagesTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getpeerinfo()[0]['bytesrecv_per_msg']['*other*'], 26) assert_equal(self.nodes[0].getpeerinfo()[0]['bytesrecv_per_msg']['*other*'], 26)
self.nodes[0].disconnect_p2ps() self.nodes[0].disconnect_p2ps()
def test_addrv2(self, label, required_log_messages, raw_addrv2):
node = self.nodes[0]
conn = node.add_p2p_connection(SenderOfAddrV2())
# Make sure bitcoind signals support for ADDRv2, otherwise this test
# will bombard an old node with messages it does not recognize which
# will produce unexpected results.
conn.wait_for_sendaddrv2()
self.log.info('Test addrv2: ' + label)
msg = msg_unrecognized(str_data=b'')
msg.msgtype = b'addrv2'
with node.assert_debug_log(required_log_messages):
# override serialize() which would include the length of the data
msg.serialize = lambda: raw_addrv2
conn.send_raw_message(conn.build_message(msg))
conn.sync_with_ping()
node.disconnect_p2ps()
def test_addrv2_empty(self):
self.test_addrv2('empty',
[
'received: addrv2 (0 bytes)',
'ProcessMessages(addrv2, 0 bytes): Exception',
'end of data',
],
b'')
def test_addrv2_no_addresses(self):
self.test_addrv2('no addresses',
[
'received: addrv2 (1 bytes)',
],
hex_str_to_bytes('00'))
def test_addrv2_too_long_address(self):
self.test_addrv2('too long address',
[
'received: addrv2 (525 bytes)',
'ProcessMessages(addrv2, 525 bytes): Exception',
'Address too long: 513 > 512',
],
hex_str_to_bytes(
'01' + # number of entries
'61bc6649' + # time, Fri Jan 9 02:54:25 UTC 2009
'00' + # service flags, COMPACTSIZE(NODE_NONE)
'01' + # network type (IPv4)
'fd0102' + # address length (COMPACTSIZE(513))
'ab' * 513 + # address
'208d')) # port
def test_addrv2_unrecognized_network(self):
now_hex = struct.pack('<I', int(time.time())).hex()
self.test_addrv2('unrecognized network',
[
'received: addrv2 (25 bytes)',
'IP 9.9.9.9 mapped',
'Added 1 addresses',
],
hex_str_to_bytes(
'02' + # number of entries
# this should be ignored without impeding acceptance of subsequent ones
now_hex + # time
'01' + # service flags, COMPACTSIZE(NODE_NETWORK)
'99' + # network type (unrecognized)
'02' + # address length (COMPACTSIZE(2))
'ab' * 2 + # address
'208d' + # port
# this should be added:
now_hex + # time
'01' + # service flags, COMPACTSIZE(NODE_NETWORK)
'01' + # network type (IPv4)
'04' + # address length (COMPACTSIZE(4))
'09' * 4 + # address
'208d')) # port
def test_oversized_msg(self, msg, size): def test_oversized_msg(self, msg, size):
msg_type = msg.msgtype.decode('ascii') msg_type = msg.msgtype.decode('ascii')
self.log.info("Test {} message of size {} is logged as misbehaving".format(msg_type, size)) self.log.info("Test {} message of size {} is logged as misbehaving".format(msg_type, size))

View File

@ -136,12 +136,17 @@ def uint256_from_compact(c):
return v return v
def deser_vector(f, c): # deser_function_name: Allow for an alternate deserialization function on the
# entries in the vector.
def deser_vector(f, c, deser_function_name=None):
nit = deser_compact_size(f) nit = deser_compact_size(f)
r = [] r = []
for _ in range(nit): for _ in range(nit):
t = c() t = c()
t.deserialize(f) if deser_function_name:
getattr(t, deser_function_name)(f)
else:
t.deserialize(f)
r.append(t) r.append(t)
return r return r
@ -204,38 +209,82 @@ def ToHex(obj):
class CAddress: class CAddress:
__slots__ = ("ip", "nServices", "pchReserved", "port", "time") __slots__ = ("net", "ip", "nServices", "port", "time")
# see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
NET_IPV4 = 1
ADDRV2_NET_NAME = {
NET_IPV4: "IPv4"
}
ADDRV2_ADDRESS_LENGTH = {
NET_IPV4: 4
}
def __init__(self): def __init__(self):
self.time = 0 self.time = 0
self.nServices = 1 self.nServices = 1
self.pchReserved = b"\x00" * 10 + b"\xff" * 2 self.net = self.NET_IPV4
self.ip = "0.0.0.0" self.ip = "0.0.0.0"
self.port = 0 self.port = 0
def deserialize(self, f, *, with_time=True): def deserialize(self, f, *, with_time=True):
"""Deserialize from addrv1 format (pre-BIP155)"""
if with_time: if with_time:
# VERSION messages serialize CAddress objects without time # VERSION messages serialize CAddress objects without time
self.time = struct.unpack("<i", f.read(4))[0] self.time = struct.unpack("<I", f.read(4))[0]
self.nServices = struct.unpack("<Q", f.read(8))[0] self.nServices = struct.unpack("<Q", f.read(8))[0]
self.pchReserved = f.read(12) # We only support IPv4 which means skip 12 bytes and read the next 4 as IPv4 address.
f.read(12)
self.net = self.NET_IPV4
self.ip = socket.inet_ntoa(f.read(4)) self.ip = socket.inet_ntoa(f.read(4))
self.port = struct.unpack(">H", f.read(2))[0] self.port = struct.unpack(">H", f.read(2))[0]
def serialize(self, *, with_time=True): def serialize(self, *, with_time=True):
"""Serialize in addrv1 format (pre-BIP155)"""
assert self.net == self.NET_IPV4
r = b"" r = b""
if with_time: if with_time:
# VERSION messages serialize CAddress objects without time # VERSION messages serialize CAddress objects without time
r += struct.pack("<i", self.time) r += struct.pack("<I", self.time)
r += struct.pack("<Q", self.nServices) r += struct.pack("<Q", self.nServices)
r += self.pchReserved r += b"\x00" * 10 + b"\xff" * 2
r += socket.inet_aton(self.ip)
r += struct.pack(">H", self.port)
return r
def deserialize_v2(self, f):
"""Deserialize from addrv2 format (BIP155)"""
self.time = struct.unpack("<I", f.read(4))[0]
self.nServices = deser_compact_size(f)
self.net = struct.unpack("B", f.read(1))[0]
assert self.net == self.NET_IPV4
address_length = deser_compact_size(f)
assert address_length == self.ADDRV2_ADDRESS_LENGTH[self.net]
self.ip = socket.inet_ntoa(f.read(4))
self.port = struct.unpack(">H", f.read(2))[0]
def serialize_v2(self):
"""Serialize in addrv2 format (BIP155)"""
assert self.net == self.NET_IPV4
r = b""
r += struct.pack("<I", self.time)
r += ser_compact_size(self.nServices)
r += struct.pack("B", self.net)
r += ser_compact_size(self.ADDRV2_ADDRESS_LENGTH[self.net])
r += socket.inet_aton(self.ip) r += socket.inet_aton(self.ip)
r += struct.pack(">H", self.port) r += struct.pack(">H", self.port)
return r return r
def __repr__(self): def __repr__(self):
return "CAddress(nServices=%i ip=%s port=%i)" % (self.nServices, return ("CAddress(nServices=%i net=%s addr=%s port=%i)"
self.ip, self.port) % (self.nServices, self.ADDRV2_NET_NAME[self.net], self.ip, self.port))
class CInv: class CInv:
@ -1064,6 +1113,40 @@ class msg_addr:
return "msg_addr(addrs=%s)" % (repr(self.addrs)) return "msg_addr(addrs=%s)" % (repr(self.addrs))
class msg_addrv2:
__slots__ = ("addrs",)
msgtype = b"addrv2"
def __init__(self):
self.addrs = []
def deserialize(self, f):
self.addrs = deser_vector(f, CAddress, "deserialize_v2")
def serialize(self):
return ser_vector(self.addrs, "serialize_v2")
def __repr__(self):
return "msg_addrv2(addrs=%s)" % (repr(self.addrs))
class msg_sendaddrv2:
__slots__ = ()
msgtype = b"sendaddrv2"
def __init__(self):
pass
def deserialize(self, f):
pass
def serialize(self):
return b""
def __repr__(self):
return "msg_sendaddrv2()"
class msg_inv: class msg_inv:
__slots__ = ("inv",) __slots__ = ("inv",)
msgtype = b"inv" msgtype = b"inv"

View File

@ -33,6 +33,7 @@ from test_framework.messages import (
MAX_HEADERS_RESULTS, MAX_HEADERS_RESULTS,
MIN_VERSION_SUPPORTED, MIN_VERSION_SUPPORTED,
msg_addr, msg_addr,
msg_addrv2,
msg_block, msg_block,
MSG_BLOCK, MSG_BLOCK,
msg_blocktxn, msg_blocktxn,
@ -56,6 +57,7 @@ from test_framework.messages import (
msg_notfound, msg_notfound,
msg_ping, msg_ping,
msg_pong, msg_pong,
msg_sendaddrv2,
msg_sendcmpct, msg_sendcmpct,
msg_sendheaders, msg_sendheaders,
msg_tx, msg_tx,
@ -75,6 +77,7 @@ logger = logging.getLogger("TestFramework.p2p")
MESSAGEMAP = { MESSAGEMAP = {
b"addr": msg_addr, b"addr": msg_addr,
b"addrv2": msg_addrv2,
b"block": msg_block, b"block": msg_block,
b"blocktxn": msg_blocktxn, b"blocktxn": msg_blocktxn,
b"cfcheckpt": msg_cfcheckpt, b"cfcheckpt": msg_cfcheckpt,
@ -97,6 +100,7 @@ MESSAGEMAP = {
b"notfound": msg_notfound, b"notfound": msg_notfound,
b"ping": msg_ping, b"ping": msg_ping,
b"pong": msg_pong, b"pong": msg_pong,
b"sendaddrv2": msg_sendaddrv2,
b"sendcmpct": msg_sendcmpct, b"sendcmpct": msg_sendcmpct,
b"sendheaders": msg_sendheaders, b"sendheaders": msg_sendheaders,
b"tx": msg_tx, b"tx": msg_tx,
@ -285,7 +289,7 @@ class P2PInterface(P2PConnection):
Individual testcases should subclass this and override the on_* methods Individual testcases should subclass this and override the on_* methods
if they want to alter message handling behaviour.""" if they want to alter message handling behaviour."""
def __init__(self): def __init__(self, support_addrv2=False):
super().__init__() super().__init__()
# Track number of messages of each type received. # Track number of messages of each type received.
@ -303,6 +307,8 @@ class P2PInterface(P2PConnection):
# The network services received from the peer # The network services received from the peer
self.nServices = 0 self.nServices = 0
self.support_addrv2 = support_addrv2
def peer_connect(self, *args, services=NODE_NETWORK|NODE_WITNESS, send_version=True, **kwargs): def peer_connect(self, *args, services=NODE_NETWORK|NODE_WITNESS, send_version=True, **kwargs):
create_conn = super().peer_connect(*args, **kwargs) create_conn = super().peer_connect(*args, **kwargs)
@ -345,6 +351,7 @@ class P2PInterface(P2PConnection):
pass pass
def on_addr(self, message): pass def on_addr(self, message): pass
def on_addrv2(self, message): pass
def on_block(self, message): pass def on_block(self, message): pass
def on_blocktxn(self, message): pass def on_blocktxn(self, message): pass
def on_cfcheckpt(self, message): pass def on_cfcheckpt(self, message): pass
@ -365,6 +372,7 @@ class P2PInterface(P2PConnection):
def on_merkleblock(self, message): pass def on_merkleblock(self, message): pass
def on_notfound(self, message): pass def on_notfound(self, message): pass
def on_pong(self, message): pass def on_pong(self, message): pass
def on_sendaddrv2(self, message): pass
def on_sendcmpct(self, message): pass def on_sendcmpct(self, message): pass
def on_sendheaders(self, message): pass def on_sendheaders(self, message): pass
def on_tx(self, message): pass def on_tx(self, message): pass
@ -389,6 +397,8 @@ class P2PInterface(P2PConnection):
if message.nVersion >= 70016: if message.nVersion >= 70016:
self.send_message(msg_wtxidrelay()) self.send_message(msg_wtxidrelay())
self.send_message(msg_verack()) self.send_message(msg_verack())
if self.support_addrv2:
self.send_message(msg_sendaddrv2())
self.nServices = message.nServices self.nServices = message.nServices
# Connection helper methods # Connection helper methods

View File

@ -154,6 +154,7 @@ BASE_SCRIPTS = [
'feature_proxy.py', 'feature_proxy.py',
'rpc_signrawtransaction.py', 'rpc_signrawtransaction.py',
'wallet_groups.py', 'wallet_groups.py',
'p2p_addrv2_relay.py',
'p2p_disconnect_ban.py', 'p2p_disconnect_ban.py',
'rpc_decodescript.py', 'rpc_decodescript.py',
'rpc_blockchain.py', 'rpc_blockchain.py',