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.
This commit is contained in:
Javivi 2015-10-11 23:20:23 +02:00
parent 915da57d32
commit 533e858ab9
2 changed files with 321 additions and 188 deletions

View File

@ -55,7 +55,7 @@
<ClCompile> <ClCompile>
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\;..\..\..\public;..\..\..\public\sdk;..\..\..\public\amtl;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>..\;..\..\..\public;..\..\..\public\sdk;..\..\..\public\amtl;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;SOCKETS_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;HAVE_STDINT_H;_DEBUG;_WINDOWS;_USRDLL;SOCKETS_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild> <MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@ -66,7 +66,7 @@
<DebugInformationFormat>EditAndContinue</DebugInformationFormat> <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
</ClCompile> </ClCompile>
<Link> <Link>
<AdditionalDependencies>wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<ProgramDatabaseFile>$(OutDir)sockets.pdb</ProgramDatabaseFile> <ProgramDatabaseFile>$(OutDir)sockets.pdb</ProgramDatabaseFile>
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
@ -79,7 +79,7 @@
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile> <ClCompile>
<AdditionalIncludeDirectories>..\;..\..\..\public;..\..\..\public\sdk;..\..\..\public\amtl;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>..\;..\..\..\public;..\..\..\public\sdk;..\..\..\public\amtl;..\..\third_party;..\..\third_party\hashing;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;SOCKETS_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;HAVE_STDINT_H;NDEBUG;_WINDOWS;_USRDLL;SOCKETS_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary> <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<RuntimeTypeInfo>false</RuntimeTypeInfo> <RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader> <PrecompiledHeader>
@ -88,7 +88,7 @@
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile> </ClCompile>
<Link> <Link>
<AdditionalDependencies>wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>

View File

@ -3,10 +3,6 @@
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). // AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
// Copyright (C) The AMX Mod X Development Team. // 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. // 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: // Additional exceptions apply. For full license details, see LICENSE.txt or visit:
// https://alliedmods.net/amxmodx-license // https://alliedmods.net/amxmodx-license
@ -15,233 +11,370 @@
// Sockets Module // Sockets Module
// //
#include <stdlib.h> #include "amxxmodule.h"
#include <fcntl.h> #include <amtl/am-string.h>
#include <errno.h>
#include <string.h>
#ifdef _WIN32 #ifdef _WIN32
/* Windows */ #include <winsock2.h>
#include <winsock.h> #include <ws2tcpip.h>
#include <io.h>
#define socklen_t int #undef errno
#undef close
#define errno WSAGetLastError()
#define close(sockfd) closesocket(sockfd)
#define EINPROGRESS WSAEINPROGRESS
#define EWOULDBLOCK WSAEWOULDBLOCK
#else #else
/* Unix/Linux */ #include <netinet/in.h>
#include <unistd.h> #include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <unistd.h>
#include <netdb.h> #include <errno.h>
#include <arpa/inet.h> #include <netdb.h>
#define closesocket(s) close(s) #include <fcntl.h>
#endif #endif
// AMX Headers // Backwards compatibility
#include "amxxmodule.h" #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 #ifdef _WIN32
#define SOCKET_UDP 2 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); // 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) /* 2 param */ static cell AMX_NATIVE_CALL socket_open(AMX *amx, cell *params)
{ {
unsigned int port = params[2]; int length = 0;
int len; char *hostname = MF_GetAmxString(amx, params[1], 0, &length);
char* hostname = MF_GetAmxString(amx,params[1],0,&len); // Get the hostname from AMX
cell *err = MF_GetAmxAddr(amx, params[4]); cell *error = MF_GetAmxAddr(amx, params[4]);
if(len == 0) { // just to prevent to work with a nonset hostname *error = 0;
*err = 2; // server unknown
return -1; bool libc_errors = false;
}
*err = 0; // params[4] is error backchannel if((*params / sizeof(cell)) == 5)
struct sockaddr_in server; libc_errors = (params[5] == 1) ? true : false;
struct hostent *host_info;
unsigned long addr; if(length == 0)
int sock=-1; {
int contr; *error = libc_errors ? ERROR_EHOSTUNREACH : ERROR_SERVER_UNKNOWN;
// Create a Socket return -1;
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. char port_number[6];
*err = 1; ke::SafeSprintf(port_number, sizeof(port_number), "%d", params[2]);
return -1;
} int sockfd = -1, getaddrinfo_status = -1, connect_status = -1;
struct addrinfo hints, *server_info, *server;
// Clear the server structure (set everything to 0)
memset( &server, 0, sizeof (server)); memset(&hints, 0, sizeof(hints));
// Test the hostname, and resolve if needed hints.ai_family = AF_UNSPEC;
if ((addr = inet_addr(hostname)) != INADDR_NONE) { hints.ai_socktype = params[3];
// seems like hostname is a numeric ip, so put it into the structure
memcpy( (char *)&server.sin_addr, &addr, sizeof(addr)); if((getaddrinfo_status = getaddrinfo(hostname, port_number, &hints, &server_info)) != 0)
} {
else { *error = libc_errors ? getaddrinfo_status : ERROR_SERVER_UNKNOWN;
// hostname is a domain, so resolve it to an ip return -1;
host_info = gethostbyname(hostname); }
if (host_info == NULL) {
// an error occured, the hostname is unknown server = server_info;
*err = 2; // server unknown
return -1; do
} {
// If not, put it in the Server structure if((sockfd = socket(server->ai_family, server->ai_socktype, server->ai_protocol)) != -1)
memcpy( (char *)&server.sin_addr, host_info->h_addr, host_info->h_length); {
} if((connect_status = connect(sockfd, server->ai_addr, server->ai_addrlen)) == -1)
// Set the type of the Socket {
server.sin_family = AF_INET; *error = libc_errors ? errno : ERROR_WHILE_CONNECTING;
// Change the port to network byte order, and put it into the structure close(sockfd);
server.sin_port = htons(port); }
else
// Not, let's try to open a connection to the server {
contr = connect(sock, (struct sockaddr*)&server, sizeof( server)); *error = 0;
if (contr < 0) { }
// If an error occured cancel }
*err = 3; //error while connecting else
return -1; {
} if(*error == 0)
// Everything went well, so return the socket *error = libc_errors ? errno : ERROR_CREATE_SOCKET;
return sock; }
} 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); // 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]; return (close(params[1]) == -1) ? -1 : 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
} }
// native socket_recv(_socket, _data[], _length); // 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 sockfd = params[1];
int length = params[3]; int length = params[3];
int tmp = -1;
// First we dynamicly allocate a block of heap memory for recieving our data char *recv_buffer = new char[length];
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 if(recv_buffer == nullptr)
// And set it all to 0, because the memory could contain old trash return -1;
memset(tmpchar, 0, length);
// Now we recieve memset(recv_buffer, 0, length);
tmp = recv(socket, tmpchar, length-1, 0);
if (tmp == -1) int bytes_received = -1;
bytes_received = recv(sockfd, recv_buffer, length - 1, 0);
if(bytes_received == -1)
{ {
delete [] tmpchar; delete[] recv_buffer;
return -1; return -1;
} }
// And put a copy of our recieved data into amx's string
tmpchar[tmp]='\0'; recv_buffer[bytes_received] = '\0';
int nlen = 0;
//int max = params[3]; cell* destination = MF_GetAmxAddr(amx, params[2]);
int max = length-1; strncopy(destination, recv_buffer, length - 1);
const char* src = tmpchar;
cell* dest = MF_GetAmxAddr(amx,params[2]); delete[] recv_buffer;
while(max--&&nlen<tmp){
*dest++ = (cell)*src++; return bytes_received;
nlen++;
}
*dest = 0;
// And we need to free up the space to avoid wasting memory
delete [] tmpchar;
// And finnally, return the what recv returnd
return tmp;
} }
// native socket_send(_socket, _data[], _length); // native socket_send(_socket, _data[], _length);
static cell AMX_NATIVE_CALL socket_send(AMX *amx, cell *params) /* 3 param */ static cell AMX_NATIVE_CALL socket_send(AMX *amx, cell *params)
{ {
// We get the string from amx int sockfd = params[1];
int len; int length = 0;
int socket = params[1];
char* data = MF_GetAmxString(amx,params[2],0,&len); char *data = MF_GetAmxString(amx, params[2], 0, &length);
// And send it to the socket
return send(socket, data, len, 0);
}
static char *g_buffer = NULL; return send(sockfd, data, length, 0);
static size_t g_buflen = 0; }
// native socket_send2(_socket, _data[], _length); // native socket_send2(_socket, _data[], _length);
static cell AMX_NATIVE_CALL socket_send2(AMX *amx, cell *params) /* 3 param */ static cell AMX_NATIVE_CALL socket_send2(AMX *amx, cell *params)
{ {
// We get the string from amx int sockfd = params[1];
int len = params[3]; int length = params[3];
int socket = params[1];
if ((size_t)len > g_buflen) if(length > g_send2_buffer_length)
{ {
delete [] g_buffer; delete[] g_send2_buffer;
g_buffer = new char[len+1];
g_buflen = len; g_send2_buffer = new char[length + 1];
g_send2_buffer_length = length;
} }
cell *pData = MF_GetAmxAddr(amx, params[2]); cell *data = MF_GetAmxAddr(amx, params[2]);
char *pBuffer = g_buffer;
while (len--) while(length--)
*pBuffer++ = (char)*pData++; *g_send2_buffer++ = (char)*data++;
// And send it to the socket return send(sockfd, g_send2_buffer, length, 0);
return send(socket, g_buffer, params[3], 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", socket_open},
{"socket_open_nb", socket_open_nb},
{"socket_close", socket_close}, {"socket_close", socket_close},
{"socket_change", socket_change},
{"socket_recv", socket_recv}, {"socket_recv", socket_recv},
{"socket_send", socket_send}, {"socket_send", socket_send},
{"socket_send2", socket_send2}, {"socket_send2", socket_send2},
{"socket_change", socket_change},
{"socket_is_readable", socket_is_readable},
{"socket_is_writable", socket_is_writable},
{NULL, NULL} {NULL, NULL}
}; };
void OnAmxxAttach() 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); 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() void OnAmxxDetach()
{ {
#ifdef _WIN32 #ifdef _WIN32
WSACleanup(); if(g_winsock_initialized)
#endif WSACleanup();
delete [] g_buffer; #endif
return;
delete[] g_send2_buffer;
} }