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