diff --git a/src/chainparams.h b/src/chainparams.h --- a/src/chainparams.h +++ b/src/chainparams.h @@ -15,11 +15,11 @@ #include struct CDNSSeedData { - std::string name, host; + std::string host; bool supportsServiceBitsFiltering; - CDNSSeedData(const std::string &strName, const std::string &strHost, - bool supportsServiceBitsFilteringIn = false) - : name(strName), host(strHost), + CDNSSeedData(const std::string &strHost, + bool supportsServiceBitsFilteringIn) + : host(strHost), supportsServiceBitsFiltering(supportsServiceBitsFilteringIn) {} }; diff --git a/src/chainparams.cpp b/src/chainparams.cpp --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -163,23 +163,17 @@ // Note that of those with the service bits flag, most only support a // subset of possible options. // Bitcoin ABC seeder - vSeeds.push_back( - CDNSSeedData("bitcoinabc.org", "seed.bitcoinabc.org", true)); + vSeeds.emplace_back("seed.bitcoinabc.org", true); // bitcoinforks seeders - vSeeds.push_back(CDNSSeedData("bitcoinforks.org", - "seed-abc.bitcoinforks.org", true)); + vSeeds.emplace_back("seed-abc.bitcoinforks.org", true); // BU backed seeder - vSeeds.push_back(CDNSSeedData("bitcoinunlimited.info", - "btccash-seeder.bitcoinunlimited.info", - true)); + vSeeds.emplace_back("btccash-seeder.bitcoinunlimited.info", true); // Bitprim - vSeeds.push_back(CDNSSeedData("bitprim.org", "seed.bitprim.org", true)); + vSeeds.emplace_back("seed.bitprim.org", true); // Amaury SÉCHET - vSeeds.push_back( - CDNSSeedData("deadalnix.me", "seed.deadalnix.me", true)); + vSeeds.emplace_back("seed.deadalnix.me", true); // criptolayer.net - vSeeds.push_back( - CDNSSeedData("criptolayer.net", "seeder.criptolayer.net", true)); + vSeeds.emplace_back("seeder.criptolayer.net", true); base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 0); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 5); @@ -331,20 +325,15 @@ vSeeds.clear(); // nodes with support for servicebits filtering should be at the top // Bitcoin ABC seeder - vSeeds.push_back(CDNSSeedData("bitcoinabc.org", - "testnet-seed.bitcoinabc.org", true)); + vSeeds.emplace_back("testnet-seed.bitcoinabc.org", true); // bitcoinforks seeders - vSeeds.push_back(CDNSSeedData( - "bitcoinforks.org", "testnet-seed-abc.bitcoinforks.org", true)); + vSeeds.emplace_back("testnet-seed-abc.bitcoinforks.org", true); // Bitprim - vSeeds.push_back( - CDNSSeedData("bitprim.org", "testnet-seed.bitprim.org", true)); + vSeeds.emplace_back("testnet-seed.bitprim.org", true); // Amaury SÉCHET - vSeeds.push_back( - CDNSSeedData("deadalnix.me", "testnet-seed.deadalnix.me", true)); + vSeeds.emplace_back("testnet-seed.deadalnix.me", true); // criptolayer.net - vSeeds.push_back(CDNSSeedData("criptolayer.net", - "testnet-seeder.criptolayer.net", true)); + vSeeds.emplace_back("testnet-seeder.criptolayer.net", true); base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 111); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 196); diff --git a/src/net.cpp b/src/net.cpp --- a/src/net.cpp +++ b/src/net.cpp @@ -231,10 +231,11 @@ return true; } -/** Make a particular network entirely off-limits (no automatic connects to it) +/** + * Make a particular network entirely off-limits (no automatic connects to it). */ void SetLimited(enum Network net, bool fLimited) { - if (net == NET_UNROUTABLE) { + if (net == NET_UNROUTABLE || net == NET_INTERNAL) { return; } LOCK(cs_mapLocalHost); @@ -1706,8 +1707,12 @@ std::vector vIPs; std::vector vAdd; ServiceFlags requiredServiceBits = nRelevantServices; - if (LookupHost(GetDNSHost(seed, &requiredServiceBits).c_str(), vIPs, - 0, true)) { + std::string host = GetDNSHost(seed, &requiredServiceBits); + CNetAddr resolveSource; + if (!resolveSource.SetInternal(host)) { + continue; + } + if (LookupHost(host.c_str(), vIPs, 0, true)) { for (const CNetAddr &ip : vIPs) { int nOneDay = 24 * 3600; CAddress addr = CAddress( @@ -1718,16 +1723,7 @@ vAdd.push_back(addr); found++; } - } - // TODO: The seed name resolve may fail, yielding an IP of [::], - // which results in addrman assigning the same source to results - // from different seeds. This should switch to a hard-coded stable - // dummy IP for each seed name, so that the resolve is not required - // at all. - if (!vIPs.empty()) { - CService seedSource; - Lookup(seed.name.c_str(), seedSource, 0, true); - addrman.Add(vAdd, seedSource); + addrman.Add(vAdd, resolveSource); } } } @@ -1816,7 +1812,7 @@ LogPrintf("Adding fixed seed nodes as DNS doesn't seem to be " "available.\n"); CNetAddr local; - LookupHost("127.0.0.1", local, false); + local.SetInternal("fixedseeds"); addrman.Add(convertSeed6(config->GetChainParams().FixedSeeds()), local); done = true; diff --git a/src/netaddress.h b/src/netaddress.h --- a/src/netaddress.h +++ b/src/netaddress.h @@ -21,6 +21,7 @@ NET_IPV4, NET_IPV6, NET_TOR, + NET_INTERNAL, NET_MAX, }; @@ -45,6 +46,12 @@ */ void SetRaw(Network network, const uint8_t *data); + /** + * Transform an arbitrary string into a non-routable ipv6 address. + * Useful for mapping resolved addresses back to their source. + */ + bool SetInternal(const std::string &name); + // for Tor addresses bool SetSpecial(const std::string &strName); // IPv4 mapped address (::FFFF:0:0/96, 0.0.0.0/0) @@ -81,6 +88,7 @@ bool IsTor() const; bool IsLocal() const; bool IsRoutable() const; + bool IsInternal() const; bool IsValid() const; enum Network GetNetwork() const; std::string ToString() const; diff --git a/src/netaddress.cpp b/src/netaddress.cpp --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -15,6 +15,10 @@ static const uint8_t pchIPv4[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}; static const uint8_t pchOnionCat[] = {0xFD, 0x87, 0xD8, 0x7E, 0xEB, 0x43}; +// 0xFD + sha256("bitcoin")[0:5] +static const unsigned char g_internal_prefix[] = {0xFD, 0x6B, 0x88, + 0xC0, 0x87, 0x24}; + void CNetAddr::Init() { memset(ip, 0, sizeof(ip)); scopeId = 0; @@ -38,15 +42,30 @@ } } +bool CNetAddr::SetInternal(const std::string &name) { + if (name.empty()) { + return false; + } + uint8_t hash[32] = {}; + CSHA256().Write((const uint8_t *)name.data(), name.size()).Finalize(hash); + memcpy(ip, g_internal_prefix, sizeof(g_internal_prefix)); + memcpy(ip + sizeof(g_internal_prefix), hash, + sizeof(ip) - sizeof(g_internal_prefix)); + return true; +} + bool CNetAddr::SetSpecial(const std::string &strName) { if (strName.size() > 6 && strName.substr(strName.size() - 6, 6) == ".onion") { std::vector vchAddr = DecodeBase32(strName.substr(0, strName.size() - 6).c_str()); - if (vchAddr.size() != 16 - sizeof(pchOnionCat)) return false; + if (vchAddr.size() != 16 - sizeof(pchOnionCat)) { + return false; + } memcpy(ip, pchOnionCat, sizeof(pchOnionCat)); - for (unsigned int i = 0; i < 16 - sizeof(pchOnionCat); i++) + for (unsigned int i = 0; i < 16 - sizeof(pchOnionCat); i++) { ip[i + sizeof(pchOnionCat)] = vchAddr[i]; + } return true; } return false; @@ -74,7 +93,7 @@ } bool CNetAddr::IsIPv6() const { - return (!IsIPv4() && !IsTor()); + return !IsIPv4() && !IsTor() && !IsInternal(); } bool CNetAddr::IsRFC1918() const { @@ -176,6 +195,10 @@ // documentation IPv6 address if (IsRFC3849()) return false; + if (IsInternal()) { + return false; + } + if (IsIPv4()) { // INADDR_NONE uint32_t ipNone = INADDR_NONE; @@ -193,40 +216,63 @@ return IsValid() && !(IsRFC1918() || IsRFC2544() || IsRFC3927() || IsRFC4862() || IsRFC6598() || IsRFC5737() || (IsRFC4193() && !IsTor()) || - IsRFC4843() || IsLocal()); + IsRFC4843() || IsLocal() || IsInternal()); +} + +bool CNetAddr::IsInternal() const { + return memcmp(ip, g_internal_prefix, sizeof(g_internal_prefix)) == 0; } enum Network CNetAddr::GetNetwork() const { - if (!IsRoutable()) return NET_UNROUTABLE; + if (IsInternal()) { + return NET_INTERNAL; + } - if (IsIPv4()) return NET_IPV4; + if (!IsRoutable()) { + return NET_UNROUTABLE; + } - if (IsTor()) return NET_TOR; + if (IsIPv4()) { + return NET_IPV4; + } + + if (IsTor()) { + return NET_TOR; + } return NET_IPV6; } std::string CNetAddr::ToStringIP() const { - if (IsTor()) return EncodeBase32(&ip[6], 10) + ".onion"; + if (IsTor()) { + return EncodeBase32(&ip[6], 10) + ".onion"; + } + if (IsInternal()) { + return EncodeBase32(ip + sizeof(g_internal_prefix), + sizeof(ip) - sizeof(g_internal_prefix)) + + ".internal"; + } CService serv(*this, 0); struct sockaddr_storage sockaddr; socklen_t socklen = sizeof(sockaddr); if (serv.GetSockAddr((struct sockaddr *)&sockaddr, &socklen)) { char name[1025] = ""; if (!getnameinfo((const struct sockaddr *)&sockaddr, socklen, name, - sizeof(name), nullptr, 0, NI_NUMERICHOST)) + sizeof(name), nullptr, 0, NI_NUMERICHOST)) { return std::string(name); + } } - if (IsIPv4()) + if (IsIPv4()) { return strprintf("%u.%u.%u.%u", GetByte(3), GetByte(2), GetByte(1), GetByte(0)); - else - return strprintf( - "%x:%x:%x:%x:%x:%x:%x:%x", GetByte(15) << 8 | GetByte(14), - GetByte(13) << 8 | GetByte(12), GetByte(11) << 8 | GetByte(10), - GetByte(9) << 8 | GetByte(8), GetByte(7) << 8 | GetByte(6), - GetByte(5) << 8 | GetByte(4), GetByte(3) << 8 | GetByte(2), - GetByte(1) << 8 | GetByte(0)); + } + + return strprintf("%x:%x:%x:%x:%x:%x:%x:%x", GetByte(15) << 8 | GetByte(14), + GetByte(13) << 8 | GetByte(12), + GetByte(11) << 8 | GetByte(10), + GetByte(9) << 8 | GetByte(8), GetByte(7) << 8 | GetByte(6), + GetByte(5) << 8 | GetByte(4), GetByte(3) << 8 | GetByte(2), + GetByte(1) << 8 | GetByte(0)); } std::string CNetAddr::ToString() const { @@ -270,8 +316,13 @@ nBits = 0; } - if (!IsRoutable()) { - // all unroutable addresses belong to the same group + if (IsInternal()) { + // all internal-usage addresses get their own group + nClass = NET_INTERNAL; + nStartByte = sizeof(g_internal_prefix); + nBits = (sizeof(ip) - sizeof(g_internal_prefix)) * 8; + } else if (!IsRoutable()) { + // all other unroutable addresses belong to the same group nClass = NET_UNROUTABLE; nBits = 0; } else if (IsIPv4() || IsRFC6145() || IsRFC6052()) { @@ -309,8 +360,9 @@ nStartByte++; nBits -= 8; } - if (nBits > 0) + if (nBits > 0) { vchRet.push_back(GetByte(15 - nStartByte) | ((1 << (8 - nBits)) - 1)); + } return vchRet; } @@ -344,7 +396,9 @@ REACH_PRIVATE }; - if (!IsRoutable()) return REACH_UNREACHABLE; + if (!IsRoutable() || IsInternal()) { + return REACH_UNREACHABLE; + } int ourNet = GetExtNetwork(this); int theirNet = GetExtNetwork(paddrPartner); @@ -507,7 +561,7 @@ } std::string CService::ToStringIPPort() const { - if (IsIPv4() || IsTor()) { + if (IsIPv4() || IsTor() || IsInternal()) { return ToStringIP() + ":" + ToStringPort(); } else { return "[" + ToStringIP() + "]:" + ToStringPort(); diff --git a/src/netbase.cpp b/src/netbase.cpp --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -92,16 +92,23 @@ struct addrinfo *aiTrav = aiRes; while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions)) { + CNetAddr resolved; if (aiTrav->ai_family == AF_INET) { assert(aiTrav->ai_addrlen >= sizeof(sockaddr_in)); - vIP.push_back( - CNetAddr(((struct sockaddr_in *)(aiTrav->ai_addr))->sin_addr)); + resolved = + CNetAddr(((struct sockaddr_in *)(aiTrav->ai_addr))->sin_addr); } if (aiTrav->ai_family == AF_INET6) { assert(aiTrav->ai_addrlen >= sizeof(sockaddr_in6)); struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)aiTrav->ai_addr; - vIP.push_back(CNetAddr(s6->sin6_addr, s6->sin6_scope_id)); + resolved = CNetAddr(s6->sin6_addr, s6->sin6_scope_id); + } + + // Never allow resolving to an internal address. Consider any such + // result invalid. + if (!resolved.IsInternal()) { + vIP.push_back(resolved); } aiTrav = aiTrav->ai_next; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -468,7 +468,9 @@ UniValue networks(UniValue::VARR); for (int n = 0; n < NET_MAX; ++n) { enum Network network = static_cast(n); - if (network == NET_UNROUTABLE) continue; + if (network == NET_UNROUTABLE || network == NET_INTERNAL) { + continue; + } proxyType proxy; UniValue obj(UniValue::VOBJ); GetProxy(network, proxy); diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -24,6 +24,12 @@ return ret; } +static CNetAddr CreateInternal(const char *host) { + CNetAddr addr; + addr.SetInternal(host); + return addr; +} + BOOST_AUTO_TEST_CASE(netbase_networks) { BOOST_CHECK(ResolveIP("127.0.0.1").GetNetwork() == NET_UNROUTABLE); BOOST_CHECK(ResolveIP("::1").GetNetwork() == NET_UNROUTABLE); @@ -32,6 +38,7 @@ BOOST_CHECK( ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetNetwork() == NET_TOR); + BOOST_CHECK(CreateInternal("foo.com").GetNetwork() == NET_INTERNAL); } BOOST_AUTO_TEST_CASE(netbase_properties) { @@ -56,6 +63,9 @@ BOOST_CHECK(ResolveIP("8.8.8.8").IsRoutable()); BOOST_CHECK(ResolveIP("2001::1").IsRoutable()); BOOST_CHECK(ResolveIP("127.0.0.1").IsValid()); + BOOST_CHECK( + CreateInternal("FD6B:88C0:8724:edb1:8e4:3588:e546:35ca").IsInternal()); + BOOST_CHECK(CreateInternal("bar.com").IsInternal()); } static bool TestSplitHost(std::string test, std::string host, int port) { @@ -97,6 +107,12 @@ BOOST_CHECK(TestParse("[::]:8333", "[::]:8333")); BOOST_CHECK(TestParse("[127.0.0.1]", "127.0.0.1:65535")); BOOST_CHECK(TestParse(":::", "[::]:0")); + + // verify that an internal address fails to resolve + BOOST_CHECK(TestParse("[fd6b:88c0:8724:1:2:3:4:5]", "[::]:0")); + // and that a one-off resolves correctly + BOOST_CHECK(TestParse("[fd6c:88c0:8724:1:2:3:4:5]", + "[fd6c:88c0:8724:1:2:3:4:5]:65535")); } BOOST_AUTO_TEST_CASE(onioncat_test) { @@ -310,6 +326,12 @@ BOOST_CHECK( ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup() == Vec8({NET_IPV6, 32, 1, 32, 1})); + + // baz.net sha256 hash: + // 12929400eb4607c4ac075f087167e75286b179c693eb059a01774b864e8fe505 + Vec8 internal_group = {NET_INTERNAL, 0x12, 0x92, 0x94, 0x00, 0xeb, + 0x46, 0x07, 0xc4, 0xac, 0x07}; + BOOST_CHECK(CreateInternal("baz.net").GetGroup() == internal_group); } BOOST_AUTO_TEST_SUITE_END()