From a5e721f8cf55f3e174f70412ef4a6f11aaa40a59 Mon Sep 17 00:00:00 2001 From: skotlex Date: Mon, 18 Sep 2006 22:28:07 +0000 Subject: - Merged Meruru's update to socket.c, which includes a rewritten parse function, which should hopefully be more efficient than the previous code. - The new code includes support for two config settings (packet_athena.txt): frame_size, which can be used to alter the logic packet-size allowed by the code, and mode_neg, which when set to yes, sets TCP_NODELAY on all connections (defaults to yes). git-svn-id: https://rathena.svn.sourceforge.net/svnroot/rathena/trunk@8799 54d463be-8e91-2dee-dedb-b68131a5f0ec --- Changelog-Trunk.txt | 7 + conf-tmpl/Changelog.txt | 4 + conf-tmpl/packet_athena.conf | 6 + src/common/socket.c | 381 +++++++++++++++++++------------------------ 4 files changed, 185 insertions(+), 213 deletions(-) diff --git a/Changelog-Trunk.txt b/Changelog-Trunk.txt index 89c810eb2..0f63b0524 100644 --- a/Changelog-Trunk.txt +++ b/Changelog-Trunk.txt @@ -5,6 +5,13 @@ IF YOU HAVE A WORKING AND TESTED BUGFIX PUT IT INTO STABLE AS WELL AS TRUNK. 2006/09/18 + * Merged Meruru's update to socket.c, which includes a rewritten parse + function, which should hopefully be more efficient than the previous code. + [Skotlex] + * The new code includes support for two config settings + (packet_athena.txt): frame_size, which can be used to alter the logic + packet-size allowed by the code, and mode_neg, which when set to yes, sets + TCP_NODELAY on all connections (defaults to yes). [Skotlex] * High-Jump is usable everywhere now, except that on maps where it previously failed, now will just make you jump in place. [Skotlex] * Fixed TK_RUN as per packets provided by AuronX. [Skotlex] diff --git a/conf-tmpl/Changelog.txt b/conf-tmpl/Changelog.txt index 9779269eb..ec313b138 100644 --- a/conf-tmpl/Changelog.txt +++ b/conf-tmpl/Changelog.txt @@ -1,6 +1,10 @@ Date Added 2006/09/18 + * The new socket update code includes support for two new config settings + (packet_athena.txt): frame_size, which can be used to alter the logic + packet-size allowed by the code, and mode_neg, which when set to yes, sets + TCP_NODELAY on all connections (defaults to yes). [Skotlex] * Due to a recent update of how cards/equipment status change defense works, the max sc resistance settings (battle/status.conf) no longer apply to them. [Skotlex] diff --git a/conf-tmpl/packet_athena.conf b/conf-tmpl/packet_athena.conf index 1c29c84bf..6f5a1ac2f 100644 --- a/conf-tmpl/packet_athena.conf +++ b/conf-tmpl/packet_athena.conf @@ -8,6 +8,12 @@ debug: no // How long can a socket stall before closing the connection (in seconds) stall_time: 60 +// When enabled, sets TCP_NODELAY (disable nagel Algorythm) on all connections +mode_neg: yes + +// frame packet size as considered by the server (when there's enough +// information in queue to fill the frame_size, a "send" is forced) +//frame_size: 1054 //----- IP Rules Settings ----- diff --git a/src/common/socket.c b/src/common/socket.c index d66dda38a..3209008c5 100644 --- a/src/common/socket.c +++ b/src/common/socket.c @@ -11,6 +11,7 @@ #include #include #include + typedef int socklen_t; #else #include @@ -29,6 +30,14 @@ typedef int socklen_t; #endif +#ifdef _WIN32 +#define SEBADF WSAENOTSOCK +#define serrno WSAGetLastError() +#else +#define SEBADF EBADF +#define serrno errno +#endif + #include #include @@ -49,16 +58,37 @@ int ip_rules = 1; #define SO_REUSEPORT 15 #endif +#ifndef TCP_FRAME_LEN +#define TCP_FRAME_LEN 1024 +#endif + +#ifndef MINCORE +enum { + ACO_DENY_ALLOW=0, + ACO_ALLOW_DENY, + ACO_MUTUAL_FAILTURE, +}; + +static struct _access_control *access_allow; +static struct _access_control *access_deny; +static int access_order=ACO_DENY_ALLOW; +static int access_allownum=0; +static int access_denynum=0; +static int access_debug=0; +static int mode_neg=1; +static int frame_size=TCP_FRAME_LEN; +static int ddos_count = 10; +static int ddos_interval = 3000; +static int ddos_autoreset = 600*1000; +#endif + + // values derived from freya // a player that send more than 2k is probably a hacker without be parsed // biggest known packet: S 0153 .w .?B -> 24x24 256 color .bmp (0153 + len.w + 1618/1654/1756 bytes) size_t rfifo_size = (16*1024); size_t wfifo_size = (16*1024); -#ifndef TCP_FRAME_LEN -#define TCP_FRAME_LEN 1053 -#endif - #define CONVIP(ip) ip&0xFF,(ip>>8)&0xFF,(ip>>16)&0xFF,ip>>24 struct socket_data *session[FD_SETSIZE]; @@ -85,8 +115,9 @@ void set_defaultparse(int (*defaultparse)(int)) void set_nonblocking(int fd, int yes) { // I don't think we need this - // TCP_NODELAY BOOL Disables the Nagle algorithm for send coalescing. - setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); + // TCP_NODELAY BOOL Disables the Nagle algorithm for send coalescing. + if(mode_neg) + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); // 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. @@ -298,7 +329,7 @@ static int connect_client(int listen_fd) fd = accept(listen_fd,(struct sockaddr*)&client_address,&len); #ifdef __WIN32 if (fd == SOCKET_ERROR || fd == INVALID_SOCKET || fd < 0) { - ShowError("accept failed (code %d)!\n", fd, WSAGetLastError()); + ShowError("accept failed (code %i)!\n", WSAGetLastError()); return -1; } #else @@ -349,83 +380,6 @@ static int connect_client(int listen_fd) return fd; } -int make_listen_port(int port) -{ - struct sockaddr_in server_address; - int fd; - int result; - - fd = socket( AF_INET, SOCK_STREAM, 0 ); -#ifdef __WIN32 - if (fd == INVALID_SOCKET) { - ShowError("socket() creation failed (code %d)!\n", fd, WSAGetLastError()); - exit(1); - } -#else - if (fd == -1) { - perror("make_listen_port:socket()"); - exit(1); - } -#endif - -#ifdef __WIN32 - { - unsigned long val = 1; - if (ioctlsocket(fd, FIONBIO, &val) != 0) - ShowError("Couldn't set the socket to non-blocking mode (code %d)!\n", WSAGetLastError()); - } -#else - if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) - perror("make_listen_port (set nonblock)"); -#endif - - setsocketopts(fd); - - server_address.sin_family = AF_INET; - server_address.sin_addr.s_addr = htonl( INADDR_ANY ); - server_address.sin_port = htons((unsigned short)port); - - result = bind(fd, (struct sockaddr*)&server_address, sizeof(server_address)); -#ifdef __WIN32 - if( result == SOCKET_ERROR ) { - ShowError("bind failed (socket %d, code %d)!\n", fd, WSAGetLastError()); - exit(1); - } -#else - if( result == -1 ) { - perror("bind"); - exit(1); - } -#endif - result = listen( fd, 5 ); -#ifdef __WIN32 - if( result == SOCKET_ERROR ) { - ShowError("listen failed (socket %d, code %d)!\n", fd, WSAGetLastError()); - exit(1); - } -#else - if( result != 0 ) { /* error */ - perror("listen"); - exit(1); - } -#endif - if ( fd < 0 || fd > FD_SETSIZE ) - { //Crazy error that can happen in Windows? (info from Freya) - ShowFatalError("listen() returned invalid fd %d!\n",fd); - exit(1); - } - - if(fd_max<=fd) fd_max=fd+1; - FD_SET(fd, &readfds ); - - CREATE(session[fd], struct socket_data, 1); - - malloc_set(session[fd],0,sizeof(*session[fd])); - session[fd]->func_recv = connect_client; - - return fd; -} - int make_listen_bind(long ip,int port) { struct sockaddr_in server_address; @@ -501,12 +455,14 @@ int make_listen_bind(long ip,int port) malloc_set(session[fd],0,sizeof(*session[fd])); session[fd]->func_recv = connect_client; - ShowStatus("Open listen port on %d.%d.%d.%d:%i\n", - (ip)&0xFF,(ip>>8)&0xFF,(ip>>16)&0xFF,(ip>>24)&0xFF,port); - return fd; } +int make_listen_port(int port) +{ + return make_listen_bind(INADDR_ANY,port); +} + // Console Reciever [Wizputer] int console_recieve(int i) { int n; @@ -657,11 +613,8 @@ int make_connection(long ip,int port) return fd; } -int delete_session(int fd) +void free_session_mem(int fd) { - if (fd <= 0 || fd >= FD_SETSIZE) - return -1; - FD_CLR(fd, &readfds); if (session[fd]){ if (session[fd]->rdata) aFree(session[fd]->rdata); @@ -672,6 +625,14 @@ int delete_session(int fd) aFree(session[fd]); session[fd] = NULL; } +} + +int delete_session(int fd) +{ + if (fd <= 0 || fd >= FD_SETSIZE) + return -1; + FD_CLR(fd, &readfds); + free_session_mem(fd); //ShowMessage("delete_session:%d\n",fd); return 0; } @@ -749,7 +710,7 @@ int WFIFOSET(int fd,int len) // For inter-server connections, let the reserve be 1/8th of the link size. newreserve = s->wdata_size + (s->max_wdata>=FIFOSIZE_SERVERLINK?FIFOSIZE_SERVERLINK<<3:wfifo_size); - if (s->wdata_size > (TCP_FRAME_LEN)) + if(s->wdata_size >= frame_size) send_from_fifo(fd); // realloc after sending @@ -762,127 +723,128 @@ int WFIFOSET(int fd,int len) int do_sendrecv(int next) { - fd_set rfd,wfd,efd; //Added the Error Set so that such sockets can be made eof. They are the same as the rfd for now. [Skotlex] + fd_set rfd,efd; //Added the Error Set so that such sockets can be made eof. They are the same as the rfd for now. [Skotlex] + struct sockaddr_in addr_check; struct timeval timeout; - int ret,i; + int ret,i,size; last_tick = time(0); - //memcpy(&rfd, &readfds, sizeof(rfd)); - //memcpy(&efd, &readfds, sizeof(efd)); - FD_ZERO(&wfd); - - for (i = 1; i < fd_max; i++){ //Session 0 is never a valid session, so it's best to skip it. [Skotlex] - if(!session[i]) { - if (FD_ISSET(i, &readfds)){ - ShowDebug("force clear fds %d\n", i); - FD_CLR(i, &readfds); - //FD_CLR(i, &rfd); - //FD_CLR(i, &efd); - } + + //PRESEND Need to do this to ensure that the clients get something to do + //which hopefully will cause them to send packets. [Meruru] + for (i = 1; i < fd_max; i++) + { + if(!session[i]) continue; - } - if(session[i]->wdata_size) - FD_SET(i, &wfd); + + if(session[i]->wdata_size && session[i]->func_send) + session[i]->func_send(i); } timeout.tv_sec = next/1000; timeout.tv_usec = next%1000*1000; - memcpy(&rfd, &readfds, sizeof(rfd)); - memcpy(&efd, &readfds, sizeof(efd)); - ret = select(fd_max, &rfd, &wfd, &efd, &timeout); -#ifdef __WIN32 - if (ret == SOCKET_ERROR) { - if (WSAGetLastError() == WSAEWOULDBLOCK) - return 0; //Eh... try again later? - ShowError("do_sendrecv: select error (code %d)\n", WSAGetLastError()); -#else - if (ret < 0) { - perror("do_sendrecv"); - if (errno == 11) //Isn't there a constantI can use instead of this hardcoded value? This should be "resource temporarily unavailable": ie: try again. + for(memcpy(&rfd, &readfds, sizeof(rfd)), + memcpy(&efd, &readfds, sizeof(efd)); + (ret = select(fd_max, &rfd, NULL, &efd, &timeout))<0; + memcpy(&rfd, &readfds, sizeof(rfd)), + memcpy(&efd, &readfds, sizeof(efd))) + { + if(serrno != SEBADF) return 0; -#endif - - //if error, remove invalid connections - //Individual socket handling code shamelessly assimilated from Freya :3 - // an error give invalid values in fd_set structures -> init them again - FD_ZERO(&rfd); - FD_ZERO(&wfd); - FD_ZERO(&efd); - for(i = 1; i < fd_max; i++) { //Session 0 is not parsed, it's a 'vacuum' for disconnected sessions. [Skotlex] - if (!session[i]) { -#ifdef __WIN32 - //Debug to locate runaway sockets in Windows [Skotlex] - if (FD_ISSET(i, &readfds)) { - FD_CLR(i, &readfds); - ShowDebug("Socket %d was set (read fifos) without a session, removed.\n", i); - } -#endif + + //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(!session[i]) continue; - } - if (FD_ISSET(i, &readfds)){ - FD_SET(i, &rfd); - FD_SET(i, &efd); - } - if (session[i]->wdata_size) - FD_SET(i, &wfd); - timeout.tv_sec = 0; - timeout.tv_usec = 0; - if (select(i + 1, &rfd, &wfd, &efd, &timeout) >= 0 && !FD_ISSET(i, &efd)) { - if (FD_ISSET(i, &wfd)) { - if (session[i]->func_send) - session[i]->func_send(i); - FD_CLR(i, &wfd); - } - if (FD_ISSET(i, &rfd)) { - if (session[i]->func_recv) - session[i]->func_recv(i); - FD_CLR(i, &rfd); + + //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(serrno == SEBADF) //See the #defines at the top + { + free_session_mem(i); //free the bad session + continue; } - FD_CLR(i, &efd); - } else { - ShowDebug("do_sendrecv: Session #%d caused error in select(), disconnecting.\n", i); - set_eof(i); // set eof - // an error gives invalid values in fd_set structures -> init them again - FD_ZERO(&rfd); - FD_ZERO(&wfd); - FD_ZERO(&efd); - } + + FD_SET(i,&readfds); + ret = i; } - return 0; - }else if(ret > 0) { - for (i = 1; i < fd_max; i++){ - if(!session[i]) - continue; - if(FD_ISSET(i,&efd)){ - //ShowMessage("error:%d\n",i); - ShowDebug("do_sendrecv: Connection error on Session %d.\n", i); - set_eof(i); - continue; - } - - if (FD_ISSET(i, &wfd)) { - //ShowMessage("write:%d\n",i); - if(session[i]->func_send) - session[i]->func_send(i); - } - - if(FD_ISSET(i,&rfd)){ - //ShowMessage("read:%d\n",i); - if(session[i]->func_recv) - session[i]->func_recv(i); - } + fd_max = ret; + } - - if(session[i] && session[i]->eof) //The session check is for when the connection ended in func_parse - { //Finally, even if there is no data to parse, connections signalled eof should be closed, so we call parse_func [Skotlex] - if (session[i]->func_parse) - session[i]->func_parse(i); //This should close the session inmediately. - } - } // for (i = 0 + //ok under windows to use FD_ISSET is FUCKING stupid + //because windows uses an array so lets do them part by part [Meruru] +#ifdef _WIN32 + //Do the socket sets. Unlike linux which uses a bit mask windows uses + //a array. So calls to FS_ISSET are SLOW AS SHIT. So we have to do + //a special case for them which actually turns out ok [Meruru] + for(i=0;i<(int)rfd.fd_count;i++) + { + if(session[rfd.fd_array[i]] && + session[rfd.fd_array[i]]->func_recv) + session[rfd.fd_array[i]]->func_recv(rfd.fd_array[i]); + } + for(i=0;i<(int)efd.fd_count;i++) + set_eof(efd.fd_array[i]); + + for (i = 1; i < fd_max; i++) + { + if(!session[i]) + continue; + + //POSTSEND: Does write EVER BLOCK? NO!! not unless WE ARE CURRENTLY SENDING SOMETHING + //Or just have opened a connection and dont know if its ready + //And since eA isn't multi threaded and all the sockets are non blocking THIS ISNT A PROBLEM! [Meruru] + + if(session[i]->wdata_size && session[i]->func_send) + session[i]->func_send(i); + + if(session[i] && session[i]->eof) //The session check is for when the connection ended in func_parse + { //Finally, even if there is no data to parse, connections signalled eof should be closed, so we call parse_func [Skotlex] + if (session[i]->func_parse) + session[i]->func_parse(i); //This should close the session inmediately. + } + } + +#else //where under linux its just a bit check so its smart [Meruru] + + for (i = 1; i < fd_max; i++){ + if(!session[i]) + continue; + + if(FD_ISSET(i,&efd)){ + //ShowMessage("error:%d\n",i); + ShowDebug("do_sendrecv: Connection error on Session %d.\n", i); + set_eof(i); + continue; + } + + + if(FD_ISSET(i,&rfd)){ + //ShowMessage("read:%d\n",i); + if(session[i]->func_recv) + session[i]->func_recv(i); + } + + //Does write EVER BLOCK. NO not unless WE ARE CURRENTALLY SENDING SOMETHING + //And sence eA isnt multi threaded THIS ISNT A PROBLEM! + if(session[i]->wdata_size && session[i]->func_send) + session[i]->func_send(i); + + if(session[i] && session[i]->eof) //The session check is for when the connection ended in func_parse + { //Finally, even if there is no data to parse, connections signalled eof should be closed, so we call parse_func [Skotlex] + if (session[i]->func_parse) + session[i]->func_parse(i); //This should close the session inmediately. + } } +#endif + return 0; } @@ -921,27 +883,11 @@ int do_parsepacket(void) /* DDoS U΍ */ #ifndef MINICORE -enum { - ACO_DENY_ALLOW=0, - ACO_ALLOW_DENY, - ACO_MUTUAL_FAILTURE, -}; - struct _access_control { unsigned int ip; unsigned int mask; }; -static struct _access_control *access_allow; -static struct _access_control *access_deny; -static int access_order=ACO_DENY_ALLOW; -static int access_allownum=0; -static int access_denynum=0; -static int access_debug=0; -static int ddos_count = 10; -static int ddos_interval = 3000; -static int ddos_autoreset = 600*1000; - struct _connect_history { struct _connect_history *next; struct _connect_history *prev; @@ -1193,7 +1139,16 @@ int socket_config_read(const char *cfgName) { access_debug = 0; else access_debug = atoi(w2); #endif - } else if (strcmpi(w1, "import") == 0) + } else if (strcmpi(w1, "mode_neg") == 0) + { + if(strcmpi(w2,"yes")==0) + mode_neg = 1; + else if(strcmpi(w2,"no")==0) + mode_neg = 0; + else mode_neg = atoi(w2); + } else if (strcmpi(w1, "frame_size") == 0) + frame_size = atoi(w2); + else if (strcmpi(w1, "import") == 0) socket_config_read(w2); } fclose(fp); -- cgit v1.2.3-70-g09d2