From 4e690feefb8b5974a31750824621d65aaa914404 Mon Sep 17 00:00:00 2001 From: SChernykh <15806605+SChernykh@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:33:20 +0200 Subject: [PATCH] Support .onion domains for `addpeers` command and `--addpeers` option (requires SOCKS5 proxy) --- src/merge_mining_client_tari.cpp | 2 +- src/p2p_server.cpp | 33 +++++---- src/stratum_server.cpp | 2 +- src/tcp_server.cpp | 115 +++++++++++++++++++++++-------- src/tcp_server.h | 11 ++- 5 files changed, 120 insertions(+), 43 deletions(-) diff --git a/src/merge_mining_client_tari.cpp b/src/merge_mining_client_tari.cpp index 1520c50..1603c43 100644 --- a/src/merge_mining_client_tari.cpp +++ b/src/merge_mining_client_tari.cpp @@ -911,7 +911,7 @@ bool MergeMiningClientTari::TariServer::connect_upstream(TariClient* downstream) upstream->m_owner = this; upstream->m_port = port; - upstream->m_isV6 = is_v6; + upstream->m_addressType = is_v6 ? Client::AddressType::IPv6 : Client::AddressType::IPv4; if (!str_to_ip(is_v6, ip.c_str(), upstream->m_addr)) { return_client(upstream); diff --git a/src/p2p_server.cpp b/src/p2p_server.cpp index f6d909b..e880a41 100644 --- a/src/p2p_server.cpp +++ b/src/p2p_server.cpp @@ -274,7 +274,13 @@ void P2PServer::connect_to_peers(const std::string& peer_list) parse_address_list(peer_list, [this](bool is_v6, const std::string& /*address*/, std::string ip, int port) { - if (!m_pool->params().m_dns || resolve_host(ip, is_v6)) { + if (!m_socks5Proxy.empty() && (ip.find_first_not_of("0123456789.:") != std::string::npos)) { + // Assume it's a domain name and use the proxy to resolve it + if (!connect_to_peer(ip, port)) { + LOGERR(5, "connect_to_peers: failed to connect to " << ip); + } + } + else if (!m_pool->params().m_dns || resolve_host(ip, is_v6)) { if (!connect_to_peer(is_v6, ip.c_str(), port)) { LOGERR(5, "connect_to_peers: failed to connect to " << ip); } @@ -315,11 +321,14 @@ void P2PServer::update_peer_connections() connected_clients.reserve(m_numConnections); for (P2PClient* client = static_cast(m_connectedClientsList->m_next); client != m_connectedClientsList; client = static_cast(client->m_next)) { const int timeout = client->m_handshakeComplete ? 300 : 10; - if ((cur_time >= client->m_lastAlive + timeout) && (client->m_socks5ProxyState == Client::Socks5ProxyState::Default)) { - const uint64_t idle_time = static_cast(cur_time - client->m_lastAlive); - LOGWARN(5, "peer " << static_cast(client->m_addrString) << " has been idle for " << idle_time << " seconds, disconnecting"); - client->close(); - continue; + if (cur_time >= client->m_lastAlive + timeout) { + const Client::Socks5ProxyState s = client->m_socks5ProxyState; + if ((s == Client::Socks5ProxyState::Default) || (s == Client::Socks5ProxyState::ConnectRequestSent)) { + const uint64_t idle_time = static_cast(cur_time - client->m_lastAlive); + LOGWARN(5, "peer " << static_cast(client->m_addrString) << " has been idle for " << idle_time << " seconds, disconnecting"); + client->close(); + continue; + } } if (client->m_handshakeComplete && client->m_lastBroadcastTimestamp) { @@ -791,7 +800,7 @@ void P2PServer::remove_peer_from_list(const P2PClient* client) for (auto it = m_peerList.begin(); it != m_peerList.end(); ++it) { const Peer& p = *it; - if ((p.m_isV6 == client->m_isV6) && (p.m_port == client->m_listenPort) && (p.m_addr == client->m_addr)) { + if ((p.m_isV6 == client->isV6()) && (p.m_port == client->m_listenPort) && (p.m_addr == client->m_addr)) { m_peerList.erase(it); return; } @@ -2462,7 +2471,7 @@ bool P2PServer::P2PClient::on_listen_port(const uint8_t* buf) m_listenPort = port; - static_cast(m_owner)->update_peer_in_list(m_isV6, m_addr, port); + static_cast(m_owner)->update_peer_in_list(isV6(), m_addr, port); return true; } @@ -2675,7 +2684,7 @@ bool P2PServer::P2PClient::on_peer_list_request(const uint8_t*) continue; } - const Peer p{ client->m_isV6, client->m_addr, client->m_listenPort, 0, 0 }; + const Peer p{ client->isV6(), client->m_addr, client->m_listenPort, 0, 0 }; ++n; // Use https://en.wikipedia.org/wiki/Reservoir_sampling algorithm @@ -3146,7 +3155,7 @@ bool P2PServer::P2PClient::on_monero_block_broadcast(const uint8_t* buf, uint32_ bool pow_check_passed; }; - Work* work = new Work{ {}, server, this, m_resetCounter, m_isV6, m_addr, pool->hasher(), std::move(blob), height, seed, diff, { buf0, buf0 + size0 }, false }; + Work* work = new Work{ {}, server, this, m_resetCounter, isV6(), m_addr, pool->hasher(), std::move(blob), height, seed, diff, { buf0, buf0 + size0 }, false }; work->req.data = work; const int err = uv_queue_work(&server->m_loop, &work->req, @@ -3301,7 +3310,7 @@ bool P2PServer::P2PClient::handle_incoming_block_async(const PoolBlock* block, u bool result; }; - Work* work = new Work{ {}, *block, this, server, m_resetCounter.load(), m_isV6, m_addr, {}, true }; + Work* work = new Work{ {}, *block, this, server, m_resetCounter.load(), isV6(), m_addr, {}, true }; work->req.data = work; const int err = uv_queue_work(&server->m_loop, &work->req, @@ -3369,7 +3378,7 @@ void P2PServer::P2PClient::post_handle_incoming_block(p2pool* pool, const PoolBl P2PClient* c = server->m_fastestPeer; if (c && (c != this) && (c->m_broadcastMaxHeight >= block.m_sidechainHeight)) { LOGINFO(5, "peer " << static_cast(c->m_addrString) << " is faster, sending BLOCK_REQUEST to it instead"); - c->post_handle_incoming_block(pool, block, c->m_resetCounter.load(), c->m_isV6, c->m_addr, missing_blocks, true); + c->post_handle_incoming_block(pool, block, c->m_resetCounter.load(), c->isV6(), c->m_addr, missing_blocks, true); return; } } diff --git a/src/stratum_server.cpp b/src/stratum_server.cpp index 3c52116..c3652b5 100644 --- a/src/stratum_server.cpp +++ b/src/stratum_server.cpp @@ -448,7 +448,7 @@ bool StratumServer::on_submit(StratumClient* client, uint32_t id, const char* jo share.m_server = this; share.m_client = client; - share.m_clientIPv6 = client->m_isV6; + share.m_clientIPv6 = client->isV6(); share.m_clientAddr = client->m_addr; memcpy(share.m_clientAddrString, client->m_addrString, sizeof(share.m_clientAddrString)); memcpy(share.m_clientCustomUser, client->m_customUser, sizeof(share.m_clientCustomUser)); diff --git a/src/tcp_server.cpp b/src/tcp_server.cpp index 8c78c52..5f2826a 100644 --- a/src/tcp_server.cpp +++ b/src/tcp_server.cpp @@ -282,7 +282,7 @@ bool TCPServer::connect_to_peer(bool is_v6, const char* ip, int port) Client* client = get_client(); client->m_owner = this; client->m_port = port; - client->m_isV6 = is_v6; + client->m_addressType = is_v6 ? Client::AddressType::IPv6 : Client::AddressType::IPv4; if (!str_to_ip(is_v6, ip, client->m_addr)) { return_client(client); @@ -310,12 +310,40 @@ bool TCPServer::connect_to_peer(bool is_v6, const raw_ip& ip, int port) client->m_owner = this; client->m_addr = ip; client->m_port = port; - client->m_isV6 = is_v6; + client->m_addressType = is_v6 ? Client::AddressType::IPv6 : Client::AddressType::IPv4; client->init_addr_string(); return connect_to_peer(client); } +bool TCPServer::connect_to_peer(const std::string& domain, int port) +{ + if (m_socks5Proxy.empty()) { + LOGERR(1, "Can't connect to " << domain << ": SOCKS5 proxy is required"); + return false; + } + + if (m_finished.load()) { + return false; + } + + Client* client = get_client(); + client->m_owner = this; + client->m_addr = raw_ip::localhost_ipv4; + client->m_port = port; + client->m_addressType = Client::AddressType::DomainName; + + log::Stream s(client->m_addrString); + s << domain << ':' << port << '\0'; + + if (s.m_spilled) { + LOGERR(1, "Can't connect to " << domain << ": too long domain name"); + return false; + } + + return connect_to_peer(client); +} + bool TCPServer::is_banned(bool is_v6, raw_ip ip) { if (ip.is_localhost()) { @@ -345,13 +373,13 @@ bool TCPServer::is_banned(bool is_v6, raw_ip ip) bool TCPServer::connect_to_peer(Client* client) { - if (is_banned(client->m_isV6, client->m_addr)) { + if (is_banned(client->isV6(), client->m_addr)) { LOGINFO(5, "peer " << log::Gray() << static_cast(client->m_addrString) << log::NoColor() << " is banned, not connecting to it"); return_client(client); return false; } - if (!m_pendingConnections.insert(client->m_addr).second) { + if ((client->m_addressType != Client::AddressType::DomainName) && (m_pendingConnections.find(client->m_addr) != m_pendingConnections.end())) { LOGINFO(6, "there is already a pending connection to this IP, not connecting to " << log::Gray() << static_cast(client->m_addrString)); return_client(client); return false; @@ -385,18 +413,22 @@ bool TCPServer::connect_to_peer(Client* client) sockaddr_storage addr{}; if (m_socks5Proxy.empty()) { - if (client->m_isV6) { + if (client->m_addressType == Client::AddressType::IPv6) { sockaddr_in6* addr6 = reinterpret_cast(&addr); addr6->sin6_family = AF_INET6; memcpy(&addr6->sin6_addr, client->m_addr.data, sizeof(in6_addr)); addr6->sin6_port = htons(static_cast(client->m_port)); } - else { + else if (client->m_addressType == Client::AddressType::IPv4) { sockaddr_in* addr4 = reinterpret_cast(&addr); addr4->sin_family = AF_INET; memcpy(&addr4->sin_addr, client->m_addr.data + sizeof(raw_ip::ipv4_prefix), sizeof(in_addr)); addr4->sin_port = htons(static_cast(client->m_port)); } + else { + LOGWARN(5, "failed to initiate tcp connection to " << static_cast(client->m_addrString) << ": domain name is unresolved and SOCKS5 proxy is not enabled"); + return false; + } } else { if (m_socks5ProxyV6) { @@ -416,11 +448,14 @@ bool TCPServer::connect_to_peer(Client* client) err = uv_tcp_connect(connect_request, &client->m_socket, reinterpret_cast(&addr), on_connect); if (err) { LOGWARN(5, "failed to initiate tcp connection to " << static_cast(client->m_addrString) << ", error " << uv_err_name(err)); - m_pendingConnections.erase(client->m_addr); uv_close(reinterpret_cast(&client->m_socket), on_connection_error); return false; } + if (client->m_addressType != Client::AddressType::DomainName) { + m_pendingConnections.insert(client->m_addr); + } + LOGINFO(5, "connecting to " << log::Gray() << static_cast(client->m_addrString)); return true; } @@ -755,7 +790,9 @@ void TCPServer::on_connect(uv_connect_t* req, int status) return; } - server->m_pendingConnections.erase(client->m_addr); + if (client->m_addressType != Client::AddressType::DomainName) { + server->m_pendingConnections.erase(client->m_addr); + } if (status) { if (status == UV_ETIMEDOUT) { @@ -764,7 +801,7 @@ void TCPServer::on_connect(uv_connect_t* req, int status) else { LOGWARN(5, "failed to connect to " << static_cast(client->m_addrString) << ", error " << uv_err_name(status)); } - server->on_connect_failed(client->m_isV6, client->m_addr, client->m_port); + server->on_connect_failed(client->isV6(), client->m_addr, client->m_port); client->on_connect_failed(status); uv_close(reinterpret_cast(&client->m_socket), on_connection_error); return; @@ -823,7 +860,12 @@ void TCPServer::on_new_client(uv_stream_t* server, Client* client) if (client->m_isIncoming) { ++m_numIncomingConnections; - client->m_isV6 = (std::find(m_listenSockets6.begin(), m_listenSockets6.end(), reinterpret_cast(server)) != m_listenSockets6.end()); + if (std::find(m_listenSockets6.begin(), m_listenSockets6.end(), reinterpret_cast(server)) != m_listenSockets6.end()) { + client->m_addressType = Client::AddressType::IPv6; + } + else { + client->m_addressType = Client::AddressType::IPv4; + } sockaddr_storage peer_addr; int peer_addr_len = static_cast(sizeof(peer_addr)); @@ -834,7 +876,7 @@ void TCPServer::on_new_client(uv_stream_t* server, Client* client) return; } - if (client->m_isV6) { + if (client->isV6()) { memcpy(client->m_addr.data, &reinterpret_cast(&peer_addr)->sin6_addr, sizeof(in6_addr)); client->m_port = ntohs(reinterpret_cast(&peer_addr)->sin6_port); } @@ -849,7 +891,7 @@ void TCPServer::on_new_client(uv_stream_t* server, Client* client) LOGINFO(5, "new connection " << (client->m_isIncoming ? "from " : "to ") << log::Gray() << static_cast(client->m_addrString)); - if (is_banned(client->m_isV6, client->m_addr)) { + if (is_banned(client->isV6(), client->m_addr)) { LOGINFO(5, "peer " << log::Gray() << static_cast(client->m_addrString) << log::NoColor() << " is banned, disconnecting"); client->close(); return; @@ -1033,12 +1075,12 @@ TCPServer::Client::Client(char* read_buf, size_t size) , m_prev(nullptr) , m_next(nullptr) , m_socket{} - , m_isV6(false) + , m_addressType(AddressType::IPv4) , m_isIncoming(false) , m_readBufInUse(false) , m_isClosing(false) , m_numRead(0) - , m_addr{} + , m_addr(raw_ip::localhost_ipv4) , m_port(0) , m_addrString{} , m_socks5ProxyState(Socks5ProxyState::Default) @@ -1059,12 +1101,12 @@ void TCPServer::Client::reset() m_prev = nullptr; m_next = nullptr; memset(&m_socket, 0, sizeof(m_socket)); - m_isV6 = false; + m_addressType = AddressType::IPv4; m_isIncoming = false; m_readBufInUse = false; m_isClosing = false; m_numRead = 0; - m_addr = {}; + m_addr = raw_ip::localhost_ipv4; m_port = -1; m_addrString[0] = '\0'; m_socks5ProxyState = Socks5ProxyState::Default; @@ -1157,27 +1199,44 @@ bool TCPServer::Client::on_proxy_handshake(const char* data, uint32_t size) const bool result = m_owner->send(this, [this](uint8_t* buf, size_t buf_size) -> size_t { - if (buf_size < 22) { + if (buf_size < ADDR_STRING_SIZE + 7) { return 0; } buf[0] = 5; // Protocol version (SOCKS5) buf[1] = 1; // CONNECT buf[2] = 0; // RESERVED - if (m_isV6) { + + if (m_addressType == AddressType::DomainName) { + buf[3] = 3; // ATYP + + const char* s = strchr(m_addrString, ':'); + const size_t domain_len = s ? (s - m_addrString) : strlen(m_addrString); + + buf[4] = static_cast(domain_len); + memcpy(buf + 5, m_addrString, domain_len); + + buf[domain_len + 5] = static_cast(m_port >> 8); + buf[domain_len + 6] = static_cast(m_port & 0xFF); + + return domain_len + 7; + } + + if (m_addressType == AddressType::IPv6) { buf[3] = 4; // ATYP memcpy(buf + 4, m_addr.data, 16); buf[20] = static_cast(m_port >> 8); buf[21] = static_cast(m_port & 0xFF); - } - else { - buf[3] = 1; // ATYP - memcpy(buf + 4, m_addr.data + sizeof(raw_ip::ipv4_prefix), 4); - buf[8] = static_cast(m_port >> 8); - buf[9] = static_cast(m_port & 0xFF); + + return 22; } - return m_isV6 ? 22 : 10; + buf[3] = 1; // ATYP + memcpy(buf + 4, m_addr.data + sizeof(raw_ip::ipv4_prefix), 4); + buf[8] = static_cast(m_port >> 8); + buf[9] = static_cast(m_port & 0xFF); + + return 10; }); if (result) { @@ -1298,7 +1357,7 @@ void TCPServer::Client::ban(uint64_t seconds) if (m_owner) { LOGWARN(3, "peer " << static_cast(m_addrString) << " banned for " << seconds << " seconds"); - m_owner->ban(m_isV6, m_addr, seconds); + m_owner->ban(isV6(), m_addr, seconds); } } @@ -1307,7 +1366,7 @@ void TCPServer::Client::init_addr_string() const char* addr_str; char addr_str_buf[64]; - if (m_isV6) { + if (isV6()) { addr_str = inet_ntop(AF_INET6, m_addr.data, addr_str_buf, sizeof(addr_str_buf)); } else { @@ -1321,7 +1380,7 @@ void TCPServer::Client::init_addr_string() } log::Stream s(m_addrString); - if (m_isV6) { + if (isV6()) { s << '[' << log::const_buf(addr_str, n) << "]:" << m_port << '\0'; } else { diff --git a/src/tcp_server.h b/src/tcp_server.h index 5314fa4..4499485 100644 --- a/src/tcp_server.h +++ b/src/tcp_server.h @@ -47,6 +47,7 @@ public: [[nodiscard]] virtual int external_listen_port() const { return m_listenPort; } [[nodiscard]] bool connect_to_peer(bool is_v6, const raw_ip& ip, int port); + [[nodiscard]] bool connect_to_peer(const std::string& domain, int port); [[nodiscard]] bool connect_to_peer(Client* client); virtual void on_connect_failed(bool /*is_v6*/, const raw_ip& /*ip*/, int /*port*/) {} @@ -80,6 +81,8 @@ public: void asan_poison_this() const; + FORCEINLINE bool isV6() const { return m_addressType == AddressType::IPv6; } + char* m_readBuf; uint32_t m_readBufSize; @@ -91,7 +94,13 @@ public: uv_tcp_t m_socket; - bool m_isV6; + enum class AddressType { + IPv4, + IPv6, + DomainName + }; + + AddressType m_addressType; bool m_isIncoming; bool m_readBufInUse; bool m_isClosing;