git / moonbite-core / src/test/denialofservice_tests.cpp · raw

// Copyright (c) 2011-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.// Unit tests for denial-of-service detection/prevention code#include <arith_uint256.h>#include <banman.h>#include <chainparams.h>#include <net.h>#include <net_processing.h>#include <pubkey.h>#include <script/sign.h>#include <script/signingprovider.h>#include <script/standard.h>#include <serialize.h>#include <util/memory.h>#include <util/string.h>#include <util/system.h>#include <util/time.h>#include <validation.h>#include <test/util/setup_common.h>#include <array>#include <stdint.h>#include <boost/test/unit_test.hpp>struct CConnmanTest : public CConnman {    using CConnman::CConnman;    void AddNode(CNode& node)    {        LOCK(cs_vNodes);        vNodes.push_back(&node);    }    void ClearNodes()    {        LOCK(cs_vNodes);        for (CNode* node : vNodes) {            delete node;        }        vNodes.clear();    }};// Tests these internal-to-net_processing.cpp methods:extern bool AddOrphanTx(const CTransactionRef& tx, NodeId peer);extern void EraseOrphansFor(NodeId peer);extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans);struct COrphanTx {    CTransactionRef tx;    NodeId fromPeer;    int64_t nTimeExpire;};extern std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans);static CService ip(uint32_t i){    struct in_addr s;    s.s_addr = i;    return CService(CNetAddr(s), Params().GetDefaultPort());}static NodeId id = 0;void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds);BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup)// Test eviction of an outbound peer whose chain never advances// Mock a node connection, and use mocktime to simulate a peer// which never sends any headers messages.  PeerLogic should// decide to evict that outbound peer, after the appropriate timeouts.// Note that we protect 4 outbound nodes from being subject to// this logic; this test takes advantage of that protection only// being applied to nodes which send headers with sufficient// work.BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction){    const CChainParams& chainparams = Params();    auto connman = MakeUnique<CConnman>(0x1337, 0x1337);    auto peerLogic = MakeUnique<PeerManager>(chainparams, *connman, nullptr, *m_node.scheduler, *m_node.chainman, *m_node.mempool);    // Mock an outbound peer    CAddress addr1(ip(0xa0b0c001), NODE_NONE);    CNode dummyNode1(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), 0, INVALID_SOCKET, addr1, 0, 0, CAddress(), "", ConnectionType::OUTBOUND_FULL_RELAY);    dummyNode1.SetCommonVersion(PROTOCOL_VERSION);    peerLogic->InitializeNode(&dummyNode1);    dummyNode1.fSuccessfullyConnected = true;    // This test requires that we have a chain with non-zero work.    {        LOCK(cs_main);        BOOST_CHECK(::ChainActive().Tip() != nullptr);        BOOST_CHECK(::ChainActive().Tip()->nChainWork > 0);    }    // Test starts here    {        LOCK(dummyNode1.cs_sendProcessing);        BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in getheaders    }    {        LOCK(dummyNode1.cs_vSend);        BOOST_CHECK(dummyNode1.vSendMsg.size() > 0);        dummyNode1.vSendMsg.clear();    }    int64_t nStartTime = GetTime();    // Wait 21 minutes    SetMockTime(nStartTime+21*60);    {        LOCK(dummyNode1.cs_sendProcessing);        BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in getheaders    }    {        LOCK(dummyNode1.cs_vSend);        BOOST_CHECK(dummyNode1.vSendMsg.size() > 0);    }    // Wait 3 more minutes    SetMockTime(nStartTime+24*60);    {        LOCK(dummyNode1.cs_sendProcessing);        BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in disconnect    }    BOOST_CHECK(dummyNode1.fDisconnect == true);    SetMockTime(0);    bool dummy;    peerLogic->FinalizeNode(dummyNode1, dummy);}static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerManager &peerLogic, CConnmanTest* connman){    CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE);    vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), 0, INVALID_SOCKET, addr, 0, 0, CAddress(), "", ConnectionType::OUTBOUND_FULL_RELAY));    CNode &node = *vNodes.back();    node.SetCommonVersion(PROTOCOL_VERSION);    peerLogic.InitializeNode(&node);    node.fSuccessfullyConnected = true;    connman->AddNode(node);}BOOST_AUTO_TEST_CASE(stale_tip_peer_management){    const CChainParams& chainparams = Params();    auto connman = MakeUnique<CConnmanTest>(0x1337, 0x1337);    auto peerLogic = MakeUnique<PeerManager>(chainparams, *connman, nullptr, *m_node.scheduler, *m_node.chainman, *m_node.mempool);    constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS;    CConnman::Options options;    options.nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS;    options.m_max_outbound_full_relay = max_outbound_full_relay;    options.nMaxFeeler = MAX_FEELER_CONNECTIONS;    connman->Init(options);    std::vector<CNode *> vNodes;    // Mock some outbound peers    for (int i=0; i<max_outbound_full_relay; ++i) {        AddRandomOutboundPeer(vNodes, *peerLogic, connman.get());    }    peerLogic->CheckForStaleTipAndEvictPeers();    // No nodes should be marked for disconnection while we have no extra peers    for (const CNode *node : vNodes) {        BOOST_CHECK(node->fDisconnect == false);    }    SetMockTime(GetTime() + 3 * chainparams.GetConsensus().nPowTargetSpacing + 1);    // Now tip should definitely be stale, and we should look for an extra    // outbound peer    peerLogic->CheckForStaleTipAndEvictPeers();    BOOST_CHECK(connman->GetTryNewOutboundPeer());    // Still no peers should be marked for disconnection    for (const CNode *node : vNodes) {        BOOST_CHECK(node->fDisconnect == false);    }    // If we add one more peer, something should get marked for eviction    // on the next check (since we're mocking the time to be in the future, the    // required time connected check should be satisfied).    AddRandomOutboundPeer(vNodes, *peerLogic, connman.get());    peerLogic->CheckForStaleTipAndEvictPeers();    for (int i = 0; i < max_outbound_full_relay; ++i) {        BOOST_CHECK(vNodes[i]->fDisconnect == false);    }    // Last added node should get marked for eviction    BOOST_CHECK(vNodes.back()->fDisconnect == true);    vNodes.back()->fDisconnect = false;    // Update the last announced block time for the last    // peer, and check that the next newest node gets evicted.    UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime());    peerLogic->CheckForStaleTipAndEvictPeers();    for (int i = 0; i < max_outbound_full_relay - 1; ++i) {        BOOST_CHECK(vNodes[i]->fDisconnect == false);    }    BOOST_CHECK(vNodes[max_outbound_full_relay-1]->fDisconnect == true);    BOOST_CHECK(vNodes.back()->fDisconnect == false);    bool dummy;    for (const CNode *node : vNodes) {        peerLogic->FinalizeNode(*node, dummy);    }    connman->ClearNodes();}BOOST_AUTO_TEST_CASE(peer_discouragement){    const CChainParams& chainparams = Params();    auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);    auto connman = MakeUnique<CConnmanTest>(0x1337, 0x1337);    auto peerLogic = MakeUnique<PeerManager>(chainparams, *connman, banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool);    CNetAddr tor_netaddr;    BOOST_REQUIRE(        tor_netaddr.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"));    const CService tor_service(tor_netaddr, Params().GetDefaultPort());    const std::array<CAddress, 3> addr{CAddress{ip(0xa0b0c001), NODE_NONE},                                       CAddress{ip(0xa0b0c002), NODE_NONE},                                       CAddress{tor_service, NODE_NONE}};    const CNetAddr other_addr{ip(0xa0b0ff01)}; // Not any of addr[].    std::array<CNode*, 3> nodes;    banman->ClearBanned();    nodes[0] = new CNode{id++, NODE_NETWORK, 0, INVALID_SOCKET, addr[0], 0, 0, CAddress(), "", ConnectionType::INBOUND};    nodes[0]->SetCommonVersion(PROTOCOL_VERSION);    peerLogic->InitializeNode(nodes[0]);    nodes[0]->fSuccessfullyConnected = true;    connman->AddNode(*nodes[0]);    peerLogic->Misbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD, /* message */ ""); // Should be discouraged    {        LOCK(nodes[0]->cs_sendProcessing);        BOOST_CHECK(peerLogic->SendMessages(nodes[0]));    }    BOOST_CHECK(banman->IsDiscouraged(addr[0]));    BOOST_CHECK(nodes[0]->fDisconnect);    BOOST_CHECK(!banman->IsDiscouraged(other_addr)); // Different address, not discouraged    nodes[1] = new CNode{id++, NODE_NETWORK, 0, INVALID_SOCKET, addr[1], 1, 1, CAddress(), "", ConnectionType::INBOUND};    nodes[1]->SetCommonVersion(PROTOCOL_VERSION);    peerLogic->InitializeNode(nodes[1]);    nodes[1]->fSuccessfullyConnected = true;    connman->AddNode(*nodes[1]);    peerLogic->Misbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1, /* message */ "");    {        LOCK(nodes[1]->cs_sendProcessing);        BOOST_CHECK(peerLogic->SendMessages(nodes[1]));    }    // [0] is still discouraged/disconnected.    BOOST_CHECK(banman->IsDiscouraged(addr[0]));    BOOST_CHECK(nodes[0]->fDisconnect);    // [1] is not discouraged/disconnected yet.    BOOST_CHECK(!banman->IsDiscouraged(addr[1]));    BOOST_CHECK(!nodes[1]->fDisconnect);    peerLogic->Misbehaving(nodes[1]->GetId(), 1, /* message */ ""); // [1] reaches discouragement threshold    {        LOCK(nodes[1]->cs_sendProcessing);        BOOST_CHECK(peerLogic->SendMessages(nodes[1]));    }    // Expect both [0] and [1] to be discouraged/disconnected now.    BOOST_CHECK(banman->IsDiscouraged(addr[0]));    BOOST_CHECK(nodes[0]->fDisconnect);    BOOST_CHECK(banman->IsDiscouraged(addr[1]));    BOOST_CHECK(nodes[1]->fDisconnect);    // Make sure non-IP peers are discouraged and disconnected properly.    nodes[2] = new CNode{id++, NODE_NETWORK, 0, INVALID_SOCKET, addr[2], 1, 1, CAddress(), "",                         ConnectionType::OUTBOUND_FULL_RELAY};    nodes[2]->SetCommonVersion(PROTOCOL_VERSION);    peerLogic->InitializeNode(nodes[2]);    nodes[2]->fSuccessfullyConnected = true;    connman->AddNode(*nodes[2]);    peerLogic->Misbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD, /* message */ "");    {        LOCK(nodes[2]->cs_sendProcessing);        BOOST_CHECK(peerLogic->SendMessages(nodes[2]));    }    BOOST_CHECK(banman->IsDiscouraged(addr[0]));    BOOST_CHECK(banman->IsDiscouraged(addr[1]));    BOOST_CHECK(banman->IsDiscouraged(addr[2]));    BOOST_CHECK(nodes[0]->fDisconnect);    BOOST_CHECK(nodes[1]->fDisconnect);    BOOST_CHECK(nodes[2]->fDisconnect);    bool dummy;    for (CNode* node : nodes) {        peerLogic->FinalizeNode(*node, dummy);    }    connman->ClearNodes();}BOOST_AUTO_TEST_CASE(DoS_bantime){    const CChainParams& chainparams = Params();    auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);    auto connman = MakeUnique<CConnman>(0x1337, 0x1337);    auto peerLogic = MakeUnique<PeerManager>(chainparams, *connman, banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool);    banman->ClearBanned();    int64_t nStartTime = GetTime();    SetMockTime(nStartTime); // Overrides future calls to GetTime()    CAddress addr(ip(0xa0b0c001), NODE_NONE);    CNode dummyNode(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr, 4, 4, CAddress(), "", ConnectionType::INBOUND);    dummyNode.SetCommonVersion(PROTOCOL_VERSION);    peerLogic->InitializeNode(&dummyNode);    dummyNode.fSuccessfullyConnected = true;    peerLogic->Misbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD, /* message */ "");    {        LOCK(dummyNode.cs_sendProcessing);        BOOST_CHECK(peerLogic->SendMessages(&dummyNode));    }    BOOST_CHECK(banman->IsDiscouraged(addr));    bool dummy;    peerLogic->FinalizeNode(dummyNode, dummy);}static CTransactionRef RandomOrphan(){    std::map<uint256, COrphanTx>::iterator it;    LOCK2(cs_main, g_cs_orphans);    it = mapOrphanTransactions.lower_bound(InsecureRand256());    if (it == mapOrphanTransactions.end())        it = mapOrphanTransactions.begin();    return it->second.tx;}static void MakeNewKeyWithFastRandomContext(CKey& key){    std::vector<unsigned char> keydata;    keydata = g_insecure_rand_ctx.randbytes(32);    key.Set(keydata.data(), keydata.data() + keydata.size(), /*fCompressedIn*/ true);    assert(key.IsValid());}BOOST_AUTO_TEST_CASE(DoS_mapOrphans){    // This test had non-deterministic coverage due to    // randomly selected seeds.    // This seed is chosen so that all branches of the function    // ecdsa_signature_parse_der_lax are executed during this test.    // Specifically branches that run only when an ECDSA    // signature's R and S values have leading zeros.    g_insecure_rand_ctx = FastRandomContext(ArithToUint256(arith_uint256(33)));    CKey key;    MakeNewKeyWithFastRandomContext(key);    FillableSigningProvider keystore;    BOOST_CHECK(keystore.AddKey(key));    // 50 orphan transactions:    for (int i = 0; i < 50; i++)    {        CMutableTransaction tx;        tx.vin.resize(1);        tx.vin[0].prevout.n = 0;        tx.vin[0].prevout.hash = InsecureRand256();        tx.vin[0].scriptSig << OP_1;        tx.vout.resize(1);        tx.vout[0].nValue = 1*CENT;        tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));        AddOrphanTx(MakeTransactionRef(tx), i);    }    // ... and 50 that depend on other orphans:    for (int i = 0; i < 50; i++)    {        CTransactionRef txPrev = RandomOrphan();        CMutableTransaction tx;        tx.vin.resize(1);        tx.vin[0].prevout.n = 0;        tx.vin[0].prevout.hash = txPrev->GetHash();        tx.vout.resize(1);        tx.vout[0].nValue = 1*CENT;        tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));        BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL));        AddOrphanTx(MakeTransactionRef(tx), i);    }    // This really-big orphan should be ignored:    for (int i = 0; i < 10; i++)    {        CTransactionRef txPrev = RandomOrphan();        CMutableTransaction tx;        tx.vout.resize(1);        tx.vout[0].nValue = 1*CENT;        tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));        tx.vin.resize(2777);        for (unsigned int j = 0; j < tx.vin.size(); j++)        {            tx.vin[j].prevout.n = j;            tx.vin[j].prevout.hash = txPrev->GetHash();        }        BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL));        // Re-use same signature for other inputs        // (they don't have to be valid for this test)        for (unsigned int j = 1; j < tx.vin.size(); j++)            tx.vin[j].scriptSig = tx.vin[0].scriptSig;        BOOST_CHECK(!AddOrphanTx(MakeTransactionRef(tx), i));    }    LOCK2(cs_main, g_cs_orphans);    // Test EraseOrphansFor:    for (NodeId i = 0; i < 3; i++)    {        size_t sizeBefore = mapOrphanTransactions.size();        EraseOrphansFor(i);        BOOST_CHECK(mapOrphanTransactions.size() < sizeBefore);    }    // Test LimitOrphanTxSize() function:    LimitOrphanTxSize(40);    BOOST_CHECK(mapOrphanTransactions.size() <= 40);    LimitOrphanTxSize(10);    BOOST_CHECK(mapOrphanTransactions.size() <= 10);    LimitOrphanTxSize(0);    BOOST_CHECK(mapOrphanTransactions.empty());}BOOST_AUTO_TEST_SUITE_END()