Introduce whitelisted peers.

This adds a -whitelist option to specify subnet ranges from which peers
that connect are whitelisted. In addition, there is a -whitebind option
which works like -bind, except peers connecting to it are also
whitelisted (allowing a separate listen port for trusted connections).

Being whitelisted has two effects (for now):
* They are immune to DoS disconnection/banning.
* Transactions they broadcast (which are valid) are always relayed,
  even if they were already in the mempool. This means that a node
  can function as a gateway for a local network, and that rebroadcasts
  from the local network will work as expected.

Whitelisting replaces the magic exemption localhost had for DoS
disconnection (local addresses are still never banned, though), which
implied hidden service connects (from a localhost Tor node) were
incorrectly immune to DoS disconnection as well. This old
behaviour is removed for that reason, but can be restored using
-whitelist=127.0.0.1 or -whitelist=::1 can be specified. -whitebind
is safer to use in case non-trusted localhost connections are expected
(like hidden services).
This commit is contained in:
Pieter Wuille 2014-06-21 13:34:36 +02:00 committed by Ross Nicoll
parent 33333d2e88
commit 6f86ee9cae
6 changed files with 101 additions and 29 deletions

View file

@ -10,7 +10,7 @@ touch "$DATADIR/regtest/debug.log"
tail -q -n 1 -F "$DATADIR/regtest/debug.log" | grep -m 1 -q "Done loading" &
WAITER=$!
PORT=`expr $BASHPID + 10000`
"@abs_top_builddir@/src/bitcoind@EXEEXT@" -connect=0.0.0.0 -datadir="$DATADIR" -rpcuser=user -rpcpassword=pass -listen -keypool=3 -debug -debug=net -logtimestamps -port=$PORT -regtest -rpcport=`expr $PORT + 1` &
"@abs_top_builddir@/src/bitcoind@EXEEXT@" -connect=0.0.0.0 -datadir="$DATADIR" -rpcuser=user -rpcpassword=pass -listen -keypool=3 -debug -debug=net -logtimestamps -port=$PORT -whitelist=127.0.0.1 -regtest -rpcport=`expr $PORT + 1` &
BITCOIND=$!
#Install a watchdog.

View file

@ -62,7 +62,8 @@ CWallet* pwalletMain;
enum BindFlags {
BF_NONE = 0,
BF_EXPLICIT = (1U << 0),
BF_REPORT_ERROR = (1U << 1)
BF_REPORT_ERROR = (1U << 1),
BF_WHITELIST = (1U << 2),
};
@ -183,7 +184,7 @@ bool static Bind(const CService &addr, unsigned int flags) {
if (!(flags & BF_EXPLICIT) && IsLimited(addr))
return false;
std::string strError;
if (!BindListenPort(addr, strError)) {
if (!BindListenPort(addr, strError, flags & BF_WHITELIST)) {
if (flags & BF_REPORT_ERROR)
return InitError(strError);
return false;
@ -246,6 +247,8 @@ std::string HelpMessage(HelpMessageMode hmm)
strUsage += " -upnp " + _("Use UPnP to map the listening port (default: 0)") + "\n";
#endif
#endif
strUsage += " -whitebind=<addr> " + _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6") + "\n";
strUsage += " -whitelist=<netmask> " + _("Whitelist peers connecting from the given netmask or ip. Can be specified multiple times.") + "\n";
#ifdef ENABLE_WALLET
strUsage += "\n" + _("Wallet options:") + "\n";
@ -478,11 +481,11 @@ bool AppInit2(boost::thread_group& threadGroup)
nLocalServices |= NODE_BLOOM;
}
if (mapArgs.count("-bind")) {
if (mapArgs.count("-bind") || mapArgs.count("-whitebind")) {
// when specifying an explicit binding address, you want to listen on it
// even when -connect or -proxy is specified
if (SoftSetBoolArg("-listen", true))
LogPrintf("AppInit2 : parameter interaction: -bind set -> setting -listen=1\n");
LogPrintf("AppInit2 : parameter interaction: -bind or -whitebind set -> setting -listen=1\n");
}
if (mapArgs.count("-connect") && mapMultiArgs["-connect"].size() > 0) {
@ -526,7 +529,7 @@ bool AppInit2(boost::thread_group& threadGroup)
}
// Make sure enough file descriptors are available
int nBind = std::max((int)mapArgs.count("-bind"), 1);
int nBind = std::max((int)mapArgs.count("-bind") + (int)mapArgs.count("-whitebind"), 1);
nMaxConnections = GetArg("-maxconnections", 125);
nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0);
int nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS);
@ -752,6 +755,15 @@ bool AppInit2(boost::thread_group& threadGroup)
}
}
if (mapArgs.count("-whitelist")) {
BOOST_FOREACH(const std::string& net, mapMultiArgs["-whitelist"]) {
CSubNet subnet(net);
if (!subnet.IsValid())
return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'"), net));
CNode::AddWhitelistedRange(subnet);
}
}
CService addrProxy;
bool fProxy = false;
if (mapArgs.count("-proxy")) {
@ -792,13 +804,21 @@ bool AppInit2(boost::thread_group& threadGroup)
bool fBound = false;
if (fListen) {
if (mapArgs.count("-bind")) {
if (mapArgs.count("-bind") || mapArgs.count("-whitebind")) {
BOOST_FOREACH(std::string strBind, mapMultiArgs["-bind"]) {
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false))
return InitError(strprintf(_("Cannot resolve -bind address: '%s'"), strBind));
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR));
}
BOOST_FOREACH(std::string strBind, mapMultiArgs["-whitebind"]) {
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, 0, false))
return InitError(strprintf(_("Cannot resolve -whitebind address: '%s'"), strBind));
if (addrBind.GetPort() == 0)
return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'"), strBind));
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST));
}
}
else {
struct in_addr inaddr_any;

View file

@ -209,7 +209,7 @@ struct CBlockReject {
struct CNodeState {
// Accumulated misbehaviour score for this peer.
int nMisbehavior;
// Whether this peer should be disconnected and banned.
// Whether this peer should be disconnected and banned (unless whitelisted).
bool fShouldBan;
// String name of this peer (debugging/logging purposes).
std::string name;
@ -1571,7 +1571,8 @@ void Misbehaving(NodeId pnode, int howmuch)
return;
state->nMisbehavior += howmuch;
if (state->nMisbehavior >= GetArg("-banscore", 100))
int banscore = GetArg("-banscore", 100);
if (state->nMisbehavior >= banscore && state->nMisbehavior - howmuch < banscore)
{
LogPrintf("Misbehaving: %s (%d -> %d) BAN THRESHOLD EXCEEDED\n", state->name, state->nMisbehavior-howmuch, state->nMisbehavior);
state->fShouldBan = true;
@ -4163,6 +4164,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx);
if (nEvicted > 0)
LogPrint("mempool", "mapOrphan overflow, removed %u tx\n", nEvicted);
} else if (pfrom->fWhitelisted) {
// Always relay transactions received from whitelisted peers, even
// if they are already in the mempool (allowing the node to function
// as a gateway for nodes hidden behind it).
RelayTransaction(tx, tx.GetHash());
}
int nDoS = 0;
if (state.IsInvalid(nDoS))
@ -4649,11 +4655,14 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
CNodeState &state = *State(pto->GetId());
if (state.fShouldBan) {
if (pto->addr.IsLocal())
LogPrintf("Warning: not banning local node %s!\n", pto->addr.ToString());
if (pto->fWhitelisted)
LogPrintf("Warning: not punishing whitelisted peer %s!\n", pto->addr.ToString());
else {
pto->fDisconnect = true;
CNode::Ban(pto->addr);
if (pto->addr.IsLocal())
LogPrintf("Warning: not banning local peer %s!\n", pto->addr.ToString());
else
CNode::Ban(pto->addr);
}
state.fShouldBan = false;
}

View file

@ -50,6 +50,17 @@
using namespace std;
using namespace boost;
namespace {
const int MAX_OUTBOUND_CONNECTIONS = 8;
struct ListenSocket {
SOCKET socket;
bool whitelisted;
ListenSocket(SOCKET socket, bool whitelisted) : socket(socket), whitelisted(whitelisted) {}
};
}
bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false);
@ -66,7 +77,7 @@ static bool vfLimited[NET_MAX] = {};
static CNode* pnodeLocalHost = NULL;
static CNode* pnodeSync = NULL;
uint64_t nLocalHostNonce = 0;
static std::vector<SOCKET> vhListenSocket;
static std::vector<ListenSocket> vhListenSocket;
CAddrMan addrman;
int nMaxConnections = 125;
int nMaxOutboundConnections = 8;
@ -577,6 +588,24 @@ bool CNode::Ban(const CNetAddr &addr) {
return true;
}
std::vector<CSubNet> CNode::vWhitelistedRange;
CCriticalSection CNode::cs_vWhitelistedRange;
bool CNode::IsWhitelistedRange(const CNetAddr &addr) {
LOCK(cs_vWhitelistedRange);
BOOST_FOREACH(const CSubNet& subnet, vWhitelistedRange) {
if (subnet.Match(addr))
return true;
}
return false;
}
void CNode::AddWhitelistedRange(const CSubNet &subnet) {
LOCK(cs_vWhitelistedRange);
vWhitelistedRange.push_back(subnet);
}
#undef X
#define X(name) stats.name = name
void CNode::copyStats(CNodeStats &stats)
@ -593,6 +622,7 @@ void CNode::copyStats(CNodeStats &stats)
X(nStartingHeight);
X(nSendBytes);
X(nRecvBytes);
X(fWhitelisted);
stats.fSyncNode = (this == pnodeSync);
// It is common for nodes with good ping times to suddenly become lagged,
@ -828,9 +858,9 @@ void ThreadSocketHandler()
SOCKET hSocketMax = 0;
bool have_fds = false;
BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) {
FD_SET(hListenSocket, &fdsetRecv);
hSocketMax = max(hSocketMax, hListenSocket);
BOOST_FOREACH(const ListenSocket& hListenSocket, vhListenSocket) {
FD_SET(hListenSocket.socket, &fdsetRecv);
hSocketMax = max(hSocketMax, hListenSocket.socket);
have_fds = true;
}
@ -897,13 +927,13 @@ void ThreadSocketHandler()
//
// Accept new connections
//
BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket)
BOOST_FOREACH(const ListenSocket& hListenSocket, vhListenSocket)
{
if (hListenSocket != INVALID_SOCKET && FD_ISSET(hListenSocket, &fdsetRecv))
if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv))
{
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
SOCKET hSocket = accept(hListenSocket, (struct sockaddr*)&sockaddr, &len);
SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len);
CAddress addr;
int nInbound = 0;
@ -911,6 +941,7 @@ void ThreadSocketHandler()
if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr))
LogPrintf("Warning: Unknown socket family\n");
bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr);
{
LOCK(cs_vNodes);
BOOST_FOREACH(CNode* pnode, vNodes)
@ -928,7 +959,7 @@ void ThreadSocketHandler()
{
closesocket(hSocket);
}
else if (CNode::IsBanned(addr))
else if (CNode::IsBanned(addr) && !whitelisted)
{
LogPrintf("connection from %s dropped (banned)\n", addr.ToString());
closesocket(hSocket);
@ -938,6 +969,7 @@ void ThreadSocketHandler()
LogPrint("net", "accepted connection %s\n", addr.ToString());
CNode* pnode = new CNode(hSocket, addr, "", true);
pnode->AddRef();
pnode->fWhitelisted = whitelisted;
{
LOCK(cs_vNodes);
@ -1571,7 +1603,7 @@ void ThreadMessageHandler()
bool BindListenPort(const CService &addrBind, string& strError)
bool BindListenPort(const CService &addrBind, string& strError, bool fWhitelisted)
{
strError = "";
int nOne = 1;
@ -1652,9 +1684,9 @@ bool BindListenPort(const CService &addrBind, string& strError)
return false;
}
vhListenSocket.push_back(hListenSocket);
vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted));
if (addrBind.IsRoutable() && fDiscover)
if (addrBind.IsRoutable() && fDiscover && !fWhitelisted)
AddLocal(addrBind, LOCAL_BIND);
return true;
@ -1782,9 +1814,9 @@ public:
BOOST_FOREACH(CNode* pnode, vNodes)
if (pnode->hSocket != INVALID_SOCKET)
closesocket(pnode->hSocket);
BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket)
if (hListenSocket != INVALID_SOCKET)
if (closesocket(hListenSocket) == SOCKET_ERROR)
BOOST_FOREACH(ListenSocket& hListenSocket, vhListenSocket)
if (hListenSocket.socket != INVALID_SOCKET)
if (closesocket(hListenSocket.socket) == SOCKET_ERROR)
LogPrintf("closesocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError()));
// clean up some globals (to help leak detection)

View file

@ -58,7 +58,7 @@ CNode* FindNode(const CService& ip);
CNode* ConnectNode(CAddress addrConnect, const char *strDest = NULL);
void MapPort(bool fUseUPnP);
unsigned short GetListenPort();
bool BindListenPort(const CService &bindAddr, std::string& strError=REF(std::string()));
bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false);
void StartNode(boost::thread_group& threadGroup);
bool StopNode();
void SocketSendData(CNode *pnode);
@ -149,6 +149,7 @@ public:
uint64_t nSendBytes;
uint64_t nRecvBytes;
bool fSyncNode;
bool fWhitelisted;
double dPingTime;
double dPingWait;
std::string addrLocal;
@ -231,6 +232,7 @@ public:
// store the sanitized version in cleanSubVer. The original should be used when dealing with
// the network or wire types and the cleaned string used when displayed or logged.
std::string strSubVer, cleanSubVer;
bool fWhitelisted; // This peer can bypass DoS banning.
bool fOneShot;
bool fClient;
bool fInbound;
@ -254,6 +256,11 @@ protected:
static std::map<CNetAddr, int64_t> setBanned;
static CCriticalSection cs_setBanned;
// Whitelisted ranges. Any node connecting from these is automatically
// whitelisted (as well as those connecting to whitelisted binds).
static std::vector<CSubNet> vWhitelistedRange;
static CCriticalSection cs_vWhitelistedRange;
// Basic fuzz-testing
void Fuzz(int nChance); // modifies ssSend
@ -300,6 +307,7 @@ public:
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;
nVersion = 0;
strSubVer = "";
fWhitelisted = false;
fOneShot = false;
fClient = false; // set by version message
fInbound = fInboundIn;
@ -713,6 +721,9 @@ public:
static bool Ban(const CNetAddr &ip);
void copyStats(CNodeStats &stats);
static bool IsWhitelistedRange(const CNetAddr &ip);
static void AddWhitelistedRange(const CSubNet &subnet);
// Network stats
static void RecordBytesRecv(uint64_t bytes);
static void RecordBytesSent(uint64_t bytes);

View file

@ -134,8 +134,8 @@ Value getpeerinfo(const Array& params, bool fHelp)
if (fStateStats) {
obj.push_back(Pair("banscore", statestats.nMisbehavior));
}
if (stats.fSyncNode)
obj.push_back(Pair("syncnode", true));
obj.push_back(Pair("syncnode", stats.fSyncNode));
obj.push_back(Pair("whitelisted", stats.fWhitelisted));
ret.push_back(obj);
}