From 48c8c93ec4791002e473e4ea7af2bea9d693be0a Mon Sep 17 00:00:00 2001 From: Shauren Date: Wed, 9 Apr 2025 21:02:31 +0200 Subject: Core/Network: Move to separate project (cherry picked from commit 71b681bbf0f5189cd87a6cea66ef51667223f54a) --- src/common/Asio/AsioHacksFwd.h | 6 +- src/common/Asio/IpAddress.h | 36 -- src/common/Asio/IpNetwork.cpp | 284 ---------------- src/common/Asio/IpNetwork.h | 39 --- src/common/Asio/Resolver.h | 69 ---- src/common/CMakeLists.txt | 14 +- src/common/Define.h | 6 + src/common/IPLocation/IPLocation.cpp | 150 --------- src/common/IPLocation/IPLocation.h | 58 ---- src/common/Utilities/Util.cpp | 16 +- src/common/Utilities/Util.h | 2 - src/common/network/AsyncAcceptor.h | 137 ++++++++ src/common/network/CMakeLists.txt | 53 +++ .../SocketConnectionInitializer.h | 51 +++ src/common/network/Http/BaseHttpSocket.cpp | 146 +++++++++ src/common/network/Http/BaseHttpSocket.h | 228 +++++++++++++ src/common/network/Http/HttpCommon.h | 55 ++++ src/common/network/Http/HttpService.cpp | 267 +++++++++++++++ src/common/network/Http/HttpService.h | 188 +++++++++++ src/common/network/Http/HttpSessionState.h | 35 ++ src/common/network/Http/HttpSocket.h | 53 +++ src/common/network/Http/HttpSslSocket.h | 58 ++++ src/common/network/IpAddress.h | 33 ++ src/common/network/IpNetwork.cpp | 284 ++++++++++++++++ src/common/network/IpNetwork.h | 39 +++ src/common/network/NetworkThread.h | 179 ++++++++++ src/common/network/Resolver.cpp | 47 +++ src/common/network/Resolver.h | 47 +++ src/common/network/Socket.h | 362 +++++++++++++++++++++ src/common/network/SocketMgr.h | 146 +++++++++ src/common/network/SslStream.h | 131 ++++++++ src/server/bnetserver/REST/LoginHttpSession.cpp | 18 +- src/server/bnetserver/REST/LoginHttpSession.h | 8 +- src/server/bnetserver/REST/LoginRESTService.cpp | 2 +- src/server/scripts/Commands/cs_ban.cpp | 29 +- src/server/shared/CMakeLists.txt | 1 + src/server/shared/IpLocation/IPLocation.cpp | 150 +++++++++ src/server/shared/IpLocation/IPLocation.h | 58 ++++ src/server/shared/Networking/AsyncAcceptor.h | 137 -------- .../IpBanCheckConnectionInitializer.h | 2 +- .../SocketConnectionInitializer.h | 51 --- .../shared/Networking/Http/BaseHttpSocket.cpp | 146 --------- src/server/shared/Networking/Http/BaseHttpSocket.h | 245 -------------- src/server/shared/Networking/Http/HttpCommon.h | 55 ---- src/server/shared/Networking/Http/HttpService.cpp | 267 --------------- src/server/shared/Networking/Http/HttpService.h | 188 ----------- .../shared/Networking/Http/HttpSessionState.h | 35 -- src/server/shared/Networking/Http/HttpSocket.h | 53 --- src/server/shared/Networking/Http/HttpSslSocket.h | 58 ---- src/server/shared/Networking/NetworkThread.h | 179 ---------- src/server/shared/Networking/Socket.h | 362 --------------------- src/server/shared/Networking/SocketMgr.h | 146 --------- src/server/shared/Networking/SslStream.h | 131 -------- src/server/shared/Realm/RealmList.cpp | 2 +- src/server/shared/Realm/RealmList.h | 2 +- src/tools/extractor_common/CMakeLists.txt | 3 +- src/tools/extractor_common/CascHandles.cpp | 2 +- 57 files changed, 2819 insertions(+), 2730 deletions(-) delete mode 100644 src/common/Asio/IpAddress.h delete mode 100644 src/common/Asio/IpNetwork.cpp delete mode 100644 src/common/Asio/IpNetwork.h delete mode 100644 src/common/Asio/Resolver.h delete mode 100644 src/common/IPLocation/IPLocation.cpp delete mode 100644 src/common/IPLocation/IPLocation.h create mode 100644 src/common/network/AsyncAcceptor.h create mode 100644 src/common/network/CMakeLists.txt create mode 100644 src/common/network/ConnectionInitializers/SocketConnectionInitializer.h create mode 100644 src/common/network/Http/BaseHttpSocket.cpp create mode 100644 src/common/network/Http/BaseHttpSocket.h create mode 100644 src/common/network/Http/HttpCommon.h create mode 100644 src/common/network/Http/HttpService.cpp create mode 100644 src/common/network/Http/HttpService.h create mode 100644 src/common/network/Http/HttpSessionState.h create mode 100644 src/common/network/Http/HttpSocket.h create mode 100644 src/common/network/Http/HttpSslSocket.h create mode 100644 src/common/network/IpAddress.h create mode 100644 src/common/network/IpNetwork.cpp create mode 100644 src/common/network/IpNetwork.h create mode 100644 src/common/network/NetworkThread.h create mode 100644 src/common/network/Resolver.cpp create mode 100644 src/common/network/Resolver.h create mode 100644 src/common/network/Socket.h create mode 100644 src/common/network/SocketMgr.h create mode 100644 src/common/network/SslStream.h create mode 100644 src/server/shared/IpLocation/IPLocation.cpp create mode 100644 src/server/shared/IpLocation/IPLocation.h delete mode 100644 src/server/shared/Networking/AsyncAcceptor.h delete mode 100644 src/server/shared/Networking/ConnectionInitializers/SocketConnectionInitializer.h delete mode 100644 src/server/shared/Networking/Http/BaseHttpSocket.cpp delete mode 100644 src/server/shared/Networking/Http/BaseHttpSocket.h delete mode 100644 src/server/shared/Networking/Http/HttpCommon.h delete mode 100644 src/server/shared/Networking/Http/HttpService.cpp delete mode 100644 src/server/shared/Networking/Http/HttpService.h delete mode 100644 src/server/shared/Networking/Http/HttpSessionState.h delete mode 100644 src/server/shared/Networking/Http/HttpSocket.h delete mode 100644 src/server/shared/Networking/Http/HttpSslSocket.h delete mode 100644 src/server/shared/Networking/NetworkThread.h delete mode 100644 src/server/shared/Networking/Socket.h delete mode 100644 src/server/shared/Networking/SocketMgr.h delete mode 100644 src/server/shared/Networking/SslStream.h (limited to 'src') diff --git a/src/common/Asio/AsioHacksFwd.h b/src/common/Asio/AsioHacksFwd.h index a8f04b16d81..06f5e531c20 100644 --- a/src/common/Asio/AsioHacksFwd.h +++ b/src/common/Asio/AsioHacksFwd.h @@ -60,9 +60,13 @@ namespace Trinity { class DeadlineTimer; class IoContext; - class Resolver; class Strand; } + + namespace Net + { + class Resolver; + } } #endif // AsioHacksFwd_h__ diff --git a/src/common/Asio/IpAddress.h b/src/common/Asio/IpAddress.h deleted file mode 100644 index 7d85b0028ac..00000000000 --- a/src/common/Asio/IpAddress.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef IpAddress_h__ -#define IpAddress_h__ - -#include "Define.h" -#include - -namespace Trinity -{ - namespace Net - { - using boost::asio::ip::make_address; - using boost::asio::ip::make_address_v4; - using boost::asio::ip::make_address_v6; - using boost::asio::ip::v4_mapped_t::v4_mapped; - inline uint32 address_to_uint(boost::asio::ip::address_v4 const& address) { return address.to_uint(); } - } -} - -#endif // IpAddress_h__ diff --git a/src/common/Asio/IpNetwork.cpp b/src/common/Asio/IpNetwork.cpp deleted file mode 100644 index 85f176d21e4..00000000000 --- a/src/common/Asio/IpNetwork.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#include "IpNetwork.h" -#include "IpAddress.h" -#include -#include -#include - -namespace -{ -std::vector LocalV4Networks; -std::vector LocalV6Networks; -} - -namespace Trinity::Net -{ -bool IsInLocalNetwork(boost::asio::ip::address const& clientAddress) -{ - if (clientAddress.is_v4()) - { - return std::any_of(LocalV4Networks.begin(), LocalV4Networks.end(), [clientAddressV4 = clientAddress.to_v4()](boost::asio::ip::network_v4 const& network) - { - return IsInNetwork(network, clientAddressV4); - }); - } - - if (clientAddress.is_v6()) - { - return std::any_of(LocalV6Networks.begin(), LocalV6Networks.end(), [clientAddressV6 = clientAddress.to_v6()](boost::asio::ip::network_v6 const& network) - { - return IsInNetwork(network, clientAddressV6); - }); - } - - return false; -}; - -bool IsInNetwork(boost::asio::ip::network_v4 const& network, boost::asio::ip::address_v4 const& clientAddress) -{ - if (clientAddress == network.address()) - return true; - - boost::asio::ip::network_v4 endpointAsNetwork = boost::asio::ip::make_network_v4(clientAddress, 32); - return endpointAsNetwork.is_subnet_of(network); -} - -bool IsInNetwork(boost::asio::ip::network_v6 const& network, boost::asio::ip::address_v6 const& clientAddress) -{ - if (clientAddress == network.address()) - return true; - - boost::asio::ip::network_v6 endpointAsNetwork = boost::asio::ip::make_network_v6(clientAddress, 128); - return endpointAsNetwork.is_subnet_of(network); -} - -Optional SelectAddressForClient(boost::asio::ip::address const& clientAddress, std::span const& addresses) -{ - Optional localIpv6Index; - Optional externalIpv6Index; - Optional loopbackIpv6Index; - Optional localIpv4Index; - Optional externalIpv4Index; - Optional loopbackIpv4Index; - - for (std::size_t i = 0; i < addresses.size(); ++i) - { - boost::asio::ip::address const& address = addresses[i]; - - if (address.is_loopback()) - { - if (address.is_v6() && !loopbackIpv6Index) - loopbackIpv6Index = i; - - if (address.is_v4() && !loopbackIpv4Index) - loopbackIpv4Index = i; - } - else if (IsInLocalNetwork(address)) - { - if (address.is_v6() && !localIpv6Index) - localIpv6Index = i; - - if (address.is_v4() && !localIpv4Index) - localIpv4Index = i; - } - else - { - if (address.is_v6() && !externalIpv6Index) - externalIpv6Index = i; - - if (address.is_v4() && !externalIpv4Index) - externalIpv4Index = i; - } - } - - if (IsInLocalNetwork(clientAddress) || clientAddress.is_loopback()) - { - // client is in the same network as this process, prefer local addresses - - // first, try finding a local ipv6 address - if (clientAddress.is_v6() && localIpv6Index) - { - // we have a usable ipv6 local address - return localIpv6Index; - } - - // we dont have a local v6, return local v4 - if (localIpv4Index) - return localIpv4Index; - } - - if (clientAddress.is_loopback()) - { - // fallback, search for a loopback address in configuration - if (clientAddress.is_v6() && loopbackIpv6Index) - return loopbackIpv6Index; - - if (loopbackIpv4Index) - return loopbackIpv4Index; - } - - // client is NOT in the same network as this process - if (clientAddress.is_v6() && externalIpv6Index) - return externalIpv6Index; - - return externalIpv4Index; -} -} - -#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS - -#include -#include - -void Trinity::Net::ScanLocalNetworks() -{ - LocalV4Networks.clear(); - LocalV6Networks.clear(); - - boost::system::error_code dllError; - boost::dll::shared_library iphlp("Iphlpapi.dll", dllError, boost::dll::load_mode::search_system_folders); - if (dllError || !iphlp.is_loaded()) - return; - - auto getAdaptersAddresses = iphlp.get("GetAdaptersAddresses"); - if (!getAdaptersAddresses) - return; - - ULONG queryFlags = GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_FRIENDLY_NAME; - ULONG bufferSize = 0; - - if (getAdaptersAddresses(AF_UNSPEC, queryFlags, nullptr, nullptr, &bufferSize) != ERROR_BUFFER_OVERFLOW) - return; - - std::unique_ptr addressesBuffer = std::make_unique(bufferSize); - if (getAdaptersAddresses(AF_UNSPEC, queryFlags, nullptr, reinterpret_cast(addressesBuffer.get()), &bufferSize) != ERROR_SUCCESS) - return; - - for (IP_ADAPTER_ADDRESSES* itr = reinterpret_cast(addressesBuffer.get()); itr; itr = itr->Next) - { - if (itr->IfType == IF_TYPE_SOFTWARE_LOOPBACK) - continue; - - if (itr->OperStatus != IfOperStatusUp) - continue; - - for (IP_ADAPTER_PREFIX_XP* prefix = itr->FirstPrefix; prefix; prefix = prefix->Next) - { - switch (prefix->Address.lpSockaddr->sa_family) - { - case AF_INET: - { - SOCKADDR_IN* ipv4raw = reinterpret_cast(prefix->Address.lpSockaddr); - boost::asio::ip::address_v4::bytes_type addressBytes; - std::memcpy(addressBytes.data(), &ipv4raw->sin_addr.s_addr, addressBytes.size()); - boost::asio::ip::address_v4 address = make_address_v4(addressBytes); - if (address.is_unspecified() || address.is_multicast() || address == boost::asio::ip::address_v4::broadcast()) - continue; - - LocalV4Networks.push_back(boost::asio::ip::make_network_v4(address, prefix->PrefixLength)); - break; - } - case AF_INET6: - { - SOCKADDR_IN6* ipv6raw = reinterpret_cast(prefix->Address.lpSockaddr); - boost::asio::ip::address_v6::bytes_type addressBytes; - std::memcpy(addressBytes.data(), ipv6raw->sin6_addr.s6_addr, addressBytes.size()); - boost::asio::ip::address_v6 address = make_address_v6(addressBytes, ipv6raw->sin6_scope_id); - if (address.is_unspecified() || address.is_multicast()) - continue; - - LocalV6Networks.push_back(boost::asio::ip::make_network_v6(address, prefix->PrefixLength)); - break; - } - default: - break; - } - } - } -} - -#else - -#include -#include - -void Trinity::Net::ScanLocalNetworks() -{ - LocalV4Networks.clear(); - LocalV6Networks.clear(); - - ifaddrs* addressesLinkedList = nullptr; - if (getifaddrs(&addressesLinkedList) == -1) - return; - - for (ifaddrs* itr = addressesLinkedList; itr; itr = itr->ifa_next) - { - if (!itr->ifa_addr) - continue; - - switch (itr->ifa_addr->sa_family) - { - case AF_INET: - { - sockaddr_in* ipv4raw = reinterpret_cast(itr->ifa_addr); - boost::asio::ip::address_v4::bytes_type addressBytes; - std::memcpy(addressBytes.data(), &ipv4raw->sin_addr.s_addr, addressBytes.size()); - boost::asio::ip::address_v4 address = make_address_v4(addressBytes); - if (address.is_unspecified() || address.is_loopback() || address.is_multicast() || address == boost::asio::ip::address_v4::broadcast()) - continue; - - if (sockaddr_in* netmask4raw = reinterpret_cast(itr->ifa_netmask)) - { - boost::asio::ip::address_v4::bytes_type netmaskBytes; - std::memcpy(netmaskBytes.data(), &netmask4raw->sin_addr.s_addr, netmaskBytes.size()); - boost::asio::ip::address_v4 netmask = make_address_v4(netmaskBytes); - LocalV4Networks.push_back(boost::asio::ip::make_network_v4(address, netmask)); - } - else - LocalV4Networks.push_back(boost::asio::ip::make_network_v4(address, 32)); - break; - } - case AF_INET6: - { - sockaddr_in6* ipv6raw = reinterpret_cast(itr->ifa_addr); - boost::asio::ip::address_v6::bytes_type addressBytes; - std::memcpy(addressBytes.data(), ipv6raw->sin6_addr.s6_addr, addressBytes.size()); - boost::asio::ip::address_v6 address = make_address_v6(addressBytes, ipv6raw->sin6_scope_id); - if (address.is_unspecified() || address.is_loopback() || address.is_multicast()) - continue; - - if (sockaddr_in6* netmask6raw = reinterpret_cast(itr->ifa_netmask)) - { - int32 prefixLength = 0; - for (uint8 addressByte : netmask6raw->sin6_addr.s6_addr) - prefixLength += std::countl_one(addressByte); - - LocalV6Networks.push_back(boost::asio::ip::make_network_v6(address, prefixLength)); - } - else - LocalV6Networks.push_back(boost::asio::ip::make_network_v6(address, 128)); - break; - } - } - } - - freeifaddrs(addressesLinkedList); -} - -#endif diff --git a/src/common/Asio/IpNetwork.h b/src/common/Asio/IpNetwork.h deleted file mode 100644 index 08db7d49f84..00000000000 --- a/src/common/Asio/IpNetwork.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef IpNetwork_h__ -#define IpNetwork_h__ - -#include "AsioHacksFwd.h" -#include "Define.h" -#include "Optional.h" -#include - -namespace Trinity::Net -{ -TC_COMMON_API bool IsInLocalNetwork(boost::asio::ip::address const& clientAddress); - -TC_COMMON_API bool IsInNetwork(boost::asio::ip::network_v4 const& network, boost::asio::ip::address_v4 const& clientAddress); - -TC_COMMON_API bool IsInNetwork(boost::asio::ip::network_v6 const& network, boost::asio::ip::address_v6 const& clientAddress); - -TC_COMMON_API Optional SelectAddressForClient(boost::asio::ip::address const& clientAddress, std::span const& addresses); - -TC_COMMON_API void ScanLocalNetworks(); -} - -#endif // IpNetwork_h__ diff --git a/src/common/Asio/Resolver.h b/src/common/Asio/Resolver.h deleted file mode 100644 index 84dedd21bfa..00000000000 --- a/src/common/Asio/Resolver.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef Resolver_h__ -#define Resolver_h__ - -#include "IoContext.h" -#include "Optional.h" -#include -#include -#include -#include - -namespace Trinity -{ - namespace Asio - { - /** - Hack to make it possible to forward declare resolver (one of its template arguments is a typedef to something super long and using nested classes) - */ - class Resolver - { - public: - explicit Resolver(IoContext& ioContext) : _impl(ioContext) { } - - Optional Resolve(boost::asio::ip::tcp const& protocol, std::string_view host, std::string_view service) - { - boost::system::error_code ec; - boost::asio::ip::resolver_base::flags flagsResolver = boost::asio::ip::resolver_base::all_matching; - boost::asio::ip::tcp::resolver::results_type results = _impl.resolve(protocol, host, service, flagsResolver, ec); - if (results.begin() == results.end() || ec) - return {}; - - return results.begin()->endpoint(); - } - - std::vector ResolveAll(std::string_view host, std::string_view service) - { - boost::system::error_code ec; - boost::asio::ip::resolver_base::flags flagsResolver = boost::asio::ip::resolver_base::all_matching; - boost::asio::ip::tcp::resolver::results_type results = _impl.resolve(host, service, flagsResolver, ec); - std::vector result; - if (!ec) - std::ranges::transform(results, std::back_inserter(result), [](boost::asio::ip::tcp::resolver::results_type::value_type const& entry) { return entry.endpoint(); }); - - return result; - } - - private: - boost::asio::ip::tcp::resolver _impl; - }; - } -} - -#endif // Resolver_h__ diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index b202dc5f9a4..8b8ecc0f471 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -14,7 +14,8 @@ CollectSourceFiles( # Exclude ${CMAKE_CURRENT_SOURCE_DIR}/Debugging/Windows ${CMAKE_CURRENT_SOURCE_DIR}/Platform - ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders) + ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders + ${CMAKE_CURRENT_SOURCE_DIR}/network) if(WIN32) CollectSourceFiles( @@ -27,6 +28,8 @@ if(WIN32) WINDOWS_PLATFORM_SOURCES) list(APPEND PRIVATE_SOURCES ${WINDOWS_PLATFORM_SOURCES}) + unset(WINDOWS_DEBUGGING_SOURCES) + unset(WINDOWS_PLATFORM_SOURCES) endif() if(USE_COREPCH) @@ -43,7 +46,8 @@ CollectIncludeDirectories( ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC_INCLUDES # Exclude - ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders) + ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders + ${CMAKE_CURRENT_SOURCE_DIR}/network) target_include_directories(common PUBLIC @@ -95,3 +99,9 @@ endif() if(USE_COREPCH) add_cxx_pch(common ${PRIVATE_PCH_HEADER}) endif() + +unset(PRIVATE_SOURCES) +unset(PRIVATE_PCH_HEADER) +unset(PUBLIC_INCLUDES) + +add_subdirectory(network) diff --git a/src/common/Define.h b/src/common/Define.h index e4a2333c66d..f918db84314 100644 --- a/src/common/Define.h +++ b/src/common/Define.h @@ -111,6 +111,12 @@ # define TC_DATABASE_API TC_API_IMPORT #endif +#ifdef TRINITY_API_EXPORT_NETWORK +# define TC_NETWORK_API TC_API_EXPORT +#else +# define TC_NETWORK_API TC_API_IMPORT +#endif + #ifdef TRINITY_API_EXPORT_SHARED # define TC_SHARED_API TC_API_EXPORT #else diff --git a/src/common/IPLocation/IPLocation.cpp b/src/common/IPLocation/IPLocation.cpp deleted file mode 100644 index 72383bd3554..00000000000 --- a/src/common/IPLocation/IPLocation.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#include "IPLocation.h" -#include "BigNumber.h" -#include "Config.h" -#include "Errors.h" -#include "IpAddress.h" -#include "Log.h" -#include "Util.h" -#include -#include - -IpLocationStore::IpLocationStore() = default; -IpLocationStore::~IpLocationStore() = default; - -void IpLocationStore::Load() -{ - _ipLocationStore.clear(); - TC_LOG_INFO("server.loading", "Loading IP Location Database..."); - - std::string databaseFilePath = sConfigMgr->GetStringDefault("IPLocationFile", ""); - if (databaseFilePath.empty()) - return; - - // Check if file exists - std::ifstream databaseFile(databaseFilePath); - if (!databaseFile) - { - TC_LOG_ERROR("server.loading", "IPLocation: No ip database file exists ({}).", databaseFilePath); - return; - } - - if (!databaseFile.is_open()) - { - TC_LOG_ERROR("server.loading", "IPLocation: Ip database file ({}) can not be opened.", databaseFilePath); - return; - } - - std::string ipFrom; - std::string ipTo; - std::string countryCode; - std::string countryName; - BigNumber bnParser; - BigNumber ipv4Max(0xFFFFFFFF); - BigNumber ipv6MappedMask(0xFFFF); - ipv6MappedMask <<= 32; - - auto parseStringToIPv6 = [&](std::string const& str) -> Optional> - { - bnParser.SetDecStr(str); - if (!bnParser.SetDecStr(str)) - return {}; - // convert ipv4 to ipv6 v4 mapped value - if (bnParser <= ipv4Max) - bnParser += ipv6MappedMask; - return bnParser.ToByteArray<16>(false); - }; - - while (databaseFile.good()) - { - // Read lines - if (!std::getline(databaseFile, ipFrom, ',')) - break; - if (!std::getline(databaseFile, ipTo, ',')) - break; - if (!std::getline(databaseFile, countryCode, ',')) - break; - if (!std::getline(databaseFile, countryName, '\n')) - break; - - // Remove new lines and return - std::erase_if(countryName, [](char c) { return c == '\r' || c == '\n'; }); - - // Remove quotation marks - std::erase(ipFrom, '"'); - std::erase(ipTo, '"'); - std::erase(countryCode, '"'); - std::erase(countryName, '"'); - - if (countryCode == "-") - continue; - - // Convert country code to lowercase - strToLower(countryCode); - - Optional> from = parseStringToIPv6(ipFrom); - if (!from) - continue; - - Optional> to = parseStringToIPv6(ipTo); - if (!to) - continue; - - _ipLocationStore.emplace_back(*from, *to, std::move(countryCode), std::move(countryName)); - } - - std::ranges::sort(_ipLocationStore, {}, &IpLocationRecord::IpFrom); - ASSERT(std::ranges::is_sorted(_ipLocationStore, [](IpLocationRecord const& a, IpLocationRecord const& b) { return a.IpFrom < b.IpTo; }), - "Overlapping IP ranges detected in database file"); - - databaseFile.close(); - - TC_LOG_INFO("server.loading", ">> Loaded {} ip location entries.", _ipLocationStore.size()); -} - -IpLocationRecord const* IpLocationStore::GetLocationRecord(std::string const& ipAddress) const -{ - boost::system::error_code error; - boost::asio::ip::address address = Trinity::Net::make_address(ipAddress, error); - if (error) - return nullptr; - - std::array bytes = [&]() -> std::array - { - if (address.is_v6()) - return address.to_v6().to_bytes(); - if (address.is_v4()) - return Trinity::Net::make_address_v6(Trinity::Net::v4_mapped, address.to_v4()).to_bytes(); - return {}; - }(); - auto itr = std::ranges::upper_bound(_ipLocationStore, bytes, {}, &IpLocationRecord::IpTo); - if (itr == _ipLocationStore.end()) - return nullptr; - - if (bytes < itr->IpFrom) - return nullptr; - - return &(*itr); -} - -IpLocationStore* IpLocationStore::Instance() -{ - static IpLocationStore instance; - return &instance; -} diff --git a/src/common/IPLocation/IPLocation.h b/src/common/IPLocation/IPLocation.h deleted file mode 100644 index 5471948586b..00000000000 --- a/src/common/IPLocation/IPLocation.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef IPLOCATION_H -#define IPLOCATION_H - -#include "Define.h" -#include -#include -#include - -struct IpLocationRecord -{ - IpLocationRecord() : IpFrom(), IpTo() { } - IpLocationRecord(std::array ipFrom, std::array ipTo, std::string&& countryCode, std::string&& countryName) - : IpFrom(ipFrom), IpTo(ipTo), CountryCode(std::move(countryCode)), CountryName(std::move(countryName)) { } - - std::array IpFrom; - std::array IpTo; - std::string CountryCode; - std::string CountryName; -}; - -class TC_COMMON_API IpLocationStore -{ - public: - IpLocationStore(); - IpLocationStore(IpLocationStore const&) = delete; - IpLocationStore(IpLocationStore&&) = delete; - IpLocationStore& operator=(IpLocationStore const&) = delete; - IpLocationStore& operator=(IpLocationStore&&) = delete; - ~IpLocationStore(); - static IpLocationStore* Instance(); - - void Load(); - IpLocationRecord const* GetLocationRecord(std::string const& ipAddress) const; - - private: - std::vector _ipLocationStore; -}; - -#define sIPLocation IpLocationStore::Instance() - -#endif diff --git a/src/common/Utilities/Util.cpp b/src/common/Utilities/Util.cpp index 93bc3b853a5..c374bff9cf1 100644 --- a/src/common/Utilities/Util.cpp +++ b/src/common/Utilities/Util.cpp @@ -18,7 +18,6 @@ #include "Util.h" #include "Common.h" #include "Containers.h" -#include "IpAddress.h" #include "StringConvert.h" #include "StringFormat.h" #include @@ -29,6 +28,10 @@ #include #include +#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS +#include +#endif + void Trinity::VerifyOsVersion() { #if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS @@ -270,17 +273,6 @@ std::string TimeToHumanReadable(time_t t) return std::string(buf); } -/// Check if the string is a valid ip address representation -bool IsIPAddress(char const* ipaddress) -{ - if (!ipaddress) - return false; - - boost::system::error_code error; - Trinity::Net::make_address(ipaddress, error); - return !error; -} - /// create PID file uint32 CreatePIDFile(std::string const& filename) { diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h index c85fd858806..4907b4edbae 100644 --- a/src/common/Utilities/Util.h +++ b/src/common/Utilities/Util.h @@ -390,8 +390,6 @@ TC_COMMON_API bool WriteWinConsole(std::string_view str, bool error = false); TC_COMMON_API Optional RemoveCRLF(std::string& str); -TC_COMMON_API bool IsIPAddress(char const* ipaddress); - TC_COMMON_API uint32 CreatePIDFile(std::string const& filename); TC_COMMON_API uint32 GetPID(); diff --git a/src/common/network/AsyncAcceptor.h b/src/common/network/AsyncAcceptor.h new file mode 100644 index 00000000000..dd0857c2b38 --- /dev/null +++ b/src/common/network/AsyncAcceptor.h @@ -0,0 +1,137 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_ASYNC_ACCEPTOR_H +#define TRINITYCORE_ASYNC_ACCEPTOR_H + +#include "IoContext.h" +#include "IpAddress.h" +#include "Log.h" +#include "Socket.h" +#include +#include +#include +#include + +#define TRINITY_MAX_LISTEN_CONNECTIONS boost::asio::socket_base::max_listen_connections + +namespace Trinity::Net +{ +template +concept AcceptCallback = std::invocable; + +class AsyncAcceptor +{ +public: + AsyncAcceptor(Asio::IoContext& ioContext, std::string const& bindIp, uint16 port) : + _acceptor(ioContext), _endpoint(make_address(bindIp), port), + _socket(ioContext), _closed(false), _socketFactory([this] { return DefeaultSocketFactory(); }) + { + } + + template + void AsyncAccept(Callback&& acceptCallback) + { + auto [tmpSocket, tmpThreadIndex] = _socketFactory(); + // TODO: get rid of temporary variables (clang 15 cannot handle variables from structured bindings as lambda captures) + IoContextTcpSocket* socket = tmpSocket; + uint32 threadIndex = tmpThreadIndex; + _acceptor.async_accept(*socket, [this, socket, threadIndex, acceptCallback = std::forward(acceptCallback)](boost::system::error_code const& error) mutable + { + if (!error) + { + try + { + socket->non_blocking(true); + + acceptCallback(std::move(*socket), threadIndex); + } + catch (boost::system::system_error const& err) + { + TC_LOG_INFO("network", "Failed to initialize client's socket {}", err.what()); + } + } + + if (!_closed) + this->AsyncAccept(std::move(acceptCallback)); + }); + } + + bool Bind() + { + boost::system::error_code errorCode; + _acceptor.open(_endpoint.protocol(), errorCode); + if (errorCode) + { + TC_LOG_INFO("network", "Failed to open acceptor {}", errorCode.message()); + return false; + } + +#if TRINITY_PLATFORM != TRINITY_PLATFORM_WINDOWS + _acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), errorCode); + if (errorCode) + { + TC_LOG_INFO("network", "Failed to set reuse_address option on acceptor {}", errorCode.message()); + return false; + } +#endif + + // v6_only is enabled on some *BSD distributions by default + // we want to allow both v4 and v6 connections to the same listener + if (_endpoint.protocol() == boost::asio::ip::tcp::v6()) + _acceptor.set_option(boost::asio::ip::v6_only(false)); + + _acceptor.bind(_endpoint, errorCode); + if (errorCode) + { + TC_LOG_INFO("network", "Could not bind to {}:{} {}", _endpoint.address().to_string(), _endpoint.port(), errorCode.message()); + return false; + } + + _acceptor.listen(TRINITY_MAX_LISTEN_CONNECTIONS, errorCode); + if (errorCode) + { + TC_LOG_INFO("network", "Failed to start listening on {}:{} {}", _endpoint.address().to_string(), _endpoint.port(), errorCode.message()); + return false; + } + + return true; + } + + void Close() + { + if (_closed.exchange(true)) + return; + + boost::system::error_code err; + _acceptor.close(err); + } + + void SetSocketFactory(std::function()> func) { _socketFactory = std::move(func); } + +private: + std::pair DefeaultSocketFactory() { return std::make_pair(&_socket, 0); } + + boost::asio::basic_socket_acceptor _acceptor; + boost::asio::ip::tcp::endpoint _endpoint; + IoContextTcpSocket _socket; + std::atomic _closed; + std::function()> _socketFactory; +}; +} + +#endif // TRINITYCORE_ASYNC_ACCEPTOR_H diff --git a/src/common/network/CMakeLists.txt b/src/common/network/CMakeLists.txt new file mode 100644 index 00000000000..70408faed51 --- /dev/null +++ b/src/common/network/CMakeLists.txt @@ -0,0 +1,53 @@ +# This file is part of the TrinityCore Project. See AUTHORS file for Copyright information +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +CollectSourceFiles( + ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE_SOURCES) + +GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(network + ${PRIVATE_SOURCES}) + +CollectIncludeDirectories( + ${CMAKE_CURRENT_SOURCE_DIR} + PUBLIC_INCLUDES) + +target_include_directories(network + PUBLIC + ${PUBLIC_INCLUDES} + PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}) + +target_link_libraries(network + PRIVATE + trinity-core-interface + PUBLIC + common) + +set_target_properties(network + PROPERTIES + COMPILE_WARNING_AS_ERROR ${WITH_WARNINGS_AS_ERRORS} + DEFINE_SYMBOL TRINITY_API_EXPORT_NETWORK + FOLDER "server" + OUTPUT_NAME trinity_network) + +if(BUILD_SHARED_LIBS) + if(UNIX) + install(TARGETS network + LIBRARY + DESTINATION lib) + elseif(WIN32) + install(TARGETS network + RUNTIME + DESTINATION "${CMAKE_INSTALL_PREFIX}") + endif() +endif() diff --git a/src/common/network/ConnectionInitializers/SocketConnectionInitializer.h b/src/common/network/ConnectionInitializers/SocketConnectionInitializer.h new file mode 100644 index 00000000000..d3f0bb16dbf --- /dev/null +++ b/src/common/network/ConnectionInitializers/SocketConnectionInitializer.h @@ -0,0 +1,51 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_SOCKET_CONNECTION_INITIALIZER_H +#define TRINITYCORE_SOCKET_CONNECTION_INITIALIZER_H + +#include +#include + +namespace Trinity::Net +{ +struct SocketConnectionInitializer : public std::enable_shared_from_this +{ + SocketConnectionInitializer() = default; + + SocketConnectionInitializer(SocketConnectionInitializer const&) = delete; + SocketConnectionInitializer(SocketConnectionInitializer&&) noexcept = default; + SocketConnectionInitializer& operator=(SocketConnectionInitializer const&) = delete; + SocketConnectionInitializer& operator=(SocketConnectionInitializer&&) noexcept = default; + + virtual ~SocketConnectionInitializer() = default; + + virtual void Start() = 0; + + std::shared_ptr next; + + static std::shared_ptr& SetupChain(std::span> initializers) + { + for (std::size_t i = initializers.size(); i > 1; --i) + initializers[i - 2]->next.swap(initializers[i - 1]); + + return initializers[0]; + } +}; +} + +#endif // TRINITYCORE_SOCKET_CONNECTION_INITIALIZER_H diff --git a/src/common/network/Http/BaseHttpSocket.cpp b/src/common/network/Http/BaseHttpSocket.cpp new file mode 100644 index 00000000000..33053399c14 --- /dev/null +++ b/src/common/network/Http/BaseHttpSocket.cpp @@ -0,0 +1,146 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "BaseHttpSocket.h" +#include +#include + +namespace Trinity::Net::Http +{ +using RequestSerializer = boost::beast::http::request_serializer; +using ResponseSerializer = boost::beast::http::response_serializer; + +bool AbstractSocket::ParseRequest(MessageBuffer& packet, RequestParser& parser) +{ + if (!parser.is_done()) + { + // need more data in the payload + boost::system::error_code ec = {}; + std::size_t readDataSize = parser.put(boost::asio::const_buffer(packet.GetReadPointer(), packet.GetActiveSize()), ec); + packet.ReadCompleted(readDataSize); + } + + return parser.is_done(); +} + +std::string AbstractSocket::SerializeRequest(Request const& request) +{ + RequestSerializer serializer(request); + + std::string buffer; + while (!serializer.is_done()) + { + size_t totalBytes = 0; + boost::system::error_code ec = {}; + serializer.next(ec, [&](boost::system::error_code const&, ConstBufferSequence const& buffers) + { + size_t totalBytesInBuffers = boost::asio::buffer_size(buffers); + + buffer.reserve(buffer.size() + totalBytes); + + auto begin = boost::asio::buffers_begin(buffers); + auto end = boost::asio::buffers_end(buffers); + + std::copy(begin, end, std::back_inserter(buffer)); + totalBytes += totalBytesInBuffers; + }); + + serializer.consume(totalBytes); + } + + return buffer; +} + +MessageBuffer AbstractSocket::SerializeResponse(Request const& request, Response& response) +{ + response.prepare_payload(); + + ResponseSerializer serializer(response); + bool (*serializerIsDone)(ResponseSerializer&); + if (request.method() != boost::beast::http::verb::head) + { + serializerIsDone = [](ResponseSerializer& s) { return s.is_done(); }; + } + else + { + serializerIsDone = [](ResponseSerializer& s) { return s.is_header_done(); }; + serializer.split(true); + } + + MessageBuffer buffer; + while (!serializerIsDone(serializer)) + { + serializer.limit(buffer.GetRemainingSpace()); + + size_t totalBytes = 0; + boost::system::error_code ec = {}; + serializer.next(ec, [&](boost::system::error_code& currentError, ConstBufferSequence const& buffers) + { + size_t totalBytesInBuffers = boost::asio::buffer_size(buffers); + if (totalBytesInBuffers > buffer.GetRemainingSpace()) + { + currentError = boost::beast::http::error::need_more; + return; + } + + auto begin = boost::asio::buffers_begin(buffers); + auto end = boost::asio::buffers_end(buffers); + + std::copy(begin, end, buffer.GetWritePointer()); + buffer.WriteCompleted(totalBytesInBuffers); + totalBytes += totalBytesInBuffers; + }); + + serializer.consume(totalBytes); + + if (ec == boost::beast::http::error::need_more) + buffer.Resize(buffer.GetBufferSize() + 4096); + } + + return buffer; +} + +void AbstractSocket::LogRequestAndResponse(RequestContext const& context, MessageBuffer& buffer) const +{ + if (Logger const* logger = sLog->GetEnabledLogger("server.http", LOG_LEVEL_DEBUG)) + { + std::string clientInfo = GetClientInfo(); + sLog->OutMessageTo(logger, "server.http", LOG_LEVEL_DEBUG, "{} Request {} {} done, status {}", clientInfo, + ToStdStringView(context.request.method_string()), ToStdStringView(context.request.target()), context.response.result_int()); + if (sLog->ShouldLog("server.http", LOG_LEVEL_TRACE)) + { + sLog->OutMessageTo(logger, "server.http", LOG_LEVEL_TRACE, "{} Request: {}", clientInfo, + CanLogRequestContent(context) ? SerializeRequest(context.request) : ""); + sLog->OutMessageTo(logger, "server.http", LOG_LEVEL_TRACE, "{} Response: {}", clientInfo, + CanLogResponseContent(context) ? std::string_view(reinterpret_cast(buffer.GetBasePointer()), buffer.GetActiveSize()) : ""); + } + } +} + +std::string AbstractSocket::GetClientInfo(boost::asio::ip::address const& address, uint16 port, SessionState const* state) +{ + std::string info = StringFormat("[{}:{}", address.to_string(), port); + if (state) + { + info.append(", Session Id: "); + info.append(boost::uuids::to_string(state->Id)); + } + + info += ']'; + return info; +} +} diff --git a/src/common/network/Http/BaseHttpSocket.h b/src/common/network/Http/BaseHttpSocket.h new file mode 100644 index 00000000000..2d58b6d5aa1 --- /dev/null +++ b/src/common/network/Http/BaseHttpSocket.h @@ -0,0 +1,228 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_BASE_HTTP_SOCKET_H +#define TRINITYCORE_BASE_HTTP_SOCKET_H + +#include "AsyncCallbackProcessor.h" +#include "HttpCommon.h" +#include "HttpSessionState.h" +#include "Optional.h" +#include "Socket.h" +#include "SocketConnectionInitializer.h" +#include +#include +#include +#include + +namespace Trinity::Net::Http +{ +using IoContextHttpSocket = boost::beast::basic_stream; + +namespace Impl +{ +class BoostBeastSocketWrapper : public IoContextHttpSocket +{ +public: + using IoContextHttpSocket::basic_stream; + + void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError) + { + socket().shutdown(what, shutdownError); + } + + void close(boost::system::error_code& /*error*/) + { + IoContextHttpSocket::close(); + } + + template + void async_wait(boost::asio::socket_base::wait_type type, WaitHandlerType&& handler) + { + socket().async_wait(type, std::forward(handler)); + } + + IoContextTcpSocket::endpoint_type remote_endpoint() const + { + return socket().remote_endpoint(); + } +}; +} + +using RequestParser = boost::beast::http::request_parser; + +class TC_NETWORK_API AbstractSocket +{ +public: + AbstractSocket() = default; + AbstractSocket(AbstractSocket const& other) = default; + AbstractSocket(AbstractSocket&& other) = default; + AbstractSocket& operator=(AbstractSocket const& other) = default; + AbstractSocket& operator=(AbstractSocket&& other) = default; + virtual ~AbstractSocket() = default; + + static bool ParseRequest(MessageBuffer& packet, RequestParser& parser); + + static std::string SerializeRequest(Request const& request); + static MessageBuffer SerializeResponse(Request const& request, Response& response); + + virtual void SendResponse(RequestContext& context) = 0; + + void LogRequestAndResponse(RequestContext const& context, MessageBuffer& buffer) const; + + virtual std::string GetClientInfo() const = 0; + + static std::string GetClientInfo(boost::asio::ip::address const& address, uint16 port, SessionState const* state); + + virtual SessionState* GetSessionState() const = 0; + + Optional GetSessionId() const + { + if (SessionState* state = this->GetSessionState()) + return state->Id; + + return {}; + } + + virtual void Start() = 0; + + virtual bool Update() = 0; + + virtual boost::asio::ip::address const& GetRemoteIpAddress() const = 0; + + virtual bool IsOpen() const = 0; + + virtual void CloseSocket() = 0; +}; + +template +struct HttpConnectionInitializer final : SocketConnectionInitializer +{ + explicit HttpConnectionInitializer(SocketImpl* socket) : _socket(socket) { } + + void Start() override + { + _socket->ResetHttpParser(); + + if (this->next) + this->next->Start(); + } + +private: + SocketImpl* _socket; +}; + +template +class BaseSocket : public Trinity::Net::Socket, public AbstractSocket +{ + using Base = Trinity::Net::Socket; + +public: + using Base::Base; + + BaseSocket(BaseSocket const& other) = delete; + BaseSocket(BaseSocket&& other) = delete; + BaseSocket& operator=(BaseSocket const& other) = delete; + BaseSocket& operator=(BaseSocket&& other) = delete; + + ~BaseSocket() = default; + + SocketReadCallbackResult ReadHandler() final + { + MessageBuffer& packet = this->GetReadBuffer(); + while (packet.GetActiveSize() > 0) + { + if (!ParseRequest(packet, *_httpParser)) + { + // Couldn't receive the whole data this time. + break; + } + + if (!HandleMessage(_httpParser->get())) + { + this->CloseSocket(); + return SocketReadCallbackResult::Stop; + } + + this->ResetHttpParser(); + } + + return SocketReadCallbackResult::KeepReading; + } + + bool HandleMessage(Request& request) + { + RequestContext context { .request = std::move(request) }; + + if (!_state) + _state = this->ObtainSessionState(context); + + RequestHandlerResult status = this->RequestHandler(context); + + if (status != RequestHandlerResult::Async) + this->SendResponse(context); + + return status != RequestHandlerResult::Error; + } + + virtual RequestHandlerResult RequestHandler(RequestContext& context) = 0; + + void SendResponse(RequestContext& context) final + { + MessageBuffer buffer = SerializeResponse(context.request, context.response); + + this->LogRequestAndResponse(context, buffer); + + this->QueuePacket(std::move(buffer)); + + if (!context.response.keep_alive()) + this->DelayedCloseSocket(); + } + + void Start() override { return this->Base::Start(); } + + bool Update() override { return this->Base::Update(); } + + boost::asio::ip::address const& GetRemoteIpAddress() const final { return this->Base::GetRemoteIpAddress(); } + + bool IsOpen() const final { return this->Base::IsOpen(); } + + void CloseSocket() final { return this->Base::CloseSocket(); } + + std::string GetClientInfo() const override + { + return AbstractSocket::GetClientInfo(this->GetRemoteIpAddress(), this->GetRemotePort(), this->_state.get()); + } + + SessionState* GetSessionState() const override { return _state.get(); } + + void ResetHttpParser() + { + this->_httpParser.reset(); + this->_httpParser.emplace(); + this->_httpParser->eager(true); + } + +protected: + virtual std::shared_ptr ObtainSessionState(RequestContext& context) const = 0; + + Optional _httpParser; + std::shared_ptr _state; +}; +} + +#endif // TRINITYCORE_BASE_HTTP_SOCKET_H diff --git a/src/common/network/Http/HttpCommon.h b/src/common/network/Http/HttpCommon.h new file mode 100644 index 00000000000..274b59d7536 --- /dev/null +++ b/src/common/network/Http/HttpCommon.h @@ -0,0 +1,55 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_HTTP_COMMON_H +#define TRINITYCORE_HTTP_COMMON_H + +#include "Define.h" +#include +#include + +namespace Trinity::Net::Http +{ +using RequestBody = boost::beast::http::string_body; +using ResponseBody = boost::beast::http::string_body; + +using Request = boost::beast::http::request; +using Response = boost::beast::http::response; + +struct RequestContext +{ + Request request; + Response response; + struct RequestHandler const* handler = nullptr; +}; + +TC_NETWORK_API bool CanLogRequestContent(RequestContext const& context); +TC_NETWORK_API bool CanLogResponseContent(RequestContext const& context); + +inline std::string_view ToStdStringView(boost::beast::string_view bsw) +{ + return { bsw.data(), bsw.size() }; +} + +enum class RequestHandlerResult +{ + Handled, + Error, + Async, +}; +} +#endif // TRINITYCORE_HTTP_COMMON_H diff --git a/src/common/network/Http/HttpService.cpp b/src/common/network/Http/HttpService.cpp new file mode 100644 index 00000000000..b01e27e296a --- /dev/null +++ b/src/common/network/Http/HttpService.cpp @@ -0,0 +1,267 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "HttpService.h" +#include "BaseHttpSocket.h" +#include "CryptoRandom.h" +#include "Timezone.h" +#include +#include +#include + +namespace Trinity::Net::Http +{ +bool CanLogRequestContent(RequestContext const& context) +{ + return !context.handler || !context.handler->Flags.HasFlag(RequestHandlerFlag::DoNotLogRequestContent); +} + +bool CanLogResponseContent(RequestContext const& context) +{ + return !context.handler || !context.handler->Flags.HasFlag(RequestHandlerFlag::DoNotLogResponseContent); +} + +RequestHandlerResult DispatcherService::HandleRequest(std::shared_ptr session, RequestContext& context) +{ + TC_LOG_DEBUG(_logger, "{} Starting request {} {}", session->GetClientInfo(), + ToStdStringView(context.request.method_string()), ToStdStringView(context.request.target())); + + std::string_view path = [&] + { + std::string_view path = ToStdStringView(context.request.target()); + size_t queryIndex = path.find('?'); + if (queryIndex != std::string_view::npos) + path = path.substr(0, queryIndex); + return path; + }(); + + context.handler = [&]() -> HttpMethodHandlerMap::mapped_type const* + { + switch (context.request.method()) + { + case boost::beast::http::verb::get: + case boost::beast::http::verb::head: + { + auto itr = _getHandlers.find(path); + return itr != _getHandlers.end() ? &itr->second : nullptr; + } + case boost::beast::http::verb::post: + { + auto itr = _postHandlers.find(path); + return itr != _postHandlers.end() ? &itr->second : nullptr; + } + default: + break; + } + return nullptr; + }(); + + SystemTimePoint responseDate = SystemTimePoint::clock::now(); + context.response.set(boost::beast::http::field::date, StringFormat("{:%a, %d %b %Y %T GMT}", responseDate - Timezone::GetSystemZoneOffsetAt(responseDate))); + context.response.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); + context.response.keep_alive(context.request.keep_alive()); + + if (!context.handler) + return HandlePathNotFound(std::move(session), context); + + return context.handler->Func(std::move(session), context); +} + +RequestHandlerResult DispatcherService::HandleBadRequest(std::shared_ptr /*session*/, RequestContext& context) +{ + context.response.result(boost::beast::http::status::bad_request); + return RequestHandlerResult::Handled; +} + +RequestHandlerResult DispatcherService::HandleUnauthorized(std::shared_ptr /*session*/, RequestContext& context) +{ + context.response.result(boost::beast::http::status::unauthorized); + return RequestHandlerResult::Handled; +} + +RequestHandlerResult DispatcherService::HandlePathNotFound(std::shared_ptr /*session*/, RequestContext& context) +{ + context.response.result(boost::beast::http::status::not_found); + return RequestHandlerResult::Handled; +} + +void DispatcherService::RegisterHandler(boost::beast::http::verb method, std::string_view path, + std::function session, RequestContext& context)> handler, + RequestHandlerFlag flags) +{ + HttpMethodHandlerMap& handlerMap = [&]() -> HttpMethodHandlerMap& + { + switch (method) + { + case boost::beast::http::verb::get: + return _getHandlers; + case boost::beast::http::verb::post: + return _postHandlers; + default: + { + std::string_view methodString = ToStdStringView(boost::beast::http::to_string(method)); + ABORT_MSG("Tried to register a handler for unsupported HTTP method " STRING_VIEW_FMT, STRING_VIEW_FMT_ARG(methodString)); + } + } + }(); + + handlerMap[std::string(path)] = { .Func = std::move(handler), .Flags = flags }; + TC_LOG_INFO(_logger, "Registered new handler for {} {}", ToStdStringView(boost::beast::http::to_string(method)), path); +} + +void SessionService::InitAndStoreSessionState(std::shared_ptr state, boost::asio::ip::address const& address) +{ + state->RemoteAddress = address; + + // Generate session id + { + std::unique_lock lock{ _sessionsMutex }; + + while (state->Id.is_nil() || _sessions.contains(state->Id)) + std::copy_n(Trinity::Crypto::GetRandomBytes<16>().begin(), 16, state->Id.begin()); + + TC_LOG_DEBUG(_logger, "Client at {} created new session {}", address.to_string(), boost::uuids::to_string(state->Id)); + _sessions[state->Id] = std::move(state); + } +} + +void SessionService::Start(Asio::IoContext& ioContext) +{ + _inactiveSessionsKillTimer = std::make_unique(ioContext); + _inactiveSessionsKillTimer->expires_after(1min); + _inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err) + { + if (err) + return; + + KillInactiveSessions(); + }); +} + +void SessionService::Stop() +{ + _inactiveSessionsKillTimer = nullptr; + { + std::unique_lock lock{ _sessionsMutex }; + _sessions.clear(); + } + { + std::unique_lock lock{ _inactiveSessionsMutex }; + _inactiveSessions.clear(); + } +} + +std::shared_ptr SessionService::FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address) +{ + std::shared_ptr state; + + { + std::shared_lock lock{ _sessionsMutex }; + auto itr = _sessions.find(boost::uuids::string_generator()(id.begin(), id.end())); + if (itr == _sessions.end()) + { + TC_LOG_DEBUG(_logger, "Client at {} attempted to use a session {} that was expired", address.to_string(), id); + return nullptr; // no session + } + + state = itr->second; + } + + if (state->RemoteAddress != address) + { + TC_LOG_ERROR(_logger, "Client at {} attempted to use a session {} that was last accessed from {}, denied access", + address.to_string(), id, state->RemoteAddress.to_string()); + return nullptr; + } + + { + std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex }; + _inactiveSessions.erase(state->Id); + } + + return state; +} + +void SessionService::MarkSessionInactive(boost::uuids::uuid const& id) +{ + bool wasActive = true; + { + std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex }; + wasActive = _inactiveSessions.insert(id).second; + } + + if (wasActive) + { + std::shared_lock lock{ _sessionsMutex }; + auto itr = _sessions.find(id); + if (itr != _sessions.end()) + { + itr->second->InactiveTimestamp = TimePoint::clock::now() + Minutes(5); + TC_LOG_TRACE(_logger, "Session {} marked as inactive", boost::uuids::to_string(id)); + } + } +} + +void SessionService::KillInactiveSessions() +{ + std::set inactiveSessions; + + { + std::unique_lock lock{ _inactiveSessionsMutex }; + std::swap(_inactiveSessions, inactiveSessions); + } + + { + TimePoint now = TimePoint::clock::now(); + std::size_t inactiveSessionsCount = inactiveSessions.size(); + + std::unique_lock lock{ _sessionsMutex }; + for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); ) + { + auto sessionItr = _sessions.find(*itr); + if (sessionItr == _sessions.end() || sessionItr->second->InactiveTimestamp < now) + { + _sessions.erase(sessionItr); + itr = inactiveSessions.erase(itr); + } + else + ++itr; + } + + TC_LOG_DEBUG(_logger, "Killed {} inactive sessions", inactiveSessionsCount - inactiveSessions.size()); + } + + { + // restore sessions not killed to inactive queue + std::unique_lock lock{ _inactiveSessionsMutex }; + for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); ) + { + auto node = inactiveSessions.extract(itr++); + _inactiveSessions.insert(std::move(node)); + } + } + + _inactiveSessionsKillTimer->expires_after(1min); + _inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err) + { + if (err) + return; + + KillInactiveSessions(); + }); +} +} diff --git a/src/common/network/Http/HttpService.h b/src/common/network/Http/HttpService.h new file mode 100644 index 00000000000..1549893576f --- /dev/null +++ b/src/common/network/Http/HttpService.h @@ -0,0 +1,188 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_HTTP_SERVICE_H +#define TRINITYCORE_HTTP_SERVICE_H + +#include "AsioHacksFwd.h" +#include "Concepts.h" +#include "Define.h" +#include "EnumFlag.h" +#include "HttpCommon.h" +#include "HttpSessionState.h" +#include "Optional.h" +#include "SocketMgr.h" +#include +#include +#include +#include +#include + +namespace Trinity::Net::Http +{ +class AbstractSocket; + +enum class RequestHandlerFlag +{ + None = 0x0, + DoNotLogRequestContent = 0x1, + DoNotLogResponseContent = 0x2, +}; + +DEFINE_ENUM_FLAG(RequestHandlerFlag); + +struct RequestHandler +{ + std::function session, RequestContext& context)> Func; + EnumFlag Flags = RequestHandlerFlag::None; +}; + +class TC_NETWORK_API DispatcherService +{ +public: + explicit DispatcherService(std::string_view loggerSuffix) : _logger("server.http.dispatcher.") + { + _logger.append(loggerSuffix); + } + + RequestHandlerResult HandleRequest(std::shared_ptr session, RequestContext& context); + + static RequestHandlerResult HandleBadRequest(std::shared_ptr session, RequestContext& context); + static RequestHandlerResult HandleUnauthorized(std::shared_ptr session, RequestContext& context); + static RequestHandlerResult HandlePathNotFound(std::shared_ptr session, RequestContext& context); + +protected: + void RegisterHandler(boost::beast::http::verb method, std::string_view path, + std::function session, RequestContext& context)> handler, + RequestHandlerFlag flags = RequestHandlerFlag::None); + +private: + using HttpMethodHandlerMap = std::map>; + + HttpMethodHandlerMap _getHandlers; + HttpMethodHandlerMap _postHandlers; + + std::string _logger; +}; + +class TC_NETWORK_API SessionService +{ +public: + explicit SessionService(std::string_view loggerSuffix) : _logger("server.http.session.") + { + _logger.append(loggerSuffix); + } + + void Start(Asio::IoContext& ioContext); + void Stop(); + + std::shared_ptr FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address); + void MarkSessionInactive(boost::uuids::uuid const& id); + +protected: + void InitAndStoreSessionState(std::shared_ptr state, boost::asio::ip::address const& address); + + void KillInactiveSessions(); + +private: + std::shared_mutex _sessionsMutex; + std::map> _sessions; + + std::mutex _inactiveSessionsMutex; + std::set _inactiveSessions; + std::unique_ptr _inactiveSessionsKillTimer; + + std::string _logger; +}; + +template +concept HttpRequestHandler = invocable_r, RequestContext&>; + +template +class HttpService : public SocketMgr, public DispatcherService, public SessionService +{ +public: + HttpService(std::string_view loggerSuffix) : DispatcherService(loggerSuffix), SessionService(loggerSuffix), _ioContext(nullptr), _logger("server.http.") + { + _logger.append(loggerSuffix); + } + + bool StartNetwork(Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount = 1) override + { + if (!SocketMgr::StartNetwork(ioContext, bindIp, port, threadCount)) + return false; + + SessionService::Start(ioContext); + return true; + } + + void StopNetwork() override + { + SessionService::Stop(); + SocketMgr::StopNetwork(); + } + + // http handling + using DispatcherService::RegisterHandler; + + template Callable> + void RegisterHandler(boost::beast::http::verb method, std::string_view path, Callable handler, RequestHandlerFlag flags = RequestHandlerFlag::None) + { + this->DispatcherService::RegisterHandler(method, path, [handler = std::move(handler)](std::shared_ptr session, RequestContext& context) -> RequestHandlerResult + { + return handler(std::static_pointer_cast(std::move(session)), context); + }, flags); + } + + // session tracking + virtual std::shared_ptr CreateNewSessionState(boost::asio::ip::address const& address) + { + std::shared_ptr state = std::make_shared(); + InitAndStoreSessionState(state, address); + return state; + } + +protected: + class Thread : public NetworkThread + { + protected: + void SocketRemoved(std::shared_ptr const& session) override + { + if (Optional id = session->GetSessionId()) + _service->MarkSessionInactive(*id); + } + + private: + friend HttpService; + + SessionService* _service; + }; + + NetworkThread* CreateThreads() const override + { + Thread* threads = new Thread[this->GetNetworkThreadCount()]; + for (int32 i = 0; i < this->GetNetworkThreadCount(); ++i) + threads[i]._service = const_cast(this); + return threads; + } + + Asio::IoContext* _ioContext; + std::string _logger; +}; +} + +#endif // TRINITYCORE_HTTP_SERVICE_H diff --git a/src/common/network/Http/HttpSessionState.h b/src/common/network/Http/HttpSessionState.h new file mode 100644 index 00000000000..3012a2efc65 --- /dev/null +++ b/src/common/network/Http/HttpSessionState.h @@ -0,0 +1,35 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_HTTP_SESSION_STATE_H +#define TRINITYCORE_HTTP_SESSION_STATE_H + +#include "Duration.h" +#include +#include + +namespace Trinity::Net::Http +{ +struct SessionState +{ + boost::uuids::uuid Id = { }; + boost::asio::ip::address RemoteAddress; + TimePoint InactiveTimestamp = TimePoint::max(); +}; +} + +#endif // TRINITYCORE_HTTP_SESSION_STATE_H diff --git a/src/common/network/Http/HttpSocket.h b/src/common/network/Http/HttpSocket.h new file mode 100644 index 00000000000..2cfc3ba8ed8 --- /dev/null +++ b/src/common/network/Http/HttpSocket.h @@ -0,0 +1,53 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_HTTP_SOCKET_H +#define TRINITYCORE_HTTP_SOCKET_H + +#include "BaseHttpSocket.h" +#include + +namespace Trinity::Net::Http +{ +class Socket : public BaseSocket +{ + using SocketBase = BaseSocket; + +public: + using SocketBase::SocketBase; + + Socket(Socket const& other) = delete; + Socket(Socket&& other) = delete; + Socket& operator=(Socket const& other) = delete; + Socket& operator=(Socket&& other) = delete; + + ~Socket() = default; + + void Start() override + { + std::array, 2> initializers = + { { + std::make_shared>(this), + std::make_shared>(this), + } }; + + SocketConnectionInitializer::SetupChain(initializers)->Start(); + } +}; +} + +#endif // TRINITYCORE_HTTP_SOCKET_H diff --git a/src/common/network/Http/HttpSslSocket.h b/src/common/network/Http/HttpSslSocket.h new file mode 100644 index 00000000000..c789cbfefaf --- /dev/null +++ b/src/common/network/Http/HttpSslSocket.h @@ -0,0 +1,58 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_HTTP_SSL_SOCKET_H +#define TRINITYCORE_HTTP_SSL_SOCKET_H + +#include "BaseHttpSocket.h" +#include "SslStream.h" + +namespace Trinity::Net::Http +{ +class SslSocket : public BaseSocket> +{ + using SocketBase = BaseSocket>; + +public: + explicit SslSocket(IoContextTcpSocket&& socket, boost::asio::ssl::context& sslContext) + : SocketBase(std::move(socket), sslContext) { } + + explicit SslSocket(boost::asio::io_context& context, boost::asio::ssl::context& sslContext) + : SocketBase(context, sslContext) { } + + SslSocket(SslSocket const& other) = delete; + SslSocket(SslSocket&& other) = delete; + SslSocket& operator=(SslSocket const& other) = delete; + SslSocket& operator=(SslSocket&& other) = delete; + + ~SslSocket() = default; + + void Start() override + { + std::array, 3> initializers = + { { + std::make_shared>(this), + std::make_shared>(this), + std::make_shared>(this), + } }; + + SocketConnectionInitializer::SetupChain(initializers)->Start(); + } +}; +} + +#endif // TRINITYCORE_HTTP_SSL_SOCKET_H diff --git a/src/common/network/IpAddress.h b/src/common/network/IpAddress.h new file mode 100644 index 00000000000..b856d7f6340 --- /dev/null +++ b/src/common/network/IpAddress.h @@ -0,0 +1,33 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_IP_ADDRESS_H +#define TRINITYCORE_IP_ADDRESS_H + +#include "Define.h" +#include + +namespace Trinity::Net +{ + using boost::asio::ip::make_address; + using boost::asio::ip::make_address_v4; + using boost::asio::ip::make_address_v6; + using boost::asio::ip::v4_mapped_t::v4_mapped; + inline uint32 address_to_uint(boost::asio::ip::address_v4 const& address) { return address.to_uint(); } +} + +#endif // TRINITYCORE_IP_ADDRESS_H diff --git a/src/common/network/IpNetwork.cpp b/src/common/network/IpNetwork.cpp new file mode 100644 index 00000000000..b6235a5a947 --- /dev/null +++ b/src/common/network/IpNetwork.cpp @@ -0,0 +1,284 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "IpNetwork.h" +#include "IpAddress.h" +#include +#include +#include + +namespace +{ +std::vector LocalV4Networks; +std::vector LocalV6Networks; +} + +namespace Trinity::Net +{ +bool IsInLocalNetwork(boost::asio::ip::address const& clientAddress) +{ + if (clientAddress.is_v4()) + { + return std::ranges::any_of(LocalV4Networks, [clientAddressV4 = clientAddress.to_v4()](boost::asio::ip::network_v4 const& network) + { + return IsInNetwork(network, clientAddressV4); + }); + } + + if (clientAddress.is_v6()) + { + return std::ranges::any_of(LocalV6Networks, [clientAddressV6 = clientAddress.to_v6()](boost::asio::ip::network_v6 const& network) + { + return IsInNetwork(network, clientAddressV6); + }); + } + + return false; +}; + +bool IsInNetwork(boost::asio::ip::network_v4 const& network, boost::asio::ip::address_v4 const& clientAddress) +{ + if (clientAddress == network.address()) + return true; + + boost::asio::ip::network_v4 endpointAsNetwork = boost::asio::ip::make_network_v4(clientAddress, 32); + return endpointAsNetwork.is_subnet_of(network); +} + +bool IsInNetwork(boost::asio::ip::network_v6 const& network, boost::asio::ip::address_v6 const& clientAddress) +{ + if (clientAddress == network.address()) + return true; + + boost::asio::ip::network_v6 endpointAsNetwork = boost::asio::ip::make_network_v6(clientAddress, 128); + return endpointAsNetwork.is_subnet_of(network); +} + +Optional SelectAddressForClient(boost::asio::ip::address const& clientAddress, std::span const& addresses) +{ + Optional localIpv6Index; + Optional externalIpv6Index; + Optional loopbackIpv6Index; + Optional localIpv4Index; + Optional externalIpv4Index; + Optional loopbackIpv4Index; + + for (std::size_t i = 0; i < addresses.size(); ++i) + { + boost::asio::ip::address const& address = addresses[i]; + + if (address.is_loopback()) + { + if (address.is_v6() && !loopbackIpv6Index) + loopbackIpv6Index = i; + + if (address.is_v4() && !loopbackIpv4Index) + loopbackIpv4Index = i; + } + else if (IsInLocalNetwork(address)) + { + if (address.is_v6() && !localIpv6Index) + localIpv6Index = i; + + if (address.is_v4() && !localIpv4Index) + localIpv4Index = i; + } + else + { + if (address.is_v6() && !externalIpv6Index) + externalIpv6Index = i; + + if (address.is_v4() && !externalIpv4Index) + externalIpv4Index = i; + } + } + + if (IsInLocalNetwork(clientAddress) || clientAddress.is_loopback()) + { + // client is in the same network as this process, prefer local addresses + + // first, try finding a local ipv6 address + if (clientAddress.is_v6() && localIpv6Index) + { + // we have a usable ipv6 local address + return localIpv6Index; + } + + // we dont have a local v6, return local v4 + if (localIpv4Index) + return localIpv4Index; + } + + if (clientAddress.is_loopback()) + { + // fallback, search for a loopback address in configuration + if (clientAddress.is_v6() && loopbackIpv6Index) + return loopbackIpv6Index; + + if (loopbackIpv4Index) + return loopbackIpv4Index; + } + + // client is NOT in the same network as this process + if (clientAddress.is_v6() && externalIpv6Index) + return externalIpv6Index; + + return externalIpv4Index; +} +} + +#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS + +#include +#include + +void Trinity::Net::ScanLocalNetworks() +{ + LocalV4Networks.clear(); + LocalV6Networks.clear(); + + boost::system::error_code dllError; + boost::dll::shared_library iphlp("Iphlpapi.dll", dllError, boost::dll::load_mode::search_system_folders); + if (dllError || !iphlp.is_loaded()) + return; + + auto getAdaptersAddresses = iphlp.get("GetAdaptersAddresses"); + if (!getAdaptersAddresses) + return; + + ULONG queryFlags = GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_FRIENDLY_NAME; + ULONG bufferSize = 0; + + if (getAdaptersAddresses(AF_UNSPEC, queryFlags, nullptr, nullptr, &bufferSize) != ERROR_BUFFER_OVERFLOW) + return; + + std::unique_ptr addressesBuffer = std::make_unique(bufferSize); + if (getAdaptersAddresses(AF_UNSPEC, queryFlags, nullptr, reinterpret_cast(addressesBuffer.get()), &bufferSize) != ERROR_SUCCESS) + return; + + for (IP_ADAPTER_ADDRESSES* itr = reinterpret_cast(addressesBuffer.get()); itr; itr = itr->Next) + { + if (itr->IfType == IF_TYPE_SOFTWARE_LOOPBACK) + continue; + + if (itr->OperStatus != IfOperStatusUp) + continue; + + for (IP_ADAPTER_PREFIX_XP* prefix = itr->FirstPrefix; prefix; prefix = prefix->Next) + { + switch (prefix->Address.lpSockaddr->sa_family) + { + case AF_INET: + { + SOCKADDR_IN* ipv4raw = reinterpret_cast(prefix->Address.lpSockaddr); + boost::asio::ip::address_v4::bytes_type addressBytes; + std::memcpy(addressBytes.data(), &ipv4raw->sin_addr.s_addr, addressBytes.size()); + boost::asio::ip::address_v4 address = make_address_v4(addressBytes); + if (address.is_unspecified() || address.is_multicast() || address == boost::asio::ip::address_v4::broadcast()) + continue; + + LocalV4Networks.push_back(boost::asio::ip::make_network_v4(address, prefix->PrefixLength)); + break; + } + case AF_INET6: + { + SOCKADDR_IN6* ipv6raw = reinterpret_cast(prefix->Address.lpSockaddr); + boost::asio::ip::address_v6::bytes_type addressBytes; + std::memcpy(addressBytes.data(), ipv6raw->sin6_addr.s6_addr, addressBytes.size()); + boost::asio::ip::address_v6 address = make_address_v6(addressBytes, ipv6raw->sin6_scope_id); + if (address.is_unspecified() || address.is_multicast()) + continue; + + LocalV6Networks.push_back(boost::asio::ip::make_network_v6(address, prefix->PrefixLength)); + break; + } + default: + break; + } + } + } +} + +#else + +#include +#include + +void Trinity::Net::ScanLocalNetworks() +{ + LocalV4Networks.clear(); + LocalV6Networks.clear(); + + ifaddrs* addressesLinkedList = nullptr; + if (getifaddrs(&addressesLinkedList) == -1) + return; + + for (ifaddrs* itr = addressesLinkedList; itr; itr = itr->ifa_next) + { + if (!itr->ifa_addr) + continue; + + switch (itr->ifa_addr->sa_family) + { + case AF_INET: + { + sockaddr_in* ipv4raw = reinterpret_cast(itr->ifa_addr); + boost::asio::ip::address_v4::bytes_type addressBytes; + std::memcpy(addressBytes.data(), &ipv4raw->sin_addr.s_addr, addressBytes.size()); + boost::asio::ip::address_v4 address = make_address_v4(addressBytes); + if (address.is_unspecified() || address.is_loopback() || address.is_multicast() || address == boost::asio::ip::address_v4::broadcast()) + continue; + + if (sockaddr_in* netmask4raw = reinterpret_cast(itr->ifa_netmask)) + { + boost::asio::ip::address_v4::bytes_type netmaskBytes; + std::memcpy(netmaskBytes.data(), &netmask4raw->sin_addr.s_addr, netmaskBytes.size()); + boost::asio::ip::address_v4 netmask = make_address_v4(netmaskBytes); + LocalV4Networks.push_back(boost::asio::ip::make_network_v4(address, netmask)); + } + else + LocalV4Networks.push_back(boost::asio::ip::make_network_v4(address, 32)); + break; + } + case AF_INET6: + { + sockaddr_in6* ipv6raw = reinterpret_cast(itr->ifa_addr); + boost::asio::ip::address_v6::bytes_type addressBytes; + std::memcpy(addressBytes.data(), ipv6raw->sin6_addr.s6_addr, addressBytes.size()); + boost::asio::ip::address_v6 address = make_address_v6(addressBytes, ipv6raw->sin6_scope_id); + if (address.is_unspecified() || address.is_loopback() || address.is_multicast()) + continue; + + if (sockaddr_in6* netmask6raw = reinterpret_cast(itr->ifa_netmask)) + { + int32 prefixLength = 0; + for (uint8 addressByte : netmask6raw->sin6_addr.s6_addr) + prefixLength += std::countl_one(addressByte); + + LocalV6Networks.push_back(boost::asio::ip::make_network_v6(address, prefixLength)); + } + else + LocalV6Networks.push_back(boost::asio::ip::make_network_v6(address, 128)); + break; + } + } + } + + freeifaddrs(addressesLinkedList); +} + +#endif diff --git a/src/common/network/IpNetwork.h b/src/common/network/IpNetwork.h new file mode 100644 index 00000000000..c05c3076598 --- /dev/null +++ b/src/common/network/IpNetwork.h @@ -0,0 +1,39 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_IP_NETWORK_H +#define TRINITYCORE_IP_NETWORK_H + +#include "AsioHacksFwd.h" +#include "Define.h" +#include "Optional.h" +#include + +namespace Trinity::Net +{ +TC_NETWORK_API bool IsInLocalNetwork(boost::asio::ip::address const& clientAddress); + +TC_NETWORK_API bool IsInNetwork(boost::asio::ip::network_v4 const& network, boost::asio::ip::address_v4 const& clientAddress); + +TC_NETWORK_API bool IsInNetwork(boost::asio::ip::network_v6 const& network, boost::asio::ip::address_v6 const& clientAddress); + +TC_NETWORK_API Optional SelectAddressForClient(boost::asio::ip::address const& clientAddress, std::span const& addresses); + +TC_NETWORK_API void ScanLocalNetworks(); +} + +#endif // TRINITYCORE_IP_NETWORK_H diff --git a/src/common/network/NetworkThread.h b/src/common/network/NetworkThread.h new file mode 100644 index 00000000000..d16da442149 --- /dev/null +++ b/src/common/network/NetworkThread.h @@ -0,0 +1,179 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_NETWORK_THREAD_H +#define TRINITYCORE_NETWORK_THREAD_H + +#include "Containers.h" +#include "DeadlineTimer.h" +#include "Define.h" +#include "Errors.h" +#include "IoContext.h" +#include "Log.h" +#include "Socket.h" +#include +#include +#include +#include +#include + +namespace Trinity::Net +{ +template +class NetworkThread +{ +public: + NetworkThread() : _connections(0), _stopped(false), _thread(nullptr), _ioContext(1), + _acceptSocket(_ioContext), _updateTimer(_ioContext) + { + } + + NetworkThread(NetworkThread const&) = delete; + NetworkThread(NetworkThread&&) = delete; + NetworkThread& operator=(NetworkThread const&) = delete; + NetworkThread& operator=(NetworkThread&&) = delete; + + virtual ~NetworkThread() + { + Stop(); + if (_thread) + Wait(); + } + + void Stop() + { + _stopped = true; + _ioContext.stop(); + } + + bool Start() + { + if (_thread) + return false; + + _thread = std::make_unique(&NetworkThread::Run, this); + return true; + } + + void Wait() + { + ASSERT(_thread); + + _thread->join(); + _thread = nullptr; + } + + int32 GetConnectionCount() const + { + return _connections; + } + + void AddSocket(std::shared_ptr sock) + { + std::lock_guard lock(_newSocketsLock); + + ++_connections; + SocketAdded(_newSockets.emplace_back(std::move(sock))); + } + + Trinity::Net::IoContextTcpSocket* GetSocketForAccept() { return &_acceptSocket; } + +protected: + virtual void SocketAdded(std::shared_ptr const& /*sock*/) { } + virtual void SocketRemoved(std::shared_ptr const& /*sock*/) { } + + void AddNewSockets() + { + std::lock_guard lock(_newSocketsLock); + + if (_newSockets.empty()) + return; + + for (std::shared_ptr& sock : _newSockets) + { + if (!sock->IsOpen()) + { + SocketRemoved(sock); + --_connections; + } + else + _sockets.emplace_back(std::move(sock)); + } + + _newSockets.clear(); + } + + void Run() + { + TC_LOG_DEBUG("misc", "Network Thread Starting"); + + _updateTimer.expires_after(1ms); + _updateTimer.async_wait([this](boost::system::error_code const&) { Update(); }); + _ioContext.run(); + + TC_LOG_DEBUG("misc", "Network Thread exits"); + _newSockets.clear(); + _sockets.clear(); + } + + void Update() + { + if (_stopped) + return; + + _updateTimer.expires_after(1ms); + _updateTimer.async_wait([this](boost::system::error_code const&) { Update(); }); + + AddNewSockets(); + + Trinity::Containers::EraseIf(_sockets, [this](std::shared_ptr const& sock) + { + if (!sock->Update()) + { + if (sock->IsOpen()) + sock->CloseSocket(); + + this->SocketRemoved(sock); + + --this->_connections; + return true; + } + + return false; + }); + } + +private: + typedef std::vector> SocketContainer; + + std::atomic _connections; + std::atomic _stopped; + + std::unique_ptr _thread; + + SocketContainer _sockets; + + std::mutex _newSocketsLock; + SocketContainer _newSockets; + + Trinity::Asio::IoContext _ioContext; + Trinity::Net::IoContextTcpSocket _acceptSocket; + Trinity::Asio::DeadlineTimer _updateTimer; +}; +} + +#endif // TRINITYCORE_NETWORK_THREAD_H diff --git a/src/common/network/Resolver.cpp b/src/common/network/Resolver.cpp new file mode 100644 index 00000000000..9bcfcf28e78 --- /dev/null +++ b/src/common/network/Resolver.cpp @@ -0,0 +1,47 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "Resolver.h" +#include + +Optional Trinity::Net::Resolver::Resolve(boost::asio::ip::tcp const& protocol, std::string_view host, std::string_view service) +{ + boost::system::error_code ec; + boost::asio::ip::resolver_base::flags flagsResolver = boost::asio::ip::resolver_base::all_matching; + boost::asio::ip::tcp::resolver::results_type results = _impl.resolve(protocol, host, service, flagsResolver, ec); + Optional result; + if (!ec) + if (auto itr = results.begin(); itr != results.end()) + result.emplace(itr->endpoint()); + + return result; +} + +std::vector Trinity::Net::Resolver::ResolveAll(std::string_view host, std::string_view service) +{ + boost::system::error_code ec; + boost::asio::ip::resolver_base::flags flagsResolver = boost::asio::ip::resolver_base::all_matching; + boost::asio::ip::tcp::resolver::results_type results = _impl.resolve(host, service, flagsResolver, ec); + std::vector result; + if (!ec) + { + result.resize(results.size()); + std::ranges::transform(results, result.begin(), [](boost::asio::ip::tcp::resolver::results_type::value_type const& entry) { return entry.endpoint(); }); + } + + return result; +} diff --git a/src/common/network/Resolver.h b/src/common/network/Resolver.h new file mode 100644 index 00000000000..c7d24658aa5 --- /dev/null +++ b/src/common/network/Resolver.h @@ -0,0 +1,47 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_RESOLVER_H +#define TRINITYCORE_RESOLVER_H + +#include "Define.h" +#include "IoContext.h" +#include "Optional.h" +#include +#include +#include + +namespace Trinity::Net +{ +/** + Hack to make it possible to forward declare resolver (one of its template arguments is a typedef to something super long and using nested classes) +*/ +class TC_NETWORK_API Resolver +{ +public: + explicit Resolver(Asio::IoContext& ioContext) : _impl(ioContext) { } + + Optional Resolve(boost::asio::ip::tcp const& protocol, std::string_view host, std::string_view service); + + std::vector ResolveAll(std::string_view host, std::string_view service); + +private: + boost::asio::ip::tcp::resolver _impl; +}; +} + +#endif // TRINITYCORE_RESOLVER_H diff --git a/src/common/network/Socket.h b/src/common/network/Socket.h new file mode 100644 index 00000000000..565cc175318 --- /dev/null +++ b/src/common/network/Socket.h @@ -0,0 +1,362 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_SOCKET_H +#define TRINITYCORE_SOCKET_H + +#include "Concepts.h" +#include "Log.h" +#include "MessageBuffer.h" +#include "SocketConnectionInitializer.h" +#include +#include +#include +#include +#include +#include + +#define READ_BLOCK_SIZE 4096 +#ifdef BOOST_ASIO_HAS_IOCP +#define TC_SOCKET_USE_IOCP +#endif + +namespace Trinity::Net +{ +using IoContextTcpSocket = boost::asio::basic_stream_socket; + +enum class SocketReadCallbackResult +{ + KeepReading, + Stop +}; + +template +concept SocketReadCallback = Trinity::invocable_r; + +template +struct InvokeReadHandlerCallback +{ + SocketReadCallbackResult operator()() const + { + return this->Socket->ReadHandler(); + } + + SocketType* Socket; +}; + +template +struct ReadConnectionInitializer final : SocketConnectionInitializer +{ + explicit ReadConnectionInitializer(SocketType* socket) : ReadCallback({ .Socket = socket }) { } + + void Start() override + { + ReadCallback.Socket->AsyncRead(std::move(ReadCallback)); + + if (this->next) + this->next->Start(); + } + + InvokeReadHandlerCallback ReadCallback; +}; + +/** + @class Socket + + Base async socket implementation + + @tparam Stream stream type used for operations on socket + Stream must implement the following methods: + + void close(boost::system::error_code& error); + + void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError); + + template + void async_read_some(MutableBufferSequence const& buffers, ReadHandlerType&& handler); + + template + void async_write_some(ConstBufferSequence const& buffers, WriteHandlerType&& handler); + + template + std::size_t write_some(ConstBufferSequence const& buffers, boost::system::error_code& error); + + template + void async_wait(boost::asio::socket_base::wait_type type, WaitHandlerType&& handler); + + template + void set_option(SettableSocketOption const& option, boost::system::error_code& error); + + tcp::socket::endpoint_type remote_endpoint() const; +*/ +template +class Socket : public std::enable_shared_from_this> +{ +public: + template + explicit Socket(IoContextTcpSocket&& socket, Args&&... args) : _socket(std::move(socket), std::forward(args)...), + _remoteAddress(_socket.remote_endpoint().address()), _remotePort(_socket.remote_endpoint().port()), _openState(OpenState_Open) + { + } + + template + explicit Socket(boost::asio::io_context& context, Args&&... args) : _socket(context, std::forward(args)...), _openState(OpenState_Closed) + { + } + + Socket(Socket const& other) = delete; + Socket(Socket&& other) = delete; + Socket& operator=(Socket const& other) = delete; + Socket& operator=(Socket&& other) = delete; + + virtual ~Socket() + { + _openState = OpenState_Closed; + boost::system::error_code error; + _socket.close(error); + } + + virtual void Start() { } + + virtual bool Update() + { + if (_openState == OpenState_Closed) + return false; + +#ifndef TC_SOCKET_USE_IOCP + if (_isWritingAsync || (_writeQueue.empty() && _openState == OpenState_Open)) + return true; + + for (; HandleQueue();) + ; +#endif + + return true; + } + + boost::asio::ip::address const& GetRemoteIpAddress() const + { + return _remoteAddress; + } + + uint16 GetRemotePort() const + { + return _remotePort; + } + + template + void AsyncRead(Callback&& callback) + { + if (!IsOpen()) + return; + + _readBuffer.Normalize(); + _readBuffer.EnsureFreeSpace(); + _socket.async_read_some(boost::asio::buffer(_readBuffer.GetWritePointer(), _readBuffer.GetRemainingSpace()), + [self = this->shared_from_this(), callback = std::forward(callback)](boost::system::error_code const& error, size_t transferredBytes) mutable + { + if (self->ReadHandlerInternal(error, transferredBytes)) + if (callback() == SocketReadCallbackResult::KeepReading) + self->AsyncRead(std::forward(callback)); + }); + } + + void QueuePacket(MessageBuffer&& buffer) + { + _writeQueue.push(std::move(buffer)); + +#ifdef TC_SOCKET_USE_IOCP + AsyncProcessQueue(); +#endif + } + + bool IsOpen() const { return _openState == OpenState_Open; } + + void CloseSocket() + { + if ((_openState.fetch_or(OpenState_Closed) & OpenState_Closed) == 0) + return; + + boost::system::error_code shutdownError; + _socket.shutdown(boost::asio::socket_base::shutdown_send, shutdownError); + if (shutdownError) + TC_LOG_DEBUG("network", "Socket::CloseSocket: {} errored when shutting down socket: {} ({})", GetRemoteIpAddress().to_string(), + shutdownError.value(), shutdownError.message()); + + this->OnClose(); + } + + /// Marks the socket for closing after write buffer becomes empty + void DelayedCloseSocket() + { + if (_openState.fetch_or(OpenState_Closing) != 0) + return; + + if (_writeQueue.empty()) + CloseSocket(); + } + + MessageBuffer& GetReadBuffer() { return _readBuffer; } + + Stream& underlying_stream() + { + return _socket; + } + +protected: + virtual void OnClose() { } + + virtual SocketReadCallbackResult ReadHandler() { return SocketReadCallbackResult::KeepReading; } + + bool AsyncProcessQueue() + { + if (_isWritingAsync) + return false; + + _isWritingAsync = true; + +#ifdef TC_SOCKET_USE_IOCP + MessageBuffer& buffer = _writeQueue.front(); + _socket.async_write_some(boost::asio::buffer(buffer.GetReadPointer(), buffer.GetActiveSize()), + [self = this->shared_from_this()](boost::system::error_code const& error, std::size_t transferedBytes) + { + self->WriteHandler(error, transferedBytes); + }); +#else + _socket.async_wait(boost::asio::socket_base::wait_type::wait_write, + [self = this->shared_from_this()](boost::system::error_code const& error) + { + self->WriteHandlerWrapper(error); + }); +#endif + + return false; + } + + void SetNoDelay(bool enable) + { + boost::system::error_code err; + _socket.set_option(boost::asio::ip::tcp::no_delay(enable), err); + if (err) + TC_LOG_DEBUG("network", "Socket::SetNoDelay: failed to set_option(boost::asio::ip::tcp::no_delay) for {} - {} ({})", + GetRemoteIpAddress().to_string(), err.value(), err.message()); + } + +private: + bool ReadHandlerInternal(boost::system::error_code const& error, size_t transferredBytes) + { + if (error) + { + CloseSocket(); + return false; + } + + _readBuffer.WriteCompleted(transferredBytes); + return IsOpen(); + } + +#ifdef TC_SOCKET_USE_IOCP + + void WriteHandler(boost::system::error_code const& error, std::size_t transferedBytes) + { + if (!error) + { + _isWritingAsync = false; + _writeQueue.front().ReadCompleted(transferedBytes); + if (!_writeQueue.front().GetActiveSize()) + _writeQueue.pop(); + + if (!_writeQueue.empty()) + AsyncProcessQueue(); + else if (_openState == OpenState_Closing) + CloseSocket(); + } + else + CloseSocket(); + } + +#else + + void WriteHandlerWrapper(boost::system::error_code const& /*error*/) + { + _isWritingAsync = false; + HandleQueue(); + } + + bool HandleQueue() + { + if (_writeQueue.empty()) + return false; + + MessageBuffer& queuedMessage = _writeQueue.front(); + + std::size_t bytesToSend = queuedMessage.GetActiveSize(); + + boost::system::error_code error; + std::size_t bytesSent = _socket.write_some(boost::asio::buffer(queuedMessage.GetReadPointer(), bytesToSend), error); + + if (error) + { + if (error == boost::asio::error::would_block || error == boost::asio::error::try_again) + return AsyncProcessQueue(); + + _writeQueue.pop(); + if (_openState == OpenState_Closing && _writeQueue.empty()) + CloseSocket(); + return false; + } + else if (bytesSent == 0) + { + _writeQueue.pop(); + if (_openState == OpenState_Closing && _writeQueue.empty()) + CloseSocket(); + return false; + } + else if (bytesSent < bytesToSend) // now n > 0 + { + queuedMessage.ReadCompleted(bytesSent); + return AsyncProcessQueue(); + } + + _writeQueue.pop(); + if (_openState == OpenState_Closing && _writeQueue.empty()) + CloseSocket(); + return !_writeQueue.empty(); + } + +#endif + + Stream _socket; + + boost::asio::ip::address _remoteAddress; + uint16 _remotePort = 0; + + MessageBuffer _readBuffer = MessageBuffer(READ_BLOCK_SIZE); + std::queue _writeQueue; + + // Socket open state "enum" (not enum to enable integral std::atomic api) + static constexpr uint8 OpenState_Open = 0x0; + static constexpr uint8 OpenState_Closing = 0x1; ///< Transition to Closed state after sending all queued data + static constexpr uint8 OpenState_Closed = 0x2; + + std::atomic _openState; + + bool _isWritingAsync = false; +}; +} + +#endif // TRINITYCORE_SOCKET_H diff --git a/src/common/network/SocketMgr.h b/src/common/network/SocketMgr.h new file mode 100644 index 00000000000..07252355308 --- /dev/null +++ b/src/common/network/SocketMgr.h @@ -0,0 +1,146 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_SOCKET_MGR_H +#define TRINITYCORE_SOCKET_MGR_H + +#include "AsyncAcceptor.h" +#include "Errors.h" +#include "NetworkThread.h" +#include "Socket.h" +#include +#include + +namespace Trinity::Net +{ +template +class SocketMgr +{ +public: + SocketMgr(SocketMgr const&) = delete; + SocketMgr(SocketMgr&&) = delete; + SocketMgr& operator=(SocketMgr const&) = delete; + SocketMgr& operator=(SocketMgr&&) = delete; + + virtual ~SocketMgr() + { + ASSERT(!_threads && !_acceptor && !_threadCount, "StopNetwork must be called prior to SocketMgr destruction"); + } + + virtual bool StartNetwork(Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount) + { + ASSERT(threadCount > 0); + + std::unique_ptr acceptor = nullptr; + try + { + acceptor = std::make_unique(ioContext, bindIp, port); + } + catch (boost::system::system_error const& err) + { + TC_LOG_ERROR("network", "Exception caught in SocketMgr.StartNetwork ({}:{}): {}", bindIp, port, err.what()); + return false; + } + + if (!acceptor->Bind()) + { + TC_LOG_ERROR("network", "StartNetwork failed to bind socket acceptor"); + return false; + } + + _acceptor = std::move(acceptor); + _threadCount = threadCount; + _threads.reset(CreateThreads()); + + ASSERT(_threads); + + for (int32 i = 0; i < _threadCount; ++i) + _threads[i].Start(); + + _acceptor->SetSocketFactory([this]() { return GetSocketForAccept(); }); + + return true; + } + + virtual void StopNetwork() + { + _acceptor->Close(); + + for (int32 i = 0; i < _threadCount; ++i) + _threads[i].Stop(); + + Wait(); + + _acceptor = nullptr; + _threads = nullptr; + _threadCount = 0; + } + + void Wait() + { + for (int32 i = 0; i < _threadCount; ++i) + _threads[i].Wait(); + } + + virtual void OnSocketOpen(IoContextTcpSocket&& sock, uint32 threadIndex) + { + try + { + std::shared_ptr newSocket = std::make_shared(std::move(sock)); + newSocket->Start(); + + _threads[threadIndex].AddSocket(newSocket); + } + catch (boost::system::system_error const& err) + { + TC_LOG_WARN("network", "Failed to retrieve client's remote address {}", err.what()); + } + } + + int32 GetNetworkThreadCount() const { return _threadCount; } + + uint32 SelectThreadWithMinConnections() const + { + uint32 min = 0; + + for (int32 i = 1; i < _threadCount; ++i) + if (_threads[i].GetConnectionCount() < _threads[min].GetConnectionCount()) + min = i; + + return min; + } + + std::pair GetSocketForAccept() + { + uint32 threadIndex = SelectThreadWithMinConnections(); + return std::make_pair(_threads[threadIndex].GetSocketForAccept(), threadIndex); + } + +protected: + SocketMgr() : _threadCount(0) + { + } + + virtual NetworkThread* CreateThreads() const = 0; + + std::unique_ptr _acceptor; + std::unique_ptr[]> _threads; + int32 _threadCount; +}; +} + +#endif // TRINITYCORE_SOCKET_MGR_H diff --git a/src/common/network/SslStream.h b/src/common/network/SslStream.h new file mode 100644 index 00000000000..2cced44e5ff --- /dev/null +++ b/src/common/network/SslStream.h @@ -0,0 +1,131 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef TRINITYCORE_SSL_STREAM_H +#define TRINITYCORE_SSL_STREAM_H + +#include "SocketConnectionInitializer.h" +#include +#include +#include + +namespace Trinity::Net +{ +template +struct SslHandshakeConnectionInitializer final : SocketConnectionInitializer +{ + explicit SslHandshakeConnectionInitializer(SocketImpl* socket) : _socket(socket) { } + + void Start() override + { + _socket->underlying_stream().async_handshake(boost::asio::ssl::stream_base::server, + [socketRef = _socket->weak_from_this(), self = this->shared_from_this()](boost::system::error_code const& error) + { + std::shared_ptr socket = static_pointer_cast(socketRef.lock()); + if (!socket) + return; + + if (error) + { + TC_LOG_ERROR("session", "{} SSL Handshake failed {}", socket->GetClientInfo(), error.message()); + socket->CloseSocket(); + return; + } + + if (self->next) + self->next->Start(); + }); + } + +private: + SocketImpl* _socket; +}; + +template +class SslStream +{ +public: + explicit SslStream(IoContextTcpSocket&& socket, boost::asio::ssl::context& sslContext) : _sslSocket(std::move(socket), sslContext) + { + _sslSocket.set_verify_mode(boost::asio::ssl::verify_none); + } + + explicit SslStream(boost::asio::io_context& context, boost::asio::ssl::context& sslContext) : _sslSocket(context, sslContext) + { + _sslSocket.set_verify_mode(boost::asio::ssl::verify_none); + } + + // adapting tcp::socket api + void close(boost::system::error_code& error) + { + _sslSocket.next_layer().close(error); + } + + void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError) + { + _sslSocket.shutdown(shutdownError); + _sslSocket.next_layer().shutdown(what, shutdownError); + } + + template + void async_read_some(MutableBufferSequence const& buffers, ReadHandlerType&& handler) + { + _sslSocket.async_read_some(buffers, std::forward(handler)); + } + + template + void async_write_some(ConstBufferSequence const& buffers, WriteHandlerType&& handler) + { + _sslSocket.async_write_some(buffers, std::forward(handler)); + } + + template + std::size_t write_some(ConstBufferSequence const& buffers, boost::system::error_code& error) + { + return _sslSocket.write_some(buffers, error); + } + + template + void async_wait(boost::asio::socket_base::wait_type type, WaitHandlerType&& handler) + { + _sslSocket.next_layer().async_wait(type, std::forward(handler)); + } + + template + void set_option(SettableSocketOption const& option, boost::system::error_code& error) + { + _sslSocket.next_layer().set_option(option, error); + } + + IoContextTcpSocket::endpoint_type remote_endpoint() const + { + return _sslSocket.next_layer().remote_endpoint(); + } + + // ssl api + template + void async_handshake(boost::asio::ssl::stream_base::handshake_type type, HandshakeHandlerType&& handler) + { + _sslSocket.async_handshake(type, std::forward(handler)); + } + +protected: + boost::asio::ssl::stream _sslSocket; +}; +} + +#endif // TRINITYCORE_SSL_STREAM_H diff --git a/src/server/bnetserver/REST/LoginHttpSession.cpp b/src/server/bnetserver/REST/LoginHttpSession.cpp index 23a317d3726..bd8afdbcf2f 100644 --- a/src/server/bnetserver/REST/LoginHttpSession.cpp +++ b/src/server/bnetserver/REST/LoginHttpSession.cpp @@ -83,7 +83,7 @@ public: // build initializer chain boost::container::static_vector, 4> initializers; - initializers.stable_emplace_back(std::make_shared>(this)); + initializers.stable_emplace_back(std::make_shared>(&_owner)); if constexpr (std::is_same_v) initializers.stable_emplace_back(std::make_shared>(this)); @@ -124,10 +124,26 @@ LoginHttpSession::LoginHttpSession(Trinity::Net::IoContextTcpSocket&& socket) { } +LoginHttpSession::~LoginHttpSession() = default; + void LoginHttpSession::Start() { TC_LOG_TRACE("server.http.session", "{} Accepted connection", GetClientInfo()); return _socket->Start(); } + +bool LoginHttpSession::Update() +{ + if (!_socket->Update()) + return false; + + _queryProcessor.ProcessReadyCallbacks(); + return true; +} + +void LoginHttpSession::QueueQuery(QueryCallback&& queryCallback) +{ + _queryProcessor.AddCallback(std::move(queryCallback)); +} } diff --git a/src/server/bnetserver/REST/LoginHttpSession.h b/src/server/bnetserver/REST/LoginHttpSession.h index c15442f9e0c..9690b02f14a 100644 --- a/src/server/bnetserver/REST/LoginHttpSession.h +++ b/src/server/bnetserver/REST/LoginHttpSession.h @@ -18,7 +18,9 @@ #ifndef TRINITYCORE_LOGIN_HTTP_SESSION_H #define TRINITYCORE_LOGIN_HTTP_SESSION_H +#include "AsyncCallbackProcessor.h" #include "BaseHttpSocket.h" +#include "DatabaseEnvFwd.h" #include "SRP6.h" namespace Battlenet @@ -34,20 +36,22 @@ public: static constexpr std::string_view SESSION_ID_COOKIE = "JSESSIONID="; explicit LoginHttpSession(Trinity::Net::IoContextTcpSocket&& socket); + ~LoginHttpSession(); void Start() override; - bool Update() override { return _socket->Update(); } + bool Update() override; boost::asio::ip::address const& GetRemoteIpAddress() const override { return _socket->GetRemoteIpAddress(); } bool IsOpen() const override { return _socket->IsOpen(); } void CloseSocket() override { return _socket->CloseSocket(); } void SendResponse(Trinity::Net::Http::RequestContext& context) override { return _socket->SendResponse(context); } - void QueueQuery(QueryCallback&& queryCallback) override { return _socket->QueueQuery(std::move(queryCallback)); } + void QueueQuery(QueryCallback&& queryCallback); std::string GetClientInfo() const override { return _socket->GetClientInfo(); } LoginSessionState* GetSessionState() const override { return static_cast(_socket->GetSessionState()); } private: std::shared_ptr _socket; + QueryCallbackProcessor _queryProcessor; }; } diff --git a/src/server/bnetserver/REST/LoginRESTService.cpp b/src/server/bnetserver/REST/LoginRESTService.cpp index 1afcc5c88bb..2cb804b4307 100644 --- a/src/server/bnetserver/REST/LoginRESTService.cpp +++ b/src/server/bnetserver/REST/LoginRESTService.cpp @@ -80,7 +80,7 @@ bool LoginRESTService::StartNetwork(Trinity::Asio::IoContext& ioContext, std::st using namespace std::string_literals; std::array configKeys = { { "LoginREST.ExternalAddress"s, "LoginREST.LocalAddress"s } }; - Trinity::Asio::Resolver resolver(ioContext); + Trinity::Net::Resolver resolver(ioContext); for (std::size_t i = 0; i < _hostnames.size(); ++i) { diff --git a/src/server/scripts/Commands/cs_ban.cpp b/src/server/scripts/Commands/cs_ban.cpp index 5e717b379db..7af5a820ccc 100644 --- a/src/server/scripts/Commands/cs_ban.cpp +++ b/src/server/scripts/Commands/cs_ban.cpp @@ -29,6 +29,7 @@ EndScriptData */ #include "ChatCommand.h" #include "DatabaseEnv.h" #include "GameTime.h" +#include "IpAddress.h" #include "Language.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" @@ -161,6 +162,13 @@ public: return HandleBanHelper(BAN_IP, args, handler); } + static bool IsIPAddress(std::string const& text) + { + boost::system::error_code error; + Trinity::Net::make_address(text, error); + return !error; + } + static bool HandleBanHelper(BanMode mode, char const* args, ChatHandler* handler) { if (!*args) @@ -199,7 +207,7 @@ public: } break; case BAN_IP: - if (!IsIPAddress(nameOrIP.c_str())) + if (!IsIPAddress(nameOrIP)) return false; break; } @@ -360,22 +368,13 @@ public: return true; } - static bool HandleBanInfoIPCommand(ChatHandler* handler, char const* args) + static bool HandleBanInfoIPCommand(ChatHandler* handler, std::string&& ip) { - if (!*args) + if (!IsIPAddress(ip)) return false; - char* ipStr = strtok((char*)args, ""); - if (!ipStr) - return false; - - if (!IsIPAddress(ipStr)) - return false; - - std::string IP = ipStr; - - LoginDatabase.EscapeString(IP); - QueryResult result = LoginDatabase.PQuery("SELECT ip, FROM_UNIXTIME(bandate), FROM_UNIXTIME(unbandate), unbandate-UNIX_TIMESTAMP(), banreason, bannedby, unbandate-bandate FROM ip_banned WHERE ip = '{}'", IP); + LoginDatabase.EscapeString(ip); + QueryResult result = LoginDatabase.PQuery("SELECT ip, FROM_UNIXTIME(bandate), FROM_UNIXTIME(unbandate), unbandate-UNIX_TIMESTAMP(), banreason, bannedby, unbandate-bandate FROM ip_banned WHERE ip = '{}'", ip); if (!result) { handler->PSendSysMessage(LANG_BANINFO_NOIP); @@ -745,7 +744,7 @@ public: } break; case BAN_IP: - if (!IsIPAddress(nameOrIP.c_str())) + if (!IsIPAddress(nameOrIP)) return false; break; } diff --git a/src/server/shared/CMakeLists.txt b/src/server/shared/CMakeLists.txt index e5de78eb747..cac46028064 100644 --- a/src/server/shared/CMakeLists.txt +++ b/src/server/shared/CMakeLists.txt @@ -41,6 +41,7 @@ target_link_libraries(shared trinity-core-interface PUBLIC database + network rapidjson proto zlib) diff --git a/src/server/shared/IpLocation/IPLocation.cpp b/src/server/shared/IpLocation/IPLocation.cpp new file mode 100644 index 00000000000..72383bd3554 --- /dev/null +++ b/src/server/shared/IpLocation/IPLocation.cpp @@ -0,0 +1,150 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "IPLocation.h" +#include "BigNumber.h" +#include "Config.h" +#include "Errors.h" +#include "IpAddress.h" +#include "Log.h" +#include "Util.h" +#include +#include + +IpLocationStore::IpLocationStore() = default; +IpLocationStore::~IpLocationStore() = default; + +void IpLocationStore::Load() +{ + _ipLocationStore.clear(); + TC_LOG_INFO("server.loading", "Loading IP Location Database..."); + + std::string databaseFilePath = sConfigMgr->GetStringDefault("IPLocationFile", ""); + if (databaseFilePath.empty()) + return; + + // Check if file exists + std::ifstream databaseFile(databaseFilePath); + if (!databaseFile) + { + TC_LOG_ERROR("server.loading", "IPLocation: No ip database file exists ({}).", databaseFilePath); + return; + } + + if (!databaseFile.is_open()) + { + TC_LOG_ERROR("server.loading", "IPLocation: Ip database file ({}) can not be opened.", databaseFilePath); + return; + } + + std::string ipFrom; + std::string ipTo; + std::string countryCode; + std::string countryName; + BigNumber bnParser; + BigNumber ipv4Max(0xFFFFFFFF); + BigNumber ipv6MappedMask(0xFFFF); + ipv6MappedMask <<= 32; + + auto parseStringToIPv6 = [&](std::string const& str) -> Optional> + { + bnParser.SetDecStr(str); + if (!bnParser.SetDecStr(str)) + return {}; + // convert ipv4 to ipv6 v4 mapped value + if (bnParser <= ipv4Max) + bnParser += ipv6MappedMask; + return bnParser.ToByteArray<16>(false); + }; + + while (databaseFile.good()) + { + // Read lines + if (!std::getline(databaseFile, ipFrom, ',')) + break; + if (!std::getline(databaseFile, ipTo, ',')) + break; + if (!std::getline(databaseFile, countryCode, ',')) + break; + if (!std::getline(databaseFile, countryName, '\n')) + break; + + // Remove new lines and return + std::erase_if(countryName, [](char c) { return c == '\r' || c == '\n'; }); + + // Remove quotation marks + std::erase(ipFrom, '"'); + std::erase(ipTo, '"'); + std::erase(countryCode, '"'); + std::erase(countryName, '"'); + + if (countryCode == "-") + continue; + + // Convert country code to lowercase + strToLower(countryCode); + + Optional> from = parseStringToIPv6(ipFrom); + if (!from) + continue; + + Optional> to = parseStringToIPv6(ipTo); + if (!to) + continue; + + _ipLocationStore.emplace_back(*from, *to, std::move(countryCode), std::move(countryName)); + } + + std::ranges::sort(_ipLocationStore, {}, &IpLocationRecord::IpFrom); + ASSERT(std::ranges::is_sorted(_ipLocationStore, [](IpLocationRecord const& a, IpLocationRecord const& b) { return a.IpFrom < b.IpTo; }), + "Overlapping IP ranges detected in database file"); + + databaseFile.close(); + + TC_LOG_INFO("server.loading", ">> Loaded {} ip location entries.", _ipLocationStore.size()); +} + +IpLocationRecord const* IpLocationStore::GetLocationRecord(std::string const& ipAddress) const +{ + boost::system::error_code error; + boost::asio::ip::address address = Trinity::Net::make_address(ipAddress, error); + if (error) + return nullptr; + + std::array bytes = [&]() -> std::array + { + if (address.is_v6()) + return address.to_v6().to_bytes(); + if (address.is_v4()) + return Trinity::Net::make_address_v6(Trinity::Net::v4_mapped, address.to_v4()).to_bytes(); + return {}; + }(); + auto itr = std::ranges::upper_bound(_ipLocationStore, bytes, {}, &IpLocationRecord::IpTo); + if (itr == _ipLocationStore.end()) + return nullptr; + + if (bytes < itr->IpFrom) + return nullptr; + + return &(*itr); +} + +IpLocationStore* IpLocationStore::Instance() +{ + static IpLocationStore instance; + return &instance; +} diff --git a/src/server/shared/IpLocation/IPLocation.h b/src/server/shared/IpLocation/IPLocation.h new file mode 100644 index 00000000000..233404db241 --- /dev/null +++ b/src/server/shared/IpLocation/IPLocation.h @@ -0,0 +1,58 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef IPLOCATION_H +#define IPLOCATION_H + +#include "Define.h" +#include +#include +#include + +struct IpLocationRecord +{ + IpLocationRecord() : IpFrom(), IpTo() { } + IpLocationRecord(std::array ipFrom, std::array ipTo, std::string&& countryCode, std::string&& countryName) + : IpFrom(ipFrom), IpTo(ipTo), CountryCode(std::move(countryCode)), CountryName(std::move(countryName)) { } + + std::array IpFrom; + std::array IpTo; + std::string CountryCode; + std::string CountryName; +}; + +class TC_SHARED_API IpLocationStore +{ + public: + IpLocationStore(); + IpLocationStore(IpLocationStore const&) = delete; + IpLocationStore(IpLocationStore&&) = delete; + IpLocationStore& operator=(IpLocationStore const&) = delete; + IpLocationStore& operator=(IpLocationStore&&) = delete; + ~IpLocationStore(); + static IpLocationStore* Instance(); + + void Load(); + IpLocationRecord const* GetLocationRecord(std::string const& ipAddress) const; + + private: + std::vector _ipLocationStore; +}; + +#define sIPLocation IpLocationStore::Instance() + +#endif diff --git a/src/server/shared/Networking/AsyncAcceptor.h b/src/server/shared/Networking/AsyncAcceptor.h deleted file mode 100644 index dd0857c2b38..00000000000 --- a/src/server/shared/Networking/AsyncAcceptor.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_ASYNC_ACCEPTOR_H -#define TRINITYCORE_ASYNC_ACCEPTOR_H - -#include "IoContext.h" -#include "IpAddress.h" -#include "Log.h" -#include "Socket.h" -#include -#include -#include -#include - -#define TRINITY_MAX_LISTEN_CONNECTIONS boost::asio::socket_base::max_listen_connections - -namespace Trinity::Net -{ -template -concept AcceptCallback = std::invocable; - -class AsyncAcceptor -{ -public: - AsyncAcceptor(Asio::IoContext& ioContext, std::string const& bindIp, uint16 port) : - _acceptor(ioContext), _endpoint(make_address(bindIp), port), - _socket(ioContext), _closed(false), _socketFactory([this] { return DefeaultSocketFactory(); }) - { - } - - template - void AsyncAccept(Callback&& acceptCallback) - { - auto [tmpSocket, tmpThreadIndex] = _socketFactory(); - // TODO: get rid of temporary variables (clang 15 cannot handle variables from structured bindings as lambda captures) - IoContextTcpSocket* socket = tmpSocket; - uint32 threadIndex = tmpThreadIndex; - _acceptor.async_accept(*socket, [this, socket, threadIndex, acceptCallback = std::forward(acceptCallback)](boost::system::error_code const& error) mutable - { - if (!error) - { - try - { - socket->non_blocking(true); - - acceptCallback(std::move(*socket), threadIndex); - } - catch (boost::system::system_error const& err) - { - TC_LOG_INFO("network", "Failed to initialize client's socket {}", err.what()); - } - } - - if (!_closed) - this->AsyncAccept(std::move(acceptCallback)); - }); - } - - bool Bind() - { - boost::system::error_code errorCode; - _acceptor.open(_endpoint.protocol(), errorCode); - if (errorCode) - { - TC_LOG_INFO("network", "Failed to open acceptor {}", errorCode.message()); - return false; - } - -#if TRINITY_PLATFORM != TRINITY_PLATFORM_WINDOWS - _acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), errorCode); - if (errorCode) - { - TC_LOG_INFO("network", "Failed to set reuse_address option on acceptor {}", errorCode.message()); - return false; - } -#endif - - // v6_only is enabled on some *BSD distributions by default - // we want to allow both v4 and v6 connections to the same listener - if (_endpoint.protocol() == boost::asio::ip::tcp::v6()) - _acceptor.set_option(boost::asio::ip::v6_only(false)); - - _acceptor.bind(_endpoint, errorCode); - if (errorCode) - { - TC_LOG_INFO("network", "Could not bind to {}:{} {}", _endpoint.address().to_string(), _endpoint.port(), errorCode.message()); - return false; - } - - _acceptor.listen(TRINITY_MAX_LISTEN_CONNECTIONS, errorCode); - if (errorCode) - { - TC_LOG_INFO("network", "Failed to start listening on {}:{} {}", _endpoint.address().to_string(), _endpoint.port(), errorCode.message()); - return false; - } - - return true; - } - - void Close() - { - if (_closed.exchange(true)) - return; - - boost::system::error_code err; - _acceptor.close(err); - } - - void SetSocketFactory(std::function()> func) { _socketFactory = std::move(func); } - -private: - std::pair DefeaultSocketFactory() { return std::make_pair(&_socket, 0); } - - boost::asio::basic_socket_acceptor _acceptor; - boost::asio::ip::tcp::endpoint _endpoint; - IoContextTcpSocket _socket; - std::atomic _closed; - std::function()> _socketFactory; -}; -} - -#endif // TRINITYCORE_ASYNC_ACCEPTOR_H diff --git a/src/server/shared/Networking/ConnectionInitializers/IpBanCheckConnectionInitializer.h b/src/server/shared/Networking/ConnectionInitializers/IpBanCheckConnectionInitializer.h index ff8210a7f69..84df525918e 100644 --- a/src/server/shared/Networking/ConnectionInitializers/IpBanCheckConnectionInitializer.h +++ b/src/server/shared/Networking/ConnectionInitializers/IpBanCheckConnectionInitializer.h @@ -47,7 +47,7 @@ struct IpBanCheckConnectionInitializer final : SocketConnectionInitializer if (IpBanCheckHelpers::IsBanned(result)) { TC_LOG_ERROR("network", "IpBanCheckConnectionInitializer: IP {} is banned.", socket->GetRemoteIpAddress().to_string()); - socket->DelayedCloseSocket(); + socket->CloseSocket(); return; } diff --git a/src/server/shared/Networking/ConnectionInitializers/SocketConnectionInitializer.h b/src/server/shared/Networking/ConnectionInitializers/SocketConnectionInitializer.h deleted file mode 100644 index d3f0bb16dbf..00000000000 --- a/src/server/shared/Networking/ConnectionInitializers/SocketConnectionInitializer.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_SOCKET_CONNECTION_INITIALIZER_H -#define TRINITYCORE_SOCKET_CONNECTION_INITIALIZER_H - -#include -#include - -namespace Trinity::Net -{ -struct SocketConnectionInitializer : public std::enable_shared_from_this -{ - SocketConnectionInitializer() = default; - - SocketConnectionInitializer(SocketConnectionInitializer const&) = delete; - SocketConnectionInitializer(SocketConnectionInitializer&&) noexcept = default; - SocketConnectionInitializer& operator=(SocketConnectionInitializer const&) = delete; - SocketConnectionInitializer& operator=(SocketConnectionInitializer&&) noexcept = default; - - virtual ~SocketConnectionInitializer() = default; - - virtual void Start() = 0; - - std::shared_ptr next; - - static std::shared_ptr& SetupChain(std::span> initializers) - { - for (std::size_t i = initializers.size(); i > 1; --i) - initializers[i - 2]->next.swap(initializers[i - 1]); - - return initializers[0]; - } -}; -} - -#endif // TRINITYCORE_SOCKET_CONNECTION_INITIALIZER_H diff --git a/src/server/shared/Networking/Http/BaseHttpSocket.cpp b/src/server/shared/Networking/Http/BaseHttpSocket.cpp deleted file mode 100644 index 33053399c14..00000000000 --- a/src/server/shared/Networking/Http/BaseHttpSocket.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#include "BaseHttpSocket.h" -#include -#include - -namespace Trinity::Net::Http -{ -using RequestSerializer = boost::beast::http::request_serializer; -using ResponseSerializer = boost::beast::http::response_serializer; - -bool AbstractSocket::ParseRequest(MessageBuffer& packet, RequestParser& parser) -{ - if (!parser.is_done()) - { - // need more data in the payload - boost::system::error_code ec = {}; - std::size_t readDataSize = parser.put(boost::asio::const_buffer(packet.GetReadPointer(), packet.GetActiveSize()), ec); - packet.ReadCompleted(readDataSize); - } - - return parser.is_done(); -} - -std::string AbstractSocket::SerializeRequest(Request const& request) -{ - RequestSerializer serializer(request); - - std::string buffer; - while (!serializer.is_done()) - { - size_t totalBytes = 0; - boost::system::error_code ec = {}; - serializer.next(ec, [&](boost::system::error_code const&, ConstBufferSequence const& buffers) - { - size_t totalBytesInBuffers = boost::asio::buffer_size(buffers); - - buffer.reserve(buffer.size() + totalBytes); - - auto begin = boost::asio::buffers_begin(buffers); - auto end = boost::asio::buffers_end(buffers); - - std::copy(begin, end, std::back_inserter(buffer)); - totalBytes += totalBytesInBuffers; - }); - - serializer.consume(totalBytes); - } - - return buffer; -} - -MessageBuffer AbstractSocket::SerializeResponse(Request const& request, Response& response) -{ - response.prepare_payload(); - - ResponseSerializer serializer(response); - bool (*serializerIsDone)(ResponseSerializer&); - if (request.method() != boost::beast::http::verb::head) - { - serializerIsDone = [](ResponseSerializer& s) { return s.is_done(); }; - } - else - { - serializerIsDone = [](ResponseSerializer& s) { return s.is_header_done(); }; - serializer.split(true); - } - - MessageBuffer buffer; - while (!serializerIsDone(serializer)) - { - serializer.limit(buffer.GetRemainingSpace()); - - size_t totalBytes = 0; - boost::system::error_code ec = {}; - serializer.next(ec, [&](boost::system::error_code& currentError, ConstBufferSequence const& buffers) - { - size_t totalBytesInBuffers = boost::asio::buffer_size(buffers); - if (totalBytesInBuffers > buffer.GetRemainingSpace()) - { - currentError = boost::beast::http::error::need_more; - return; - } - - auto begin = boost::asio::buffers_begin(buffers); - auto end = boost::asio::buffers_end(buffers); - - std::copy(begin, end, buffer.GetWritePointer()); - buffer.WriteCompleted(totalBytesInBuffers); - totalBytes += totalBytesInBuffers; - }); - - serializer.consume(totalBytes); - - if (ec == boost::beast::http::error::need_more) - buffer.Resize(buffer.GetBufferSize() + 4096); - } - - return buffer; -} - -void AbstractSocket::LogRequestAndResponse(RequestContext const& context, MessageBuffer& buffer) const -{ - if (Logger const* logger = sLog->GetEnabledLogger("server.http", LOG_LEVEL_DEBUG)) - { - std::string clientInfo = GetClientInfo(); - sLog->OutMessageTo(logger, "server.http", LOG_LEVEL_DEBUG, "{} Request {} {} done, status {}", clientInfo, - ToStdStringView(context.request.method_string()), ToStdStringView(context.request.target()), context.response.result_int()); - if (sLog->ShouldLog("server.http", LOG_LEVEL_TRACE)) - { - sLog->OutMessageTo(logger, "server.http", LOG_LEVEL_TRACE, "{} Request: {}", clientInfo, - CanLogRequestContent(context) ? SerializeRequest(context.request) : ""); - sLog->OutMessageTo(logger, "server.http", LOG_LEVEL_TRACE, "{} Response: {}", clientInfo, - CanLogResponseContent(context) ? std::string_view(reinterpret_cast(buffer.GetBasePointer()), buffer.GetActiveSize()) : ""); - } - } -} - -std::string AbstractSocket::GetClientInfo(boost::asio::ip::address const& address, uint16 port, SessionState const* state) -{ - std::string info = StringFormat("[{}:{}", address.to_string(), port); - if (state) - { - info.append(", Session Id: "); - info.append(boost::uuids::to_string(state->Id)); - } - - info += ']'; - return info; -} -} diff --git a/src/server/shared/Networking/Http/BaseHttpSocket.h b/src/server/shared/Networking/Http/BaseHttpSocket.h deleted file mode 100644 index 4b7c3bd9dd1..00000000000 --- a/src/server/shared/Networking/Http/BaseHttpSocket.h +++ /dev/null @@ -1,245 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_BASE_HTTP_SOCKET_H -#define TRINITYCORE_BASE_HTTP_SOCKET_H - -#include "AsyncCallbackProcessor.h" -#include "DatabaseEnvFwd.h" -#include "HttpCommon.h" -#include "HttpSessionState.h" -#include "Optional.h" -#include "QueryCallback.h" -#include "Socket.h" -#include "SocketConnectionInitializer.h" -#include -#include -#include -#include - -namespace Trinity::Net::Http -{ -using IoContextHttpSocket = boost::beast::basic_stream; - -namespace Impl -{ -class BoostBeastSocketWrapper : public IoContextHttpSocket -{ -public: - using IoContextHttpSocket::basic_stream; - - void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError) - { - socket().shutdown(what, shutdownError); - } - - void close(boost::system::error_code& /*error*/) - { - IoContextHttpSocket::close(); - } - - template - void async_wait(boost::asio::socket_base::wait_type type, WaitHandlerType&& handler) - { - socket().async_wait(type, std::forward(handler)); - } - - IoContextTcpSocket::endpoint_type remote_endpoint() const - { - return socket().remote_endpoint(); - } -}; -} - -using RequestParser = boost::beast::http::request_parser; - -class TC_SHARED_API AbstractSocket -{ -public: - AbstractSocket() = default; - AbstractSocket(AbstractSocket const& other) = default; - AbstractSocket(AbstractSocket&& other) = default; - AbstractSocket& operator=(AbstractSocket const& other) = default; - AbstractSocket& operator=(AbstractSocket&& other) = default; - virtual ~AbstractSocket() = default; - - static bool ParseRequest(MessageBuffer& packet, RequestParser& parser); - - static std::string SerializeRequest(Request const& request); - static MessageBuffer SerializeResponse(Request const& request, Response& response); - - virtual void SendResponse(RequestContext& context) = 0; - - void LogRequestAndResponse(RequestContext const& context, MessageBuffer& buffer) const; - - virtual void QueueQuery(QueryCallback&& queryCallback) = 0; - - virtual std::string GetClientInfo() const = 0; - - static std::string GetClientInfo(boost::asio::ip::address const& address, uint16 port, SessionState const* state); - - virtual SessionState* GetSessionState() const = 0; - - Optional GetSessionId() const - { - if (SessionState* state = this->GetSessionState()) - return state->Id; - - return {}; - } - - virtual void Start() = 0; - - virtual bool Update() = 0; - - virtual boost::asio::ip::address const& GetRemoteIpAddress() const = 0; - - virtual bool IsOpen() const = 0; - - virtual void CloseSocket() = 0; -}; - -template -struct HttpConnectionInitializer final : SocketConnectionInitializer -{ - explicit HttpConnectionInitializer(SocketImpl* socket) : _socket(socket) { } - - void Start() override - { - _socket->ResetHttpParser(); - - if (this->next) - this->next->Start(); - } - -private: - SocketImpl* _socket; -}; - -template -class BaseSocket : public Trinity::Net::Socket, public AbstractSocket -{ - using Base = Trinity::Net::Socket; - -public: - using Base::Base; - - BaseSocket(BaseSocket const& other) = delete; - BaseSocket(BaseSocket&& other) = delete; - BaseSocket& operator=(BaseSocket const& other) = delete; - BaseSocket& operator=(BaseSocket&& other) = delete; - - ~BaseSocket() = default; - - SocketReadCallbackResult ReadHandler() final - { - MessageBuffer& packet = this->GetReadBuffer(); - while (packet.GetActiveSize() > 0) - { - if (!ParseRequest(packet, *_httpParser)) - { - // Couldn't receive the whole data this time. - break; - } - - if (!HandleMessage(_httpParser->get())) - { - this->CloseSocket(); - return SocketReadCallbackResult::Stop; - } - - this->ResetHttpParser(); - } - - return SocketReadCallbackResult::KeepReading; - } - - bool HandleMessage(Request& request) - { - RequestContext context { .request = std::move(request) }; - - if (!_state) - _state = this->ObtainSessionState(context); - - RequestHandlerResult status = this->RequestHandler(context); - - if (status != RequestHandlerResult::Async) - this->SendResponse(context); - - return status != RequestHandlerResult::Error; - } - - virtual RequestHandlerResult RequestHandler(RequestContext& context) = 0; - - void SendResponse(RequestContext& context) final - { - MessageBuffer buffer = SerializeResponse(context.request, context.response); - - this->LogRequestAndResponse(context, buffer); - - this->QueuePacket(std::move(buffer)); - - if (!context.response.keep_alive()) - this->DelayedCloseSocket(); - } - - void QueueQuery(QueryCallback&& queryCallback) final - { - this->_queryProcessor.AddCallback(std::move(queryCallback)); - } - - void Start() override { return this->Base::Start(); } - - bool Update() override - { - if (!this->Base::Update()) - return false; - - this->_queryProcessor.ProcessReadyCallbacks(); - return true; - } - - boost::asio::ip::address const& GetRemoteIpAddress() const final { return this->Base::GetRemoteIpAddress(); } - - bool IsOpen() const final { return this->Base::IsOpen(); } - - void CloseSocket() final { return this->Base::CloseSocket(); } - - std::string GetClientInfo() const override - { - return AbstractSocket::GetClientInfo(this->GetRemoteIpAddress(), this->GetRemotePort(), this->_state.get()); - } - - SessionState* GetSessionState() const override { return _state.get(); } - - void ResetHttpParser() - { - this->_httpParser.reset(); - this->_httpParser.emplace(); - this->_httpParser->eager(true); - } - -protected: - virtual std::shared_ptr ObtainSessionState(RequestContext& context) const = 0; - - QueryCallbackProcessor _queryProcessor; - Optional _httpParser; - std::shared_ptr _state; -}; -} - -#endif // TRINITYCORE_BASE_HTTP_SOCKET_H diff --git a/src/server/shared/Networking/Http/HttpCommon.h b/src/server/shared/Networking/Http/HttpCommon.h deleted file mode 100644 index 5f6ecb6c147..00000000000 --- a/src/server/shared/Networking/Http/HttpCommon.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_HTTP_COMMON_H -#define TRINITYCORE_HTTP_COMMON_H - -#include "Define.h" -#include -#include - -namespace Trinity::Net::Http -{ -using RequestBody = boost::beast::http::string_body; -using ResponseBody = boost::beast::http::string_body; - -using Request = boost::beast::http::request; -using Response = boost::beast::http::response; - -struct RequestContext -{ - Request request; - Response response; - struct RequestHandler const* handler = nullptr; -}; - -TC_SHARED_API bool CanLogRequestContent(RequestContext const& context); -TC_SHARED_API bool CanLogResponseContent(RequestContext const& context); - -inline std::string_view ToStdStringView(boost::beast::string_view bsw) -{ - return { bsw.data(), bsw.size() }; -} - -enum class RequestHandlerResult -{ - Handled, - Error, - Async, -}; -} -#endif // TRINITYCORE_HTTP_COMMON_H diff --git a/src/server/shared/Networking/Http/HttpService.cpp b/src/server/shared/Networking/Http/HttpService.cpp deleted file mode 100644 index b01e27e296a..00000000000 --- a/src/server/shared/Networking/Http/HttpService.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#include "HttpService.h" -#include "BaseHttpSocket.h" -#include "CryptoRandom.h" -#include "Timezone.h" -#include -#include -#include - -namespace Trinity::Net::Http -{ -bool CanLogRequestContent(RequestContext const& context) -{ - return !context.handler || !context.handler->Flags.HasFlag(RequestHandlerFlag::DoNotLogRequestContent); -} - -bool CanLogResponseContent(RequestContext const& context) -{ - return !context.handler || !context.handler->Flags.HasFlag(RequestHandlerFlag::DoNotLogResponseContent); -} - -RequestHandlerResult DispatcherService::HandleRequest(std::shared_ptr session, RequestContext& context) -{ - TC_LOG_DEBUG(_logger, "{} Starting request {} {}", session->GetClientInfo(), - ToStdStringView(context.request.method_string()), ToStdStringView(context.request.target())); - - std::string_view path = [&] - { - std::string_view path = ToStdStringView(context.request.target()); - size_t queryIndex = path.find('?'); - if (queryIndex != std::string_view::npos) - path = path.substr(0, queryIndex); - return path; - }(); - - context.handler = [&]() -> HttpMethodHandlerMap::mapped_type const* - { - switch (context.request.method()) - { - case boost::beast::http::verb::get: - case boost::beast::http::verb::head: - { - auto itr = _getHandlers.find(path); - return itr != _getHandlers.end() ? &itr->second : nullptr; - } - case boost::beast::http::verb::post: - { - auto itr = _postHandlers.find(path); - return itr != _postHandlers.end() ? &itr->second : nullptr; - } - default: - break; - } - return nullptr; - }(); - - SystemTimePoint responseDate = SystemTimePoint::clock::now(); - context.response.set(boost::beast::http::field::date, StringFormat("{:%a, %d %b %Y %T GMT}", responseDate - Timezone::GetSystemZoneOffsetAt(responseDate))); - context.response.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); - context.response.keep_alive(context.request.keep_alive()); - - if (!context.handler) - return HandlePathNotFound(std::move(session), context); - - return context.handler->Func(std::move(session), context); -} - -RequestHandlerResult DispatcherService::HandleBadRequest(std::shared_ptr /*session*/, RequestContext& context) -{ - context.response.result(boost::beast::http::status::bad_request); - return RequestHandlerResult::Handled; -} - -RequestHandlerResult DispatcherService::HandleUnauthorized(std::shared_ptr /*session*/, RequestContext& context) -{ - context.response.result(boost::beast::http::status::unauthorized); - return RequestHandlerResult::Handled; -} - -RequestHandlerResult DispatcherService::HandlePathNotFound(std::shared_ptr /*session*/, RequestContext& context) -{ - context.response.result(boost::beast::http::status::not_found); - return RequestHandlerResult::Handled; -} - -void DispatcherService::RegisterHandler(boost::beast::http::verb method, std::string_view path, - std::function session, RequestContext& context)> handler, - RequestHandlerFlag flags) -{ - HttpMethodHandlerMap& handlerMap = [&]() -> HttpMethodHandlerMap& - { - switch (method) - { - case boost::beast::http::verb::get: - return _getHandlers; - case boost::beast::http::verb::post: - return _postHandlers; - default: - { - std::string_view methodString = ToStdStringView(boost::beast::http::to_string(method)); - ABORT_MSG("Tried to register a handler for unsupported HTTP method " STRING_VIEW_FMT, STRING_VIEW_FMT_ARG(methodString)); - } - } - }(); - - handlerMap[std::string(path)] = { .Func = std::move(handler), .Flags = flags }; - TC_LOG_INFO(_logger, "Registered new handler for {} {}", ToStdStringView(boost::beast::http::to_string(method)), path); -} - -void SessionService::InitAndStoreSessionState(std::shared_ptr state, boost::asio::ip::address const& address) -{ - state->RemoteAddress = address; - - // Generate session id - { - std::unique_lock lock{ _sessionsMutex }; - - while (state->Id.is_nil() || _sessions.contains(state->Id)) - std::copy_n(Trinity::Crypto::GetRandomBytes<16>().begin(), 16, state->Id.begin()); - - TC_LOG_DEBUG(_logger, "Client at {} created new session {}", address.to_string(), boost::uuids::to_string(state->Id)); - _sessions[state->Id] = std::move(state); - } -} - -void SessionService::Start(Asio::IoContext& ioContext) -{ - _inactiveSessionsKillTimer = std::make_unique(ioContext); - _inactiveSessionsKillTimer->expires_after(1min); - _inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err) - { - if (err) - return; - - KillInactiveSessions(); - }); -} - -void SessionService::Stop() -{ - _inactiveSessionsKillTimer = nullptr; - { - std::unique_lock lock{ _sessionsMutex }; - _sessions.clear(); - } - { - std::unique_lock lock{ _inactiveSessionsMutex }; - _inactiveSessions.clear(); - } -} - -std::shared_ptr SessionService::FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address) -{ - std::shared_ptr state; - - { - std::shared_lock lock{ _sessionsMutex }; - auto itr = _sessions.find(boost::uuids::string_generator()(id.begin(), id.end())); - if (itr == _sessions.end()) - { - TC_LOG_DEBUG(_logger, "Client at {} attempted to use a session {} that was expired", address.to_string(), id); - return nullptr; // no session - } - - state = itr->second; - } - - if (state->RemoteAddress != address) - { - TC_LOG_ERROR(_logger, "Client at {} attempted to use a session {} that was last accessed from {}, denied access", - address.to_string(), id, state->RemoteAddress.to_string()); - return nullptr; - } - - { - std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex }; - _inactiveSessions.erase(state->Id); - } - - return state; -} - -void SessionService::MarkSessionInactive(boost::uuids::uuid const& id) -{ - bool wasActive = true; - { - std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex }; - wasActive = _inactiveSessions.insert(id).second; - } - - if (wasActive) - { - std::shared_lock lock{ _sessionsMutex }; - auto itr = _sessions.find(id); - if (itr != _sessions.end()) - { - itr->second->InactiveTimestamp = TimePoint::clock::now() + Minutes(5); - TC_LOG_TRACE(_logger, "Session {} marked as inactive", boost::uuids::to_string(id)); - } - } -} - -void SessionService::KillInactiveSessions() -{ - std::set inactiveSessions; - - { - std::unique_lock lock{ _inactiveSessionsMutex }; - std::swap(_inactiveSessions, inactiveSessions); - } - - { - TimePoint now = TimePoint::clock::now(); - std::size_t inactiveSessionsCount = inactiveSessions.size(); - - std::unique_lock lock{ _sessionsMutex }; - for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); ) - { - auto sessionItr = _sessions.find(*itr); - if (sessionItr == _sessions.end() || sessionItr->second->InactiveTimestamp < now) - { - _sessions.erase(sessionItr); - itr = inactiveSessions.erase(itr); - } - else - ++itr; - } - - TC_LOG_DEBUG(_logger, "Killed {} inactive sessions", inactiveSessionsCount - inactiveSessions.size()); - } - - { - // restore sessions not killed to inactive queue - std::unique_lock lock{ _inactiveSessionsMutex }; - for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); ) - { - auto node = inactiveSessions.extract(itr++); - _inactiveSessions.insert(std::move(node)); - } - } - - _inactiveSessionsKillTimer->expires_after(1min); - _inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err) - { - if (err) - return; - - KillInactiveSessions(); - }); -} -} diff --git a/src/server/shared/Networking/Http/HttpService.h b/src/server/shared/Networking/Http/HttpService.h deleted file mode 100644 index 2a377c734da..00000000000 --- a/src/server/shared/Networking/Http/HttpService.h +++ /dev/null @@ -1,188 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_HTTP_SERVICE_H -#define TRINITYCORE_HTTP_SERVICE_H - -#include "AsioHacksFwd.h" -#include "Concepts.h" -#include "Define.h" -#include "EnumFlag.h" -#include "HttpCommon.h" -#include "HttpSessionState.h" -#include "Optional.h" -#include "SocketMgr.h" -#include -#include -#include -#include -#include - -namespace Trinity::Net::Http -{ -class AbstractSocket; - -enum class RequestHandlerFlag -{ - None = 0x0, - DoNotLogRequestContent = 0x1, - DoNotLogResponseContent = 0x2, -}; - -DEFINE_ENUM_FLAG(RequestHandlerFlag); - -struct RequestHandler -{ - std::function session, RequestContext& context)> Func; - EnumFlag Flags = RequestHandlerFlag::None; -}; - -class TC_SHARED_API DispatcherService -{ -public: - explicit DispatcherService(std::string_view loggerSuffix) : _logger("server.http.dispatcher.") - { - _logger.append(loggerSuffix); - } - - RequestHandlerResult HandleRequest(std::shared_ptr session, RequestContext& context); - - static RequestHandlerResult HandleBadRequest(std::shared_ptr session, RequestContext& context); - static RequestHandlerResult HandleUnauthorized(std::shared_ptr session, RequestContext& context); - static RequestHandlerResult HandlePathNotFound(std::shared_ptr session, RequestContext& context); - -protected: - void RegisterHandler(boost::beast::http::verb method, std::string_view path, - std::function session, RequestContext& context)> handler, - RequestHandlerFlag flags = RequestHandlerFlag::None); - -private: - using HttpMethodHandlerMap = std::map>; - - HttpMethodHandlerMap _getHandlers; - HttpMethodHandlerMap _postHandlers; - - std::string _logger; -}; - -class TC_SHARED_API SessionService -{ -public: - explicit SessionService(std::string_view loggerSuffix) : _logger("server.http.session.") - { - _logger.append(loggerSuffix); - } - - void Start(Asio::IoContext& ioContext); - void Stop(); - - std::shared_ptr FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address); - void MarkSessionInactive(boost::uuids::uuid const& id); - -protected: - void InitAndStoreSessionState(std::shared_ptr state, boost::asio::ip::address const& address); - - void KillInactiveSessions(); - -private: - std::shared_mutex _sessionsMutex; - std::map> _sessions; - - std::mutex _inactiveSessionsMutex; - std::set _inactiveSessions; - std::unique_ptr _inactiveSessionsKillTimer; - - std::string _logger; -}; - -template -concept HttpRequestHandler = invocable_r, RequestContext&>; - -template -class HttpService : public SocketMgr, public DispatcherService, public SessionService -{ -public: - HttpService(std::string_view loggerSuffix) : DispatcherService(loggerSuffix), SessionService(loggerSuffix), _ioContext(nullptr), _logger("server.http.") - { - _logger.append(loggerSuffix); - } - - bool StartNetwork(Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount = 1) override - { - if (!SocketMgr::StartNetwork(ioContext, bindIp, port, threadCount)) - return false; - - SessionService::Start(ioContext); - return true; - } - - void StopNetwork() override - { - SessionService::Stop(); - SocketMgr::StopNetwork(); - } - - // http handling - using DispatcherService::RegisterHandler; - - template Callable> - void RegisterHandler(boost::beast::http::verb method, std::string_view path, Callable handler, RequestHandlerFlag flags = RequestHandlerFlag::None) - { - this->DispatcherService::RegisterHandler(method, path, [handler = std::move(handler)](std::shared_ptr session, RequestContext& context) -> RequestHandlerResult - { - return handler(std::static_pointer_cast(std::move(session)), context); - }, flags); - } - - // session tracking - virtual std::shared_ptr CreateNewSessionState(boost::asio::ip::address const& address) - { - std::shared_ptr state = std::make_shared(); - InitAndStoreSessionState(state, address); - return state; - } - -protected: - class Thread : public NetworkThread - { - protected: - void SocketRemoved(std::shared_ptr const& session) override - { - if (Optional id = session->GetSessionId()) - _service->MarkSessionInactive(*id); - } - - private: - friend HttpService; - - SessionService* _service; - }; - - NetworkThread* CreateThreads() const override - { - Thread* threads = new Thread[this->GetNetworkThreadCount()]; - for (int32 i = 0; i < this->GetNetworkThreadCount(); ++i) - threads[i]._service = const_cast(this); - return threads; - } - - Asio::IoContext* _ioContext; - std::string _logger; -}; -} - -#endif // TRINITYCORE_HTTP_SERVICE_H diff --git a/src/server/shared/Networking/Http/HttpSessionState.h b/src/server/shared/Networking/Http/HttpSessionState.h deleted file mode 100644 index 3012a2efc65..00000000000 --- a/src/server/shared/Networking/Http/HttpSessionState.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_HTTP_SESSION_STATE_H -#define TRINITYCORE_HTTP_SESSION_STATE_H - -#include "Duration.h" -#include -#include - -namespace Trinity::Net::Http -{ -struct SessionState -{ - boost::uuids::uuid Id = { }; - boost::asio::ip::address RemoteAddress; - TimePoint InactiveTimestamp = TimePoint::max(); -}; -} - -#endif // TRINITYCORE_HTTP_SESSION_STATE_H diff --git a/src/server/shared/Networking/Http/HttpSocket.h b/src/server/shared/Networking/Http/HttpSocket.h deleted file mode 100644 index 2cfc3ba8ed8..00000000000 --- a/src/server/shared/Networking/Http/HttpSocket.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_HTTP_SOCKET_H -#define TRINITYCORE_HTTP_SOCKET_H - -#include "BaseHttpSocket.h" -#include - -namespace Trinity::Net::Http -{ -class Socket : public BaseSocket -{ - using SocketBase = BaseSocket; - -public: - using SocketBase::SocketBase; - - Socket(Socket const& other) = delete; - Socket(Socket&& other) = delete; - Socket& operator=(Socket const& other) = delete; - Socket& operator=(Socket&& other) = delete; - - ~Socket() = default; - - void Start() override - { - std::array, 2> initializers = - { { - std::make_shared>(this), - std::make_shared>(this), - } }; - - SocketConnectionInitializer::SetupChain(initializers)->Start(); - } -}; -} - -#endif // TRINITYCORE_HTTP_SOCKET_H diff --git a/src/server/shared/Networking/Http/HttpSslSocket.h b/src/server/shared/Networking/Http/HttpSslSocket.h deleted file mode 100644 index c789cbfefaf..00000000000 --- a/src/server/shared/Networking/Http/HttpSslSocket.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_HTTP_SSL_SOCKET_H -#define TRINITYCORE_HTTP_SSL_SOCKET_H - -#include "BaseHttpSocket.h" -#include "SslStream.h" - -namespace Trinity::Net::Http -{ -class SslSocket : public BaseSocket> -{ - using SocketBase = BaseSocket>; - -public: - explicit SslSocket(IoContextTcpSocket&& socket, boost::asio::ssl::context& sslContext) - : SocketBase(std::move(socket), sslContext) { } - - explicit SslSocket(boost::asio::io_context& context, boost::asio::ssl::context& sslContext) - : SocketBase(context, sslContext) { } - - SslSocket(SslSocket const& other) = delete; - SslSocket(SslSocket&& other) = delete; - SslSocket& operator=(SslSocket const& other) = delete; - SslSocket& operator=(SslSocket&& other) = delete; - - ~SslSocket() = default; - - void Start() override - { - std::array, 3> initializers = - { { - std::make_shared>(this), - std::make_shared>(this), - std::make_shared>(this), - } }; - - SocketConnectionInitializer::SetupChain(initializers)->Start(); - } -}; -} - -#endif // TRINITYCORE_HTTP_SSL_SOCKET_H diff --git a/src/server/shared/Networking/NetworkThread.h b/src/server/shared/Networking/NetworkThread.h deleted file mode 100644 index d16da442149..00000000000 --- a/src/server/shared/Networking/NetworkThread.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_NETWORK_THREAD_H -#define TRINITYCORE_NETWORK_THREAD_H - -#include "Containers.h" -#include "DeadlineTimer.h" -#include "Define.h" -#include "Errors.h" -#include "IoContext.h" -#include "Log.h" -#include "Socket.h" -#include -#include -#include -#include -#include - -namespace Trinity::Net -{ -template -class NetworkThread -{ -public: - NetworkThread() : _connections(0), _stopped(false), _thread(nullptr), _ioContext(1), - _acceptSocket(_ioContext), _updateTimer(_ioContext) - { - } - - NetworkThread(NetworkThread const&) = delete; - NetworkThread(NetworkThread&&) = delete; - NetworkThread& operator=(NetworkThread const&) = delete; - NetworkThread& operator=(NetworkThread&&) = delete; - - virtual ~NetworkThread() - { - Stop(); - if (_thread) - Wait(); - } - - void Stop() - { - _stopped = true; - _ioContext.stop(); - } - - bool Start() - { - if (_thread) - return false; - - _thread = std::make_unique(&NetworkThread::Run, this); - return true; - } - - void Wait() - { - ASSERT(_thread); - - _thread->join(); - _thread = nullptr; - } - - int32 GetConnectionCount() const - { - return _connections; - } - - void AddSocket(std::shared_ptr sock) - { - std::lock_guard lock(_newSocketsLock); - - ++_connections; - SocketAdded(_newSockets.emplace_back(std::move(sock))); - } - - Trinity::Net::IoContextTcpSocket* GetSocketForAccept() { return &_acceptSocket; } - -protected: - virtual void SocketAdded(std::shared_ptr const& /*sock*/) { } - virtual void SocketRemoved(std::shared_ptr const& /*sock*/) { } - - void AddNewSockets() - { - std::lock_guard lock(_newSocketsLock); - - if (_newSockets.empty()) - return; - - for (std::shared_ptr& sock : _newSockets) - { - if (!sock->IsOpen()) - { - SocketRemoved(sock); - --_connections; - } - else - _sockets.emplace_back(std::move(sock)); - } - - _newSockets.clear(); - } - - void Run() - { - TC_LOG_DEBUG("misc", "Network Thread Starting"); - - _updateTimer.expires_after(1ms); - _updateTimer.async_wait([this](boost::system::error_code const&) { Update(); }); - _ioContext.run(); - - TC_LOG_DEBUG("misc", "Network Thread exits"); - _newSockets.clear(); - _sockets.clear(); - } - - void Update() - { - if (_stopped) - return; - - _updateTimer.expires_after(1ms); - _updateTimer.async_wait([this](boost::system::error_code const&) { Update(); }); - - AddNewSockets(); - - Trinity::Containers::EraseIf(_sockets, [this](std::shared_ptr const& sock) - { - if (!sock->Update()) - { - if (sock->IsOpen()) - sock->CloseSocket(); - - this->SocketRemoved(sock); - - --this->_connections; - return true; - } - - return false; - }); - } - -private: - typedef std::vector> SocketContainer; - - std::atomic _connections; - std::atomic _stopped; - - std::unique_ptr _thread; - - SocketContainer _sockets; - - std::mutex _newSocketsLock; - SocketContainer _newSockets; - - Trinity::Asio::IoContext _ioContext; - Trinity::Net::IoContextTcpSocket _acceptSocket; - Trinity::Asio::DeadlineTimer _updateTimer; -}; -} - -#endif // TRINITYCORE_NETWORK_THREAD_H diff --git a/src/server/shared/Networking/Socket.h b/src/server/shared/Networking/Socket.h deleted file mode 100644 index 565cc175318..00000000000 --- a/src/server/shared/Networking/Socket.h +++ /dev/null @@ -1,362 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_SOCKET_H -#define TRINITYCORE_SOCKET_H - -#include "Concepts.h" -#include "Log.h" -#include "MessageBuffer.h" -#include "SocketConnectionInitializer.h" -#include -#include -#include -#include -#include -#include - -#define READ_BLOCK_SIZE 4096 -#ifdef BOOST_ASIO_HAS_IOCP -#define TC_SOCKET_USE_IOCP -#endif - -namespace Trinity::Net -{ -using IoContextTcpSocket = boost::asio::basic_stream_socket; - -enum class SocketReadCallbackResult -{ - KeepReading, - Stop -}; - -template -concept SocketReadCallback = Trinity::invocable_r; - -template -struct InvokeReadHandlerCallback -{ - SocketReadCallbackResult operator()() const - { - return this->Socket->ReadHandler(); - } - - SocketType* Socket; -}; - -template -struct ReadConnectionInitializer final : SocketConnectionInitializer -{ - explicit ReadConnectionInitializer(SocketType* socket) : ReadCallback({ .Socket = socket }) { } - - void Start() override - { - ReadCallback.Socket->AsyncRead(std::move(ReadCallback)); - - if (this->next) - this->next->Start(); - } - - InvokeReadHandlerCallback ReadCallback; -}; - -/** - @class Socket - - Base async socket implementation - - @tparam Stream stream type used for operations on socket - Stream must implement the following methods: - - void close(boost::system::error_code& error); - - void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError); - - template - void async_read_some(MutableBufferSequence const& buffers, ReadHandlerType&& handler); - - template - void async_write_some(ConstBufferSequence const& buffers, WriteHandlerType&& handler); - - template - std::size_t write_some(ConstBufferSequence const& buffers, boost::system::error_code& error); - - template - void async_wait(boost::asio::socket_base::wait_type type, WaitHandlerType&& handler); - - template - void set_option(SettableSocketOption const& option, boost::system::error_code& error); - - tcp::socket::endpoint_type remote_endpoint() const; -*/ -template -class Socket : public std::enable_shared_from_this> -{ -public: - template - explicit Socket(IoContextTcpSocket&& socket, Args&&... args) : _socket(std::move(socket), std::forward(args)...), - _remoteAddress(_socket.remote_endpoint().address()), _remotePort(_socket.remote_endpoint().port()), _openState(OpenState_Open) - { - } - - template - explicit Socket(boost::asio::io_context& context, Args&&... args) : _socket(context, std::forward(args)...), _openState(OpenState_Closed) - { - } - - Socket(Socket const& other) = delete; - Socket(Socket&& other) = delete; - Socket& operator=(Socket const& other) = delete; - Socket& operator=(Socket&& other) = delete; - - virtual ~Socket() - { - _openState = OpenState_Closed; - boost::system::error_code error; - _socket.close(error); - } - - virtual void Start() { } - - virtual bool Update() - { - if (_openState == OpenState_Closed) - return false; - -#ifndef TC_SOCKET_USE_IOCP - if (_isWritingAsync || (_writeQueue.empty() && _openState == OpenState_Open)) - return true; - - for (; HandleQueue();) - ; -#endif - - return true; - } - - boost::asio::ip::address const& GetRemoteIpAddress() const - { - return _remoteAddress; - } - - uint16 GetRemotePort() const - { - return _remotePort; - } - - template - void AsyncRead(Callback&& callback) - { - if (!IsOpen()) - return; - - _readBuffer.Normalize(); - _readBuffer.EnsureFreeSpace(); - _socket.async_read_some(boost::asio::buffer(_readBuffer.GetWritePointer(), _readBuffer.GetRemainingSpace()), - [self = this->shared_from_this(), callback = std::forward(callback)](boost::system::error_code const& error, size_t transferredBytes) mutable - { - if (self->ReadHandlerInternal(error, transferredBytes)) - if (callback() == SocketReadCallbackResult::KeepReading) - self->AsyncRead(std::forward(callback)); - }); - } - - void QueuePacket(MessageBuffer&& buffer) - { - _writeQueue.push(std::move(buffer)); - -#ifdef TC_SOCKET_USE_IOCP - AsyncProcessQueue(); -#endif - } - - bool IsOpen() const { return _openState == OpenState_Open; } - - void CloseSocket() - { - if ((_openState.fetch_or(OpenState_Closed) & OpenState_Closed) == 0) - return; - - boost::system::error_code shutdownError; - _socket.shutdown(boost::asio::socket_base::shutdown_send, shutdownError); - if (shutdownError) - TC_LOG_DEBUG("network", "Socket::CloseSocket: {} errored when shutting down socket: {} ({})", GetRemoteIpAddress().to_string(), - shutdownError.value(), shutdownError.message()); - - this->OnClose(); - } - - /// Marks the socket for closing after write buffer becomes empty - void DelayedCloseSocket() - { - if (_openState.fetch_or(OpenState_Closing) != 0) - return; - - if (_writeQueue.empty()) - CloseSocket(); - } - - MessageBuffer& GetReadBuffer() { return _readBuffer; } - - Stream& underlying_stream() - { - return _socket; - } - -protected: - virtual void OnClose() { } - - virtual SocketReadCallbackResult ReadHandler() { return SocketReadCallbackResult::KeepReading; } - - bool AsyncProcessQueue() - { - if (_isWritingAsync) - return false; - - _isWritingAsync = true; - -#ifdef TC_SOCKET_USE_IOCP - MessageBuffer& buffer = _writeQueue.front(); - _socket.async_write_some(boost::asio::buffer(buffer.GetReadPointer(), buffer.GetActiveSize()), - [self = this->shared_from_this()](boost::system::error_code const& error, std::size_t transferedBytes) - { - self->WriteHandler(error, transferedBytes); - }); -#else - _socket.async_wait(boost::asio::socket_base::wait_type::wait_write, - [self = this->shared_from_this()](boost::system::error_code const& error) - { - self->WriteHandlerWrapper(error); - }); -#endif - - return false; - } - - void SetNoDelay(bool enable) - { - boost::system::error_code err; - _socket.set_option(boost::asio::ip::tcp::no_delay(enable), err); - if (err) - TC_LOG_DEBUG("network", "Socket::SetNoDelay: failed to set_option(boost::asio::ip::tcp::no_delay) for {} - {} ({})", - GetRemoteIpAddress().to_string(), err.value(), err.message()); - } - -private: - bool ReadHandlerInternal(boost::system::error_code const& error, size_t transferredBytes) - { - if (error) - { - CloseSocket(); - return false; - } - - _readBuffer.WriteCompleted(transferredBytes); - return IsOpen(); - } - -#ifdef TC_SOCKET_USE_IOCP - - void WriteHandler(boost::system::error_code const& error, std::size_t transferedBytes) - { - if (!error) - { - _isWritingAsync = false; - _writeQueue.front().ReadCompleted(transferedBytes); - if (!_writeQueue.front().GetActiveSize()) - _writeQueue.pop(); - - if (!_writeQueue.empty()) - AsyncProcessQueue(); - else if (_openState == OpenState_Closing) - CloseSocket(); - } - else - CloseSocket(); - } - -#else - - void WriteHandlerWrapper(boost::system::error_code const& /*error*/) - { - _isWritingAsync = false; - HandleQueue(); - } - - bool HandleQueue() - { - if (_writeQueue.empty()) - return false; - - MessageBuffer& queuedMessage = _writeQueue.front(); - - std::size_t bytesToSend = queuedMessage.GetActiveSize(); - - boost::system::error_code error; - std::size_t bytesSent = _socket.write_some(boost::asio::buffer(queuedMessage.GetReadPointer(), bytesToSend), error); - - if (error) - { - if (error == boost::asio::error::would_block || error == boost::asio::error::try_again) - return AsyncProcessQueue(); - - _writeQueue.pop(); - if (_openState == OpenState_Closing && _writeQueue.empty()) - CloseSocket(); - return false; - } - else if (bytesSent == 0) - { - _writeQueue.pop(); - if (_openState == OpenState_Closing && _writeQueue.empty()) - CloseSocket(); - return false; - } - else if (bytesSent < bytesToSend) // now n > 0 - { - queuedMessage.ReadCompleted(bytesSent); - return AsyncProcessQueue(); - } - - _writeQueue.pop(); - if (_openState == OpenState_Closing && _writeQueue.empty()) - CloseSocket(); - return !_writeQueue.empty(); - } - -#endif - - Stream _socket; - - boost::asio::ip::address _remoteAddress; - uint16 _remotePort = 0; - - MessageBuffer _readBuffer = MessageBuffer(READ_BLOCK_SIZE); - std::queue _writeQueue; - - // Socket open state "enum" (not enum to enable integral std::atomic api) - static constexpr uint8 OpenState_Open = 0x0; - static constexpr uint8 OpenState_Closing = 0x1; ///< Transition to Closed state after sending all queued data - static constexpr uint8 OpenState_Closed = 0x2; - - std::atomic _openState; - - bool _isWritingAsync = false; -}; -} - -#endif // TRINITYCORE_SOCKET_H diff --git a/src/server/shared/Networking/SocketMgr.h b/src/server/shared/Networking/SocketMgr.h deleted file mode 100644 index 07252355308..00000000000 --- a/src/server/shared/Networking/SocketMgr.h +++ /dev/null @@ -1,146 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_SOCKET_MGR_H -#define TRINITYCORE_SOCKET_MGR_H - -#include "AsyncAcceptor.h" -#include "Errors.h" -#include "NetworkThread.h" -#include "Socket.h" -#include -#include - -namespace Trinity::Net -{ -template -class SocketMgr -{ -public: - SocketMgr(SocketMgr const&) = delete; - SocketMgr(SocketMgr&&) = delete; - SocketMgr& operator=(SocketMgr const&) = delete; - SocketMgr& operator=(SocketMgr&&) = delete; - - virtual ~SocketMgr() - { - ASSERT(!_threads && !_acceptor && !_threadCount, "StopNetwork must be called prior to SocketMgr destruction"); - } - - virtual bool StartNetwork(Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount) - { - ASSERT(threadCount > 0); - - std::unique_ptr acceptor = nullptr; - try - { - acceptor = std::make_unique(ioContext, bindIp, port); - } - catch (boost::system::system_error const& err) - { - TC_LOG_ERROR("network", "Exception caught in SocketMgr.StartNetwork ({}:{}): {}", bindIp, port, err.what()); - return false; - } - - if (!acceptor->Bind()) - { - TC_LOG_ERROR("network", "StartNetwork failed to bind socket acceptor"); - return false; - } - - _acceptor = std::move(acceptor); - _threadCount = threadCount; - _threads.reset(CreateThreads()); - - ASSERT(_threads); - - for (int32 i = 0; i < _threadCount; ++i) - _threads[i].Start(); - - _acceptor->SetSocketFactory([this]() { return GetSocketForAccept(); }); - - return true; - } - - virtual void StopNetwork() - { - _acceptor->Close(); - - for (int32 i = 0; i < _threadCount; ++i) - _threads[i].Stop(); - - Wait(); - - _acceptor = nullptr; - _threads = nullptr; - _threadCount = 0; - } - - void Wait() - { - for (int32 i = 0; i < _threadCount; ++i) - _threads[i].Wait(); - } - - virtual void OnSocketOpen(IoContextTcpSocket&& sock, uint32 threadIndex) - { - try - { - std::shared_ptr newSocket = std::make_shared(std::move(sock)); - newSocket->Start(); - - _threads[threadIndex].AddSocket(newSocket); - } - catch (boost::system::system_error const& err) - { - TC_LOG_WARN("network", "Failed to retrieve client's remote address {}", err.what()); - } - } - - int32 GetNetworkThreadCount() const { return _threadCount; } - - uint32 SelectThreadWithMinConnections() const - { - uint32 min = 0; - - for (int32 i = 1; i < _threadCount; ++i) - if (_threads[i].GetConnectionCount() < _threads[min].GetConnectionCount()) - min = i; - - return min; - } - - std::pair GetSocketForAccept() - { - uint32 threadIndex = SelectThreadWithMinConnections(); - return std::make_pair(_threads[threadIndex].GetSocketForAccept(), threadIndex); - } - -protected: - SocketMgr() : _threadCount(0) - { - } - - virtual NetworkThread* CreateThreads() const = 0; - - std::unique_ptr _acceptor; - std::unique_ptr[]> _threads; - int32 _threadCount; -}; -} - -#endif // TRINITYCORE_SOCKET_MGR_H diff --git a/src/server/shared/Networking/SslStream.h b/src/server/shared/Networking/SslStream.h deleted file mode 100644 index 2cced44e5ff..00000000000 --- a/src/server/shared/Networking/SslStream.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#ifndef TRINITYCORE_SSL_STREAM_H -#define TRINITYCORE_SSL_STREAM_H - -#include "SocketConnectionInitializer.h" -#include -#include -#include - -namespace Trinity::Net -{ -template -struct SslHandshakeConnectionInitializer final : SocketConnectionInitializer -{ - explicit SslHandshakeConnectionInitializer(SocketImpl* socket) : _socket(socket) { } - - void Start() override - { - _socket->underlying_stream().async_handshake(boost::asio::ssl::stream_base::server, - [socketRef = _socket->weak_from_this(), self = this->shared_from_this()](boost::system::error_code const& error) - { - std::shared_ptr socket = static_pointer_cast(socketRef.lock()); - if (!socket) - return; - - if (error) - { - TC_LOG_ERROR("session", "{} SSL Handshake failed {}", socket->GetClientInfo(), error.message()); - socket->CloseSocket(); - return; - } - - if (self->next) - self->next->Start(); - }); - } - -private: - SocketImpl* _socket; -}; - -template -class SslStream -{ -public: - explicit SslStream(IoContextTcpSocket&& socket, boost::asio::ssl::context& sslContext) : _sslSocket(std::move(socket), sslContext) - { - _sslSocket.set_verify_mode(boost::asio::ssl::verify_none); - } - - explicit SslStream(boost::asio::io_context& context, boost::asio::ssl::context& sslContext) : _sslSocket(context, sslContext) - { - _sslSocket.set_verify_mode(boost::asio::ssl::verify_none); - } - - // adapting tcp::socket api - void close(boost::system::error_code& error) - { - _sslSocket.next_layer().close(error); - } - - void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError) - { - _sslSocket.shutdown(shutdownError); - _sslSocket.next_layer().shutdown(what, shutdownError); - } - - template - void async_read_some(MutableBufferSequence const& buffers, ReadHandlerType&& handler) - { - _sslSocket.async_read_some(buffers, std::forward(handler)); - } - - template - void async_write_some(ConstBufferSequence const& buffers, WriteHandlerType&& handler) - { - _sslSocket.async_write_some(buffers, std::forward(handler)); - } - - template - std::size_t write_some(ConstBufferSequence const& buffers, boost::system::error_code& error) - { - return _sslSocket.write_some(buffers, error); - } - - template - void async_wait(boost::asio::socket_base::wait_type type, WaitHandlerType&& handler) - { - _sslSocket.next_layer().async_wait(type, std::forward(handler)); - } - - template - void set_option(SettableSocketOption const& option, boost::system::error_code& error) - { - _sslSocket.next_layer().set_option(option, error); - } - - IoContextTcpSocket::endpoint_type remote_endpoint() const - { - return _sslSocket.next_layer().remote_endpoint(); - } - - // ssl api - template - void async_handshake(boost::asio::ssl::stream_base::handshake_type type, HandshakeHandlerType&& handler) - { - _sslSocket.async_handshake(type, std::forward(handler)); - } - -protected: - boost::asio::ssl::stream _sslSocket; -}; -} - -#endif // TRINITYCORE_SSL_STREAM_H diff --git a/src/server/shared/Realm/RealmList.cpp b/src/server/shared/Realm/RealmList.cpp index fb720182bf4..c0e6ba5b2a6 100644 --- a/src/server/shared/Realm/RealmList.cpp +++ b/src/server/shared/Realm/RealmList.cpp @@ -68,7 +68,7 @@ void RealmList::Initialize(Trinity::Asio::IoContext& ioContext, uint32 updateInt { _updateInterval = updateInterval; _updateTimer = std::make_unique(ioContext); - _resolver = std::make_unique(ioContext); + _resolver = std::make_unique(ioContext); ClientBuild::LoadBuildInfo(); // Get the content of the realmlist table in the database diff --git a/src/server/shared/Realm/RealmList.h b/src/server/shared/Realm/RealmList.h index ddb42ae7e1f..fdc0cb393b8 100644 --- a/src/server/shared/Realm/RealmList.h +++ b/src/server/shared/Realm/RealmList.h @@ -86,7 +86,7 @@ private: std::unordered_set _subRegions; uint32 _updateInterval; std::unique_ptr _updateTimer; - std::unique_ptr _resolver; + std::unique_ptr _resolver; Optional _currentRealmId; }; diff --git a/src/tools/extractor_common/CMakeLists.txt b/src/tools/extractor_common/CMakeLists.txt index 8455731db7e..cded754666d 100644 --- a/src/tools/extractor_common/CMakeLists.txt +++ b/src/tools/extractor_common/CMakeLists.txt @@ -19,7 +19,8 @@ target_link_libraries(extractor_common trinity-core-interface PUBLIC casc - common) + common + network) target_include_directories(extractor_common PUBLIC diff --git a/src/tools/extractor_common/CascHandles.cpp b/src/tools/extractor_common/CascHandles.cpp index a08f1fb9a53..f2964a550a5 100644 --- a/src/tools/extractor_common/CascHandles.cpp +++ b/src/tools/extractor_common/CascHandles.cpp @@ -65,7 +65,7 @@ namespace sslContext.set_options(boost::asio::ssl::context::no_tlsv1_1, error); sslContext.set_default_verify_paths(error); - Trinity::Asio::Resolver resolver(ioContext); + Trinity::Net::Resolver resolver(ioContext); Optional endpoint = resolver.Resolve(boost::asio::ip::tcp::v4(), serverName, std::to_string(port)); if (!endpoint) -- cgit v1.2.3