1
0
Fork 0

Merge pull request #1701 from mc-server/libevent

LibEvent-based socket API
This commit is contained in:
Mattes D 2015-01-24 09:27:55 +01:00
commit f91de20ca1
23 changed files with 2293 additions and 8 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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()

1
lib/libevent Submodule

@ -0,0 +1 @@
Subproject commit 0b49ae34594533daa82c06a506078de9e336a013

View File

@ -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)

View File

@ -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);
}

View File

@ -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)

View File

@ -1,5 +1,6 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "CriticalSection.h"

View File

@ -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;
}

View File

@ -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;

111
src/OSSupport/IPLookup.cpp Normal file
View File

@ -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);
}

49
src/OSSupport/IPLookup.h Normal file
View File

@ -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;

246
src/OSSupport/Network.h Normal file
View File

@ -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
);
};

View File

@ -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[]
}

View File

@ -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);
};

View File

@ -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);
}

View File

@ -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);
};

View File

@ -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);
}

122
src/OSSupport/TCPLinkImpl.h Normal file
View File

@ -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);
};

View File

@ -5,3 +5,4 @@ enable_testing()
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(ChunkData)
add_subdirectory(Network)

View File

@ -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)

View File

@ -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;
}

118
tests/Network/Google.cpp Normal file
View File

@ -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;
}

View File

@ -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;
}