diff options
-rw-r--r-- | src/OSSupport/CMakeLists.txt | 9 | ||||
-rw-r--r-- | src/OSSupport/HostnameLookup.cpp | 109 | ||||
-rw-r--r-- | src/OSSupport/HostnameLookup.h | 41 | ||||
-rw-r--r-- | src/OSSupport/IPLookup.cpp | 91 | ||||
-rw-r--r-- | src/OSSupport/IPLookup.h | 40 | ||||
-rw-r--r-- | src/OSSupport/Network.cpp | 998 | ||||
-rw-r--r-- | src/OSSupport/Network.h | 19 | ||||
-rw-r--r-- | src/OSSupport/NetworkSingleton.cpp | 51 | ||||
-rw-r--r-- | src/OSSupport/NetworkSingleton.h | 6 | ||||
-rw-r--r-- | src/OSSupport/ServerHandleImpl.cpp | 302 | ||||
-rw-r--r-- | src/OSSupport/ServerHandleImpl.h | 109 | ||||
-rw-r--r-- | src/OSSupport/TCPLinkImpl.cpp | 314 | ||||
-rw-r--r-- | src/OSSupport/TCPLinkImpl.h | 109 | ||||
-rw-r--r-- | tests/Network/CMakeLists.txt | 24 |
14 files changed, 1170 insertions, 1052 deletions
diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index 167afb784..9424b63da 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -10,14 +10,17 @@ SET (SRCS Event.cpp File.cpp GZipFile.cpp + HostnameLookup.cpp + IPLookup.cpp IsThread.cpp ListenThread.cpp - Network.cpp NetworkSingleton.cpp Semaphore.cpp + ServerHandleImpl.cpp Socket.cpp SocketThreads.cpp StackTrace.cpp + TCPLinkImpl.cpp ) SET (HDRS @@ -26,15 +29,19 @@ SET (HDRS Event.h File.h GZipFile.h + HostnameLookup.h + IPLookup.h IsThread.h ListenThread.h Network.h NetworkSingleton.h Queue.h Semaphore.h + ServerHandleImpl.h Socket.h SocketThreads.h StackTrace.h + TCPLinkImpl.h ) if(NOT MSVC) diff --git a/src/OSSupport/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp new file mode 100644 index 000000000..9e35f7163 --- /dev/null +++ b/src/OSSupport/HostnameLookup.cpp @@ -0,0 +1,109 @@ + +// HostnameLookup.cpp + +// Implements the cHostnameLookup class representing an in-progress hostname-to-IP lookup + +#include "Globals.h" +#include "HostnameLookup.h" +#include <event2/dns.h> +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cHostnameLookup: + +cHostnameLookup::cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks), + m_Hostname(a_Hostname) +{ + evutil_addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + hints.ai_flags = EVUTIL_AI_CANONNAME; + evdns_getaddrinfo(cNetworkSingleton::Get().GetDNSBase(), a_Hostname.c_str(), nullptr, &hints, Callback, this); +} + + + + + +void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a_Self) +{ + // Get the Self class: + cHostnameLookup * Self = reinterpret_cast<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); + 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(1); + } + else + { + Self->m_Callbacks->OnFinished(); + } + evutil_freeaddrinfo(OrigAddr); + cNetworkSingleton::Get().RemoveHostnameLookup(Self); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +bool cNetwork::HostnameToIP( + const AString & a_Hostname, + cNetwork::cResolveNameCallbacksPtr a_Callbacks +) +{ + return cNetworkSingleton::Get().HostnameToIP(a_Hostname, a_Callbacks); +} + + + + diff --git a/src/OSSupport/HostnameLookup.h b/src/OSSupport/HostnameLookup.h new file mode 100644 index 000000000..98f48b933 --- /dev/null +++ b/src/OSSupport/HostnameLookup.h @@ -0,0 +1,41 @@ + +// HostnameLookup.h + +// Declares the cHostnameLookup class representing an in-progress hostname-to-IP lookup + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include <event2/util.h> + + + + + +/** Holds information about an in-progress Hostname-to-IP lookup. */ +class cHostnameLookup +{ + /** The callbacks to call for resolved names / errors. */ + cNetwork::cResolveNameCallbacksPtr m_Callbacks; + + /** The hostname that was queried (needed for the callbacks). */ + AString m_Hostname; + + static void Callback(int a_ErrCode, struct evutil_addrinfo * a_Addr, void * a_Self); + +public: + cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks); +}; +typedef SharedPtr<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..bbcfbfe40 --- /dev/null +++ b/src/OSSupport/IPLookup.cpp @@ -0,0 +1,91 @@ + +// IPLookup.cpp + +// Implements the cIPLookup class representing an IP-to-hostname lookup in progress. + +#include "Globals.h" +#include "IPLookup.h" +#include <event2/dns.h> +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cIPLookup: + +cIPLookup::cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks), + m_IP(a_IP) +{ + sockaddr_storage sa; + int salen = static_cast<int>(sizeof(sa)); + memset(&sa, 0, sizeof(sa)); + evutil_parse_sockaddr_port(a_IP.c_str(), reinterpret_cast<sockaddr *>(&sa), &salen); + 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"); + break; + } + } // switch (address family) +} + + + + + +void cIPLookup::Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self) +{ + // Get the Self class: + cIPLookup * Self = reinterpret_cast<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); + } + 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 +) +{ + return cNetworkSingleton::Get().IPToHostName(a_IP, a_Callbacks); +} + + + + diff --git a/src/OSSupport/IPLookup.h b/src/OSSupport/IPLookup.h new file mode 100644 index 000000000..f39b955aa --- /dev/null +++ b/src/OSSupport/IPLookup.h @@ -0,0 +1,40 @@ + +// IPLookup.h + +// Declares the cIPLookup class representing an IP-to-hostname lookup in progress. + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" + + + + + +/** Holds information about an in-progress IP-to-Hostname lookup. */ +class cIPLookup +{ + /** The callbacks to call for resolved names / errors. */ + cNetwork::cResolveNameCallbacksPtr m_Callbacks; + + /** The IP that was queried (needed for the callbacks). */ + AString m_IP; + + static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); + +public: + cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks); +}; +typedef SharedPtr<cIPLookup> cIPLookupPtr; +typedef std::vector<cIPLookupPtr> cIPLookupPtrs; + + + + + diff --git a/src/OSSupport/Network.cpp b/src/OSSupport/Network.cpp deleted file mode 100644 index 02bdfa39c..000000000 --- a/src/OSSupport/Network.cpp +++ /dev/null @@ -1,998 +0,0 @@ - -// Network.cpp - -// Implements the classes used for the Network API - -#include "Globals.h" -#include "Network.h" -#include <event2/event.h> -#include <event2/thread.h> -#include <event2/bufferevent.h> -#include <event2/dns.h> -#include <event2/listener.h> -#include <thread> -#include "Event.h" -#include "CriticalSection.h" -#include "NetworkSingleton.h" - - - - - -// fwd: -class cServerHandleImpl; -class cTCPLinkImpl; -typedef SharedPtr<cTCPLinkImpl> cTCPLinkImplPtr; -typedef std::vector<cTCPLinkImplPtr> cTCPLinkImplPtrs; -typedef SharedPtr<cServerHandleImpl> cServerHandleImplPtr; -typedef std::vector<cServerHandleImplPtr> cServerHandleImplPtrs; - - - - - -//////////////////////////////////////////////////////////////////////////////// -// Class definitions: - -/** Holds information about an in-progress Hostname-to-IP lookup. */ -class cHostnameLookup -{ - /** The callbacks to call for resolved names / errors. */ - cNetwork::cResolveNameCallbacksPtr m_Callbacks; - - /** The hostname that was queried (needed for the callbacks). */ - AString m_Hostname; - - static void Callback(int a_ErrCode, struct evutil_addrinfo * a_Addr, void * a_Self); - -public: - cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks); -}; -typedef SharedPtr<cHostnameLookup> cHostnameLookupPtr; -typedef std::vector<cHostnameLookupPtr> cHostnameLookupPtrs; - - - - - -/** Holds information about an in-progress IP-to-Hostname lookup. */ -class cIPLookup -{ - /** The callbacks to call for resolved names / errors. */ - cNetwork::cResolveNameCallbacksPtr m_Callbacks; - - /** The IP that was queried (needed for the callbacks). */ - AString m_IP; - - static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); - -public: - cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks); -}; -typedef SharedPtr<cIPLookup> cIPLookupPtr; -typedef std::vector<cIPLookupPtr> cIPLookupPtrs; - - - - - -/** Implements the cTCPLink details so that it can represent the single connection between two endpoints. */ -class cTCPLinkImpl: - public cTCPLink -{ - typedef cTCPLink super; - -public: - /** Creates a new link based on the given socket. - Used for connections accepted in a server using cNetwork::Listen(). - a_Address and a_AddrLen describe the remote peer that has connected. */ - cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int a_AddrLen); - - /** Destroys the LibEvent handle representing the link. */ - ~cTCPLinkImpl(); - - /** Queues a connection request to the specified host. - a_ConnectCallbacks must be valid. - Returns a link that has the connection request queued, or NULL for failure. */ - static cTCPLinkImplPtr Connect(const AString & a_Host, UInt16 a_Port, cTCPLink::cCallbacksPtr a_LinkCallbacks, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks); - - // cTCPLink overrides: - virtual bool Send(const void * a_Data, size_t a_Length) override; - virtual AString GetLocalIP(void) const override { return m_LocalIP; } - virtual UInt16 GetLocalPort(void) const override { return m_LocalPort; } - virtual AString GetRemoteIP(void) const override { return m_RemoteIP; } - virtual UInt16 GetRemotePort(void) const override { return m_RemotePort; } - virtual void Shutdown(void) override; - virtual void Close(void) override; - -protected: - - /** Callbacks to call when the connection is established. - May be NULL if not used. Only used for outgoing connections (cNetwork::Connect()). */ - cNetwork::cConnectCallbacksPtr m_ConnectCallbacks; - - /** The LibEvent handle representing this connection. */ - bufferevent * m_BufferEvent; - - /** The server handle that has created this link. - Only valid for incoming connections, NULL for outgoing connections. */ - cServerHandleImpl * m_Server; - - /** The IP address of the local endpoint. Valid only after the socket has been connected. */ - AString m_LocalIP; - - /** The port of the local endpoint. Valid only after the socket has been connected. */ - UInt16 m_LocalPort; - - /** The IP address of the remote endpoint. Valid only after the socket has been connected. */ - AString m_RemoteIP; - - /** The port of the remote endpoint. Valid only after the socket has been connected. */ - UInt16 m_RemotePort; - - - /** Creates a new link to be queued to connect to a specified host:port. - Used for outgoing connections created using cNetwork::Connect(). - To be used only by the Connect() factory function. */ - cTCPLinkImpl(const cCallbacksPtr a_LinkCallbacks); - - /** Callback that LibEvent calls when there's data available from the remote peer. */ - static void ReadCallback(bufferevent * a_BufferEvent, void * a_Self); - - /** Callback that LibEvent calls when there's a non-data-related event on the socket. */ - static void EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self); - - /** Sets a_IP and a_Port to values read from a_Address, based on the correct address family. */ - static void UpdateAddress(const sockaddr * a_Address, int a_AddrLen, AString & a_IP, UInt16 & a_Port); - - /** Updates m_LocalIP and m_LocalPort based on the metadata read from the socket. */ - void UpdateLocalAddress(void); - - /** Updates m_RemoteIP and m_RemotePort based on the metadata read from the socket. */ - void UpdateRemoteAddress(void); -}; - - - - - -/** Implements the cServerHandle details so that it can represent a real server socket, with a list of clients. */ -class cServerHandleImpl: - public cServerHandle -{ - typedef cServerHandle super; - friend class cTCPLinkImpl; - -public: - /** Closes the server, dropping all the connections. */ - ~cServerHandleImpl(); - - /** Creates a new server instance listening on the specified port. - Both IPv4 and IPv6 interfaces are used, if possible. - Always returns a server instance; in the event of a failure, the instance holds the error details. Use IsListening() to query success. */ - static cServerHandleImplPtr Listen( - UInt16 a_Port, - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks - ); - - // cServerHandle overrides: - virtual void Close(void) override; - virtual bool IsListening(void) const override { return m_IsListening; } - -protected: - /** The callbacks used to notify about incoming connections. */ - cNetwork::cListenCallbacksPtr m_ListenCallbacks; - - /** The callbacks used to create new cTCPLink instances for incoming connections. */ - cTCPLink::cCallbacksPtr m_LinkCallbacks; - - /** The LibEvent handle representing the main listening socket. */ - evconnlistener * m_ConnListener; - - /** The LibEvent handle representing the secondary listening socket (only when side-by-side listening is needed, such as WinXP). */ - evconnlistener * m_SecondaryConnListener; - - /** Set to true when the server is initialized successfully and is listening for incoming connections. */ - bool m_IsListening; - - /** Container for all currently active connections on this server. */ - cTCPLinkImplPtrs m_Connections; - - /** Mutex protecting m_Connections againt multithreaded access. */ - cCriticalSection m_CS; - - /** Contains the error code for the failure to listen. Only valid for non-listening instances. */ - int m_ErrorCode; - - /** Contains the error message for the failure to listen. Only valid for non-listening instances. */ - AString m_ErrorMsg; - - - - /** Creates a new instance with the specified callbacks. - Initializes the internals, but doesn't start listening yet. */ - cServerHandleImpl( - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks - ); - - /** Starts listening on the specified port. - Returns true if successful, false on failure. On failure, sets m_ErrorCode and m_ErrorMsg. */ - bool Listen(UInt16 a_Port); - - /** The callback called by LibEvent upon incoming connection. */ - static void Callback(evconnlistener * a_Listener, evutil_socket_t a_Socket, sockaddr * a_Addr, int a_Len, void * a_Self); - - /** Removes the specified link from m_Connections. - Called by cTCPLinkImpl when the link is terminated. */ - void RemoveLink(const cTCPLinkImpl * a_Link); -}; - - - - - -//////////////////////////////////////////////////////////////////////////////// -// Globals: - -bool IsValidSocket(evutil_socket_t a_Socket) -{ - #ifdef _WIN32 - return (a_Socket != INVALID_SOCKET); - #else // _WIN32 - return (a_Socket >= 0); - #endif // else _WIN32 -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cHostnameLookup: - -cHostnameLookup::cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks): - m_Callbacks(a_Callbacks), - m_Hostname(a_Hostname) -{ - evutil_addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_protocol = IPPROTO_TCP; - hints.ai_socktype = SOCK_STREAM; - hints.ai_family = AF_UNSPEC; - hints.ai_flags = EVUTIL_AI_CANONNAME; - evdns_getaddrinfo(cNetworkSingleton::Get().GetDNSBase(), a_Hostname.c_str(), nullptr, &hints, Callback, this); -} - - - - - -void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a_Self) -{ - // Get the Self class: - cHostnameLookup * Self = reinterpret_cast<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); - 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(1); - } - else - { - Self->m_Callbacks->OnFinished(); - } - evutil_freeaddrinfo(OrigAddr); - cNetworkSingleton::Get().RemoveHostnameLookup(Self); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cIPLookup: - -cIPLookup::cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks): - m_Callbacks(a_Callbacks), - m_IP(a_IP) -{ - sockaddr_storage sa; - int salen = static_cast<int>(sizeof(sa)); - evutil_parse_sockaddr_port(a_IP.c_str(), reinterpret_cast<sockaddr *>(&sa), &salen); - 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"); - break; - } - } // switch (address family) -} - - - - - -void cIPLookup::Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self) -{ - // Get the Self class: - cIPLookup * Self = reinterpret_cast<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); - } - 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); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cTCPLinkImpl: - -cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): - super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE)), - m_Server(nullptr) -{ - // Create the LibEvent handle, but don't assign a socket to it yet (will be assigned within Connect() method): - bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); - bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); -} - - - - - -cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int a_AddrLen): - super(a_LinkCallbacks), - m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE)), - m_Server(a_Server) -{ - // Update the endpoint addresses: - UpdateLocalAddress(); - UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort); - - // Create the LibEvent handle: - bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); - bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); -} - - - - - -cTCPLinkImpl::~cTCPLinkImpl() -{ - bufferevent_free(m_BufferEvent); -} - - - - - -cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTCPLink::cCallbacksPtr a_LinkCallbacks, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks) -{ - ASSERT(a_LinkCallbacks != nullptr); - ASSERT(a_ConnectCallbacks != nullptr); - - // Create a new link: - cTCPLinkImplPtr res{new cTCPLinkImpl(a_LinkCallbacks)}; // Cannot use std::make_shared here, constructor is not accessible - res->m_ConnectCallbacks = a_ConnectCallbacks; - cNetworkSingleton::Get().AddLink(res); - - // If a_Host is an IP address, schedule a connection immediately: - sockaddr_storage sa; - int salen = static_cast<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; -} - - - - - -bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) -{ - return (bufferevent_write(m_BufferEvent, a_Data, a_Length) == 0); -} - - - - - -void cTCPLinkImpl::Shutdown(void) -{ - #ifdef _WIN32 - shutdown(bufferevent_getfd(m_BufferEvent), SD_SEND); - #else - shutdown(bufferevent_getfd(m_BufferEvent), SHUT_WR); - #endif - bufferevent_disable(m_BufferEvent, EV_WRITE); -} - - - - - -void cTCPLinkImpl::Close(void) -{ - // Disable all events on the socket, but keep it alive: - bufferevent_disable(m_BufferEvent, EV_READ | EV_WRITE); - if (m_Server == nullptr) - { - cNetworkSingleton::Get().RemoveLink(this); - } - else - { - m_Server->RemoveLink(this); - } -} - - - - - - -void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self) -{ - ASSERT(a_Self != nullptr); - cTCPLinkImpl * Self = static_cast<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(*Self, data, length); - } -} - - - - - -void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) -{ - ASSERT(a_Self != nullptr); - cTCPLinkImpl * Self = static_cast<cTCPLinkImpl *>(a_Self); - - // If an error is reported, call the error callback: - if (a_What & BEV_EVENT_ERROR) - { - // Choose the proper callback to call based on whether we were waiting for connection or not: - if (Self->m_ConnectCallbacks != nullptr) - { - Self->m_ConnectCallbacks->OnError(EVUTIL_SOCKET_ERROR()); - } - else - { - Self->m_Callbacks->OnError(*Self, EVUTIL_SOCKET_ERROR()); - if (Self->m_Server == nullptr) - { - cNetworkSingleton::Get().RemoveLink(Self); - } - else - { - Self->m_Server->RemoveLink(Self); - } - } - return; - } - - // Pending connection succeeded, call the connection callback: - if (a_What & BEV_EVENT_CONNECTED) - { - if (Self->m_ConnectCallbacks != nullptr) - { - Self->m_ConnectCallbacks->OnSuccess(*Self); - // Reset the connect callbacks so that later errors get reported through the link callbacks: - Self->m_ConnectCallbacks.reset(); - return; - } - Self->UpdateLocalAddress(); - Self->UpdateRemoteAddress(); - } - - // If the connection has been closed, call the link callback and remove the connection: - if (a_What & BEV_EVENT_EOF) - { - Self->m_Callbacks->OnRemoteClosed(*Self); - if (Self->m_Server != nullptr) - { - Self->m_Server->RemoveLink(Self); - } - else - { - cNetworkSingleton::Get().RemoveLink(Self); - } - return; - } - - // Unknown event, report it: - LOGWARNING("cTCPLinkImpl: Unhandled LibEvent event %d (0x%x)", a_What, a_What); - ASSERT(!"cTCPLinkImpl: Unhandled LibEvent event"); -} - - - - - -void cTCPLinkImpl::UpdateAddress(const sockaddr * a_Address, int a_AddrLen, AString & a_IP, UInt16 & a_Port) -{ - // Based on the family specified in the address, use the correct datastructure to convert to IP string: - char IP[128]; - switch (a_Address->sa_family) - { - case AF_INET: // IPv4: - { - const sockaddr_in * sin = reinterpret_cast<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); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cServerHandleImpl: - -cServerHandleImpl::cServerHandleImpl( - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks -): - m_ListenCallbacks(a_ListenCallbacks), - m_LinkCallbacks(a_LinkCallbacks), - m_ConnListener(nullptr), - m_SecondaryConnListener(nullptr), - m_IsListening(false), - m_ErrorCode(0) -{ -} - - - - - -cServerHandleImpl::~cServerHandleImpl() -{ - if (m_ConnListener != nullptr) - { - evconnlistener_free(m_ConnListener); - } - if (m_SecondaryConnListener != nullptr) - { - evconnlistener_free(m_SecondaryConnListener); - } -} - - - - - -void cServerHandleImpl::Close(void) -{ - // Stop the listener sockets: - evconnlistener_disable(m_ConnListener); - if (m_SecondaryConnListener != nullptr) - { - evconnlistener_disable(m_SecondaryConnListener); - } - m_IsListening = false; - - // Shutdown all connections: - cTCPLinkImplPtrs Conns; - { - cCSLock Lock(m_CS); - std::swap(Conns, m_Connections); - } - for (auto conn: Conns) - { - conn->Shutdown(); - } -} - - - - - -cServerHandleImplPtr cServerHandleImpl::Listen( - UInt16 a_Port, - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks -) -{ - cServerHandleImplPtr res = cServerHandleImplPtr{new cServerHandleImpl(a_ListenCallbacks, a_LinkCallbacks)}; - if (res->Listen(a_Port)) - { - cNetworkSingleton::Get().AddServer(res); - } - else - { - a_ListenCallbacks->OnError(res->m_ErrorCode, res->m_ErrorMsg); - } - return res; -} - - - - - -bool cServerHandleImpl::Listen(UInt16 a_Port) -{ - // Make sure the cNetwork internals are innitialized: - cNetworkSingleton::Get(); - - // Set up the main socket: - // It should listen on IPv6 with IPv4 fallback, when available; IPv4 when IPv6 is not available. - bool NeedsTwoSockets = false; - int err; - evutil_socket_t MainSock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); - if (!IsValidSocket(MainSock)) - { - // Failed to create IPv6 socket, create an IPv4 one instead: - err = EVUTIL_SOCKET_ERROR(); - LOGD("Failed to create IPv6 MainSock: %d (%s)", err, evutil_socket_error_to_string(err)); - MainSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (!IsValidSocket(MainSock)) - { - m_ErrorCode = EVUTIL_SOCKET_ERROR(); - Printf(m_ErrorMsg, "Cannot create socket for port %d: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); - return false; - } - - // Bind to all interfaces: - sockaddr_in name; - memset(&name, 0, sizeof(name)); - name.sin_family = AF_INET; - name.sin_port = ntohs(a_Port); - if (bind(MainSock, reinterpret_cast<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: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); - evutil_closesocket(MainSock); - return false; - } - } - if (evutil_make_socket_nonblocking(MainSock) != 0) - { - m_ErrorCode = EVUTIL_SOCKET_ERROR(); - Printf(m_ErrorMsg, "Cannot make socket on port %d non-blocking: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); - evutil_closesocket(MainSock); - return false; - } - if (listen(MainSock, 0) != 0) - { - m_ErrorCode = EVUTIL_SOCKET_ERROR(); - Printf(m_ErrorMsg, "Cannot listen on port %d: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); - evutil_closesocket(MainSock); - return false; - } - m_ConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, MainSock); - m_IsListening = true; - if (!NeedsTwoSockets) - { - return true; - } - - // If a secondary socket is required (WinXP dual-stack), create it here: - LOGD("Creating a second socket for IPv4"); - evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (!IsValidSocket(SecondSock)) - { - err = EVUTIL_SOCKET_ERROR(); - LOGD("socket(AF_INET, ...) failed for secondary socket: %d, %s", err, evutil_socket_error_to_string(err)); - return true; // Report as success, the primary socket is working - } - - // Make the secondary socket nonblocking: - if (evutil_make_socket_nonblocking(SecondSock) != 0) - { - err = EVUTIL_SOCKET_ERROR(); - LOGD("evutil_make_socket_nonblocking() failed for secondary socket: %d, %s", err, evutil_socket_error_to_string(err)); - evutil_closesocket(SecondSock); - } - - // Bind to all IPv4 interfaces: - sockaddr_in name; - memset(&name, 0, sizeof(name)); - name.sin_family = AF_INET; - name.sin_port = ntohs(a_Port); - if (bind(SecondSock, reinterpret_cast<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; - } - - if (listen(SecondSock, 0) != 0) - { - err = EVUTIL_SOCKET_ERROR(); - LOGD("Cannot listen on on secondary socket on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); - evutil_closesocket(SecondSock); - return false; - } - - m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); - return true; -} - - - - - -void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_Socket, sockaddr * a_Addr, int a_Len, void * a_Self) -{ - // Cast to true self: - cServerHandleImpl * Self = reinterpret_cast<cServerHandleImpl *>(a_Self); - ASSERT(Self != nullptr); - - // Create a new cTCPLink for the incoming connection: - cTCPLinkImplPtr Link = std::make_shared<cTCPLinkImpl>(a_Socket, Self->m_LinkCallbacks, Self, a_Addr, a_Len); - { - cCSLock Lock(Self->m_CS); - Self->m_Connections.push_back(Link); - } // Lock(m_CS) - - // Call the OnAccepted callback: - Self->m_ListenCallbacks->OnAccepted(*Link); -} - - - - - -void cServerHandleImpl::RemoveLink(const cTCPLinkImpl * a_Link) -{ - cCSLock Lock(m_CS); - for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) - { - if (itr->get() == a_Link) - { - m_Connections.erase(itr); - return; - } - } // for itr - m_Connections[] -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cNetwork: - -bool cNetwork::Connect( - const AString & a_Host, - const UInt16 a_Port, - cNetwork::cConnectCallbacksPtr a_ConnectCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks -) -{ - // Add a connection request to the queue: - cTCPLinkImplPtr Conn = cTCPLinkImpl::Connect(a_Host, a_Port, a_LinkCallbacks, a_ConnectCallbacks); - return (Conn != nullptr); -} - - - - - -cServerHandlePtr cNetwork::Listen( - const UInt16 a_Port, - cNetwork::cListenCallbacksPtr a_ListenCallbacks, - cTCPLink::cCallbacksPtr a_LinkCallbacks -) -{ - return cServerHandleImpl::Listen(a_Port, a_ListenCallbacks, a_LinkCallbacks); -} - - - - - -bool cNetwork::HostnameToIP( - const AString & a_Hostname, - cNetwork::cResolveNameCallbacksPtr a_Callbacks -) -{ - return cNetworkSingleton::Get().HostnameToIP(a_Hostname, a_Callbacks); -} - - - - - -bool cNetwork::IPToHostName( - const AString & a_IP, - cNetwork::cResolveNameCallbacksPtr a_Callbacks -) -{ - return cNetworkSingleton::Get().IPToHostName(a_IP, a_Callbacks); -} - - - - -//////////////////////////////////////////////////////////////////////////////// -// cTCPLink: - -cTCPLink::cTCPLink(cCallbacksPtr a_Callbacks): - m_Callbacks(a_Callbacks) -{ -} - - - - - diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index cb5badaeb..0452c3b6a 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -80,7 +80,10 @@ protected: /** Creates a new link, with the specified callbacks. */ - cTCPLink(cCallbacksPtr a_Callbacks); + cTCPLink(cCallbacksPtr a_Callbacks): + m_Callbacks(a_Callbacks) + { + } }; @@ -154,7 +157,7 @@ public: virtual ~cResolveNameCallbacks() {} /** Called when the hostname is successfully resolved into an IP address. - May be called multiple times if an address resolves to multiple addresses. + May be called multiple times if a name resolves to multiple addresses. a_IP may be either an IPv4 or an IPv6 address with their proper formatting. */ virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) = 0; @@ -173,7 +176,8 @@ public: Calls one the connection callbacks (success, error) when the connection is successfully established, or upon failure. The a_LinkCallbacks is passed to the newly created cTCPLink. Returns true if queueing was successful, false on failure to queue. - Note that the return value doesn't report the success of the actual connection; the connection is established asynchronously in the background. */ + Note that the return value doesn't report the success of the actual connection; the connection is established asynchronously in the background. + Implemented in TCPLinkImpl.cpp. */ static bool Connect( const AString & a_Host, const UInt16 a_Port, @@ -185,7 +189,8 @@ public: /** Opens up the specified port for incoming connections. Calls an OnAccepted callback for each incoming connection. A cTCPLink with the specified link callbacks is created for each connection. - Returns a cServerHandle that can be used to query the operation status and close the server. */ + Returns a cServerHandle that can be used to query the operation status and close the server. + Implemented in ServerHandleImpl.cpp. */ static cServerHandlePtr Listen( const UInt16 a_Port, cListenCallbacksPtr a_ListenCallbacks, @@ -196,7 +201,8 @@ public: /** Queues a DNS query to resolve the specified hostname to IP address. Calls one of the callbacks when the resolving succeeds, or when it fails. Returns true if queueing was successful, false if not. - Note that the return value doesn't report the success of the actual lookup; the lookup happens asynchronously on the background. */ + Note that the return value doesn't report the success of the actual lookup; the lookup happens asynchronously on the background. + Implemented in HostnameLookup.cpp. */ static bool HostnameToIP( const AString & a_Hostname, cResolveNameCallbacksPtr a_Callbacks @@ -206,7 +212,8 @@ public: /** Queues a DNS query to resolve the specified IP address to a hostname. Calls one of the callbacks when the resolving succeeds, or when it fails. Returns true if queueing was successful, false if not. - Note that the return value doesn't report the success of the actual lookup; the lookup happens asynchronously on the background. */ + Note that the return value doesn't report the success of the actual lookup; the lookup happens asynchronously on the background. + Implemented in IPLookup.cpp. */ static bool IPToHostName( const AString & a_IP, cResolveNameCallbacksPtr a_Callbacks diff --git a/src/OSSupport/NetworkSingleton.cpp b/src/OSSupport/NetworkSingleton.cpp index 552abad64..c9d9b1d81 100644 --- a/src/OSSupport/NetworkSingleton.cpp +++ b/src/OSSupport/NetworkSingleton.cpp @@ -11,51 +11,8 @@ #include <event2/bufferevent.h> #include <event2/dns.h> #include <event2/listener.h> - - - - - -//////////////////////////////////////////////////////////////////////////////// -// Class definitions: - -/** Holds information about an in-progress Hostname-to-IP lookup. */ -class cHostnameLookup -{ - /** The callbacks to call for resolved names / errors. */ - cNetwork::cResolveNameCallbacksPtr m_Callbacks; - - /** The hostname that was queried (needed for the callbacks). */ - AString m_Hostname; - - static void Callback(int a_ErrCode, struct evutil_addrinfo * a_Addr, void * a_Self); - -public: - cHostnameLookup(const AString & a_Hostname, cNetwork::cResolveNameCallbacksPtr a_Callbacks); -}; -typedef SharedPtr<cHostnameLookup> cHostnameLookupPtr; -typedef std::vector<cHostnameLookupPtr> cHostnameLookupPtrs; - - - - - -/** Holds information about an in-progress IP-to-Hostname lookup. */ -class cIPLookup -{ - /** The callbacks to call for resolved names / errors. */ - cNetwork::cResolveNameCallbacksPtr m_Callbacks; - - /** The IP that was queried (needed for the callbacks). */ - AString m_IP; - - static void Callback(int a_Result, char a_Type, int a_Count, int a_Ttl, void * a_Addresses, void * a_Self); - -public: - cIPLookup(const AString & a_IP, cNetwork::cResolveNameCallbacksPtr a_Callbacks); -}; -typedef SharedPtr<cIPLookup> cIPLookupPtr; -typedef std::vector<cIPLookupPtr> cIPLookupPtrs; +#include "IPLookup.h" +#include "HostnameLookup.h" @@ -130,6 +87,8 @@ bool cNetworkSingleton::HostnameToIP( { try { + // TODO: This has a race condition with possible memory leak: + // If a lookup finishes immediately, the constructor calls the removal before this addition cCSLock Lock(m_CS); m_HostnameLookups.push_back(std::make_shared<cHostnameLookup>(a_Hostname, a_Callbacks)); } @@ -150,6 +109,8 @@ bool cNetworkSingleton::IPToHostName( { try { + // TODO: This has a race condition with possible memory leak: + // If a lookup finishes immediately, the constructor calls the removal before this addition cCSLock Lock(m_CS); m_IPLookups.push_back(std::make_shared<cIPLookup>(a_IP, a_Callbacks)); } diff --git a/src/OSSupport/NetworkSingleton.h b/src/OSSupport/NetworkSingleton.h index d5a4ec279..064e075fe 100644 --- a/src/OSSupport/NetworkSingleton.h +++ b/src/OSSupport/NetworkSingleton.h @@ -4,6 +4,12 @@ // Declares the cNetworkSingleton class representing the storage for global data pertaining to network API // such as a list of all connections, all listening sockets and the LibEvent dispatch thread. +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + #pragma once #include "Network.h" diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp new file mode 100644 index 000000000..c399c2279 --- /dev/null +++ b/src/OSSupport/ServerHandleImpl.cpp @@ -0,0 +1,302 @@ + +// ServerHandleImpl.cpp + +// Implements the cServerHandleImpl class implementing the TCP server functionality + +#include "Globals.h" +#include "ServerHandleImpl.h" +#include "TCPLinkImpl.h" +#include "NetworkSingleton.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// Globals: + +static bool IsValidSocket(evutil_socket_t a_Socket) +{ + #ifdef _WIN32 + return (a_Socket != INVALID_SOCKET); + #else // _WIN32 + return (a_Socket >= 0); + #endif // else _WIN32 +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cServerHandleImpl: + +cServerHandleImpl::cServerHandleImpl( + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +): + m_ListenCallbacks(a_ListenCallbacks), + m_LinkCallbacks(a_LinkCallbacks), + m_ConnListener(nullptr), + m_SecondaryConnListener(nullptr), + m_IsListening(false), + m_ErrorCode(0) +{ +} + + + + + +cServerHandleImpl::~cServerHandleImpl() +{ + if (m_ConnListener != nullptr) + { + evconnlistener_free(m_ConnListener); + } + if (m_SecondaryConnListener != nullptr) + { + evconnlistener_free(m_SecondaryConnListener); + } +} + + + + + +void cServerHandleImpl::Close(void) +{ + // Stop the listener sockets: + evconnlistener_disable(m_ConnListener); + if (m_SecondaryConnListener != nullptr) + { + evconnlistener_disable(m_SecondaryConnListener); + } + m_IsListening = false; + + // Shutdown all connections: + cTCPLinkImplPtrs Conns; + { + cCSLock Lock(m_CS); + std::swap(Conns, m_Connections); + } + for (auto conn: Conns) + { + conn->Shutdown(); + } +} + + + + + +cServerHandleImplPtr cServerHandleImpl::Listen( + UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + cServerHandleImplPtr res = cServerHandleImplPtr{new cServerHandleImpl(a_ListenCallbacks, a_LinkCallbacks)}; + if (res->Listen(a_Port)) + { + cNetworkSingleton::Get().AddServer(res); + } + else + { + a_ListenCallbacks->OnError(res->m_ErrorCode, res->m_ErrorMsg); + } + return res; +} + + + + + +bool cServerHandleImpl::Listen(UInt16 a_Port) +{ + // Make sure the cNetwork internals are innitialized: + cNetworkSingleton::Get(); + + // Set up the main socket: + // It should listen on IPv6 with IPv4 fallback, when available; IPv4 when IPv6 is not available. + bool NeedsTwoSockets = false; + int err; + evutil_socket_t MainSock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(MainSock)) + { + // Failed to create IPv6 socket, create an IPv4 one instead: + err = EVUTIL_SOCKET_ERROR(); + LOGD("Failed to create IPv6 MainSock: %d (%s)", err, evutil_socket_error_to_string(err)); + MainSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(MainSock)) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot create socket for port %d: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); + return false; + } + + // Bind to all interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(a_Port); + if (bind(MainSock, reinterpret_cast<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: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); + evutil_closesocket(MainSock); + return false; + } + } + if (evutil_make_socket_nonblocking(MainSock) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot make socket on port %d non-blocking: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); + evutil_closesocket(MainSock); + return false; + } + if (listen(MainSock, 0) != 0) + { + m_ErrorCode = EVUTIL_SOCKET_ERROR(); + Printf(m_ErrorMsg, "Cannot listen on port %d: %s", a_Port, evutil_socket_error_to_string(m_ErrorCode)); + evutil_closesocket(MainSock); + return false; + } + m_ConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, MainSock); + m_IsListening = true; + if (!NeedsTwoSockets) + { + return true; + } + + // If a secondary socket is required (WinXP dual-stack), create it here: + LOGD("Creating a second socket for IPv4"); + evutil_socket_t SecondSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (!IsValidSocket(SecondSock)) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("socket(AF_INET, ...) failed for secondary socket: %d, %s", err, evutil_socket_error_to_string(err)); + return true; // Report as success, the primary socket is working + } + + // Make the secondary socket nonblocking: + if (evutil_make_socket_nonblocking(SecondSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("evutil_make_socket_nonblocking() failed for secondary socket: %d, %s", err, evutil_socket_error_to_string(err)); + evutil_closesocket(SecondSock); + } + + // Bind to all IPv4 interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(a_Port); + if (bind(SecondSock, reinterpret_cast<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; + } + + if (listen(SecondSock, 0) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOGD("Cannot listen on on secondary socket on port %d: %d (%s)", a_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(SecondSock); + return false; + } + + m_SecondaryConnListener = evconnlistener_new(cNetworkSingleton::Get().GetEventBase(), Callback, this, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, SecondSock); + return true; +} + + + + + +void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_Socket, sockaddr * a_Addr, int a_Len, void * a_Self) +{ + // Cast to true self: + cServerHandleImpl * Self = reinterpret_cast<cServerHandleImpl *>(a_Self); + ASSERT(Self != nullptr); + + // Create a new cTCPLink for the incoming connection: + cTCPLinkImplPtr Link = std::make_shared<cTCPLinkImpl>(a_Socket, Self->m_LinkCallbacks, Self, a_Addr, a_Len); + { + cCSLock Lock(Self->m_CS); + Self->m_Connections.push_back(Link); + } // Lock(m_CS) + + // Call the OnAccepted callback: + Self->m_ListenCallbacks->OnAccepted(*Link); +} + + + + + +void cServerHandleImpl::RemoveLink(const cTCPLinkImpl * a_Link) +{ + cCSLock Lock(m_CS); + for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) + { + if (itr->get() == a_Link) + { + m_Connections.erase(itr); + return; + } + } // for itr - m_Connections[] +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +cServerHandlePtr cNetwork::Listen( + const UInt16 a_Port, + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + return cServerHandleImpl::Listen(a_Port, a_ListenCallbacks, a_LinkCallbacks); +} + + + + + diff --git a/src/OSSupport/ServerHandleImpl.h b/src/OSSupport/ServerHandleImpl.h new file mode 100644 index 000000000..b325a0f37 --- /dev/null +++ b/src/OSSupport/ServerHandleImpl.h @@ -0,0 +1,109 @@ + +// ServerHandleImpl.h + +// Declares the cServerHandleImpl class implementing the TCP server functionality + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include <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, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); + + // cServerHandle overrides: + virtual void Close(void) override; + virtual bool IsListening(void) const override { return m_IsListening; } + +protected: + /** The callbacks used to notify about incoming connections. */ + cNetwork::cListenCallbacksPtr m_ListenCallbacks; + + /** The callbacks used to create new cTCPLink instances for incoming connections. */ + cTCPLink::cCallbacksPtr m_LinkCallbacks; + + /** The LibEvent handle representing the main listening socket. */ + evconnlistener * m_ConnListener; + + /** The LibEvent handle representing the secondary listening socket (only when side-by-side listening is needed, such as WinXP). */ + evconnlistener * m_SecondaryConnListener; + + /** Set to true when the server is initialized successfully and is listening for incoming connections. */ + bool m_IsListening; + + /** Container for all currently active connections on this server. */ + cTCPLinkImplPtrs m_Connections; + + /** Mutex protecting m_Connections againt multithreaded access. */ + cCriticalSection m_CS; + + /** Contains the error code for the failure to listen. Only valid for non-listening instances. */ + int m_ErrorCode; + + /** Contains the error message for the failure to listen. Only valid for non-listening instances. */ + AString m_ErrorMsg; + + + + /** Creates a new instance with the specified callbacks. + Initializes the internals, but doesn't start listening yet. */ + cServerHandleImpl( + cNetwork::cListenCallbacksPtr a_ListenCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks + ); + + /** Starts listening on the specified port. + Returns true if successful, false on failure. On failure, sets m_ErrorCode and m_ErrorMsg. */ + bool Listen(UInt16 a_Port); + + /** The callback called by LibEvent upon incoming connection. */ + static void Callback(evconnlistener * a_Listener, evutil_socket_t a_Socket, sockaddr * a_Addr, int a_Len, void * a_Self); + + /** Removes the specified link from m_Connections. + Called by cTCPLinkImpl when the link is terminated. */ + void RemoveLink(const cTCPLinkImpl * a_Link); +}; + + + + + diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp new file mode 100644 index 000000000..bcacc0569 --- /dev/null +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -0,0 +1,314 @@ + +// TCPLinkImpl.cpp + +// Implements the cTCPLinkImpl class implementing the TCP link functionality + +#include "Globals.h" +#include "TCPLinkImpl.h" +#include "NetworkSingleton.h" +#include "ServerHandleImpl.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cTCPLinkImpl: + +cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): + super(a_LinkCallbacks), + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE)), + m_Server(nullptr) +{ + // Create the LibEvent handle, but don't assign a socket to it yet (will be assigned within Connect() method): + bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); + bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); +} + + + + + +cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int a_AddrLen): + super(a_LinkCallbacks), + m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE)), + m_Server(a_Server) +{ + // Update the endpoint addresses: + UpdateLocalAddress(); + UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort); + + // Create the LibEvent handle: + bufferevent_setcb(m_BufferEvent, ReadCallback, nullptr, EventCallback, this); + bufferevent_enable(m_BufferEvent, EV_READ | EV_WRITE); +} + + + + + +cTCPLinkImpl::~cTCPLinkImpl() +{ + bufferevent_free(m_BufferEvent); +} + + + + + +cTCPLinkImplPtr cTCPLinkImpl::Connect(const AString & a_Host, UInt16 a_Port, cTCPLink::cCallbacksPtr a_LinkCallbacks, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks) +{ + ASSERT(a_LinkCallbacks != nullptr); + ASSERT(a_ConnectCallbacks != nullptr); + + // Create a new link: + cTCPLinkImplPtr res{new cTCPLinkImpl(a_LinkCallbacks)}; // Cannot use std::make_shared here, constructor is not accessible + res->m_ConnectCallbacks = a_ConnectCallbacks; + cNetworkSingleton::Get().AddLink(res); + + // If a_Host is an IP address, schedule a connection immediately: + sockaddr_storage sa; + int salen = static_cast<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; +} + + + + + +bool cTCPLinkImpl::Send(const void * a_Data, size_t a_Length) +{ + return (bufferevent_write(m_BufferEvent, a_Data, a_Length) == 0); +} + + + + + +void cTCPLinkImpl::Shutdown(void) +{ + #ifdef _WIN32 + shutdown(bufferevent_getfd(m_BufferEvent), SD_SEND); + #else + shutdown(bufferevent_getfd(m_BufferEvent), SHUT_WR); + #endif + bufferevent_disable(m_BufferEvent, EV_WRITE); +} + + + + + +void cTCPLinkImpl::Close(void) +{ + // Disable all events on the socket, but keep it alive: + bufferevent_disable(m_BufferEvent, EV_READ | EV_WRITE); + if (m_Server == nullptr) + { + cNetworkSingleton::Get().RemoveLink(this); + } + else + { + m_Server->RemoveLink(this); + } +} + + + + + + +void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self) +{ + ASSERT(a_Self != nullptr); + cTCPLinkImpl * Self = static_cast<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(*Self, data, length); + } +} + + + + + +void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) +{ + ASSERT(a_Self != nullptr); + cTCPLinkImpl * Self = static_cast<cTCPLinkImpl *>(a_Self); + + // If an error is reported, call the error callback: + if (a_What & BEV_EVENT_ERROR) + { + // Choose the proper callback to call based on whether we were waiting for connection or not: + if (Self->m_ConnectCallbacks != nullptr) + { + Self->m_ConnectCallbacks->OnError(EVUTIL_SOCKET_ERROR()); + } + else + { + Self->m_Callbacks->OnError(*Self, EVUTIL_SOCKET_ERROR()); + if (Self->m_Server == nullptr) + { + cNetworkSingleton::Get().RemoveLink(Self); + } + else + { + Self->m_Server->RemoveLink(Self); + } + } + return; + } + + // Pending connection succeeded, call the connection callback: + if (a_What & BEV_EVENT_CONNECTED) + { + if (Self->m_ConnectCallbacks != nullptr) + { + Self->m_ConnectCallbacks->OnSuccess(*Self); + // Reset the connect callbacks so that later errors get reported through the link callbacks: + Self->m_ConnectCallbacks.reset(); + return; + } + Self->UpdateLocalAddress(); + Self->UpdateRemoteAddress(); + } + + // If the connection has been closed, call the link callback and remove the connection: + if (a_What & BEV_EVENT_EOF) + { + Self->m_Callbacks->OnRemoteClosed(*Self); + if (Self->m_Server != nullptr) + { + Self->m_Server->RemoveLink(Self); + } + else + { + cNetworkSingleton::Get().RemoveLink(Self); + } + return; + } + + // Unknown event, report it: + LOGWARNING("cTCPLinkImpl: Unhandled LibEvent event %d (0x%x)", a_What, a_What); + ASSERT(!"cTCPLinkImpl: Unhandled LibEvent event"); +} + + + + + +void cTCPLinkImpl::UpdateAddress(const sockaddr * a_Address, int a_AddrLen, AString & a_IP, UInt16 & a_Port) +{ + // Based on the family specified in the address, use the correct datastructure to convert to IP string: + char IP[128]; + switch (a_Address->sa_family) + { + case AF_INET: // IPv4: + { + const sockaddr_in * sin = reinterpret_cast<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, + const UInt16 a_Port, + cNetwork::cConnectCallbacksPtr a_ConnectCallbacks, + cTCPLink::cCallbacksPtr a_LinkCallbacks +) +{ + // Add a connection request to the queue: + cTCPLinkImplPtr Conn = cTCPLinkImpl::Connect(a_Host, a_Port, a_LinkCallbacks, a_ConnectCallbacks); + return (Conn != nullptr); +} + + + + + diff --git a/src/OSSupport/TCPLinkImpl.h b/src/OSSupport/TCPLinkImpl.h new file mode 100644 index 000000000..3c60b1ad8 --- /dev/null +++ b/src/OSSupport/TCPLinkImpl.h @@ -0,0 +1,109 @@ + +// TCPLinkImpl.h + +// Declares the cTCPLinkImpl class implementing the TCP link functionality + +// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead + + + + + +#pragma once + +#include "Network.h" +#include <event2/event.h> +#include <event2/bufferevent.h> + + + + + +// fwd: +class cServerHandleImpl; +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. */ + cTCPLinkImpl(evutil_socket_t a_Socket, cCallbacksPtr a_LinkCallbacks, cServerHandleImpl * a_Server, const sockaddr * a_Address, int a_AddrLen); + + /** Destroys the LibEvent handle representing the link. */ + ~cTCPLinkImpl(); + + /** Queues a connection request to the specified host. + a_ConnectCallbacks must be valid. + Returns a link that has the connection request queued, or NULL for failure. */ + static cTCPLinkImplPtr Connect(const AString & a_Host, UInt16 a_Port, cTCPLink::cCallbacksPtr a_LinkCallbacks, cNetwork::cConnectCallbacksPtr a_ConnectCallbacks); + + // cTCPLink overrides: + virtual bool Send(const void * a_Data, size_t a_Length) override; + virtual AString GetLocalIP(void) const override { return m_LocalIP; } + virtual UInt16 GetLocalPort(void) const override { return m_LocalPort; } + virtual AString GetRemoteIP(void) const override { return m_RemoteIP; } + virtual UInt16 GetRemotePort(void) const override { return m_RemotePort; } + virtual void Shutdown(void) override; + virtual void Close(void) override; + +protected: + + /** Callbacks to call when the connection is established. + May be NULL if not used. Only used for outgoing connections (cNetwork::Connect()). */ + cNetwork::cConnectCallbacksPtr m_ConnectCallbacks; + + /** The LibEvent handle representing this connection. */ + bufferevent * m_BufferEvent; + + /** The server handle that has created this link. + Only valid for incoming connections, NULL for outgoing connections. */ + cServerHandleImpl * m_Server; + + /** The IP address of the local endpoint. Valid only after the socket has been connected. */ + AString m_LocalIP; + + /** The port of the local endpoint. Valid only after the socket has been connected. */ + UInt16 m_LocalPort; + + /** The IP address of the remote endpoint. Valid only after the socket has been connected. */ + AString m_RemoteIP; + + /** The port of the remote endpoint. Valid only after the socket has been connected. */ + UInt16 m_RemotePort; + + + /** Creates a new link to be queued to connect to a specified host:port. + Used for outgoing connections created using cNetwork::Connect(). + To be used only by the Connect() factory function. */ + cTCPLinkImpl(const cCallbacksPtr a_LinkCallbacks); + + /** Callback that LibEvent calls when there's data available from the remote peer. */ + static void ReadCallback(bufferevent * a_BufferEvent, void * a_Self); + + /** Callback that LibEvent calls when there's a non-data-related event on the socket. */ + static void EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self); + + /** Sets a_IP and a_Port to values read from a_Address, based on the correct address family. */ + static void UpdateAddress(const sockaddr * a_Address, int a_AddrLen, AString & a_IP, UInt16 & a_Port); + + /** Updates m_LocalIP and m_LocalPort based on the metadata read from the socket. */ + void UpdateLocalAddress(void); + + /** Updates m_RemoteIP and m_RemotePort based on the metadata read from the socket. */ + void UpdateRemoteAddress(void); +}; + + + + diff --git a/tests/Network/CMakeLists.txt b/tests/Network/CMakeLists.txt index 6bf291544..c0af50e2c 100644 --- a/tests/Network/CMakeLists.txt +++ b/tests/Network/CMakeLists.txt @@ -8,14 +8,34 @@ include_directories(${CMAKE_SOURCE_DIR}/lib/libevent/include) add_definitions(-DTEST_GLOBALS=1) # Create a single Network library that contains all the networking code: -add_library(Network +set (Network_SRCS ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp - ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/HostnameLookup.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/IPLookup.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/NetworkSingleton.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/ServerHandleImpl.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/TCPLinkImpl.cpp ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp ) +set (Network_HDRS + ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/HostnameLookup.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/IPLookup.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/Network.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/NetworkSingleton.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/ServerHandleImpl.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/TCPLinkImpl.h + ${CMAKE_SOURCE_DIR}/src/StringUtils.h +) + +add_library(Network + ${Network_SRCS} + ${Network_HDRS} +) + target_link_libraries(Network event_core event_extra) if (MSVC) target_link_libraries(Network ws2_32.lib) |