diff --git a/modules/sockets/msvc12/sockets.vcxproj b/modules/sockets/msvc12/sockets.vcxproj
index 0524e6d4..8920980e 100644
--- a/modules/sockets/msvc12/sockets.vcxproj
+++ b/modules/sockets/msvc12/sockets.vcxproj
@@ -55,7 +55,7 @@
Disabled
..\;..\..\..\public;..\..\..\public\sdk;..\..\..\public\amtl;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)
- WIN32;_DEBUG;_WINDOWS;_USRDLL;SOCKETS_EXPORTS;%(PreprocessorDefinitions)
+ WIN32;HAVE_STDINT_H;_DEBUG;_WINDOWS;_USRDLL;SOCKETS_EXPORTS;%(PreprocessorDefinitions)
true
EnableFastChecks
MultiThreadedDebug
@@ -66,7 +66,7 @@
EditAndContinue
- wsock32.lib;%(AdditionalDependencies)
+ ws2_32.lib;%(AdditionalDependencies)
true
$(OutDir)sockets.pdb
Windows
@@ -79,7 +79,7 @@
..\;..\..\..\public;..\..\..\public\sdk;..\..\..\public\amtl;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)
- WIN32;NDEBUG;_WINDOWS;_USRDLL;SOCKETS_EXPORTS;%(PreprocessorDefinitions)
+ WIN32;HAVE_STDINT_H;NDEBUG;_WINDOWS;_USRDLL;SOCKETS_EXPORTS;%(PreprocessorDefinitions)
MultiThreaded
false
@@ -88,7 +88,7 @@
ProgramDatabase
- wsock32.lib;%(AdditionalDependencies)
+ ws2_32.lib;%(AdditionalDependencies)
true
Windows
true
diff --git a/modules/sockets/sockets.cpp b/modules/sockets/sockets.cpp
index 2ff14e4c..740d5ebe 100644
--- a/modules/sockets/sockets.cpp
+++ b/modules/sockets/sockets.cpp
@@ -3,10 +3,6 @@
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
// Copyright (C) The AMX Mod X Development Team.
//
-// Codebase from Ivan, -g-s-ivan@web.de (AMX 0.9.3)
-// Modification by Olaf Reusch, kenterfie@hlsw.de (AMXX 0.16, AMX 0.96)
-// Modification by David Anderson, dvander@tcwonline.org (AMXx 0.20)
-//
// This software is licensed under the GNU General Public License, version 3 or higher.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
// https://alliedmods.net/amxmodx-license
@@ -15,233 +11,370 @@
// Sockets Module
//
-#include
-#include
-#include
-#include
+#include "amxxmodule.h"
+#include
#ifdef _WIN32
-/* Windows */
-#include
-#include
-#define socklen_t int
+ #include
+ #include
+
+ #undef errno
+ #undef close
+
+ #define errno WSAGetLastError()
+ #define close(sockfd) closesocket(sockfd)
+
+ #define EINPROGRESS WSAEINPROGRESS
+ #define EWOULDBLOCK WSAEWOULDBLOCK
#else
-/* Unix/Linux */
-#include
-#include
-#include
-#include
-#include
-#include
-#define closesocket(s) close(s)
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
#endif
-// AMX Headers
-#include "amxxmodule.h"
+// Backwards compatibility
+#define ERROR_CREATE_SOCKET 1 // Couldn't create a socket
+#define ERROR_SERVER_UNKNOWN 2 // Server unknown
+#define ERROR_WHILE_CONNECTING 3 // Error while connecting
+#define ERROR_EHOSTUNREACH 113 // libc error code: No route to host
-#define SOCKET_TCP 1
-#define SOCKET_UDP 2
+#ifdef _WIN32
+ bool g_winsock_initialized = false;
+#endif
-// And global Variables:
+static char *g_send2_buffer = nullptr;
+static int g_send2_buffer_length = 0;
-// native socket_open(_hostname[], _port, _protocol = SOCKET_TCP, &_error);
-static cell AMX_NATIVE_CALL socket_open(AMX *amx, cell *params) /* 2 param */
-{
- unsigned int port = params[2];
- int len;
- char* hostname = MF_GetAmxString(amx,params[1],0,&len); // Get the hostname from AMX
- cell *err = MF_GetAmxAddr(amx, params[4]);
- if(len == 0) { // just to prevent to work with a nonset hostname
- *err = 2; // server unknown
- return -1;
- }
- *err = 0; // params[4] is error backchannel
- struct sockaddr_in server;
- struct hostent *host_info;
- unsigned long addr;
- int sock=-1;
- int contr;
- // Create a Socket
- sock = socket(AF_INET, params[3]==SOCKET_TCP?SOCK_STREAM:SOCK_DGRAM, 0);
- if (sock < 0) {
- // Error, couldn't create a socket, so set an error and return.
- *err = 1;
- return -1;
- }
-
- // Clear the server structure (set everything to 0)
- memset( &server, 0, sizeof (server));
- // Test the hostname, and resolve if needed
- if ((addr = inet_addr(hostname)) != INADDR_NONE) {
- // seems like hostname is a numeric ip, so put it into the structure
- memcpy( (char *)&server.sin_addr, &addr, sizeof(addr));
- }
- else {
- // hostname is a domain, so resolve it to an ip
- host_info = gethostbyname(hostname);
- if (host_info == NULL) {
- // an error occured, the hostname is unknown
- *err = 2; // server unknown
- return -1;
- }
- // If not, put it in the Server structure
- memcpy( (char *)&server.sin_addr, host_info->h_addr, host_info->h_length);
- }
- // Set the type of the Socket
- server.sin_family = AF_INET;
- // Change the port to network byte order, and put it into the structure
- server.sin_port = htons(port);
-
- // Not, let's try to open a connection to the server
- contr = connect(sock, (struct sockaddr*)&server, sizeof( server));
- if (contr < 0) {
- // If an error occured cancel
- *err = 3; //error while connecting
- return -1;
- }
- // Everything went well, so return the socket
- return sock;
+// native socket_open(_hostname[], _port, _protocol = SOCKET_TCP, &_error, _libc_errors = DISABLE_LIBC_ERRORS);
+static cell AMX_NATIVE_CALL socket_open(AMX *amx, cell *params)
+{
+ int length = 0;
+ char *hostname = MF_GetAmxString(amx, params[1], 0, &length);
+
+ cell *error = MF_GetAmxAddr(amx, params[4]);
+ *error = 0;
+
+ bool libc_errors = false;
+
+ if((*params / sizeof(cell)) == 5)
+ libc_errors = (params[5] == 1) ? true : false;
+
+ if(length == 0)
+ {
+ *error = libc_errors ? ERROR_EHOSTUNREACH : ERROR_SERVER_UNKNOWN;
+ return -1;
+ }
+
+ char port_number[6];
+ ke::SafeSprintf(port_number, sizeof(port_number), "%d", params[2]);
+
+ int sockfd = -1, getaddrinfo_status = -1, connect_status = -1;
+ struct addrinfo hints, *server_info, *server;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = params[3];
+
+ if((getaddrinfo_status = getaddrinfo(hostname, port_number, &hints, &server_info)) != 0)
+ {
+ *error = libc_errors ? getaddrinfo_status : ERROR_SERVER_UNKNOWN;
+ return -1;
+ }
+
+ server = server_info;
+
+ do
+ {
+ if((sockfd = socket(server->ai_family, server->ai_socktype, server->ai_protocol)) != -1)
+ {
+ if((connect_status = connect(sockfd, server->ai_addr, server->ai_addrlen)) == -1)
+ {
+ *error = libc_errors ? errno : ERROR_WHILE_CONNECTING;
+ close(sockfd);
+ }
+ else
+ {
+ *error = 0;
+ }
+ }
+ else
+ {
+ if(*error == 0)
+ *error = libc_errors ? errno : ERROR_CREATE_SOCKET;
+ }
+
+ } while(connect_status != 0 && (server = server->ai_next) != nullptr);
+
+ freeaddrinfo(server_info);
+
+ if(sockfd == -1 || server == nullptr)
+ return -1;
+
+ return sockfd;
+}
+
+int set_nonblocking(int sockfd)
+{
+#ifdef _WIN32
+ unsigned long flags = 1;
+
+ if(ioctlsocket(sockfd, FIONBIO, &flags) == 0)
+ return 0;
+ else
+ return errno;
+#else
+ int flags = -1;
+
+ if((flags = fcntl(sockfd, F_GETFL, 0)) == -1)
+ return errno;
+
+ if(fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1)
+ return errno;
+
+ return 0;
+#endif
+}
+
+// native socket_open_nb(_hostname[], _port, _protocol = SOCKET_TCP, &_error);
+static cell AMX_NATIVE_CALL socket_open_nb(AMX *amx, cell *params)
+{
+ int length = 0;
+ char *hostname = MF_GetAmxString(amx, params[1], 0, &length);
+
+ cell *error = MF_GetAmxAddr(amx, params[4]);
+ *error = 0;
+
+ if(length == 0)
+ {
+ *error = ERROR_EHOSTUNREACH;
+ return -1;
+ }
+
+ char port_number[6];
+ ke::SafeSprintf(port_number, sizeof(port_number), "%d", params[2]);
+
+ int sockfd = -1, getaddrinfo_status = -1, connect_status = -1, setnonblocking_status = -1;
+ bool connect_inprogress = false;
+ struct addrinfo hints, *server_info, *server;
+
+ memset(&hints, 0, sizeof(hints));
+
+ // Both hostname and port should be numeric to prevent the name resolution service from being called and potentially blocking the call for a long time
+ hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = params[3];
+
+ if((getaddrinfo_status = getaddrinfo(hostname, port_number, &hints, &server_info)) != 0)
+ {
+ *error = getaddrinfo_status;
+ return -1;
+ }
+
+ server = server_info;
+
+ do
+ {
+ if((sockfd = socket(server->ai_family, server->ai_socktype, server->ai_protocol)) != -1)
+ {
+ setnonblocking_status = set_nonblocking(sockfd);
+
+ if(setnonblocking_status == 0)
+ {
+ if((connect_status = connect(sockfd, server->ai_addr, server->ai_addrlen)) == -1)
+ {
+ *error = errno;
+
+ if(*error == EINPROGRESS)
+ connect_inprogress = true;
+ else
+ close(sockfd);
+ }
+ else
+ {
+ *error = 0;
+ }
+ }
+ else
+ {
+ if(*error == 0)
+ *error = setnonblocking_status;
+ }
+ }
+ else
+ {
+ if(*error == 0)
+ *error = errno;
+ }
+
+ } while(connect_inprogress == false && connect_status != 0 && (server = server->ai_next) != nullptr);
+
+ freeaddrinfo(server_info);
+
+ if(sockfd == -1 || server == nullptr)
+ return -1;
+
+ return sockfd;
}
// native socket_close(_socket);
-static cell AMX_NATIVE_CALL socket_close(AMX *amx, cell *params) /* 2 param */
+static cell AMX_NATIVE_CALL socket_close(AMX *amx, cell *params)
{
- int socket = params[1];
- //PRINT_CONSOLE("Function: Close | Socket: %i\n", socket);
- #ifdef _WIN32 // On windows, check whether the sockets are initialized correctly
- closesocket(socket);
- #else
- // Close the socket (linux/unix styled systems)
- close(socket);
- #endif
- return 0;
-}
-
-// native socket_change(_socket, _timeout=100000);
-// 1 sec =1000000 usec
-static cell AMX_NATIVE_CALL socket_change(AMX *amx, cell *params) /* 2 param */
-{
- int socket = params[1];
- unsigned int timeout = params[2];
- //PRINT_CONSOLE("Function: Change | Socket: %i | Timeout: %i\n", socket, timeout);
- // We need both a timeout structure and a fdset for our filedescriptor
- fd_set rfds;
- struct timeval tv;
- // Fill in ...
- FD_ZERO(&rfds);
- FD_SET(socket, &rfds);
- tv.tv_sec = 0;
- tv.tv_usec = timeout;
- // Now we "select", which will show us if new data is waiting in the socket's buffer
- if (select(socket+1, &rfds, NULL, NULL, &tv) > 0)
- return 1; // Ok, new data, return it
- else
- return 0; // No new data, return it
+ return (close(params[1]) == -1) ? -1 : 1;
}
// native socket_recv(_socket, _data[], _length);
-static cell AMX_NATIVE_CALL socket_recv(AMX *amx, cell *params) /* 2 param */
+static cell AMX_NATIVE_CALL socket_recv(AMX *amx, cell *params)
{
- int socket = params[1];
- int length = params[3];
- int tmp = -1;
- // First we dynamicly allocate a block of heap memory for recieving our data
- char *tmpchar = new char[length];
- if(tmpchar == NULL) return -1; // If we didn't got a block, we have to quit here to avoid sigsegv
- // And set it all to 0, because the memory could contain old trash
- memset(tmpchar, 0, length);
- // Now we recieve
- tmp = recv(socket, tmpchar, length-1, 0);
- if (tmp == -1)
+ int sockfd = params[1];
+ int length = params[3];
+
+ char *recv_buffer = new char[length];
+
+ if(recv_buffer == nullptr)
+ return -1;
+
+ memset(recv_buffer, 0, length);
+
+ int bytes_received = -1;
+ bytes_received = recv(sockfd, recv_buffer, length - 1, 0);
+
+ if(bytes_received == -1)
{
- delete [] tmpchar;
+ delete[] recv_buffer;
return -1;
}
- // And put a copy of our recieved data into amx's string
- tmpchar[tmp]='\0';
- int nlen = 0;
- //int max = params[3];
- int max = length-1;
- const char* src = tmpchar;
- cell* dest = MF_GetAmxAddr(amx,params[2]);
- while(max--&&nlen g_buflen)
+ int sockfd = params[1];
+ int length = params[3];
+
+ if(length > g_send2_buffer_length)
{
- delete [] g_buffer;
- g_buffer = new char[len+1];
- g_buflen = len;
+ delete[] g_send2_buffer;
+
+ g_send2_buffer = new char[length + 1];
+ g_send2_buffer_length = length;
}
- cell *pData = MF_GetAmxAddr(amx, params[2]);
- char *pBuffer = g_buffer;
+ cell *data = MF_GetAmxAddr(amx, params[2]);
- while (len--)
- *pBuffer++ = (char)*pData++;
+ while(length--)
+ *g_send2_buffer++ = (char)*data++;
- // And send it to the socket
- return send(socket, g_buffer, params[3], 0);
+ return send(sockfd, g_send2_buffer, length, 0);
}
-AMX_NATIVE_INFO sockets_natives[] = {
+// native socket_change(_socket, _timeout = 100000);
+static cell AMX_NATIVE_CALL socket_change(AMX *amx, cell *params)
+{
+ int sockfd = params[1];
+ unsigned int timeout = params[2];
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = timeout;
+
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(sockfd, &readfds);
+
+ return (select(sockfd + 1, &readfds, nullptr, nullptr, &tv) > 0) ? 1 : -1;
+}
+
+// native socket_is_readable(_socket, _timeout = 100000);
+static cell AMX_NATIVE_CALL socket_is_readable(AMX *amx, cell *params)
+{
+ return socket_change(amx, params);
+}
+
+// native socket_is_writable(_socket, _timeout = 100000);
+static cell AMX_NATIVE_CALL socket_is_writable(AMX *amx, cell *params)
+{
+ int sockfd = params[1];
+ unsigned int timeout = params[2];
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = timeout;
+
+ fd_set writefds;
+ FD_ZERO(&writefds);
+ FD_SET(sockfd, &writefds);
+
+ return (select(sockfd + 1, nullptr, &writefds, nullptr, &tv) > 0) ? 1 : -1;
+}
+
+AMX_NATIVE_INFO sockets_natives[] =
+{
{"socket_open", socket_open},
+ {"socket_open_nb", socket_open_nb},
+
{"socket_close", socket_close},
- {"socket_change", socket_change},
+
{"socket_recv", socket_recv},
+
{"socket_send", socket_send},
{"socket_send2", socket_send2},
+
+ {"socket_change", socket_change},
+ {"socket_is_readable", socket_is_readable},
+ {"socket_is_writable", socket_is_writable},
+
{NULL, NULL}
};
void OnAmxxAttach()
{
+#ifdef _WIN32
+ WSADATA WSAData;
+ int errorcode = WSAStartup(MAKEWORD(2, 2), &WSAData);
+
+ if(errorcode != 0)
+ {
+ MF_Log("[%s]: WSAStartup failed with error code %d. Natives will not be available.", MODULE_LOGTAG, errorcode);
+ return;
+ }
+
+ g_winsock_initialized = true;
+#endif
+
MF_AddNatives(sockets_natives);
- // And, if win32, we have to specially start up the winsock environment
- #ifdef _WIN32
- short wVersionRequested;
- WSADATA wsaData;
- wVersionRequested = MAKEWORD (1, 1);
- if (WSAStartup (wVersionRequested, &wsaData) != 0) {
- MF_Log("Sockets Module: Error while starting up winsock environment.!");
- }
- #endif
- return;
}
void OnAmxxDetach()
{
- #ifdef _WIN32
- WSACleanup();
- #endif
- delete [] g_buffer;
- return;
+#ifdef _WIN32
+ if(g_winsock_initialized)
+ WSACleanup();
+#endif
+
+ delete[] g_send2_buffer;
}