From a7e3c2814c8e49197889a4679461be42254e5c51 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Tue, 30 May 2017 11:59:42 +0200 Subject: [PATCH 1/2] rpc: Add listen address to incoming connections in `getpeerinfo` This adds the listening address on which incoming connections were received to the CNode and CNodeStats structures. The address is reported in `getpeerinfo`. This can be useful for distinguishing connections received on different listening ports (e.g. when using a different listening port for Tor hidden service connections) or different networks. --- src/net.cpp | 32 +++++++++++++++++++++++++++----- src/net.h | 10 +++++++++- src/rpc/net.cpp | 5 ++++- src/test/DoS_tests.cpp | 8 ++++---- src/test/net_tests.cpp | 4 ++-- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 378ac99d6..14ac5618e 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -340,6 +340,22 @@ bool CConnman::CheckIncomingNonce(uint64_t nonce) return true; } +/** Get the bind address for a socket as CAddress */ +static CAddress GetBindAddress(SOCKET sock) +{ + CAddress addr_bind; + struct sockaddr_storage sockaddr_bind; + socklen_t sockaddr_bind_len = sizeof(sockaddr_bind); + if (sock != INVALID_SOCKET) { + if (!getsockname(sock, (struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) { + addr_bind.SetSockAddr((const struct sockaddr*)&sockaddr_bind); + } else { + LogPrint(BCLog::NET, "Warning: getsockname failed\n"); + } + } + return addr_bind; +} + CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure) { if (pszDest == NULL) { @@ -393,7 +409,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo // 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); + CAddress addr_bind = GetBindAddress(hSocket); + CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false); pnode->nServicesExpected = ServiceFlags(addrConnect.nServices & nRelevantServices); pnode->AddRef(); @@ -635,6 +652,7 @@ void CNode::copyStats(CNodeStats &stats) stats.nodeid = this->GetId(); X(nServices); X(addr); + X(addrBind); { LOCK(cs_filter); X(fRelayTxes); @@ -1036,9 +1054,11 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { int nInbound = 0; int nMaxInbound = nMaxConnections - (nMaxOutbound + nMaxFeeler); - if (hSocket != INVALID_SOCKET) - if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) + if (hSocket != INVALID_SOCKET) { + if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { LogPrintf("Warning: Unknown socket family\n"); + } + } bool whitelisted = hListenSocket.whitelisted || IsWhitelistedRange(addr); { @@ -1092,8 +1112,9 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); + CAddress addr_bind = GetBindAddress(hSocket); - CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, "", true); + CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true); pnode->AddRef(); pnode->fWhitelisted = whitelisted; GetNodeSignals().InitializeNode(pnode, *this); @@ -2639,9 +2660,10 @@ int CConnman::GetBestHeight() const unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } unsigned int CConnman::GetSendBufferSize() const{ return nSendBufferMaxSize; } -CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const std::string& addrNameIn, bool fInboundIn) : +CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string& addrNameIn, bool fInboundIn) : nTimeConnected(GetSystemTimeInSeconds()), addr(addrIn), + addrBind(addrBindIn), fInbound(fInboundIn), nKeyedNetGroup(nKeyedNetGroupIn), addrKnown(5000, 0.001), diff --git a/src/net.h b/src/net.h index 00d04e991..b89c13a90 100644 --- a/src/net.h +++ b/src/net.h @@ -504,8 +504,12 @@ public: double dPingTime; double dPingWait; double dMinPing; + // Our address, as reported by the peer std::string addrLocal; + // Address of this peer CAddress addr; + // Bind address of our side of the connection + CAddress addrBind; }; @@ -586,7 +590,10 @@ public: std::atomic nLastRecv; const int64_t nTimeConnected; std::atomic nTimeOffset; + // Address of this peer const CAddress addr; + // Bind address of our side of the connection + const CAddress addrBind; std::atomic nVersion; // strSubVer is whatever byte array we read from the wire. However, this field is intended // to be printed out, displayed to humans in various forms and so on. So we sanitize it and @@ -676,7 +683,7 @@ public: CAmount lastSentFeeFilter; int64_t nextSendTimeFeeFilter; - CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const std::string &addrNameIn = "", bool fInboundIn = false); + CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false); ~CNode(); private: @@ -695,6 +702,7 @@ private: mutable CCriticalSection cs_addrName; std::string addrName; + // Our address, as reported by the peer CService addrLocal; mutable CCriticalSection cs_addrLocal; public: diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index cde5ae723..10bf99eb3 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -76,7 +76,8 @@ UniValue getpeerinfo(const JSONRPCRequest& request) " {\n" " \"id\": n, (numeric) Peer index\n" " \"addr\":\"host:port\", (string) The ip address and port of the peer\n" - " \"addrlocal\":\"ip:port\", (string) local address\n" + " \"addrbind\":\"ip:port\", (string) Bind address of the connection to the peer\n" + " \"addrlocal\":\"ip:port\", (string) Local address as reported by the peer\n" " \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services offered\n" " \"relaytxes\":true|false, (boolean) Whether peer has asked us to relay transactions to it\n" " \"lastsend\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last send\n" @@ -133,6 +134,8 @@ UniValue getpeerinfo(const JSONRPCRequest& request) obj.push_back(Pair("addr", stats.addrName)); if (!(stats.addrLocal.empty())) obj.push_back(Pair("addrlocal", stats.addrLocal)); + if (stats.addrBind.IsValid()) + obj.push_back(Pair("addrbind", stats.addrBind.ToString())); obj.push_back(Pair("services", strprintf("%016x", stats.nServices))); obj.push_back(Pair("relaytxes", stats.fRelayTxes)); obj.push_back(Pair("lastsend", stats.nLastSend)); diff --git a/src/test/DoS_tests.cpp b/src/test/DoS_tests.cpp index c62e6ae83..e6b45a3b5 100644 --- a/src/test/DoS_tests.cpp +++ b/src/test/DoS_tests.cpp @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE(DoS_banning) connman->ClearBanned(); CAddress addr1(ip(0xa0b0c001), NODE_NONE); - CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 0, 0, "", true); + CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 0, 0, CAddress(), "", true); dummyNode1.SetSendVersion(PROTOCOL_VERSION); GetNodeSignals().InitializeNode(&dummyNode1, *connman); dummyNode1.nVersion = 1; @@ -62,7 +62,7 @@ BOOST_AUTO_TEST_CASE(DoS_banning) BOOST_CHECK(!connman->IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different IP, not banned CAddress addr2(ip(0xa0b0c002), NODE_NONE); - CNode dummyNode2(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr2, 1, 1, "", true); + CNode dummyNode2(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr2, 1, 1, CAddress(), "", true); dummyNode2.SetSendVersion(PROTOCOL_VERSION); GetNodeSignals().InitializeNode(&dummyNode2, *connman); dummyNode2.nVersion = 1; @@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) connman->ClearBanned(); ForceSetArg("-banscore", "111"); // because 11 is my favorite number CAddress addr1(ip(0xa0b0c001), NODE_NONE); - CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 3, 1, "", true); + CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 3, 1, CAddress(), "", true); dummyNode1.SetSendVersion(PROTOCOL_VERSION); GetNodeSignals().InitializeNode(&dummyNode1, *connman); dummyNode1.nVersion = 1; @@ -109,7 +109,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) SetMockTime(nStartTime); // Overrides future calls to GetTime() CAddress addr(ip(0xa0b0c001), NODE_NONE); - CNode dummyNode(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr, 4, 4, "", true); + CNode dummyNode(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr, 4, 4, CAddress(), "", true); dummyNode.SetSendVersion(PROTOCOL_VERSION); GetNodeSignals().InitializeNode(&dummyNode, *connman); dummyNode.nVersion = 1; diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 0c7f3e5e2..66354699b 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -175,12 +175,12 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) bool fInboundIn = false; // Test that fFeeler is false by default. - std::unique_ptr pnode1(new CNode(id++, NODE_NETWORK, height, hSocket, addr, 0, 0, pszDest, fInboundIn)); + std::unique_ptr pnode1(new CNode(id++, NODE_NETWORK, height, hSocket, addr, 0, 0, CAddress(), pszDest, fInboundIn)); BOOST_CHECK(pnode1->fInbound == false); BOOST_CHECK(pnode1->fFeeler == false); fInboundIn = true; - std::unique_ptr pnode2(new CNode(id++, NODE_NETWORK, height, hSocket, addr, 1, 1, pszDest, fInboundIn)); + std::unique_ptr pnode2(new CNode(id++, NODE_NETWORK, height, hSocket, addr, 1, 1, CAddress(), pszDest, fInboundIn)); BOOST_CHECK(pnode2->fInbound == true); BOOST_CHECK(pnode2->fFeeler == false); } From 3457331386f2e38fda69cd3cff93a2e13984df57 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Mon, 5 Jun 2017 13:57:13 +0200 Subject: [PATCH 2/2] test: Add test for `getpeerinfo` `bindaddr` field --- test/functional/net.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/functional/net.py b/test/functional/net.py index fb4606444..3ba3764cf 100755 --- a/test/functional/net.py +++ b/test/functional/net.py @@ -29,6 +29,7 @@ class NetTest(BitcoinTestFramework): self._test_getnettotals() self._test_getnetworkinginfo() self._test_getaddednodeinfo() + self._test_getpeerinfo() def _test_connection_count(self): # connect_nodes_bi connects each node to the other @@ -88,6 +89,12 @@ class NetTest(BitcoinTestFramework): assert_raises_jsonrpc(-24, "Node has not been added", self.nodes[0].getaddednodeinfo, '1.1.1.1') + def _test_getpeerinfo(self): + peer_info = [x.getpeerinfo() for x in self.nodes] + # check both sides of bidirectional connection between nodes + # the address bound to on one side will be the source address for the other node + assert_equal(peer_info[0][0]['addrbind'], peer_info[1][0]['addr']) + assert_equal(peer_info[1][0]['addrbind'], peer_info[0][0]['addr']) if __name__ == '__main__': NetTest().main()