From a8a7488b54b06b5768ae66036adea937e519b242 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Mon, 5 Jan 2015 22:14:48 +0100 Subject: Added the libevent library. --- .gitmodules | 3 +++ CMakeLists.txt | 11 +++++++++++ lib/libevent | 1 + src/CMakeLists.txt | 2 +- 4 files changed, 16 insertions(+), 1 deletion(-) create mode 160000 lib/libevent 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..728f5c935 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) 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..50bdb0e8e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -323,4 +323,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) -- cgit v1.2.3 From 6f5e267d5881db22843cedb9a752e92cce9f01ee Mon Sep 17 00:00:00 2001 From: Mattes D Date: Fri, 9 Jan 2015 11:07:43 +0100 Subject: Implemented LibEvent-based client connections. --- src/CMakeLists.txt | 1 + src/OSSupport/CMakeLists.txt | 2 + src/OSSupport/Network.cpp | 837 +++++++++++++++++++++++++++++++++++++++++++ src/OSSupport/Network.h | 188 ++++++++++ 4 files changed, 1028 insertions(+) create mode 100644 src/OSSupport/Network.cpp create mode 100644 src/OSSupport/Network.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 50bdb0e8e..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 diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index e943ceb18..8454279fe 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -12,6 +12,7 @@ SET (SRCS GZipFile.cpp IsThread.cpp ListenThread.cpp + Network.cpp Semaphore.cpp Socket.cpp SocketThreads.cpp @@ -26,6 +27,7 @@ SET (HDRS GZipFile.h IsThread.h ListenThread.h + Network.h Queue.h Semaphore.h Socket.h diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp new file mode 100644 index 000000000..d947bfa90 --- /dev/null +++ b/src/OSSupport/Network.cpp @@ -0,0 +1,837 @@ + +// Network.cpp + +// Implements the classes used for the Network API + +#include "Globals.h" +#include "Network.h" +#include +#include +#include +#include + + + + + +//////////////////////////////////////////////////////////////////////////////// +// Self-test: + +class cNetworkTest +{ +public: + /** cTCPLink callbacks that dump everything it received to the log. */ + class cDumpCallbacks: + public cTCPLink::cCallbacks + { + cEvent & m_Event; + + virtual void OnReceivedData(cTCPLink & a_Link, const char * a_Data, size_t a_Size) override + { + // 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(cTCPLink & a_Link) override + { + LOGD("Remote has closed the connection."); + m_Event.Set(); + } + + virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) override + { + LOGD("Error in the cDumpCallbacks."); + m_Event.Set(); + } + + public: + cDumpCallbacks(cEvent & a_Event): + m_Event(a_Event) + { + } + }; + + + /** cTCPLink callbacks that echo everything they receive back to the remote peer. */ + class cEchoCallbacks: + public cTCPLink::cCallbacks + { + cEvent & m_Event; + + virtual void OnReceivedData(cTCPLink & a_Link, const char * a_Data, size_t a_Size) override + { + // Echo the incoming data back to outgoing data: + LOGD("Data received (%u bytes), echoing back.", static_cast(a_Size)); + a_Link.Send(a_Data, a_Size); + LOGD("Echo queued"); + } + + virtual void OnRemoteClosed(cTCPLink & a_Link) override + { + LOGD("Remote has closed the connection."); + m_Event.Set(); + } + + virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) override + { + LOGD("Error in the cEchoCallbacks."); + m_Event.Set(); + } + + public: + cEchoCallbacks(cEvent & a_Event): + m_Event(a_Event) + { + } + }; + + + /** Connect callbacks that send a simple test message when connected. */ + class cConnectCallbacks: + public cNetwork::cConnectCallbacks + { + cEvent & m_Event; + + virtual void OnSuccess(cTCPLink & a_Link) override + { + LOGD("Connected, sending test message"); + a_Link.Send("test message"); + LOGD("Message queued."); + } + + virtual void OnError(int a_ErrorCode) override + { + LOGD("Error while connecting: %d", a_ErrorCode); + m_Event.Set(); + } + + public: + cConnectCallbacks(cEvent & a_Event): + m_Event(a_Event) + { + } + }; + + + /** Connect callbacks that send a HTTP GET request for google.com when connected. */ + class cHTTPConnectCallbacks: + public cNetwork::cConnectCallbacks + { + cEvent & m_Event; + virtual void OnSuccess(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) override + { + LOGD("Error while connecting HTTP: %d", a_ErrorCode); + m_Event.Set(); + } + + public: + cHTTPConnectCallbacks(cEvent & a_Event): + m_Event(a_Event) + { + } + }; + + + /** Listen callbacks that send a simple welcome message to all connecting peers. */ + class cListenCallbacks: + public cNetwork::cListenCallbacks + { + virtual void OnAccepted(cTCPLink & a_Link) override + { + // Send some trivial data: + a_Link.Send("Welcome to echo server\n"); + } + }; + + cNetworkTest(void) + { + 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."); + + /* + LOGD("Creating a server on port 33033"); + auto Server = cNetwork::Listen(33033, std::make_shared(), std::make_shared()); + LOGD("Test server created."); + + LOGD("Connecting to test server"); + cNetwork::Connect("localhost", 33033, std::make_shared(), std::make_shared()); + + LOGD("Waiting for network operations to finish."); + evtFinish.Wait(); + + LOGD("Terminating test server."); + Server->Close(); + */ + + evtFinish.Wait(); + LOGD("Network test finished"); + } +} g_NetworkTest; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// Class definitions: + +/** Implements the cTCPLink details so that it can represent the single connection between two endpoints. */ +class cTCPLinkImpl: + public cTCPLink +{ + typedef cTCPLink super; +public: + /** Creates a new link to be queued to connect to a specified host:port. + Used for outgoing connections created using cNetwork::Connect(). + Call Connect() first, before using the link. */ + cTCPLinkImpl(const cCallbacksPtr a_LinkCallbacks); + + /** Creates a new link based on the given socket. + Used for connections accepted in a server using cNetwork::Listen(). */ + cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks); + + /** Queues a connection request to the specified host. + a_ConnectCallbacks must be valid. + The object must have been constructed by the right constructor (without the Socket param). */ + bool Connect(const AString & a_Host, UInt16 a_Port, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks); + + // cTCPLink overrides: + virtual bool Send(const void * a_Data, size_t a_Length) override; + virtual AString GetLocalIP(void) const override; + virtual UInt16 GetLocalPort(void) const override; + virtual AString GetRemoteIP(void) const override; + virtual UInt16 GetRemotePort(void) const override; + virtual void Close(void) override; + virtual void Drop(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; + + + /** 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); +}; +typedef SharedPtr cTCPLinkImplPtr; +typedef std::vector cTCPLinkImplPtrs; + + + + + +/** Implements the cServerHandle details so that it can represent a real server socket, with a list of clients. */ +class cServerHandleImpl: + public cServerHandle +{ +public: + /** Creates a new instance with the specified callbacks. + Initializes the internals, but doesn't start listening yet. */ + cServerHandleImpl( + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); + + /** Starts listening on the specified port. + Both IPv4 and IPv6 interfaces are used, if possible. */ + bool Listen(UInt16 a_Port); + + // 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 callbacks used to create new cTCPLink instances for incoming connections. */ + cTCPLink::cCallbacksPtr m_LinkCallbacks; + + /** Set to true when the server is initialized successfully and is listening for incoming connections. */ + bool m_IsListening; +}; +typedef SharedPtr cServerHandleImplPtr; +typedef std::vector cServerHandleImplPtrs; + + + + + +class cNetworkSingleton +{ + friend class cTCPLinkImpl; + +public: + /** Returns the singleton instance of this class */ + static cNetworkSingleton & Get(void); + + + // The following functions are implementations for the cNetwork class + + /** 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. */ + bool Connect( + const AString & a_Host, + const UInt16 a_Port, + cNetwork::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. */ + cServerHandlePtr Listen( + const UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); + + + /** 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. */ + bool HostnameToIP( + const AString & a_Hostname, + cNetwork::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. */ + bool IPToHostName( + const AString & a_IP, + cNetwork::cResolveNameCallbacksPtr a_Callbacks + ); + +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; + + + /** 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); + + /** Runs the thread that LibEvent uses to dispatch event. */ + static void EventLoopThread(cNetworkSingleton * a_Self); +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cTCPLinkImpl: + +cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): + super(a_LinkCallbacks), + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, -1, BEV_OPT_CLOSE_ON_FREE)) +{ + bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); + bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); +} + + + + + +cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks): + super(a_LinkCallbacks), + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, a_Socket, BEV_OPT_CLOSE_ON_FREE)) +{ + bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); + bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); +} + + + + + +/** Schedules the actual connection request. +Returns true on success, false on failure. */ +bool cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks) +{ + ASSERT(bufferevent_getfd(m_BufferEvent) == -1); // Did you create this object using the right constructor (the one without the Socket param)? + ASSERT(a_ConnectCallbacks != nullptr); + + m_ConnectCallbacks = a_ConnectCallbacks; + + // 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); + } + + if (bufferevent_socket_connect(m_BufferEvent, reinterpret_cast(&sa), salen) == 0) + { + // Success + return true; + } + // Failure + return false; + } + + // a_Host is a hostname, connect after a lookup: + if (bufferevent_socket_connect_hostname(m_BufferEvent, cNetworkSingleton::Get().m_DNSBase, AF_UNSPEC, a_Host.c_str(), a_Port) == 0) + { + // Success + return true; + } + // Failure + return false; +} + + + + + +bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) +{ + return (bufferevent_write(m_BufferEvent, a_Data, a_Length) == 0); +} + + + + + +AString cTCPLinkImpl::GetLocalIP(void) const +{ + // TODO + ASSERT(!"cTCPLinkImpl::GetLocalIP: Not implemented yet"); + return ""; +} + + + + + +UInt16 cTCPLinkImpl::GetLocalPort(void) const +{ + // TODO + ASSERT(!"cTCPLinkImpl::GetLocalPort(): Not implemented yet"); + return 0; +} + + + + + +AString cTCPLinkImpl::GetRemoteIP(void) const +{ + // TODO + ASSERT(!"cTCPLinkImpl::GetRemoteIP(): Not implemented yet"); + return ""; +} + + + + + +UInt16 cTCPLinkImpl::GetRemotePort(void) const +{ + // TODO + ASSERT(!"cTCPLinkImpl::GetRemotePort(): Not implemented yet"); + return 0; +} + + + + + +void cTCPLinkImpl::Close(void) +{ + // TODO + ASSERT(!"cTCPLinkImpl::Close(): Not implemented yet"); +} + + + + + +void cTCPLinkImpl::Drop(void) +{ + // TODO + ASSERT(!"cTCPLinkImpl::Drop(): Not implemented yet"); +} + + + + + + +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(*Self, data, length); + } +} + + + + + +void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) +{ + ASSERT(a_Self != nullptr); + cTCPLinkImpl * Self = static_cast(a_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: + if (Self->m_ConnectCallbacks != nullptr) + { + Self->m_ConnectCallbacks->OnError(EVUTIL_SOCKET_ERROR()); + } + else + { + Self->m_Callbacks->OnError(*Self, EVUTIL_SOCKET_ERROR()); + } + return; + } + + // Pending connection succeeded, call the connection callback: + if (a_What & BEV_EVENT_CONNECTED) + { + if (Self->m_ConnectCallbacks != nullptr) + { + Self->m_ConnectCallbacks->OnSuccess(*Self); + // Reset the connect callbacks so that later errors get reported through the link callbacks: + Self->m_ConnectCallbacks.reset(); + return; + } + } + + // If the connectino has been closed, call the link callback: + if (a_What & BEV_EVENT_EOF) + { + Self->m_Callbacks->OnRemoteClosed(*Self); + return; + } + + // Unknown event, report it: + LOGWARNING("cTCPLinkImpl: Unhandled LibEvent event %d (0x%x)", a_What, a_What); + ASSERT(!"cTCPLinkImpl: Unhandled LibEvent event"); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cServerHandleImpl: + +cServerHandleImpl::cServerHandleImpl(cNetwork::cListenCallbacksPtr a_ListenCallbacks, cTCPLink::cCallbacksPtr a_LinkCallbacks): + m_ListenCallbacks(a_ListenCallbacks), + m_LinkCallbacks(a_LinkCallbacks) +{ + // TODO + ASSERT(!"Not implemented yet!"); +} + + + + + +void cServerHandleImpl::Close(void) +{ + // TODO + ASSERT(!"Not implemented yet!"); +} + + + + + +bool cServerHandleImpl::Listen(UInt16 a_Port) +{ + // TODO + ASSERT(!"Not implemented yet!"); + return false; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork: + +bool cNetwork::Connect( + const AString & a_Host, + const UInt16 a_Port, + cNetwork::cConnectCallbacksPtr a_ConnectCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + return cNetworkSingleton::Get().Connect(a_Host, a_Port, a_ConnectCallbacks, a_LinkCallbacks); +} + + + + + +cServerHandlePtr cNetwork::Listen( + const UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + return cNetworkSingleton::Get().Listen(a_Port, a_ListenCallbacks, a_LinkCallbacks); +} + + + + + +bool cNetwork::HostnameToIP( + const AString & a_Hostname, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + return cNetworkSingleton::Get().HostnameToIP(a_Hostname, a_Callbacks); +} + + + + + +bool cNetwork::IPToHostName( + const AString & a_IP, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + return cNetworkSingleton::Get().IPToHostName(a_IP, a_Callbacks); +} + + + + +//////////////////////////////////////////////////////////////////////////////// +// cTCPLink: + +cTCPLink::cTCPLink(cCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks) +{ +} + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetworkSingleton: + +cNetworkSingleton::cNetworkSingleton(void) +{ + // Windows: initialize networking: + #ifdef _WIN32 + WSADATA wsaData; + memset(&wsaData, 0, sizeof(wsaData)); + WSAStartup (MAKEWORD(2, 2), &wsaData); + #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::thread(EventLoopThread, this).detach(); +} + + + + + +cNetworkSingleton & cNetworkSingleton::Get(void) +{ + static cNetworkSingleton Instance; + return Instance; +} + + +bool cNetworkSingleton::Connect( + const AString & a_Host, + const UInt16 a_Port, + cNetwork::cConnectCallbacksPtr a_ConnectCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + // Add a connection request to the queue: + cTCPLinkImplPtr ConnRequest = std::make_shared(a_LinkCallbacks); + m_Connections.push_back(ConnRequest); + + // Queue the connection: + if (!ConnRequest->Connect(a_Host, a_Port, a_ConnectCallbacks)) + { + // TODO: m_Connections.remove(ConnRequest); + return false; + } + + // Everything successful, return success: + return true; +} + + + + + +cServerHandlePtr cNetworkSingleton::Listen( + const UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + cServerHandleImplPtr res = std::make_shared(a_ListenCallbacks, a_LinkCallbacks); + if (!res->Listen(a_Port)) + { + return res; + } + m_Servers.push_back(res); + return res; +} + + + + + +bool cNetworkSingleton::HostnameToIP( + const AString & a_Hostname, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + // TODO + ASSERT(!"Not implemented yet!"); + return false; +} + + + + +bool cNetworkSingleton::IPToHostName( + const AString & a_IP, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + // TODO + ASSERT(!"Not implemented yet!"); + return false; +} + + + + +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: + { + printf("LibEvent: %s", a_Msg); + break; + } + } +} + + + + + +void cNetworkSingleton::EventLoopThread(cNetworkSingleton * a_Self) +{ + event_base_loop(a_Self->m_EventBase, EVLOOP_NO_EXIT_ON_EMPTY); +} + + + + diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h new file mode 100644 index 000000000..447cd457b --- /dev/null +++ b/src/OSSupport/Network.h @@ -0,0 +1,188 @@ + +// Network.h + +// Declares the classes used for the Network API + + + + + +#pragma once + + + + + +/** Interface that provides the methods available on a single TCP connection. */ +class cTCPLink +{ + friend class cNetwork; + +public: + class cCallbacks + { + public: + /** Called when there's data incoming from the remote peer. */ + virtual void OnReceivedData(cTCPLink & a_Link, 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(cTCPLink & a_Link) = 0; + + /** Called when an error is detected on the connection. */ + virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) = 0; + }; + typedef SharedPtr cCallbacksPtr; + + + /** 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 keep trying to send the queued data, then it will send the FIN packet. */ + virtual void Close(void) = 0; + + /** Drops the connection without any more processing. + Sends the RST packet, queued outgoing and incoming data is lost. */ + virtual void Drop(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); +}; + + + + + +/** Interface that provides the methods available on a listening server socket. */ +class cServerHandle +{ + friend class cNetwork; +public: + + /** Stops the server, no more incoming connections will be accepted. */ + 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; +}; +typedef SharedPtr cServerHandlePtr; + + + + + +class cNetwork +{ +public: + /** Callbacks used for connecting to other servers as a client. */ + class cConnectCallbacks + { + public: + /** Called when the Connect call succeeds. + Provides the newly created link that can be used for communication. */ + virtual void OnSuccess(cTCPLink & a_Link) = 0; + + /** Called when the Connect call fails. */ + virtual void OnError(int a_ErrorCode) = 0; + }; + typedef SharedPtr cConnectCallbacksPtr; + + + /** Callbacks used when listening for incoming connections as a server. */ + class cListenCallbacks + { + public: + /** Called when the TCP server created with Listen() accepts an incoming connection. + Provides the newly created Link that can be used for communication. */ + virtual void OnAccepted(cTCPLink & a_Link) = 0; + }; + typedef SharedPtr cListenCallbacksPtr; + + + /** Callbacks used when resolving names to IPs. */ + class cResolveNameCallbacks + { + public: + /** Called when the hostname is successfully resolved into an IP address. */ + virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) = 0; + + /** Called when an error is encountered while resolving. */ + virtual void OnError(int a_ErrorCode) = 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. */ + static bool Connect( + const AString & a_Host, + const 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. */ + static cServerHandlePtr Listen( + const UInt16 a_Port, + cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); + + + /** 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. */ + 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. */ + static bool IPToHostName( + const AString & a_IP, + cResolveNameCallbacksPtr a_Callbacks + ); +}; + + + + -- cgit v1.2.3 From b8b3409b74e93dd7d1e87f60f498c724e5374f26 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Fri, 9 Jan 2015 11:20:19 +0100 Subject: cNetwork: Linux compilation fix. --- src/OSSupport/Network.cpp | 9 +++++---- src/OSSupport/Network.h | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index d947bfa90..0e19e40de 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -358,8 +358,8 @@ protected: /** Converts LibEvent-generated log events into log messages in MCS log. */ static void LogCallback(int a_Severity, const char * a_Msg); - /** Runs the thread that LibEvent uses to dispatch event. */ - static void EventLoopThread(cNetworkSingleton * a_Self); + /** Implements the thread that runs LibEvent's event dispatcher loop. */ + static void RunEventLoop(cNetworkSingleton * a_Self); }; @@ -722,7 +722,8 @@ cNetworkSingleton::cNetworkSingleton(void) } // Create the event loop thread: - std::thread::thread(EventLoopThread, this).detach(); + std::thread EventLoopThread(RunEventLoop, this); + EventLoopThread.detach(); } @@ -827,7 +828,7 @@ void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg) -void cNetworkSingleton::EventLoopThread(cNetworkSingleton * a_Self) +void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self) { event_base_loop(a_Self->m_EventBase, EVLOOP_NO_EXIT_ON_EMPTY); } diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index 447cd457b..5cca511dc 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -22,6 +22,9 @@ public: class cCallbacks { public: + // Force a virtual destructor for all descendants: + virtual ~cCallbacks() {} + /** Called when there's data incoming from the remote peer. */ virtual void OnReceivedData(cTCPLink & a_Link, const char * a_Data, size_t a_Length) = 0; @@ -36,6 +39,9 @@ public: 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; @@ -86,6 +92,9 @@ 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. */ virtual void Close(void) = 0; @@ -105,6 +114,9 @@ public: 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 OnSuccess(cTCPLink & a_Link) = 0; @@ -119,6 +131,9 @@ public: class cListenCallbacks { public: + // Force a virtual destructor for all descendants: + virtual ~cListenCallbacks() {} + /** Called when the TCP server created with Listen() accepts an incoming connection. Provides the newly created Link that can be used for communication. */ virtual void OnAccepted(cTCPLink & a_Link) = 0; @@ -130,6 +145,9 @@ public: class cResolveNameCallbacks { public: + // Force a virtual destructor for all descendants: + virtual ~cResolveNameCallbacks() {} + /** Called when the hostname is successfully resolved into an IP address. */ virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) = 0; -- cgit v1.2.3 From a9b8a530b1e082d2ab108947ef28e0a58c8fd838 Mon Sep 17 00:00:00 2001 From: Tycho Date: Fri, 9 Jan 2015 14:07:59 +0000 Subject: Extracted Google connection test --- src/Globals.h | 10 ++++++ src/OSSupport/Network.cpp | 77 ++------------------------------------- tests/CMakeLists.txt | 1 + tests/Network/CMakeLists.txt | 14 ++++++++ tests/Network/Google.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 74 deletions(-) create mode 100644 tests/Network/CMakeLists.txt create mode 100644 tests/Network/Google.cpp diff --git a/src/Globals.h b/src/Globals.h index f0726454b..51ddc4e79 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -298,6 +298,16 @@ void inline LOGD(const char* a_Format, ...) 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); + va_end(argList); +} + #endif diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 0e19e40de..0f532624d 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -5,12 +5,13 @@ #include "Globals.h" #include "Network.h" +#include "Event.h" #include #include #include #include - +#include @@ -20,38 +21,6 @@ class cNetworkTest { public: - /** cTCPLink callbacks that dump everything it received to the log. */ - class cDumpCallbacks: - public cTCPLink::cCallbacks - { - cEvent & m_Event; - - virtual void OnReceivedData(cTCPLink & a_Link, const char * a_Data, size_t a_Size) override - { - // 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(cTCPLink & a_Link) override - { - LOGD("Remote has closed the connection."); - m_Event.Set(); - } - - virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) override - { - LOGD("Error in the cDumpCallbacks."); - m_Event.Set(); - } - - public: - cDumpCallbacks(cEvent & a_Event): - m_Event(a_Event) - { - } - }; /** cTCPLink callbacks that echo everything they receive back to the remote peer. */ @@ -114,36 +83,6 @@ public: } }; - - /** Connect callbacks that send a HTTP GET request for google.com when connected. */ - class cHTTPConnectCallbacks: - public cNetwork::cConnectCallbacks - { - cEvent & m_Event; - virtual void OnSuccess(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) override - { - LOGD("Error while connecting HTTP: %d", a_ErrorCode); - m_Event.Set(); - } - - public: - cHTTPConnectCallbacks(cEvent & a_Event): - m_Event(a_Event) - { - } - }; - - /** Listen callbacks that send a simple welcome message to all connecting peers. */ class cListenCallbacks: public cNetwork::cListenCallbacks @@ -157,15 +96,6 @@ public: cNetworkTest(void) { - 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."); /* LOGD("Creating a server on port 33033"); @@ -182,8 +112,7 @@ public: Server->Close(); */ - evtFinish.Wait(); - LOGD("Network test finished"); + } } g_NetworkTest; 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..8aae84590 --- /dev/null +++ b/tests/Network/CMakeLists.txt @@ -0,0 +1,14 @@ +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) +add_library(Network ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp) +target_link_libraries(Network event_core event_extra) + +add_executable(Google-exe Google.cpp) +target_link_libraries(Google-exe Network) +add_test(NAME Google-test COMMAND Google-exe) diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp new file mode 100644 index 000000000..0aa52d025 --- /dev/null +++ b/tests/Network/Google.cpp @@ -0,0 +1,85 @@ + +#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 OnSuccess(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) override + { + LOGD("Error while connecting HTTP: %d", a_ErrorCode); + 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; + + virtual void OnReceivedData(cTCPLink & a_Link, const char * a_Data, size_t a_Size) override + { + // 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(cTCPLink & a_Link) override + { + LOGD("Remote has closed the connection."); + m_Event.Set(); + } + + virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) override + { + LOGD("Error in the cDumpCallbacks."); + 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"); +} -- cgit v1.2.3 From bef957ef14fa6b215bd9d649c119a6cb16221248 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 11 Jan 2015 10:34:50 +0100 Subject: Added newline to logged messages in the tests. --- src/Globals.h | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Globals.h b/src/Globals.h index 51ddc4e79..654ede95f 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -268,43 +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, ...) FORMATSTRING(1, 2); -void inline LOG(const char* a_Format, ...) +void inline LOG(const char * a_Format, ...) { va_list argList; va_start(argList, a_Format); vprintf(a_Format, argList); + putchar('\n'); va_end(argList); } -- cgit v1.2.3 From c7335255acebc1598f6acccf4d4a2065cf2bd628 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 11 Jan 2015 10:43:02 +0100 Subject: Excluded PolarSSL tests from MCS tests. --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 728f5c935..9d7a34c14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,13 +137,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() -- cgit v1.2.3 From fde44cba0815f626253c0d352cd0d782eec94328 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 11 Jan 2015 11:21:18 +0100 Subject: cNetwork: Implemented HostnameToIP lookups. --- src/OSSupport/Network.cpp | 136 +++++++++++++++++++++++++++++++++++++++++-- src/OSSupport/Network.h | 11 +++- tests/Network/CMakeLists.txt | 9 +++ tests/Network/EchoServer.cpp | 19 ++++++ tests/Network/Google.cpp | 23 +++++++- tests/Network/NameLookup.cpp | 67 +++++++++++++++++++++ 6 files changed, 256 insertions(+), 9 deletions(-) create mode 100644 tests/Network/EchoServer.cpp create mode 100644 tests/Network/NameLookup.cpp diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 0f532624d..8e2d060b4 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -123,6 +123,27 @@ public: //////////////////////////////////////////////////////////////////////////////// // Class definitions: +/** Holds information about an in-progress hostname lookup. */ +class cHostnameLookup +{ + /** 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); + +public: + cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks); +}; +typedef SharedPtr cHostnameLookupPtr; +typedef std::vector cHostnameLookupPtrs; + + + + + /** Implements the cTCPLink details so that it can represent the single connection between two endpoints. */ class cTCPLinkImpl: public cTCPLink @@ -214,7 +235,8 @@ typedef std::vector cServerHandleImplPtrs; class cNetworkSingleton { - friend class cTCPLinkImpl; + friend class cHostnameLookup; // Needs access to m_DNSBase + friend class cTCPLinkImpl; // Needs access to m_EventBase and m_DNSBase public: /** Returns the singleton instance of this class */ @@ -280,6 +302,9 @@ protected: /** Container for all servers that are currently active. */ cServerHandleImplPtrs m_Servers; + /** Container for all pending hostname lookups. */ + cHostnameLookupPtrs m_HostnameLookups; + /** Initializes the LibEvent internals. */ cNetworkSingleton(void); @@ -289,12 +314,94 @@ protected: /** Implements the thread that runs LibEvent's event dispatcher loop. */ static void RunEventLoop(cNetworkSingleton * a_Self); + + /** Removes the specified hostname lookup from m_HostnameLookups. */ + void RemoveHostnameLookup(cHostnameLookup * a_HostnameLookup); }; +//////////////////////////////////////////////////////////////////////////////// +// cHostnameLookup: + +cHostnameLookup::cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks), + m_Hostname(a_Hostname) +{ + 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().m_DNSBase, a_Hostname.c_str(), nullptr, &hints, Callback, this); +} + + + + + +void cHostnameLookup::Callback(int a_ErrCode, struct 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); + cNetworkSingleton::Get().RemoveHostnameLookup(Self); + return; + } + + // Call the success handler for each entry received: + bool HasResolved = false; + 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 error + continue; + } + } + Self->m_Callbacks->OnNameResolved(Self->m_Hostname, IP); + HasResolved = true; + } + + // If only unsupported families were reported, call the Error handler: + if (!HasResolved) + { + Self->m_Callbacks->OnError(1); + } + else + { + Self->m_Callbacks->OnFinished(); + } + cNetworkSingleton::Get().RemoveHostnameLookup(Self); +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cTCPLinkImpl: @@ -716,9 +823,15 @@ bool cNetworkSingleton::HostnameToIP( cNetwork::cResolveNameCallbacksPtr a_Callbacks ) { - // TODO - ASSERT(!"Not implemented yet!"); - return false; + try + { + m_HostnameLookups.push_back(std::make_shared(a_Hostname, a_Callbacks)); + } + catch (...) + { + return false; + } + return true; } @@ -765,3 +878,18 @@ void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self) +void cNetworkSingleton::RemoveHostnameLookup(cHostnameLookup * a_HostnameLookup) +{ + 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[] +} + + + + diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index 5cca511dc..3f60b03a7 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -148,11 +148,18 @@ public: // Force a virtual destructor for all descendants: virtual ~cResolveNameCallbacks() {} - /** Called when the hostname is successfully resolved into an IP address. */ + /** Called when the hostname is successfully resolved into an IP address. + May be called multiple times if an address 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. */ + /** 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) = 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; diff --git a/tests/Network/CMakeLists.txt b/tests/Network/CMakeLists.txt index 8aae84590..a63ce7dfe 100644 --- a/tests/Network/CMakeLists.txt +++ b/tests/Network/CMakeLists.txt @@ -8,7 +8,16 @@ include_directories(${CMAKE_SOURCE_DIR}/lib/libevent/include) add_definitions(-DTEST_GLOBALS=1) add_library(Network ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp) target_link_libraries(Network event_core event_extra) +if (MSVC) + target_link_libraries(Network ws2_32.lib) +endif() add_executable(Google-exe Google.cpp) target_link_libraries(Google-exe Network) add_test(NAME Google-test COMMAND Google-exe) + +add_executable(EchoServer EchoServer.cpp) +target_link_libraries(EchoServer Network) + +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..def064083 --- /dev/null +++ b/tests/Network/EchoServer.cpp @@ -0,0 +1,19 @@ + +// EchoServer.cpp + +// Implements an Echo server using the LibEvent-based cNetwork API, as a test of that API + + + + + + +int main() +{ + // TODO + return 0; +} + + + + diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp index 0aa52d025..de5f95e1f 100644 --- a/tests/Network/Google.cpp +++ b/tests/Network/Google.cpp @@ -1,11 +1,17 @@ -#include "Globals.h" +// 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 @@ -35,6 +41,9 @@ public: }; + + + /** cTCPLink callbacks that dump everything it received to the log. */ class cDumpCallbacks: public cTCPLink::cCallbacks @@ -69,7 +78,11 @@ public: }; -int main() { + + + +int main() +{ cEvent evtFinish; LOGD("Network test: Connecting to google.com:80, reading front page via HTTP."); @@ -83,3 +96,7 @@ int main() { evtFinish.Wait(); LOGD("Network test finished"); } + + + + diff --git a/tests/Network/NameLookup.cpp b/tests/Network/NameLookup.cpp new file mode 100644 index 000000000..daa72a3cb --- /dev/null +++ b/tests/Network/NameLookup.cpp @@ -0,0 +1,67 @@ + +// 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) override + { + LOGD("Error %d while performing lookup!", a_ErrorCode); + abort(); + } + + virtual void OnFinished(void) override + { + LOGD("Resolving finished."); + m_Event.Set(); + } + +public: + cFinishLookupCallbacks(cEvent & a_Event): + m_Event(a_Event) + { + } +}; + + + + + +int main() +{ + cEvent evtFinish; + + LOGD("Network test: Looking up google.com"); + if (!cNetwork::HostnameToIP("google.com", std::make_shared(evtFinish))) + { + LOGWARNING("Cannot resolve google.com"); + abort(); + } + LOGD("Name lookup has been successfully queued"); + + evtFinish.Wait(); + LOGD("Network test finished"); + return 0; +} + + + + -- cgit v1.2.3 From 251c96952bb80c57f24d243def2677e3ee94efe1 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 11 Jan 2015 12:59:07 +0100 Subject: cNetwork: Implemented IP-to-hostname lookup. --- src/OSSupport/Network.cpp | 122 ++++++++++++++++++++++++++++++++++++++++--- tests/Network/NameLookup.cpp | 19 +++++-- 2 files changed, 130 insertions(+), 11 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 8e2d060b4..392fb586e 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -123,7 +123,7 @@ public: //////////////////////////////////////////////////////////////////////////////// // Class definitions: -/** Holds information about an in-progress hostname lookup. */ +/** Holds information about an in-progress Hostname-to-IP lookup. */ class cHostnameLookup { /** The callbacks to call for resolved names / errors. */ @@ -144,6 +144,27 @@ typedef std::vector cHostnameLookupPtrs; +/** Holds information about an in-progress IP-to-Hostname lookup. */ +class cIPLookup +{ + /** The callbacks to call for resolved names / errors. */ + cNetwork::cResolveNameCallbacksPtr m_Callbacks; + + /** The IP that was queried (needed for the callbacks). */ + AString m_IP; + + static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); + +public: + cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks); +}; +typedef SharedPtr cIPLookupPtr; +typedef std::vector cIPLookupPtrs; + + + + + /** Implements the cTCPLink details so that it can represent the single connection between two endpoints. */ class cTCPLinkImpl: public cTCPLink @@ -236,6 +257,7 @@ typedef std::vector cServerHandleImplPtrs; class cNetworkSingleton { friend class cHostnameLookup; // Needs access to m_DNSBase + friend class cIPLookup; // Needs access to m_DNSBase friend class cTCPLinkImpl; // Needs access to m_EventBase and m_DNSBase public: @@ -305,6 +327,9 @@ protected: /** Container for all pending hostname lookups. */ cHostnameLookupPtrs m_HostnameLookups; + /** Container for all pending IP lookups. */ + cIPLookupPtrs m_IPLookups; + /** Initializes the LibEvent internals. */ cNetworkSingleton(void); @@ -315,8 +340,13 @@ protected: /** Implements the thread that runs LibEvent's event dispatcher loop. */ static void RunEventLoop(cNetworkSingleton * a_Self); - /** Removes the specified hostname lookup from m_HostnameLookups. */ - void RemoveHostnameLookup(cHostnameLookup * 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); + + /** 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); }; @@ -343,7 +373,7 @@ cHostnameLookup::cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveN -void cHostnameLookup::Callback(int a_ErrCode, struct evutil_addrinfo * a_Addr, void * a_Self) +void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a_Self) { // Get the Self class: cHostnameLookup * Self = reinterpret_cast(a_Self); @@ -359,6 +389,7 @@ void cHostnameLookup::Callback(int a_ErrCode, struct evutil_addrinfo * a_Addr, v // 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]; @@ -395,6 +426,7 @@ void cHostnameLookup::Callback(int a_ErrCode, struct evutil_addrinfo * a_Addr, v { Self->m_Callbacks->OnFinished(); } + evutil_freeaddrinfo(OrigAddr); cNetworkSingleton::Get().RemoveHostnameLookup(Self); } @@ -402,6 +434,57 @@ void cHostnameLookup::Callback(int a_ErrCode, struct evutil_addrinfo * a_Addr, v +//////////////////////////////////////////////////////////////////////////////// +// cIPLookup: + +cIPLookup::cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks), + m_IP(a_IP) +{ + sockaddr_storage sa; + int salen = static_cast(sizeof(sa)); + evutil_parse_sockaddr_port(a_IP.c_str(), reinterpret_cast(&sa), &salen); + switch (sa.ss_family) + { + case AF_INET: + { + sockaddr_in * sa4 = reinterpret_cast(&sa); + evdns_base_resolve_reverse(cNetworkSingleton::Get().m_DNSBase, &(sa4->sin_addr), 0, Callback, this); + } + case AF_INET6: + { + } + } +} + + + + + +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); + + if ((a_Result != 0) || (a_Addresses == nullptr)) + { + // An error has occurred, notify the error callback: + Self->m_Callbacks->OnError(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); +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cTCPLinkImpl: @@ -842,9 +925,15 @@ bool cNetworkSingleton::IPToHostName( cNetwork::cResolveNameCallbacksPtr a_Callbacks ) { - // TODO - ASSERT(!"Not implemented yet!"); - return false; + try + { + m_IPLookups.push_back(std::make_shared(a_IP, a_Callbacks)); + } + catch (...) + { + return false; + } + return true; } @@ -878,7 +967,8 @@ void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self) -void cNetworkSingleton::RemoveHostnameLookup(cHostnameLookup * a_HostnameLookup) + +void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameLookup) { for (auto itr = m_HostnameLookups.begin(), end = m_HostnameLookups.end(); itr != end; ++itr) { @@ -893,3 +983,19 @@ void cNetworkSingleton::RemoveHostnameLookup(cHostnameLookup * a_HostnameLookup) + +void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup) +{ + 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_HostnameLookups[] +} + + + + diff --git a/tests/Network/NameLookup.cpp b/tests/Network/NameLookup.cpp index daa72a3cb..74a57258c 100644 --- a/tests/Network/NameLookup.cpp +++ b/tests/Network/NameLookup.cpp @@ -25,7 +25,7 @@ class cFinishLookupCallbacks: virtual void OnError(int a_ErrorCode) override { LOGD("Error %d while performing lookup!", a_ErrorCode); - abort(); + exit(a_ErrorCode); } virtual void OnFinished(void) override @@ -49,15 +49,28 @@ 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"); + 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; } -- cgit v1.2.3 From 28e97d54684a246ce2c46197fe229cb9cb152562 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Mon, 12 Jan 2015 09:38:00 +0100 Subject: cNetwork: Implemented basic server functionality. --- src/OSSupport/Network.cpp | 337 +++++++++++++++++++++++++++---------------- tests/Network/EchoServer.cpp | 68 ++++++++- 2 files changed, 282 insertions(+), 123 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 392fb586e..399bffe50 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -5,116 +5,13 @@ #include "Globals.h" #include "Network.h" -#include "Event.h" #include #include #include #include - +#include #include - - - -//////////////////////////////////////////////////////////////////////////////// -// Self-test: - -class cNetworkTest -{ -public: - - - /** cTCPLink callbacks that echo everything they receive back to the remote peer. */ - class cEchoCallbacks: - public cTCPLink::cCallbacks - { - cEvent & m_Event; - - virtual void OnReceivedData(cTCPLink & a_Link, const char * a_Data, size_t a_Size) override - { - // Echo the incoming data back to outgoing data: - LOGD("Data received (%u bytes), echoing back.", static_cast(a_Size)); - a_Link.Send(a_Data, a_Size); - LOGD("Echo queued"); - } - - virtual void OnRemoteClosed(cTCPLink & a_Link) override - { - LOGD("Remote has closed the connection."); - m_Event.Set(); - } - - virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) override - { - LOGD("Error in the cEchoCallbacks."); - m_Event.Set(); - } - - public: - cEchoCallbacks(cEvent & a_Event): - m_Event(a_Event) - { - } - }; - - - /** Connect callbacks that send a simple test message when connected. */ - class cConnectCallbacks: - public cNetwork::cConnectCallbacks - { - cEvent & m_Event; - - virtual void OnSuccess(cTCPLink & a_Link) override - { - LOGD("Connected, sending test message"); - a_Link.Send("test message"); - LOGD("Message queued."); - } - - virtual void OnError(int a_ErrorCode) override - { - LOGD("Error while connecting: %d", a_ErrorCode); - m_Event.Set(); - } - - public: - cConnectCallbacks(cEvent & a_Event): - m_Event(a_Event) - { - } - }; - - /** Listen callbacks that send a simple welcome message to all connecting peers. */ - class cListenCallbacks: - public cNetwork::cListenCallbacks - { - virtual void OnAccepted(cTCPLink & a_Link) override - { - // Send some trivial data: - a_Link.Send("Welcome to echo server\n"); - } - }; - - cNetworkTest(void) - { - - /* - LOGD("Creating a server on port 33033"); - auto Server = cNetwork::Listen(33033, std::make_shared(), std::make_shared()); - LOGD("Test server created."); - - LOGD("Connecting to test server"); - cNetwork::Connect("localhost", 33033, std::make_shared(), std::make_shared()); - - LOGD("Waiting for network operations to finish."); - evtFinish.Wait(); - - LOGD("Terminating test server."); - Server->Close(); - */ - - - } -} g_NetworkTest; +#include "Event.h" @@ -170,6 +67,8 @@ class cTCPLinkImpl: public cTCPLink { typedef cTCPLink super; + friend class cServerHandleImpl; + public: /** Creates a new link to be queued to connect to a specified host:port. Used for outgoing connections created using cNetwork::Connect(). @@ -178,7 +77,7 @@ public: /** Creates a new link based on the given socket. Used for connections accepted in a server using cNetwork::Listen(). */ - cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks); + cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server); /** Queues a connection request to the specified host. a_ConnectCallbacks must be valid. @@ -203,6 +102,10 @@ protected: /** The LibEvent handle representing this connection. */ bufferevent * m_BufferEvent; + /** The server handle that has created this link. + Only valid for incoming connections, NULL for outgoing connections. */ + cServerHandleImpl * m_Server; + /** Callback that LibEvent calls when there's data available from the remote peer. */ static void ReadCallback(bufferevent * a_BufferEvent, void * a_Self); @@ -221,6 +124,9 @@ typedef std::vector cTCPLinkImplPtrs; class cServerHandleImpl: public cServerHandle { + typedef cServerHandle super; + friend class cTCPLinkImpl; + public: /** Creates a new instance with the specified callbacks. Initializes the internals, but doesn't start listening yet. */ @@ -244,8 +150,25 @@ protected: /** The callbacks used to create new cTCPLink instances for incoming connections. */ cTCPLink::cCallbacksPtr m_LinkCallbacks; + /** 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; + + + /** 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); }; typedef SharedPtr cServerHandleImplPtr; typedef std::vector cServerHandleImplPtrs; @@ -256,9 +179,10 @@ typedef std::vector cServerHandleImplPtrs; class cNetworkSingleton { - friend class cHostnameLookup; // Needs access to m_DNSBase - friend class cIPLookup; // Needs access to m_DNSBase - friend class cTCPLinkImpl; // Needs access to m_EventBase and m_DNSBase + friend class cHostnameLookup; // Needs access to m_DNSBase + friend class cIPLookup; // Needs access to m_DNSBase + friend class cTCPLinkImpl; // Needs access to m_EventBase and m_DNSBase + friend class cServerHandleImpl; // Needs access to m_EventBase public: /** Returns the singleton instance of this class */ @@ -347,6 +271,10 @@ protected: /** 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); + + /** 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); }; @@ -490,7 +418,8 @@ void cIPLookup::Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, -1, BEV_OPT_CLOSE_ON_FREE)) + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, -1, BEV_OPT_CLOSE_ON_FREE)), + m_Server(nullptr) { bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); @@ -500,9 +429,10 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): -cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks): +cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server): super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, a_Socket, BEV_OPT_CLOSE_ON_FREE)) + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, a_Socket, BEV_OPT_CLOSE_ON_FREE)), + m_Server(a_Server) { bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); @@ -517,6 +447,7 @@ Returns true on success, false on failure. */ bool cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks) { ASSERT(bufferevent_getfd(m_BufferEvent) == -1); // Did you create this object using the right constructor (the one without the Socket param)? + ASSERT(m_Server == nullptr); ASSERT(a_ConnectCallbacks != nullptr); m_ConnectCallbacks = a_ConnectCallbacks; @@ -668,6 +599,14 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void else { Self->m_Callbacks->OnError(*Self, EVUTIL_SOCKET_ERROR()); + if (Self->m_Server == nullptr) + { + cNetworkSingleton::Get().RemoveLink(Self); + } + else + { + Self->m_Server->RemoveLink(Self); + } } return; } @@ -684,10 +623,18 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void } } - // If the connectino has been closed, call the link callback: + // If the connection has been closed, call the link callback and remove the connection: if (a_What & BEV_EVENT_EOF) { Self->m_Callbacks->OnRemoteClosed(*Self); + if (Self->m_Server != nullptr) + { + Self->m_Server->RemoveLink(Self); + } + else + { + cNetworkSingleton::Get().RemoveLink(Self); + } return; } @@ -705,10 +652,11 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void cServerHandleImpl::cServerHandleImpl(cNetwork::cListenCallbacksPtr a_ListenCallbacks, cTCPLink::cCallbacksPtr a_LinkCallbacks): m_ListenCallbacks(a_ListenCallbacks), - m_LinkCallbacks(a_LinkCallbacks) + m_LinkCallbacks(a_LinkCallbacks), + m_ConnListener(nullptr), + m_SecondaryConnListener(nullptr), + m_IsListening(false) { - // TODO - ASSERT(!"Not implemented yet!"); } @@ -717,8 +665,23 @@ cServerHandleImpl::cServerHandleImpl(cNetwork::cListenCallbacksPtr a_ListenCallb void cServerHandleImpl::Close(void) { - // TODO - ASSERT(!"Not implemented yet!"); + // Stop the listener sockets: + evconnlistener_free(m_ConnListener); + m_ConnListener = nullptr; + if (m_SecondaryConnListener != nullptr) + { + evconnlistener_free(m_SecondaryConnListener); + m_SecondaryConnListener = nullptr; + } + m_IsListening = false; + + // Close all connections: + cTCPLinkImplPtrs Conns; + std::swap(Conns, m_Connections); + for (auto conn: Conns) + { + conn->Close(); + } } @@ -727,9 +690,123 @@ void cServerHandleImpl::Close(void) bool cServerHandleImpl::Listen(UInt16 a_Port) { - // TODO - ASSERT(!"Not implemented yet!"); - return false; + ASSERT(!m_IsListening); + + // Set up the main socket: + // It should listen on IPv6 with IPv4 fallback, when available; IPv4 when IPv6 is not available. + bool NeedsTwoSockets = false; + evutil_socket_t MainSock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (MainSock == SOCKET_ERROR) + { + // Failed to create IPv6 socket, create an IPv4 one instead: + MainSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (MainSock == SOCKET_ERROR) + { + 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) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot bind to IPv4 port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + 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: + NeedsTwoSockets = ( + (setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&Zero), sizeof(Zero)) == SOCKET_ERROR) && + (EVUTIL_SOCKET_ERROR() == WSAENOPROTOOPT) + ); + #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) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot bind to IPv6 port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(MainSock); + return false; + } + } + if (evutil_make_socket_nonblocking(MainSock) != 0) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot make socket for port %d non-blocking: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(MainSock); + return false; + } + if (listen(MainSock, 0) != 0) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot listen on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(MainSock); + return false; + } + m_ConnListener = evconnlistener_new(cNetworkSingleton::Get().m_EventBase, Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, MainSock); + + // If a secondary socket is required (WinXP dual-stack), create it here: + if (NeedsTwoSockets) + { + evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (SecondSock != SOCKET_ERROR) + { + evutil_make_socket_nonblocking(SecondSock); + m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().m_EventBase, Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); + } + } + m_IsListening = true; + 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); + + // Create a new cTCPLink for the incoming connection: + cTCPLinkImplPtr Link = std::make_shared(a_Socket, Self->m_LinkCallbacks, Self); + Self->m_Connections.push_back(Link); + + // Call the OnAccepted callback: + Self->m_ListenCallbacks->OnAccepted(*Link); +} + + + + + +void cServerHandleImpl::RemoveLink(const cTCPLinkImpl * a_Link) +{ + 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[] } @@ -993,7 +1070,23 @@ void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup) m_IPLookups.erase(itr); return; } - } // for itr - m_HostnameLookups[] + } // for itr - m_IPLookups[] +} + + + + + +void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link) +{ + 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[] } diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp index def064083..e6571a57f 100644 --- a/tests/Network/EchoServer.cpp +++ b/tests/Network/EchoServer.cpp @@ -3,6 +3,53 @@ // 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" + + + + + +class cEchoServerCallbacks: + public cNetwork::cListenCallbacks +{ + virtual void OnAccepted(cTCPLink & a_Link) override + { + LOGD("New client accepted, sending welcome message."); + // Send a welcome message to each connecting client: + a_Link.Send("Welcome to the simple echo server.\r\n"); + LOGD("Welcome message queued."); + } +}; + + + + + +/** cTCPLink callbacks that echo everything they receive back to the remote peer. */ +class cEchoLinkCallbacks: + public cTCPLink::cCallbacks +{ + virtual void OnReceivedData(cTCPLink & a_Link, const char * a_Data, size_t a_Size) override + { + // Echo the incoming data back to outgoing data: + LOGD("%p: Data received (%u bytes), echoing back.", &a_Link, static_cast(a_Size)); + a_Link.Send(a_Data, a_Size); + LOGD("Echo queued"); + } + + virtual void OnRemoteClosed(cTCPLink & a_Link) override + { + LOGD("%p: Remote has closed the connection.", &a_Link); + } + + virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) override + { + LOGD("%p: Error %d in the cEchoLinkCallbacks.", &a_Link, a_ErrorCode); + } +}; @@ -10,7 +57,26 @@ int main() { - // TODO + LOGD("EchoServer: starting up"); + cServerHandlePtr Server = cNetwork::Listen(9876, std::make_shared(), 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 terminate 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()); + + LOG("Network test finished."); return 0; } -- cgit v1.2.3 From a2aa37bdc505c72edd5c9d41bac6e7679cea13c3 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Mon, 12 Jan 2015 10:17:11 +0100 Subject: cNetwork: Implemented link address getting. --- src/OSSupport/Network.cpp | 174 +++++++++++++++++++++++++++++-------------- tests/Network/EchoServer.cpp | 8 +- 2 files changed, 121 insertions(+), 61 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 399bffe50..2c9b2ef37 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -62,6 +62,13 @@ typedef std::vector cIPLookupPtrs; +// fwd: +class cServerHandleImpl; + + + + + /** Implements the cTCPLink details so that it can represent the single connection between two endpoints. */ class cTCPLinkImpl: public cTCPLink @@ -76,8 +83,9 @@ public: cTCPLinkImpl(const cCallbacksPtr a_LinkCallbacks); /** Creates a new link based on the given socket. - Used for connections accepted in a server using cNetwork::Listen(). */ - cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server); + Used for connections accepted in a server using cNetwork::Listen(). + a_Address and a_AddrLen describe the remote peer that has connected. */ + cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int a_AddrLen); /** Queues a connection request to the specified host. a_ConnectCallbacks must be valid. @@ -86,10 +94,10 @@ public: // cTCPLink overrides: virtual bool Send(const void * a_Data, size_t a_Length) override; - virtual AString GetLocalIP(void) const override; - virtual UInt16 GetLocalPort(void) const override; - virtual AString GetRemoteIP(void) const override; - virtual UInt16 GetRemotePort(void) const 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 Close(void) override; virtual void Drop(void) override; @@ -106,12 +114,33 @@ protected: Only valid for incoming connections, NULL for outgoing connections. */ cServerHandleImpl * 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; + /** 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, int 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); }; typedef SharedPtr cTCPLinkImplPtr; typedef std::vector cTCPLinkImplPtrs; @@ -337,13 +366,13 @@ void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a } default: { - // Unknown address family, handle as error - continue; + // 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) @@ -378,11 +407,21 @@ cIPLookup::cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_ { sockaddr_in * sa4 = reinterpret_cast(&sa); evdns_base_resolve_reverse(cNetworkSingleton::Get().m_DNSBase, &(sa4->sin_addr), 0, Callback, this); + break; } case AF_INET6: { + sockaddr_in6 * sa6 = reinterpret_cast(&sa); + evdns_base_resolve_reverse_ipv6(cNetworkSingleton::Get().m_DNSBase, &(sa6->sin6_addr), 0, Callback, this); + break; } - } + default: + { + LOGWARNING("%s: Unknown address family: %d", __FUNCTION__, sa.ss_family); + ASSERT(!"Unknown address family"); + break; + } + } // switch (address family) } @@ -429,11 +468,13 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): -cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server): +cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int a_AddrLen): super(a_LinkCallbacks), m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, a_Socket, BEV_OPT_CLOSE_ON_FREE)), m_Server(a_Server) { + UpdateLocalAddress(); + UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort); bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); } @@ -499,50 +540,6 @@ bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) -AString cTCPLinkImpl::GetLocalIP(void) const -{ - // TODO - ASSERT(!"cTCPLinkImpl::GetLocalIP: Not implemented yet"); - return ""; -} - - - - - -UInt16 cTCPLinkImpl::GetLocalPort(void) const -{ - // TODO - ASSERT(!"cTCPLinkImpl::GetLocalPort(): Not implemented yet"); - return 0; -} - - - - - -AString cTCPLinkImpl::GetRemoteIP(void) const -{ - // TODO - ASSERT(!"cTCPLinkImpl::GetRemoteIP(): Not implemented yet"); - return ""; -} - - - - - -UInt16 cTCPLinkImpl::GetRemotePort(void) const -{ - // TODO - ASSERT(!"cTCPLinkImpl::GetRemotePort(): Not implemented yet"); - return 0; -} - - - - - void cTCPLinkImpl::Close(void) { // TODO @@ -621,6 +618,8 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void Self->m_ConnectCallbacks.reset(); return; } + Self->UpdateLocalAddress(); + Self->UpdateRemoteAddress(); } // If the connection has been closed, call the link callback and remove the connection: @@ -647,6 +646,64 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void +void cTCPLinkImpl::UpdateAddress(const sockaddr * a_Address, int a_AddrLen, AString & a_IP, UInt16 & a_Port) +{ + 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; + int 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; + int salen = static_cast(sizeof(sa)); + getpeername(bufferevent_getfd(m_BufferEvent), reinterpret_cast(&sa), &salen); + UpdateAddress(reinterpret_cast(&sa), salen, m_LocalIP, m_LocalPort); +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cServerHandleImpl: @@ -786,7 +843,7 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_ ASSERT(Self != nullptr); // Create a new cTCPLink for the incoming connection: - cTCPLinkImplPtr Link = std::make_shared(a_Socket, Self->m_LinkCallbacks, Self); + cTCPLinkImplPtr Link = std::make_shared(a_Socket, Self->m_LinkCallbacks, Self, a_Addr, a_Len); Self->m_Connections.push_back(Link); // Call the OnAccepted callback: @@ -933,6 +990,9 @@ cNetworkSingleton & cNetworkSingleton::Get(void) } + + + bool cNetworkSingleton::Connect( const AString & a_Host, const UInt16 a_Port, @@ -947,7 +1007,7 @@ bool cNetworkSingleton::Connect( // Queue the connection: if (!ConnRequest->Connect(a_Host, a_Port, a_ConnectCallbacks)) { - // TODO: m_Connections.remove(ConnRequest); + RemoveLink(ConnRequest.get()); return false; } diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp index e6571a57f..2316d1177 100644 --- a/tests/Network/EchoServer.cpp +++ b/tests/Network/EchoServer.cpp @@ -17,7 +17,7 @@ class cEchoServerCallbacks: { virtual void OnAccepted(cTCPLink & a_Link) override { - LOGD("New client accepted, sending welcome message."); + LOGD("New client accepted (%s:%p), 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."); @@ -35,19 +35,19 @@ class cEchoLinkCallbacks: virtual void OnReceivedData(cTCPLink & a_Link, const char * a_Data, size_t a_Size) override { // Echo the incoming data back to outgoing data: - LOGD("%p: Data received (%u bytes), echoing back.", &a_Link, static_cast(a_Size)); + LOGD("%p (%s:%d): Data received (%u bytes), echoing back.", &a_Link, a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort(), static_cast(a_Size)); a_Link.Send(a_Data, a_Size); LOGD("Echo queued"); } virtual void OnRemoteClosed(cTCPLink & a_Link) override { - LOGD("%p: Remote has closed the connection.", &a_Link); + LOGD("%p (%s:%d): Remote has closed the connection.", &a_Link, a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort()); } virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) override { - LOGD("%p: Error %d in the cEchoLinkCallbacks.", &a_Link, a_ErrorCode); + LOGD("%p (%s:%d): Error %d in the cEchoLinkCallbacks.", &a_Link, a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort(), a_ErrorCode); } }; -- cgit v1.2.3 From 9ffca127090e98f473a3a6b686804672fa0c40b0 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Mon, 12 Jan 2015 10:54:28 +0100 Subject: cNetwork: Fixed Linux compilation. --- src/OSSupport/Network.cpp | 28 +++++++++++++++++++++++----- tests/Network/EchoServer.cpp | 2 +- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 2c9b2ef37..a34f7ebca 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -310,6 +310,22 @@ protected: +//////////////////////////////////////////////////////////////////////////////// +// Globals: + +bool IsValidSocket(evutil_socket_t a_Socket) +{ + #ifdef _WIN32 + return (a_Socket != INVALID_SOCKET); + #else // _WIN32 + return (a_Socket >= 0); + #endif // else _WIN32 +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cHostnameLookup: @@ -683,7 +699,7 @@ void cTCPLinkImpl::UpdateAddress(const sockaddr * a_Address, int a_AddrLen, AStr void cTCPLinkImpl::UpdateLocalAddress(void) { sockaddr_storage sa; - int salen = static_cast(sizeof(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); } @@ -695,7 +711,7 @@ void cTCPLinkImpl::UpdateLocalAddress(void) void cTCPLinkImpl::UpdateRemoteAddress(void) { sockaddr_storage sa; - int salen = static_cast(sizeof(sa)); + socklen_t salen = static_cast(sizeof(sa)); getpeername(bufferevent_getfd(m_BufferEvent), reinterpret_cast(&sa), &salen); UpdateAddress(reinterpret_cast(&sa), salen, m_LocalIP, m_LocalPort); } @@ -753,12 +769,14 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) // It should listen on IPv6 with IPv4 fallback, when available; IPv4 when IPv6 is not available. bool NeedsTwoSockets = false; evutil_socket_t MainSock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); - if (MainSock == SOCKET_ERROR) + if (!IsValidSocket(MainSock)) { // Failed to create IPv6 socket, create an IPv4 one instead: MainSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (MainSock == SOCKET_ERROR) + if (!IsValidSocket(MainSock)) { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("%s: Cannot create a socket for neither IPv6 nor IPv4: %d (%s)", __FUNCTION__, err, evutil_socket_error_to_string(err)); return false; } @@ -822,7 +840,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) if (NeedsTwoSockets) { evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (SecondSock != SOCKET_ERROR) + if (!IsValidSocket(SecondSock)) { evutil_make_socket_nonblocking(SecondSock); m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().m_EventBase, Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp index 2316d1177..3d9a6228d 100644 --- a/tests/Network/EchoServer.cpp +++ b/tests/Network/EchoServer.cpp @@ -17,7 +17,7 @@ class cEchoServerCallbacks: { virtual void OnAccepted(cTCPLink & a_Link) override { - LOGD("New client accepted (%s:%p), sending welcome message.", a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort()); + 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."); -- cgit v1.2.3 From d8ac99a0374b528caca66ad8ecb5fb36f6334e77 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Mon, 12 Jan 2015 14:58:52 +0100 Subject: cNetwork: Implemented connection shutdown and close. --- src/OSSupport/Network.cpp | 66 +++++++++++++++++++++++++++++++++++--------- src/OSSupport/Network.h | 10 ++++--- tests/Network/EchoServer.cpp | 16 ++++++++++- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index a34f7ebca..2ef6ace3c 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -87,6 +87,9 @@ public: a_Address and a_AddrLen describe the remote peer that has connected. */ cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int a_AddrLen); + /** Destroys the LibEvent handle representing the link. */ + ~cTCPLinkImpl(); + /** Queues a connection request to the specified host. a_ConnectCallbacks must be valid. The object must have been constructed by the right constructor (without the Socket param). */ @@ -98,8 +101,8 @@ public: 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; - virtual void Drop(void) override; protected: @@ -164,6 +167,9 @@ public: cTCPLink::cCallbacksPtr a_LinkCallbacks ); + /** Closes the server, dropping all the connections. */ + ~cServerHandleImpl(); + /** Starts listening on the specified port. Both IPv4 and IPv6 interfaces are used, if possible. */ bool Listen(UInt16 a_Port); @@ -499,6 +505,15 @@ cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_L +cTCPLinkImpl::~cTCPLinkImpl() +{ + bufferevent_free(m_BufferEvent); +} + + + + + /** Schedules the actual connection request. Returns true on success, false on failure. */ bool cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks) @@ -556,20 +571,31 @@ bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) -void cTCPLinkImpl::Close(void) +void cTCPLinkImpl::Shutdown(void) { - // TODO - ASSERT(!"cTCPLinkImpl::Close(): Not implemented yet"); + #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::Drop(void) +void cTCPLinkImpl::Close(void) { - // TODO - ASSERT(!"cTCPLinkImpl::Drop(): Not implemented yet"); + bufferevent_disable(m_BufferEvent, EV_READ | EV_WRITE); + if (m_Server == nullptr) + { + cNetworkSingleton::Get().RemoveLink(this); + } + else + { + m_Server->RemoveLink(this); + } } @@ -736,24 +762,38 @@ cServerHandleImpl::cServerHandleImpl(cNetwork::cListenCallbacksPtr a_ListenCallb +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_free(m_ConnListener); - m_ConnListener = nullptr; + evconnlistener_disable(m_ConnListener); if (m_SecondaryConnListener != nullptr) { - evconnlistener_free(m_SecondaryConnListener); - m_SecondaryConnListener = nullptr; + evconnlistener_disable(m_SecondaryConnListener); } m_IsListening = false; - // Close all connections: + // Shutdown all connections: cTCPLinkImplPtrs Conns; std::swap(Conns, m_Connections); for (auto conn: Conns) { - conn->Close(); + conn->Shutdown(); } } diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index 3f60b03a7..11c45b152 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -66,12 +66,13 @@ public: virtual UInt16 GetRemotePort(void) const = 0; /** Closes the link gracefully. - The link will keep trying to send the queued data, then it will send the FIN packet. */ - virtual void Close(void) = 0; + 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 Drop(void) = 0; + virtual void Close(void) = 0; protected: /** Callbacks to be used for the various situations. */ @@ -95,7 +96,8 @@ public: // Force a virtual destructor for all descendants: virtual ~cServerHandle() {} - /** Stops the server, no more incoming connections will be accepted. */ + /** 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. */ diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp index 3d9a6228d..1e0ac023f 100644 --- a/tests/Network/EchoServer.cpp +++ b/tests/Network/EchoServer.cpp @@ -38,6 +38,16 @@ class cEchoLinkCallbacks: LOGD("%p (%s:%d): Data received (%u bytes), echoing back.", &a_Link, a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort(), static_cast(a_Size)); a_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') + { + a_Link.Close(); + return; + } + } } virtual void OnRemoteClosed(cTCPLink & a_Link) override @@ -67,7 +77,7 @@ int main() ASSERT(Server->IsListening()); // Wait for the user to terminate the server: - printf("Press enter to terminate the server.\n"); + printf("Press enter to close the server.\n"); AString line; std::getline(std::cin, line); @@ -75,6 +85,10 @@ int main() 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; -- cgit v1.2.3 From ddb1818dd54d82e19e7f629e6a729bccb68bfcf1 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Mon, 12 Jan 2015 15:45:30 +0100 Subject: cNetwork: Added multithreading protection. --- src/OSSupport/CriticalSection.cpp | 1 + src/OSSupport/Network.cpp | 30 +++++++++++++++++++++++++++--- tests/Network/CMakeLists.txt | 8 +++++++- 3 files changed, 35 insertions(+), 4 deletions(-) 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/Network.cpp b/src/OSSupport/Network.cpp index 2ef6ace3c..bee5bc09f 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -12,6 +12,7 @@ #include #include #include "Event.h" +#include "CriticalSection.h" @@ -197,6 +198,9 @@ protected: /** Container for all currently active connections on this server. */ cTCPLinkImplPtrs m_Connections; + /** Mutex protecting m_Connections againt multithreaded access. */ + cCriticalSection m_CS; + /** 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); @@ -289,6 +293,9 @@ protected: /** Container for all pending IP lookups. */ cIPLookupPtrs m_IPLookups; + /** Mutex protecting all containers against multithreaded access. */ + cCriticalSection m_CS; + /** Initializes the LibEvent internals. */ cNetworkSingleton(void); @@ -587,6 +594,7 @@ void cTCPLinkImpl::Shutdown(void) void cTCPLinkImpl::Close(void) { + // Disable all events on the socket, but keep it alive (multithreading): bufferevent_disable(m_BufferEvent, EV_READ | EV_WRITE); if (m_Server == nullptr) { @@ -790,7 +798,10 @@ void cServerHandleImpl::Close(void) // Shutdown all connections: cTCPLinkImplPtrs Conns; - std::swap(Conns, m_Connections); + { + cCSLock Lock(m_CS); + std::swap(Conns, m_Connections); + } for (auto conn: Conns) { conn->Shutdown(); @@ -902,7 +913,10 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_ // Create a new cTCPLink for the incoming connection: cTCPLinkImplPtr Link = std::make_shared(a_Socket, Self->m_LinkCallbacks, Self, a_Addr, a_Len); - Self->m_Connections.push_back(Link); + { + cCSLock Lock(Self->m_CS); + Self->m_Connections.push_back(Link); + } // Lock(m_CS) // Call the OnAccepted callback: Self->m_ListenCallbacks->OnAccepted(*Link); @@ -914,6 +928,7 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_ 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) @@ -1060,7 +1075,10 @@ bool cNetworkSingleton::Connect( { // Add a connection request to the queue: cTCPLinkImplPtr ConnRequest = std::make_shared(a_LinkCallbacks); - m_Connections.push_back(ConnRequest); + { + cCSLock Lock(m_CS); + m_Connections.push_back(ConnRequest); + } // Lock(m_CS) // Queue the connection: if (!ConnRequest->Connect(a_Host, a_Port, a_ConnectCallbacks)) @@ -1088,6 +1106,7 @@ cServerHandlePtr cNetworkSingleton::Listen( { return res; } + cCSLock Lock(m_CS); m_Servers.push_back(res); return res; } @@ -1103,6 +1122,7 @@ bool cNetworkSingleton::HostnameToIP( { try { + cCSLock Lock(m_CS); m_HostnameLookups.push_back(std::make_shared(a_Hostname, a_Callbacks)); } catch (...) @@ -1122,6 +1142,7 @@ bool cNetworkSingleton::IPToHostName( { try { + cCSLock Lock(m_CS); m_IPLookups.push_back(std::make_shared(a_IP, a_Callbacks)); } catch (...) @@ -1165,6 +1186,7 @@ void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self) 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) @@ -1181,6 +1203,7 @@ void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameL 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) @@ -1197,6 +1220,7 @@ void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup) 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) diff --git a/tests/Network/CMakeLists.txt b/tests/Network/CMakeLists.txt index a63ce7dfe..9d0dcdc24 100644 --- a/tests/Network/CMakeLists.txt +++ b/tests/Network/CMakeLists.txt @@ -6,7 +6,13 @@ include_directories(${CMAKE_SOURCE_DIR}/src/) include_directories(${CMAKE_SOURCE_DIR}/lib/libevent/include) add_definitions(-DTEST_GLOBALS=1) -add_library(Network ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp) +add_library(Network + ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp + ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp +) + target_link_libraries(Network event_core event_extra) if (MSVC) target_link_libraries(Network ws2_32.lib) -- cgit v1.2.3 From 235b8f1f6b84dbea8ea18da50a8d87bf5ae8c319 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Mon, 12 Jan 2015 16:19:24 +0100 Subject: cNetwork: Added error logging to server socket creation. This is mainly for WinXP and RasPi testing. --- src/OSSupport/Network.cpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index bee5bc09f..b916fd804 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -823,6 +823,8 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) if (!IsValidSocket(MainSock)) { // Failed to create IPv6 socket, create an IPv4 one instead: + int 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)) { @@ -850,9 +852,12 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) UInt32 Zero = 0; #ifdef _WIN32 // WinXP doesn't support this feature, so if the setting fails, create another socket later on: - NeedsTwoSockets = ( - (setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&Zero), sizeof(Zero)) == SOCKET_ERROR) && - (EVUTIL_SOCKET_ERROR() == WSAENOPROTOOPT) + int res = setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&Zero), sizeof(Zero)); + int 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)); @@ -890,11 +895,24 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) // If a secondary socket is required (WinXP dual-stack), create it here: if (NeedsTwoSockets) { + LOGD("Creating a second socket for IPv4"); evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (!IsValidSocket(SecondSock)) + if (IsValidSocket(SecondSock)) { - evutil_make_socket_nonblocking(SecondSock); - m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().m_EventBase, Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); + if (evutil_make_socket_nonblocking(SecondSock) == 0) + { + m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().m_EventBase, Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); + } + else + { + int err = EVUTIL_SOCKET_ERROR(); + LOGD("evutil_make_socket_nonblocking() failed: %d, %s", err, evutil_socket_error_to_string(err)); + } + } + else + { + int err = EVUTIL_SOCKET_ERROR(); + LOGD("socket(AF_INET, ...) failed: %d, %s", err, evutil_socket_error_to_string(err)); } } m_IsListening = true; -- cgit v1.2.3 From 19121d2962694468aa32cdfe839af7b0ec48acff Mon Sep 17 00:00:00 2001 From: worktycho Date: Mon, 12 Jan 2015 16:28:56 +0000 Subject: Wrap debug variable in ifdef avoidss unused variable error in clang --- src/OSSupport/Network.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index b916fd804..7a4e92610 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -823,7 +823,9 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) if (!IsValidSocket(MainSock)) { // Failed to create IPv6 socket, create an IPv4 one instead: + #ifdef DEBUG int err = EVUTIL_SOCKET_ERROR(); + #endif 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)) -- cgit v1.2.3 From b59206207c924b0d7034ffcc239b657f5dfb4dd0 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Tue, 13 Jan 2015 12:55:10 +0100 Subject: cNetwork: Fixed compilation. --- src/OSSupport/Network.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 7a4e92610..036e48d53 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -819,13 +819,12 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) // 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: - #ifdef DEBUG - int err = EVUTIL_SOCKET_ERROR(); - #endif + 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)) @@ -842,7 +841,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) name.sin_port = ntohs(a_Port); if (bind(MainSock, reinterpret_cast(&name), sizeof(name)) != 0) { - int err = EVUTIL_SOCKET_ERROR(); + err = EVUTIL_SOCKET_ERROR(); LOGWARNING("Cannot bind to IPv4 port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); evutil_closesocket(MainSock); return false; @@ -855,7 +854,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) #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)); - int err = EVUTIL_SOCKET_ERROR(); + 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), @@ -880,14 +879,14 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) } if (evutil_make_socket_nonblocking(MainSock) != 0) { - int err = EVUTIL_SOCKET_ERROR(); + err = EVUTIL_SOCKET_ERROR(); LOGWARNING("Cannot make socket for port %d non-blocking: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); evutil_closesocket(MainSock); return false; } if (listen(MainSock, 0) != 0) { - int err = EVUTIL_SOCKET_ERROR(); + err = EVUTIL_SOCKET_ERROR(); LOGWARNING("Cannot listen on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); evutil_closesocket(MainSock); return false; @@ -907,13 +906,13 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) } else { - int err = EVUTIL_SOCKET_ERROR(); + err = EVUTIL_SOCKET_ERROR(); LOGD("evutil_make_socket_nonblocking() failed: %d, %s", err, evutil_socket_error_to_string(err)); } } else { - int err = EVUTIL_SOCKET_ERROR(); + err = EVUTIL_SOCKET_ERROR(); LOGD("socket(AF_INET, ...) failed: %d, %s", err, evutil_socket_error_to_string(err)); } } -- cgit v1.2.3 From 5cc8d82a23b89776d2e61d69e1d9fd31fbb10039 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Tue, 13 Jan 2015 14:44:03 +0100 Subject: cNetwork: Fixed compilation in out-of-source builds. --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d7a34c14..5b3fd5e7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,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() -- cgit v1.2.3 From 6f29cfe084a77a55b4bfc4288ac2c7bd1d9559e9 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Tue, 13 Jan 2015 17:48:25 +0100 Subject: cNetwork: Cosmetics. --- src/OSSupport/Network.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 036e48d53..e08109ad0 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -463,6 +463,7 @@ void cIPLookup::Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void 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: @@ -470,7 +471,7 @@ void cIPLookup::Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void } else { - // Call the success handler:: + // Call the success handler: Self->m_Callbacks->OnNameResolved(*(reinterpret_cast(a_Addresses)), Self->m_IP); Self->m_Callbacks->OnFinished(); } @@ -489,6 +490,7 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, -1, BEV_OPT_CLOSE_ON_FREE)), m_Server(nullptr) { + // Create the LibEvent handle, but don't assign a socket to it yet (will be assigned within Connect() method): bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); } @@ -502,8 +504,11 @@ cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_L m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, 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); + + // Create the LibEvent handle: bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); } @@ -521,8 +526,6 @@ cTCPLinkImpl::~cTCPLinkImpl() -/** Schedules the actual connection request. -Returns true on success, false on failure. */ bool cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks) { ASSERT(bufferevent_getfd(m_BufferEvent) == -1); // Did you create this object using the right constructor (the one without the Socket param)? @@ -594,7 +597,7 @@ void cTCPLinkImpl::Shutdown(void) void cTCPLinkImpl::Close(void) { - // Disable all events on the socket, but keep it alive (multithreading): + // Disable all events on the socket, but keep it alive: bufferevent_disable(m_BufferEvent, EV_READ | EV_WRITE); if (m_Server == nullptr) { @@ -698,6 +701,7 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void void cTCPLinkImpl::UpdateAddress(const sockaddr * a_Address, int 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) { @@ -747,7 +751,7 @@ 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_LocalIP, m_LocalPort); + UpdateAddress(reinterpret_cast(&sa), salen, m_RemoteIP, m_RemotePort); } @@ -1184,7 +1188,7 @@ void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg) case _EVENT_LOG_ERR: LOGERROR ("LibEvent: %s", a_Msg); break; default: { - printf("LibEvent: %s", a_Msg); + LOGWARNING("LibEvent: Unknown log severity (%d): %s", a_Severity, a_Msg); break; } } -- cgit v1.2.3 From 728a624e48fd3f2cc16baa2bd317a52c92c92f59 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 14 Jan 2015 14:04:52 +0100 Subject: cNetwork: Refactored cTCPLinkImpl::Connect into a factory. --- src/OSSupport/Network.cpp | 89 +++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index e08109ad0..f29a3da17 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -18,6 +18,16 @@ +// fwd: +class cServerHandleImpl; +class cTCPLinkImpl; +typedef SharedPtr cTCPLinkImplPtr; +typedef std::vector cTCPLinkImplPtrs; + + + + + //////////////////////////////////////////////////////////////////////////////// // Class definitions: @@ -63,13 +73,6 @@ typedef std::vector cIPLookupPtrs; -// fwd: -class cServerHandleImpl; - - - - - /** Implements the cTCPLink details so that it can represent the single connection between two endpoints. */ class cTCPLinkImpl: public cTCPLink @@ -78,11 +81,6 @@ class cTCPLinkImpl: friend class cServerHandleImpl; public: - /** Creates a new link to be queued to connect to a specified host:port. - Used for outgoing connections created using cNetwork::Connect(). - Call Connect() first, before using the link. */ - cTCPLinkImpl(const cCallbacksPtr a_LinkCallbacks); - /** 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. */ @@ -93,8 +91,8 @@ public: /** Queues a connection request to the specified host. a_ConnectCallbacks must be valid. - The object must have been constructed by the right constructor (without the Socket param). */ - bool Connect(const AString & a_Host, UInt16 a_Port, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks); + 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); // cTCPLink overrides: virtual bool Send(const void * a_Data, size_t a_Length) override; @@ -131,6 +129,11 @@ protected: UInt16 m_RemotePort; + /** 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. */ + 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); @@ -146,8 +149,6 @@ protected: /** Updates m_RemoteIP and m_RemotePort based on the metadata read from the socket. */ void UpdateRemoteAddress(void); }; -typedef SharedPtr cTCPLinkImplPtr; -typedef std::vector cTCPLinkImplPtrs; @@ -314,6 +315,10 @@ protected: 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); @@ -526,13 +531,15 @@ cTCPLinkImpl::~cTCPLinkImpl() -bool cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks) +cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTCPLink::cCallbacksPtr a_LinkCallbacks, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks) { - ASSERT(bufferevent_getfd(m_BufferEvent) == -1); // Did you create this object using the right constructor (the one without the Socket param)? - ASSERT(m_Server == nullptr); + ASSERT(a_LinkCallbacks != nullptr); ASSERT(a_ConnectCallbacks != nullptr); - m_ConnectCallbacks = a_ConnectCallbacks; + // 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); // If a_Host is an IP address, schedule a connection immediately: sockaddr_storage sa; @@ -549,23 +556,26 @@ bool cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cNetwork::cCon reinterpret_cast(&sa)->sin_port = htons(a_Port); } - if (bufferevent_socket_connect(m_BufferEvent, reinterpret_cast(&sa), salen) == 0) + // Queue the connect request: + if (bufferevent_socket_connect(res->m_BufferEvent, reinterpret_cast(&sa), salen) == 0) { // Success - return true; + return res; } // Failure - return false; + cNetworkSingleton::Get().RemoveLink(res.get()); + return nullptr; } // a_Host is a hostname, connect after a lookup: - if (bufferevent_socket_connect_hostname(m_BufferEvent, cNetworkSingleton::Get().m_DNSBase, AF_UNSPEC, a_Host.c_str(), a_Port) == 0) + if (bufferevent_socket_connect_hostname(res->m_BufferEvent, cNetworkSingleton::Get().m_DNSBase, AF_UNSPEC, a_Host.c_str(), a_Port) == 0) { // Success - return true; + return res; } // Failure - return false; + cNetworkSingleton::Get().RemoveLink(res.get()); + return nullptr; } @@ -1097,21 +1107,8 @@ bool cNetworkSingleton::Connect( ) { // Add a connection request to the queue: - cTCPLinkImplPtr ConnRequest = std::make_shared(a_LinkCallbacks); - { - cCSLock Lock(m_CS); - m_Connections.push_back(ConnRequest); - } // Lock(m_CS) - - // Queue the connection: - if (!ConnRequest->Connect(a_Host, a_Port, a_ConnectCallbacks)) - { - RemoveLink(ConnRequest.get()); - return false; - } - - // Everything successful, return success: - return true; + cTCPLinkImplPtr Conn = cTCPLinkImpl::Connect(a_Host, a_Port, a_LinkCallbacks, a_ConnectCallbacks); + return (Conn != nullptr); } @@ -1241,6 +1238,16 @@ void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup) +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); -- cgit v1.2.3 From 4647e6ad716eccde67059ffde95d00ff06fc891f Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 14 Jan 2015 14:06:14 +0100 Subject: cNetwork: Removed unneeded dependency. --- src/OSSupport/Network.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index f29a3da17..1eb635333 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -78,7 +78,6 @@ class cTCPLinkImpl: public cTCPLink { typedef cTCPLink super; - friend class cServerHandleImpl; public: /** Creates a new link based on the given socket. -- cgit v1.2.3 From 646e636933e8a4a048ac62a82d1bcf3cb9790f56 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Thu, 15 Jan 2015 16:52:06 +0100 Subject: cNetwork: Rewritten server listen into a factory method. --- src/OSSupport/Network.cpp | 190 +++++++++++++++++++++++++--------------------- 1 file changed, 102 insertions(+), 88 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 1eb635333..af63c75d0 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -23,6 +23,8 @@ class cServerHandleImpl; class cTCPLinkImpl; typedef SharedPtr cTCPLinkImplPtr; typedef std::vector cTCPLinkImplPtrs; +typedef SharedPtr cServerHandleImplPtr; +typedef std::vector cServerHandleImplPtrs; @@ -161,19 +163,17 @@ class cServerHandleImpl: friend class cTCPLinkImpl; public: - /** Creates a new instance with the specified callbacks. - Initializes the internals, but doesn't start listening yet. */ - cServerHandleImpl( - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks - ); - /** Closes the server, dropping all the connections. */ ~cServerHandleImpl(); - /** Starts listening on the specified port. - Both IPv4 and IPv6 interfaces are used, if possible. */ - bool Listen(UInt16 a_Port); + /** 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, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); // cServerHandle overrides: virtual void Close(void) override; @@ -201,6 +201,24 @@ protected: /** 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; + + + + /** Creates a new instance with the specified callbacks. + Initializes the internals, but doesn't start listening yet. */ + cServerHandleImpl( + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); + + /** 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); @@ -209,8 +227,6 @@ protected: Called by cTCPLinkImpl when the link is terminated. */ void RemoveLink(const cTCPLinkImpl * a_Link); }; -typedef SharedPtr cServerHandleImplPtr; -typedef std::vector cServerHandleImplPtrs; @@ -230,30 +246,6 @@ public: // The following functions are implementations for the cNetwork class - /** 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. */ - bool Connect( - const AString & a_Host, - const UInt16 a_Port, - cNetwork::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. */ - cServerHandlePtr Listen( - const UInt16 a_Port, - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks - ); - - /** 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. @@ -321,6 +313,15 @@ protected: /** 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); }; @@ -770,12 +771,16 @@ void cTCPLinkImpl::UpdateRemoteAddress(void) //////////////////////////////////////////////////////////////////////////////// // cServerHandleImpl: -cServerHandleImpl::cServerHandleImpl(cNetwork::cListenCallbacksPtr a_ListenCallbacks, cTCPLink::cCallbacksPtr a_LinkCallbacks): +cServerHandleImpl::cServerHandleImpl( + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +): m_ListenCallbacks(a_ListenCallbacks), m_LinkCallbacks(a_LinkCallbacks), m_ConnListener(nullptr), m_SecondaryConnListener(nullptr), - m_IsListening(false) + m_IsListening(false), + m_ErrorCode(0) { } @@ -825,10 +830,26 @@ void cServerHandleImpl::Close(void) -bool cServerHandleImpl::Listen(UInt16 a_Port) +cServerHandleImplPtr cServerHandleImpl::Listen( + UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) { - ASSERT(!m_IsListening); + cServerHandleImplPtr res = cServerHandleImplPtr{new cServerHandleImpl(a_ListenCallbacks, a_LinkCallbacks)}; + if (res->Listen(a_Port)) + { + cNetworkSingleton::Get().AddServer(res); + } + return res; +} + + + + +bool cServerHandleImpl::Listen(UInt16 a_Port) +{ // Set up the main socket: // It should listen on IPv6 with IPv4 fallback, when available; IPv4 when IPv6 is not available. bool NeedsTwoSockets = false; @@ -842,8 +863,8 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) MainSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (!IsValidSocket(MainSock)) { - int err = EVUTIL_SOCKET_ERROR(); - LOGWARNING("%s: Cannot create a socket for neither IPv6 nor IPv4: %d (%s)", __FUNCTION__, err, evutil_socket_error_to_string(err)); + 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; } @@ -854,8 +875,8 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) name.sin_port = ntohs(a_Port); if (bind(MainSock, reinterpret_cast(&name), sizeof(name)) != 0) { - err = EVUTIL_SOCKET_ERROR(); - LOGWARNING("Cannot bind to IPv4 port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + 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; } @@ -884,23 +905,23 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) name.sin6_port = ntohs(a_Port); if (bind(MainSock, reinterpret_cast(&name), sizeof(name)) != 0) { - int err = EVUTIL_SOCKET_ERROR(); - LOGWARNING("Cannot bind to IPv6 port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot bind IPv6 socket to port %d: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); evutil_closesocket(MainSock); return false; } } if (evutil_make_socket_nonblocking(MainSock) != 0) { - err = EVUTIL_SOCKET_ERROR(); - LOGWARNING("Cannot make socket for port %d non-blocking: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot make socket on port %d non-blocking: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); evutil_closesocket(MainSock); return false; } if (listen(MainSock, 0) != 0) { - err = EVUTIL_SOCKET_ERROR(); - LOGWARNING("Cannot listen on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot listen on port %d: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); evutil_closesocket(MainSock); return false; } @@ -985,7 +1006,9 @@ bool cNetwork::Connect( cTCPLink::cCallbacksPtr a_LinkCallbacks ) { - return cNetworkSingleton::Get().Connect(a_Host, a_Port, a_ConnectCallbacks, a_LinkCallbacks); + // Add a connection request to the queue: + cTCPLinkImplPtr Conn = cTCPLinkImpl::Connect(a_Host, a_Port, a_LinkCallbacks, a_ConnectCallbacks); + return (Conn != nullptr); } @@ -998,7 +1021,7 @@ cServerHandlePtr cNetwork::Listen( cTCPLink::cCallbacksPtr a_LinkCallbacks ) { - return cNetworkSingleton::Get().Listen(a_Port, a_ListenCallbacks, a_LinkCallbacks); + return cServerHandleImpl::Listen(a_Port, a_ListenCallbacks, a_LinkCallbacks); } @@ -1098,42 +1121,6 @@ cNetworkSingleton & cNetworkSingleton::Get(void) -bool cNetworkSingleton::Connect( - const AString & a_Host, - const 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); -} - - - - - -cServerHandlePtr cNetworkSingleton::Listen( - const UInt16 a_Port, - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks -) -{ - cServerHandleImplPtr res = std::make_shared(a_ListenCallbacks, a_LinkCallbacks); - if (!res->Listen(a_Port)) - { - return res; - } - cCSLock Lock(m_CS); - m_Servers.push_back(res); - return res; -} - - - - - bool cNetworkSingleton::HostnameToIP( const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks @@ -1263,3 +1250,30 @@ void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link) + +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[] +} + + + + -- cgit v1.2.3 From 7cddb6237418f2d7ec984cd0d4cbdac7140330b0 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Thu, 15 Jan 2015 21:10:14 +0100 Subject: cNetwork: Added an OnError callback for listening servers. The callback receives the error details. --- src/OSSupport/Network.cpp | 5 +++++ src/OSSupport/Network.h | 3 +++ tests/Network/EchoServer.cpp | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index af63c75d0..4dc8d1df1 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -841,6 +841,10 @@ cServerHandleImplPtr cServerHandleImpl::Listen( { cNetworkSingleton::Get().AddServer(res); } + else + { + a_ListenCallbacks->OnError(res->m_ErrorCode, res->m_ErrorMsg); + } return res; } @@ -1062,6 +1066,7 @@ cTCPLink::cTCPLink(cCallbacksPtr a_Callbacks): + //////////////////////////////////////////////////////////////////////////////// // cNetworkSingleton: diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index 11c45b152..cb5badaeb 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -139,6 +139,9 @@ public: /** Called when the TCP server created with Listen() accepts an incoming connection. Provides the newly created Link that can be used for communication. */ 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; diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp index 1e0ac023f..333c31e08 100644 --- a/tests/Network/EchoServer.cpp +++ b/tests/Network/EchoServer.cpp @@ -22,6 +22,11 @@ class cEchoServerCallbacks: 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()); + } }; -- cgit v1.2.3 From 60807adfb577552bc462660920f42fac0c3c60f3 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Thu, 15 Jan 2015 21:11:36 +0100 Subject: cNetwork: Handle WSA initialization failures. --- src/OSSupport/Network.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 4dc8d1df1..c26a8e17d 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -1076,7 +1076,13 @@ cNetworkSingleton::cNetworkSingleton(void) #ifdef _WIN32 WSADATA wsaData; memset(&wsaData, 0, sizeof(wsaData)); - WSAStartup (MAKEWORD(2, 2), &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: -- cgit v1.2.3 From ae29a82ebae4742e81901f900010ea5cda2b9c4b Mon Sep 17 00:00:00 2001 From: Mattes D Date: Fri, 16 Jan 2015 16:13:02 +0100 Subject: cNetwork: Fixed WSAStartup()-not-called error in Listen(). --- src/OSSupport/Network.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index c26a8e17d..decd111ea 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -854,6 +854,9 @@ cServerHandleImplPtr cServerHandleImpl::Listen( 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; -- cgit v1.2.3 From d3076a3e1664c6a6e7c0d4cf24ac4335bb0a3899 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sat, 17 Jan 2015 23:33:59 +0100 Subject: cNetwork: Split cNetworkSingleton to a separate file. --- src/OSSupport/CMakeLists.txt | 2 + src/OSSupport/Network.cpp | 334 +------------------------------------ src/OSSupport/NetworkSingleton.cpp | 281 +++++++++++++++++++++++++++++++ src/OSSupport/NetworkSingleton.h | 138 +++++++++++++++ tests/Network/CMakeLists.txt | 13 +- 5 files changed, 442 insertions(+), 326 deletions(-) create mode 100644 src/OSSupport/NetworkSingleton.cpp create mode 100644 src/OSSupport/NetworkSingleton.h diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index 8454279fe..167afb784 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -13,6 +13,7 @@ SET (SRCS IsThread.cpp ListenThread.cpp Network.cpp + NetworkSingleton.cpp Semaphore.cpp Socket.cpp SocketThreads.cpp @@ -28,6 +29,7 @@ SET (HDRS IsThread.h ListenThread.h Network.h + NetworkSingleton.h Queue.h Semaphore.h Socket.h diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index decd111ea..359206632 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -13,6 +13,7 @@ #include #include "Event.h" #include "CriticalSection.h" +#include "NetworkSingleton.h" @@ -232,102 +233,6 @@ protected: -class cNetworkSingleton -{ - friend class cHostnameLookup; // Needs access to m_DNSBase - friend class cIPLookup; // Needs access to m_DNSBase - friend class cTCPLinkImpl; // Needs access to m_EventBase and m_DNSBase - friend class cServerHandleImpl; // Needs access to m_EventBase - -public: - /** Returns the singleton instance of this class */ - static cNetworkSingleton & Get(void); - - - // The following functions are implementations for the cNetwork class - - /** 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. */ - bool HostnameToIP( - const AString & a_Hostname, - cNetwork::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. */ - bool IPToHostName( - const AString & a_IP, - cNetwork::cResolveNameCallbacksPtr a_Callbacks - ); - -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; - - - /** 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); - - /** 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); - - /** 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); -}; - - - - - //////////////////////////////////////////////////////////////////////////////// // Globals: @@ -357,7 +262,7 @@ cHostnameLookup::cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveN hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; hints.ai_flags = EVUTIL_AI_CANONNAME; - evdns_getaddrinfo(cNetworkSingleton::Get().m_DNSBase, a_Hostname.c_str(), nullptr, &hints, Callback, this); + evdns_getaddrinfo(cNetworkSingleton::Get().GetDNSBase(), a_Hostname.c_str(), nullptr, &hints, Callback, this); } @@ -440,13 +345,13 @@ cIPLookup::cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_ case AF_INET: { sockaddr_in * sa4 = reinterpret_cast(&sa); - evdns_base_resolve_reverse(cNetworkSingleton::Get().m_DNSBase, &(sa4->sin_addr), 0, Callback, this); + 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().m_DNSBase, &(sa6->sin6_addr), 0, Callback, this); + evdns_base_resolve_reverse_ipv6(cNetworkSingleton::Get().GetDNSBase(), &(sa6->sin6_addr), 0, Callback, this); break; } default: @@ -492,7 +397,7 @@ void cIPLookup::Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, -1, BEV_OPT_CLOSE_ON_FREE)), + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE)), m_Server(nullptr) { // Create the LibEvent handle, but don't assign a socket to it yet (will be assigned within Connect() method): @@ -506,7 +411,7 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int a_AddrLen): super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().m_EventBase, a_Socket, BEV_OPT_CLOSE_ON_FREE)), + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE)), m_Server(a_Server) { // Update the endpoint addresses: @@ -568,7 +473,7 @@ cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTC } // a_Host is a hostname, connect after a lookup: - if (bufferevent_socket_connect_hostname(res->m_BufferEvent, cNetworkSingleton::Get().m_DNSBase, AF_UNSPEC, a_Host.c_str(), a_Port) == 0) + if (bufferevent_socket_connect_hostname(res->m_BufferEvent, cNetworkSingleton::Get().GetDNSBase(), AF_UNSPEC, a_Host.c_str(), a_Port) == 0) { // Success return res; @@ -932,7 +837,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) evutil_closesocket(MainSock); return false; } - m_ConnListener = evconnlistener_new(cNetworkSingleton::Get().m_EventBase, Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, MainSock); + m_ConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, MainSock); // If a secondary socket is required (WinXP dual-stack), create it here: if (NeedsTwoSockets) @@ -943,7 +848,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) { if (evutil_make_socket_nonblocking(SecondSock) == 0) { - m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().m_EventBase, Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); + m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); } else { @@ -1070,224 +975,3 @@ cTCPLink::cTCPLink(cCallbacksPtr a_Callbacks): -//////////////////////////////////////////////////////////////////////////////// -// cNetworkSingleton: - -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::Get(void) -{ - static cNetworkSingleton Instance; - return Instance; -} - - - - - -bool cNetworkSingleton::HostnameToIP( - const AString & a_Hostname, - cNetwork::cResolveNameCallbacksPtr a_Callbacks -) -{ - try - { - cCSLock Lock(m_CS); - m_HostnameLookups.push_back(std::make_shared(a_Hostname, a_Callbacks)); - } - catch (...) - { - return false; - } - return true; -} - - - - -bool cNetworkSingleton::IPToHostName( - const AString & a_IP, - cNetwork::cResolveNameCallbacksPtr a_Callbacks -) -{ - try - { - cCSLock Lock(m_CS); - m_IPLookups.push_back(std::make_shared(a_IP, a_Callbacks)); - } - catch (...) - { - return false; - } - return true; -} - - - - -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); -} - - - - - -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::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.cpp b/src/OSSupport/NetworkSingleton.cpp new file mode 100644 index 000000000..552abad64 --- /dev/null +++ b/src/OSSupport/NetworkSingleton.cpp @@ -0,0 +1,281 @@ + +// 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 + + + + + +//////////////////////////////////////////////////////////////////////////////// +// Class definitions: + +/** Holds information about an in-progress Hostname-to-IP lookup. */ +class cHostnameLookup +{ + /** 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); + +public: + cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks); +}; +typedef SharedPtr cHostnameLookupPtr; +typedef std::vector cHostnameLookupPtrs; + + + + + +/** Holds information about an in-progress IP-to-Hostname lookup. */ +class cIPLookup +{ + /** The callbacks to call for resolved names / errors. */ + cNetwork::cResolveNameCallbacksPtr m_Callbacks; + + /** The IP that was queried (needed for the callbacks). */ + AString m_IP; + + static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); + +public: + cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks); +}; +typedef SharedPtr cIPLookupPtr; +typedef std::vector cIPLookupPtrs; + + + + + +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::Get(void) +{ + static cNetworkSingleton Instance; + return Instance; +} + + + + + +bool cNetworkSingleton::HostnameToIP( + const AString & a_Hostname, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + try + { + cCSLock Lock(m_CS); + m_HostnameLookups.push_back(std::make_shared(a_Hostname, a_Callbacks)); + } + catch (...) + { + return false; + } + return true; +} + + + + +bool cNetworkSingleton::IPToHostName( + const AString & a_IP, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + try + { + cCSLock Lock(m_CS); + m_IPLookups.push_back(std::make_shared(a_IP, a_Callbacks)); + } + catch (...) + { + return false; + } + return true; +} + + + + +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); +} + + + + + +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::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..d5a4ec279 --- /dev/null +++ b/src/OSSupport/NetworkSingleton.h @@ -0,0 +1,138 @@ + +// 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. + +#pragma once + +#include "Network.h" +#include "CriticalSection.h" + + + + + +// fwd: +struct event_base; +struct evdns_base; +class cServerHandleImpl; +class cTCPLinkImpl; +class cHostnameLookup; +class cIPLookup; +typedef SharedPtr cTCPLinkImplPtr; +typedef std::vector cTCPLinkImplPtrs; +typedef SharedPtr cServerHandleImplPtr; +typedef std::vector cServerHandleImplPtrs; +typedef SharedPtr cHostnameLookupPtr; +typedef std::vector cHostnameLookupPtrs; +typedef SharedPtr cIPLookupPtr; +typedef std::vector cIPLookupPtrs; + + + + + +class cNetworkSingleton +{ +public: + /** Returns the singleton instance of this class */ + static cNetworkSingleton & Get(void); + + + // The following functions are implementations for the cNetwork class + + /** 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. + TODO: Move this out into a separate file with cHostnameLookup. */ + bool HostnameToIP( + const AString & a_Hostname, + cNetwork::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. + TODO: Move this out into a separate file with cIPLookup. */ + bool IPToHostName( + const AString & a_IP, + cNetwork::cResolveNameCallbacksPtr a_Callbacks + ); + + /** 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; } + + /** 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); + + /** 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; + + + /** 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/tests/Network/CMakeLists.txt b/tests/Network/CMakeLists.txt index 9d0dcdc24..6bf291544 100644 --- a/tests/Network/CMakeLists.txt +++ b/tests/Network/CMakeLists.txt @@ -6,10 +6,13 @@ 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: add_library(Network - ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/NetworkSingleton.cpp ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp ) @@ -18,12 +21,20 @@ 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) -- cgit v1.2.3 From 1e5aedaceaee55438c7e1681f7e221d6c8689800 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sat, 17 Jan 2015 23:47:28 +0100 Subject: cNetwork: Fixed WinXP dualstack listening. --- src/OSSupport/Network.cpp | 63 +++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp index 359206632..02bdfa39c 100644 --- a/src/OSSupport/Network.cpp +++ b/src/OSSupport/Network.cpp @@ -838,31 +838,52 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) 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: - if (NeedsTwoSockets) + LOGD("Creating a second socket for IPv4"); + evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(SecondSock)) { - LOGD("Creating a second socket for IPv4"); - evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (IsValidSocket(SecondSock)) - { - if (evutil_make_socket_nonblocking(SecondSock) == 0) - { - m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); - } - else - { - err = EVUTIL_SOCKET_ERROR(); - LOGD("evutil_make_socket_nonblocking() failed: %d, %s", err, evutil_socket_error_to_string(err)); - } - } - else - { - err = EVUTIL_SOCKET_ERROR(); - LOGD("socket(AF_INET, ...) failed: %d, %s", err, evutil_socket_error_to_string(err)); - } + 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 } - m_IsListening = true; + + // 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); + } + + // 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; + } + + 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 false; + } + + m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); return true; } -- cgit v1.2.3 From c0cb787c101725a649d26de68fca2632c82830ba Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 18 Jan 2015 11:57:16 +0100 Subject: cNetwork: Split the main cpp file into several files. --- src/OSSupport/CMakeLists.txt | 9 +- src/OSSupport/HostnameLookup.cpp | 109 ++++ src/OSSupport/HostnameLookup.h | 41 ++ src/OSSupport/IPLookup.cpp | 91 ++++ src/OSSupport/IPLookup.h | 40 ++ src/OSSupport/Network.cpp | 998 ------------------------------------- src/OSSupport/Network.h | 19 +- src/OSSupport/NetworkSingleton.cpp | 51 +- src/OSSupport/NetworkSingleton.h | 6 + src/OSSupport/ServerHandleImpl.cpp | 302 +++++++++++ src/OSSupport/ServerHandleImpl.h | 109 ++++ src/OSSupport/TCPLinkImpl.cpp | 314 ++++++++++++ src/OSSupport/TCPLinkImpl.h | 109 ++++ tests/Network/CMakeLists.txt | 24 +- 14 files changed, 1170 insertions(+), 1052 deletions(-) create mode 100644 src/OSSupport/HostnameLookup.cpp create mode 100644 src/OSSupport/HostnameLookup.h create mode 100644 src/OSSupport/IPLookup.cpp create mode 100644 src/OSSupport/IPLookup.h delete mode 100644 src/OSSupport/Network.cpp create mode 100644 src/OSSupport/ServerHandleImpl.cpp create mode 100644 src/OSSupport/ServerHandleImpl.h create mode 100644 src/OSSupport/TCPLinkImpl.cpp create mode 100644 src/OSSupport/TCPLinkImpl.h diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index 167afb784..9424b63da 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -10,14 +10,17 @@ SET (SRCS Event.cpp File.cpp GZipFile.cpp + HostnameLookup.cpp + IPLookup.cpp IsThread.cpp ListenThread.cpp - Network.cpp NetworkSingleton.cpp Semaphore.cpp + ServerHandleImpl.cpp Socket.cpp SocketThreads.cpp StackTrace.cpp + TCPLinkImpl.cpp ) SET (HDRS @@ -26,15 +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/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp new file mode 100644 index 000000000..9e35f7163 --- /dev/null +++ b/src/OSSupport/HostnameLookup.cpp @@ -0,0 +1,109 @@ + +// 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(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks), + m_Hostname(a_Hostname) +{ + 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); + 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(1); + } + 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 +) +{ + return cNetworkSingleton::Get().HostnameToIP(a_Hostname, a_Callbacks); +} + + + + diff --git a/src/OSSupport/HostnameLookup.h b/src/OSSupport/HostnameLookup.h new file mode 100644 index 000000000..98f48b933 --- /dev/null +++ b/src/OSSupport/HostnameLookup.h @@ -0,0 +1,41 @@ + +// 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 +{ + /** 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); + +public: + cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks); +}; +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..bbcfbfe40 --- /dev/null +++ b/src/OSSupport/IPLookup.cpp @@ -0,0 +1,91 @@ + +// 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(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks), + m_IP(a_IP) +{ + sockaddr_storage sa; + int salen = static_cast(sizeof(sa)); + memset(&sa, 0, sizeof(sa)); + evutil_parse_sockaddr_port(a_IP.c_str(), reinterpret_cast(&sa), &salen); + 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"); + break; + } + } // switch (address family) +} + + + + + +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); + } + 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 +) +{ + return cNetworkSingleton::Get().IPToHostName(a_IP, a_Callbacks); +} + + + + diff --git a/src/OSSupport/IPLookup.h b/src/OSSupport/IPLookup.h new file mode 100644 index 000000000..f39b955aa --- /dev/null +++ b/src/OSSupport/IPLookup.h @@ -0,0 +1,40 @@ + +// 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 +{ + /** The callbacks to call for resolved names / errors. */ + cNetwork::cResolveNameCallbacksPtr m_Callbacks; + + /** The IP that was queried (needed for the callbacks). */ + AString m_IP; + + static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); + +public: + cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks); +}; +typedef SharedPtr cIPLookupPtr; +typedef std::vector cIPLookupPtrs; + + + + + diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp deleted file mode 100644 index 02bdfa39c..000000000 --- a/src/OSSupport/Network.cpp +++ /dev/null @@ -1,998 +0,0 @@ - -// Network.cpp - -// Implements the classes used for the Network API - -#include "Globals.h" -#include "Network.h" -#include -#include -#include -#include -#include -#include -#include "Event.h" -#include "CriticalSection.h" -#include "NetworkSingleton.h" - - - - - -// fwd: -class cServerHandleImpl; -class cTCPLinkImpl; -typedef SharedPtr cTCPLinkImplPtr; -typedef std::vector cTCPLinkImplPtrs; -typedef SharedPtr cServerHandleImplPtr; -typedef std::vector cServerHandleImplPtrs; - - - - - -//////////////////////////////////////////////////////////////////////////////// -// Class definitions: - -/** Holds information about an in-progress Hostname-to-IP lookup. */ -class cHostnameLookup -{ - /** 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); - -public: - cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks); -}; -typedef SharedPtr cHostnameLookupPtr; -typedef std::vector cHostnameLookupPtrs; - - - - - -/** Holds information about an in-progress IP-to-Hostname lookup. */ -class cIPLookup -{ - /** The callbacks to call for resolved names / errors. */ - cNetwork::cResolveNameCallbacksPtr m_Callbacks; - - /** The IP that was queried (needed for the callbacks). */ - AString m_IP; - - static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); - -public: - cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks); -}; -typedef SharedPtr cIPLookupPtr; -typedef std::vector cIPLookupPtrs; - - - - - -/** Implements the cTCPLink details so that it can represent the single connection between two endpoints. */ -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. */ - cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int 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); - - // 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, NULL for outgoing connections. */ - cServerHandleImpl * 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; - - - /** 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. */ - 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, int 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); -}; - - - - - -/** Implements the cServerHandle details so that it can represent a real server socket, with a list of clients. */ -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, - cTCPLink::cCallbacksPtr a_LinkCallbacks - ); - - // 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 callbacks used to create new cTCPLink instances for incoming connections. */ - cTCPLink::cCallbacksPtr m_LinkCallbacks; - - /** 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; - - - - /** Creates a new instance with the specified callbacks. - Initializes the internals, but doesn't start listening yet. */ - cServerHandleImpl( - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks - ); - - /** 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); -}; - - - - - -//////////////////////////////////////////////////////////////////////////////// -// Globals: - -bool IsValidSocket(evutil_socket_t a_Socket) -{ - #ifdef _WIN32 - return (a_Socket != INVALID_SOCKET); - #else // _WIN32 - return (a_Socket >= 0); - #endif // else _WIN32 -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cHostnameLookup: - -cHostnameLookup::cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks): - m_Callbacks(a_Callbacks), - m_Hostname(a_Hostname) -{ - 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); - 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(1); - } - else - { - Self->m_Callbacks->OnFinished(); - } - evutil_freeaddrinfo(OrigAddr); - cNetworkSingleton::Get().RemoveHostnameLookup(Self); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cIPLookup: - -cIPLookup::cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks): - m_Callbacks(a_Callbacks), - m_IP(a_IP) -{ - sockaddr_storage sa; - int salen = static_cast(sizeof(sa)); - evutil_parse_sockaddr_port(a_IP.c_str(), reinterpret_cast(&sa), &salen); - 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"); - break; - } - } // switch (address family) -} - - - - - -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); - } - else - { - // Call the success handler: - Self->m_Callbacks->OnNameResolved(*(reinterpret_cast(a_Addresses)), Self->m_IP); - Self->m_Callbacks->OnFinished(); - } - cNetworkSingleton::Get().RemoveIPLookup(Self); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cTCPLinkImpl: - -cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): - super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE)), - m_Server(nullptr) -{ - // Create the LibEvent handle, but don't assign a socket to it yet (will be assigned within Connect() method): - bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); - bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); -} - - - - - -cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int 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); - - // Create the LibEvent handle: - bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); - bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); -} - - - - - -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); - - // 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; -} - - - - - -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); - } -} - - - - - - -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(*Self, data, length); - } -} - - - - - -void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) -{ - ASSERT(a_Self != nullptr); - cTCPLinkImpl * Self = static_cast(a_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: - if (Self->m_ConnectCallbacks != nullptr) - { - Self->m_ConnectCallbacks->OnError(EVUTIL_SOCKET_ERROR()); - } - else - { - Self->m_Callbacks->OnError(*Self, EVUTIL_SOCKET_ERROR()); - if (Self->m_Server == nullptr) - { - cNetworkSingleton::Get().RemoveLink(Self); - } - else - { - Self->m_Server->RemoveLink(Self); - } - } - return; - } - - // Pending connection succeeded, call the connection callback: - if (a_What & BEV_EVENT_CONNECTED) - { - if (Self->m_ConnectCallbacks != nullptr) - { - Self->m_ConnectCallbacks->OnSuccess(*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(*Self); - if (Self->m_Server != nullptr) - { - Self->m_Server->RemoveLink(Self); - } - else - { - cNetworkSingleton::Get().RemoveLink(Self); - } - 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, int 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); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cServerHandleImpl: - -cServerHandleImpl::cServerHandleImpl( - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks -): - m_ListenCallbacks(a_ListenCallbacks), - m_LinkCallbacks(a_LinkCallbacks), - 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(); - } -} - - - - - -cServerHandleImplPtr cServerHandleImpl::Listen( - UInt16 a_Port, - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks -) -{ - cServerHandleImplPtr res = cServerHandleImplPtr{new cServerHandleImpl(a_ListenCallbacks, a_LinkCallbacks)}; - if (res->Listen(a_Port)) - { - cNetworkSingleton::Get().AddServer(res); - } - else - { - a_ListenCallbacks->OnError(res->m_ErrorCode, res->m_ErrorMsg); - } - 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: %s", a_Port, 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: %s", a_Port, 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: %s", a_Port, 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); - } - - // 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; - } - - 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 false; - } - - 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); - - // Create a new cTCPLink for the incoming connection: - cTCPLinkImplPtr Link = std::make_shared(a_Socket, Self->m_LinkCallbacks, Self, a_Addr, a_Len); - { - cCSLock Lock(Self->m_CS); - Self->m_Connections.push_back(Link); - } // Lock(m_CS) - - // 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: - -bool cNetwork::Connect( - const AString & a_Host, - const 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); -} - - - - - -cServerHandlePtr cNetwork::Listen( - const UInt16 a_Port, - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks -) -{ - return cServerHandleImpl::Listen(a_Port, a_ListenCallbacks, a_LinkCallbacks); -} - - - - - -bool cNetwork::HostnameToIP( - const AString & a_Hostname, - cNetwork::cResolveNameCallbacksPtr a_Callbacks -) -{ - return cNetworkSingleton::Get().HostnameToIP(a_Hostname, a_Callbacks); -} - - - - - -bool cNetwork::IPToHostName( - const AString & a_IP, - cNetwork::cResolveNameCallbacksPtr a_Callbacks -) -{ - return cNetworkSingleton::Get().IPToHostName(a_IP, a_Callbacks); -} - - - - -//////////////////////////////////////////////////////////////////////////////// -// cTCPLink: - -cTCPLink::cTCPLink(cCallbacksPtr a_Callbacks): - m_Callbacks(a_Callbacks) -{ -} - - - - - diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index cb5badaeb..0452c3b6a 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -80,7 +80,10 @@ protected: /** Creates a new link, with the specified callbacks. */ - cTCPLink(cCallbacksPtr a_Callbacks); + cTCPLink(cCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks) + { + } }; @@ -154,7 +157,7 @@ public: virtual ~cResolveNameCallbacks() {} /** Called when the hostname is successfully resolved into an IP address. - May be called multiple times if an address resolves to multiple addresses. + 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; @@ -173,7 +176,8 @@ public: 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. */ + 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, const UInt16 a_Port, @@ -185,7 +189,8 @@ public: /** 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. */ + Returns a cServerHandle that can be used to query the operation status and close the server. + Implemented in ServerHandleImpl.cpp. */ static cServerHandlePtr Listen( const UInt16 a_Port, cListenCallbacksPtr a_ListenCallbacks, @@ -196,7 +201,8 @@ public: /** 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. */ + 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 @@ -206,7 +212,8 @@ public: /** 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. */ + 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 index 552abad64..c9d9b1d81 100644 --- a/src/OSSupport/NetworkSingleton.cpp +++ b/src/OSSupport/NetworkSingleton.cpp @@ -11,51 +11,8 @@ #include #include #include - - - - - -//////////////////////////////////////////////////////////////////////////////// -// Class definitions: - -/** Holds information about an in-progress Hostname-to-IP lookup. */ -class cHostnameLookup -{ - /** 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); - -public: - cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks); -}; -typedef SharedPtr cHostnameLookupPtr; -typedef std::vector cHostnameLookupPtrs; - - - - - -/** Holds information about an in-progress IP-to-Hostname lookup. */ -class cIPLookup -{ - /** The callbacks to call for resolved names / errors. */ - cNetwork::cResolveNameCallbacksPtr m_Callbacks; - - /** The IP that was queried (needed for the callbacks). */ - AString m_IP; - - static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); - -public: - cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks); -}; -typedef SharedPtr cIPLookupPtr; -typedef std::vector cIPLookupPtrs; +#include "IPLookup.h" +#include "HostnameLookup.h" @@ -130,6 +87,8 @@ bool cNetworkSingleton::HostnameToIP( { try { + // TODO: This has a race condition with possible memory leak: + // If a lookup finishes immediately, the constructor calls the removal before this addition cCSLock Lock(m_CS); m_HostnameLookups.push_back(std::make_shared(a_Hostname, a_Callbacks)); } @@ -150,6 +109,8 @@ bool cNetworkSingleton::IPToHostName( { try { + // TODO: This has a race condition with possible memory leak: + // If a lookup finishes immediately, the constructor calls the removal before this addition cCSLock Lock(m_CS); m_IPLookups.push_back(std::make_shared(a_IP, a_Callbacks)); } diff --git a/src/OSSupport/NetworkSingleton.h b/src/OSSupport/NetworkSingleton.h index d5a4ec279..064e075fe 100644 --- a/src/OSSupport/NetworkSingleton.h +++ b/src/OSSupport/NetworkSingleton.h @@ -4,6 +4,12 @@ // 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" diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp new file mode 100644 index 000000000..c399c2279 --- /dev/null +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -0,0 +1,302 @@ + +// 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, + cTCPLink::cCallbacksPtr a_LinkCallbacks +): + m_ListenCallbacks(a_ListenCallbacks), + m_LinkCallbacks(a_LinkCallbacks), + 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(); + } +} + + + + + +cServerHandleImplPtr cServerHandleImpl::Listen( + UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + cServerHandleImplPtr res = cServerHandleImplPtr{new cServerHandleImpl(a_ListenCallbacks, a_LinkCallbacks)}; + if (res->Listen(a_Port)) + { + cNetworkSingleton::Get().AddServer(res); + } + else + { + a_ListenCallbacks->OnError(res->m_ErrorCode, res->m_ErrorMsg); + } + 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: %s", a_Port, 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: %s", a_Port, 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: %s", a_Port, 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); + } + + // 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; + } + + 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 false; + } + + 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); + + // Create a new cTCPLink for the incoming connection: + cTCPLinkImplPtr Link = std::make_shared(a_Socket, Self->m_LinkCallbacks, Self, a_Addr, a_Len); + { + cCSLock Lock(Self->m_CS); + Self->m_Connections.push_back(Link); + } // Lock(m_CS) + + // 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( + const UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + return cServerHandleImpl::Listen(a_Port, a_ListenCallbacks, a_LinkCallbacks); +} + + + + + diff --git a/src/OSSupport/ServerHandleImpl.h b/src/OSSupport/ServerHandleImpl.h new file mode 100644 index 000000000..b325a0f37 --- /dev/null +++ b/src/OSSupport/ServerHandleImpl.h @@ -0,0 +1,109 @@ + +// 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, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); + + // 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 callbacks used to create new cTCPLink instances for incoming connections. */ + cTCPLink::cCallbacksPtr m_LinkCallbacks; + + /** 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; + + + + /** Creates a new instance with the specified callbacks. + Initializes the internals, but doesn't start listening yet. */ + cServerHandleImpl( + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); + + /** 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..bcacc0569 --- /dev/null +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -0,0 +1,314 @@ + +// 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)), + m_Server(nullptr) +{ + // Create the LibEvent handle, but don't assign a socket to it yet (will be assigned within Connect() method): + bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); + bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); +} + + + + + +cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int 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); + + // Create the LibEvent handle: + bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); + bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); +} + + + + + +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); + + // 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; +} + + + + + +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); + } +} + + + + + + +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(*Self, data, length); + } +} + + + + + +void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) +{ + ASSERT(a_Self != nullptr); + cTCPLinkImpl * Self = static_cast(a_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: + if (Self->m_ConnectCallbacks != nullptr) + { + Self->m_ConnectCallbacks->OnError(EVUTIL_SOCKET_ERROR()); + } + else + { + Self->m_Callbacks->OnError(*Self, EVUTIL_SOCKET_ERROR()); + if (Self->m_Server == nullptr) + { + cNetworkSingleton::Get().RemoveLink(Self); + } + else + { + Self->m_Server->RemoveLink(Self); + } + } + return; + } + + // Pending connection succeeded, call the connection callback: + if (a_What & BEV_EVENT_CONNECTED) + { + if (Self->m_ConnectCallbacks != nullptr) + { + Self->m_ConnectCallbacks->OnSuccess(*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(*Self); + if (Self->m_Server != nullptr) + { + Self->m_Server->RemoveLink(Self); + } + else + { + cNetworkSingleton::Get().RemoveLink(Self); + } + 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, int 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, + const 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..3c60b1ad8 --- /dev/null +++ b/src/OSSupport/TCPLinkImpl.h @@ -0,0 +1,109 @@ + +// 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; +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. */ + cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int 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); + + // 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, NULL for outgoing connections. */ + cServerHandleImpl * 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; + + + /** 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. */ + 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, int 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/Network/CMakeLists.txt b/tests/Network/CMakeLists.txt index 6bf291544..c0af50e2c 100644 --- a/tests/Network/CMakeLists.txt +++ b/tests/Network/CMakeLists.txt @@ -8,14 +8,34 @@ include_directories(${CMAKE_SOURCE_DIR}/lib/libevent/include) add_definitions(-DTEST_GLOBALS=1) # Create a single Network library that contains all the networking code: -add_library(Network +set (Network_SRCS ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp - ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.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) -- cgit v1.2.3 From d4682463a1d503c349ac95e275b11d67d402268c Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 18 Jan 2015 12:35:02 +0100 Subject: cNetwork: Fixed race conditions with lookups; proper shutdown. --- src/OSSupport/HostnameLookup.cpp | 25 ++++++++++--- src/OSSupport/HostnameLookup.h | 12 ++++-- src/OSSupport/IPLookup.cpp | 32 +++++++++++++--- src/OSSupport/IPLookup.h | 15 ++++++-- src/OSSupport/NetworkSingleton.cpp | 77 +++++++++++++++++++------------------- src/OSSupport/NetworkSingleton.h | 44 +++++++++------------- tests/Network/Google.cpp | 1 + 7 files changed, 124 insertions(+), 82 deletions(-) diff --git a/src/OSSupport/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp index 9e35f7163..860e0d88f 100644 --- a/src/OSSupport/HostnameLookup.cpp +++ b/src/OSSupport/HostnameLookup.cpp @@ -15,10 +15,22 @@ //////////////////////////////////////////////////////////////////////////////// // cHostnameLookup: -cHostnameLookup::cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks): - m_Callbacks(a_Callbacks), - m_Hostname(a_Hostname) +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; @@ -79,7 +91,7 @@ void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a // If only unsupported families were reported, call the Error handler: if (!HasResolved) { - Self->m_Callbacks->OnError(1); + Self->m_Callbacks->OnError(DNS_ERR_NODATA); } else { @@ -101,7 +113,10 @@ bool cNetwork::HostnameToIP( cNetwork::cResolveNameCallbacksPtr a_Callbacks ) { - return cNetworkSingleton::Get().HostnameToIP(a_Hostname, 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 index 98f48b933..d69f24707 100644 --- a/src/OSSupport/HostnameLookup.h +++ b/src/OSSupport/HostnameLookup.h @@ -21,6 +21,15 @@ /** 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; @@ -28,9 +37,6 @@ class cHostnameLookup AString m_Hostname; static void Callback(int a_ErrCode, struct evutil_addrinfo * a_Addr, void * a_Self); - -public: - cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks); }; typedef SharedPtr cHostnameLookupPtr; typedef std::vector cHostnameLookupPtrs; diff --git a/src/OSSupport/IPLookup.cpp b/src/OSSupport/IPLookup.cpp index bbcfbfe40..a52b6480b 100644 --- a/src/OSSupport/IPLookup.cpp +++ b/src/OSSupport/IPLookup.cpp @@ -15,14 +15,31 @@ //////////////////////////////////////////////////////////////////////////////// // cIPLookup: -cIPLookup::cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks): - m_Callbacks(a_Callbacks), - m_IP(a_IP) +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)); - evutil_parse_sockaddr_port(a_IP.c_str(), reinterpret_cast(&sa), &salen); + 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: @@ -41,9 +58,10 @@ cIPLookup::cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_ { LOGWARNING("%s: Unknown address family: %d", __FUNCTION__, sa.ss_family); ASSERT(!"Unknown address family"); - break; + return false; } } // switch (address family) + return true; } @@ -83,7 +101,9 @@ bool cNetwork::IPToHostName( cNetwork::cResolveNameCallbacksPtr a_Callbacks ) { - return cNetworkSingleton::Get().IPToHostName(a_IP, 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 index f39b955aa..af878cbf1 100644 --- a/src/OSSupport/IPLookup.h +++ b/src/OSSupport/IPLookup.h @@ -20,16 +20,25 @@ /** 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; - static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); -public: - cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks); + /** 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/NetworkSingleton.cpp b/src/OSSupport/NetworkSingleton.cpp index c9d9b1d81..8c38fb4eb 100644 --- a/src/OSSupport/NetworkSingleton.cpp +++ b/src/OSSupport/NetworkSingleton.cpp @@ -70,60 +70,40 @@ cNetworkSingleton::cNetworkSingleton(void) -cNetworkSingleton & cNetworkSingleton::Get(void) +cNetworkSingleton::~cNetworkSingleton() { - static cNetworkSingleton Instance; - return Instance; -} - + // Wait for the LibEvent event loop to terminate: + event_base_loopbreak(m_EventBase); + m_EventLoopTerminated.Wait(); - - - -bool cNetworkSingleton::HostnameToIP( - const AString & a_Hostname, - cNetwork::cResolveNameCallbacksPtr a_Callbacks -) -{ - try + // Remove all objects: { - // TODO: This has a race condition with possible memory leak: - // If a lookup finishes immediately, the constructor calls the removal before this addition cCSLock Lock(m_CS); - m_HostnameLookups.push_back(std::make_shared(a_Hostname, a_Callbacks)); + m_Connections.clear(); + m_Servers.clear(); + m_HostnameLookups.clear(); + m_IPLookups.clear(); } - catch (...) - { - return false; - } - return true; + + // Free the underlying LibEvent objects: + evdns_base_free(m_DNSBase, true); + event_base_free(m_EventBase); } -bool cNetworkSingleton::IPToHostName( - const AString & a_IP, - cNetwork::cResolveNameCallbacksPtr a_Callbacks -) + +cNetworkSingleton & cNetworkSingleton::Get(void) { - try - { - // TODO: This has a race condition with possible memory leak: - // If a lookup finishes immediately, the constructor calls the removal before this addition - cCSLock Lock(m_CS); - m_IPLookups.push_back(std::make_shared(a_IP, a_Callbacks)); - } - catch (...) - { - return false; - } - return true; + static cNetworkSingleton Instance; + return Instance; } + void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg) { switch (a_Severity) @@ -147,6 +127,17 @@ void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg) 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); } @@ -170,6 +161,16 @@ void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameL +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); diff --git a/src/OSSupport/NetworkSingleton.h b/src/OSSupport/NetworkSingleton.h index 064e075fe..1d26fc8f4 100644 --- a/src/OSSupport/NetworkSingleton.h +++ b/src/OSSupport/NetworkSingleton.h @@ -14,6 +14,7 @@ #include "Network.h" #include "CriticalSection.h" +#include "Event.h" @@ -22,16 +23,16 @@ // fwd: struct event_base; struct evdns_base; -class cServerHandleImpl; class cTCPLinkImpl; -class cHostnameLookup; -class cIPLookup; 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; @@ -42,43 +43,29 @@ typedef std::vector cIPLookupPtrs; class cNetworkSingleton { public: + ~cNetworkSingleton(); + /** Returns the singleton instance of this class */ static cNetworkSingleton & Get(void); - - // The following functions are implementations for the cNetwork class - - /** 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. - TODO: Move this out into a separate file with cHostnameLookup. */ - bool HostnameToIP( - const AString & a_Hostname, - cNetwork::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. - TODO: Move this out into a separate file with cIPLookup. */ - bool IPToHostName( - const AString & a_IP, - cNetwork::cResolveNameCallbacksPtr a_Callbacks - ); - /** 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); @@ -123,6 +110,9 @@ protected: /** 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); diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp index de5f95e1f..8ee04f8ee 100644 --- a/tests/Network/Google.cpp +++ b/tests/Network/Google.cpp @@ -95,6 +95,7 @@ int main() evtFinish.Wait(); LOGD("Network test finished"); + return 0; } -- cgit v1.2.3 From 00253403b3850911833638a960f3e7d0ea46e1ce Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 18 Jan 2015 15:40:39 +0100 Subject: cTCPLinkImpl: Fixed type conversion warning. --- src/OSSupport/ServerHandleImpl.cpp | 2 +- src/OSSupport/TCPLinkImpl.cpp | 4 ++-- src/OSSupport/TCPLinkImpl.h | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp index c399c2279..866aa01be 100644 --- a/src/OSSupport/ServerHandleImpl.cpp +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -253,7 +253,7 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_ ASSERT(Self != nullptr); // Create a new cTCPLink for the incoming connection: - cTCPLinkImplPtr Link = std::make_shared(a_Socket, Self->m_LinkCallbacks, Self, a_Addr, a_Len); + cTCPLinkImplPtr Link = std::make_shared(a_Socket, Self->m_LinkCallbacks, Self, a_Addr, static_cast(a_Len)); { cCSLock Lock(Self->m_CS); Self->m_Connections.push_back(Link); diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp index bcacc0569..3c0775367 100644 --- a/src/OSSupport/TCPLinkImpl.cpp +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -29,7 +29,7 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): -cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int a_AddrLen): +cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * 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) @@ -234,7 +234,7 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void -void cTCPLinkImpl::UpdateAddress(const sockaddr * a_Address, int a_AddrLen, AString & a_IP, UInt16 & a_Port) +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]; diff --git a/src/OSSupport/TCPLinkImpl.h b/src/OSSupport/TCPLinkImpl.h index 3c60b1ad8..edd295fe2 100644 --- a/src/OSSupport/TCPLinkImpl.h +++ b/src/OSSupport/TCPLinkImpl.h @@ -38,7 +38,7 @@ 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. */ - cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int a_AddrLen); + cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, socklen_t a_AddrLen); /** Destroys the LibEvent handle representing the link. */ ~cTCPLinkImpl(); @@ -95,7 +95,7 @@ protected: 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, int a_AddrLen, AString & a_IP, UInt16 & a_Port); + 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); -- cgit v1.2.3 From 64855ed340e76779b99f37fbc866a7f5952e11db Mon Sep 17 00:00:00 2001 From: Mattes D Date: Tue, 20 Jan 2015 11:27:05 +0100 Subject: cNetwork: Added error message to error callbacks. --- src/OSSupport/HostnameLookup.cpp | 4 ++-- src/OSSupport/IPLookup.cpp | 2 +- src/OSSupport/Network.h | 6 +++--- src/OSSupport/ServerHandleImpl.cpp | 11 ++++++----- src/OSSupport/TCPLinkImpl.cpp | 10 ++++++++-- tests/Network/EchoServer.cpp | 4 ++-- tests/Network/Google.cpp | 8 ++++---- tests/Network/NameLookup.cpp | 4 ++-- 8 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/OSSupport/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp index 860e0d88f..3a2997ffd 100644 --- a/src/OSSupport/HostnameLookup.cpp +++ b/src/OSSupport/HostnameLookup.cpp @@ -53,7 +53,7 @@ void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a // If an error has occurred, notify the error callback: if (a_ErrCode != 0) { - Self->m_Callbacks->OnError(a_ErrCode); + Self->m_Callbacks->OnError(a_ErrCode, evutil_socket_error_to_string(a_ErrCode)); cNetworkSingleton::Get().RemoveHostnameLookup(Self); return; } @@ -91,7 +91,7 @@ void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a // If only unsupported families were reported, call the Error handler: if (!HasResolved) { - Self->m_Callbacks->OnError(DNS_ERR_NODATA); + Self->m_Callbacks->OnError(DNS_ERR_NODATA, "The name does not resolve to any known address."); } else { diff --git a/src/OSSupport/IPLookup.cpp b/src/OSSupport/IPLookup.cpp index a52b6480b..8cdc5132d 100644 --- a/src/OSSupport/IPLookup.cpp +++ b/src/OSSupport/IPLookup.cpp @@ -78,7 +78,7 @@ void cIPLookup::Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void if ((a_Result != 0) || (a_Addresses == nullptr)) { // An error has occurred, notify the error callback: - Self->m_Callbacks->OnError(a_Result); + Self->m_Callbacks->OnError(a_Result, evutil_socket_error_to_string(a_Result)); } else { diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index 0452c3b6a..3ed9885ec 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -34,7 +34,7 @@ public: virtual void OnRemoteClosed(cTCPLink & a_Link) = 0; /** Called when an error is detected on the connection. */ - virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) = 0; + virtual void OnError(cTCPLink & a_Link, int a_ErrorCode, const AString & a_ErrorMsg) = 0; }; typedef SharedPtr cCallbacksPtr; @@ -127,7 +127,7 @@ public: virtual void OnSuccess(cTCPLink & a_Link) = 0; /** Called when the Connect call fails. */ - virtual void OnError(int a_ErrorCode) = 0; + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; }; typedef SharedPtr cConnectCallbacksPtr; @@ -163,7 +163,7 @@ public: /** 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) = 0; + 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. */ diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp index 866aa01be..d81d4ba46 100644 --- a/src/OSSupport/ServerHandleImpl.cpp +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -173,7 +173,7 @@ bool cServerHandleImpl::Listen(UInt16 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: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); + 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; } @@ -181,14 +181,14 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) if (evutil_make_socket_nonblocking(MainSock) != 0) { m_ErrorCode = EVUTIL_SOCKET_ERROR(); - Printf(m_ErrorMsg, "Cannot make socket on port %d non-blocking: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); + 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: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); + 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; } @@ -215,6 +215,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) 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: @@ -227,7 +228,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) 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; + return true; // Report as success, the primary socket is working } if (listen(SecondSock, 0) != 0) @@ -235,7 +236,7 @@ bool cServerHandleImpl::Listen(UInt16 a_Port) 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 false; + 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); diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp index 3c0775367..f87f68280 100644 --- a/src/OSSupport/TCPLinkImpl.cpp +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -177,13 +177,19 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void 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) { - Self->m_ConnectCallbacks->OnError(EVUTIL_SOCKET_ERROR()); + 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(*Self, EVUTIL_SOCKET_ERROR()); + Self->m_Callbacks->OnError(*Self, err, evutil_socket_error_to_string(err)); if (Self->m_Server == nullptr) { cNetworkSingleton::Get().RemoveLink(Self); diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp index 333c31e08..86b517245 100644 --- a/tests/Network/EchoServer.cpp +++ b/tests/Network/EchoServer.cpp @@ -60,9 +60,9 @@ class cEchoLinkCallbacks: LOGD("%p (%s:%d): Remote has closed the connection.", &a_Link, a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort()); } - virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) override + virtual void OnError(cTCPLink & a_Link, int a_ErrorCode, const AString & a_ErrorMsg) override { - LOGD("%p (%s:%d): Error %d in the cEchoLinkCallbacks.", &a_Link, a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort(), a_ErrorCode); + LOGD("%p (%s:%d): Error %d in the cEchoLinkCallbacks: %s", &a_Link, a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort(), a_ErrorCode, a_ErrorMsg.c_str()); } }; diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp index 8ee04f8ee..be08f179c 100644 --- a/tests/Network/Google.cpp +++ b/tests/Network/Google.cpp @@ -27,9 +27,9 @@ class cHTTPConnectCallbacks: LOGD("HTTP GET queued."); } - virtual void OnError(int a_ErrorCode) override + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override { - LOGD("Error while connecting HTTP: %d", a_ErrorCode); + LOGD("Error while connecting HTTP: %d (%s)", a_ErrorCode, a_ErrorMsg.c_str()); m_Event.Set(); } @@ -64,9 +64,9 @@ class cDumpCallbacks: m_Event.Set(); } - virtual void OnError(cTCPLink & a_Link, int a_ErrorCode) override + virtual void OnError(cTCPLink & a_Link, int a_ErrorCode, const AString & a_ErrorMsg) override { - LOGD("Error in the cDumpCallbacks."); + LOGD("Error %d (%s) in the cDumpCallbacks.", a_ErrorCode, a_ErrorMsg.c_str()); m_Event.Set(); } diff --git a/tests/Network/NameLookup.cpp b/tests/Network/NameLookup.cpp index 74a57258c..822a96baf 100644 --- a/tests/Network/NameLookup.cpp +++ b/tests/Network/NameLookup.cpp @@ -22,9 +22,9 @@ class cFinishLookupCallbacks: LOGD("%s resolves to IP %s", a_Name.c_str(), a_IP.c_str()); } - virtual void OnError(int a_ErrorCode) override + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override { - LOGD("Error %d while performing lookup!", a_ErrorCode); + LOGD("Error %d (%s) while performing lookup!", a_ErrorCode, a_ErrorMsg.c_str()); exit(a_ErrorCode); } -- cgit v1.2.3 From 5b4c5cf2befebb78ff2b16955c244e79841648a7 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 21 Jan 2015 21:12:11 +0100 Subject: cNetwork: Changed listening API. The link-callbacks for each new accepted link are now received from the OnIncomingConnection listen-callback. --- src/OSSupport/Network.h | 18 +++++++++----- src/OSSupport/ServerHandleImpl.cpp | 49 +++++++++++++++++++++++++++---------- src/OSSupport/ServerHandleImpl.h | 11 ++------- src/OSSupport/TCPLinkImpl.cpp | 2 +- tests/Network/EchoServer.cpp | 50 +++++++++++++++++++++----------------- 5 files changed, 79 insertions(+), 51 deletions(-) diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index 3ed9885ec..b9ca377cb 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -139,8 +139,15 @@ public: // Force a virtual destructor for all descendants: virtual ~cListenCallbacks() {} - /** Called when the TCP server created with Listen() accepts an incoming connection. - Provides the newly created Link that can be used for communication. */ + /** 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. */ @@ -180,7 +187,7 @@ public: Implemented in TCPLinkImpl.cpp. */ static bool Connect( const AString & a_Host, - const UInt16 a_Port, + UInt16 a_Port, cConnectCallbacksPtr a_ConnectCallbacks, cTCPLink::cCallbacksPtr a_LinkCallbacks ); @@ -192,9 +199,8 @@ public: Returns a cServerHandle that can be used to query the operation status and close the server. Implemented in ServerHandleImpl.cpp. */ static cServerHandlePtr Listen( - const UInt16 a_Port, - cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks + UInt16 a_Port, + cListenCallbacksPtr a_ListenCallbacks ); diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp index d81d4ba46..82cbecef2 100644 --- a/src/OSSupport/ServerHandleImpl.cpp +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -31,12 +31,8 @@ static bool IsValidSocket(evutil_socket_t a_Socket) //////////////////////////////////////////////////////////////////////////////// // cServerHandleImpl: -cServerHandleImpl::cServerHandleImpl( - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks -): +cServerHandleImpl::cServerHandleImpl(cNetwork::cListenCallbacksPtr a_ListenCallbacks): m_ListenCallbacks(a_ListenCallbacks), - m_LinkCallbacks(a_LinkCallbacks), m_ConnListener(nullptr), m_SecondaryConnListener(nullptr), m_IsListening(false), @@ -92,11 +88,10 @@ void cServerHandleImpl::Close(void) cServerHandleImplPtr cServerHandleImpl::Listen( UInt16 a_Port, - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks + cNetwork::cListenCallbacksPtr a_ListenCallbacks ) { - cServerHandleImplPtr res = cServerHandleImplPtr{new cServerHandleImpl(a_ListenCallbacks, a_LinkCallbacks)}; + cServerHandleImplPtr res = cServerHandleImplPtr{new cServerHandleImpl(a_ListenCallbacks)}; if (res->Listen(a_Port)) { cNetworkSingleton::Get().AddServer(res); @@ -253,8 +248,37 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_ cServerHandleImpl * Self = reinterpret_cast(a_Self); ASSERT(Self != 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, Self->m_LinkCallbacks, Self, a_Addr, static_cast(a_Len)); + cTCPLinkImplPtr Link = std::make_shared(a_Socket, LinkCallbacks, Self, a_Addr, static_cast(a_Len)); { cCSLock Lock(Self->m_CS); Self->m_Connections.push_back(Link); @@ -289,12 +313,11 @@ void cServerHandleImpl::RemoveLink(const cTCPLinkImpl * a_Link) // cNetwork API: cServerHandlePtr cNetwork::Listen( - const UInt16 a_Port, - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks + UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks ) { - return cServerHandleImpl::Listen(a_Port, a_ListenCallbacks, a_LinkCallbacks); + return cServerHandleImpl::Listen(a_Port, a_ListenCallbacks); } diff --git a/src/OSSupport/ServerHandleImpl.h b/src/OSSupport/ServerHandleImpl.h index b325a0f37..33ff787f2 100644 --- a/src/OSSupport/ServerHandleImpl.h +++ b/src/OSSupport/ServerHandleImpl.h @@ -46,8 +46,7 @@ public: 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, - cTCPLink::cCallbacksPtr a_LinkCallbacks + cNetwork::cListenCallbacksPtr a_ListenCallbacks ); // cServerHandle overrides: @@ -58,9 +57,6 @@ protected: /** The callbacks used to notify about incoming connections. */ cNetwork::cListenCallbacksPtr m_ListenCallbacks; - /** The callbacks used to create new cTCPLink instances for incoming connections. */ - cTCPLink::cCallbacksPtr m_LinkCallbacks; - /** The LibEvent handle representing the main listening socket. */ evconnlistener * m_ConnListener; @@ -86,10 +82,7 @@ protected: /** Creates a new instance with the specified callbacks. Initializes the internals, but doesn't start listening yet. */ - cServerHandleImpl( - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks - ); + 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. */ diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp index f87f68280..6f937646f 100644 --- a/src/OSSupport/TCPLinkImpl.cpp +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -304,7 +304,7 @@ void cTCPLinkImpl::UpdateRemoteAddress(void) bool cNetwork::Connect( const AString & a_Host, - const UInt16 a_Port, + UInt16 a_Port, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks, cTCPLink::cCallbacksPtr a_LinkCallbacks ) diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp index 86b517245..061310c82 100644 --- a/tests/Network/EchoServer.cpp +++ b/tests/Network/EchoServer.cpp @@ -12,27 +12,6 @@ -class cEchoServerCallbacks: - public cNetwork::cListenCallbacks -{ - 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()); - } -}; - - - - - /** cTCPLink callbacks that echo everything they receive back to the remote peer. */ class cEchoLinkCallbacks: public cTCPLink::cCallbacks @@ -70,10 +49,37 @@ class cEchoLinkCallbacks: +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(), std::make_shared()); + cServerHandlePtr Server = cNetwork::Listen(9876, std::make_shared()); if (!Server->IsListening()) { LOGWARNING("Cannot listen on port 9876"); -- cgit v1.2.3 From dbf7f13bd414daea5e787da2543df186dc465c34 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Thu, 22 Jan 2015 13:00:32 +0100 Subject: cNetwork: Added link creation callback. This allows the callback classes to store the link inside them and use it internally later on, mainly for sending data. --- src/OSSupport/Network.h | 18 +++++++++++++++--- src/OSSupport/ServerHandleImpl.cpp | 2 ++ src/OSSupport/TCPLinkImpl.cpp | 26 ++++++++++++++++---------- src/OSSupport/TCPLinkImpl.h | 11 +++++++++-- tests/Network/EchoServer.cpp | 38 ++++++++++++++++++++++++++++++-------- tests/Network/Google.cpp | 21 ++++++++++++++++++--- 6 files changed, 90 insertions(+), 26 deletions(-) diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index b9ca377cb..85c7c5dcb 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -13,6 +13,14 @@ +// fwd: +class cTCPLink; +typedef SharedPtr cTCPLinkPtr; + + + + + /** Interface that provides the methods available on a single TCP connection. */ class cTCPLink { @@ -25,16 +33,20 @@ 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(cTCPLink & a_Link, const char * a_Data, size_t a_Length) = 0; + 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(cTCPLink & a_Link) = 0; + virtual void OnRemoteClosed(void) = 0; /** Called when an error is detected on the connection. */ - virtual void OnError(cTCPLink & a_Link, int a_ErrorCode, const AString & a_ErrorMsg) = 0; + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; }; typedef SharedPtr cCallbacksPtr; diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp index 82cbecef2..026c0fdc1 100644 --- a/src/OSSupport/ServerHandleImpl.cpp +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -283,6 +283,8 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_ cCSLock Lock(Self->m_CS); Self->m_Connections.push_back(Link); } // Lock(m_CS) + LinkCallbacks->OnLinkCreated(Link); + Link->Enable(); // Call the OnAccepted callback: Self->m_ListenCallbacks->OnAccepted(*Link); diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp index 6f937646f..5d31da22e 100644 --- a/src/OSSupport/TCPLinkImpl.cpp +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -20,9 +20,6 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE)), m_Server(nullptr) { - // Create the LibEvent handle, but don't assign a socket to it yet (will be assigned within Connect() method): - bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); - bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); } @@ -37,10 +34,6 @@ cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_L // Update the endpoint addresses: UpdateLocalAddress(); UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort); - - // Create the LibEvent handle: - bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); - bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); } @@ -65,6 +58,8 @@ cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTC 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(); // If a_Host is an IP address, schedule a connection immediately: sockaddr_storage sa; @@ -107,6 +102,17 @@ cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTC +void cTCPLinkImpl::Enable(void) +{ + // 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); @@ -160,7 +166,7 @@ void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self) size_t length; while ((length = bufferevent_read(a_BufferEvent, data, sizeof(data))) > 0) { - Self->m_Callbacks->OnReceivedData(*Self, data, length); + Self->m_Callbacks->OnReceivedData(data, length); } } @@ -189,7 +195,7 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void } else { - Self->m_Callbacks->OnError(*Self, err, evutil_socket_error_to_string(err)); + Self->m_Callbacks->OnError(err, evutil_socket_error_to_string(err)); if (Self->m_Server == nullptr) { cNetworkSingleton::Get().RemoveLink(Self); @@ -219,7 +225,7 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void // If the connection has been closed, call the link callback and remove the connection: if (a_What & BEV_EVENT_EOF) { - Self->m_Callbacks->OnRemoteClosed(*Self); + Self->m_Callbacks->OnRemoteClosed(); if (Self->m_Server != nullptr) { Self->m_Server->RemoveLink(Self); diff --git a/src/OSSupport/TCPLinkImpl.h b/src/OSSupport/TCPLinkImpl.h index edd295fe2..66347afe0 100644 --- a/src/OSSupport/TCPLinkImpl.h +++ b/src/OSSupport/TCPLinkImpl.h @@ -37,7 +37,8 @@ class cTCPLinkImpl: 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. */ + 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, cServerHandleImpl * a_Server, const sockaddr * a_Address, socklen_t a_AddrLen); /** Destroys the LibEvent handle representing the link. */ @@ -48,6 +49,11 @@ public: 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. */ + void Enable(void); + // cTCPLink overrides: virtual bool Send(const void * a_Data, size_t a_Length) override; virtual AString GetLocalIP(void) const override { return m_LocalIP; } @@ -85,7 +91,8 @@ protected: /** 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. */ + 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. */ diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp index 061310c82..728db0b7c 100644 --- a/tests/Network/EchoServer.cpp +++ b/tests/Network/EchoServer.cpp @@ -16,11 +16,21 @@ class cEchoLinkCallbacks: public cTCPLink::cCallbacks { - virtual void OnReceivedData(cTCPLink & a_Link, const char * a_Data, size_t a_Size) override + // 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.", &a_Link, a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort(), static_cast(a_Size)); - a_Link.Send(a_Data, a_Size); + 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: @@ -28,21 +38,33 @@ class cEchoLinkCallbacks: { if (a_Data[i] == '\x1a') { - a_Link.Close(); + m_Link->Close(); + m_Link.reset(); return; } } } - virtual void OnRemoteClosed(cTCPLink & a_Link) override + + virtual void OnRemoteClosed(void) override { - LOGD("%p (%s:%d): Remote has closed the connection.", &a_Link, a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort()); + 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(cTCPLink & a_Link, int a_ErrorCode, const AString & a_ErrorMsg) override + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override { - LOGD("%p (%s:%d): Error %d in the cEchoLinkCallbacks: %s", &a_Link, a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort(), a_ErrorCode, a_ErrorMsg.c_str()); + 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; }; diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp index be08f179c..8985eef17 100644 --- a/tests/Network/Google.cpp +++ b/tests/Network/Google.cpp @@ -49,24 +49,39 @@ class cDumpCallbacks: public cTCPLink::cCallbacks { cEvent & m_Event; + cTCPLinkPtr m_Link; - virtual void OnReceivedData(cTCPLink & a_Link, const char * a_Data, size_t a_Size) override + 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(cTCPLink & a_Link) override + virtual void OnRemoteClosed(void) override { + ASSERT(m_Link != nullptr); + LOGD("Remote has closed the connection."); + m_Link.reset(); m_Event.Set(); } - virtual void OnError(cTCPLink & a_Link, int a_ErrorCode, const AString & a_ErrorMsg) override + 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(); } -- cgit v1.2.3 From 9014bdfa3233dac70274b27eb40df3739b6f49eb Mon Sep 17 00:00:00 2001 From: Mattes D Date: Thu, 22 Jan 2015 22:49:37 +0100 Subject: cNetwork: Renamed callback to OnConnected() --- src/OSSupport/Network.h | 7 +++++-- src/OSSupport/TCPLinkImpl.cpp | 2 +- tests/Network/Google.cpp | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index 85c7c5dcb..cdf6ba0e9 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -16,6 +16,10 @@ // fwd: class cTCPLink; typedef SharedPtr cTCPLinkPtr; +typedef std::vector cTCPLinkPtrs; +class cServerHandle; +typedef SharedPtr cServerHandlePtr; +typedef std::vector cServerHandlePtrs; @@ -118,7 +122,6 @@ public: /** Returns true if the server has been started correctly and is currently listening for incoming connections. */ virtual bool IsListening(void) const = 0; }; -typedef SharedPtr cServerHandlePtr; @@ -136,7 +139,7 @@ public: /** Called when the Connect call succeeds. Provides the newly created link that can be used for communication. */ - virtual void OnSuccess(cTCPLink & a_Link) = 0; + virtual void OnConnected(cTCPLink & a_Link) = 0; /** Called when the Connect call fails. */ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp index 5d31da22e..71b3d572d 100644 --- a/src/OSSupport/TCPLinkImpl.cpp +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -213,7 +213,7 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void { if (Self->m_ConnectCallbacks != nullptr) { - Self->m_ConnectCallbacks->OnSuccess(*Self); + Self->m_ConnectCallbacks->OnConnected(*Self); // Reset the connect callbacks so that later errors get reported through the link callbacks: Self->m_ConnectCallbacks.reset(); return; diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp index 8985eef17..2b8830c24 100644 --- a/tests/Network/Google.cpp +++ b/tests/Network/Google.cpp @@ -17,7 +17,7 @@ class cHTTPConnectCallbacks: public cNetwork::cConnectCallbacks { cEvent & m_Event; - virtual void OnSuccess(cTCPLink & a_Link) override + 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")) -- cgit v1.2.3 From 10cfa61fbc5d0720f4e4864f50f1298937327348 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Fri, 23 Jan 2015 23:01:18 +0100 Subject: cNetwork: Added self pointers to keep objects alive for callbacks. Ref.: http://forum.mc-server.org/showthread.php?tid=1700&pid=17947#pid17947 --- src/OSSupport/NetworkSingleton.cpp | 2 ++ src/OSSupport/ServerHandleImpl.cpp | 10 ++++++++-- src/OSSupport/ServerHandleImpl.h | 3 +++ src/OSSupport/TCPLinkImpl.cpp | 25 +++++++++++++++---------- src/OSSupport/TCPLinkImpl.h | 16 +++++++++++----- 5 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/OSSupport/NetworkSingleton.cpp b/src/OSSupport/NetworkSingleton.cpp index 8c38fb4eb..92f0604cd 100644 --- a/src/OSSupport/NetworkSingleton.cpp +++ b/src/OSSupport/NetworkSingleton.cpp @@ -88,6 +88,8 @@ cNetworkSingleton::~cNetworkSingleton() // Free the underlying LibEvent objects: evdns_base_free(m_DNSBase, true); event_base_free(m_EventBase); + + libevent_global_shutdown(); } diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp index 026c0fdc1..ba38dbf2e 100644 --- a/src/OSSupport/ServerHandleImpl.cpp +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -80,6 +80,9 @@ void cServerHandleImpl::Close(void) { conn->Shutdown(); } + + // Remove the ptr to self, so that the object may be freed: + m_SelfPtr.reset(); } @@ -92,6 +95,7 @@ cServerHandleImplPtr cServerHandleImpl::Listen( ) { cServerHandleImplPtr res = cServerHandleImplPtr{new cServerHandleImpl(a_ListenCallbacks)}; + res->m_SelfPtr = res; if (res->Listen(a_Port)) { cNetworkSingleton::Get().AddServer(res); @@ -99,6 +103,7 @@ cServerHandleImplPtr cServerHandleImpl::Listen( else { a_ListenCallbacks->OnError(res->m_ErrorCode, res->m_ErrorMsg); + res->m_SelfPtr.reset(); } return res; } @@ -247,6 +252,7 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_ // 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]; @@ -278,13 +284,13 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_ } // Create a new cTCPLink for the incoming connection: - cTCPLinkImplPtr Link = std::make_shared(a_Socket, LinkCallbacks, Self, a_Addr, static_cast(a_Len)); + 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->Enable(Link); // Call the OnAccepted callback: Self->m_ListenCallbacks->OnAccepted(*Link); diff --git a/src/OSSupport/ServerHandleImpl.h b/src/OSSupport/ServerHandleImpl.h index 33ff787f2..dbb18fc6d 100644 --- a/src/OSSupport/ServerHandleImpl.h +++ b/src/OSSupport/ServerHandleImpl.h @@ -78,6 +78,9 @@ protected: /** 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. diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp index 71b3d572d..b4cefa60c 100644 --- a/src/OSSupport/TCPLinkImpl.cpp +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -17,8 +17,7 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE)), - m_Server(nullptr) + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE)) { } @@ -26,7 +25,7 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): -cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, socklen_t a_AddrLen): +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) @@ -59,7 +58,7 @@ cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTC res->m_ConnectCallbacks = a_ConnectCallbacks; cNetworkSingleton::Get().AddLink(res); res->m_Callbacks->OnLinkCreated(res); - res->Enable(); + res->Enable(res); // If a_Host is an IP address, schedule a connection immediately: sockaddr_storage sa; @@ -102,8 +101,11 @@ cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTC -void cTCPLinkImpl::Enable(void) +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); @@ -148,6 +150,7 @@ void cTCPLinkImpl::Close(void) { m_Server->RemoveLink(this); } + m_Self.reset(); } @@ -177,7 +180,7 @@ void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self) void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) { ASSERT(a_Self != nullptr); - cTCPLinkImpl * Self = static_cast(a_Self); + cTCPLinkImplPtr Self = static_cast(a_Self)->m_Self; // If an error is reported, call the error callback: if (a_What & BEV_EVENT_ERROR) @@ -198,13 +201,14 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void Self->m_Callbacks->OnError(err, evutil_socket_error_to_string(err)); if (Self->m_Server == nullptr) { - cNetworkSingleton::Get().RemoveLink(Self); + cNetworkSingleton::Get().RemoveLink(Self.get()); } else { - Self->m_Server->RemoveLink(Self); + Self->m_Server->RemoveLink(Self.get()); } } + Self->m_Self.reset(); return; } @@ -228,12 +232,13 @@ void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void Self->m_Callbacks->OnRemoteClosed(); if (Self->m_Server != nullptr) { - Self->m_Server->RemoveLink(Self); + Self->m_Server->RemoveLink(Self.get()); } else { - cNetworkSingleton::Get().RemoveLink(Self); + cNetworkSingleton::Get().RemoveLink(Self.get()); } + Self->m_Self.reset(); return; } diff --git a/src/OSSupport/TCPLinkImpl.h b/src/OSSupport/TCPLinkImpl.h index 66347afe0..735e8ed9d 100644 --- a/src/OSSupport/TCPLinkImpl.h +++ b/src/OSSupport/TCPLinkImpl.h @@ -21,6 +21,7 @@ // fwd: class cServerHandleImpl; +typedef SharedPtr cServerHandleImplPtr; class cTCPLinkImpl; typedef SharedPtr cTCPLinkImplPtr; typedef std::vector cTCPLinkImplPtrs; @@ -39,7 +40,7 @@ public: 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, cServerHandleImpl * a_Server, const sockaddr * a_Address, socklen_t a_AddrLen); + 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(); @@ -51,8 +52,9 @@ public: /** 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. */ - void Enable(void); + 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; @@ -73,8 +75,8 @@ protected: bufferevent * m_BufferEvent; /** The server handle that has created this link. - Only valid for incoming connections, NULL for outgoing connections. */ - cServerHandleImpl * m_Server; + 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; @@ -88,6 +90,10 @@ protected: /** 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(). -- cgit v1.2.3