summaryrefslogtreecommitdiff
path: root/src/common/socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/socket.c')
-rw-r--r--src/common/socket.c193
1 files changed, 85 insertions, 108 deletions
diff --git a/src/common/socket.c b/src/common/socket.c
index 6b52dd402..d8f3365d3 100644
--- a/src/common/socket.c
+++ b/src/common/socket.c
@@ -41,6 +41,7 @@
#define s_errno WSAGetLastError()
#define S_ENOTSOCK WSAENOTSOCK
#define S_EWOULDBLOCK WSAEWOULDBLOCK
+ #define S_EINTR WSAEINTR
#define S_ECONNABORTED WSAECONNABORTED
#define SHUT_RD SD_RECEIVE
@@ -55,6 +56,7 @@
#define s_errno errno
#define S_ENOTSOCK EBADF
#define S_EWOULDBLOCK EAGAIN
+ #define S_EINTR EINTR
#define S_ECONNABORTED ECONNABORTED
#endif
@@ -66,13 +68,11 @@ time_t stall_time = 60;
uint32 addr_[16]; // ip addresses of local host (host byte order)
int naddr_ = 0; // # of ip addresses
-#define MODE_NODELAY 1 // disables|enables packet buffering
-
-// values derived from freya
-// a player that send more than 2k is probably a hacker without be parsed
+// initial recv buffer size (this will also be the max. size)
// biggest known packet: S 0153 <len>.w <emblem data>.?B -> 24x24 256 color .bmp (0153 + len.w + 1618/1654/1756 bytes)
-size_t rfifo_size = (16*1024);
-size_t wfifo_size = (16*1024);
+#define RFIFO_SIZE (2*1024)
+// initial send buffer size (will be resized as needed)
+#define WFIFO_SIZE (16*1024)
struct socket_data* session[FD_SETSIZE];
@@ -82,7 +82,7 @@ int send_shortlist_count = 0;// how many fd's are in the shortlist
fd_set send_shortlist_fd_set;// to know if specific fd's are already in the shortlist
#endif
-int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseFunc func_parse);
+static int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseFunc func_parse);
#ifndef MINICORE
int ip_rules = 1;
@@ -110,11 +110,6 @@ void set_defaultparse(ParseFunc defaultparse)
*--------------------------------------*/
void set_nonblocking(int fd, unsigned long yes)
{
- // TCP_NODELAY BOOL Disables the Nagle algorithm for send coalescing.
-#if defined(MODE_NODELAY) && MODE_NODELAY == 1
- setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&yes, sizeof yes);
-#endif
-
// FIONBIO Use with a nonzero argp parameter to enable the nonblocking mode of socket s.
// The argp parameter is zero if nonblocking is to be disabled.
if (ioctlsocket(fd, FIONBIO, &yes) != 0)
@@ -133,9 +128,10 @@ void setsocketopts(int fd)
setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof(yes));
#endif
#endif
+
+ // Set the socket into no-delay mode; otherwise packets get delayed for up to 200ms, likely creating server-side lag.
+ // The RO protocol is mainly single-packet request/response, plus the FIFO model already does packet grouping anyway.
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&yes, sizeof(yes));
-// setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *) &wfifo_size , sizeof(rfifo_size ));
-// setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *) &rfifo_size , sizeof(rfifo_size ));
// force the socket into no-wait, graceful-close mode (should be the default, but better make sure)
//(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winsock/winsock/closesocket_2.asp)
@@ -172,19 +168,17 @@ int recv_to_fifo(int fd)
len = recv(fd, (char *) session[fd]->rdata + session[fd]->rdata_size, (int)RFIFOSPACE(fd), 0);
- if (len == SOCKET_ERROR) {
- if (s_errno == S_ECONNABORTED) {
- ShowWarning("recv_to_fifo: Software caused connection abort on session #%d\n", fd);
- FD_CLR(fd, &readfds); //Remove the socket so the select() won't hang on it.
- }
- if (s_errno != S_EWOULDBLOCK) {
- //ShowDebug("recv_to_fifo: error %d, ending connection #%d\n", s_errno, fd);
+ if( len == SOCKET_ERROR )
+ {//An exception has occured
+ if( s_errno != S_EWOULDBLOCK ) {
+ ShowDebug("recv_to_fifo: code %d, closing connection #%d\n", s_errno, fd);
set_eof(fd);
}
return 0;
}
- if (len == 0) { //Normal connection end.
+ if( len == 0 )
+ {//Normal connection end.
set_eof(fd);
return 0;
}
@@ -201,27 +195,28 @@ int send_from_fifo(int fd)
if( !session_isValid(fd) )
return -1;
- if (session[fd]->wdata_size == 0)
- return 0;
+ if( session[fd]->wdata_size == 0 )
+ return 0; // nothing to send
+
+ ShowInfo("Session #%d sending %d bytes.\n", fd, session[fd]->wdata_size);
len = send(fd, (const char *) session[fd]->wdata, (int)session[fd]->wdata_size, 0);
- if (len == SOCKET_ERROR) {
- if (s_errno == S_ECONNABORTED) {
- ShowWarning("send_from_fifo: Software caused connection abort on session #%d\n", fd);
- FD_CLR(fd, &readfds); //Remove the socket so the select() won't hang on it.
- }
- if (s_errno != S_EWOULDBLOCK) {
- //ShowDebug("send_from_fifo: error %d, ending connection #%d\n", s_errno, fd);
+ if( len == SOCKET_ERROR )
+ {
+ if( s_errno != S_EWOULDBLOCK ) {
+ ShowDebug("send_from_fifo: error %d, ending connection #%d\n", s_errno, fd);
session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex]
set_eof(fd);
}
return 0;
}
- //{ int i; ShowMessage("send %d : ",fd); for(i=0;i<len;i++){ ShowMessage("%02x ",session[fd]->wdata[i]); } ShowMessage("\n");}
- if(len > 0) {
- if((size_t)len < session[fd]->wdata_size)
+ // some data could not be transferred?
+ if( len > 0 )
+ {
+ // shift unsent data to the beginning of the queue
+ if( (size_t)len < session[fd]->wdata_size )
memmove(session[fd]->wdata, session[fd]->wdata + len, session[fd]->wdata_size - len);
session[fd]->wdata_size -= len;
@@ -282,7 +277,6 @@ int connect_client(int listen_fd)
create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse);
session[fd]->client_addr = ntohl(client_address.sin_addr.s_addr);
- session[fd]->rdata_tick = last_tick;
return fd;
}
@@ -333,6 +327,8 @@ int make_listen_bind(uint32 ip, uint16 port)
FD_SET(fd, &readfds);
create_session(fd, connect_client, null_send, null_parse);
+ session[fd]->rdata_tick = 0; // disable timeouts on this socket
+ session[fd]->client_addr = 0;
return fd;
}
@@ -377,25 +373,26 @@ int make_connection(uint32 ip, uint16 port)
FD_SET(fd,&readfds);
create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse);
- session[fd]->rdata_tick = last_tick;
+ session[fd]->client_addr = 0;
return fd;
}
-int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseFunc func_parse)
+static int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseFunc func_parse)
{
CREATE(session[fd], struct socket_data, 1);
- CREATE(session[fd]->rdata, unsigned char, rfifo_size);
- CREATE(session[fd]->wdata, unsigned char, wfifo_size);
- session[fd]->max_rdata = rfifo_size;
- session[fd]->max_wdata = wfifo_size;
+ CREATE(session[fd]->rdata, unsigned char, RFIFO_SIZE);
+ CREATE(session[fd]->wdata, unsigned char, WFIFO_SIZE);
+ session[fd]->max_rdata = RFIFO_SIZE;
+ session[fd]->max_wdata = WFIFO_SIZE;
session[fd]->func_recv = func_recv;
session[fd]->func_send = func_send;
session[fd]->func_parse = func_parse;
+ session[fd]->rdata_tick = last_tick;
return 0;
}
-int delete_session(int fd)
+static int delete_session(int fd)
{
if (fd <= 0 || fd >= FD_SETSIZE)
return -1;
@@ -435,29 +432,45 @@ int realloc_writefifo(int fd, size_t addition)
return 0;
if( session[fd]->wdata_size + addition > session[fd]->max_wdata )
- { // grow rule; grow in multiples of wfifo_size
- newsize = wfifo_size;
+ { // grow rule; grow in multiples of WFIFO_SIZE
+ newsize = WFIFO_SIZE;
while( session[fd]->wdata_size + addition > newsize ) newsize += newsize;
}
- else if( session[fd]->max_wdata >= FIFOSIZE_SERVERLINK) {
+ else
+ if( session[fd]->max_wdata >= FIFOSIZE_SERVERLINK)
+ {
//Inter-server adjust. [Skotlex]
if ((session[fd]->wdata_size+addition)*4 < session[fd]->max_wdata)
newsize = session[fd]->max_wdata / 2;
else
return 0; //No change
- } else if( session[fd]->max_wdata > wfifo_size && (session[fd]->wdata_size+addition)*4 < session[fd]->max_wdata )
+ }
+ else
+ if( session[fd]->max_wdata > WFIFO_SIZE && (session[fd]->wdata_size+addition)*4 < session[fd]->max_wdata )
{ // shrink rule, shrink by 2 when only a quater of the fifo is used, don't shrink below 4*addition
newsize = session[fd]->max_wdata / 2;
}
else // no change
return 0;
+ // crash prevention for bugs that cause the send queue to fill up in an infinite loop
+ if( newsize > 1*1024*1024 ) // 1 megabyte is way beyond reasonable
+ {
+ ShowError("realloc_writefifo: session #%d's send buffer was overloaded! Disconnecting...\n", fd);
+ // drop all data (but the space will still be available)
+ session[fd]->wdata_size = 0;
+ // request disconnect
+ set_eof(fd);
+ return 0;
+ }
+
RECREATE(session[fd]->wdata, unsigned char, newsize);
session[fd]->max_wdata = newsize;
return 0;
}
+/// advance the RFIFO cursor (marking 'len' bytes as processed)
int RFIFOSKIP(int fd, size_t len)
{
struct socket_data *s;
@@ -468,16 +481,15 @@ int RFIFOSKIP(int fd, size_t len)
s = session[fd];
if ( s->rdata_size < s->rdata_pos + len ) {
- //fprintf(stderr,"too many skip\n");
- //exit(EXIT_FAILURE);
- //better than a COMPLETE program abort // TEST! :)
- ShowError("too many skip (%d) now skipped: %d (FD: %d)\n", len, RFIFOREST(fd), fd);
+ ShowError("RFIFOSKIP: skipped past end of read buffer! Adjusting from %d to %d (session #%d)\n", len, RFIFOREST(fd), fd);
len = RFIFOREST(fd);
}
+
s->rdata_pos = s->rdata_pos + len;
return 0;
}
+/// advance the WFIFO cursor (marking 'len' bytes for sending)
int WFIFOSET(int fd, size_t len)
{
size_t newreserve;
@@ -490,20 +502,18 @@ int WFIFOSET(int fd, size_t len)
if(s->wdata_size+len > s->max_wdata)
{ // actually there was a buffer overflow already
uint32 ip = s->client_addr;
- ShowFatalError("socket: Buffer Overflow. Connection %d (%d.%d.%d.%d) has written %d bytes on a %d/%d bytes buffer.\n",
- fd, CONVIP(ip), len, s->wdata_size, s->max_wdata);
+ ShowFatalError("WFIFOSET: Write Buffer Overflow. Connection %d (%d.%d.%d.%d) has written %d bytes on a %d/%d bytes buffer.\n", fd, CONVIP(ip), len, s->wdata_size, s->max_wdata);
ShowDebug("Likely command that caused it: 0x%x\n", (*(unsigned short*)(s->wdata + s->wdata_size)));
// no other chance, make a better fifo model
exit(EXIT_FAILURE);
}
s->wdata_size += len;
- // always keep a wfifo_size reserve in the buffer
+ // always keep a WFIFO_SIZE reserve in the buffer
// For inter-server connections, let the reserve be 1/4th of the link size.
- newreserve = s->wdata_size + (s->max_wdata >= FIFOSIZE_SERVERLINK ? FIFOSIZE_SERVERLINK / 4 : wfifo_size);
+ newreserve = s->wdata_size + (s->max_wdata >= FIFOSIZE_SERVERLINK ? FIFOSIZE_SERVERLINK / 4 : WFIFO_SIZE);
- // readfifo does not need to be realloced at all
- // Even the inter-server buffer may need reallocating! [Skotlex]
+ // readjust the buffer to the newly chosen size
realloc_writefifo(fd, newreserve);
#ifdef SEND_SHORTLIST
@@ -513,17 +523,16 @@ int WFIFOSET(int fd, size_t len)
return 0;
}
-int do_sendrecv(int next)
+int do_sockets(int next)
{
fd_set rfd;
- struct sockaddr_in addr_check;
struct timeval timeout;
- int ret,i,size;
+ int ret,i;
last_tick = time(0);
- // PRESEND Timers are executed before do_sendrecv and can send packets
- // and/or set sessions to eof. Send remaining data and handle eof sessions.
+ // PRESEND Timers are executed before do_sendrecv and can send packets and/or set sessions to eof.
+ // Send remaining data and process client-side disconnects here.
#ifdef SEND_SHORTLIST
send_shortlist_do_sends();
#else
@@ -541,45 +550,17 @@ int do_sendrecv(int next)
timeout.tv_sec = next/1000;
timeout.tv_usec = next%1000*1000;
- for(memcpy(&rfd, &readfds, sizeof(rfd));
- (ret = select(fd_max, &rfd, NULL, NULL, &timeout))<0;
- memcpy(&rfd, &readfds, sizeof(rfd)))
- {
- if(s_errno != S_ENOTSOCK)
- return 0;
+ memcpy(&rfd, &readfds, sizeof(rfd));
+ ret = select(fd_max, &rfd, NULL, NULL, &timeout);
- //Well then the error is due to a bad socket. Lets find and remove it
- //and try again
- for(i = 1; i < fd_max; i++)
+ if( ret < 0 )
+ {
+ if( ret != S_EINTR )
{
- if(!session[i])
- {
- if (FD_ISSET(i, &readfds)) {
- ShowError("Deleting non-cleared session %d\n", i);
- FD_CLR(i, &readfds);
- }
- continue;
- }
-
- //check the validity of the socket. Does what the last thing did
- //just alot faster [Meruru]
- size = sizeof(struct sockaddr);
- if(getsockname(i,(struct sockaddr*)&addr_check,&size)<0)
- if(s_errno == S_ENOTSOCK)
- {
- ShowError("Deleting invalid session %d\n", i);
- //So the code can react accordingly
- set_eof(i);
- session[i]->func_parse(i);
- delete_session(i); //free the bad session
- continue;
- }
-
- if (!FD_ISSET(i, &readfds))
- FD_SET(i,&readfds);
- ret = i;
+ ShowFatalError("do_sockets: select() returned %d!\n", ret);
+ exit(EXIT_FAILURE);
}
- fd_max = ret;
+ return 0;
}
#ifdef WIN32
@@ -618,12 +599,7 @@ int do_sendrecv(int next)
}
#endif
- return 0;
-}
-
-int do_parsepacket(void)
-{
- int i;
+ // parse input data on each socket
for(i = 1; i < fd_max; i++)
{
if(!session[i])
@@ -639,13 +615,14 @@ int do_parsepacket(void)
if(!session[i])
continue;
- /* after parse, check client's RFIFO size to know if there is an invalid packet (too big and not parsed) */
- if (session[i]->rdata_size == rfifo_size && session[i]->max_rdata == rfifo_size) {
+ // after parse, check client's RFIFO size to know if there is an invalid packet (too big and not parsed)
+ if (session[i]->rdata_size == RFIFO_SIZE && session[i]->max_rdata == RFIFO_SIZE) {
set_eof(i);
continue;
}
RFIFOFLUSH(i);
}
+
return 0;
}
@@ -1120,12 +1097,12 @@ void socket_init(void)
}
-int session_isValid(int fd)
+bool session_isValid(int fd)
{
- return ( (fd > 0) && (fd < FD_SETSIZE) && (session[fd] != NULL) );
+ return ( fd > 0 && fd < FD_SETSIZE && session[fd] != NULL );
}
-int session_isActive(int fd)
+bool session_isActive(int fd)
{
return ( session_isValid(fd) && !session[fd]->eof );
}