diff options
Diffstat (limited to 'src/common/socket.c')
-rw-r--r-- | src/common/socket.c | 193 |
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 ); } |