// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
# if defined(HAVE_CONFIG_H)
# include "config/bitcoin-config.h"
# endif
# include "net.h"
# include "addrman.h"
# include "chainparams.h"
# include "clientversion.h"
# include "consensus/consensus.h"
# include "crypto/common.h"
# include "crypto/sha256.h"
# include "hash.h"
# include "primitives/transaction.h"
# include "netbase.h"
# include "scheduler.h"
# include "ui_interface.h"
# include "utilstrencodings.h"
# ifdef WIN32
# include <string.h>
# else
# include <fcntl.h>
# endif
# ifdef USE_UPNP
# include <miniupnpc/miniupnpc.h>
# include <miniupnpc/miniwget.h>
# include <miniupnpc/upnpcommands.h>
# include <miniupnpc/upnperrors.h>
# endif
# include <boost/filesystem.hpp>
Split up util.cpp/h
Split up util.cpp/h into:
- string utilities (hex, base32, base64): no internal dependencies, no dependency on boost (apart from foreach)
- money utilities (parsesmoney, formatmoney)
- time utilities (gettime*, sleep, format date):
- and the rest (logging, argument parsing, config file parsing)
The latter is basically the environment and OS handling,
and is stripped of all utility functions, so we may want to
rename it to something else than util.cpp/h for clarity (Matt suggested
osinterface).
Breaks dependency of sha256.cpp on all the things pulled in by util.
9 years ago
# include <boost/thread.hpp>
# include <math.h>
// Dump addresses to peers.dat and banlist.dat every 15 minutes (900s)
# define DUMP_ADDRESSES_INTERVAL 900
// We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization.
# define FEELER_SLEEP_WINDOW 1
# if !defined(HAVE_MSG_NOSIGNAL) && !defined(MSG_NOSIGNAL)
# define MSG_NOSIGNAL 0
# endif
// Fix for ancient MinGW versions, that don't have defined these in ws2tcpip.h.
// Todo: Can be removed when our pull-tester is upgraded to a modern MinGW version.
# ifdef WIN32
# ifndef PROTECTION_LEVEL_UNRESTRICTED
# define PROTECTION_LEVEL_UNRESTRICTED 10
# endif
# ifndef IPV6_PROTECTION_LEVEL
# define IPV6_PROTECTION_LEVEL 23
# endif
# endif
const static std : : string NET_MESSAGE_COMMAND_OTHER = " *other* " ;
static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL ; // SHA256("netgroup")[0:8]
static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL ; // SHA256("localhostnonce")[0:8]
//
// Global state variables
//
bool fDiscover = true ;
bool fListen = true ;
bool fRelayTxes = true ;
CCriticalSection cs_mapLocalHost ;
std : : map < CNetAddr , LocalServiceInfo > mapLocalHost ;
static bool vfLimited [ NET_MAX ] = { } ;
std : : string strSubVersion ;
limitedmap < uint256 , int64_t > mapAlreadyAskedFor ( MAX_INV_SZ ) ;
// Signals for message handling
static CNodeSignals g_signals ;
CNodeSignals & GetNodeSignals ( ) { return g_signals ; }
void CConnman : : AddOneShot ( const std : : string & strDest )
{
LOCK ( cs_vOneShots ) ;
vOneShots . push_back ( strDest ) ;
}
unsigned short GetListenPort ( )
{
return ( unsigned short ) ( GetArg ( " -port " , Params ( ) . GetDefaultPort ( ) ) ) ;
}
// find 'best' local address for a particular peer
bool GetLocal ( CService & addr , const CNetAddr * paddrPeer )
{
if ( ! fListen )
return false ;
int nBestScore = - 1 ;
int nBestReachability = - 1 ;
{
LOCK ( cs_mapLocalHost ) ;
for ( std : : map < CNetAddr , LocalServiceInfo > : : iterator it = mapLocalHost . begin ( ) ; it ! = mapLocalHost . end ( ) ; it + + )
{
int nScore = ( * it ) . second . nScore ;
int nReachability = ( * it ) . first . GetReachabilityFrom ( paddrPeer ) ;
if ( nReachability > nBestReachability | | ( nReachability = = nBestReachability & & nScore > nBestScore ) )
{
addr = CService ( ( * it ) . first , ( * it ) . second . nPort ) ;
nBestReachability = nReachability ;
nBestScore = nScore ;
}
}
}
return nBestScore > = 0 ;
}
//! Convert the pnSeeds6 array into usable address objects.
static std : : vector < CAddress > convertSeed6 ( const std : : vector < SeedSpec6 > & vSeedsIn )
{
// It'll only connect to one or two seed nodes because once it connects,
// it'll get a pile of addresses with newer timestamps.
// Seed nodes are given a random 'last seen time' of between one and two
// weeks ago.
const int64_t nOneWeek = 7 * 24 * 60 * 60 ;
std : : vector < CAddress > vSeedsOut ;
vSeedsOut . reserve ( vSeedsIn . size ( ) ) ;
for ( std : : vector < SeedSpec6 > : : const_iterator i ( vSeedsIn . begin ( ) ) ; i ! = vSeedsIn . end ( ) ; + + i )
{
struct in6_addr ip ;
memcpy ( & ip , i - > addr , sizeof ( ip ) ) ;
CAddress addr ( CService ( ip , i - > port ) , NODE_NETWORK ) ;
addr . nTime = GetTime ( ) - GetRand ( nOneWeek ) - nOneWeek ;
vSeedsOut . push_back ( addr ) ;
}
return vSeedsOut ;
}
// get best local address for a particular peer as a CAddress
// Otherwise, return the unroutable 0.0.0.0 but filled in with
// the normal parameters, since the IP may be changed to a useful
// one by discovery.
CAddress GetLocalAddress ( const CNetAddr * paddrPeer , ServiceFlags nLocalServices )
{
CAddress ret ( CService ( CNetAddr ( ) , GetListenPort ( ) ) , NODE_NONE ) ;
CService addr ;
if ( GetLocal ( addr , paddrPeer ) )
{
ret = CAddress ( addr , nLocalServices ) ;
}
ret . nTime = GetAdjustedTime ( ) ;
return ret ;
}
int GetnScore ( const CService & addr )
{
LOCK ( cs_mapLocalHost ) ;
if ( mapLocalHost . count ( addr ) = = LOCAL_NONE )
return 0 ;
return mapLocalHost [ addr ] . nScore ;
}
// Is our peer's addrLocal potentially useful as an external IP source?
bool IsPeerAddrLocalGood ( CNode * pnode )
{
return fDiscover & & pnode - > addr . IsRoutable ( ) & & pnode - > addrLocal . IsRoutable ( ) & &
! IsLimited ( pnode - > addrLocal . GetNetwork ( ) ) ;
}
// pushes our own address to a peer
void AdvertiseLocal ( CNode * pnode )
{
if ( fListen & & pnode - > fSuccessfullyConnected )
{
CAddress addrLocal = GetLocalAddress ( & pnode - > addr , pnode - > GetLocalServices ( ) ) ;
// If discovery is enabled, sometimes give our peer the address it
// tells us that it sees us as in case it has a better idea of our
// address than we do.
if ( IsPeerAddrLocalGood ( pnode ) & & ( ! addrLocal . IsRoutable ( ) | |
GetRand ( ( GetnScore ( addrLocal ) > LOCAL_MANUAL ) ? 8 : 2 ) = = 0 ) )
{
addrLocal . SetIP ( pnode - > addrLocal ) ;
}
if ( addrLocal . IsRoutable ( ) )
{
LogPrint ( " net " , " AdvertiseLocal: advertising address %s \n " , addrLocal . ToString ( ) ) ;
FastRandomContext insecure_rand ;
pnode - > PushAddress ( addrLocal , insecure_rand ) ;
}
}
}
// learn a new local address
bool AddLocal ( const CService & addr , int nScore )
{
if ( ! addr . IsRoutable ( ) )
return false ;
if ( ! fDiscover & & nScore < LOCAL_MANUAL )
return false ;
if ( IsLimited ( addr ) )
return false ;
LogPrintf ( " AddLocal(%s,%i) \n " , addr . ToString ( ) , nScore ) ;
{
LOCK ( cs_mapLocalHost ) ;
bool fAlready = mapLocalHost . count ( addr ) > 0 ;
LocalServiceInfo & info = mapLocalHost [ addr ] ;
if ( ! fAlready | | nScore > = info . nScore ) {
info . nScore = nScore + ( fAlready ? 1 : 0 ) ;
info . nPort = addr . GetPort ( ) ;
}
}
return true ;
}
bool AddLocal ( const CNetAddr & addr , int nScore )
{
return AddLocal ( CService ( addr , GetListenPort ( ) ) , nScore ) ;
}
bool RemoveLocal ( const CService & addr )
{
LOCK ( cs_mapLocalHost ) ;
LogPrintf ( " RemoveLocal(%s) \n " , addr . ToString ( ) ) ;
mapLocalHost . erase ( addr ) ;
return true ;
}
/** Make a particular network entirely off-limits (no automatic connects to it) */
void SetLimited ( enum Network net , bool fLimited )
{
if ( net = = NET_UNROUTABLE )
return ;
LOCK ( cs_mapLocalHost ) ;
vfLimited [ net ] = fLimited ;
}
bool IsLimited ( enum Network net )
{
LOCK ( cs_mapLocalHost ) ;
return vfLimited [ net ] ;
}
bool IsLimited ( const CNetAddr & addr )
{
return IsLimited ( addr . GetNetwork ( ) ) ;
}
/** vote for a local address */
bool SeenLocal ( const CService & addr )
{
{
LOCK ( cs_mapLocalHost ) ;
if ( mapLocalHost . count ( addr ) = = 0 )
return false ;
mapLocalHost [ addr ] . nScore + + ;
}
return true ;
}
/** check whether a given address is potentially local */
bool IsLocal ( const CService & addr )
{
LOCK ( cs_mapLocalHost ) ;
return mapLocalHost . count ( addr ) > 0 ;
}
/** check whether a given network is one we can probably connect to */
bool IsReachable ( enum Network net )
{
LOCK ( cs_mapLocalHost ) ;
return ! vfLimited [ net ] ;
}
/** check whether a given address is in a network we can probably connect to */
bool IsReachable ( const CNetAddr & addr )
{
enum Network net = addr . GetNetwork ( ) ;
return IsReachable ( net ) ;
}
CNode * CConnman : : FindNode ( const CNetAddr & ip )
{
LOCK ( cs_vNodes ) ;
BOOST_FOREACH ( CNode * pnode , vNodes )
if ( ( CNetAddr ) pnode - > addr = = ip )
return ( pnode ) ;
return NULL ;
}
CNode * CConnman : : FindNode ( const CSubNet & subNet )
{
LOCK ( cs_vNodes ) ;
BOOST_FOREACH ( CNode * pnode , vNodes )
if ( subNet . Match ( ( CNetAddr ) pnode - > addr ) )
return ( pnode ) ;
return NULL ;
}
CNode * CConnman : : FindNode ( const std : : string & addrName )
{
LOCK ( cs_vNodes ) ;
BOOST_FOREACH ( CNode * pnode , vNodes )
if ( pnode - > addrName = = addrName )
return ( pnode ) ;
return NULL ;
}
CNode * CConnman : : FindNode ( const CService & addr )
{
LOCK ( cs_vNodes ) ;
BOOST_FOREACH ( CNode * pnode , vNodes )
if ( ( CService ) pnode - > addr = = addr )
return ( pnode ) ;
return NULL ;
}
bool CConnman : : CheckIncomingNonce ( uint64_t nonce )
{
LOCK ( cs_vNodes ) ;
BOOST_FOREACH ( CNode * pnode , vNodes ) {
if ( ! pnode - > fSuccessfullyConnected & & ! pnode - > fInbound & & pnode - > GetLocalNonce ( ) = = nonce )
return false ;
}
return true ;
}
CNode * CConnman : : ConnectNode ( CAddress addrConnect , const char * pszDest , bool fCountFailure )
{
if ( pszDest = = NULL ) {
if ( IsLocal ( addrConnect ) )
return NULL ;
// Look for an existing connection
CNode * pnode = FindNode ( ( CService ) addrConnect ) ;
if ( pnode )
{
pnode - > AddRef ( ) ;
return pnode ;
}
}
/// debug print
LogPrint ( " net " , " trying connection %s lastseen=%.1fhrs \n " ,
pszDest ? pszDest : addrConnect . ToString ( ) ,
pszDest ? 0.0 : ( double ) ( GetAdjustedTime ( ) - addrConnect . nTime ) / 3600.0 ) ;
// Connect
SOCKET hSocket ;
bool proxyConnectionFailed = false ;
if ( pszDest ? ConnectSocketByName ( addrConnect , hSocket , pszDest , Params ( ) . GetDefaultPort ( ) , nConnectTimeout , & proxyConnectionFailed ) :
ConnectSocket ( addrConnect , hSocket , nConnectTimeout , & proxyConnectionFailed ) )
{
if ( ! IsSelectableSocket ( hSocket ) ) {
LogPrintf ( " Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?) \n " ) ;
CloseSocket ( hSocket ) ;
return NULL ;
}
if ( pszDest & & addrConnect . IsValid ( ) ) {
// It is possible that we already have a connection to the IP/port pszDest resolved to.
// In that case, drop the connection that was just created, and return the existing CNode instead.
// Also store the name we used to connect in that CNode, so that future FindNode() calls to that
// name catch this early.
CNode * pnode = FindNode ( ( CService ) addrConnect ) ;
if ( pnode )
{
pnode - > AddRef ( ) ;
{
LOCK ( cs_vNodes ) ;
if ( pnode - > addrName . empty ( ) ) {
pnode - > addrName = std : : string ( pszDest ) ;
}
}
CloseSocket ( hSocket ) ;
return pnode ;
}
}
addrman . Attempt ( addrConnect , fCountFailure ) ;
// Add node
NodeId id = GetNewNodeId ( ) ;
uint64_t nonce = GetDeterministicRandomizer ( RANDOMIZER_ID_LOCALHOSTNONCE ) . Write ( id ) . Finalize ( ) ;
CNode * pnode = new CNode ( id , nLocalServices , GetBestHeight ( ) , hSocket , addrConnect , CalculateKeyedNetGroup ( addrConnect ) , nonce , pszDest ? pszDest : " " , false ) ;
pnode - > nServicesExpected = ServiceFlags ( addrConnect . nServices & nRelevantServices ) ;
pnode - > nTimeConnected = GetTime ( ) ;
pnode - > AddRef ( ) ;
GetNodeSignals ( ) . InitializeNode ( pnode , * this ) ;
{
LOCK ( cs_vNodes ) ;
vNodes . push_back ( pnode ) ;
}
return pnode ;
} else if ( ! proxyConnectionFailed ) {
// If connecting to the node failed, and failure is not caused by a problem connecting to
// the proxy, mark this as an attempt.
addrman . Attempt ( addrConnect , fCountFailure ) ;
}
return NULL ;
}
void CConnman : : DumpBanlist ( )
{
SweepBanned ( ) ; // clean unused entries (if bantime has expired)
if ( ! BannedSetIsDirty ( ) )
return ;
int64_t nStart = GetTimeMillis ( ) ;
CBanDB bandb ;
banmap_t banmap ;
SetBannedSetDirty ( false ) ;
GetBanned ( banmap ) ;
if ( ! bandb . Write ( banmap ) )
SetBannedSetDirty ( true ) ;
LogPrint ( " net " , " Flushed %d banned node ips/subnets to banlist.dat %dms \n " ,
banmap . size ( ) , GetTimeMillis ( ) - nStart ) ;
}
void CNode : : CloseSocketDisconnect ( )
{
fDisconnect = true ;
if ( hSocket ! = INVALID_SOCKET )
{
LogPrint ( " net " , " disconnecting peer=%d \n " , id ) ;
CloseSocket ( hSocket ) ;
}
// in case this fails, we'll empty the recv buffer when the CNode is deleted
TRY_LOCK ( cs_vRecvMsg , lockRecv ) ;
if ( lockRecv )
vRecvMsg . clear ( ) ;
}
void CConnman : : ClearBanned ( )
{
{
LOCK ( cs_setBanned ) ;
setBanned . clear ( ) ;
setBannedIsDirty = true ;
}
DumpBanlist ( ) ; //store banlist to disk
if ( clientInterface )
clientInterface - > BannedListChanged ( ) ;
}
bool CConnman : : IsBanned ( CNetAddr ip )
{
bool fResult = false ;
{
LOCK ( cs_setBanned ) ;
for ( banmap_t : : iterator it = setBanned . begin ( ) ; it ! = setBanned . end ( ) ; it + + )
{
CSubNet subNet = ( * it ) . first ;
CBanEntry banEntry = ( * it ) . second ;
if ( subNet . Match ( ip ) & & GetTime ( ) < banEntry . nBanUntil )
fResult = true ;
}
}
return fResult ;
}
bool CConnman : : IsBanned ( CSubNet subnet )
{
bool fResult = false ;
{
LOCK ( cs_setBanned ) ;
banmap_t : : iterator i = setBanned . find ( subnet ) ;
if ( i ! = setBanned . end ( ) )
{
CBanEntry banEntry = ( * i ) . second ;
if ( GetTime ( ) < banEntry . nBanUntil )
fResult = true ;
}
}
return fResult ;
}
void CConnman : : Ban ( const CNetAddr & addr , const BanReason & banReason , int64_t bantimeoffset , bool sinceUnixEpoch ) {
CSubNet subNet ( addr ) ;
Ban ( subNet , banReason , bantimeoffset , sinceUnixEpoch ) ;
}
void CConnman : : Ban ( const CSubNet & subNet , const BanReason & banReason , int64_t bantimeoffset , bool sinceUnixEpoch ) {
CBanEntry banEntry ( GetTime ( ) ) ;
banEntry . banReason = banReason ;
if ( bantimeoffset < = 0 )
{
bantimeoffset = GetArg ( " -bantime " , DEFAULT_MISBEHAVING_BANTIME ) ;
sinceUnixEpoch = false ;
}
banEntry . nBanUntil = ( sinceUnixEpoch ? 0 : GetTime ( ) ) + bantimeoffset ;
{
LOCK ( cs_setBanned ) ;
if ( setBanned [ subNet ] . nBanUntil < banEntry . nBanUntil ) {
setBanned [ subNet ] = banEntry ;
setBannedIsDirty = true ;
}
else
return ;
}
if ( clientInterface )
clientInterface - > BannedListChanged ( ) ;
{
LOCK ( cs_vNodes ) ;
BOOST_FOREACH ( CNode * pnode , vNodes ) {
if ( subNet . Match ( ( CNetAddr ) pnode - > addr ) )
pnode - > fDisconnect = true ;
}
}
if ( banReason = = BanReasonManuallyAdded )
DumpBanlist ( ) ; //store banlist to disk immediately if user requested ban
}
bool CConnman : : Unban ( const CNetAddr & addr ) {
CSubNet subNet ( addr ) ;
return Unban ( subNet ) ;
}
bool CConnman : : Unban ( const CSubNet & subNet ) {
{
LOCK ( cs_setBanned ) ;
if ( ! setBanned . erase ( subNet ) )
return false ;
setBannedIsDirty = true ;
}
if ( clientInterface )
clientInterface - > BannedListChanged ( ) ;
DumpBanlist ( ) ; //store banlist to disk immediately
return true ;
}
void CConnman : : GetBanned ( banmap_t & banMap )
{
LOCK ( cs_setBanned ) ;
banMap = setBanned ; //create a thread safe copy
}
void CConnman : : SetBanned ( const banmap_t & banMap )
{
LOCK ( cs_setBanned ) ;
setBanned = banMap ;
setBannedIsDirty = true ;
}
void CConnman : : SweepBanned ( )
{
int64_t now = GetTime ( ) ;
LOCK ( cs_setBanned ) ;
banmap_t : : iterator it = setBanned . begin ( ) ;
while ( it ! = setBanned . end ( ) )
{
CSubNet subNet = ( * it ) . first ;
CBanEntry banEntry = ( * it ) . second ;
if ( now > banEntry . nBanUntil )
{
setBanned . erase ( it + + ) ;
setBannedIsDirty = true ;
LogPrint ( " net " , " %s: Removed banned node ip/subnet from banlist.dat: %s \n " , __func__ , subNet . ToString ( ) ) ;
}
else
+ + it ;
}
}
bool CConnman : : BannedSetIsDirty ( )
{
LOCK ( cs_setBanned ) ;
return setBannedIsDirty ;
}
void CConnman : : SetBannedSetDirty ( bool dirty )
{
LOCK ( cs_setBanned ) ; //reuse setBanned lock for the isDirty flag
setBannedIsDirty = dirty ;
}
bool CConnman : : IsWhitelistedRange ( const CNetAddr & addr ) {
LOCK ( cs_vWhitelistedRange ) ;
BOOST_FOREACH ( const CSubNet & subnet , vWhitelistedRange ) {
if ( subnet . Match ( addr ) )
return true ;
}
return false ;
}
void CConnman : : AddWhitelistedRange ( const CSubNet & subnet ) {
LOCK ( cs_vWhitelistedRange ) ;
vWhitelistedRange . push_back ( subnet ) ;
}
# undef X
# define X(name) stats.name = name
void CNode : : copyStats ( CNodeStats & stats )
{
stats . nodeid = this - > GetId ( ) ;
X ( nServices ) ;
X ( addr ) ;
X ( fRelayTxes ) ;
X ( nLastSend ) ;
X ( nLastRecv ) ;
X ( nTimeConnected ) ;
X ( nTimeOffset ) ;
X ( addrName ) ;
X ( nVersion ) ;
X ( cleanSubVer ) ;
X ( fInbound ) ;
X ( nStartingHeight ) ;
X ( nSendBytes ) ;
X ( mapSendBytesPerMsgCmd ) ;
X ( nRecvBytes ) ;
X ( mapRecvBytesPerMsgCmd ) ;
X ( fWhitelisted ) ;
// It is common for nodes with good ping times to suddenly become lagged,
// due to a new block arriving or other large transfer.
// Merely reporting pingtime might fool the caller into thinking the node was still responsive,
// since pingtime does not update until the ping is complete, which might take a while.
// So, if a ping is taking an unusually long time in flight,
// the caller can immediately detect that this is happening.
int64_t nPingUsecWait = 0 ;
if ( ( 0 ! = nPingNonceSent ) & & ( 0 ! = nPingUsecStart ) ) {
nPingUsecWait = GetTimeMicros ( ) - nPingUsecStart ;
}
// Raw ping time is in microseconds, but show it to user as whole seconds (Bitcoin users should be well used to small numbers with many decimal places by now :)
stats . dPingTime = ( ( ( double ) nPingUsecTime ) / 1e6 ) ;
stats . dMinPing = ( ( ( double ) nMinPingUsecTime ) / 1e6 ) ;
stats . dPingWait = ( ( ( double ) nPingUsecWait ) / 1e6 ) ;