From 533e858ab90abef6002041767587f8cdc4381afd Mon Sep 17 00:00:00 2001 From: Javivi Date: Sun, 11 Oct 2015 23:20:23 +0200 Subject: [PATCH] Module rewrite - WinSock version changed from 1.1 to 2.2. - Properly check for WinSock initialization on OnAmxxAttach/Detach. - Now natives will not be added if we can't start up WinSock. - socket_open() is now IP version agnostic (both IPv4 and IPv6 are supported). - Error reporting has been changed on socket_open(), a new parameter called _libc_errors has been added, and, if enabled, libc errors will be returned instead of the previous made-up errors. - socket_close() now returns a value on success/failure. - Added non-blocking sockets at socket_open_nb(). - Added socket_is_writable() to check if a socket is ready for write. - Added socket_is_readable() as an alias to socket_change(). - Code rewritten to be more readable, it should be self-explaining now. --- modules/sockets/msvc12/sockets.vcxproj | 8 +- modules/sockets/sockets.cpp | 501 ++++++++++++++++--------- 2 files changed, 321 insertions(+), 188 deletions(-) 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; }