瀏覽代碼

- 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://svn.code.sf.net/p/rathena/svn/trunk@8799 54d463be-8e91-2dee-dedb-b68131a5f0ec

skotlex 18 年之前
父節點
當前提交
da13939d47
共有 4 個文件被更改,包括 185 次插入213 次删除
  1. 7 0
      Changelog-Trunk.txt
  2. 4 0
      conf-tmpl/Changelog.txt
  3. 6 0
      conf-tmpl/packet_athena.conf
  4. 168 213
      src/common/socket.c

+ 7 - 0
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]

+ 4 - 0
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]

+ 6 - 0
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 -----
 

+ 168 - 213
src/common/socket.c

@@ -11,6 +11,7 @@
 #include <windows.h>
 #include <winsock.h>
 #include <io.h>
+
 typedef int socklen_t;
 #else
 #include <sys/socket.h>
@@ -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 <fcntl.h>
 #include <string.h>
 
@@ -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 <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);
 
-#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);