summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Bindings/CMakeLists.txt2
-rw-r--r--src/Bindings/LuaState.cpp12
-rw-r--r--src/Bindings/LuaState.h2
-rw-r--r--src/Bindings/LuaUDPEndpoint.cpp221
-rw-r--r--src/Bindings/LuaUDPEndpoint.h86
-rw-r--r--src/Bindings/ManualBindings_Network.cpp328
-rw-r--r--src/OSSupport/CMakeLists.txt2
-rw-r--r--src/OSSupport/HostnameLookup.cpp12
-rw-r--r--src/OSSupport/Network.h78
-rw-r--r--src/OSSupport/UDPEndpointImpl.cpp608
-rw-r--r--src/OSSupport/UDPEndpointImpl.h81
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);
+};
+
+
+
+