Changeset View
Changeset View
Standalone View
Standalone View
src/netaddress.cpp
// Copyright (c) 2009-2010 Satoshi Nakamoto | // Copyright (c) 2009-2010 Satoshi Nakamoto | ||||
// Copyright (c) 2009-2016 The Bitcoin Core developers | // Copyright (c) 2009-2016 The Bitcoin Core developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include <netaddress.h> | #include <netaddress.h> | ||||
#include <crypto/common.h> | |||||
#include <crypto/sha3.h> | |||||
#include <hash.h> | #include <hash.h> | ||||
#include <prevector.h> | |||||
#include <util/asmap.h> | #include <util/asmap.h> | ||||
#include <util/strencodings.h> | #include <util/strencodings.h> | ||||
#include <util/string.h> | #include <util/string.h> | ||||
#include <tinyformat.h> | #include <tinyformat.h> | ||||
#include <algorithm> | #include <algorithm> | ||||
#include <array> | #include <array> | ||||
#include <cstdint> | #include <cstdint> | ||||
#include <ios> | #include <ios> | ||||
#include <iterator> | #include <iterator> | ||||
#include <tuple> | #include <tuple> | ||||
constexpr size_t CNetAddr::V1_SERIALIZATION_SIZE; | constexpr size_t CNetAddr::V1_SERIALIZATION_SIZE; | ||||
constexpr size_t CNetAddr::MAX_ADDRV2_SIZE; | constexpr size_t CNetAddr::MAX_ADDRV2_SIZE; | ||||
CNetAddr::BIP155Network CNetAddr::GetBIP155Network() const { | CNetAddr::BIP155Network CNetAddr::GetBIP155Network() const { | ||||
switch (m_net) { | switch (m_net) { | ||||
case NET_IPV4: | case NET_IPV4: | ||||
return BIP155Network::IPV4; | return BIP155Network::IPV4; | ||||
case NET_IPV6: | case NET_IPV6: | ||||
return BIP155Network::IPV6; | return BIP155Network::IPV6; | ||||
case NET_ONION: | case NET_ONION: | ||||
switch (m_addr.size()) { | |||||
case ADDR_TORV2_SIZE: | |||||
return BIP155Network::TORV2; | return BIP155Network::TORV2; | ||||
case ADDR_TORV3_SIZE: | |||||
return BIP155Network::TORV3; | |||||
default: | |||||
assert(false); | |||||
} | |||||
case NET_I2P: | |||||
return BIP155Network::I2P; | |||||
case NET_CJDNS: | |||||
return BIP155Network::CJDNS; | |||||
case NET_INTERNAL: | case NET_INTERNAL: | ||||
// should have been handled before calling this function | // should have been handled before calling this function | ||||
case NET_UNROUTABLE: | case NET_UNROUTABLE: | ||||
// m_net is never and should not be set to NET_UNROUTABLE | // m_net is never and should not be set to NET_UNROUTABLE | ||||
case NET_MAX: | case NET_MAX: | ||||
// m_net is never and should not be set to NET_MAX | // m_net is never and should not be set to NET_MAX | ||||
assert(false); | assert(false); | ||||
} // no default case, so the compiler can warn about missing cases | } // no default case, so the compiler can warn about missing cases | ||||
Show All 23 Lines | switch (possible_bip155_net) { | ||||
case BIP155Network::TORV2: | case BIP155Network::TORV2: | ||||
if (address_size == ADDR_TORV2_SIZE) { | if (address_size == ADDR_TORV2_SIZE) { | ||||
m_net = NET_ONION; | m_net = NET_ONION; | ||||
return true; | return true; | ||||
} | } | ||||
throw std::ios_base::failure( | throw std::ios_base::failure( | ||||
strprintf("BIP155 TORv2 address with length %u (should be %u)", | strprintf("BIP155 TORv2 address with length %u (should be %u)", | ||||
address_size, ADDR_TORV2_SIZE)); | address_size, ADDR_TORV2_SIZE)); | ||||
case BIP155Network::TORV3: | |||||
if (address_size == ADDR_TORV3_SIZE) { | |||||
m_net = NET_ONION; | |||||
return true; | |||||
} | |||||
throw std::ios_base::failure( | |||||
strprintf("BIP155 TORv3 address with length %u (should be %u)", | |||||
address_size, ADDR_TORV3_SIZE)); | |||||
case BIP155Network::I2P: | |||||
if (address_size == ADDR_I2P_SIZE) { | |||||
m_net = NET_I2P; | |||||
return true; | |||||
} | |||||
throw std::ios_base::failure( | |||||
strprintf("BIP155 I2P address with length %u (should be %u)", | |||||
address_size, ADDR_I2P_SIZE)); | |||||
case BIP155Network::CJDNS: | |||||
if (address_size == ADDR_CJDNS_SIZE) { | |||||
m_net = NET_CJDNS; | |||||
return true; | |||||
} | |||||
throw std::ios_base::failure( | |||||
strprintf("BIP155 CJDNS address with length %u (should be %u)", | |||||
address_size, ADDR_CJDNS_SIZE)); | |||||
} | } | ||||
// Don't throw on addresses with unknown network ids (maybe from the | // Don't throw on addresses with unknown network ids (maybe from the | ||||
// future). Instead silently drop them and have the unserialization code | // future). Instead silently drop them and have the unserialization code | ||||
// consume subsequent ones which may be known to us. | // consume subsequent ones which may be known to us. | ||||
return false; | return false; | ||||
} | } | ||||
Show All 9 Lines | void CNetAddr::SetIP(const CNetAddr &ipIn) { | ||||
switch (ipIn.m_net) { | switch (ipIn.m_net) { | ||||
case NET_IPV4: | case NET_IPV4: | ||||
assert(ipIn.m_addr.size() == ADDR_IPV4_SIZE); | assert(ipIn.m_addr.size() == ADDR_IPV4_SIZE); | ||||
break; | break; | ||||
case NET_IPV6: | case NET_IPV6: | ||||
assert(ipIn.m_addr.size() == ADDR_IPV6_SIZE); | assert(ipIn.m_addr.size() == ADDR_IPV6_SIZE); | ||||
break; | break; | ||||
case NET_ONION: | case NET_ONION: | ||||
assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE); | assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE || | ||||
ipIn.m_addr.size() == ADDR_TORV3_SIZE); | |||||
break; | |||||
case NET_I2P: | |||||
assert(ipIn.m_addr.size() == ADDR_I2P_SIZE); | |||||
break; | |||||
case NET_CJDNS: | |||||
assert(ipIn.m_addr.size() == ADDR_CJDNS_SIZE); | |||||
break; | break; | ||||
case NET_INTERNAL: | case NET_INTERNAL: | ||||
assert(ipIn.m_addr.size() == ADDR_INTERNAL_SIZE); | assert(ipIn.m_addr.size() == ADDR_INTERNAL_SIZE); | ||||
break; | break; | ||||
case NET_UNROUTABLE: | case NET_UNROUTABLE: | ||||
case NET_MAX: | case NET_MAX: | ||||
assert(false); | assert(false); | ||||
} // no default case, so the compiler can warn about missing cases | } // no default case, so the compiler can warn about missing cases | ||||
Show All 40 Lines | bool CNetAddr::SetInternal(const std::string &name) { | ||||
} | } | ||||
m_net = NET_INTERNAL; | m_net = NET_INTERNAL; | ||||
uint8_t hash[32] = {}; | uint8_t hash[32] = {}; | ||||
CSHA256().Write((const uint8_t *)name.data(), name.size()).Finalize(hash); | CSHA256().Write((const uint8_t *)name.data(), name.size()).Finalize(hash); | ||||
m_addr.assign(hash, hash + ADDR_INTERNAL_SIZE); | m_addr.assign(hash, hash + ADDR_INTERNAL_SIZE); | ||||
return true; | return true; | ||||
} | } | ||||
namespace torv3 { | |||||
// https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt#n2135 | |||||
static constexpr size_t CHECKSUM_LEN = 2; | |||||
static const uint8_t VERSION[] = {3}; | |||||
static constexpr size_t TOTAL_LEN = | |||||
ADDR_TORV3_SIZE + CHECKSUM_LEN + sizeof(VERSION); | |||||
static void Checksum(Span<const uint8_t> addr_pubkey, | |||||
uint8_t (&checksum)[CHECKSUM_LEN]) { | |||||
// TORv3 CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2] | |||||
static const uint8_t prefix[] = ".onion checksum"; | |||||
static constexpr size_t prefix_len = 15; | |||||
SHA3_256 hasher; | |||||
hasher.Write(MakeSpan(prefix).first(prefix_len)); | |||||
hasher.Write(addr_pubkey); | |||||
hasher.Write(VERSION); | |||||
uint8_t checksum_full[SHA3_256::OUTPUT_SIZE]; | |||||
hasher.Finalize(checksum_full); | |||||
memcpy(checksum, checksum_full, sizeof(checksum)); | |||||
} | |||||
}; // namespace torv3 | |||||
/** | /** | ||||
* Parse a TORv2 address and set this object to it. | * Parse a TOR address and set this object to it. | ||||
* | * | ||||
* @returns Whether or not the operation was successful. | * @returns Whether or not the operation was successful. | ||||
* | * | ||||
* @see CNetAddr::IsTor() | * @see CNetAddr::IsTor() | ||||
*/ | */ | ||||
bool CNetAddr::SetSpecial(const std::string &strName) { | bool CNetAddr::SetSpecial(const std::string &str) { | ||||
if (strName.size() > 6 && | static const char *suffix{".onion"}; | ||||
strName.substr(strName.size() - 6, 6) == ".onion") { | static constexpr size_t suffix_len{6}; | ||||
std::vector<uint8_t> vchAddr = | |||||
DecodeBase32(strName.substr(0, strName.size() - 6).c_str()); | if (!ValidAsCString(str) || str.size() <= suffix_len || | ||||
if (vchAddr.size() != ADDR_TORV2_SIZE) { | str.substr(str.size() - suffix_len) != suffix) { | ||||
return false; | |||||
} | |||||
bool invalid; | |||||
const auto &input = | |||||
DecodeBase32(str.substr(0, str.size() - suffix_len).c_str(), &invalid); | |||||
if (invalid) { | |||||
return false; | |||||
} | |||||
switch (input.size()) { | |||||
case ADDR_TORV2_SIZE: | |||||
m_net = NET_ONION; | |||||
m_addr.assign(input.begin(), input.end()); | |||||
return true; | |||||
case torv3::TOTAL_LEN: { | |||||
Span<const uint8_t> input_pubkey{input.data(), ADDR_TORV3_SIZE}; | |||||
Span<const uint8_t> input_checksum{input.data() + ADDR_TORV3_SIZE, | |||||
torv3::CHECKSUM_LEN}; | |||||
Span<const uint8_t> input_version{input.data() + ADDR_TORV3_SIZE + | |||||
torv3::CHECKSUM_LEN, | |||||
sizeof(torv3::VERSION)}; | |||||
uint8_t calculated_checksum[torv3::CHECKSUM_LEN]; | |||||
torv3::Checksum(input_pubkey, calculated_checksum); | |||||
if (input_checksum != calculated_checksum || | |||||
input_version != torv3::VERSION) { | |||||
return false; | return false; | ||||
} | } | ||||
m_net = NET_ONION; | m_net = NET_ONION; | ||||
m_addr.assign(vchAddr.begin(), vchAddr.end()); | m_addr.assign(input_pubkey.begin(), input_pubkey.end()); | ||||
return true; | return true; | ||||
} | } | ||||
} | |||||
return false; | return false; | ||||
} | } | ||||
CNetAddr::CNetAddr(const struct in_addr &ipv4Addr) { | CNetAddr::CNetAddr(const struct in_addr &ipv4Addr) { | ||||
m_net = NET_IPV4; | m_net = NET_IPV4; | ||||
const uint8_t *ptr = reinterpret_cast<const uint8_t *>(&ipv4Addr); | const uint8_t *ptr = reinterpret_cast<const uint8_t *>(&ipv4Addr); | ||||
m_addr.assign(ptr, ptr + ADDR_IPV4_SIZE); | m_addr.assign(ptr, ptr + ADDR_IPV4_SIZE); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 96 Lines • ▼ Show 20 Lines | |||||
} | } | ||||
bool CNetAddr::IsHeNet() const { | bool CNetAddr::IsHeNet() const { | ||||
return IsIPv6() && | return IsIPv6() && | ||||
HasPrefix(m_addr, std::array<uint8_t, 4>{{0x20, 0x01, 0x04, 0x70}}); | HasPrefix(m_addr, std::array<uint8_t, 4>{{0x20, 0x01, 0x04, 0x70}}); | ||||
} | } | ||||
/** | /** | ||||
* @returns Whether or not this is a dummy address that maps an onion address | * Check whether this object represents a TOR address. | ||||
* into IPv6. | |||||
* | * | ||||
* @see CNetAddr::SetSpecial(const std::string &) | * @see CNetAddr::SetSpecial(const std::string &) | ||||
*/ | */ | ||||
bool CNetAddr::IsTor() const { | bool CNetAddr::IsTor() const { | ||||
return m_net == NET_ONION; | return m_net == NET_ONION; | ||||
} | } | ||||
/** | |||||
* Check whether this object represents an I2P address. | |||||
*/ | |||||
bool CNetAddr::IsI2P() const { | |||||
return m_net == NET_I2P; | |||||
} | |||||
/** | |||||
* Check whether this object represents a CJDNS address. | |||||
*/ | |||||
bool CNetAddr::IsCJDNS() const { | |||||
return m_net == NET_CJDNS; | |||||
} | |||||
bool CNetAddr::IsLocal() const { | bool CNetAddr::IsLocal() const { | ||||
// IPv4 loopback (127.0.0.0/8 or 0.0.0.0/8) | // IPv4 loopback (127.0.0.0/8 or 0.0.0.0/8) | ||||
if (IsIPv4() && (m_addr[0] == 127 || m_addr[0] == 0)) { | if (IsIPv4() && (m_addr[0] == 127 || m_addr[0] == 0)) { | ||||
return true; | return true; | ||||
} | } | ||||
// IPv6 loopback (::1/128) | // IPv6 loopback (::1/128) | ||||
static const uint8_t pchLocal[16] = {0, 0, 0, 0, 0, 0, 0, 0, | static const uint8_t pchLocal[16] = {0, 0, 0, 0, 0, 0, 0, 0, | ||||
▲ Show 20 Lines • Show All 84 Lines • ▼ Show 20 Lines | enum Network CNetAddr::GetNetwork() const { | ||||
if (!IsRoutable()) { | if (!IsRoutable()) { | ||||
return NET_UNROUTABLE; | return NET_UNROUTABLE; | ||||
} | } | ||||
return m_net; | return m_net; | ||||
} | } | ||||
std::string CNetAddr::ToStringIP() const { | static std::string IPv6ToString(Span<const uint8_t> a) { | ||||
if (IsTor()) { | assert(a.size() == ADDR_IPV6_SIZE); | ||||
return EncodeBase32(m_addr) + ".onion"; | // clang-format off | ||||
} | return strprintf("%x:%x:%x:%x:%x:%x:%x:%x", | ||||
if (IsInternal()) { | ReadBE16(&a[0]), | ||||
return EncodeBase32(m_addr) + ".internal"; | ReadBE16(&a[2]), | ||||
ReadBE16(&a[4]), | |||||
ReadBE16(&a[6]), | |||||
ReadBE16(&a[8]), | |||||
ReadBE16(&a[10]), | |||||
ReadBE16(&a[12]), | |||||
ReadBE16(&a[14])); | |||||
// clang-format on | |||||
} | } | ||||
std::string CNetAddr::ToStringIP() const { | |||||
switch (m_net) { | |||||
case NET_IPV4: | |||||
case NET_IPV6: { | |||||
CService serv(*this, 0); | CService serv(*this, 0); | ||||
struct sockaddr_storage sockaddr; | struct sockaddr_storage sockaddr; | ||||
socklen_t socklen = sizeof(sockaddr); | socklen_t socklen = sizeof(sockaddr); | ||||
if (serv.GetSockAddr((struct sockaddr *)&sockaddr, &socklen)) { | if (serv.GetSockAddr((struct sockaddr *)&sockaddr, &socklen)) { | ||||
char name[1025] = ""; | char name[1025] = ""; | ||||
if (!getnameinfo((const struct sockaddr *)&sockaddr, socklen, name, | if (!getnameinfo((const struct sockaddr *)&sockaddr, socklen, | ||||
sizeof(name), nullptr, 0, NI_NUMERICHOST)) { | name, sizeof(name), nullptr, 0, | ||||
NI_NUMERICHOST)) { | |||||
return std::string(name); | return std::string(name); | ||||
} | } | ||||
} | } | ||||
if (IsIPv4()) { | if (m_net == NET_IPV4) { | ||||
return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], | return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], | ||||
m_addr[3]); | m_addr[3]); | ||||
} | } | ||||
assert(IsIPv6()); | return IPv6ToString(m_addr); | ||||
return strprintf("%x:%x:%x:%x:%x:%x:%x:%x", m_addr[0] << 8 | m_addr[1], | } | ||||
m_addr[2] << 8 | m_addr[3], m_addr[4] << 8 | m_addr[5], | case NET_ONION: | ||||
m_addr[6] << 8 | m_addr[7], m_addr[8] << 8 | m_addr[9], | switch (m_addr.size()) { | ||||
m_addr[10] << 8 | m_addr[11], m_addr[12] << 8 | m_addr[13], | case ADDR_TORV2_SIZE: | ||||
m_addr[14] << 8 | m_addr[15]); | return EncodeBase32(m_addr) + ".onion"; | ||||
case ADDR_TORV3_SIZE: { | |||||
uint8_t checksum[torv3::CHECKSUM_LEN]; | |||||
torv3::Checksum(m_addr, checksum); | |||||
// TORv3 onion_address = base32(PUBKEY | CHECKSUM | VERSION) | |||||
// + ".onion" | |||||
prevector<torv3::TOTAL_LEN, uint8_t> address{m_addr.begin(), | |||||
m_addr.end()}; | |||||
address.insert(address.end(), checksum, | |||||
checksum + torv3::CHECKSUM_LEN); | |||||
address.insert(address.end(), torv3::VERSION, | |||||
torv3::VERSION + sizeof(torv3::VERSION)); | |||||
return EncodeBase32(address) + ".onion"; | |||||
} | |||||
default: | |||||
assert(false); | |||||
} | |||||
case NET_I2P: | |||||
return EncodeBase32(m_addr, false /* don't pad with = */) + | |||||
".b32.i2p"; | |||||
case NET_CJDNS: | |||||
return IPv6ToString(m_addr); | |||||
case NET_INTERNAL: | |||||
return EncodeBase32(m_addr) + ".internal"; | |||||
case NET_UNROUTABLE: | |||||
// m_net is never and should not be set to NET_UNROUTABLE | |||||
case NET_MAX: | |||||
// m_net is never and should not be set to NET_MAX | |||||
assert(false); | |||||
} // no default case, so the compiler can warn about missing cases | |||||
assert(false); | |||||
} | } | ||||
std::string CNetAddr::ToString() const { | std::string CNetAddr::ToString() const { | ||||
return ToStringIP(); | return ToStringIP(); | ||||
} | } | ||||
bool operator==(const CNetAddr &a, const CNetAddr &b) { | bool operator==(const CNetAddr &a, const CNetAddr &b) { | ||||
return a.m_net == b.m_net && a.m_addr == b.m_addr; | return a.m_net == b.m_net && a.m_addr == b.m_addr; | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | if (IsIPv4()) { | ||||
// Teredo tunneled IPv4: the IPv4 address is in the last 4 bytes of the | // Teredo tunneled IPv4: the IPv4 address is in the last 4 bytes of the | ||||
// address, but bitflipped | // address, but bitflipped | ||||
return ~ReadBE32(MakeSpan(m_addr).last(ADDR_IPV4_SIZE).data()); | return ~ReadBE32(MakeSpan(m_addr).last(ADDR_IPV4_SIZE).data()); | ||||
} | } | ||||
assert(false); | assert(false); | ||||
} | } | ||||
uint32_t CNetAddr::GetNetClass() const { | uint32_t CNetAddr::GetNetClass() const { | ||||
uint32_t net_class = NET_IPV6; | // Make sure that if we return NET_IPV6, then IsIPv6() is true. The callers | ||||
if (IsLocal()) { | // expect that. | ||||
net_class = 255; | |||||
} | // Check for "internal" first because such addresses are also !IsRoutable() | ||||
// and we don't want to return NET_UNROUTABLE in that case. | |||||
if (IsInternal()) { | if (IsInternal()) { | ||||
net_class = NET_INTERNAL; | return NET_INTERNAL; | ||||
} else if (!IsRoutable()) { | |||||
net_class = NET_UNROUTABLE; | |||||
} else if (HasLinkedIPv4()) { | |||||
net_class = NET_IPV4; | |||||
} else if (IsTor()) { | |||||
net_class = NET_ONION; | |||||
} | } | ||||
return net_class; | if (!IsRoutable()) { | ||||
return NET_UNROUTABLE; | |||||
} | |||||
if (HasLinkedIPv4()) { | |||||
return NET_IPV4; | |||||
} | |||||
return m_net; | |||||
} | } | ||||
uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const { | uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const { | ||||
uint32_t net_class = GetNetClass(); | uint32_t net_class = GetNetClass(); | ||||
if (asmap.size() == 0 || (net_class != NET_IPV4 && net_class != NET_IPV6)) { | if (asmap.size() == 0 || (net_class != NET_IPV4 && net_class != NET_IPV6)) { | ||||
return 0; // Indicates not found, safe because AS0 is reserved per | return 0; // Indicates not found, safe because AS0 is reserved per | ||||
// RFC7607. | // RFC7607. | ||||
} | } | ||||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | std::vector<uint8_t> CNetAddr::GetGroup(const std::vector<bool> &asmap) const { | ||||
} else if (!IsRoutable()) { | } else if (!IsRoutable()) { | ||||
// all other unroutable addresses belong to the same group | // all other unroutable addresses belong to the same group | ||||
} else if (HasLinkedIPv4()) { | } else if (HasLinkedIPv4()) { | ||||
// IPv4 addresses (and mapped IPv4 addresses) use /16 groups | // IPv4 addresses (and mapped IPv4 addresses) use /16 groups | ||||
uint32_t ipv4 = GetLinkedIPv4(); | uint32_t ipv4 = GetLinkedIPv4(); | ||||
vchRet.push_back((ipv4 >> 24) & 0xFF); | vchRet.push_back((ipv4 >> 24) & 0xFF); | ||||
vchRet.push_back((ipv4 >> 16) & 0xFF); | vchRet.push_back((ipv4 >> 16) & 0xFF); | ||||
return vchRet; | return vchRet; | ||||
} else if (IsTor()) { | } else if (IsTor() || IsI2P() || IsCJDNS()) { | ||||
nBits = 4; | nBits = 4; | ||||
} else if (IsHeNet()) { | } else if (IsHeNet()) { | ||||
// for he.net, use /36 groups | // for he.net, use /36 groups | ||||
nBits = 36; | nBits = 36; | ||||
} else { | } else { | ||||
// for the rest of the IPv6 network, use /32 groups | // for the rest of the IPv6 network, use /32 groups | ||||
nBits = 32; | nBits = 32; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 229 Lines • ▼ Show 20 Lines | std::vector<uint8_t> CService::GetKey() const { | ||||
return key; | return key; | ||||
} | } | ||||
std::string CService::ToStringPort() const { | std::string CService::ToStringPort() const { | ||||
return strprintf("%u", port); | return strprintf("%u", port); | ||||
} | } | ||||
std::string CService::ToStringIPPort() const { | std::string CService::ToStringIPPort() const { | ||||
if (IsIPv4() || IsTor() || IsInternal()) { | if (IsIPv4() || IsTor() || IsI2P() || IsInternal()) { | ||||
return ToStringIP() + ":" + ToStringPort(); | return ToStringIP() + ":" + ToStringPort(); | ||||
} else { | } else { | ||||
return "[" + ToStringIP() + "]:" + ToStringPort(); | return "[" + ToStringIP() + "]:" + ToStringPort(); | ||||
} | } | ||||
} | } | ||||
std::string CService::ToString() const { | std::string CService::ToString() const { | ||||
return ToStringIPPort(); | return ToStringIPPort(); | ||||
▲ Show 20 Lines • Show All 165 Lines • Show Last 20 Lines |