summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt17
m---------lib/libevent0
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/Globals.h26
-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
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/Network/CMakeLists.txt60
-rw-r--r--tests/Network/EchoServer.cpp132
-rw-r--r--tests/Network/Google.cpp118
-rw-r--r--tests/Network/NameLookup.cpp80
23 files changed, 2292 insertions, 8 deletions
diff --git a/.gitmodules b/.gitmodules
index d2ce2c855..93fac9d1f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -25,3 +25,6 @@
[submodule "lib/SQLiteCpp"]
path = lib/SQLiteCpp
url = https://github.com/mc-server/SQLiteCpp.git
+[submodule "lib/libevent"]
+ path = lib/libevent
+ url = https://github.com/mc-server/libevent.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 493cecdb3..5b3fd5e7d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -95,6 +95,13 @@ set(SQLITECPP_BUILD_EXAMPLES OFF CACHE BOOL "Build examples."
set(SQLITECPP_BUILD_TESTS OFF CACHE BOOL "Build and run tests." FORCE)
set(SQLITECPP_INTERNAL_SQLITE OFF CACHE BOOL "Add the internal SQLite3 source to the project." FORCE)
+# Set options for LibEvent, disable all their tests and benchmarks:
+set(EVENT__DISABLE_OPENSSL YES CACHE BOOL "Disable OpenSSL in LibEvent" FORCE)
+set(EVENT__DISABLE_BENCHMARK YES CACHE BOOL "Disable LibEvent benchmarks" FORCE)
+set(EVENT__DISABLE_TESTS YES CACHE BOOL "Disable LibEvent tests" FORCE)
+set(EVENT__DISABLE_REGRESS YES CACHE BOOL "Disable LibEvent regression tests" FORCE)
+set(EVENT__DISABLE_SAMPLES YES CACHE BOOL "Disable LibEvent samples" FORCE)
+
# Check that the libraries are present:
if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/SQLiteCpp/CMakeLists.txt)
message(FATAL_ERROR "SQLiteCpp is missing in folder lib/SQLiteCpp. Have you initialized the submodules / downloaded the extra libraries?")
@@ -102,6 +109,9 @@ endif()
if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/polarssl/CMakeLists.txt)
message(FATAL_ERROR "PolarSSL is missing in folder lib/polarssl. Have you initialized the submodules / downloaded the extra libraries?")
endif()
+if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/libevent/CMakeLists.txt)
+ message(FATAL_ERROR "LibEvent is missing in folder lib/libevent. Have you initialized and updated the submodules / downloaded the extra libraries?")
+endif()
# Include all the libraries:
add_subdirectory(lib/jsoncpp/)
@@ -112,6 +122,7 @@ add_subdirectory(lib/sqlite/)
add_subdirectory(lib/SQLiteCpp/)
add_subdirectory(lib/expat/)
add_subdirectory(lib/luaexpat/)
+add_subdirectory(lib/libevent/)
# Add proper include directories so that SQLiteCpp can find SQLite3:
get_property(SQLITECPP_INCLUDES DIRECTORY "lib/SQLiteCpp/" PROPERTY INCLUDE_DIRECTORIES)
@@ -119,6 +130,9 @@ set(SQLITECPP_INCLUDES "${SQLITECPP_INCLUDES}" "${CMAKE_CURRENT_SOURCE_DIR}/lib/
set_property(DIRECTORY lib/SQLiteCpp/ PROPERTY INCLUDE_DIRECTORIES "${SQLITECPP_INCLUDES}")
set_property(TARGET SQLiteCpp PROPERTY INCLUDE_DIRECTORIES "${SQLITECPP_INCLUDES}")
+# Add proper includes for LibEvent's event-config.h header:
+include_directories(SYSTEM ${LIBEVENT_INCLUDE_DIRS})
+
if (WIN32)
add_subdirectory(lib/luaproxy/)
endif()
@@ -126,13 +140,14 @@ endif()
# We use EXCLUDE_FROM_ALL so that only the explicit dependencies are used
# (PolarSSL also has test and example programs in their CMakeLists.txt, we don't want those)
-include(lib/polarssl.cmake)
+include(lib/polarssl.cmake EXCLUDE_FROM_ALL)
set_exe_flags()
add_subdirectory (src)
if(${SELF_TEST})
+ message("Tests enabled")
enable_testing()
add_subdirectory (tests)
endif()
diff --git a/lib/libevent b/lib/libevent
new file mode 160000
+Subproject 0b49ae34594533daa82c06a506078de9e336a01
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 997326cc7..c39f5f6e6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -5,6 +5,7 @@ project (MCServer)
include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/")
include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/jsoncpp/include")
include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/polarssl/include")
+include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/libevent/include")
set(FOLDERS
OSSupport HTTPServer Items Blocks Protocol Generating PolarSSL++ Bindings
@@ -323,4 +324,4 @@ endif ()
if (WIN32)
target_link_libraries(${EXECUTABLE} expat tolualib ws2_32.lib Psapi.lib)
endif()
-target_link_libraries(${EXECUTABLE} luaexpat jsoncpp polarssl zlib sqlite lua SQLiteCpp)
+target_link_libraries(${EXECUTABLE} luaexpat jsoncpp polarssl zlib sqlite lua SQLiteCpp event_core event_extra)
diff --git a/src/Globals.h b/src/Globals.h
index f0726454b..654ede95f 100644
--- a/src/Globals.h
+++ b/src/Globals.h
@@ -268,33 +268,47 @@ template class SizeChecker<UInt16, 2>;
#include "OSSupport/StackTrace.h"
#else
// Logging functions
-void inline LOGERROR(const char* a_Format, ...) FORMATSTRING(1, 2);
+void inline LOGERROR(const char * a_Format, ...) FORMATSTRING(1, 2);
-void inline LOGERROR(const char* a_Format, ...)
+void inline LOGERROR(const char * a_Format, ...)
{
va_list argList;
va_start(argList, a_Format);
vprintf(a_Format, argList);
+ putchar('\n');
va_end(argList);
}
-void inline LOGWARNING(const char* a_Format, ...) FORMATSTRING(1, 2);
+void inline LOGWARNING(const char * a_Format, ...) FORMATSTRING(1, 2);
-void inline LOGWARNING(const char* a_Format, ...)
+void inline LOGWARNING(const char * a_Format, ...)
{
va_list argList;
va_start(argList, a_Format);
vprintf(a_Format, argList);
+ putchar('\n');
va_end(argList);
}
-void inline LOGD(const char* a_Format, ...) FORMATSTRING(1, 2);
+void inline LOGD(const char * a_Format, ...) FORMATSTRING(1, 2);
-void inline LOGD(const char* a_Format, ...)
+void inline LOGD(const char * a_Format, ...)
{
va_list argList;
va_start(argList, a_Format);
vprintf(a_Format, argList);
+ putchar('\n');
+ va_end(argList);
+}
+
+void inline LOG(const char * a_Format, ...) FORMATSTRING(1, 2);
+
+void inline LOG(const char * a_Format, ...)
+{
+ va_list argList;
+ va_start(argList, a_Format);
+ vprintf(a_Format, argList);
+ putchar('\n');
va_end(argList);
}
diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt
index e943ceb18..9424b63da 100644
--- a/src/OSSupport/CMakeLists.txt
+++ b/src/OSSupport/CMakeLists.txt
@@ -10,12 +10,17 @@ SET (SRCS
Event.cpp
File.cpp
GZipFile.cpp
+ HostnameLookup.cpp
+ IPLookup.cpp
IsThread.cpp
ListenThread.cpp
+ NetworkSingleton.cpp
Semaphore.cpp
+ ServerHandleImpl.cpp
Socket.cpp
SocketThreads.cpp
StackTrace.cpp
+ TCPLinkImpl.cpp
)
SET (HDRS
@@ -24,13 +29,19 @@ SET (HDRS
Event.h
File.h
GZipFile.h
+ HostnameLookup.h
+ IPLookup.h
IsThread.h
ListenThread.h
+ Network.h
+ NetworkSingleton.h
Queue.h
Semaphore.h
+ ServerHandleImpl.h
Socket.h
SocketThreads.h
StackTrace.h
+ TCPLinkImpl.h
)
if(NOT MSVC)
diff --git a/src/OSSupport/CriticalSection.cpp b/src/OSSupport/CriticalSection.cpp
index 13a3e4d9f..5248356c5 100644
--- a/src/OSSupport/CriticalSection.cpp
+++ b/src/OSSupport/CriticalSection.cpp
@@ -1,5 +1,6 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+#include "CriticalSection.h"
diff --git a/src/OSSupport/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp
new file mode 100644
index 000000000..3a2997ffd
--- /dev/null
+++ b/src/OSSupport/HostnameLookup.cpp
@@ -0,0 +1,124 @@
+
+// HostnameLookup.cpp
+
+// Implements the cHostnameLookup class representing an in-progress hostname-to-IP lookup
+
+#include "Globals.h"
+#include "HostnameLookup.h"
+#include <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);
+};
+
+
+
+
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 1fbd88f04..265640cc8 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -5,3 +5,4 @@ enable_testing()
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(ChunkData)
+add_subdirectory(Network)
diff --git a/tests/Network/CMakeLists.txt b/tests/Network/CMakeLists.txt
new file mode 100644
index 000000000..c0af50e2c
--- /dev/null
+++ b/tests/Network/CMakeLists.txt
@@ -0,0 +1,60 @@
+cmake_minimum_required (VERSION 2.6)
+
+enable_testing()
+
+include_directories(${CMAKE_SOURCE_DIR}/src/)
+include_directories(${CMAKE_SOURCE_DIR}/lib/libevent/include)
+
+add_definitions(-DTEST_GLOBALS=1)
+
+# Create a single Network library that contains all the networking code:
+set (Network_SRCS
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/HostnameLookup.cpp
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/IPLookup.cpp
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/NetworkSingleton.cpp
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/ServerHandleImpl.cpp
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/TCPLinkImpl.cpp
+ ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp
+)
+
+set (Network_HDRS
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.h
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.h
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/HostnameLookup.h
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/IPLookup.h
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.h
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/NetworkSingleton.h
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/ServerHandleImpl.h
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/TCPLinkImpl.h
+ ${CMAKE_SOURCE_DIR}/src/StringUtils.h
+)
+
+add_library(Network
+ ${Network_SRCS}
+ ${Network_HDRS}
+)
+
+target_link_libraries(Network event_core event_extra)
+if (MSVC)
+ target_link_libraries(Network ws2_32.lib)
+endif()
+
+
+
+
+# Define individual tests:
+
+# Google: download the google.com frontpage using http client socket:
+add_executable(Google-exe Google.cpp)
+target_link_libraries(Google-exe Network)
+add_test(NAME Google-test COMMAND Google-exe)
+
+# EchoServer: Listen on port 9876, echo everything back:
+add_executable(EchoServer EchoServer.cpp)
+target_link_libraries(EchoServer Network)
+
+# NameLookup: Lookup hostname-to-IP and IP-to-hostname:
+add_executable(NameLookup NameLookup.cpp)
+target_link_libraries(NameLookup Network)
diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp
new file mode 100644
index 000000000..728db0b7c
--- /dev/null
+++ b/tests/Network/EchoServer.cpp
@@ -0,0 +1,132 @@
+
+// EchoServer.cpp
+
+// Implements an Echo server using the LibEvent-based cNetwork API, as a test of that API
+
+#include "Globals.h"
+#include <iostream>
+#include <string>
+#include "OSSupport/Network.h"
+
+
+
+
+
+/** cTCPLink callbacks that echo everything they receive back to the remote peer. */
+class cEchoLinkCallbacks:
+ public cTCPLink::cCallbacks
+{
+ // cTCPLink::cCallbacks overrides:
+ virtual void OnLinkCreated(cTCPLinkPtr a_Link) override
+ {
+ ASSERT(m_Link == nullptr);
+ m_Link = a_Link;
+ }
+
+
+ virtual void OnReceivedData(const char * a_Data, size_t a_Size) override
+ {
+ ASSERT(m_Link != nullptr);
+
+ // Echo the incoming data back to outgoing data:
+ LOGD("%p (%s:%d): Data received (%u bytes), echoing back.", m_Link.get(), m_Link->GetRemoteIP().c_str(), m_Link->GetRemotePort(), static_cast<unsigned>(a_Size));
+ m_Link->Send(a_Data, a_Size);
+ LOGD("Echo queued");
+
+ // Search for a Ctrl+Z, if found, drop the connection:
+ for (size_t i = 0; i < a_Size; i++)
+ {
+ if (a_Data[i] == '\x1a')
+ {
+ m_Link->Close();
+ m_Link.reset();
+ return;
+ }
+ }
+ }
+
+
+ virtual void OnRemoteClosed(void) override
+ {
+ ASSERT(m_Link != nullptr);
+
+ LOGD("%p (%s:%d): Remote has closed the connection.", m_Link.get(), m_Link->GetRemoteIP().c_str(), m_Link->GetRemotePort());
+ m_Link.reset();
+ }
+
+
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
+ {
+ ASSERT(m_Link != nullptr);
+
+ LOGD("%p (%s:%d): Error %d in the cEchoLinkCallbacks: %s", m_Link.get(), m_Link->GetRemoteIP().c_str(), m_Link->GetRemotePort(), a_ErrorCode, a_ErrorMsg.c_str());
+ m_Link.reset();
+ }
+
+ /** The link attached to this callbacks instance. */
+ cTCPLinkPtr m_Link;
+};
+
+
+
+
+
+class cEchoServerCallbacks:
+ public cNetwork::cListenCallbacks
+{
+ virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort)
+ {
+ LOGD("New incoming connection(%s:%d).", a_RemoteIPAddress.c_str(), a_RemotePort);
+ return std::make_shared<cEchoLinkCallbacks>();
+ }
+
+ virtual void OnAccepted(cTCPLink & a_Link) override
+ {
+ LOGD("New client accepted (%s:%d), sending welcome message.", a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort());
+ // Send a welcome message to each connecting client:
+ a_Link.Send("Welcome to the simple echo server.\r\n");
+ LOGD("Welcome message queued.");
+ }
+
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
+ {
+ LOGWARNING("An error occured while listening for connections: %d (%s).", a_ErrorCode, a_ErrorMsg.c_str());
+ }
+};
+
+
+
+
+
+int main()
+{
+ LOGD("EchoServer: starting up");
+ cServerHandlePtr Server = cNetwork::Listen(9876, std::make_shared<cEchoServerCallbacks>());
+ if (!Server->IsListening())
+ {
+ LOGWARNING("Cannot listen on port 9876");
+ abort();
+ }
+ ASSERT(Server->IsListening());
+
+ // Wait for the user to terminate the server:
+ printf("Press enter to close the server.\n");
+ AString line;
+ std::getline(std::cin, line);
+
+ // Close the server and all its active connections:
+ LOG("Server terminating.");
+ Server->Close();
+ ASSERT(!Server->IsListening());
+ LOGD("Server has been closed.");
+
+ printf("Press enter to exit test.\n");
+ std::getline(std::cin, line);
+
+ LOG("Network test finished.");
+ return 0;
+}
+
+
+
+
diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp
new file mode 100644
index 000000000..2b8830c24
--- /dev/null
+++ b/tests/Network/Google.cpp
@@ -0,0 +1,118 @@
+
+// Google.cpp
+
+// Implements a HTTP download of the google's front page using the LibEvent-based cNetwork API
+
+#include "Globals.h"
+#include <thread>
+#include "OSSupport/Event.h"
+#include "OSSupport/Network.h"
+
+
+
+
+
+/** Connect callbacks that send a HTTP GET request for google.com when connected. */
+class cHTTPConnectCallbacks:
+ public cNetwork::cConnectCallbacks
+{
+ cEvent & m_Event;
+ virtual void OnConnected(cTCPLink & a_Link) override
+ {
+ LOGD("Connected, sending HTTP GET");
+ if (!a_Link.Send("GET / HTTP/1.0\r\nHost:google.com\r\n\r\n"))
+ {
+ LOGWARNING("Sending HTTP GET failed");
+ }
+ LOGD("HTTP GET queued.");
+ }
+
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
+ {
+ LOGD("Error while connecting HTTP: %d (%s)", a_ErrorCode, a_ErrorMsg.c_str());
+ m_Event.Set();
+ }
+
+public:
+ cHTTPConnectCallbacks(cEvent & a_Event):
+ m_Event(a_Event)
+ {
+ }
+};
+
+
+
+
+
+/** cTCPLink callbacks that dump everything it received to the log. */
+class cDumpCallbacks:
+ public cTCPLink::cCallbacks
+{
+ cEvent & m_Event;
+ cTCPLinkPtr m_Link;
+
+ virtual void OnLinkCreated(cTCPLinkPtr a_Link) override
+ {
+ ASSERT(m_Link == nullptr);
+ m_Link = a_Link;
+ }
+
+ virtual void OnReceivedData(const char * a_Data, size_t a_Size) override
+ {
+ ASSERT(m_Link != nullptr);
+
+ // Log the incoming data size:
+ AString Hex;
+ CreateHexDump(Hex, a_Data, a_Size, 16);
+ LOGD("Incoming data: %u bytes:\n%s", static_cast<unsigned>(a_Size), Hex.c_str());
+ }
+
+ virtual void OnRemoteClosed(void) override
+ {
+ ASSERT(m_Link != nullptr);
+
+ LOGD("Remote has closed the connection.");
+ m_Link.reset();
+ m_Event.Set();
+ }
+
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
+ {
+ ASSERT(m_Link != nullptr);
+
+ LOGD("Error %d (%s) in the cDumpCallbacks.", a_ErrorCode, a_ErrorMsg.c_str());
+ m_Link.reset();
+ m_Event.Set();
+ }
+
+public:
+ cDumpCallbacks(cEvent & a_Event):
+ m_Event(a_Event)
+ {
+ }
+};
+
+
+
+
+
+int main()
+{
+ cEvent evtFinish;
+
+ LOGD("Network test: Connecting to google.com:80, reading front page via HTTP.");
+ if (!cNetwork::Connect("google.com", 80, std::make_shared<cHTTPConnectCallbacks>(evtFinish), std::make_shared<cDumpCallbacks>(evtFinish)))
+ {
+ LOGWARNING("Cannot queue connection to google.com");
+ abort();
+ }
+ LOGD("Connect request has been queued.");
+
+ evtFinish.Wait();
+ LOGD("Network test finished");
+ return 0;
+}
+
+
+
+
diff --git a/tests/Network/NameLookup.cpp b/tests/Network/NameLookup.cpp
new file mode 100644
index 000000000..822a96baf
--- /dev/null
+++ b/tests/Network/NameLookup.cpp
@@ -0,0 +1,80 @@
+
+// NameLookup.cpp
+
+// Implements a DNS name lookup using the LibEvent-based cNetwork API
+
+#include "Globals.h"
+#include <thread>
+#include "OSSupport/Event.h"
+#include "OSSupport/Network.h"
+
+
+
+
+
+class cFinishLookupCallbacks:
+ public cNetwork::cResolveNameCallbacks
+{
+ cEvent & m_Event;
+
+ virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) override
+ {
+ LOGD("%s resolves to IP %s", a_Name.c_str(), a_IP.c_str());
+ }
+
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
+ {
+ LOGD("Error %d (%s) while performing lookup!", a_ErrorCode, a_ErrorMsg.c_str());
+ exit(a_ErrorCode);
+ }
+
+ virtual void OnFinished(void) override
+ {
+ LOGD("Resolving finished.");
+ m_Event.Set();
+ }
+
+public:
+ cFinishLookupCallbacks(cEvent & a_Event):
+ m_Event(a_Event)
+ {
+ }
+};
+
+
+
+
+
+int main()
+{
+ cEvent evtFinish;
+
+ // Look up google.com (has multiple IP addresses):
+ LOGD("Network test: Looking up google.com");
+ if (!cNetwork::HostnameToIP("google.com", std::make_shared<cFinishLookupCallbacks>(evtFinish)))
+ {
+ LOGWARNING("Cannot resolve google.com to IP");
+ abort();
+ }
+ LOGD("Name lookup has been successfully queued");
+ evtFinish.Wait();
+ LOGD("Lookup finished.");
+
+ // Look up 8.8.8.8 (Google free DNS):
+ LOGD("Network test: Looking up IP 8.8.8.8");
+ if (!cNetwork::IPToHostName("8.8.8.8", std::make_shared<cFinishLookupCallbacks>(evtFinish)))
+ {
+ LOGWARNING("Cannot resolve 8.8.8.8 to name");
+ abort();
+ }
+ LOGD("IP lookup has been successfully queued");
+ evtFinish.Wait();
+ LOGD("IP lookup finished.");
+
+ LOGD("Network test finished");
+ return 0;
+}
+
+
+
+