From 39857190dee3ed296112cfcfd79b0a375143b6c6 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 12 Feb 2012 13:45:24 +0100 Subject: [PATCH] Support for multiple local addresses --- src/irc.cpp | 13 ++- src/irc.h | 1 - src/main.cpp | 23 +++-- src/net.cpp | 189 +++++++++++++++++++++++++++-------------- src/net.h | 17 +++- src/netbase.cpp | 23 +++++ src/netbase.h | 1 + src/test/DoS_tests.cpp | 8 +- 8 files changed, 187 insertions(+), 88 deletions(-) diff --git a/src/irc.cpp b/src/irc.cpp index 237497055..f20152495 100644 --- a/src/irc.cpp +++ b/src/irc.cpp @@ -12,7 +12,6 @@ using namespace std; using namespace boost; int nGotIRCAddresses = 0; -bool fGotExternalIP = false; void ThreadIRCSeed2(void* parg); @@ -216,7 +215,6 @@ void ThreadIRCSeed2(void* parg) printf("ThreadIRCSeed started\n"); int nErrorWait = 10; int nRetryWait = 10; - bool fNameInUse = false; while (!fShutdown) { @@ -248,9 +246,10 @@ void ThreadIRCSeed2(void* parg) return; } + CNetAddr addrLocal; string strMyName; - if (addrLocalHost.IsRoutable() && !fUseProxy && !fNameInUse) - strMyName = EncodeAddress(addrLocalHost); + if (GetLocal(addrLocal, &addrConnect)) + strMyName = EncodeAddress(GetLocalAddress(&addrConnect)); else strMyName = strprintf("x%u", GetRand(1000000000)); @@ -265,7 +264,6 @@ void ThreadIRCSeed2(void* parg) if (nRet == 2) { printf("IRC name already in use\n"); - fNameInUse = true; Wait(10); continue; } @@ -285,9 +283,8 @@ void ThreadIRCSeed2(void* parg) if (!fUseProxy && addrFromIRC.IsRoutable()) { // IRC lets you to re-nick - fGotExternalIP = true; - addrLocalHost.SetIP(addrFromIRC); - strMyName = EncodeAddress(addrLocalHost); + AddLocal(addrFromIRC, LOCAL_IRC); + strMyName = EncodeAddress(GetLocalAddress(&addrConnect)); Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str()); } } diff --git a/src/irc.h b/src/irc.h index 08d62b83d..a6073199e 100644 --- a/src/irc.h +++ b/src/irc.h @@ -8,6 +8,5 @@ void ThreadIRCSeed(void* parg); extern int nGotIRCAddresses; -extern bool fGotExternalIP; #endif diff --git a/src/main.cpp b/src/main.cpp index 6a3cbe710..b5d8f8f27 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2232,6 +2232,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!vRecv.empty()) vRecv >> pfrom->nStartingHeight; + if (pfrom->fInbound && addrMe.IsRoutable()) + { + pfrom->addrLocal = addrMe; + SeenLocal(addrMe); + } + // Disconnect if we connected to ourself if (nNonce == nLocalHostNonce && nNonce > 1) { @@ -2255,12 +2261,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!pfrom->fInbound) { // Advertise our address - if (!fNoListen && !fUseProxy && addrLocalHost.IsRoutable() && - !IsInitialBlockDownload()) + if (!fNoListen && !fUseProxy && !IsInitialBlockDownload()) { - CAddress addr(addrLocalHost); - addr.nTime = GetAdjustedTime(); - pfrom->PushAddress(addr); + CAddress addr = GetLocalAddress(&pfrom->addr); + if (addr.IsRoutable()) + pfrom->PushAddress(addr); } // Get recent addresses @@ -2889,11 +2894,11 @@ bool SendMessages(CNode* pto, bool fSendTrickle) pnode->setAddrKnown.clear(); // Rebroadcast our address - if (!fNoListen && !fUseProxy && addrLocalHost.IsRoutable()) + if (!fNoListen && !fUseProxy) { - CAddress addr(addrLocalHost); - addr.nTime = GetAdjustedTime(); - pnode->PushAddress(addr); + CAddress addr = GetLocalAddress(&pnode->addr); + if (addr.IsRoutable()) + pnode->PushAddress(addr); } } } diff --git a/src/net.cpp b/src/net.cpp index 048320696..0a78bc445 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -45,7 +45,8 @@ bool OpenNetworkConnection(const CAddress& addrConnect, const char *strDest = NU bool fClient = false; static bool fUseUPnP = false; uint64 nLocalServices = (fClient ? 0 : NODE_NETWORK); -CAddress addrLocalHost(CService("0.0.0.0", 0), nLocalServices); +CCriticalSection cs_mapLocalHost; +map mapLocalHost; static CNode* pnodeLocalHost = NULL; uint64 nLocalHostNonce = 0; array vnThreadsRunning; @@ -92,7 +93,45 @@ void CNode::PushGetBlocks(CBlockIndex* pindexBegin, uint256 hashEnd) PushMessage("getblocks", CBlockLocator(pindexBegin), hashEnd); } +// find 'best' local address for a particular peer +bool GetLocal(CNetAddr& addr, const CNetAddr *paddrPeer) +{ + if (fUseProxy || mapArgs.count("-connect") || fNoListen) + return false; + int nBestCount = -1; + int nBestReachability = -1; + { + LOCK(cs_mapLocalHost); + for (map::iterator it = mapLocalHost.begin(); it != mapLocalHost.end(); it++) + { + int nCount = (*it).second; + int nReachability = (*it).first.GetReachabilityFrom(paddrPeer); + if (nReachability > nBestReachability || (nReachability == nBestReachability && nCount > nBestCount)) + { + addr = (*it).first; + nBestReachability = nReachability; + nBestCount = nCount; + } + } + } + return nBestCount >= 0; +} + +// get best local address for a particular peer as a CAddress +CAddress GetLocalAddress(const CNetAddr *paddrPeer) +{ + CAddress ret(CService("0.0.0.0",0),0); + CNetAddr addr; + if (GetLocal(addr, paddrPeer)) + { + ret.SetIP(addr); + ret.SetPort(GetListenPort()); + ret.nServices = nLocalServices; + ret.nTime = GetAdjustedTime(); + } + return ret; +} bool RecvLine(SOCKET hSocket, string& strLine) { @@ -145,6 +184,64 @@ bool RecvLine(SOCKET hSocket, string& strLine) } } +// used when scores of local addresses may have changed +// pushes better local address to peers +void static AdvertizeLocal() +{ + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + { + if (pnode->fSuccessfullyConnected) + { + CAddress addrLocal = GetLocalAddress(&pnode->addr); + if (addrLocal.IsRoutable() && (CNetAddr)addrLocal != (CNetAddr)pnode->addrLocal) + { + pnode->PushAddress(addrLocal); + pnode->addrLocal = addrLocal; + } + } + } +} + +// learn a new local address +bool AddLocal(const CNetAddr& addr, int nScore) +{ + if (!addr.IsRoutable()) + return false; + + printf("AddLocal(%s,%i)\n", addr.ToString().c_str(), nScore); + + { + LOCK(cs_mapLocalHost); + mapLocalHost[addr] = std::max(nScore, mapLocalHost[addr]) + (mapLocalHost.count(addr) ? 1 : 0); + } + + AdvertizeLocal(); + + return true; +} + +// vote for a local address +bool SeenLocal(const CNetAddr& addr) +{ + { + LOCK(cs_mapLocalHost); + if (mapLocalHost.count(addr) == 0) + return false; + mapLocalHost[addr]++; + } + + AdvertizeLocal(); + + return true; +} + +// check whether a given address is potentially local +bool IsLocal(const CNetAddr& addr) +{ + LOCK(cs_mapLocalHost); + return mapLocalHost.count(addr) > 0; +} bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const char* pszKeyword, CNetAddr& ipRet) @@ -258,33 +355,11 @@ bool GetMyExternalIP(CNetAddr& ipRet) void ThreadGetMyExternalIP(void* parg) { - // Wait for IRC to get it first - if (GetBoolArg("-irc", false)) - { - for (int i = 0; i < 2 * 60; i++) - { - Sleep(1000); - if (fGotExternalIP || fShutdown) - return; - } - } - - // Fallback in case IRC fails to get it + CNetAddr addrLocalHost; if (GetMyExternalIP(addrLocalHost)) { printf("GetMyExternalIP() returned %s\n", addrLocalHost.ToStringIP().c_str()); - if (addrLocalHost.IsRoutable()) - { - // If we already connected to a few before we had our IP, go back and addr them. - // setAddrKnown automatically filters any duplicate sends. - CAddress addr(addrLocalHost); - addr.nTime = GetAdjustedTime(); - { - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - pnode->PushAddress(addr); - } - } + AddLocal(addrLocalHost, LOCAL_HTTP); } } @@ -337,7 +412,7 @@ CNode* FindNode(const CService& addr) CNode* ConnectNode(CAddress addrConnect, const char *pszDest, int64 nTimeout) { if (pszDest == NULL) { - if ((CNetAddr)addrConnect == (CNetAddr)addrLocalHost) + if (IsLocal(addrConnect)) return NULL; // Look for an existing connection @@ -426,7 +501,7 @@ void CNode::PushVersion() /// when NTP implemented, change to just nTime = GetAdjustedTime() int64 nTime = (fInbound ? GetAdjustedTime() : GetTime()); CAddress addrYou = (fUseProxy ? CAddress(CService("0.0.0.0",0)) : addr); - CAddress addrMe = (fUseProxy || !addrLocalHost.IsRoutable() ? CAddress(CService("0.0.0.0",0)) : addrLocalHost); + CAddress addrMe = GetLocalAddress(&addr); RAND_bytes((unsigned char*)&nLocalHostNonce, sizeof(nLocalHostNonce)); PushMessage("version", PROTOCOL_VERSION, nLocalServices, nTime, addrYou, addrMe, nLocalHostNonce, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector()), nBestHeight); @@ -898,24 +973,19 @@ void ThreadMapPort2(void* parg) r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); if (r == 1) { - if (!addrLocalHost.IsRoutable()) + char externalIPAddress[40]; + r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); + if(r != UPNPCOMMAND_SUCCESS) + printf("UPnP: GetExternalIPAddress() returned %d\n", r); + else { - char externalIPAddress[40]; - r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); - if(r != UPNPCOMMAND_SUCCESS) - printf("UPnP: GetExternalIPAddress() returned %d\n", r); - else + if(externalIPAddress[0]) { - if(externalIPAddress[0]) - { - printf("UPnP: ExternalIPAddress = %s\n", externalIPAddress); - CAddress addrExternalFromUPnP(CService(externalIPAddress, 0), nLocalServices); - if (addrExternalFromUPnP.IsRoutable()) - addrLocalHost = addrExternalFromUPnP; - } - else - printf("UPnP: GetExternalIPAddress failed.\n"); + printf("UPnP: ExternalIPAddress = %s\n", externalIPAddress); + AddLocal(CNetAddr(externalIPAddress), LOCAL_UPNP); } + else + printf("UPnP: GetExternalIPAddress failed.\n"); } string strDesc = "Bitcoin " + FormatFullVersion(); @@ -1318,7 +1388,7 @@ void ThreadOpenConnections2(void* parg) CAddress addr = addrman.Select(10 + min(nOutbound,8)*10); // if we selected an invalid address, restart - if (!addr.IsIPv4() || !addr.IsValid() || setConnected.count(addr.GetGroup()) || addr == addrLocalHost) + if (!addr.IsIPv4() || !addr.IsValid() || setConnected.count(addr.GetGroup()) || IsLocal(addr)) break; nTries++; @@ -1436,8 +1506,8 @@ bool OpenNetworkConnection(const CAddress& addrConnect, const char *strDest, boo if (fShutdown) return false; if (!strDest) - if ((CNetAddr)addrConnect == (CNetAddr)addrLocalHost || !addrConnect.IsIPv4() || - FindNode((CNetAddr)addrConnect) || CNode::IsBanned(addrConnect) || + if (IsLocal(addrConnect) || + FindNode((CNetAddr)addrConnect) || CNode::IsBanned(addrConnect) || FindNode(addrConnect.ToStringIPPort().c_str())) return false; if (strDest && FindNode(strDest)) @@ -1550,7 +1620,6 @@ bool BindListenPort(string& strError) { strError = ""; int nOne = 1; - addrLocalHost.SetPort(GetListenPort()); #ifdef WIN32 // Initialize Windows Sockets @@ -1649,11 +1718,7 @@ void StartNode(void* parg) { BOOST_FOREACH (const CNetAddr &addr, vaddr) { - if (!addr.IsLocal()) - { - addrLocalHost.SetIP(addr); - break; - } + AddLocal(addr, LOCAL_IF); } } } @@ -1676,32 +1741,26 @@ void StartNode(void* parg) printf("ipv4 %s: %s\n", ifa->ifa_name, pszIP); // Take the first IP that isn't loopback 127.x.x.x - CAddress addr(CService(s4->sin_addr, GetListenPort()), nLocalServices); - if (addr.IsValid() && !addr.IsLocal()) - { - addrLocalHost = addr; - break; - } + CNetAddr addr(s4->sin_addr); + AddLocal(addr, LOCAL_IF); } else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6* s6 = (struct sockaddr_in6*)(ifa->ifa_addr); if (inet_ntop(ifa->ifa_addr->sa_family, (void*)&(s6->sin6_addr), pszIP, sizeof(pszIP)) != NULL) printf("ipv6 %s: %s\n", ifa->ifa_name, pszIP); + +#ifdef USE_IPV6 + CNetAddr addr(s6->sin6_addr); + AddLocal(addr, LOCAL_IF); +#endif } } freeifaddrs(myaddrs); } #endif - printf("addrLocalHost = %s\n", addrLocalHost.ToString().c_str()); - if (fUseProxy || mapArgs.count("-connect") || fNoListen) - { - // Proxies can't take incoming connections - addrLocalHost.SetIP(CNetAddr("0.0.0.0")); - printf("addrLocalHost = %s\n", addrLocalHost.ToString().c_str()); - } - else + if (!fUseProxy && !mapArgs.count("-connect") && !fNoListen) { CreateThread(ThreadGetMyExternalIP, NULL); } diff --git a/src/net.h b/src/net.h index d48512a5d..239971e2c 100644 --- a/src/net.h +++ b/src/net.h @@ -42,6 +42,21 @@ bool BindListenPort(std::string& strError=REF(std::string())); void StartNode(void* parg); bool StopNode(); +enum +{ + LOCAL_NONE, + LOCAL_IF, + LOCAL_UPNP, + LOCAL_IRC, + LOCAL_HTTP, +}; + +bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE); +bool SeenLocal(const CNetAddr& addr); +bool IsLocal(const CNetAddr& addr); +bool GetLocal(CNetAddr &addr, const CNetAddr *paddrPeer = NULL); +CAddress GetLocalAddress(const CNetAddr *paddrPeer = NULL); + enum { MSG_TX = 1, @@ -85,7 +100,6 @@ enum threadId extern bool fClient; extern uint64 nLocalServices; -extern CAddress addrLocalHost; extern uint64 nLocalHostNonce; extern boost::array vnThreadsRunning; extern CAddrMan addrman; @@ -121,6 +135,7 @@ public: unsigned int nMessageStart; CAddress addr; std::string addrName; + CNetAddr addrLocal; int nVersion; std::string strSubVer; bool fOneShot; diff --git a/src/netbase.cpp b/src/netbase.cpp index a9cc0cf4d..4fe3fb61d 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -772,6 +772,29 @@ void CNetAddr::print() const printf("CNetAddr(%s)\n", ToString().c_str()); } +// for IPv6 partners: for unknown/Teredo partners: for IPv4 partners: +// 0 - unroutable // 0 - unroutable // 0 - unroutable +// 1 - teredo // 1 - teredo // 1 - ipv4 +// 2 - tunneled ipv6 // 2 - tunneled ipv6 +// 3 - ipv4 // 3 - ipv6 +// 4 - ipv6 // 4 - ipv4 +int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const +{ + if (!IsValid() || !IsRoutable()) + return 0; + if (paddrPartner && paddrPartner->IsIPv4()) + return IsIPv4() ? 1 : 0; + if (IsRFC4380()) + return 1; + if (IsRFC3964() || IsRFC6052()) + return 2; + bool fRealIPv6 = paddrPartner && !paddrPartner->IsRFC4380() && paddrPartner->IsValid() && paddrPartner->IsRoutable(); + if (fRealIPv6) + return IsIPv4() ? 3 : 4; + else + return IsIPv4() ? 4 : 3; +} + void CService::Init() { port = 0; diff --git a/src/netbase.h b/src/netbase.h index 3d2956906..e5c466e4f 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -51,6 +51,7 @@ class CNetAddr int64 GetHash() const; bool GetInAddr(struct in_addr* pipv4Addr) const; std::vector GetGroup() const; + int GetReachabilityFrom(const CNetAddr *paddrPartner = NULL) const; void print() const; #ifdef USE_IPV6 diff --git a/src/test/DoS_tests.cpp b/src/test/DoS_tests.cpp index e5a8b4f68..04e2a95d7 100644 --- a/src/test/DoS_tests.cpp +++ b/src/test/DoS_tests.cpp @@ -31,13 +31,13 @@ BOOST_AUTO_TEST_CASE(DoS_banning) { CNode::ClearBanned(); CAddress addr1(ip(0xa0b0c001)); - CNode dummyNode1(INVALID_SOCKET, addr1, true); + CNode dummyNode1(INVALID_SOCKET, addr1, "", true); dummyNode1.Misbehaving(100); // Should get banned BOOST_CHECK(CNode::IsBanned(addr1)); BOOST_CHECK(!CNode::IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different ip, not banned CAddress addr2(ip(0xa0b0c002)); - CNode dummyNode2(INVALID_SOCKET, addr2, true); + CNode dummyNode2(INVALID_SOCKET, addr2, "", true); dummyNode2.Misbehaving(50); BOOST_CHECK(!CNode::IsBanned(addr2)); // 2 not banned yet... BOOST_CHECK(CNode::IsBanned(addr1)); // ... but 1 still should be @@ -50,7 +50,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) CNode::ClearBanned(); mapArgs["-banscore"] = "111"; // because 11 is my favorite number CAddress addr1(ip(0xa0b0c001)); - CNode dummyNode1(INVALID_SOCKET, addr1, true); + CNode dummyNode1(INVALID_SOCKET, addr1, "", true); dummyNode1.Misbehaving(100); BOOST_CHECK(!CNode::IsBanned(addr1)); dummyNode1.Misbehaving(10); @@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) SetMockTime(nStartTime); // Overrides future calls to GetTime() CAddress addr(ip(0xa0b0c001)); - CNode dummyNode(INVALID_SOCKET, addr, true); + CNode dummyNode(INVALID_SOCKET, addr, "", true); dummyNode.Misbehaving(100); BOOST_CHECK(CNode::IsBanned(addr));