summaryrefslogtreecommitdiffstats
path: root/src/OSSupport
diff options
context:
space:
mode:
Diffstat (limited to 'src/OSSupport')
-rw-r--r--src/OSSupport/CMakeLists.txt11
-rw-r--r--src/OSSupport/CriticalSection.cpp1
-rw-r--r--src/OSSupport/HostnameLookup.cpp124
-rw-r--r--src/OSSupport/HostnameLookup.h47
-rw-r--r--src/OSSupport/IPLookup.cpp111
-rw-r--r--src/OSSupport/IPLookup.h49
-rw-r--r--src/OSSupport/Network.h246
-rw-r--r--src/OSSupport/NetworkSingleton.cpp245
-rw-r--r--src/OSSupport/NetworkSingleton.h134
-rw-r--r--src/OSSupport/ServerHandleImpl.cpp334
-rw-r--r--src/OSSupport/ServerHandleImpl.h105
-rw-r--r--src/OSSupport/TCPLinkImpl.cpp331
-rw-r--r--src/OSSupport/TCPLinkImpl.h122
13 files changed, 1860 insertions, 0 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/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/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/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);
+};
+
+
+
+