diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Bindings/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/Bindings/LuaState.cpp | 12 | ||||
-rw-r--r-- | src/Bindings/LuaState.h | 2 | ||||
-rw-r--r-- | src/Bindings/LuaUDPEndpoint.cpp | 221 | ||||
-rw-r--r-- | src/Bindings/LuaUDPEndpoint.h | 86 | ||||
-rw-r--r-- | src/Bindings/ManualBindings_Network.cpp | 328 | ||||
-rw-r--r-- | src/OSSupport/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/OSSupport/HostnameLookup.cpp | 12 | ||||
-rw-r--r-- | src/OSSupport/Network.h | 78 | ||||
-rw-r--r-- | src/OSSupport/UDPEndpointImpl.cpp | 608 | ||||
-rw-r--r-- | src/OSSupport/UDPEndpointImpl.h | 81 |
11 files changed, 1400 insertions, 32 deletions
diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt index 40b8d4bd9..4cc73b350 100644 --- a/src/Bindings/CMakeLists.txt +++ b/src/Bindings/CMakeLists.txt @@ -12,6 +12,7 @@ SET (SRCS LuaServerHandle.cpp LuaState.cpp LuaTCPLink.cpp + LuaUDPEndpoint.cpp LuaWindow.cpp ManualBindings.cpp ManualBindings_Network.cpp @@ -31,6 +32,7 @@ SET (HDRS LuaServerHandle.h LuaState.h LuaTCPLink.h + LuaUDPEndpoint.h LuaWindow.h ManualBindings.h Plugin.h diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index 81770058c..25c77a652 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -692,6 +692,18 @@ void cLuaState::Push(cLuaTCPLink * a_TCPLink) +void cLuaState::Push(cLuaUDPEndpoint * a_UDPEndpoint) +{ + ASSERT(IsValid()); + + tolua_pushusertype(m_LuaState, a_UDPEndpoint, "cUDPEndpoint"); + m_NumCurrentFunctionArgs += 1; +} + + + + + void cLuaState::Push(cMonster * a_Monster) { ASSERT(IsValid()); diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index 7fc3197eb..159483634 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -61,6 +61,7 @@ class cBlockEntity; class cBoundingBox; class cLuaTCPLink; class cLuaServerHandle; +class cLuaUDPEndpoint; typedef cBoundingBox * pBoundingBox; typedef cWorld * pWorld; @@ -212,6 +213,7 @@ public: void Push(cItems * a_Items); void Push(cLuaServerHandle * a_ServerHandle); void Push(cLuaTCPLink * a_TCPLink); + void Push(cLuaUDPEndpoint * a_UDPEndpoint); void Push(cMonster * a_Monster); void Push(cPickup * a_Pickup); void Push(cPlayer * a_Player); diff --git a/src/Bindings/LuaUDPEndpoint.cpp b/src/Bindings/LuaUDPEndpoint.cpp new file mode 100644 index 000000000..8637eeb4e --- /dev/null +++ b/src/Bindings/LuaUDPEndpoint.cpp @@ -0,0 +1,221 @@ + +// LuaUDPEndpoint.cpp + +// Implements the cLuaUDPEndpoint class representing a Lua wrapper for the cUDPEndpoint class and the callbacks it needs + +#include "Globals.h" +#include "LuaUDPEndpoint.h" + + + + + +cLuaUDPEndpoint::cLuaUDPEndpoint(cPluginLua & a_Plugin, int a_CallbacksTableStackPos): + m_Plugin(a_Plugin), + m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos) +{ + // Warn if the callbacks aren't valid: + if (!m_Callbacks.IsValid()) + { + LOGWARNING("cLuaUDPEndpoint in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str()); + cPluginLua::cOperation Op(m_Plugin); + Op().LogStackTrace(); + } +} + + + + + +cLuaUDPEndpoint::~cLuaUDPEndpoint() +{ + // If the endpoint is still open, close it: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->Close(); + } + + Terminated(); +} + + + + + +bool cLuaUDPEndpoint::Open(UInt16 a_Port, cLuaUDPEndpointPtr a_Self) +{ + ASSERT(m_Self == nullptr); // Must not be opened yet + ASSERT(m_Endpoint == nullptr); + + m_Self = a_Self; + m_Endpoint = cNetwork::CreateUDPEndpoint(a_Port, *this); + return m_Endpoint->IsOpen(); +} + + + + + +bool cLuaUDPEndpoint::Send(const AString & a_Data, const AString & a_RemotePeer, UInt16 a_RemotePort) +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint == nullptr) + { + return false; + } + + // Send the data: + return Endpoint->Send(a_Data, a_RemotePeer, a_RemotePort); +} + + + + + +UInt16 cLuaUDPEndpoint::GetPort(void) const +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint == nullptr) + { + return 0; + } + + // Get the port: + return Endpoint->GetPort(); +} + + + + + +bool cLuaUDPEndpoint::IsOpen(void) const +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint == nullptr) + { + // No endpoint means that we're not open + return false; + } + + // Get the state: + return Endpoint->IsOpen(); +} + + + + + +void cLuaUDPEndpoint::Close(void) +{ + // If the endpoint is still open, close it: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->Close(); + m_Endpoint.reset(); + } + + Terminated(); +} + + + + + +void cLuaUDPEndpoint::EnableBroadcasts(void) +{ + // Safely grab a copy of the endpoint: + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->EnableBroadcasts(); + } +} + + + + + +void cLuaUDPEndpoint::Release(void) +{ + // Close the endpoint, if not already closed: + Close(); + + // Allow self to deallocate: + m_Self.reset(); +} + + + + + +void cLuaUDPEndpoint::Terminated(void) +{ + // Disable the callbacks: + if (m_Callbacks.IsValid()) + { + m_Callbacks.UnRef(); + } + + // If the endpoint is still open, close it: + { + cUDPEndpointPtr Endpoint = m_Endpoint; + if (Endpoint != nullptr) + { + Endpoint->Close(); + m_Endpoint.reset(); + } + } +} + + + + + +void cLuaUDPEndpoint::OnReceivedData(const char * a_Data, size_t a_NumBytes, const AString & a_RemotePeer, UInt16 a_RemotePort) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnReceivedData"), this, AString(a_Data, a_NumBytes), a_RemotePeer, a_RemotePort)) + { + LOGINFO("cUDPEndpoint OnReceivedData callback failed in plugin %s.", m_Plugin.GetName().c_str()); + } +} + + + + + +void cLuaUDPEndpoint::OnError(int a_ErrorCode, const AString & a_ErrorMsg) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Call the callback: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), a_ErrorCode, a_ErrorMsg)) + { + LOGINFO("cUDPEndpoint OnError() callback failed in plugin %s; the endpoint error is %d (%s).", + m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str() + ); + } + + Terminated(); +} + + + + diff --git a/src/Bindings/LuaUDPEndpoint.h b/src/Bindings/LuaUDPEndpoint.h new file mode 100644 index 000000000..0587491ab --- /dev/null +++ b/src/Bindings/LuaUDPEndpoint.h @@ -0,0 +1,86 @@ + +// LuaUDPEndpoint.h + +// Declares the cLuaUDPEndpoint class representing a Lua wrapper for the cUDPEndpoint class and the callbacks it needs + + + + + +#pragma once + +#include "../OSSupport/Network.h" +#include "PluginLua.h" + + + + + +// fwd: +class cLuaUDPEndpoint; +typedef SharedPtr<cLuaUDPEndpoint> cLuaUDPEndpointPtr; + + + + + +class cLuaUDPEndpoint: + public cUDPEndpoint::cCallbacks +{ +public: + /** Creates a new instance of the endpoint, attached to the specified plugin and wrapping the callbacks that are in a table at the specified stack pos. */ + cLuaUDPEndpoint(cPluginLua & a_Plugin, int a_CallbacksTableStackPos); + + ~cLuaUDPEndpoint(); + + /** Opens the endpoint so that it starts listening for incoming data on the specified port. + a_Self is the shared pointer to self that the object keeps to keep itself alive for as long as it needs (for Lua). */ + bool Open(UInt16 a_Port, cLuaUDPEndpointPtr a_Self); + + /** Sends the data contained in the string to the specified remote peer. + Returns true if successful, false on immediate failure (queueing the data failed etc.) */ + bool Send(const AString & a_Data, const AString & a_RemotePeer, UInt16 a_RemotePort); + + /** Returns the port on which endpoint is listening for incoming data. */ + UInt16 GetPort(void) const; + + /** Returns true if the endpoint is open for incoming data. */ + bool IsOpen(void) const; + + /** Closes the UDP listener. */ + void Close(void); + + /** Enables outgoing broadcasts to be made using this endpoint. */ + void EnableBroadcasts(void); + + /** Called when Lua garbage-collects the object. + Releases the internal SharedPtr to self, so that the instance may be deallocated. */ + void Release(void); + +protected: + /** The plugin for which the link is created. */ + cPluginLua & m_Plugin; + + /** The Lua table that holds the callbacks to be invoked. */ + cLuaState::cRef m_Callbacks; + + /** SharedPtr to self, so that the object can keep itself alive for as long as it needs (for Lua). */ + cLuaUDPEndpointPtr m_Self; + + /** The underlying network endpoint. + May be nullptr. */ + cUDPEndpointPtr m_Endpoint; + + + /** Common code called when the endpoint is considered as terminated. + Releases m_Endpoint and m_Callbacks, each when applicable. */ + void Terminated(void); + + // cUDPEndpoint::cCallbacks overrides: + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; + virtual void OnReceivedData(const char * a_Data, size_t a_Size, const AString & a_RemotePeer, UInt16 a_RemotePort) override; +}; + + + + diff --git a/src/Bindings/ManualBindings_Network.cpp b/src/Bindings/ManualBindings_Network.cpp index 30a34815c..a628eb9ca 100644 --- a/src/Bindings/ManualBindings_Network.cpp +++ b/src/Bindings/ManualBindings_Network.cpp @@ -11,6 +11,7 @@ #include "LuaTCPLink.h" #include "LuaNameLookup.h" #include "LuaServerHandle.h" +#include "LuaUDPEndpoint.h" @@ -73,6 +74,61 @@ static int tolua_cNetwork_Connect(lua_State * L) +/** Binds cNetwork::CreateUDPEndpoint */ +static int tolua_cNetwork_CreateUDPEndpoint(lua_State * L) +{ + // Function signature: + // cNetwork:CreateUDPEndpoint(Port, Callbacks) -> cUDPEndpoint + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cNetwork") || + !S.CheckParamNumber(2) || + !S.CheckParamTable(3) || + !S.CheckParamEnd(4) + ) + { + return 0; + } + + // Get the plugin instance: + cPluginLua * Plugin = GetLuaPlugin(L); + if (Plugin == nullptr) + { + // An error message has been already printed in GetLuaPlugin() + S.Push(false); + return 1; + } + + // Read the params: + int Port; + S.GetStackValues(2, Port); + + // Check validity: + if ((Port < 0) || (Port > 65535)) + { + LOGWARNING("cNetwork:CreateUDPEndpoint() called with invalid port (%d), failing the request.", Port); + S.Push(false); + return 1; + } + + // Create the LuaUDPEndpoint glue class: + auto Endpoint = std::make_shared<cLuaUDPEndpoint>(*Plugin, 3); + Endpoint->Open(Port, Endpoint); + + // Register the endpoint to be garbage-collected by Lua: + tolua_pushusertype(L, Endpoint.get(), "cUDPEndpoint"); + tolua_register_gc(L, lua_gettop(L)); + + // Return the endpoint object: + S.Push(Endpoint.get()); + return 1; +} + + + + + /** Binds cNetwork::HostnameToIP */ static int tolua_cNetwork_HostnameToIP(lua_State * L) { @@ -159,7 +215,7 @@ static int tolua_cNetwork_IPToHostname(lua_State * L) static int tolua_cNetwork_Listen(lua_State * L) { // Function signature: - // cNetwork:Listen(Port, Callbacks) -> bool + // cNetwork:Listen(Port, Callbacks) -> cServerHandle cLuaState S(L); if ( @@ -212,6 +268,90 @@ static int tolua_cNetwork_Listen(lua_State * L) //////////////////////////////////////////////////////////////////////////////// +// cServerHandle bindings (routed through cLuaServerHandle): + +/** Called when Lua destroys the object instance. +Close the server and let it deallocate on its own (it's in a SharedPtr). */ +static int tolua_collect_cServerHandle(lua_State * L) +{ + cLuaServerHandle * Srv = static_cast<cLuaServerHandle *>(tolua_tousertype(L, 1, nullptr)); + Srv->Release(); + return 0; +} + + + + + +/** Binds cLuaServerHandle::Close */ +static int tolua_cServerHandle_Close(lua_State * L) +{ + // Function signature: + // ServerInstance:Close() + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the server handle: + cLuaServerHandle * Srv; + if (lua_isnil(L, 1)) + { + LOGWARNING("cServerHandle:Close(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1)); + + // Close it: + Srv->Close(); + return 0; +} + + + + + +/** Binds cLuaServerHandle::IsListening */ +static int tolua_cServerHandle_IsListening(lua_State * L) +{ + // Function signature: + // ServerInstance:IsListening() -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the server handle: + cLuaServerHandle * Srv; + if (lua_isnil(L, 1)) + { + LOGWARNING("cServerHandle:IsListening(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1)); + + // Close it: + S.Push(Srv->IsListening()); + return 1; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// // cTCPLink bindings (routed through cLuaTCPLink): /** Binds cLuaTCPLink::Close */ @@ -549,14 +689,15 @@ static int tolua_cTCPLink_StartTLSServer(lua_State * L) //////////////////////////////////////////////////////////////////////////////// -// cServerHandle bindings (routed through cLuaServerHandle): +// cUDPEndpoint bindings (routed through cLuaUDPEndpoint): /** Called when Lua destroys the object instance. -Close the server and let it deallocate on its own (it's in a SharedPtr). */ -static int tolua_collect_cServerHandle(lua_State * L) +Close the endpoint and let it deallocate on its own (it's in a SharedPtr). */ +static int tolua_collect_cUDPEndpoint(lua_State * L) { - cLuaServerHandle * Srv = static_cast<cLuaServerHandle *>(tolua_tousertype(L, 1, nullptr)); - Srv->Release(); + LOGD("Lua: Collecting cUDPEndpoint"); + cLuaUDPEndpoint * Endpoint = static_cast<cLuaUDPEndpoint *>(tolua_tousertype(L, 1, nullptr)); + Endpoint->Release(); return 0; } @@ -564,33 +705,32 @@ static int tolua_collect_cServerHandle(lua_State * L) -/** Binds cLuaServerHandle::Close */ -static int tolua_cServerHandle_Close(lua_State * L) +/** Binds cLuaUDPEndpoint::Close */ +static int tolua_cUDPEndpoint_Close(lua_State * L) { // Function signature: - // ServerInstance:Close() + // EndpointInstance:Close() cLuaState S(L); if ( - !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamUserType(1, "cUDPEndpoint") || !S.CheckParamEnd(2) ) { return 0; } - // Get the server handle: - cLuaServerHandle * Srv; + // Get the endpoint: if (lua_isnil(L, 1)) { - LOGWARNING("cServerHandle:Close(): invalid server handle object. Stack trace:"); + LOGWARNING("cUDPEndpoint:Close(): invalid endpoint object. Stack trace:"); S.LogStackTrace(); return 0; } - Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1)); + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); // Close it: - Srv->Close(); + Endpoint->Close(); return 0; } @@ -598,33 +738,147 @@ static int tolua_cServerHandle_Close(lua_State * L) -/** Binds cLuaServerHandle::IsListening */ -static int tolua_cServerHandle_IsListening(lua_State * L) +/** Binds cLuaUDPEndpoint::EnableBroadcasts */ +static int tolua_cUDPEndpoint_EnableBroadcasts(lua_State * L) { // Function signature: - // ServerInstance:IsListening() -> bool + // EndpointInstance:EnableBroadcasts() cLuaState S(L); if ( - !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamUserType(1, "cUDPEndpoint") || !S.CheckParamEnd(2) ) { return 0; } - // Get the server handle: - cLuaServerHandle * Srv; + // Get the endpoint: if (lua_isnil(L, 1)) { - LOGWARNING("cServerHandle:IsListening(): invalid server handle object. Stack trace:"); + LOGWARNING("cUDPEndpoint:EnableBroadcasts(): invalid endpoint object. Stack trace:"); S.LogStackTrace(); return 0; } - Srv = *static_cast<cLuaServerHandle **>(lua_touserdata(L, 1)); + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Enable the broadcasts: + Endpoint->EnableBroadcasts(); + return 0; +} + + + + + +/** Binds cLuaUDPEndpoint::GetPort */ +static int tolua_cUDPEndpoint_GetPort(lua_State * L) +{ + // Function signature: + // Endpoint:GetPort() -> number + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the endpoint: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:GetPort(): invalid endpoint object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Get the Port: + S.Push(Endpoint->GetPort()); + return 1; +} + + + + + +/** Binds cLuaUDPEndpoint::IsOpen */ +static int tolua_cUDPEndpoint_IsOpen(lua_State * L) +{ + // Function signature: + // Endpoint:IsOpen() -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the endpoint: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:IsListening(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); // Close it: - S.Push(Srv->IsListening()); + S.Push(Endpoint->IsOpen()); + return 1; +} + + + + + +/** Binds cLuaUDPEndpoint::Send */ +static int tolua_cUDPEndpoint_Send(lua_State * L) +{ + // Function signature: + // LinkInstance:Send(DataString) + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cUDPEndpoint") || + !S.CheckParamString(2, 3) || + !S.CheckParamNumber(4) || + !S.CheckParamEnd(5) + ) + { + return 0; + } + + // Get the link: + if (lua_isnil(L, 1)) + { + LOGWARNING("cUDPEndpoint:Send(): invalid endpoint object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + auto Endpoint = *static_cast<cLuaUDPEndpoint **>(lua_touserdata(L, 1)); + + // Get the data to send: + AString Data, RemotePeer; + int RemotePort; + S.GetStackValues(2, Data, RemotePeer, RemotePort); + + // Check the port: + if ((RemotePort < 0) || (RemotePort > USHRT_MAX)) + { + LOGWARNING("cUDPEndpoint:Send() called with invalid port (%d), failing.", RemotePort); + S.LogStackTrace(); + S.Push(false); + return 1; + } + + // Send the data: + S.Push(Endpoint->Send(Data, RemotePeer, static_cast<UInt16>(RemotePort))); return 1; } @@ -644,13 +898,21 @@ void ManualBindings::BindNetwork(lua_State * tolua_S) tolua_cclass(tolua_S, "cTCPLink", "cTCPLink", "", nullptr); tolua_usertype(tolua_S, "cServerHandle"); tolua_cclass(tolua_S, "cServerHandle", "cServerHandle", "", tolua_collect_cServerHandle); + tolua_usertype(tolua_S, "cUDPEndpoint"); + tolua_cclass(tolua_S, "cUDPEndpoint", "cUDPEndpoint", "", tolua_collect_cUDPEndpoint); // Fill in the functions (alpha-sorted): tolua_beginmodule(tolua_S, "cNetwork"); - tolua_function(tolua_S, "Connect", tolua_cNetwork_Connect); - tolua_function(tolua_S, "HostnameToIP", tolua_cNetwork_HostnameToIP); - tolua_function(tolua_S, "IPToHostname", tolua_cNetwork_IPToHostname); - tolua_function(tolua_S, "Listen", tolua_cNetwork_Listen); + tolua_function(tolua_S, "Connect", tolua_cNetwork_Connect); + tolua_function(tolua_S, "CreateUDPEndpoint", tolua_cNetwork_CreateUDPEndpoint); + tolua_function(tolua_S, "HostnameToIP", tolua_cNetwork_HostnameToIP); + tolua_function(tolua_S, "IPToHostname", tolua_cNetwork_IPToHostname); + tolua_function(tolua_S, "Listen", tolua_cNetwork_Listen); + tolua_endmodule(tolua_S); + + tolua_beginmodule(tolua_S, "cServerHandle"); + tolua_function(tolua_S, "Close", tolua_cServerHandle_Close); + tolua_function(tolua_S, "IsListening", tolua_cServerHandle_IsListening); tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cTCPLink"); @@ -665,10 +927,14 @@ void ManualBindings::BindNetwork(lua_State * tolua_S) tolua_function(tolua_S, "StartTLSServer", tolua_cTCPLink_StartTLSServer); tolua_endmodule(tolua_S); - tolua_beginmodule(tolua_S, "cServerHandle"); - tolua_function(tolua_S, "Close", tolua_cServerHandle_Close); - tolua_function(tolua_S, "IsListening", tolua_cServerHandle_IsListening); + tolua_beginmodule(tolua_S, "cUDPEndpoint"); + tolua_function(tolua_S, "Close", tolua_cUDPEndpoint_Close); + tolua_function(tolua_S, "EnableBroadcasts", tolua_cUDPEndpoint_EnableBroadcasts); + tolua_function(tolua_S, "GetPort", tolua_cUDPEndpoint_GetPort); + tolua_function(tolua_S, "IsOpen", tolua_cUDPEndpoint_IsOpen); + tolua_function(tolua_S, "Send", tolua_cUDPEndpoint_Send); tolua_endmodule(tolua_S); + } diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index 6f609c519..81b37ef0e 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -18,6 +18,7 @@ SET (SRCS ServerHandleImpl.cpp StackTrace.cpp TCPLinkImpl.cpp + UDPEndpointImpl.cpp ) SET (HDRS @@ -36,6 +37,7 @@ SET (HDRS ServerHandleImpl.h StackTrace.h TCPLinkImpl.h + UDPEndpointImpl.h ) if(NOT MSVC) diff --git a/src/OSSupport/HostnameLookup.cpp b/src/OSSupport/HostnameLookup.cpp index 3a2997ffd..0944153be 100644 --- a/src/OSSupport/HostnameLookup.cpp +++ b/src/OSSupport/HostnameLookup.cpp @@ -69,12 +69,24 @@ void cHostnameLookup::Callback(int a_ErrCode, evutil_addrinfo * a_Addr, void * a case AF_INET: // IPv4 { sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(a_Addr->ai_addr); + if (!Self->m_Callbacks->OnNameResolvedV4(Self->m_Hostname, sin)) + { + // Callback indicated that the IP shouldn't be serialized to a string, just continue with the next address: + HasResolved = true; + continue; + } 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); + if (!Self->m_Callbacks->OnNameResolvedV6(Self->m_Hostname, sin)) + { + // Callback indicated that the IP shouldn't be serialized to a string, just continue with the next address: + HasResolved = true; + continue; + } evutil_inet_ntop(AF_INET6, &(sin->sin6_addr), IP, sizeof(IP)); break; } diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index e883dfb29..5dd596223 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -130,6 +130,64 @@ public: +/** Interface that provides methods available on UDP communication endpoints. */ +class cUDPEndpoint +{ +public: + /** Interface for the callbacks for events that can happen on the endpoint. */ + class cCallbacks + { + public: + // Force a virtual destructor in all descendants: + virtual ~cCallbacks() {} + + /** Called when an error occurs on the endpoint. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) = 0; + + /** Called when there is an incoming datagram from a remote host. */ + virtual void OnReceivedData(const char * a_Data, size_t a_Size, const AString & a_RemoteHost, UInt16 a_RemotePort) = 0; + }; + + + // Force a virtual destructor for all descendants: + virtual ~cUDPEndpoint() {} + + /** Closes the underlying socket. + Note that there still might be callbacks in-flight after this method returns. */ + virtual void Close(void) = 0; + + /** Returns true if the endpoint is open. */ + virtual bool IsOpen(void) const = 0; + + /** Returns the local port to which the underlying socket is bound. */ + virtual UInt16 GetPort(void) const = 0; + + /** Sends the specified payload in a single UDP datagram to the specified host+port combination. + Note that in order to send to a broadcast address, you need to call EnableBroadcasts() first. */ + virtual bool Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) = 0; + + /** Marks the socket as capable of sending broadcast, using whatever OS API is needed. + Without this call, sending to a broadcast address using Send() may fail. */ + virtual void EnableBroadcasts(void) = 0; + +protected: + /** The callbacks used for various events on the endpoint. */ + cCallbacks & m_Callbacks; + + + /** Creates a new instance of an endpoint, with the specified callbacks. */ + cUDPEndpoint(cCallbacks & a_Callbacks): + m_Callbacks(a_Callbacks) + { + } +}; + +typedef SharedPtr<cUDPEndpoint> cUDPEndpointPtr; + + + + + class cNetwork { public: @@ -183,9 +241,22 @@ public: /** 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. */ + a_IP may be either an IPv4 or an IPv6 address with their proper formatting. + Each call to OnNameResolved() is preceded by a call to either OnNameResolvedV4() or OnNameResolvedV6(). */ virtual void OnNameResolved(const AString & a_Name, const AString & a_IP) = 0; + /** Called when the hostname is successfully resolved into an IPv4 address. + May be called multiple times if a name resolves to multiple addresses. + Each call to OnNameResolvedV4 is followed by OnNameResolved with the IP address serialized to a string. + If this callback returns false, the OnNameResolved() call is skipped for this address. */ + virtual bool OnNameResolvedV4(const AString & a_Name, const sockaddr_in * a_IP) { return true; } + + /** Called when the hostname is successfully resolved into an IPv6 address. + May be called multiple times if a name resolves to multiple addresses. + Each call to OnNameResolvedV4 is followed by OnNameResolved with the IP address serialized to a string. + If this callback returns false, the OnNameResolved() call is skipped for this address. */ + virtual bool OnNameResolvedV6(const AString & a_Name, const sockaddr_in6 * a_IP) { return true; } + /** 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; @@ -242,6 +313,11 @@ public: const AString & a_IP, cResolveNameCallbacksPtr a_Callbacks ); + + /** Opens up an UDP endpoint for sending and receiving UDP datagrams on the specified port. + If a_Port is 0, the OS is free to assign any port number it likes to the endpoint. + Returns the endpoint object that can be interacted with. */ + static cUDPEndpointPtr CreateUDPEndpoint(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks); }; diff --git a/src/OSSupport/UDPEndpointImpl.cpp b/src/OSSupport/UDPEndpointImpl.cpp new file mode 100644 index 000000000..c5190bcf6 --- /dev/null +++ b/src/OSSupport/UDPEndpointImpl.cpp @@ -0,0 +1,608 @@ + +// UDPEndpointImpl.cpp + +// Implements the cUDPEndpointImpl class representing an implementation of an endpoint in UDP communication + +#include "Globals.h" +#include "UDPEndpointImpl.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 +} + + + + + +/** Converts a_SrcAddr in IPv4 format to a_DstAddr in IPv6 format (using IPv4-mapped IPv6). */ +static void ConvertIPv4ToMappedIPv6(sockaddr_in & a_SrcAddr, sockaddr_in6 & a_DstAddr) +{ + memset(&a_DstAddr, 0, sizeof(a_DstAddr)); + a_DstAddr.sin6_family = AF_INET6; + a_DstAddr.sin6_addr.s6_addr[10] = 0xff; + a_DstAddr.sin6_addr.s6_addr[11] = 0xff; + a_DstAddr.sin6_addr.s6_addr[12] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 0) & 0xff); + a_DstAddr.sin6_addr.s6_addr[13] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 8) & 0xff); + a_DstAddr.sin6_addr.s6_addr[14] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 16) & 0xff); + a_DstAddr.sin6_addr.s6_addr[15] = static_cast<Byte>((a_SrcAddr.sin_addr.s_addr >> 24) & 0xff); + a_DstAddr.sin6_port = a_SrcAddr.sin_port; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cUDPSendAfterLookup: + +/** A hostname-to-IP resolver callback that sends the data stored within to the resolved IP address. +This is used for sending UDP datagrams to hostnames, so that the cUDPEndpoint::Send() doesn't block. +Instead an instance of this callback is queued for resolving and the data is sent once the IP is resolved. */ +class cUDPSendAfterLookup: + public cNetwork::cResolveNameCallbacks +{ +public: + cUDPSendAfterLookup(const AString & a_Data, UInt16 a_Port, evutil_socket_t a_MainSock, evutil_socket_t a_SecondSock, bool a_IsMainSockIPv6): + m_Data(a_Data), + m_Port(a_Port), + m_MainSock(a_MainSock), + m_SecondSock(a_SecondSock), + m_IsMainSockIPv6(a_IsMainSockIPv6), + m_HasIPv4(false), + m_HasIPv6(false) + { + } + +protected: + /** The data to send after the hostname is resolved. */ + AString m_Data; + + /** The port to which to send the data. */ + UInt16 m_Port; + + /** The primary socket to use for sending. */ + evutil_socket_t m_MainSock; + + /** The secondary socket to use for sending, if needed by the OS. */ + evutil_socket_t m_SecondSock; + + /** True if m_MainSock is an IPv6 socket. */ + bool m_IsMainSockIPv6; + + /** The IPv4 address resolved, if any. */ + sockaddr_in m_AddrIPv4; + + /** Set to true if the name resolved to an IPv4 address. */ + bool m_HasIPv4; + + /** The IPv6 address resolved, if any. */ + sockaddr_in6 m_AddrIPv6; + + /** Set to true if the name resolved to an IPv6 address. */ + bool m_HasIPv6; + + + // cNetwork::cResolveNameCallbacks overrides: + virtual void OnNameResolved(const AString & a_Name, const AString & a_PI) override + { + // Not needed + } + + virtual bool OnNameResolvedV4(const AString & a_Name, const sockaddr_in * a_IP) override + { + if (!m_HasIPv4) + { + m_AddrIPv4 = *a_IP; + m_AddrIPv4.sin_port = htons(m_Port); + m_HasIPv4 = true; + } + + // Don't want OnNameResolved() callback + return false; + } + + virtual bool OnNameResolvedV6(const AString & a_Name, const sockaddr_in6 * a_IP) override + { + if (!m_HasIPv6) + { + m_AddrIPv6 = *a_IP; + m_AddrIPv6.sin6_port = htons(m_Port); + m_HasIPv6 = true; + } + + // Don't want OnNameResolved() callback + return false; + } + + virtual void OnFinished(void) override + { + // Send the actual data, through the correct socket and using the correct resolved address: + if (m_IsMainSockIPv6) + { + if (m_HasIPv6) + { + sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv6), static_cast<socklen_t>(sizeof(m_AddrIPv6))); + } + else if (m_HasIPv4) + { + // If the secondary socket is valid, it is an IPv4 socket, so use that: + if (m_SecondSock != -1) + { + sendto(m_SecondSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv4), static_cast<socklen_t>(sizeof(m_AddrIPv4))); + } + else + { + // Need an address conversion from IPv4 to IPv6-mapped-IPv4: + ConvertIPv4ToMappedIPv6(m_AddrIPv4, m_AddrIPv6); + sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv6), static_cast<socklen_t>(sizeof(m_AddrIPv6))); + } + } + else + { + LOGD("UDP endpoint queued sendto: Name not resolved"); + return; + } + } + else // m_IsMainSockIPv6 + { + // Main socket is IPv4 only, only allow IPv4 dst address: + if (!m_HasIPv4) + { + LOGD("UDP endpoint queued sendto: Name not resolved to IPv4 for an IPv4-only socket"); + return; + } + sendto(m_MainSock, m_Data.data(), static_cast<socklen_t>(m_Data.size()), 0, reinterpret_cast<const sockaddr *>(&m_AddrIPv4), static_cast<socklen_t>(sizeof(m_AddrIPv4))); + } + } + + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + // Nothing needed + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cUDPEndpointImpl: + +cUDPEndpointImpl::cUDPEndpointImpl(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks): + super(a_Callbacks), + m_Port(0), + m_MainSock(-1), + m_IsMainSockIPv6(true), + m_SecondarySock(-1), + m_MainEvent(nullptr), + m_SecondaryEvent(nullptr) +{ + Open(a_Port); +} + + + + + +void cUDPEndpointImpl::Close(void) +{ + if (m_Port == 0) + { + // Already closed + return; + } + + // Close the LibEvent handles: + if (m_MainEvent != nullptr) + { + event_free(m_MainEvent); + m_MainEvent = nullptr; + } + if (m_SecondaryEvent != nullptr) + { + event_free(m_SecondaryEvent); + m_SecondaryEvent = nullptr; + } + + // Close the OS sockets: + evutil_closesocket(m_MainSock); + m_MainSock = -1; + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + + // Mark as closed: + m_Port = 0; +} + + + + + +bool cUDPEndpointImpl::IsOpen(void) const +{ + return (m_Port != 0); +} + + + + + +UInt16 cUDPEndpointImpl::GetPort(void) const +{ + return m_Port; +} + + + + + +bool cUDPEndpointImpl::Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) +{ + // If a_Host is an IP address, send the data directly: + sockaddr_storage sa; + int salen = static_cast<int>(sizeof(sa)); + memset(&sa, 0, sizeof(sa)); + if (evutil_parse_sockaddr_port(a_Host.c_str(), reinterpret_cast<sockaddr *>(&sa), &salen) != 0) + { + // a_Host is a hostname, we need to do a lookup first: + auto queue = std::make_shared<cUDPSendAfterLookup>(a_Payload, a_Port, m_MainSock, m_SecondarySock, m_IsMainSockIPv6); + return cNetwork::HostnameToIP(a_Host, queue); + } + + // a_Host is an IP address and has been parsed into "sa" + // Insert the correct port and send data: + int NumSent; + switch (sa.ss_family) + { + case AF_INET: + { + reinterpret_cast<sockaddr_in *>(&sa)->sin_port = htons(a_Port); + if (m_IsMainSockIPv6) + { + if (IsValidSocket(m_SecondarySock)) + { + // The secondary socket, which is always IPv4, is present: + NumSent = static_cast<int>(sendto(m_SecondarySock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen))); + } + else + { + // Need to convert IPv4 to IPv6 address before sending: + sockaddr_in6 IPv6; + ConvertIPv4ToMappedIPv6(*reinterpret_cast<sockaddr_in *>(&sa), IPv6); + NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&IPv6), static_cast<socklen_t>(sizeof(IPv6)))); + } + } + else + { + NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen))); + } + break; + } + + case AF_INET6: + { + reinterpret_cast<sockaddr_in6 *>(&sa)->sin6_port = htons(a_Port); + NumSent = static_cast<int>(sendto(m_MainSock, a_Payload.data(), static_cast<socklen_t>(a_Payload.size()), 0, reinterpret_cast<const sockaddr *>(&sa), static_cast<socklen_t>(salen))); + break; + } + default: + { + LOGD("UDP sendto: Invalid address family for address \"%s\".", a_Host.c_str()); + return false; + } + } + return (NumSent > 0); +} + + + + + +void cUDPEndpointImpl::EnableBroadcasts(void) +{ + ASSERT(IsOpen()); + + // Enable broadcasts on the main socket: + // Some OSes use ints, others use chars, so we try both + int broadcastInt = 1; + char broadcastChar = 1; + // (Note that Windows uses const char * for option values, while Linux uses const void *) + if (setsockopt(m_MainSock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&broadcastInt), sizeof(broadcastInt)) == -1) + { + if (setsockopt(m_MainSock, SOL_SOCKET, SO_BROADCAST, &broadcastChar, sizeof(broadcastChar)) == -1) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot enable broadcasts on port %d: %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + return; + } + + // Enable broadcasts on the secondary socket, if opened (use char, it worked for primary): + if (IsValidSocket(m_SecondarySock)) + { + if (setsockopt(m_SecondarySock, SOL_SOCKET, SO_BROADCAST, &broadcastChar, sizeof(broadcastChar)) == -1) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot enable broadcasts on port %d (secondary): %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + } + } + return; + } + + // Enable broadcasts on the secondary socket, if opened (use int, it worked for primary): + if (IsValidSocket(m_SecondarySock)) + { + if (setsockopt(m_SecondarySock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&broadcastInt), sizeof(broadcastInt)) == -1) + { + int err = EVUTIL_SOCKET_ERROR(); + LOGWARNING("Cannot enable broadcasts on port %d (secondary): %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + } + } +} + + + + + +void cUDPEndpointImpl::Open(UInt16 a_Port) +{ + ASSERT(m_Port == 0); // Must not be already open + + // 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; + m_IsMainSockIPv6 = true; + m_MainSock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + + int err; + if (!IsValidSocket(m_MainSock)) + { + // Failed to create IPv6 socket, create an IPv4 one instead: + m_IsMainSockIPv6 = false; + err = EVUTIL_SOCKET_ERROR(); + LOGD("Failed to create IPv6 MainSock: %d (%s)", err, evutil_socket_error_to_string(err)); + m_MainSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (!IsValidSocket(m_MainSock)) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot create UDP socket for port %d: %s", a_Port, evutil_socket_error_to_string(err))); + return; + } + + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(m_MainSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOG("UDP Port %d cannot be made reusable: %d (%s). Restarting the server might not work.", + a_Port, err, evutil_socket_error_to_string(err) + ); + } + + // 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(m_MainSock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot bind UDP port %d: %s", a_Port, evutil_socket_error_to_string(err))); + evutil_closesocket(m_MainSock); + return; + } + } + 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(m_MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero)); + err = EVUTIL_SOCKET_ERROR(); + NeedsTwoSockets = ((res == SOCKET_ERROR) && (err == WSAENOPROTOOPT)); + #else + setsockopt(m_MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero)); + #endif + + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(m_MainSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + LOG("UDP Port %d cannot be made reusable: %d (%s). Restarting the server might not work.", + a_Port, err, evutil_socket_error_to_string(err) + ); + } + + // 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(m_MainSock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot bind to UDP port %d: %s", a_Port, evutil_socket_error_to_string(err))); + evutil_closesocket(m_MainSock); + return; + } + } + if (evutil_make_socket_nonblocking(m_MainSock) != 0) + { + err = EVUTIL_SOCKET_ERROR(); + m_Callbacks.OnError(err, Printf("Cannot make socket on UDP port %d nonblocking: %s", a_Port, evutil_socket_error_to_string(err))); + evutil_closesocket(m_MainSock); + return; + } + m_MainEvent = event_new(cNetworkSingleton::Get().GetEventBase(), m_MainSock, EV_READ | EV_PERSIST, RawCallback, this); + event_add(m_MainEvent, nullptr); + + // Read the actual port number on which the socket is listening: + { + sockaddr_storage name; + socklen_t namelen = static_cast<socklen_t>(sizeof(name)); + getsockname(m_MainSock, reinterpret_cast<sockaddr *>(&name), &namelen); + switch (name.ss_family) + { + case AF_INET: + { + sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(&name); + m_Port = ntohs(sin->sin_port); + break; + } + case AF_INET6: + { + sockaddr_in6 * sin6 = reinterpret_cast<sockaddr_in6 *>(&name); + m_Port = ntohs(sin6->sin6_port); + break; + } + } + } + + // If we don't need to create another socket, bail out now: + if (!NeedsTwoSockets) + { + return; + } + + // If a secondary socket is required (WinXP dual-stack), create it here: + LOGD("Creating a second UDP socket for IPv4"); + m_SecondarySock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if (!IsValidSocket(m_SecondarySock)) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("Socket creation failed for secondary UDP socket for port %d: %d, %s", m_Port, err, evutil_socket_error_to_string(err)); + return; + } + + // Allow the port to be reused right after the socket closes: + if (evutil_make_listen_socket_reuseable(m_SecondarySock) != 0) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("UDP Port %d cannot be made reusable (second socket): %d (%s). Restarting the server might not work.", + a_Port, err, evutil_socket_error_to_string(err) + ); + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + return; + } + + // Make the secondary socket nonblocking: + if (evutil_make_socket_nonblocking(m_SecondarySock) != 0) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("evutil_make_socket_nonblocking() failed for secondary UDP socket: %d, %s", err, evutil_socket_error_to_string(err)); + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + return; + } + + // Bind to all IPv4 interfaces: + sockaddr_in name; + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = ntohs(m_Port); + if (bind(m_SecondarySock, reinterpret_cast<const sockaddr *>(&name), sizeof(name)) != 0) + { + // Don't report as an error, the primary socket is working + err = EVUTIL_SOCKET_ERROR(); + LOGD("Cannot bind secondary socket to UDP port %d: %d (%s)", m_Port, err, evutil_socket_error_to_string(err)); + evutil_closesocket(m_SecondarySock); + m_SecondarySock = -1; + return; + } + + m_SecondaryEvent = event_new(cNetworkSingleton::Get().GetEventBase(), m_SecondarySock, EV_READ | EV_PERSIST, RawCallback, this); + event_add(m_SecondaryEvent, nullptr); +} + + + + + +void cUDPEndpointImpl::RawCallback(evutil_socket_t a_Socket, short a_What, void * a_Self) +{ + cUDPEndpointImpl * Self = reinterpret_cast<cUDPEndpointImpl *>(a_Self); + Self->Callback(a_Socket, a_What); +} + + + + + +void cUDPEndpointImpl::Callback(evutil_socket_t a_Socket, short a_What) +{ + if ((a_What & EV_READ) != 0) + { + // Receive datagram from the socket: + char buf[64 KiB]; + int buflen = static_cast<int>(sizeof(buf)); + sockaddr_storage sa; + socklen_t salen = static_cast<socklen_t>(sizeof(sa)); + int len = recvfrom(a_Socket, buf, buflen, 0, reinterpret_cast<sockaddr *>(&sa), &salen); + if (len >= 0) + { + // Convert the remote IP address to a string: + char RemoteHost[128]; + UInt16 RemotePort; + switch (sa.ss_family) + { + case AF_INET: + { + auto sin = reinterpret_cast<sockaddr_in *>(&sa); + evutil_inet_ntop(sa.ss_family, &sin->sin_addr, RemoteHost, sizeof(RemoteHost)); + RemotePort = ntohs(sin->sin_port); + break; + } + case AF_INET6: + { + auto sin = reinterpret_cast<sockaddr_in6 *>(&sa); + evutil_inet_ntop(sa.ss_family, &sin->sin6_addr, RemoteHost, sizeof(RemoteHost)); + RemotePort = ntohs(sin->sin6_port); + break; + } + default: + { + return; + } + } + + // Call the callback: + m_Callbacks.OnReceivedData(buf, len, RemoteHost, RemotePort); + } + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNetwork API: + +cUDPEndpointPtr cNetwork::CreateUDPEndpoint(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks) +{ + return std::make_shared<cUDPEndpointImpl>(a_Port, a_Callbacks); +} + + + + diff --git a/src/OSSupport/UDPEndpointImpl.h b/src/OSSupport/UDPEndpointImpl.h new file mode 100644 index 000000000..75942b0cf --- /dev/null +++ b/src/OSSupport/UDPEndpointImpl.h @@ -0,0 +1,81 @@ + +// UDPEndpointImpl.h + +// Declares the cUDPEndpointImpl class representing an implementation of an endpoint in UDP communication + + + + + +#pragma once + +#include "Network.h" +#include <event2/event.h> + + + + + +// fwd: +class cUDPEndpointImpl; +typedef SharedPtr<cUDPEndpointImpl> cUDPEndpointImplPtr; + + + + + +class cUDPEndpointImpl: + public cUDPEndpoint +{ + typedef cUDPEndpoint super; + +public: + /** Creates a new instance of the endpoint, with the specified callbacks. + Tries to open on the specified port; if it fails, the endpoint is left in the "closed" state. + If a_Port is 0, the OS is free to assign any port number it likes to the endpoint. */ + cUDPEndpointImpl(UInt16 a_Port, cUDPEndpoint::cCallbacks & a_Callbacks); + + // cUDPEndpoint overrides: + virtual void Close(void) override; + virtual bool IsOpen(void) const override; + virtual UInt16 GetPort(void) const override; + virtual bool Send(const AString & a_Payload, const AString & a_Host, UInt16 a_Port) override; + virtual void EnableBroadcasts(void) override; + +protected: + /** The local port on which the endpoint is open. + If this is zero, it means the endpoint is closed - either opening has failed, or it has been closed explicitly. */ + UInt16 m_Port; + + /** The primary underlying OS socket. */ + evutil_socket_t m_MainSock; + + /** True if m_MainSock is in the IPv6 namespace (needs IPv6 addresses for sending). */ + bool m_IsMainSockIPv6; + + /** The secondary OS socket (if primary doesn't support dualstack). */ + evutil_socket_t m_SecondarySock; + + /** The LibEvent handle for the primary socket. */ + event * m_MainEvent; + + /** The LibEvent handle for the secondary socket. */ + event * m_SecondaryEvent; + + + /** Creates and opens the socket on the specified port. + If a_Port is 0, the OS is free to assign any port number it likes to the endpoint. + If the opening fails, the OnError() callback is called and the endpoint is left "closed" (IsOpen() returns false). */ + void Open(UInt16 a_Port); + + /** The callback that LibEvent calls when an event occurs on one of the sockets. + Calls Callback() on a_Self. */ + static void RawCallback(evutil_socket_t a_Socket, short a_What, void * a_Self); + + /** The callback that is called when an event occurs on one of the sockets. */ + void Callback(evutil_socket_t a_Socket, short a_What); +}; + + + + |