diff --git a/.gitmodules b/.gitmodules index d2ce2c855..93fac9d1f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "lib/SQLiteCpp"] path = lib/SQLiteCpp url = https://github.com/mc-server/SQLiteCpp.git +[submodule "lib/libevent"] + path = lib/libevent + url = https://github.com/mc-server/libevent.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 493cecdb3..5b3fd5e7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,13 @@ set(SQLITECPP_BUILD_EXAMPLES OFF CACHE BOOL "Build examples." set(SQLITECPP_BUILD_TESTS OFF CACHE BOOL "Build and run tests." FORCE) set(SQLITECPP_INTERNAL_SQLITE OFF CACHE BOOL "Add the internal SQLite3 source to the project." FORCE) +# Set options for LibEvent, disable all their tests and benchmarks: +set(EVENT__DISABLE_OPENSSL YES CACHE BOOL "Disable OpenSSL in LibEvent" FORCE) +set(EVENT__DISABLE_BENCHMARK YES CACHE BOOL "Disable LibEvent benchmarks" FORCE) +set(EVENT__DISABLE_TESTS YES CACHE BOOL "Disable LibEvent tests" FORCE) +set(EVENT__DISABLE_REGRESS YES CACHE BOOL "Disable LibEvent regression tests" FORCE) +set(EVENT__DISABLE_SAMPLES YES CACHE BOOL "Disable LibEvent samples" FORCE) + # Check that the libraries are present: if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/SQLiteCpp/CMakeLists.txt) message(FATAL_ERROR "SQLiteCpp is missing in folder lib/SQLiteCpp. Have you initialized the submodules / downloaded the extra libraries?") @@ -102,6 +109,9 @@ endif() if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/polarssl/CMakeLists.txt) message(FATAL_ERROR "PolarSSL is missing in folder lib/polarssl. Have you initialized the submodules / downloaded the extra libraries?") endif() +if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/libevent/CMakeLists.txt) + message(FATAL_ERROR "LibEvent is missing in folder lib/libevent. Have you initialized and updated the submodules / downloaded the extra libraries?") +endif() # Include all the libraries: add_subdirectory(lib/jsoncpp/) @@ -112,6 +122,7 @@ add_subdirectory(lib/sqlite/) add_subdirectory(lib/SQLiteCpp/) add_subdirectory(lib/expat/) add_subdirectory(lib/luaexpat/) +add_subdirectory(lib/libevent/) # Add proper include directories so that SQLiteCpp can find SQLite3: get_property(SQLITECPP_INCLUDES DIRECTORY "lib/SQLiteCpp/" PROPERTY INCLUDE_DIRECTORIES) @@ -119,6 +130,9 @@ set(SQLITECPP_INCLUDES "${SQLITECPP_INCLUDES}" "${CMAKE_CURRENT_SOURCE_DIR}/lib/ set_property(DIRECTORY lib/SQLiteCpp/ PROPERTY INCLUDE_DIRECTORIES "${SQLITECPP_INCLUDES}") set_property(TARGET SQLiteCpp PROPERTY INCLUDE_DIRECTORIES "${SQLITECPP_INCLUDES}") +# Add proper includes for LibEvent's event-config.h header: +include_directories(SYSTEM ${LIBEVENT_INCLUDE_DIRS}) + if (WIN32) add_subdirectory(lib/luaproxy/) endif() @@ -126,13 +140,14 @@ endif() # We use EXCLUDE_FROM_ALL so that only the explicit dependencies are used # (PolarSSL also has test and example programs in their CMakeLists.txt, we don't want those) -include(lib/polarssl.cmake) +include(lib/polarssl.cmake EXCLUDE_FROM_ALL) set_exe_flags() add_subdirectory (src) if(${SELF_TEST}) + message("Tests enabled") enable_testing() add_subdirectory (tests) endif() diff --git a/lib/libevent b/lib/libevent new file mode 160000 index 000000000..0b49ae345 --- /dev/null +++ b/lib/libevent @@ -0,0 +1 @@ +Subproject commit 0b49ae34594533daa82c06a506078de9e336a013 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 997326cc7..c39f5f6e6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ project (MCServer) include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/") include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/jsoncpp/include") include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/polarssl/include") +include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/libevent/include") set(FOLDERS OSSupport HTTPServer Items Blocks Protocol Generating PolarSSL++ Bindings @@ -323,4 +324,4 @@ endif () if (WIN32) target_link_libraries(${EXECUTABLE} expat tolualib ws2_32.lib Psapi.lib) endif() -target_link_libraries(${EXECUTABLE} luaexpat jsoncpp polarssl zlib sqlite lua SQLiteCpp) +target_link_libraries(${EXECUTABLE} luaexpat jsoncpp polarssl zlib sqlite lua SQLiteCpp event_core event_extra) diff --git a/src/Globals.h b/src/Globals.h index f0726454b..654ede95f 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -268,33 +268,47 @@ template class SizeChecker; #include "OSSupport/StackTrace.h" #else // Logging functions -void inline LOGERROR(const char* a_Format, ...) FORMATSTRING(1, 2); +void inline LOGERROR(const char * a_Format, ...) FORMATSTRING(1, 2); -void inline LOGERROR(const char* a_Format, ...) +void inline LOGERROR(const char * a_Format, ...) { va_list argList; va_start(argList, a_Format); vprintf(a_Format, argList); + putchar('\n'); va_end(argList); } -void inline LOGWARNING(const char* a_Format, ...) FORMATSTRING(1, 2); +void inline LOGWARNING(const char * a_Format, ...) FORMATSTRING(1, 2); -void inline LOGWARNING(const char* a_Format, ...) +void inline LOGWARNING(const char * a_Format, ...) { va_list argList; va_start(argList, a_Format); vprintf(a_Format, argList); + putchar('\n'); va_end(argList); } -void inline LOGD(const char* a_Format, ...) FORMATSTRING(1, 2); +void inline LOGD(const char * a_Format, ...) FORMATSTRING(1, 2); -void inline LOGD(const char* a_Format, ...) +void inline LOGD(const char * a_Format, ...) { va_list argList; va_start(argList, a_Format); vprintf(a_Format, argList); + putchar('\n'); + va_end(argList); +} + +void inline LOG(const char * a_Format, ...) FORMATSTRING(1, 2); + +void inline LOG(const char * a_Format, ...) +{ + va_list argList; + va_start(argList, a_Format); + vprintf(a_Format, argList); + putchar('\n'); va_end(argList); } diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index e943ceb18..9424b63da 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -10,12 +10,17 @@ SET (SRCS Event.cpp File.cpp GZipFile.cpp + HostnameLookup.cpp + IPLookup.cpp IsThread.cpp ListenThread.cpp + NetworkSingleton.cpp Semaphore.cpp + ServerHandleImpl.cpp Socket.cpp SocketThreads.cpp StackTrace.cpp + TCPLinkImpl.cpp ) SET (HDRS @@ -24,13 +29,19 @@ SET (HDRS Event.h File.h GZipFile.h + HostnameLookup.h + IPLookup.h IsThread.h ListenThread.h + Network.h + NetworkSingleton.h Queue.h Semaphore.h + ServerHandleImpl.h Socket.h SocketThreads.h StackTrace.h + TCPLinkImpl.h ) if(NOT MSVC) diff --git a/src/OSSupport/CriticalSection.cpp b/src/OSSupport/CriticalSection.cpp index 13a3e4d9f..5248356c5 100644 --- a/src/OSSupport/CriticalSection.cpp +++ b/src/OSSupport/CriticalSection.cpp @@ -1,5 +1,6 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules +#include "CriticalSection.h" diff --git a/src/OSSupport/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp new file mode 100644 index 000000000..3a2997ffd --- /dev/null +++ b/src/OSSupport/HostnameLookup.cpp @@ -0,0 +1,124 @@ + +// HostnameLookup.cpp + +// Implements the cHostnameLookup class representing an in-progress hostname-to-IP lookup + +#include "Globals.h" +#include "HostnameLookup.h" +#include +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cHostnameLookup: + +cHostnameLookup::cHostnameLookup(cNetwork::cResolveNameCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks) +{ +} + + + + + +void cHostnameLookup::Lookup(const AString & a_Hostname) +{ + // Store the hostname for the callback: + m_Hostname = a_Hostname; + + // Start the lookup: + // Note that we don't have to store the LibEvent lookup handle, LibEvent will free it on its own. + evutil_addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + hints.ai_flags = EVUTIL_AI_CANONNAME; + evdns_getaddrinfo(cNetworkSingleton::Get().GetDNSBase(), a_Hostname.c_str(), nullptr, &hints, Callback, this); +} + + + + + +void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a_Self) +{ + // Get the Self class: + cHostnameLookup * Self = reinterpret_cast(a_Self); + ASSERT(Self != nullptr); + + // If an error has occurred, notify the error callback: + if (a_ErrCode != 0) + { + Self->m_Callbacks->OnError(a_ErrCode, evutil_socket_error_to_string(a_ErrCode)); + cNetworkSingleton::Get().RemoveHostnameLookup(Self); + return; + } + + // Call the success handler for each entry received: + bool HasResolved = false; + evutil_addrinfo * OrigAddr = a_Addr; + for (;a_Addr != nullptr; a_Addr = a_Addr->ai_next) + { + char IP[128]; + switch (a_Addr->ai_family) + { + case AF_INET: // IPv4 + { + sockaddr_in * sin = reinterpret_cast(a_Addr->ai_addr); + evutil_inet_ntop(AF_INET, &(sin->sin_addr), IP, sizeof(IP)); + break; + } + case AF_INET6: // IPv6 + { + sockaddr_in6 * sin = reinterpret_cast(a_Addr->ai_addr); + evutil_inet_ntop(AF_INET6, &(sin->sin6_addr), IP, sizeof(IP)); + break; + } + default: + { + // Unknown address family, handle as if this entry wasn't received + continue; // for (a_Addr) + } + } + Self->m_Callbacks->OnNameResolved(Self->m_Hostname, IP); + HasResolved = true; + } // for (a_Addr) + + // If only unsupported families were reported, call the Error handler: + if (!HasResolved) + { + Self->m_Callbacks->OnError(DNS_ERR_NODATA, "The name does not resolve to any known address."); + } + else + { + Self->m_Callbacks->OnFinished(); + } + evutil_freeaddrinfo(OrigAddr); + cNetworkSingleton::Get().RemoveHostnameLookup(Self); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +bool cNetwork::HostnameToIP( + const AString & a_Hostname, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + auto Lookup = std::make_shared(a_Callbacks); + cNetworkSingleton::Get().AddHostnameLookup(Lookup); + Lookup->Lookup(a_Hostname); + return true; +} + + + + diff --git a/src/OSSupport/HostnameLookup.h b/src/OSSupport/HostnameLookup.h new file mode 100644 index 000000000..d69f24707 --- /dev/null +++ b/src/OSSupport/HostnameLookup.h @@ -0,0 +1,47 @@ + +// HostnameLookup.h + +// Declares the cHostnameLookup class representing an in-progress hostname-to-IP lookup + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include + + + + + +/** Holds information about an in-progress Hostname-to-IP lookup. */ +class cHostnameLookup +{ +public: + /** Creates the lookup object. Doesn't start the lookup yet. */ + cHostnameLookup(cNetwork::cResolveNameCallbacksPtr a_Callbacks); + + /** Starts the lookup. */ + void Lookup(const AString & a_Hostname); + +protected: + + /** The callbacks to call for resolved names / errors. */ + cNetwork::cResolveNameCallbacksPtr m_Callbacks; + + /** The hostname that was queried (needed for the callbacks). */ + AString m_Hostname; + + static void Callback(int a_ErrCode, struct evutil_addrinfo * a_Addr, void * a_Self); +}; +typedef SharedPtr cHostnameLookupPtr; +typedef std::vector cHostnameLookupPtrs; + + + + + diff --git a/src/OSSupport/IPLookup.cpp b/src/OSSupport/IPLookup.cpp new file mode 100644 index 000000000..8cdc5132d --- /dev/null +++ b/src/OSSupport/IPLookup.cpp @@ -0,0 +1,111 @@ + +// IPLookup.cpp + +// Implements the cIPLookup class representing an IP-to-hostname lookup in progress. + +#include "Globals.h" +#include "IPLookup.h" +#include +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cIPLookup: + +cIPLookup::cIPLookup(cNetwork::cResolveNameCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks) +{ + ASSERT(a_Callbacks != nullptr); +} + + + + + +bool cIPLookup::Lookup(const AString & a_IP) +{ + // Parse the IP address string into a sockaddr structure: + m_IP = a_IP; + sockaddr_storage sa; + int salen = static_cast(sizeof(sa)); + memset(&sa, 0, sizeof(sa)); + if (evutil_parse_sockaddr_port(a_IP.c_str(), reinterpret_cast(&sa), &salen) != 0) + { + LOGD("Failed to parse IP address \"%s\".", a_IP.c_str()); + return false; + } + + // Call the proper resolver based on the address family: + // Note that there's no need to store the evdns_request handle returned, LibEvent frees it on its own. + switch (sa.ss_family) + { + case AF_INET: + { + sockaddr_in * sa4 = reinterpret_cast(&sa); + evdns_base_resolve_reverse(cNetworkSingleton::Get().GetDNSBase(), &(sa4->sin_addr), 0, Callback, this); + break; + } + case AF_INET6: + { + sockaddr_in6 * sa6 = reinterpret_cast(&sa); + evdns_base_resolve_reverse_ipv6(cNetworkSingleton::Get().GetDNSBase(), &(sa6->sin6_addr), 0, Callback, this); + break; + } + default: + { + LOGWARNING("%s: Unknown address family: %d", __FUNCTION__, sa.ss_family); + ASSERT(!"Unknown address family"); + return false; + } + } // switch (address family) + return true; +} + + + + + +void cIPLookup::Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self) +{ + // Get the Self class: + cIPLookup * Self = reinterpret_cast(a_Self); + ASSERT(Self != nullptr); + + // Call the proper callback based on the event received: + if ((a_Result != 0) || (a_Addresses == nullptr)) + { + // An error has occurred, notify the error callback: + Self->m_Callbacks->OnError(a_Result, evutil_socket_error_to_string(a_Result)); + } + else + { + // Call the success handler: + Self->m_Callbacks->OnNameResolved(*(reinterpret_cast(a_Addresses)), Self->m_IP); + Self->m_Callbacks->OnFinished(); + } + cNetworkSingleton::Get().RemoveIPLookup(Self); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +bool cNetwork::IPToHostName( + const AString & a_IP, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + auto res = std::make_shared(a_Callbacks); + cNetworkSingleton::Get().AddIPLookup(res); + return res->Lookup(a_IP); +} + + + + diff --git a/src/OSSupport/IPLookup.h b/src/OSSupport/IPLookup.h new file mode 100644 index 000000000..af878cbf1 --- /dev/null +++ b/src/OSSupport/IPLookup.h @@ -0,0 +1,49 @@ + +// IPLookup.h + +// Declares the cIPLookup class representing an IP-to-hostname lookup in progress. + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" + + + + + +/** Holds information about an in-progress IP-to-Hostname lookup. */ +class cIPLookup +{ +public: + /** Creates the lookup object. Doesn't start the lookup yet. */ + cIPLookup(cNetwork::cResolveNameCallbacksPtr a_Callbacks); + + /** Starts the lookup. + Returns true if lookup started successfully, false on failure (invalid IP format etc.) */ + bool Lookup(const AString & a_IP); + +protected: + + /** The callbacks to call for resolved names / errors. */ + cNetwork::cResolveNameCallbacksPtr m_Callbacks; + + /** The IP that was queried (needed for the callbacks). */ + AString m_IP; + + + /** Callback that is called by LibEvent when there's an event for the request. */ + static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); +}; +typedef SharedPtr cIPLookupPtr; +typedef std::vector cIPLookupPtrs; + + + + + diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h new file mode 100644 index 000000000..cdf6ba0e9 --- /dev/null +++ b/src/OSSupport/Network.h @@ -0,0 +1,246 @@ + +// Network.h + +// Declares the classes used for the Network API + + + + + +#pragma once + + + + + +// fwd: +class cTCPLink; +typedef SharedPtr cTCPLinkPtr; +typedef std::vector cTCPLinkPtrs; +class cServerHandle; +typedef SharedPtr cServerHandlePtr; +typedef std::vector cServerHandlePtrs; + + + + + +/** Interface that provides the methods available on a single TCP connection. */ +class cTCPLink +{ + friend class cNetwork; + +public: + class cCallbacks + { + public: + // Force a virtual destructor for all descendants: + virtual ~cCallbacks() {} + + /** Called when the cTCPLink for the connection is created. + The callback may store the cTCPLink instance for later use, but it should remove it in OnError(), OnRemoteClosed() or right after Close(). */ + virtual void OnLinkCreated(cTCPLinkPtr a_Link) = 0; + + /** Called when there's data incoming from the remote peer. */ + virtual void OnReceivedData(const char * a_Data, size_t a_Length) = 0; + + /** Called when the remote end closes the connection. + The link is still available for connection information query (IP / port). + Sending data on the link is not an error, but the data won't be delivered. */ + virtual void OnRemoteClosed(void) = 0; + + /** Called when an error is detected on the connection. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + }; + typedef SharedPtr cCallbacksPtr; + + + // Force a virtual destructor for all descendants: + virtual ~cTCPLink() {} + + /** Queues the specified data for sending to the remote peer. + Returns true on success, false on failure. Note that this success or failure only reports the queue status, not the actual data delivery. */ + virtual bool Send(const void * a_Data, size_t a_Length) = 0; + + /** Queues the specified data for sending to the remote peer. + Returns true on success, false on failure. Note that this success or failure only reports the queue status, not the actual data delivery. */ + bool Send(const AString & a_Data) + { + return Send(a_Data.data(), a_Data.size()); + } + + /** Returns the IP address of the local endpoint of the connection. */ + virtual AString GetLocalIP(void) const = 0; + + /** Returns the port used by the local endpoint of the connection. */ + virtual UInt16 GetLocalPort(void) const = 0; + + /** Returns the IP address of the remote endpoint of the connection. */ + virtual AString GetRemoteIP(void) const = 0; + + /** Returns the port used by the remote endpoint of the connection. */ + virtual UInt16 GetRemotePort(void) const = 0; + + /** Closes the link gracefully. + The link will send any queued outgoing data, then it will send the FIN packet. + The link will still receive incoming data from remote until the remote closes the connection. */ + virtual void Shutdown(void) = 0; + + /** Drops the connection without any more processing. + Sends the RST packet, queued outgoing and incoming data is lost. */ + virtual void Close(void) = 0; + +protected: + /** Callbacks to be used for the various situations. */ + cCallbacksPtr m_Callbacks; + + + /** Creates a new link, with the specified callbacks. */ + cTCPLink(cCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks) + { + } +}; + + + + + +/** Interface that provides the methods available on a listening server socket. */ +class cServerHandle +{ + friend class cNetwork; +public: + + // Force a virtual destructor for all descendants: + virtual ~cServerHandle() {} + + /** Stops the server, no more incoming connections will be accepted. + All current connections will be shut down (cTCPLink::Shutdown()). */ + virtual void Close(void) = 0; + + /** Returns true if the server has been started correctly and is currently listening for incoming connections. */ + virtual bool IsListening(void) const = 0; +}; + + + + + +class cNetwork +{ +public: + /** Callbacks used for connecting to other servers as a client. */ + class cConnectCallbacks + { + public: + // Force a virtual destructor for all descendants: + virtual ~cConnectCallbacks() {} + + /** Called when the Connect call succeeds. + Provides the newly created link that can be used for communication. */ + virtual void OnConnected(cTCPLink & a_Link) = 0; + + /** Called when the Connect call fails. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + }; + typedef SharedPtr cConnectCallbacksPtr; + + + /** Callbacks used when listening for incoming connections as a server. */ + class cListenCallbacks + { + public: + // Force a virtual destructor for all descendants: + virtual ~cListenCallbacks() {} + + /** Called when the TCP server created with Listen() receives a new incoming connection. + Returns the link callbacks that the server should use for the newly created link. + If a nullptr is returned, the connection is dropped immediately; + otherwise a new cTCPLink instance is created and OnAccepted() is called. */ + virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) = 0; + + /** Called when the TCP server created with Listen() creates a new link for an incoming connection. + Provides the newly created Link that can be used for communication. + Called right after a successful OnIncomingConnection(). */ + virtual void OnAccepted(cTCPLink & a_Link) = 0; + + /** Called when the socket fails to listen on the specified port. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + }; + typedef SharedPtr cListenCallbacksPtr; + + + /** Callbacks used when resolving names to IPs. */ + class cResolveNameCallbacks + { + public: + // Force a virtual destructor for all descendants: + virtual ~cResolveNameCallbacks() {} + + /** Called when the hostname is successfully resolved into an IP address. + May be called multiple times if a name resolves to multiple addresses. + a_IP may be either an IPv4 or an IPv6 address with their proper formatting. */ + virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) = 0; + + /** Called when an error is encountered while resolving. + If an error is reported, the OnFinished() callback is not called. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + + /** Called when all the addresses resolved have been reported via the OnNameResolved() callback. + Only called if there was no error reported. */ + virtual void OnFinished(void) = 0; + }; + typedef SharedPtr cResolveNameCallbacksPtr; + + + /** Queues a TCP connection to be made to the specified host. + Calls one the connection callbacks (success, error) when the connection is successfully established, or upon failure. + The a_LinkCallbacks is passed to the newly created cTCPLink. + Returns true if queueing was successful, false on failure to queue. + Note that the return value doesn't report the success of the actual connection; the connection is established asynchronously in the background. + Implemented in TCPLinkImpl.cpp. */ + static bool Connect( + const AString & a_Host, + UInt16 a_Port, + cConnectCallbacksPtr a_ConnectCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); + + + /** Opens up the specified port for incoming connections. + Calls an OnAccepted callback for each incoming connection. + A cTCPLink with the specified link callbacks is created for each connection. + Returns a cServerHandle that can be used to query the operation status and close the server. + Implemented in ServerHandleImpl.cpp. */ + static cServerHandlePtr Listen( + UInt16 a_Port, + cListenCallbacksPtr a_ListenCallbacks + ); + + + /** Queues a DNS query to resolve the specified hostname to IP address. + Calls one of the callbacks when the resolving succeeds, or when it fails. + Returns true if queueing was successful, false if not. + Note that the return value doesn't report the success of the actual lookup; the lookup happens asynchronously on the background. + Implemented in HostnameLookup.cpp. */ + static bool HostnameToIP( + const AString & a_Hostname, + cResolveNameCallbacksPtr a_Callbacks + ); + + + /** Queues a DNS query to resolve the specified IP address to a hostname. + Calls one of the callbacks when the resolving succeeds, or when it fails. + Returns true if queueing was successful, false if not. + Note that the return value doesn't report the success of the actual lookup; the lookup happens asynchronously on the background. + Implemented in IPLookup.cpp. */ + static bool IPToHostName( + const AString & a_IP, + cResolveNameCallbacksPtr a_Callbacks + ); +}; + + + + diff --git a/src/OSSupport/NetworkSingleton.cpp b/src/OSSupport/NetworkSingleton.cpp new file mode 100644 index 000000000..92f0604cd --- /dev/null +++ b/src/OSSupport/NetworkSingleton.cpp @@ -0,0 +1,245 @@ + +// NetworkSingleton.cpp + +// Implements the cNetworkSingleton class representing the storage for global data pertaining to network API +// such as a list of all connections, all listening sockets and the LibEvent dispatch thread. + +#include "Globals.h" +#include "NetworkSingleton.h" +#include +#include +#include +#include +#include +#include "IPLookup.h" +#include "HostnameLookup.h" + + + + + +cNetworkSingleton::cNetworkSingleton(void) +{ + // Windows: initialize networking: + #ifdef _WIN32 + WSADATA wsaData; + memset(&wsaData, 0, sizeof(wsaData)); + int res = WSAStartup (MAKEWORD(2, 2), &wsaData); + if (res != 0) + { + int err = WSAGetLastError(); + LOGWARNING("WSAStartup failed: %d, WSAGLE = %d (%s)", res, err, evutil_socket_error_to_string(err)); + exit(1); + } + #endif // _WIN32 + + // Initialize LibEvent logging: + event_set_log_callback(LogCallback); + + // Initialize threading: + #if defined(EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED) + evthread_use_windows_threads(); + #elif defined(EVTHREAD_USE_PTHREADS_IMPLEMENTED) + evthread_use_pthreads(); + #else + #error No threading implemented for EVTHREAD + #endif + + // Create the main event_base: + m_EventBase = event_base_new(); + if (m_EventBase == nullptr) + { + LOGERROR("Failed to initialize LibEvent. The server will now terminate."); + abort(); + } + + // Create the DNS lookup helper: + m_DNSBase = evdns_base_new(m_EventBase, 1); + if (m_DNSBase == nullptr) + { + LOGERROR("Failed to initialize LibEvent's DNS subsystem. The server will now terminate."); + abort(); + } + + // Create the event loop thread: + std::thread EventLoopThread(RunEventLoop, this); + EventLoopThread.detach(); +} + + + + + +cNetworkSingleton::~cNetworkSingleton() +{ + // Wait for the LibEvent event loop to terminate: + event_base_loopbreak(m_EventBase); + m_EventLoopTerminated.Wait(); + + // Remove all objects: + { + cCSLock Lock(m_CS); + m_Connections.clear(); + m_Servers.clear(); + m_HostnameLookups.clear(); + m_IPLookups.clear(); + } + + // Free the underlying LibEvent objects: + evdns_base_free(m_DNSBase, true); + event_base_free(m_EventBase); + + libevent_global_shutdown(); +} + + + + + +cNetworkSingleton & cNetworkSingleton::Get(void) +{ + static cNetworkSingleton Instance; + return Instance; +} + + + + + +void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg) +{ + switch (a_Severity) + { + case _EVENT_LOG_DEBUG: LOGD ("LibEvent: %s", a_Msg); break; + case _EVENT_LOG_MSG: LOG ("LibEvent: %s", a_Msg); break; + case _EVENT_LOG_WARN: LOGWARNING("LibEvent: %s", a_Msg); break; + case _EVENT_LOG_ERR: LOGERROR ("LibEvent: %s", a_Msg); break; + default: + { + LOGWARNING("LibEvent: Unknown log severity (%d): %s", a_Severity, a_Msg); + break; + } + } +} + + + + + +void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self) +{ + event_base_loop(a_Self->m_EventBase, EVLOOP_NO_EXIT_ON_EMPTY); + a_Self->m_EventLoopTerminated.Set(); +} + + + + + +void cNetworkSingleton::AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup) +{ + cCSLock Lock(m_CS); + m_HostnameLookups.push_back(a_HostnameLookup); +} + + + + + +void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameLookup) +{ + cCSLock Lock(m_CS); + for (auto itr = m_HostnameLookups.begin(), end = m_HostnameLookups.end(); itr != end; ++itr) + { + if (itr->get() == a_HostnameLookup) + { + m_HostnameLookups.erase(itr); + return; + } + } // for itr - m_HostnameLookups[] +} + + + + + +void cNetworkSingleton::AddIPLookup(cIPLookupPtr a_IPLookup) +{ + cCSLock Lock(m_CS); + m_IPLookups.push_back(a_IPLookup); +} + + + + + +void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup) +{ + cCSLock Lock(m_CS); + for (auto itr = m_IPLookups.begin(), end = m_IPLookups.end(); itr != end; ++itr) + { + if (itr->get() == a_IPLookup) + { + m_IPLookups.erase(itr); + return; + } + } // for itr - m_IPLookups[] +} + + + + + +void cNetworkSingleton::AddLink(cTCPLinkImplPtr a_Link) +{ + cCSLock Lock(m_CS); + m_Connections.push_back(a_Link); +} + + + + + +void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link) +{ + cCSLock Lock(m_CS); + for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) + { + if (itr->get() == a_Link) + { + m_Connections.erase(itr); + return; + } + } // for itr - m_Connections[] +} + + + + + +void cNetworkSingleton::AddServer(cServerHandleImplPtr a_Server) +{ + cCSLock Lock(m_CS); + m_Servers.push_back(a_Server); +} + + + + + +void cNetworkSingleton::RemoveServer(const cServerHandleImpl * a_Server) +{ + cCSLock Lock(m_CS); + for (auto itr = m_Servers.begin(), end = m_Servers.end(); itr != end; ++itr) + { + if (itr->get() == a_Server) + { + m_Servers.erase(itr); + return; + } + } // for itr - m_Servers[] +} + + + + diff --git a/src/OSSupport/NetworkSingleton.h b/src/OSSupport/NetworkSingleton.h new file mode 100644 index 000000000..1d26fc8f4 --- /dev/null +++ b/src/OSSupport/NetworkSingleton.h @@ -0,0 +1,134 @@ + +// NetworkSingleton.h + +// Declares the cNetworkSingleton class representing the storage for global data pertaining to network API +// such as a list of all connections, all listening sockets and the LibEvent dispatch thread. + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include "CriticalSection.h" +#include "Event.h" + + + + + +// fwd: +struct event_base; +struct evdns_base; +class cTCPLinkImpl; +typedef SharedPtr cTCPLinkImplPtr; +typedef std::vector cTCPLinkImplPtrs; +class cServerHandleImpl; +typedef SharedPtr cServerHandleImplPtr; +typedef std::vector cServerHandleImplPtrs; +class cHostnameLookup; +typedef SharedPtr cHostnameLookupPtr; +typedef std::vector cHostnameLookupPtrs; +class cIPLookup; +typedef SharedPtr cIPLookupPtr; +typedef std::vector cIPLookupPtrs; + + + + + +class cNetworkSingleton +{ +public: + ~cNetworkSingleton(); + + /** Returns the singleton instance of this class */ + static cNetworkSingleton & Get(void); + + /** Returns the main LibEvent handle for event registering. */ + event_base * GetEventBase(void) { return m_EventBase; } + + /** Returns the LibEvent handle for DNS lookups. */ + evdns_base * GetDNSBase(void) { return m_DNSBase; } + + /** Adds the specified hostname lookup to m_HostnameLookups. + Used by the underlying lookup implementation when a new lookup is initiated. */ + void AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup); + + /** Removes the specified hostname lookup from m_HostnameLookups. + Used by the underlying lookup implementation when the lookup is finished. */ + void RemoveHostnameLookup(const cHostnameLookup * a_HostnameLookup); + + /** Adds the specified IP lookup to M_IPLookups. + Used by the underlying lookup implementation when a new lookup is initiated. */ + void AddIPLookup(cIPLookupPtr a_IPLookup); + + /** Removes the specified IP lookup from m_IPLookups. + Used by the underlying lookup implementation when the lookup is finished. */ + void RemoveIPLookup(const cIPLookup * a_IPLookup); + + /** Adds the specified link to m_Connections. + Used by the underlying link implementation when a new link is created. */ + void AddLink(cTCPLinkImplPtr a_Link); + + /** Removes the specified link from m_Connections. + Used by the underlying link implementation when the link is closed / errored. */ + void RemoveLink(const cTCPLinkImpl * a_Link); + + /** Adds the specified link to m_Servers. + Used by the underlying server handle implementation when a new listening server is created. + Only servers that succeed in listening are added. */ + void AddServer(cServerHandleImplPtr a_Server); + + /** Removes the specified server from m_Servers. + Used by the underlying server handle implementation when the server is closed. */ + void RemoveServer(const cServerHandleImpl * a_Server); + +protected: + + /** The main LibEvent container for driving the event loop. */ + event_base * m_EventBase; + + /** The LibEvent handle for doing DNS lookups. */ + evdns_base * m_DNSBase; + + /** Container for all client connections, including ones with pending-connect. */ + cTCPLinkImplPtrs m_Connections; + + /** Container for all servers that are currently active. */ + cServerHandleImplPtrs m_Servers; + + /** Container for all pending hostname lookups. */ + cHostnameLookupPtrs m_HostnameLookups; + + /** Container for all pending IP lookups. */ + cIPLookupPtrs m_IPLookups; + + /** Mutex protecting all containers against multithreaded access. */ + cCriticalSection m_CS; + + /** Event that gets signalled when the event loop terminates. */ + cEvent m_EventLoopTerminated; + + + /** Initializes the LibEvent internals. */ + cNetworkSingleton(void); + + /** Converts LibEvent-generated log events into log messages in MCS log. */ + static void LogCallback(int a_Severity, const char * a_Msg); + + /** Implements the thread that runs LibEvent's event dispatcher loop. */ + static void RunEventLoop(cNetworkSingleton * a_Self); +}; + + + + + + + + + diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp new file mode 100644 index 000000000..ba38dbf2e --- /dev/null +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -0,0 +1,334 @@ + +// ServerHandleImpl.cpp + +// Implements the cServerHandleImpl class implementing the TCP server functionality + +#include "Globals.h" +#include "ServerHandleImpl.h" +#include "TCPLinkImpl.h" +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// Globals: + +static bool IsValidSocket(evutil_socket_t a_Socket) +{ + #ifdef _WIN32 + return (a_Socket != INVALID_SOCKET); + #else // _WIN32 + return (a_Socket >= 0); + #endif // else _WIN32 +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cServerHandleImpl: + +cServerHandleImpl::cServerHandleImpl(cNetwork::cListenCallbacksPtr a_ListenCallbacks): + m_ListenCallbacks(a_ListenCallbacks), + m_ConnListener(nullptr), + m_SecondaryConnListener(nullptr), + m_IsListening(false), + m_ErrorCode(0) +{ +} + + + + + +cServerHandleImpl::~cServerHandleImpl() +{ + if (m_ConnListener != nullptr) + { + evconnlistener_free(m_ConnListener); + } + if (m_SecondaryConnListener != nullptr) + { + evconnlistener_free(m_SecondaryConnListener); + } +} + + + + + +void cServerHandleImpl::Close(void) +{ + // Stop the listener sockets: + evconnlistener_disable(m_ConnListener); + if (m_SecondaryConnListener != nullptr) + { + evconnlistener_disable(m_SecondaryConnListener); + } + m_IsListening = false; + + // Shutdown all connections: + cTCPLinkImplPtrs Conns; + { + cCSLock Lock(m_CS); + std::swap(Conns, m_Connections); + } + for (auto conn: Conns) + { + conn->Shutdown(); + } + + // Remove the ptr to self, so that the object may be freed: + m_SelfPtr.reset(); +} + + + + + +cServerHandleImplPtr cServerHandleImpl::Listen( + UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks +) +{ + cServerHandleImplPtr res = cServerHandleImplPtr{new cServerHandleImpl(a_ListenCallbacks)}; + res->m_SelfPtr = res; + if (res->Listen(a_Port)) + { + cNetworkSingleton::Get().AddServer(res); + } + else + { + a_ListenCallbacks->OnError(res->m_ErrorCode, res->m_ErrorMsg); + res->m_SelfPtr.reset(); + } + return res; +} + + + + + +bool cServerHandleImpl::Listen(UInt16 a_Port) +{ + // Make sure the cNetwork internals are innitialized: + cNetworkSingleton::Get(); + + // Set up the main socket: + // It should listen on IPv6 with IPv4 fallback, when available; IPv4 when IPv6 is not available. + bool NeedsTwoSockets = false; + int err; + evutil_socket_t MainSock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(MainSock)) + { + // Failed to create IPv6 socket, create an IPv4 one instead: + err = EVUTIL_SOCKET_ERROR(); + LOGD("Failed to create IPv6 MainSock: %d (%s)", err, evutil_socket_error_to_string(err)); + MainSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(MainSock)) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot create socket for port %d: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); + return false; + } + + // Bind to all interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(a_Port); + if (bind(MainSock, reinterpret_cast(&name), sizeof(name)) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot bind IPv4 socket to port %d: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); + evutil_closesocket(MainSock); + return false; + } + } + else + { + // IPv6 socket created, switch it into "dualstack" mode: + UInt32 Zero = 0; + #ifdef _WIN32 + // WinXP doesn't support this feature, so if the setting fails, create another socket later on: + int res = setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&Zero), sizeof(Zero)); + err = EVUTIL_SOCKET_ERROR(); + NeedsTwoSockets = ((res == SOCKET_ERROR) && (err == WSAENOPROTOOPT)); + LOGD("setsockopt(IPV6_V6ONLY) returned %d, err is %d (%s). %s", + res, err, evutil_socket_error_to_string(err), + NeedsTwoSockets ? "Second socket will be created" : "Second socket not needed" + ); + #else + setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&Zero), sizeof(Zero)); + #endif + + // Bind to all interfaces: + sockaddr_in6 name; + memset(&name, 0, sizeof(name)); + name.sin6_family = AF_INET6; + name.sin6_port = ntohs(a_Port); + if (bind(MainSock, reinterpret_cast(&name), sizeof(name)) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot bind IPv6 socket to port %d: %d (%s)", a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)); + evutil_closesocket(MainSock); + return false; + } + } + if (evutil_make_socket_nonblocking(MainSock) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot make socket on port %d non-blocking: %d (%s)", a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)); + evutil_closesocket(MainSock); + return false; + } + if (listen(MainSock, 0) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot listen on port %d: %d (%s)", a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)); + evutil_closesocket(MainSock); + return false; + } + m_ConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, MainSock); + m_IsListening = true; + if (!NeedsTwoSockets) + { + return true; + } + + // If a secondary socket is required (WinXP dual-stack), create it here: + LOGD("Creating a second socket for IPv4"); + evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(SecondSock)) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("socket(AF_INET, ...) failed for secondary socket: %d, %s", err, evutil_socket_error_to_string(err)); + return true; // Report as success, the primary socket is working + } + + // Make the secondary socket nonblocking: + if (evutil_make_socket_nonblocking(SecondSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("evutil_make_socket_nonblocking() failed for secondary socket: %d, %s", err, evutil_socket_error_to_string(err)); + evutil_closesocket(SecondSock); + return true; // Report as success, the primary socket is working + } + + // Bind to all IPv4 interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(a_Port); + if (bind(SecondSock, reinterpret_cast(&name), sizeof(name)) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("Cannot bind secondary socket to port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(SecondSock); + return true; // Report as success, the primary socket is working + } + + if (listen(SecondSock, 0) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("Cannot listen on on secondary socket on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(SecondSock); + return true; // Report as success, the primary socket is working + } + + m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); + return true; +} + + + + + +void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_Socket, sockaddr * a_Addr, int a_Len, void * a_Self) +{ + // Cast to true self: + cServerHandleImpl * Self = reinterpret_cast(a_Self); + ASSERT(Self != nullptr); + ASSERT(Self->m_SelfPtr != nullptr); + + // Get the textual IP address and port number out of a_Addr: + char IPAddress[128]; + evutil_inet_ntop(a_Addr->sa_family, a_Addr->sa_data, IPAddress, ARRAYCOUNT(IPAddress)); + UInt16 Port = 0; + switch (a_Addr->sa_family) + { + case AF_INET: + { + sockaddr_in * sin = reinterpret_cast(a_Addr); + Port = ntohs(sin->sin_port); + break; + } + case AF_INET6: + { + sockaddr_in6 * sin6 = reinterpret_cast(a_Addr); + Port = ntohs(sin6->sin6_port); + break; + } + } + + // Call the OnIncomingConnection callback to get the link callbacks to use: + cTCPLink::cCallbacksPtr LinkCallbacks = Self->m_ListenCallbacks->OnIncomingConnection(IPAddress, Port); + if (LinkCallbacks == nullptr) + { + // Drop the connection: + evutil_closesocket(a_Socket); + return; + } + + // Create a new cTCPLink for the incoming connection: + cTCPLinkImplPtr Link = std::make_shared(a_Socket, LinkCallbacks, Self->m_SelfPtr, a_Addr, static_cast(a_Len)); + { + cCSLock Lock(Self->m_CS); + Self->m_Connections.push_back(Link); + } // Lock(m_CS) + LinkCallbacks->OnLinkCreated(Link); + Link->Enable(Link); + + // Call the OnAccepted callback: + Self->m_ListenCallbacks->OnAccepted(*Link); +} + + + + + +void cServerHandleImpl::RemoveLink(const cTCPLinkImpl * a_Link) +{ + cCSLock Lock(m_CS); + for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) + { + if (itr->get() == a_Link) + { + m_Connections.erase(itr); + return; + } + } // for itr - m_Connections[] +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +cServerHandlePtr cNetwork::Listen( + UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks +) +{ + return cServerHandleImpl::Listen(a_Port, a_ListenCallbacks); +} + + + + + diff --git a/src/OSSupport/ServerHandleImpl.h b/src/OSSupport/ServerHandleImpl.h new file mode 100644 index 000000000..dbb18fc6d --- /dev/null +++ b/src/OSSupport/ServerHandleImpl.h @@ -0,0 +1,105 @@ + +// ServerHandleImpl.h + +// Declares the cServerHandleImpl class implementing the TCP server functionality + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include +#include "CriticalSection.h" + + + + + +// fwd: +class cTCPLinkImpl; +typedef SharedPtr cTCPLinkImplPtr; +typedef std::vector cTCPLinkImplPtrs; +class cServerHandleImpl; +typedef SharedPtr cServerHandleImplPtr; +typedef std::vector cServerHandleImplPtrs; + + + + + +class cServerHandleImpl: + public cServerHandle +{ + typedef cServerHandle super; + friend class cTCPLinkImpl; + +public: + /** Closes the server, dropping all the connections. */ + ~cServerHandleImpl(); + + /** Creates a new server instance listening on the specified port. + Both IPv4 and IPv6 interfaces are used, if possible. + Always returns a server instance; in the event of a failure, the instance holds the error details. Use IsListening() to query success. */ + static cServerHandleImplPtr Listen( + UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks + ); + + // cServerHandle overrides: + virtual void Close(void) override; + virtual bool IsListening(void) const override { return m_IsListening; } + +protected: + /** The callbacks used to notify about incoming connections. */ + cNetwork::cListenCallbacksPtr m_ListenCallbacks; + + /** The LibEvent handle representing the main listening socket. */ + evconnlistener * m_ConnListener; + + /** The LibEvent handle representing the secondary listening socket (only when side-by-side listening is needed, such as WinXP). */ + evconnlistener * m_SecondaryConnListener; + + /** Set to true when the server is initialized successfully and is listening for incoming connections. */ + bool m_IsListening; + + /** Container for all currently active connections on this server. */ + cTCPLinkImplPtrs m_Connections; + + /** Mutex protecting m_Connections againt multithreaded access. */ + cCriticalSection m_CS; + + /** Contains the error code for the failure to listen. Only valid for non-listening instances. */ + int m_ErrorCode; + + /** Contains the error message for the failure to listen. Only valid for non-listening instances. */ + AString m_ErrorMsg; + + /** The SharedPtr to self, so that it can be passed to created links. */ + cServerHandleImplPtr m_SelfPtr; + + + + /** Creates a new instance with the specified callbacks. + Initializes the internals, but doesn't start listening yet. */ + cServerHandleImpl(cNetwork::cListenCallbacksPtr a_ListenCallbacks); + + /** Starts listening on the specified port. + Returns true if successful, false on failure. On failure, sets m_ErrorCode and m_ErrorMsg. */ + bool Listen(UInt16 a_Port); + + /** The callback called by LibEvent upon incoming connection. */ + static void Callback(evconnlistener * a_Listener, evutil_socket_t a_Socket, sockaddr * a_Addr, int a_Len, void * a_Self); + + /** Removes the specified link from m_Connections. + Called by cTCPLinkImpl when the link is terminated. */ + void RemoveLink(const cTCPLinkImpl * a_Link); +}; + + + + + diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp new file mode 100644 index 000000000..b4cefa60c --- /dev/null +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -0,0 +1,331 @@ + +// TCPLinkImpl.cpp + +// Implements the cTCPLinkImpl class implementing the TCP link functionality + +#include "Globals.h" +#include "TCPLinkImpl.h" +#include "NetworkSingleton.h" +#include "ServerHandleImpl.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cTCPLinkImpl: + +cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): + super(a_LinkCallbacks), + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE)) +{ +} + + + + + +cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImplPtr a_Server, const sockaddr * a_Address, socklen_t a_AddrLen): + super(a_LinkCallbacks), + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE)), + m_Server(a_Server) +{ + // Update the endpoint addresses: + UpdateLocalAddress(); + UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort); +} + + + + + +cTCPLinkImpl::~cTCPLinkImpl() +{ + bufferevent_free(m_BufferEvent); +} + + + + + +cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTCPLink::cCallbacksPtr a_LinkCallbacks, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks) +{ + ASSERT(a_LinkCallbacks != nullptr); + ASSERT(a_ConnectCallbacks != nullptr); + + // Create a new link: + cTCPLinkImplPtr res{new cTCPLinkImpl(a_LinkCallbacks)}; // Cannot use std::make_shared here, constructor is not accessible + res->m_ConnectCallbacks = a_ConnectCallbacks; + cNetworkSingleton::Get().AddLink(res); + res->m_Callbacks->OnLinkCreated(res); + res->Enable(res); + + // If a_Host is an IP address, schedule a connection immediately: + sockaddr_storage sa; + int salen = static_cast(sizeof(sa)); + if (evutil_parse_sockaddr_port(a_Host.c_str(), reinterpret_cast(&sa), &salen) == 0) + { + // Insert the correct port: + if (sa.ss_family == AF_INET6) + { + reinterpret_cast(&sa)->sin6_port = htons(a_Port); + } + else + { + reinterpret_cast(&sa)->sin_port = htons(a_Port); + } + + // Queue the connect request: + if (bufferevent_socket_connect(res->m_BufferEvent, reinterpret_cast(&sa), salen) == 0) + { + // Success + return res; + } + // Failure + cNetworkSingleton::Get().RemoveLink(res.get()); + return nullptr; + } + + // a_Host is a hostname, connect after a lookup: + if (bufferevent_socket_connect_hostname(res->m_BufferEvent, cNetworkSingleton::Get().GetDNSBase(), AF_UNSPEC, a_Host.c_str(), a_Port) == 0) + { + // Success + return res; + } + // Failure + cNetworkSingleton::Get().RemoveLink(res.get()); + return nullptr; +} + + + + + +void cTCPLinkImpl::Enable(cTCPLinkImplPtr a_Self) +{ + // Take hold of a shared copy of self, to keep as long as the callbacks are coming: + m_Self = a_Self; + + // Set the LibEvent callbacks and enable processing: + bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); + bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); +} + + + + + +bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) +{ + return (bufferevent_write(m_BufferEvent, a_Data, a_Length) == 0); +} + + + + + +void cTCPLinkImpl::Shutdown(void) +{ + #ifdef _WIN32 + shutdown(bufferevent_getfd(m_BufferEvent), SD_SEND); + #else + shutdown(bufferevent_getfd(m_BufferEvent), SHUT_WR); + #endif + bufferevent_disable(m_BufferEvent, EV_WRITE); +} + + + + + +void cTCPLinkImpl::Close(void) +{ + // Disable all events on the socket, but keep it alive: + bufferevent_disable(m_BufferEvent, EV_READ | EV_WRITE); + if (m_Server == nullptr) + { + cNetworkSingleton::Get().RemoveLink(this); + } + else + { + m_Server->RemoveLink(this); + } + m_Self.reset(); +} + + + + + + +void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self) +{ + ASSERT(a_Self != nullptr); + cTCPLinkImpl * Self = static_cast(a_Self); + ASSERT(Self->m_Callbacks != nullptr); + + // Read all the incoming data, in 1024-byte chunks: + char data[1024]; + size_t length; + while ((length = bufferevent_read(a_BufferEvent, data, sizeof(data))) > 0) + { + Self->m_Callbacks->OnReceivedData(data, length); + } +} + + + + + +void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) +{ + ASSERT(a_Self != nullptr); + cTCPLinkImplPtr Self = static_cast(a_Self)->m_Self; + + // If an error is reported, call the error callback: + if (a_What & BEV_EVENT_ERROR) + { + // Choose the proper callback to call based on whether we were waiting for connection or not: + int err = EVUTIL_SOCKET_ERROR(); + if (Self->m_ConnectCallbacks != nullptr) + { + if (err == 0) + { + // This could be a DNS failure + err = bufferevent_socket_get_dns_error(a_BufferEvent); + } + Self->m_ConnectCallbacks->OnError(err, evutil_socket_error_to_string(err)); + } + else + { + Self->m_Callbacks->OnError(err, evutil_socket_error_to_string(err)); + if (Self->m_Server == nullptr) + { + cNetworkSingleton::Get().RemoveLink(Self.get()); + } + else + { + Self->m_Server->RemoveLink(Self.get()); + } + } + Self->m_Self.reset(); + return; + } + + // Pending connection succeeded, call the connection callback: + if (a_What & BEV_EVENT_CONNECTED) + { + if (Self->m_ConnectCallbacks != nullptr) + { + Self->m_ConnectCallbacks->OnConnected(*Self); + // Reset the connect callbacks so that later errors get reported through the link callbacks: + Self->m_ConnectCallbacks.reset(); + return; + } + Self->UpdateLocalAddress(); + Self->UpdateRemoteAddress(); + } + + // If the connection has been closed, call the link callback and remove the connection: + if (a_What & BEV_EVENT_EOF) + { + Self->m_Callbacks->OnRemoteClosed(); + if (Self->m_Server != nullptr) + { + Self->m_Server->RemoveLink(Self.get()); + } + else + { + cNetworkSingleton::Get().RemoveLink(Self.get()); + } + Self->m_Self.reset(); + return; + } + + // Unknown event, report it: + LOGWARNING("cTCPLinkImpl: Unhandled LibEvent event %d (0x%x)", a_What, a_What); + ASSERT(!"cTCPLinkImpl: Unhandled LibEvent event"); +} + + + + + +void cTCPLinkImpl::UpdateAddress(const sockaddr * a_Address, socklen_t a_AddrLen, AString & a_IP, UInt16 & a_Port) +{ + // Based on the family specified in the address, use the correct datastructure to convert to IP string: + char IP[128]; + switch (a_Address->sa_family) + { + case AF_INET: // IPv4: + { + const sockaddr_in * sin = reinterpret_cast(a_Address); + evutil_inet_ntop(AF_INET, &(sin->sin_addr), IP, sizeof(IP)); + a_Port = ntohs(sin->sin_port); + break; + } + case AF_INET6: // IPv6 + { + const sockaddr_in6 * sin = reinterpret_cast(a_Address); + evutil_inet_ntop(AF_INET6, &(sin->sin6_addr), IP, sizeof(IP)); + a_Port = ntohs(sin->sin6_port); + break; + } + + default: + { + LOGWARNING("%s: Unknown socket address family: %d", __FUNCTION__, a_Address->sa_family); + ASSERT(!"Unknown socket address family"); + break; + } + } + a_IP.assign(IP); +} + + + + + +void cTCPLinkImpl::UpdateLocalAddress(void) +{ + sockaddr_storage sa; + socklen_t salen = static_cast(sizeof(sa)); + getsockname(bufferevent_getfd(m_BufferEvent), reinterpret_cast(&sa), &salen); + UpdateAddress(reinterpret_cast(&sa), salen, m_LocalIP, m_LocalPort); +} + + + + + +void cTCPLinkImpl::UpdateRemoteAddress(void) +{ + sockaddr_storage sa; + socklen_t salen = static_cast(sizeof(sa)); + getpeername(bufferevent_getfd(m_BufferEvent), reinterpret_cast(&sa), &salen); + UpdateAddress(reinterpret_cast(&sa), salen, m_RemoteIP, m_RemotePort); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +bool cNetwork::Connect( + const AString & a_Host, + UInt16 a_Port, + cNetwork::cConnectCallbacksPtr a_ConnectCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + // Add a connection request to the queue: + cTCPLinkImplPtr Conn = cTCPLinkImpl::Connect(a_Host, a_Port, a_LinkCallbacks, a_ConnectCallbacks); + return (Conn != nullptr); +} + + + + + diff --git a/src/OSSupport/TCPLinkImpl.h b/src/OSSupport/TCPLinkImpl.h new file mode 100644 index 000000000..735e8ed9d --- /dev/null +++ b/src/OSSupport/TCPLinkImpl.h @@ -0,0 +1,122 @@ + +// TCPLinkImpl.h + +// Declares the cTCPLinkImpl class implementing the TCP link functionality + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include +#include + + + + + +// fwd: +class cServerHandleImpl; +typedef SharedPtr cServerHandleImplPtr; +class cTCPLinkImpl; +typedef SharedPtr cTCPLinkImplPtr; +typedef std::vector cTCPLinkImplPtrs; + + + + + +class cTCPLinkImpl: + public cTCPLink +{ + typedef cTCPLink super; + +public: + /** Creates a new link based on the given socket. + Used for connections accepted in a server using cNetwork::Listen(). + a_Address and a_AddrLen describe the remote peer that has connected. + The link is created disabled, you need to call Enable() to start the regular communication. */ + cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImplPtr a_Server, const sockaddr * a_Address, socklen_t a_AddrLen); + + /** Destroys the LibEvent handle representing the link. */ + ~cTCPLinkImpl(); + + /** Queues a connection request to the specified host. + a_ConnectCallbacks must be valid. + Returns a link that has the connection request queued, or NULL for failure. */ + static cTCPLinkImplPtr Connect(const AString & a_Host, UInt16 a_Port, cTCPLink::cCallbacksPtr a_LinkCallbacks, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks); + + /** Enables communication over the link. + Links are created with communication disabled, so that creation callbacks can be called first. + This function then enables the regular communication to be reported. + The a_Self parameter is used so that the socket can keep itself alive as long as the callbacks are coming. */ + void Enable(cTCPLinkImplPtr a_Self); + + // cTCPLink overrides: + virtual bool Send(const void * a_Data, size_t a_Length) override; + virtual AString GetLocalIP(void) const override { return m_LocalIP; } + virtual UInt16 GetLocalPort(void) const override { return m_LocalPort; } + virtual AString GetRemoteIP(void) const override { return m_RemoteIP; } + virtual UInt16 GetRemotePort(void) const override { return m_RemotePort; } + virtual void Shutdown(void) override; + virtual void Close(void) override; + +protected: + + /** Callbacks to call when the connection is established. + May be NULL if not used. Only used for outgoing connections (cNetwork::Connect()). */ + cNetwork::cConnectCallbacksPtr m_ConnectCallbacks; + + /** The LibEvent handle representing this connection. */ + bufferevent * m_BufferEvent; + + /** The server handle that has created this link. + Only valid for incoming connections, nullptr for outgoing connections. */ + cServerHandleImplPtr m_Server; + + /** The IP address of the local endpoint. Valid only after the socket has been connected. */ + AString m_LocalIP; + + /** The port of the local endpoint. Valid only after the socket has been connected. */ + UInt16 m_LocalPort; + + /** The IP address of the remote endpoint. Valid only after the socket has been connected. */ + AString m_RemoteIP; + + /** The port of the remote endpoint. Valid only after the socket has been connected. */ + UInt16 m_RemotePort; + + /** SharedPtr to self, used to keep this object alive as long as the callbacks are coming. + Initialized in Enable(), cleared in Close() and EventCallback(RemoteClosed). */ + cTCPLinkImplPtr m_Self; + + + /** Creates a new link to be queued to connect to a specified host:port. + Used for outgoing connections created using cNetwork::Connect(). + To be used only by the Connect() factory function. + The link is created disabled, you need to call Enable() to start the regular communication. */ + cTCPLinkImpl(const cCallbacksPtr a_LinkCallbacks); + + /** Callback that LibEvent calls when there's data available from the remote peer. */ + static void ReadCallback(bufferevent * a_BufferEvent, void * a_Self); + + /** Callback that LibEvent calls when there's a non-data-related event on the socket. */ + static void EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self); + + /** Sets a_IP and a_Port to values read from a_Address, based on the correct address family. */ + static void UpdateAddress(const sockaddr * a_Address, socklen_t a_AddrLen, AString & a_IP, UInt16 & a_Port); + + /** Updates m_LocalIP and m_LocalPort based on the metadata read from the socket. */ + void UpdateLocalAddress(void); + + /** Updates m_RemoteIP and m_RemotePort based on the metadata read from the socket. */ + void UpdateRemoteAddress(void); +}; + + + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1fbd88f04..265640cc8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,3 +5,4 @@ enable_testing() include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(ChunkData) +add_subdirectory(Network) diff --git a/tests/Network/CMakeLists.txt b/tests/Network/CMakeLists.txt new file mode 100644 index 000000000..c0af50e2c --- /dev/null +++ b/tests/Network/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required (VERSION 2.6) + +enable_testing() + +include_directories(${CMAKE_SOURCE_DIR}/src/) +include_directories(${CMAKE_SOURCE_DIR}/lib/libevent/include) + +add_definitions(-DTEST_GLOBALS=1) + +# Create a single Network library that contains all the networking code: +set (Network_SRCS + ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/HostnameLookup.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/IPLookup.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/NetworkSingleton.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/ServerHandleImpl.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/TCPLinkImpl.cpp + ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp +) + +set (Network_HDRS + ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/HostnameLookup.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/IPLookup.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/NetworkSingleton.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/ServerHandleImpl.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/TCPLinkImpl.h + ${CMAKE_SOURCE_DIR}/src/StringUtils.h +) + +add_library(Network + ${Network_SRCS} + ${Network_HDRS} +) + +target_link_libraries(Network event_core event_extra) +if (MSVC) + target_link_libraries(Network ws2_32.lib) +endif() + + + + +# Define individual tests: + +# Google: download the google.com frontpage using http client socket: +add_executable(Google-exe Google.cpp) +target_link_libraries(Google-exe Network) +add_test(NAME Google-test COMMAND Google-exe) + +# EchoServer: Listen on port 9876, echo everything back: +add_executable(EchoServer EchoServer.cpp) +target_link_libraries(EchoServer Network) + +# NameLookup: Lookup hostname-to-IP and IP-to-hostname: +add_executable(NameLookup NameLookup.cpp) +target_link_libraries(NameLookup Network) diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp new file mode 100644 index 000000000..728db0b7c --- /dev/null +++ b/tests/Network/EchoServer.cpp @@ -0,0 +1,132 @@ + +// EchoServer.cpp + +// Implements an Echo server using the LibEvent-based cNetwork API, as a test of that API + +#include "Globals.h" +#include +#include +#include "OSSupport/Network.h" + + + + + +/** cTCPLink callbacks that echo everything they receive back to the remote peer. */ +class cEchoLinkCallbacks: + public cTCPLink::cCallbacks +{ + // cTCPLink::cCallbacks overrides: + virtual void OnLinkCreated(cTCPLinkPtr a_Link) override + { + ASSERT(m_Link == nullptr); + m_Link = a_Link; + } + + + virtual void OnReceivedData(const char * a_Data, size_t a_Size) override + { + ASSERT(m_Link != nullptr); + + // Echo the incoming data back to outgoing data: + LOGD("%p (%s:%d): Data received (%u bytes), echoing back.", m_Link.get(), m_Link->GetRemoteIP().c_str(), m_Link->GetRemotePort(), static_cast(a_Size)); + m_Link->Send(a_Data, a_Size); + LOGD("Echo queued"); + + // Search for a Ctrl+Z, if found, drop the connection: + for (size_t i = 0; i < a_Size; i++) + { + if (a_Data[i] == '\x1a') + { + m_Link->Close(); + m_Link.reset(); + return; + } + } + } + + + virtual void OnRemoteClosed(void) override + { + ASSERT(m_Link != nullptr); + + LOGD("%p (%s:%d): Remote has closed the connection.", m_Link.get(), m_Link->GetRemoteIP().c_str(), m_Link->GetRemotePort()); + m_Link.reset(); + } + + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + ASSERT(m_Link != nullptr); + + LOGD("%p (%s:%d): Error %d in the cEchoLinkCallbacks: %s", m_Link.get(), m_Link->GetRemoteIP().c_str(), m_Link->GetRemotePort(), a_ErrorCode, a_ErrorMsg.c_str()); + m_Link.reset(); + } + + /** The link attached to this callbacks instance. */ + cTCPLinkPtr m_Link; +}; + + + + + +class cEchoServerCallbacks: + public cNetwork::cListenCallbacks +{ + virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) + { + LOGD("New incoming connection(%s:%d).", a_RemoteIPAddress.c_str(), a_RemotePort); + return std::make_shared(); + } + + virtual void OnAccepted(cTCPLink & a_Link) override + { + LOGD("New client accepted (%s:%d), sending welcome message.", a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort()); + // Send a welcome message to each connecting client: + a_Link.Send("Welcome to the simple echo server.\r\n"); + LOGD("Welcome message queued."); + } + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + LOGWARNING("An error occured while listening for connections: %d (%s).", a_ErrorCode, a_ErrorMsg.c_str()); + } +}; + + + + + +int main() +{ + LOGD("EchoServer: starting up"); + cServerHandlePtr Server = cNetwork::Listen(9876, std::make_shared()); + if (!Server->IsListening()) + { + LOGWARNING("Cannot listen on port 9876"); + abort(); + } + ASSERT(Server->IsListening()); + + // Wait for the user to terminate the server: + printf("Press enter to close the server.\n"); + AString line; + std::getline(std::cin, line); + + // Close the server and all its active connections: + LOG("Server terminating."); + Server->Close(); + ASSERT(!Server->IsListening()); + LOGD("Server has been closed."); + + printf("Press enter to exit test.\n"); + std::getline(std::cin, line); + + LOG("Network test finished."); + return 0; +} + + + + diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp new file mode 100644 index 000000000..2b8830c24 --- /dev/null +++ b/tests/Network/Google.cpp @@ -0,0 +1,118 @@ + +// Google.cpp + +// Implements a HTTP download of the google's front page using the LibEvent-based cNetwork API + +#include "Globals.h" +#include +#include "OSSupport/Event.h" +#include "OSSupport/Network.h" + + + + + +/** Connect callbacks that send a HTTP GET request for google.com when connected. */ +class cHTTPConnectCallbacks: + public cNetwork::cConnectCallbacks +{ + cEvent & m_Event; + virtual void OnConnected(cTCPLink & a_Link) override + { + LOGD("Connected, sending HTTP GET"); + if (!a_Link.Send("GET / HTTP/1.0\r\nHost:google.com\r\n\r\n")) + { + LOGWARNING("Sending HTTP GET failed"); + } + LOGD("HTTP GET queued."); + } + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + LOGD("Error while connecting HTTP: %d (%s)", a_ErrorCode, a_ErrorMsg.c_str()); + m_Event.Set(); + } + +public: + cHTTPConnectCallbacks(cEvent & a_Event): + m_Event(a_Event) + { + } +}; + + + + + +/** cTCPLink callbacks that dump everything it received to the log. */ +class cDumpCallbacks: + public cTCPLink::cCallbacks +{ + cEvent & m_Event; + cTCPLinkPtr m_Link; + + virtual void OnLinkCreated(cTCPLinkPtr a_Link) override + { + ASSERT(m_Link == nullptr); + m_Link = a_Link; + } + + virtual void OnReceivedData(const char * a_Data, size_t a_Size) override + { + ASSERT(m_Link != nullptr); + + // Log the incoming data size: + AString Hex; + CreateHexDump(Hex, a_Data, a_Size, 16); + LOGD("Incoming data: %u bytes:\n%s", static_cast(a_Size), Hex.c_str()); + } + + virtual void OnRemoteClosed(void) override + { + ASSERT(m_Link != nullptr); + + LOGD("Remote has closed the connection."); + m_Link.reset(); + m_Event.Set(); + } + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + ASSERT(m_Link != nullptr); + + LOGD("Error %d (%s) in the cDumpCallbacks.", a_ErrorCode, a_ErrorMsg.c_str()); + m_Link.reset(); + m_Event.Set(); + } + +public: + cDumpCallbacks(cEvent & a_Event): + m_Event(a_Event) + { + } +}; + + + + + +int main() +{ + cEvent evtFinish; + + LOGD("Network test: Connecting to google.com:80, reading front page via HTTP."); + if (!cNetwork::Connect("google.com", 80, std::make_shared(evtFinish), std::make_shared(evtFinish))) + { + LOGWARNING("Cannot queue connection to google.com"); + abort(); + } + LOGD("Connect request has been queued."); + + evtFinish.Wait(); + LOGD("Network test finished"); + return 0; +} + + + + diff --git a/tests/Network/NameLookup.cpp b/tests/Network/NameLookup.cpp new file mode 100644 index 000000000..822a96baf --- /dev/null +++ b/tests/Network/NameLookup.cpp @@ -0,0 +1,80 @@ + +// NameLookup.cpp + +// Implements a DNS name lookup using the LibEvent-based cNetwork API + +#include "Globals.h" +#include +#include "OSSupport/Event.h" +#include "OSSupport/Network.h" + + + + + +class cFinishLookupCallbacks: + public cNetwork::cResolveNameCallbacks +{ + cEvent & m_Event; + + virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) override + { + LOGD("%s resolves to IP %s", a_Name.c_str(), a_IP.c_str()); + } + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + LOGD("Error %d (%s) while performing lookup!", a_ErrorCode, a_ErrorMsg.c_str()); + exit(a_ErrorCode); + } + + virtual void OnFinished(void) override + { + LOGD("Resolving finished."); + m_Event.Set(); + } + +public: + cFinishLookupCallbacks(cEvent & a_Event): + m_Event(a_Event) + { + } +}; + + + + + +int main() +{ + cEvent evtFinish; + + // Look up google.com (has multiple IP addresses): + LOGD("Network test: Looking up google.com"); + if (!cNetwork::HostnameToIP("google.com", std::make_shared(evtFinish))) + { + LOGWARNING("Cannot resolve google.com to IP"); + abort(); + } + LOGD("Name lookup has been successfully queued"); + evtFinish.Wait(); + LOGD("Lookup finished."); + + // Look up 8.8.8.8 (Google free DNS): + LOGD("Network test: Looking up IP 8.8.8.8"); + if (!cNetwork::IPToHostName("8.8.8.8", std::make_shared(evtFinish))) + { + LOGWARNING("Cannot resolve 8.8.8.8 to name"); + abort(); + } + LOGD("IP lookup has been successfully queued"); + evtFinish.Wait(); + LOGD("IP lookup finished."); + + LOGD("Network test finished"); + return 0; +} + + + +