Framework for banning mis-behaving peers
authorGavin Andresen <gavinandresen@gmail.com>
Tue, 6 Sep 2011 20:09:04 +0000 (16:09 -0400)
committerGavin Andresen <gavinandresen@gmail.com>
Wed, 21 Sep 2011 16:49:53 +0000 (12:49 -0400)
src/init.cpp
src/net.cpp
src/net.h
src/test/DoS_tests.cpp [new file with mode: 0644]
src/test/test_bitcoin.cpp

index dbc2c41..158418d 100644 (file)
@@ -179,6 +179,8 @@ bool AppInit2(int argc, char* argv[])
             "  -addnode=<ip>    \t  "   + _("Add a node to connect to\n") +
             "  -connect=<ip>    \t\t  " + _("Connect only to the specified node\n") +
             "  -nolisten        \t  "   + _("Don't accept connections from outside\n") +
+            "  -banscore=<n>    \t  "   + _("Threshold for disconnecting misbehaving peers (default: 100)\n") +
+            "  -bantime=<n>     \t  "   + _("Number of seconds to keep misbehaving peers from reconnecting (default: 86400)\n") +
 #ifdef USE_UPNP
 #if USE_UPNP
             "  -noupnp          \t  "   + _("Don't attempt to use UPnP to map the listening port\n") +
index 2e257a6..1792bf7 100644 (file)
@@ -726,6 +726,52 @@ void CNode::Cleanup()
 }
 
 
+std::map<unsigned int, int64> CNode::setBanned;
+CCriticalSection CNode::cs_setBanned;
+
+void CNode::ClearBanned()
+{
+    setBanned.clear();
+}
+
+bool CNode::IsBanned(unsigned int ip)
+{
+    bool fResult = false;
+    CRITICAL_BLOCK(cs_setBanned)
+    {
+        std::map<unsigned int, int64>::iterator i = setBanned.find(ip);
+        if (i != setBanned.end())
+        {
+            int64 t = (*i).second;
+            if (GetTime() < t)
+                fResult = true;
+        }
+    }
+    return fResult;
+}
+
+bool CNode::Misbehaving(int howmuch)
+{
+    if (addr.IsLocal())
+    {
+        printf("Warning: local node %s misbehaving\n", addr.ToString().c_str());
+        return false;
+    }
+
+    nMisbehavior += howmuch;
+    if (nMisbehavior >= GetArg("-banscore", 100))
+    {
+        int64 banTime = GetTime()+GetArg("-bantime", 60*60*24);  // Default 24-hour ban
+        CRITICAL_BLOCK(cs_setBanned)
+            if (setBanned[addr.ip] < banTime)
+                setBanned[addr.ip] = banTime;
+        CloseSocketDisconnect();
+        printf("Disconnected %s for misbehavior (score=%d)\n", addr.ToString().c_str(), nMisbehavior);
+        return true;
+    }
+    return false;
+}
+
 
 
 
@@ -896,6 +942,11 @@ void ThreadSocketHandler2(void* parg)
             {
                 closesocket(hSocket);
             }
+            else if (CNode::IsBanned(addr.ip))
+            {
+                printf("connetion from %s dropped (banned)\n", addr.ToString().c_str());
+                closesocket(hSocket);
+            }
             else
             {
                 printf("accepted connection %s\n", addr.ToString().c_str());
@@ -1454,7 +1505,8 @@ bool OpenNetworkConnection(const CAddress& addrConnect)
     //
     if (fShutdown)
         return false;
-    if (addrConnect.ip == addrLocalHost.ip || !addrConnect.IsIPv4() || FindNode(addrConnect.ip))
+    if (addrConnect.ip == addrLocalHost.ip || !addrConnect.IsIPv4() ||
+        FindNode(addrConnect.ip) || CNode::IsBanned(addrConnect.ip))
         return false;
 
     vnThreadsRunning[1]--;
index 0026e40..5b3568f 100644 (file)
--- a/src/net.h
+++ b/src/net.h
@@ -124,6 +124,13 @@ public:
     bool fDisconnect;
 protected:
     int nRefCount;
+
+    // Denial-of-service detection/prevention
+    // Key is ip address, value is banned-until-time
+    static std::map<unsigned int, int64> setBanned;
+    static CCriticalSection cs_setBanned;
+    int nMisbehavior;
+
 public:
     int64 nReleaseTime;
     std::map<uint256, CRequestTracker> mapRequests;
@@ -148,7 +155,6 @@ public:
     // publish and subscription
     std::vector<char> vfSubscribe;
 
-
     CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false)
     {
         nServices = 0;
@@ -185,6 +191,7 @@ public:
         nStartingHeight = -1;
         fGetAddr = false;
         vfSubscribe.assign(256, false);
+        nMisbehavior = 0;
 
         // Be shy and don't send version until we hear
         if (!fInbound)
@@ -568,6 +575,25 @@ public:
     void CancelSubscribe(unsigned int nChannel);
     void CloseSocketDisconnect();
     void Cleanup();
+
+
+    // Denial-of-service detection/prevention
+    // The idea is to detect peers that are behaving
+    // badly and disconnect/ban them, but do it in a
+    // one-coding-mistake-won't-shatter-the-entire-network
+    // way.
+    // IMPORTANT:  There should be nothing I can give a
+    // node that it will forward on that will make that
+    // node's peers drop it. If there is, an attacker
+    // can isolate a node and/or try to split the network.
+    // Dropping a node for sending stuff that is invalid
+    // now but might be valid in a later version is also
+    // dangerous, because it can cause a network split
+    // between nodes running old code and nodes running
+    // new code.
+    static void ClearBanned(); // needed for unit testing
+    static bool IsBanned(unsigned int ip);
+    bool Misbehaving(int howmuch); // 1 == a little, 100 == a lot
 };
 
 
diff --git a/src/test/DoS_tests.cpp b/src/test/DoS_tests.cpp
new file mode 100644 (file)
index 0000000..e60bb74
--- /dev/null
@@ -0,0 +1,68 @@
+//
+// Unit tests for denial-of-service detection/prevention code
+//
+#include <boost/test/unit_test.hpp>
+#include <boost/foreach.hpp>
+
+#include "../main.h"
+#include "../net.h"
+#include "../util.h"
+
+using namespace std;
+
+BOOST_AUTO_TEST_SUITE(DoS_tests)
+
+BOOST_AUTO_TEST_CASE(DoS_banning)
+{
+    CNode::ClearBanned();
+    CAddress addr1(0xa0b0c001);
+    CNode dummyNode1(INVALID_SOCKET, addr1, true);
+    dummyNode1.Misbehaving(100); // Should get banned
+    BOOST_CHECK(CNode::IsBanned(addr1.ip));
+    BOOST_CHECK(!CNode::IsBanned(addr1.ip|0x0000ff00)); // Different ip, not banned
+
+    CAddress addr2(0xa0b0c002);
+    CNode dummyNode2(INVALID_SOCKET, addr2, true);
+    dummyNode2.Misbehaving(50);
+    BOOST_CHECK(!CNode::IsBanned(addr2.ip)); // 2 not banned yet...
+    BOOST_CHECK(CNode::IsBanned(addr1.ip));  // ... but 1 still should be
+    dummyNode2.Misbehaving(50);
+    BOOST_CHECK(CNode::IsBanned(addr2.ip));
+}    
+
+BOOST_AUTO_TEST_CASE(DoS_banscore)
+{
+    CNode::ClearBanned();
+    mapArgs["-banscore"] = "111"; // because 11 is my favorite number
+    CAddress addr1(0xa0b0c001);
+    CNode dummyNode1(INVALID_SOCKET, addr1, true);
+    dummyNode1.Misbehaving(100);
+    BOOST_CHECK(!CNode::IsBanned(addr1.ip));
+    dummyNode1.Misbehaving(10);
+    BOOST_CHECK(!CNode::IsBanned(addr1.ip));
+    dummyNode1.Misbehaving(1);
+    BOOST_CHECK(CNode::IsBanned(addr1.ip));
+    mapArgs["-banscore"] = "100";
+}
+
+BOOST_AUTO_TEST_CASE(DoS_bantime)
+{
+    CNode::ClearBanned();
+    int64 nStartTime = GetTime();
+    SetMockTime(nStartTime); // Overrides future calls to GetTime()
+
+    CAddress addr(0xa0b0c001);
+    CNode dummyNode(INVALID_SOCKET, addr, true);
+
+    dummyNode.Misbehaving(100);
+    BOOST_CHECK(CNode::IsBanned(addr.ip));
+
+    SetMockTime(nStartTime+60*60);
+    BOOST_CHECK(CNode::IsBanned(addr.ip));
+
+    SetMockTime(nStartTime+60*60*24+1);
+    BOOST_CHECK(!CNode::IsBanned(addr.ip));
+}    
+
+
+BOOST_AUTO_TEST_SUITE_END()
index 0230bb6..c6f6d94 100644 (file)
@@ -8,7 +8,7 @@
 #include "uint256_tests.cpp"
 #include "script_tests.cpp"
 #include "transaction_tests.cpp"
-
+#include "DoS_tests.cpp"
 
 CWallet* pwalletMain;