diff options
Diffstat (limited to 'src/OSSupport')
-rw-r--r-- | src/OSSupport/CMakeLists.txt | 11 | ||||
-rw-r--r-- | src/OSSupport/CriticalSection.cpp | 1 | ||||
-rw-r--r-- | src/OSSupport/HostnameLookup.cpp | 124 | ||||
-rw-r--r-- | src/OSSupport/HostnameLookup.h | 47 | ||||
-rw-r--r-- | src/OSSupport/IPLookup.cpp | 111 | ||||
-rw-r--r-- | src/OSSupport/IPLookup.h | 49 | ||||
-rw-r--r-- | src/OSSupport/IsThread.cpp | 18 | ||||
-rw-r--r-- | src/OSSupport/IsThread.h | 19 | ||||
-rw-r--r-- | src/OSSupport/Network.h | 246 | ||||
-rw-r--r-- | src/OSSupport/NetworkSingleton.cpp | 245 | ||||
-rw-r--r-- | src/OSSupport/NetworkSingleton.h | 134 | ||||
-rw-r--r-- | src/OSSupport/Queue.h | 4 | ||||
-rw-r--r-- | src/OSSupport/ServerHandleImpl.cpp | 334 | ||||
-rw-r--r-- | src/OSSupport/ServerHandleImpl.h | 105 | ||||
-rw-r--r-- | src/OSSupport/StackTrace.cpp | 2 | ||||
-rw-r--r-- | src/OSSupport/TCPLinkImpl.cpp | 331 | ||||
-rw-r--r-- | src/OSSupport/TCPLinkImpl.h | 122 |
17 files changed, 1893 insertions, 10 deletions
diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index e943ceb18..9424b63da 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -10,12 +10,17 @@ SET (SRCS Event.cpp File.cpp GZipFile.cpp + HostnameLookup.cpp + IPLookup.cpp IsThread.cpp ListenThread.cpp + NetworkSingleton.cpp Semaphore.cpp + ServerHandleImpl.cpp Socket.cpp SocketThreads.cpp StackTrace.cpp + TCPLinkImpl.cpp ) SET (HDRS @@ -24,13 +29,19 @@ SET (HDRS Event.h File.h GZipFile.h + HostnameLookup.h + IPLookup.h IsThread.h ListenThread.h + Network.h + NetworkSingleton.h Queue.h Semaphore.h + ServerHandleImpl.h Socket.h SocketThreads.h StackTrace.h + TCPLinkImpl.h ) if(NOT MSVC) diff --git a/src/OSSupport/CriticalSection.cpp b/src/OSSupport/CriticalSection.cpp index 13a3e4d9f..5248356c5 100644 --- a/src/OSSupport/CriticalSection.cpp +++ b/src/OSSupport/CriticalSection.cpp @@ -1,5 +1,6 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules +#include "CriticalSection.h" diff --git a/src/OSSupport/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp new file mode 100644 index 000000000..3a2997ffd --- /dev/null +++ b/src/OSSupport/HostnameLookup.cpp @@ -0,0 +1,124 @@ + +// HostnameLookup.cpp + +// Implements the cHostnameLookup class representing an in-progress hostname-to-IP lookup + +#include "Globals.h" +#include "HostnameLookup.h" +#include <event2/dns.h> +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cHostnameLookup: + +cHostnameLookup::cHostnameLookup(cNetwork::cResolveNameCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks) +{ +} + + + + + +void cHostnameLookup::Lookup(const AString & a_Hostname) +{ + // Store the hostname for the callback: + m_Hostname = a_Hostname; + + // Start the lookup: + // Note that we don't have to store the LibEvent lookup handle, LibEvent will free it on its own. + evutil_addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + hints.ai_flags = EVUTIL_AI_CANONNAME; + evdns_getaddrinfo(cNetworkSingleton::Get().GetDNSBase(), a_Hostname.c_str(), nullptr, &hints, Callback, this); +} + + + + + +void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a_Self) +{ + // Get the Self class: + cHostnameLookup * Self = reinterpret_cast<cHostnameLookup *>(a_Self); + ASSERT(Self != nullptr); + + // If an error has occurred, notify the error callback: + if (a_ErrCode != 0) + { + Self->m_Callbacks->OnError(a_ErrCode, evutil_socket_error_to_string(a_ErrCode)); + cNetworkSingleton::Get().RemoveHostnameLookup(Self); + return; + } + + // Call the success handler for each entry received: + bool HasResolved = false; + evutil_addrinfo * OrigAddr = a_Addr; + for (;a_Addr != nullptr; a_Addr = a_Addr->ai_next) + { + char IP[128]; + switch (a_Addr->ai_family) + { + case AF_INET: // IPv4 + { + sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(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<sockaddr_in6 *>(a_Addr->ai_addr); + evutil_inet_ntop(AF_INET6, &(sin->sin6_addr), IP, sizeof(IP)); + break; + } + default: + { + // Unknown address family, handle as if this entry wasn't received + continue; // for (a_Addr) + } + } + Self->m_Callbacks->OnNameResolved(Self->m_Hostname, IP); + HasResolved = true; + } // for (a_Addr) + + // If only unsupported families were reported, call the Error handler: + if (!HasResolved) + { + Self->m_Callbacks->OnError(DNS_ERR_NODATA, "The name does not resolve to any known address."); + } + else + { + Self->m_Callbacks->OnFinished(); + } + evutil_freeaddrinfo(OrigAddr); + cNetworkSingleton::Get().RemoveHostnameLookup(Self); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +bool cNetwork::HostnameToIP( + const AString & a_Hostname, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + auto Lookup = std::make_shared<cHostnameLookup>(a_Callbacks); + cNetworkSingleton::Get().AddHostnameLookup(Lookup); + Lookup->Lookup(a_Hostname); + return true; +} + + + + diff --git a/src/OSSupport/HostnameLookup.h b/src/OSSupport/HostnameLookup.h new file mode 100644 index 000000000..d69f24707 --- /dev/null +++ b/src/OSSupport/HostnameLookup.h @@ -0,0 +1,47 @@ + +// HostnameLookup.h + +// Declares the cHostnameLookup class representing an in-progress hostname-to-IP lookup + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include <event2/util.h> + + + + + +/** Holds information about an in-progress Hostname-to-IP lookup. */ +class cHostnameLookup +{ +public: + /** Creates the lookup object. Doesn't start the lookup yet. */ + cHostnameLookup(cNetwork::cResolveNameCallbacksPtr a_Callbacks); + + /** Starts the lookup. */ + void Lookup(const AString & a_Hostname); + +protected: + + /** The callbacks to call for resolved names / errors. */ + cNetwork::cResolveNameCallbacksPtr m_Callbacks; + + /** The hostname that was queried (needed for the callbacks). */ + AString m_Hostname; + + static void Callback(int a_ErrCode, struct evutil_addrinfo * a_Addr, void * a_Self); +}; +typedef SharedPtr<cHostnameLookup> cHostnameLookupPtr; +typedef std::vector<cHostnameLookupPtr> cHostnameLookupPtrs; + + + + + diff --git a/src/OSSupport/IPLookup.cpp b/src/OSSupport/IPLookup.cpp new file mode 100644 index 000000000..8cdc5132d --- /dev/null +++ b/src/OSSupport/IPLookup.cpp @@ -0,0 +1,111 @@ + +// IPLookup.cpp + +// Implements the cIPLookup class representing an IP-to-hostname lookup in progress. + +#include "Globals.h" +#include "IPLookup.h" +#include <event2/dns.h> +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cIPLookup: + +cIPLookup::cIPLookup(cNetwork::cResolveNameCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks) +{ + ASSERT(a_Callbacks != nullptr); +} + + + + + +bool cIPLookup::Lookup(const AString & a_IP) +{ + // Parse the IP address string into a sockaddr structure: + m_IP = a_IP; + sockaddr_storage sa; + int salen = static_cast<int>(sizeof(sa)); + memset(&sa, 0, sizeof(sa)); + if (evutil_parse_sockaddr_port(a_IP.c_str(), reinterpret_cast<sockaddr *>(&sa), &salen) != 0) + { + LOGD("Failed to parse IP address \"%s\".", a_IP.c_str()); + return false; + } + + // Call the proper resolver based on the address family: + // Note that there's no need to store the evdns_request handle returned, LibEvent frees it on its own. + switch (sa.ss_family) + { + case AF_INET: + { + sockaddr_in * sa4 = reinterpret_cast<sockaddr_in *>(&sa); + evdns_base_resolve_reverse(cNetworkSingleton::Get().GetDNSBase(), &(sa4->sin_addr), 0, Callback, this); + break; + } + case AF_INET6: + { + sockaddr_in6 * sa6 = reinterpret_cast<sockaddr_in6 *>(&sa); + evdns_base_resolve_reverse_ipv6(cNetworkSingleton::Get().GetDNSBase(), &(sa6->sin6_addr), 0, Callback, this); + break; + } + default: + { + LOGWARNING("%s: Unknown address family: %d", __FUNCTION__, sa.ss_family); + ASSERT(!"Unknown address family"); + return false; + } + } // switch (address family) + return true; +} + + + + + +void cIPLookup::Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self) +{ + // Get the Self class: + cIPLookup * Self = reinterpret_cast<cIPLookup *>(a_Self); + ASSERT(Self != nullptr); + + // Call the proper callback based on the event received: + if ((a_Result != 0) || (a_Addresses == nullptr)) + { + // An error has occurred, notify the error callback: + Self->m_Callbacks->OnError(a_Result, evutil_socket_error_to_string(a_Result)); + } + else + { + // Call the success handler: + Self->m_Callbacks->OnNameResolved(*(reinterpret_cast<char **>(a_Addresses)), Self->m_IP); + Self->m_Callbacks->OnFinished(); + } + cNetworkSingleton::Get().RemoveIPLookup(Self); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +bool cNetwork::IPToHostName( + const AString & a_IP, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + auto res = std::make_shared<cIPLookup>(a_Callbacks); + cNetworkSingleton::Get().AddIPLookup(res); + return res->Lookup(a_IP); +} + + + + diff --git a/src/OSSupport/IPLookup.h b/src/OSSupport/IPLookup.h new file mode 100644 index 000000000..af878cbf1 --- /dev/null +++ b/src/OSSupport/IPLookup.h @@ -0,0 +1,49 @@ + +// IPLookup.h + +// Declares the cIPLookup class representing an IP-to-hostname lookup in progress. + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" + + + + + +/** Holds information about an in-progress IP-to-Hostname lookup. */ +class cIPLookup +{ +public: + /** Creates the lookup object. Doesn't start the lookup yet. */ + cIPLookup(cNetwork::cResolveNameCallbacksPtr a_Callbacks); + + /** Starts the lookup. + Returns true if lookup started successfully, false on failure (invalid IP format etc.) */ + bool Lookup(const AString & a_IP); + +protected: + + /** The callbacks to call for resolved names / errors. */ + cNetwork::cResolveNameCallbacksPtr m_Callbacks; + + /** The IP that was queried (needed for the callbacks). */ + AString m_IP; + + + /** Callback that is called by LibEvent when there's an event for the request. */ + static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); +}; +typedef SharedPtr<cIPLookup> cIPLookupPtr; +typedef std::vector<cIPLookupPtr> cIPLookupPtrs; + + + + + diff --git a/src/OSSupport/IsThread.cpp b/src/OSSupport/IsThread.cpp index 94bed1f56..55e96b622 100644 --- a/src/OSSupport/IsThread.cpp +++ b/src/OSSupport/IsThread.cpp @@ -68,11 +68,22 @@ cIsThread::~cIsThread() +void cIsThread::DoExecute(void) +{ + m_evtStart.Wait(); + Execute(); +} + + + + + bool cIsThread::Start(void) { try { - m_Thread = std::thread(&cIsThread::Execute, this); + // Initialize the thread: + m_Thread = std::thread(&cIsThread::DoExecute, this); #if defined (_MSC_VER) && defined(_DEBUG) if (!m_ThreadName.empty()) @@ -81,9 +92,12 @@ bool cIsThread::Start(void) } #endif + // Notify the thread that initialization is complete and it can run its code safely: + m_evtStart.Set(); + return true; } - catch (std::system_error & a_Exception) + catch (const std::system_error & a_Exception) { LOGERROR("cIsThread::Start error %i: could not construct thread %s; %s", a_Exception.code().value(), m_ThreadName.c_str(), a_Exception.code().message().c_str()); return false; diff --git a/src/OSSupport/IsThread.h b/src/OSSupport/IsThread.h index 131c6950e..f642c8724 100644 --- a/src/OSSupport/IsThread.h +++ b/src/OSSupport/IsThread.h @@ -25,23 +25,28 @@ In the descending class' constructor call the Start() method to start the thread class cIsThread { protected: - /// This is the main thread entrypoint + /** This is the main thread entrypoint. + This function, overloaded by the descendants, is called in the new thread. */ virtual void Execute(void) = 0; - /// The overriden Execute() method should check this value periodically and terminate if this is true + /** The overriden Execute() method should check this value periodically and terminate if this is true. */ volatile bool m_ShouldTerminate; +private: + /** Wrapper for Execute() that waits for the initialization event, to prevent race conditions in thread initialization. */ + void DoExecute(void); + public: cIsThread(const AString & a_ThreadName); virtual ~cIsThread(); - /// Starts the thread; returns without waiting for the actual start + /** Starts the thread; returns without waiting for the actual start. */ bool Start(void); - /// Signals the thread to terminate and waits until it's finished + /** Signals the thread to terminate and waits until it's finished. */ void Stop(void); - /// Waits for the thread to finish. Doesn't signalize the ShouldTerminate flag + /** Waits for the thread to finish. Doesn't signalize the ShouldTerminate flag. */ bool Wait(void); /** Returns true if the thread calling this function is the thread contained within this object. */ @@ -50,6 +55,10 @@ public: protected: AString m_ThreadName; std::thread m_Thread; + + /** The event that is used to wait with the thread's execution until the thread object is fully initialized. + This prevents the IsCurrentThread() call to fail because of a race-condition. */ + cEvent m_evtStart; } ; diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h new file mode 100644 index 000000000..cdf6ba0e9 --- /dev/null +++ b/src/OSSupport/Network.h @@ -0,0 +1,246 @@ + +// Network.h + +// Declares the classes used for the Network API + + + + + +#pragma once + + + + + +// fwd: +class cTCPLink; +typedef SharedPtr<cTCPLink> cTCPLinkPtr; +typedef std::vector<cTCPLinkPtr> cTCPLinkPtrs; +class cServerHandle; +typedef SharedPtr<cServerHandle> cServerHandlePtr; +typedef std::vector<cServerHandlePtr> cServerHandlePtrs; + + + + + +/** Interface that provides the methods available on a single TCP connection. */ +class cTCPLink +{ + friend class cNetwork; + +public: + class cCallbacks + { + public: + // Force a virtual destructor for all descendants: + virtual ~cCallbacks() {} + + /** Called when the cTCPLink for the connection is created. + The callback may store the cTCPLink instance for later use, but it should remove it in OnError(), OnRemoteClosed() or right after Close(). */ + virtual void OnLinkCreated(cTCPLinkPtr a_Link) = 0; + + /** Called when there's data incoming from the remote peer. */ + virtual void OnReceivedData(const char * a_Data, size_t a_Length) = 0; + + /** Called when the remote end closes the connection. + The link is still available for connection information query (IP / port). + Sending data on the link is not an error, but the data won't be delivered. */ + virtual void OnRemoteClosed(void) = 0; + + /** Called when an error is detected on the connection. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + }; + typedef SharedPtr<cCallbacks> cCallbacksPtr; + + + // Force a virtual destructor for all descendants: + virtual ~cTCPLink() {} + + /** Queues the specified data for sending to the remote peer. + Returns true on success, false on failure. Note that this success or failure only reports the queue status, not the actual data delivery. */ + virtual bool Send(const void * a_Data, size_t a_Length) = 0; + + /** Queues the specified data for sending to the remote peer. + Returns true on success, false on failure. Note that this success or failure only reports the queue status, not the actual data delivery. */ + bool Send(const AString & a_Data) + { + return Send(a_Data.data(), a_Data.size()); + } + + /** Returns the IP address of the local endpoint of the connection. */ + virtual AString GetLocalIP(void) const = 0; + + /** Returns the port used by the local endpoint of the connection. */ + virtual UInt16 GetLocalPort(void) const = 0; + + /** Returns the IP address of the remote endpoint of the connection. */ + virtual AString GetRemoteIP(void) const = 0; + + /** Returns the port used by the remote endpoint of the connection. */ + virtual UInt16 GetRemotePort(void) const = 0; + + /** Closes the link gracefully. + The link will send any queued outgoing data, then it will send the FIN packet. + The link will still receive incoming data from remote until the remote closes the connection. */ + virtual void Shutdown(void) = 0; + + /** Drops the connection without any more processing. + Sends the RST packet, queued outgoing and incoming data is lost. */ + virtual void Close(void) = 0; + +protected: + /** Callbacks to be used for the various situations. */ + cCallbacksPtr m_Callbacks; + + + /** Creates a new link, with the specified callbacks. */ + cTCPLink(cCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks) + { + } +}; + + + + + +/** Interface that provides the methods available on a listening server socket. */ +class cServerHandle +{ + friend class cNetwork; +public: + + // Force a virtual destructor for all descendants: + virtual ~cServerHandle() {} + + /** Stops the server, no more incoming connections will be accepted. + All current connections will be shut down (cTCPLink::Shutdown()). */ + virtual void Close(void) = 0; + + /** Returns true if the server has been started correctly and is currently listening for incoming connections. */ + virtual bool IsListening(void) const = 0; +}; + + + + + +class cNetwork +{ +public: + /** Callbacks used for connecting to other servers as a client. */ + class cConnectCallbacks + { + public: + // Force a virtual destructor for all descendants: + virtual ~cConnectCallbacks() {} + + /** Called when the Connect call succeeds. + Provides the newly created link that can be used for communication. */ + virtual void OnConnected(cTCPLink & a_Link) = 0; + + /** Called when the Connect call fails. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + }; + typedef SharedPtr<cConnectCallbacks> cConnectCallbacksPtr; + + + /** Callbacks used when listening for incoming connections as a server. */ + class cListenCallbacks + { + public: + // Force a virtual destructor for all descendants: + virtual ~cListenCallbacks() {} + + /** Called when the TCP server created with Listen() receives a new incoming connection. + Returns the link callbacks that the server should use for the newly created link. + If a nullptr is returned, the connection is dropped immediately; + otherwise a new cTCPLink instance is created and OnAccepted() is called. */ + virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) = 0; + + /** Called when the TCP server created with Listen() creates a new link for an incoming connection. + Provides the newly created Link that can be used for communication. + Called right after a successful OnIncomingConnection(). */ + virtual void OnAccepted(cTCPLink & a_Link) = 0; + + /** Called when the socket fails to listen on the specified port. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + }; + typedef SharedPtr<cListenCallbacks> cListenCallbacksPtr; + + + /** Callbacks used when resolving names to IPs. */ + class cResolveNameCallbacks + { + public: + // Force a virtual destructor for all descendants: + virtual ~cResolveNameCallbacks() {} + + /** Called when the hostname is successfully resolved into an IP address. + May be called multiple times if a name resolves to multiple addresses. + a_IP may be either an IPv4 or an IPv6 address with their proper formatting. */ + virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) = 0; + + /** Called when an error is encountered while resolving. + If an error is reported, the OnFinished() callback is not called. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + + /** Called when all the addresses resolved have been reported via the OnNameResolved() callback. + Only called if there was no error reported. */ + virtual void OnFinished(void) = 0; + }; + typedef SharedPtr<cResolveNameCallbacks> cResolveNameCallbacksPtr; + + + /** Queues a TCP connection to be made to the specified host. + Calls one the connection callbacks (success, error) when the connection is successfully established, or upon failure. + The a_LinkCallbacks is passed to the newly created cTCPLink. + Returns true if queueing was successful, false on failure to queue. + Note that the return value doesn't report the success of the actual connection; the connection is established asynchronously in the background. + Implemented in TCPLinkImpl.cpp. */ + static bool Connect( + const AString & a_Host, + UInt16 a_Port, + cConnectCallbacksPtr a_ConnectCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); + + + /** Opens up the specified port for incoming connections. + Calls an OnAccepted callback for each incoming connection. + A cTCPLink with the specified link callbacks is created for each connection. + Returns a cServerHandle that can be used to query the operation status and close the server. + Implemented in ServerHandleImpl.cpp. */ + static cServerHandlePtr Listen( + UInt16 a_Port, + cListenCallbacksPtr a_ListenCallbacks + ); + + + /** Queues a DNS query to resolve the specified hostname to IP address. + Calls one of the callbacks when the resolving succeeds, or when it fails. + Returns true if queueing was successful, false if not. + Note that the return value doesn't report the success of the actual lookup; the lookup happens asynchronously on the background. + Implemented in HostnameLookup.cpp. */ + static bool HostnameToIP( + const AString & a_Hostname, + cResolveNameCallbacksPtr a_Callbacks + ); + + + /** Queues a DNS query to resolve the specified IP address to a hostname. + Calls one of the callbacks when the resolving succeeds, or when it fails. + Returns true if queueing was successful, false if not. + Note that the return value doesn't report the success of the actual lookup; the lookup happens asynchronously on the background. + Implemented in IPLookup.cpp. */ + static bool IPToHostName( + const AString & a_IP, + cResolveNameCallbacksPtr a_Callbacks + ); +}; + + + + diff --git a/src/OSSupport/NetworkSingleton.cpp b/src/OSSupport/NetworkSingleton.cpp new file mode 100644 index 000000000..92f0604cd --- /dev/null +++ b/src/OSSupport/NetworkSingleton.cpp @@ -0,0 +1,245 @@ + +// NetworkSingleton.cpp + +// Implements the cNetworkSingleton class representing the storage for global data pertaining to network API +// such as a list of all connections, all listening sockets and the LibEvent dispatch thread. + +#include "Globals.h" +#include "NetworkSingleton.h" +#include <event2/event.h> +#include <event2/thread.h> +#include <event2/bufferevent.h> +#include <event2/dns.h> +#include <event2/listener.h> +#include "IPLookup.h" +#include "HostnameLookup.h" + + + + + +cNetworkSingleton::cNetworkSingleton(void) +{ + // Windows: initialize networking: + #ifdef _WIN32 + WSADATA wsaData; + memset(&wsaData, 0, sizeof(wsaData)); + int res = WSAStartup (MAKEWORD(2, 2), &wsaData); + if (res != 0) + { + int err = WSAGetLastError(); + LOGWARNING("WSAStartup failed: %d, WSAGLE = %d (%s)", res, err, evutil_socket_error_to_string(err)); + exit(1); + } + #endif // _WIN32 + + // Initialize LibEvent logging: + event_set_log_callback(LogCallback); + + // Initialize threading: + #if defined(EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED) + evthread_use_windows_threads(); + #elif defined(EVTHREAD_USE_PTHREADS_IMPLEMENTED) + evthread_use_pthreads(); + #else + #error No threading implemented for EVTHREAD + #endif + + // Create the main event_base: + m_EventBase = event_base_new(); + if (m_EventBase == nullptr) + { + LOGERROR("Failed to initialize LibEvent. The server will now terminate."); + abort(); + } + + // Create the DNS lookup helper: + m_DNSBase = evdns_base_new(m_EventBase, 1); + if (m_DNSBase == nullptr) + { + LOGERROR("Failed to initialize LibEvent's DNS subsystem. The server will now terminate."); + abort(); + } + + // Create the event loop thread: + std::thread EventLoopThread(RunEventLoop, this); + EventLoopThread.detach(); +} + + + + + +cNetworkSingleton::~cNetworkSingleton() +{ + // Wait for the LibEvent event loop to terminate: + event_base_loopbreak(m_EventBase); + m_EventLoopTerminated.Wait(); + + // Remove all objects: + { + cCSLock Lock(m_CS); + m_Connections.clear(); + m_Servers.clear(); + m_HostnameLookups.clear(); + m_IPLookups.clear(); + } + + // Free the underlying LibEvent objects: + evdns_base_free(m_DNSBase, true); + event_base_free(m_EventBase); + + libevent_global_shutdown(); +} + + + + + +cNetworkSingleton & cNetworkSingleton::Get(void) +{ + static cNetworkSingleton Instance; + return Instance; +} + + + + + +void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg) +{ + switch (a_Severity) + { + case _EVENT_LOG_DEBUG: LOGD ("LibEvent: %s", a_Msg); break; + case _EVENT_LOG_MSG: LOG ("LibEvent: %s", a_Msg); break; + case _EVENT_LOG_WARN: LOGWARNING("LibEvent: %s", a_Msg); break; + case _EVENT_LOG_ERR: LOGERROR ("LibEvent: %s", a_Msg); break; + default: + { + LOGWARNING("LibEvent: Unknown log severity (%d): %s", a_Severity, a_Msg); + break; + } + } +} + + + + + +void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self) +{ + event_base_loop(a_Self->m_EventBase, EVLOOP_NO_EXIT_ON_EMPTY); + a_Self->m_EventLoopTerminated.Set(); +} + + + + + +void cNetworkSingleton::AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup) +{ + cCSLock Lock(m_CS); + m_HostnameLookups.push_back(a_HostnameLookup); +} + + + + + +void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameLookup) +{ + cCSLock Lock(m_CS); + for (auto itr = m_HostnameLookups.begin(), end = m_HostnameLookups.end(); itr != end; ++itr) + { + if (itr->get() == a_HostnameLookup) + { + m_HostnameLookups.erase(itr); + return; + } + } // for itr - m_HostnameLookups[] +} + + + + + +void cNetworkSingleton::AddIPLookup(cIPLookupPtr a_IPLookup) +{ + cCSLock Lock(m_CS); + m_IPLookups.push_back(a_IPLookup); +} + + + + + +void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup) +{ + cCSLock Lock(m_CS); + for (auto itr = m_IPLookups.begin(), end = m_IPLookups.end(); itr != end; ++itr) + { + if (itr->get() == a_IPLookup) + { + m_IPLookups.erase(itr); + return; + } + } // for itr - m_IPLookups[] +} + + + + + +void cNetworkSingleton::AddLink(cTCPLinkImplPtr a_Link) +{ + cCSLock Lock(m_CS); + m_Connections.push_back(a_Link); +} + + + + + +void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link) +{ + cCSLock Lock(m_CS); + for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) + { + if (itr->get() == a_Link) + { + m_Connections.erase(itr); + return; + } + } // for itr - m_Connections[] +} + + + + + +void cNetworkSingleton::AddServer(cServerHandleImplPtr a_Server) +{ + cCSLock Lock(m_CS); + m_Servers.push_back(a_Server); +} + + + + + +void cNetworkSingleton::RemoveServer(const cServerHandleImpl * a_Server) +{ + cCSLock Lock(m_CS); + for (auto itr = m_Servers.begin(), end = m_Servers.end(); itr != end; ++itr) + { + if (itr->get() == a_Server) + { + m_Servers.erase(itr); + return; + } + } // for itr - m_Servers[] +} + + + + diff --git a/src/OSSupport/NetworkSingleton.h b/src/OSSupport/NetworkSingleton.h new file mode 100644 index 000000000..1d26fc8f4 --- /dev/null +++ b/src/OSSupport/NetworkSingleton.h @@ -0,0 +1,134 @@ + +// NetworkSingleton.h + +// Declares the cNetworkSingleton class representing the storage for global data pertaining to network API +// such as a list of all connections, all listening sockets and the LibEvent dispatch thread. + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include "CriticalSection.h" +#include "Event.h" + + + + + +// fwd: +struct event_base; +struct evdns_base; +class cTCPLinkImpl; +typedef SharedPtr<cTCPLinkImpl> cTCPLinkImplPtr; +typedef std::vector<cTCPLinkImplPtr> cTCPLinkImplPtrs; +class cServerHandleImpl; +typedef SharedPtr<cServerHandleImpl> cServerHandleImplPtr; +typedef std::vector<cServerHandleImplPtr> cServerHandleImplPtrs; +class cHostnameLookup; +typedef SharedPtr<cHostnameLookup> cHostnameLookupPtr; +typedef std::vector<cHostnameLookupPtr> cHostnameLookupPtrs; +class cIPLookup; +typedef SharedPtr<cIPLookup> cIPLookupPtr; +typedef std::vector<cIPLookupPtr> cIPLookupPtrs; + + + + + +class cNetworkSingleton +{ +public: + ~cNetworkSingleton(); + + /** Returns the singleton instance of this class */ + static cNetworkSingleton & Get(void); + + /** Returns the main LibEvent handle for event registering. */ + event_base * GetEventBase(void) { return m_EventBase; } + + /** Returns the LibEvent handle for DNS lookups. */ + evdns_base * GetDNSBase(void) { return m_DNSBase; } + + /** Adds the specified hostname lookup to m_HostnameLookups. + Used by the underlying lookup implementation when a new lookup is initiated. */ + void AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup); + + /** Removes the specified hostname lookup from m_HostnameLookups. + Used by the underlying lookup implementation when the lookup is finished. */ + void RemoveHostnameLookup(const cHostnameLookup * a_HostnameLookup); + + /** Adds the specified IP lookup to M_IPLookups. + Used by the underlying lookup implementation when a new lookup is initiated. */ + void AddIPLookup(cIPLookupPtr a_IPLookup); + + /** Removes the specified IP lookup from m_IPLookups. + Used by the underlying lookup implementation when the lookup is finished. */ + void RemoveIPLookup(const cIPLookup * a_IPLookup); + + /** Adds the specified link to m_Connections. + Used by the underlying link implementation when a new link is created. */ + void AddLink(cTCPLinkImplPtr a_Link); + + /** Removes the specified link from m_Connections. + Used by the underlying link implementation when the link is closed / errored. */ + void RemoveLink(const cTCPLinkImpl * a_Link); + + /** Adds the specified link to m_Servers. + Used by the underlying server handle implementation when a new listening server is created. + Only servers that succeed in listening are added. */ + void AddServer(cServerHandleImplPtr a_Server); + + /** Removes the specified server from m_Servers. + Used by the underlying server handle implementation when the server is closed. */ + void RemoveServer(const cServerHandleImpl * a_Server); + +protected: + + /** The main LibEvent container for driving the event loop. */ + event_base * m_EventBase; + + /** The LibEvent handle for doing DNS lookups. */ + evdns_base * m_DNSBase; + + /** Container for all client connections, including ones with pending-connect. */ + cTCPLinkImplPtrs m_Connections; + + /** Container for all servers that are currently active. */ + cServerHandleImplPtrs m_Servers; + + /** Container for all pending hostname lookups. */ + cHostnameLookupPtrs m_HostnameLookups; + + /** Container for all pending IP lookups. */ + cIPLookupPtrs m_IPLookups; + + /** Mutex protecting all containers against multithreaded access. */ + cCriticalSection m_CS; + + /** Event that gets signalled when the event loop terminates. */ + cEvent m_EventLoopTerminated; + + + /** Initializes the LibEvent internals. */ + cNetworkSingleton(void); + + /** Converts LibEvent-generated log events into log messages in MCS log. */ + static void LogCallback(int a_Severity, const char * a_Msg); + + /** Implements the thread that runs LibEvent's event dispatcher loop. */ + static void RunEventLoop(cNetworkSingleton * a_Self); +}; + + + + + + + + + diff --git a/src/OSSupport/Queue.h b/src/OSSupport/Queue.h index 82f221453..afbde1b0d 100644 --- a/src/OSSupport/Queue.h +++ b/src/OSSupport/Queue.h @@ -86,7 +86,7 @@ public: bool TryDequeueItem(ItemType & item) { cCSLock Lock(m_CS); - if (m_Contents.size() == 0) + if (m_Contents.empty()) { return false; } @@ -101,7 +101,7 @@ public: ItemType DequeueItem(void) { cCSLock Lock(m_CS); - while (m_Contents.size() == 0) + while (m_Contents.empty()) { cCSUnlock Unlock(Lock); m_evtAdded.Wait(); diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp new file mode 100644 index 000000000..ba38dbf2e --- /dev/null +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -0,0 +1,334 @@ + +// ServerHandleImpl.cpp + +// Implements the cServerHandleImpl class implementing the TCP server functionality + +#include "Globals.h" +#include "ServerHandleImpl.h" +#include "TCPLinkImpl.h" +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// Globals: + +static bool IsValidSocket(evutil_socket_t a_Socket) +{ + #ifdef _WIN32 + return (a_Socket != INVALID_SOCKET); + #else // _WIN32 + return (a_Socket >= 0); + #endif // else _WIN32 +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cServerHandleImpl: + +cServerHandleImpl::cServerHandleImpl(cNetwork::cListenCallbacksPtr a_ListenCallbacks): + m_ListenCallbacks(a_ListenCallbacks), + m_ConnListener(nullptr), + m_SecondaryConnListener(nullptr), + m_IsListening(false), + m_ErrorCode(0) +{ +} + + + + + +cServerHandleImpl::~cServerHandleImpl() +{ + if (m_ConnListener != nullptr) + { + evconnlistener_free(m_ConnListener); + } + if (m_SecondaryConnListener != nullptr) + { + evconnlistener_free(m_SecondaryConnListener); + } +} + + + + + +void cServerHandleImpl::Close(void) +{ + // Stop the listener sockets: + evconnlistener_disable(m_ConnListener); + if (m_SecondaryConnListener != nullptr) + { + evconnlistener_disable(m_SecondaryConnListener); + } + m_IsListening = false; + + // Shutdown all connections: + cTCPLinkImplPtrs Conns; + { + cCSLock Lock(m_CS); + std::swap(Conns, m_Connections); + } + for (auto conn: Conns) + { + conn->Shutdown(); + } + + // Remove the ptr to self, so that the object may be freed: + m_SelfPtr.reset(); +} + + + + + +cServerHandleImplPtr cServerHandleImpl::Listen( + UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks +) +{ + cServerHandleImplPtr res = cServerHandleImplPtr{new cServerHandleImpl(a_ListenCallbacks)}; + res->m_SelfPtr = res; + if (res->Listen(a_Port)) + { + cNetworkSingleton::Get().AddServer(res); + } + else + { + a_ListenCallbacks->OnError(res->m_ErrorCode, res->m_ErrorMsg); + res->m_SelfPtr.reset(); + } + return res; +} + + + + + +bool cServerHandleImpl::Listen(UInt16 a_Port) +{ + // Make sure the cNetwork internals are innitialized: + cNetworkSingleton::Get(); + + // Set up the main socket: + // It should listen on IPv6 with IPv4 fallback, when available; IPv4 when IPv6 is not available. + bool NeedsTwoSockets = false; + int err; + evutil_socket_t MainSock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(MainSock)) + { + // Failed to create IPv6 socket, create an IPv4 one instead: + err = EVUTIL_SOCKET_ERROR(); + LOGD("Failed to create IPv6 MainSock: %d (%s)", err, evutil_socket_error_to_string(err)); + MainSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(MainSock)) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot create socket for port %d: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); + return false; + } + + // Bind to all interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(a_Port); + if (bind(MainSock, reinterpret_cast<const sockaddr *>(&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<const char *>(&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<const char *>(&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<const sockaddr *>(&name), sizeof(name)) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot bind IPv6 socket to port %d: %d (%s)", a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)); + evutil_closesocket(MainSock); + return false; + } + } + if (evutil_make_socket_nonblocking(MainSock) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot make socket on port %d non-blocking: %d (%s)", a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)); + evutil_closesocket(MainSock); + return false; + } + if (listen(MainSock, 0) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot listen on port %d: %d (%s)", a_Port, m_ErrorCode, evutil_socket_error_to_string(m_ErrorCode)); + evutil_closesocket(MainSock); + return false; + } + m_ConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, MainSock); + m_IsListening = true; + if (!NeedsTwoSockets) + { + return true; + } + + // If a secondary socket is required (WinXP dual-stack), create it here: + LOGD("Creating a second socket for IPv4"); + evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(SecondSock)) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("socket(AF_INET, ...) failed for secondary socket: %d, %s", err, evutil_socket_error_to_string(err)); + return true; // Report as success, the primary socket is working + } + + // Make the secondary socket nonblocking: + if (evutil_make_socket_nonblocking(SecondSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("evutil_make_socket_nonblocking() failed for secondary socket: %d, %s", err, evutil_socket_error_to_string(err)); + evutil_closesocket(SecondSock); + return true; // Report as success, the primary socket is working + } + + // Bind to all IPv4 interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(a_Port); + if (bind(SecondSock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("Cannot bind secondary socket to port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(SecondSock); + return true; // Report as success, the primary socket is working + } + + if (listen(SecondSock, 0) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("Cannot listen on on secondary socket on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(SecondSock); + return true; // Report as success, the primary socket is working + } + + m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); + return true; +} + + + + + +void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_Socket, sockaddr * a_Addr, int a_Len, void * a_Self) +{ + // Cast to true self: + cServerHandleImpl * Self = reinterpret_cast<cServerHandleImpl *>(a_Self); + ASSERT(Self != nullptr); + ASSERT(Self->m_SelfPtr != nullptr); + + // Get the textual IP address and port number out of a_Addr: + char IPAddress[128]; + evutil_inet_ntop(a_Addr->sa_family, a_Addr->sa_data, IPAddress, ARRAYCOUNT(IPAddress)); + UInt16 Port = 0; + switch (a_Addr->sa_family) + { + case AF_INET: + { + sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(a_Addr); + Port = ntohs(sin->sin_port); + break; + } + case AF_INET6: + { + sockaddr_in6 * sin6 = reinterpret_cast<sockaddr_in6 *>(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<cTCPLinkImpl>(a_Socket, LinkCallbacks, Self->m_SelfPtr, a_Addr, static_cast<socklen_t>(a_Len)); + { + cCSLock Lock(Self->m_CS); + Self->m_Connections.push_back(Link); + } // Lock(m_CS) + LinkCallbacks->OnLinkCreated(Link); + Link->Enable(Link); + + // Call the OnAccepted callback: + Self->m_ListenCallbacks->OnAccepted(*Link); +} + + + + + +void cServerHandleImpl::RemoveLink(const cTCPLinkImpl * a_Link) +{ + cCSLock Lock(m_CS); + for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) + { + if (itr->get() == a_Link) + { + m_Connections.erase(itr); + return; + } + } // for itr - m_Connections[] +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +cServerHandlePtr cNetwork::Listen( + UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks +) +{ + return cServerHandleImpl::Listen(a_Port, a_ListenCallbacks); +} + + + + + diff --git a/src/OSSupport/ServerHandleImpl.h b/src/OSSupport/ServerHandleImpl.h new file mode 100644 index 000000000..dbb18fc6d --- /dev/null +++ b/src/OSSupport/ServerHandleImpl.h @@ -0,0 +1,105 @@ + +// ServerHandleImpl.h + +// Declares the cServerHandleImpl class implementing the TCP server functionality + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include <event2/listener.h> +#include "CriticalSection.h" + + + + + +// fwd: +class cTCPLinkImpl; +typedef SharedPtr<cTCPLinkImpl> cTCPLinkImplPtr; +typedef std::vector<cTCPLinkImplPtr> cTCPLinkImplPtrs; +class cServerHandleImpl; +typedef SharedPtr<cServerHandleImpl> cServerHandleImplPtr; +typedef std::vector<cServerHandleImplPtr> cServerHandleImplPtrs; + + + + + +class cServerHandleImpl: + public cServerHandle +{ + typedef cServerHandle super; + friend class cTCPLinkImpl; + +public: + /** Closes the server, dropping all the connections. */ + ~cServerHandleImpl(); + + /** Creates a new server instance listening on the specified port. + Both IPv4 and IPv6 interfaces are used, if possible. + Always returns a server instance; in the event of a failure, the instance holds the error details. Use IsListening() to query success. */ + static cServerHandleImplPtr Listen( + UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks + ); + + // cServerHandle overrides: + virtual void Close(void) override; + virtual bool IsListening(void) const override { return m_IsListening; } + +protected: + /** The callbacks used to notify about incoming connections. */ + cNetwork::cListenCallbacksPtr m_ListenCallbacks; + + /** The LibEvent handle representing the main listening socket. */ + evconnlistener * m_ConnListener; + + /** The LibEvent handle representing the secondary listening socket (only when side-by-side listening is needed, such as WinXP). */ + evconnlistener * m_SecondaryConnListener; + + /** Set to true when the server is initialized successfully and is listening for incoming connections. */ + bool m_IsListening; + + /** Container for all currently active connections on this server. */ + cTCPLinkImplPtrs m_Connections; + + /** Mutex protecting m_Connections againt multithreaded access. */ + cCriticalSection m_CS; + + /** Contains the error code for the failure to listen. Only valid for non-listening instances. */ + int m_ErrorCode; + + /** Contains the error message for the failure to listen. Only valid for non-listening instances. */ + AString m_ErrorMsg; + + /** The SharedPtr to self, so that it can be passed to created links. */ + cServerHandleImplPtr m_SelfPtr; + + + + /** Creates a new instance with the specified callbacks. + Initializes the internals, but doesn't start listening yet. */ + cServerHandleImpl(cNetwork::cListenCallbacksPtr a_ListenCallbacks); + + /** Starts listening on the specified port. + Returns true if successful, false on failure. On failure, sets m_ErrorCode and m_ErrorMsg. */ + bool Listen(UInt16 a_Port); + + /** The callback called by LibEvent upon incoming connection. */ + static void Callback(evconnlistener * a_Listener, evutil_socket_t a_Socket, sockaddr * a_Addr, int a_Len, void * a_Self); + + /** Removes the specified link from m_Connections. + Called by cTCPLinkImpl when the link is terminated. */ + void RemoveLink(const cTCPLinkImpl * a_Link); +}; + + + + + diff --git a/src/OSSupport/StackTrace.cpp b/src/OSSupport/StackTrace.cpp index a56568457..015a53ba0 100644 --- a/src/OSSupport/StackTrace.cpp +++ b/src/OSSupport/StackTrace.cpp @@ -34,7 +34,7 @@ void PrintStackTrace(void) // Use the backtrace() function to get and output the stackTrace: // Code adapted from http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes void * stackTrace[30]; - size_t numItems = backtrace(stackTrace, ARRAYCOUNT(stackTrace)); + int numItems = backtrace(stackTrace, ARRAYCOUNT(stackTrace)); backtrace_symbols_fd(stackTrace, numItems, STDERR_FILENO); #endif } diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp new file mode 100644 index 000000000..b4cefa60c --- /dev/null +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -0,0 +1,331 @@ + +// TCPLinkImpl.cpp + +// Implements the cTCPLinkImpl class implementing the TCP link functionality + +#include "Globals.h" +#include "TCPLinkImpl.h" +#include "NetworkSingleton.h" +#include "ServerHandleImpl.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cTCPLinkImpl: + +cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): + super(a_LinkCallbacks), + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE)) +{ +} + + + + + +cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImplPtr a_Server, const sockaddr * a_Address, socklen_t a_AddrLen): + super(a_LinkCallbacks), + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE)), + m_Server(a_Server) +{ + // Update the endpoint addresses: + UpdateLocalAddress(); + UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort); +} + + + + + +cTCPLinkImpl::~cTCPLinkImpl() +{ + bufferevent_free(m_BufferEvent); +} + + + + + +cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTCPLink::cCallbacksPtr a_LinkCallbacks, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks) +{ + ASSERT(a_LinkCallbacks != nullptr); + ASSERT(a_ConnectCallbacks != nullptr); + + // Create a new link: + cTCPLinkImplPtr res{new cTCPLinkImpl(a_LinkCallbacks)}; // Cannot use std::make_shared here, constructor is not accessible + res->m_ConnectCallbacks = a_ConnectCallbacks; + cNetworkSingleton::Get().AddLink(res); + res->m_Callbacks->OnLinkCreated(res); + res->Enable(res); + + // If a_Host is an IP address, schedule a connection immediately: + sockaddr_storage sa; + int salen = static_cast<int>(sizeof(sa)); + if (evutil_parse_sockaddr_port(a_Host.c_str(), reinterpret_cast<sockaddr *>(&sa), &salen) == 0) + { + // Insert the correct port: + if (sa.ss_family == AF_INET6) + { + reinterpret_cast<sockaddr_in6 *>(&sa)->sin6_port = htons(a_Port); + } + else + { + reinterpret_cast<sockaddr_in *>(&sa)->sin_port = htons(a_Port); + } + + // Queue the connect request: + if (bufferevent_socket_connect(res->m_BufferEvent, reinterpret_cast<sockaddr *>(&sa), salen) == 0) + { + // Success + return res; + } + // Failure + cNetworkSingleton::Get().RemoveLink(res.get()); + return nullptr; + } + + // a_Host is a hostname, connect after a lookup: + if (bufferevent_socket_connect_hostname(res->m_BufferEvent, cNetworkSingleton::Get().GetDNSBase(), AF_UNSPEC, a_Host.c_str(), a_Port) == 0) + { + // Success + return res; + } + // Failure + cNetworkSingleton::Get().RemoveLink(res.get()); + return nullptr; +} + + + + + +void cTCPLinkImpl::Enable(cTCPLinkImplPtr a_Self) +{ + // Take hold of a shared copy of self, to keep as long as the callbacks are coming: + m_Self = a_Self; + + // Set the LibEvent callbacks and enable processing: + bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); + bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); +} + + + + + +bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) +{ + return (bufferevent_write(m_BufferEvent, a_Data, a_Length) == 0); +} + + + + + +void cTCPLinkImpl::Shutdown(void) +{ + #ifdef _WIN32 + shutdown(bufferevent_getfd(m_BufferEvent), SD_SEND); + #else + shutdown(bufferevent_getfd(m_BufferEvent), SHUT_WR); + #endif + bufferevent_disable(m_BufferEvent, EV_WRITE); +} + + + + + +void cTCPLinkImpl::Close(void) +{ + // Disable all events on the socket, but keep it alive: + bufferevent_disable(m_BufferEvent, EV_READ | EV_WRITE); + if (m_Server == nullptr) + { + cNetworkSingleton::Get().RemoveLink(this); + } + else + { + m_Server->RemoveLink(this); + } + m_Self.reset(); +} + + + + + + +void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self) +{ + ASSERT(a_Self != nullptr); + cTCPLinkImpl * Self = static_cast<cTCPLinkImpl *>(a_Self); + ASSERT(Self->m_Callbacks != nullptr); + + // Read all the incoming data, in 1024-byte chunks: + char data[1024]; + size_t length; + while ((length = bufferevent_read(a_BufferEvent, data, sizeof(data))) > 0) + { + Self->m_Callbacks->OnReceivedData(data, length); + } +} + + + + + +void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) +{ + ASSERT(a_Self != nullptr); + cTCPLinkImplPtr Self = static_cast<cTCPLinkImpl *>(a_Self)->m_Self; + + // If an error is reported, call the error callback: + if (a_What & BEV_EVENT_ERROR) + { + // Choose the proper callback to call based on whether we were waiting for connection or not: + int err = EVUTIL_SOCKET_ERROR(); + if (Self->m_ConnectCallbacks != nullptr) + { + if (err == 0) + { + // This could be a DNS failure + err = bufferevent_socket_get_dns_error(a_BufferEvent); + } + Self->m_ConnectCallbacks->OnError(err, evutil_socket_error_to_string(err)); + } + else + { + Self->m_Callbacks->OnError(err, evutil_socket_error_to_string(err)); + if (Self->m_Server == nullptr) + { + cNetworkSingleton::Get().RemoveLink(Self.get()); + } + else + { + Self->m_Server->RemoveLink(Self.get()); + } + } + Self->m_Self.reset(); + return; + } + + // Pending connection succeeded, call the connection callback: + if (a_What & BEV_EVENT_CONNECTED) + { + if (Self->m_ConnectCallbacks != nullptr) + { + Self->m_ConnectCallbacks->OnConnected(*Self); + // Reset the connect callbacks so that later errors get reported through the link callbacks: + Self->m_ConnectCallbacks.reset(); + return; + } + Self->UpdateLocalAddress(); + Self->UpdateRemoteAddress(); + } + + // If the connection has been closed, call the link callback and remove the connection: + if (a_What & BEV_EVENT_EOF) + { + Self->m_Callbacks->OnRemoteClosed(); + if (Self->m_Server != nullptr) + { + Self->m_Server->RemoveLink(Self.get()); + } + else + { + cNetworkSingleton::Get().RemoveLink(Self.get()); + } + Self->m_Self.reset(); + return; + } + + // Unknown event, report it: + LOGWARNING("cTCPLinkImpl: Unhandled LibEvent event %d (0x%x)", a_What, a_What); + ASSERT(!"cTCPLinkImpl: Unhandled LibEvent event"); +} + + + + + +void cTCPLinkImpl::UpdateAddress(const sockaddr * a_Address, socklen_t a_AddrLen, AString & a_IP, UInt16 & a_Port) +{ + // Based on the family specified in the address, use the correct datastructure to convert to IP string: + char IP[128]; + switch (a_Address->sa_family) + { + case AF_INET: // IPv4: + { + const sockaddr_in * sin = reinterpret_cast<const sockaddr_in *>(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<const sockaddr_in6 *>(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<socklen_t>(sizeof(sa)); + getsockname(bufferevent_getfd(m_BufferEvent), reinterpret_cast<sockaddr *>(&sa), &salen); + UpdateAddress(reinterpret_cast<const sockaddr *>(&sa), salen, m_LocalIP, m_LocalPort); +} + + + + + +void cTCPLinkImpl::UpdateRemoteAddress(void) +{ + sockaddr_storage sa; + socklen_t salen = static_cast<socklen_t>(sizeof(sa)); + getpeername(bufferevent_getfd(m_BufferEvent), reinterpret_cast<sockaddr *>(&sa), &salen); + UpdateAddress(reinterpret_cast<const sockaddr *>(&sa), salen, m_RemoteIP, m_RemotePort); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +bool cNetwork::Connect( + const AString & a_Host, + UInt16 a_Port, + cNetwork::cConnectCallbacksPtr a_ConnectCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + // Add a connection request to the queue: + cTCPLinkImplPtr Conn = cTCPLinkImpl::Connect(a_Host, a_Port, a_LinkCallbacks, a_ConnectCallbacks); + return (Conn != nullptr); +} + + + + + diff --git a/src/OSSupport/TCPLinkImpl.h b/src/OSSupport/TCPLinkImpl.h new file mode 100644 index 000000000..735e8ed9d --- /dev/null +++ b/src/OSSupport/TCPLinkImpl.h @@ -0,0 +1,122 @@ + +// TCPLinkImpl.h + +// Declares the cTCPLinkImpl class implementing the TCP link functionality + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include <event2/event.h> +#include <event2/bufferevent.h> + + + + + +// fwd: +class cServerHandleImpl; +typedef SharedPtr<cServerHandleImpl> cServerHandleImplPtr; +class cTCPLinkImpl; +typedef SharedPtr<cTCPLinkImpl> cTCPLinkImplPtr; +typedef std::vector<cTCPLinkImplPtr> cTCPLinkImplPtrs; + + + + + +class cTCPLinkImpl: + public cTCPLink +{ + typedef cTCPLink super; + +public: + /** Creates a new link based on the given socket. + Used for connections accepted in a server using cNetwork::Listen(). + a_Address and a_AddrLen describe the remote peer that has connected. + The link is created disabled, you need to call Enable() to start the regular communication. */ + cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImplPtr a_Server, const sockaddr * a_Address, socklen_t a_AddrLen); + + /** Destroys the LibEvent handle representing the link. */ + ~cTCPLinkImpl(); + + /** Queues a connection request to the specified host. + a_ConnectCallbacks must be valid. + Returns a link that has the connection request queued, or NULL for failure. */ + static cTCPLinkImplPtr Connect(const AString & a_Host, UInt16 a_Port, cTCPLink::cCallbacksPtr a_LinkCallbacks, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks); + + /** Enables communication over the link. + Links are created with communication disabled, so that creation callbacks can be called first. + This function then enables the regular communication to be reported. + The a_Self parameter is used so that the socket can keep itself alive as long as the callbacks are coming. */ + void Enable(cTCPLinkImplPtr a_Self); + + // cTCPLink overrides: + virtual bool Send(const void * a_Data, size_t a_Length) override; + virtual AString GetLocalIP(void) const override { return m_LocalIP; } + virtual UInt16 GetLocalPort(void) const override { return m_LocalPort; } + virtual AString GetRemoteIP(void) const override { return m_RemoteIP; } + virtual UInt16 GetRemotePort(void) const override { return m_RemotePort; } + virtual void Shutdown(void) override; + virtual void Close(void) override; + +protected: + + /** Callbacks to call when the connection is established. + May be NULL if not used. Only used for outgoing connections (cNetwork::Connect()). */ + cNetwork::cConnectCallbacksPtr m_ConnectCallbacks; + + /** The LibEvent handle representing this connection. */ + bufferevent * m_BufferEvent; + + /** The server handle that has created this link. + Only valid for incoming connections, nullptr for outgoing connections. */ + cServerHandleImplPtr m_Server; + + /** The IP address of the local endpoint. Valid only after the socket has been connected. */ + AString m_LocalIP; + + /** The port of the local endpoint. Valid only after the socket has been connected. */ + UInt16 m_LocalPort; + + /** The IP address of the remote endpoint. Valid only after the socket has been connected. */ + AString m_RemoteIP; + + /** The port of the remote endpoint. Valid only after the socket has been connected. */ + UInt16 m_RemotePort; + + /** SharedPtr to self, used to keep this object alive as long as the callbacks are coming. + Initialized in Enable(), cleared in Close() and EventCallback(RemoteClosed). */ + cTCPLinkImplPtr m_Self; + + + /** Creates a new link to be queued to connect to a specified host:port. + Used for outgoing connections created using cNetwork::Connect(). + To be used only by the Connect() factory function. + The link is created disabled, you need to call Enable() to start the regular communication. */ + cTCPLinkImpl(const cCallbacksPtr a_LinkCallbacks); + + /** Callback that LibEvent calls when there's data available from the remote peer. */ + static void ReadCallback(bufferevent * a_BufferEvent, void * a_Self); + + /** Callback that LibEvent calls when there's a non-data-related event on the socket. */ + static void EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self); + + /** Sets a_IP and a_Port to values read from a_Address, based on the correct address family. */ + static void UpdateAddress(const sockaddr * a_Address, socklen_t a_AddrLen, AString & a_IP, UInt16 & a_Port); + + /** Updates m_LocalIP and m_LocalPort based on the metadata read from the socket. */ + void UpdateLocalAddress(void); + + /** Updates m_RemoteIP and m_RemotePort based on the metadata read from the socket. */ + void UpdateRemoteAddress(void); +}; + + + + |