diff options
Diffstat (limited to 'src/common/socket.c')
-rw-r--r-- | src/common/socket.c | 653 |
1 files changed, 479 insertions, 174 deletions
diff --git a/src/common/socket.c b/src/common/socket.c index 740c07bdc..d4b8bb43f 100644 --- a/src/common/socket.c +++ b/src/common/socket.c @@ -2,7 +2,7 @@ * This file is part of Hercules. * http://herc.ws - http://github.com/HerculesWS/Hercules * - * Copyright (C) 2012-2015 Hercules Dev Team + * Copyright (C) 2012-2016 Hercules Dev Team * Copyright (C) Athena Dev Teams * * Hercules is free software: you can redistribute it and/or modify @@ -25,6 +25,7 @@ #include "common/HPM.h" #include "common/cbasetypes.h" +#include "common/conf.h" #include "common/db.h" #include "common/memmgr.h" #include "common/mmo.h" @@ -37,35 +38,39 @@ #include <stdlib.h> #include <sys/types.h> +#ifdef SOCKET_EPOLL +#include <sys/epoll.h> +#endif // SOCKET_EPOLL + #ifdef WIN32 # include "common/winapi.h" -#else +#else // WIN32 # include <arpa/inet.h> # include <errno.h> # include <net/if.h> # include <netdb.h> #if defined __linux__ || defined __linux # include <linux/tcp.h> -#else +#else // defined __linux__ || defined __linux # include <netinet/in.h> # include <netinet/tcp.h> -#endif +#endif // defined __linux__ || defined __linux # include <sys/ioctl.h> # include <sys/socket.h> # include <sys/time.h> # include <unistd.h> -# ifndef SIOCGIFCONF -# include <sys/sockio.h> // SIOCGIFCONF on Solaris, maybe others? [Shinomori] -# endif -# ifndef FIONBIO -# include <sys/filio.h> // FIONBIO on Solaris [FlavioJS] -# endif +#ifndef SIOCGIFCONF +# include <sys/sockio.h> // SIOCGIFCONF on Solaris, maybe others? [Shinomori] +#endif // SIOCGIFCONF +#ifndef FIONBIO +# include <sys/filio.h> // FIONBIO on Solaris [FlavioJS] +#endif // FIONBIO -# ifdef HAVE_SETRLIMIT -# include <sys/resource.h> -# endif -#endif +#ifdef HAVE_SETRLIMIT +# include <sys/resource.h> +#endif // HAVE_SETRLIMIT +#endif // WIN32 /** * Socket Interface Source @@ -75,13 +80,15 @@ struct socket_interface *sockt; struct socket_data **session; +const char *SOCKET_CONF_FILENAME = "conf/common/socket.conf"; + #ifdef SEND_SHORTLIST // Add a fd to the shortlist so that it'll be recognized as a fd that needs // sending done on it. void send_shortlist_add_fd(int fd); // Do pending network sends (and eof handling) from the shortlist. void send_shortlist_do_sends(void); -#endif +#endif // SEND_SHORTLIST ///////////////////////////////////////////////////////////////////// #if defined(WIN32) @@ -211,7 +218,7 @@ char* sErr(int code) #define sFD_ZERO FD_ZERO ///////////////////////////////////////////////////////////////////// -#else +#else // defined(WIN32) ///////////////////////////////////////////////////////////////////// // nix portability layer @@ -243,29 +250,40 @@ char* sErr(int code) #define sFD_ZERO FD_ZERO ///////////////////////////////////////////////////////////////////// -#endif +#endif // defined(WIN32) ///////////////////////////////////////////////////////////////////// #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 -#endif +#endif // MSG_NOSIGNAL +#ifndef SOCKET_EPOLL +// Select based Event Dispatcher: fd_set readfds; +#else // SOCKET_EPOLL +// Epoll based Event Dispatcher: +static int epoll_maxevents = (FD_SETSIZE / 2); +static int epfd = SOCKET_ERROR; +static struct epoll_event epevent; +static struct epoll_event *epevents = NULL; + +#endif // SOCKET_EPOLL + // Maximum packet size in bytes, which the client is able to handle. // Larger packets cause a buffer overflow and stack corruption. #if PACKETVER >= 20131223 static size_t socket_max_client_packet = 0xFFFF; -#else +#else // PACKETVER >= 20131223 static size_t socket_max_client_packet = 0x6000; -#endif +#endif // PACKETVER >= 20131223 #ifdef SHOW_SERVER_STATS // Data I/O statistics static size_t socket_data_i = 0, socket_data_ci = 0, socket_data_qi = 0; static size_t socket_data_o = 0, socket_data_co = 0, socket_data_qo = 0; static time_t socket_data_last_tick = 0; -#endif +#endif // SHOW_SERVER_STATS // 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) @@ -281,14 +299,14 @@ static time_t socket_data_last_tick = 0; int send_shortlist_array[FD_SETSIZE];// we only support FD_SETSIZE sockets, limit the array to that int send_shortlist_count = 0;// how many fd's are in the shortlist uint32 send_shortlist_set[(FD_SETSIZE+31)/32];// to know if specific fd's are already in the shortlist -#endif +#endif // SEND_SHORTLIST static int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseFunc func_parse); #ifndef MINICORE int ip_rules = 1; static int connect_check(uint32 ip); -#endif +#endif // MINICORE const char* error_msg(void) { @@ -379,13 +397,13 @@ void setsocketopts(int fd, struct hSockOpt *opt) ShowWarning("setsocketopts: Unable to set SO_LINGER mode for connection #%d!\n", fd); #ifdef TCP_THIN_LINEAR_TIMEOUTS - if (sSetsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, (char *)&yes, sizeof(yes))) - ShowWarning("setsocketopts: Unable to set TCP_THIN_LINEAR_TIMEOUTS mode for connection #%d!\n", fd); -#endif + if (sSetsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, (char *)&yes, sizeof(yes))) + ShowWarning("setsocketopts: Unable to set TCP_THIN_LINEAR_TIMEOUTS mode for connection #%d!\n", fd); +#endif // TCP_THIN_LINEAR_TIMEOUTS #ifdef TCP_THIN_DUPACK - if (sSetsockopt(fd, IPPROTO_TCP, TCP_THIN_DUPACK, (char *)&yes, sizeof(yes))) - ShowWarning("setsocketopts: Unable to set TCP_THIN_DUPACK mode for connection #%d!\n", fd); -#endif + if (sSetsockopt(fd, IPPROTO_TCP, TCP_THIN_DUPACK, (char *)&yes, sizeof(yes))) + ShowWarning("setsocketopts: Unable to set TCP_THIN_DUPACK mode for connection #%d!\n", fd); +#endif // TCP_THIN_DUPACK } /*====================================== @@ -397,7 +415,7 @@ void set_eof(int fd) #ifdef SEND_SHORTLIST // Add this socket to the shortlist for eof handling. send_shortlist_add_fd(fd); -#endif +#endif // SEND_SHORTLIST sockt->session[fd]->flag.eof = 1; } } @@ -435,7 +453,7 @@ int recv_to_fifo(int fd) { socket_data_ci += len; } -#endif +#endif // SHOW_SERVER_STATS return 0; } @@ -452,12 +470,12 @@ int send_from_fifo(int fd) len = sSend(fd, (const char *) sockt->session[fd]->wdata, (int)sockt->session[fd]->wdata_size, MSG_NOSIGNAL); if( len == SOCKET_ERROR ) - {//An exception has occurred + { //An exception has occurred if( sErrno != S_EWOULDBLOCK ) { //ShowDebug("send_from_fifo: %s, ending connection #%d\n", error_msg(), fd); #ifdef SHOW_SERVER_STATS socket_data_qo -= sockt->session[fd]->wdata_size; -#endif +#endif // SHOW_SERVER_STATS sockt->session[fd]->wdata_size = 0; //Clear the send queue as we can't send anymore. [Skotlex] sockt->eof(fd); } @@ -479,7 +497,7 @@ int send_from_fifo(int fd) { socket_data_co += len; } -#endif +#endif // SHOW_SERVER_STATS } return 0; @@ -502,7 +520,8 @@ void flush_fifos(void) /*====================================== * CORE : Connection functions *--------------------------------------*/ -int connect_client(int listen_fd) { +int connect_client(int listen_fd) +{ int fd; struct sockaddr_in client_address; socklen_t len; @@ -533,11 +552,27 @@ int connect_client(int listen_fd) { sockt->close(fd); return -1; } -#endif +#endif // MINICORE - if( sockt->fd_max <= fd ) sockt->fd_max = fd + 1; +#ifndef SOCKET_EPOLL + // Select Based Event Dispatcher sFD_SET(fd,&readfds); +#else // SOCKET_EPOLL + // Epoll based Event Dispatcher + epevent.data.fd = fd; + epevent.events = EPOLLIN; + + if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &epevent) == SOCKET_ERROR){ + ShowError("connect_client: New Socket #%d failed to add to epoll event dispatcher: %s\n", fd, error_msg()); + sClose(fd); + return -1; + } + +#endif // SOCKET_EPOLL + + if( sockt->fd_max <= fd ) sockt->fd_max = fd + 1; + create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse); sockt->session[fd]->client_addr = ntohl(client_address.sin_addr.s_addr); @@ -585,8 +620,26 @@ int make_listen_bind(uint32 ip, uint16 port) exit(EXIT_FAILURE); } + +#ifndef SOCKET_EPOLL + // Select Based Event Dispatcher + sFD_SET(fd,&readfds); + +#else // SOCKET_EPOLL + // Epoll based Event Dispatcher + epevent.data.fd = fd; + epevent.events = EPOLLIN; + + if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &epevent) == SOCKET_ERROR){ + ShowError("make_listen_bind: failed to add listener socket #%d to epoll event dispatcher: %s\n", fd, error_msg()); + sClose(fd); + exit(EXIT_FAILURE); + } + +#endif // SOCKET_EPOLL + if(sockt->fd_max <= fd) sockt->fd_max = fd + 1; - sFD_SET(fd, &readfds); + create_session(fd, connect_client, null_send, null_parse); sockt->session[fd]->client_addr = 0; // just listens @@ -595,7 +648,8 @@ int make_listen_bind(uint32 ip, uint16 port) return fd; } -int make_connection(uint32 ip, uint16 port, struct hSockOpt *opt) { +int make_connection(uint32 ip, uint16 port, struct hSockOpt *opt) +{ struct sockaddr_in remote_address = { 0 }; int fd; int result; @@ -624,7 +678,7 @@ int make_connection(uint32 ip, uint16 port, struct hSockOpt *opt) { remote_address.sin_port = htons(port); if( !( opt && opt->silent ) ) - ShowStatus("Connecting to %d.%d.%d.%d:%i\n", CONVIP(ip), port); + ShowStatus("Connecting to %u.%u.%u.%u:%i\n", CONVIP(ip), port); result = sConnect(fd, (struct sockaddr *)(&remote_address), sizeof(struct sockaddr_in)); if( result == SOCKET_ERROR ) { @@ -636,9 +690,26 @@ int make_connection(uint32 ip, uint16 port, struct hSockOpt *opt) { //Now the socket can be made non-blocking. [Skotlex] sockt->set_nonblocking(fd, 1); - if (sockt->fd_max <= fd) sockt->fd_max = fd + 1; + +#ifndef SOCKET_EPOLL + // Select Based Event Dispatcher sFD_SET(fd,&readfds); +#else // SOCKET_EPOLL + // Epoll based Event Dispatcher + epevent.data.fd = fd; + epevent.events = EPOLLIN; + + if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &epevent) == SOCKET_ERROR){ + ShowError("make_connection: failed to add socket #%d to epoll event dispatcher: %s\n", fd, error_msg()); + sClose(fd); + return -1; + } + +#endif // SOCKET_EPOLL + + if(sockt->fd_max <= fd) sockt->fd_max = fd + 1; + create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse); sockt->session[fd]->client_addr = ntohl(remote_address.sin_addr.s_addr); @@ -667,7 +738,7 @@ static void delete_session(int fd) #ifdef SHOW_SERVER_STATS socket_data_qi -= sockt->session[fd]->rdata_size - sockt->session[fd]->rdata_pos; socket_data_qo -= sockt->session[fd]->wdata_size; -#endif +#endif // SHOW_SERVER_STATS aFree(sockt->session[fd]->rdata); aFree(sockt->session[fd]->wdata); if( sockt->session[fd]->session_data ) @@ -740,7 +811,7 @@ int rfifoskip(int fd, size_t len) s->rdata_pos = s->rdata_pos + len; #ifdef SHOW_SERVER_STATS socket_data_qi -= len; -#endif +#endif // SHOW_SERVER_STATS return 0; } @@ -748,16 +819,19 @@ int rfifoskip(int fd, size_t len) int wfifoset(int fd, size_t len) { size_t newreserve; - struct socket_data* s = sockt->session[fd]; + struct socket_data* s; - if (!sockt->session_is_valid(fd) || s->wdata == NULL) + if (!sockt->session_is_valid(fd)) + return 0; + s = sockt->session[fd]; + if (s == NULL || s->wdata == NULL) return 0; // we have written len bytes to the buffer already before calling WFIFOSET if (s->wdata_size+len > s->max_wdata) { // actually there was a buffer overflow already uint32 ip = s->client_addr; - ShowFatalError("WFIFOSET: Write Buffer Overflow. Connection %d (%d.%d.%d.%d) has written %u bytes on a %u/%u bytes buffer.\n", fd, CONVIP(ip), (unsigned int)len, (unsigned int)s->wdata_size, (unsigned int)s->max_wdata); + ShowFatalError("WFIFOSET: Write Buffer Overflow. Connection %d (%u.%u.%u.%u) has written %u bytes on a %u/%u bytes buffer.\n", fd, CONVIP(ip), (unsigned int)len, (unsigned int)s->wdata_size, (unsigned int)s->max_wdata); ShowDebug("Likely command that caused it: 0x%x\n", (*(uint16*)(s->wdata + s->wdata_size))); // no other chance, make a better fifo model exit(EXIT_FAILURE); @@ -767,7 +841,7 @@ int wfifoset(int fd, size_t len) { // dynamic packets allow up to UINT16_MAX bytes (<packet_id>.W <packet_len>.W ...) // all known fixed-size packets are within this limit, so use the same limit - ShowFatalError("WFIFOSET: Packet 0x%x is too big. (len=%u, max=%u)\n", (*(uint16*)(s->wdata + s->wdata_size)), (unsigned int)len, 0xFFFF); + ShowFatalError("WFIFOSET: Packet 0x%x is too big. (len=%u, max=%u)\n", (*(uint16*)(s->wdata + s->wdata_size)), (unsigned int)len, 0xFFFFU); exit(EXIT_FAILURE); } else if( len == 0 ) @@ -791,7 +865,7 @@ int wfifoset(int fd, size_t len) s->wdata_size += len; #ifdef SHOW_SERVER_STATS socket_data_qo += len; -#endif +#endif // SHOW_SERVER_STATS //If the interserver has 200% of its normal size full, flush the data. if( s->flag.server && s->wdata_size >= 2*FIFOSIZE_SERVERLINK ) sockt->flush(fd); @@ -805,31 +879,35 @@ int wfifoset(int fd, size_t len) #ifdef SEND_SHORTLIST send_shortlist_add_fd(fd); -#endif +#endif // SEND_SHORTLIST return 0; } int do_sockets(int next) { +#ifndef SOCKET_EPOLL fd_set rfd; struct timeval timeout; +#endif // SOCKET_EPOLL int ret,i; // 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 - for (i = 1; i < sockt->fd_max; i++) - { - if(!sockt->session[fd] +#else // SEND_SHORTLIST + for (i = 1; i < sockt->fd_max; i++) { + if (sockt->session[i] == NULL) continue; - if(sockt->session[fd]>wdata_size) - sockt->session[fd]>func_send(i); + if (sockt->session[i]->wdata_size > 0) + sockt->session[i]->func_send(i); } -#endif +#endif // SEND_SHORTLIST + +#ifndef SOCKET_EPOLL + // Select based Event Dispatcher: // can timeout until the next tick timeout.tv_sec = next/1000; @@ -847,6 +925,20 @@ int do_sockets(int next) } return 0; // interrupted by a signal, just loop and try again } +#else // SOCKET_EPOLL + // Epoll based Event Dispatcher + + ret = epoll_wait(epfd, epevents, epoll_maxevents, next); + if(ret == SOCKET_ERROR) + { + if( sErrno != S_EINTR ) + { + ShowFatalError("do_sockets: epoll_wait() failed, %s!\n", error_msg()); + exit(EXIT_FAILURE); + } + return 0; // interrupted by a signal, just loop and try again + } +#endif // SOCKET_EPOLL sockt->last_tick = time(NULL); @@ -858,7 +950,33 @@ int do_sockets(int next) if( sockt->session[fd] ) sockt->session[fd]->func_recv(fd); } -#else +#elif defined(SOCKET_EPOLL) + // epoll based selection + + for( i = 0; i < ret; i++ ) + { + struct epoll_event *it = &epevents[i]; + struct socket_data *sock = sockt->session[ it->data.fd ]; + + if(!sock) + continue; + + if ((it->events & EPOLLERR) || + (it->events & EPOLLHUP) || + (!(it->events & EPOLLIN))) + { + // Got Error on this connection + sockt->eof( it->data.fd ); + + } else if (it->events & EPOLLIN) { + // data wainting + sock->func_recv( it->data.fd ); + + } + + } + +#else // defined(SOCKET_EPOLL) // otherwise assume that the fd_set is a bit-array and enumerate it in a standard way for( i = 1; ret && i < sockt->fd_max; ++i ) { @@ -868,12 +986,12 @@ int do_sockets(int next) --ret; } } -#endif +#endif // defined(SOCKET_EPOLL) // POSTSEND Send remaining data and handle eof sessions. #ifdef SEND_SHORTLIST send_shortlist_do_sends(); -#else +#else // SEND_SHORTLIST for (i = 1; i < sockt->fd_max; i++) { if(!sockt->session[i]) @@ -887,7 +1005,7 @@ int do_sockets(int next) sockt->session[i]->func_parse(i); //This should close the session immediately. } } -#endif +#endif // SEND_SHORTLIST // parse input data on each socket for(i = 1; i < sockt->fd_max; i++) @@ -905,10 +1023,6 @@ int do_sockets(int next) } } -#ifdef __clang_analyzer__ - // Let Clang's static analyzer know this never happens (it thinks it might because of a NULL check in session_is_valid) - if (!sockt->session[i]) continue; -#endif // __clang_analyzer__ sockt->session[i]->func_parse(i); if(!sockt->session[i]) @@ -930,14 +1044,14 @@ int do_sockets(int next) sprintf(buf, "In: %.03f kB/s (%.03f kB/s, Q: %.03f kB) | Out: %.03f kB/s (%.03f kB/s, Q: %.03f kB) | RAM: %.03f MB", socket_data_i/1024., socket_data_ci/1024., socket_data_qi/1024., socket_data_o/1024., socket_data_co/1024., socket_data_qo/1024., iMalloc->usage()/1024.); #ifdef _WIN32 SetConsoleTitle(buf); -#else +#else // _WIN32 ShowMessage("\033[s\033[1;1H\033[2K%s\033[u", buf); -#endif +#endif // _WIN32 socket_data_last_tick = sockt->last_tick; socket_data_i = socket_data_ci = 0; socket_data_o = socket_data_co = 0; } -#endif +#endif // SHOW_SERVER_STATS return 0; } @@ -947,17 +1061,19 @@ int do_sockets(int next) ////////////////////////////// // IP rules and DDoS protection -typedef struct connect_history { +struct connect_history { uint32 ip; int64 tick; int count; unsigned ddos : 1; -} ConnectHistory; +}; -typedef struct access_control { +struct access_control { uint32 ip; uint32 mask; -} AccessControl; +}; + +VECTOR_STRUCT_DECL(access_control_list, struct access_control); enum aco { ACO_DENY_ALLOW, @@ -965,16 +1081,14 @@ enum aco { ACO_MUTUAL_FAILURE }; -static AccessControl* access_allow = NULL; -static AccessControl* access_deny = NULL; +static struct access_control_list access_allow; +static struct access_control_list 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 = 3*1000; static int ddos_autoreset = 10*60*1000; -DBMap *connect_history = NULL; +struct DBMap *connect_history = NULL; static int connect_check_(uint32 ip); @@ -984,7 +1098,7 @@ static int connect_check(uint32 ip) { int result = connect_check_(ip); if( access_debug ) { - ShowInfo("connect_check: Connection from %d.%d.%d.%d %s\n", CONVIP(ip),result ? "allowed." : "denied!"); + ShowInfo("connect_check: Connection from %u.%u.%u.%u %s\n", CONVIP(ip),result ? "allowed." : "denied!"); } return result; } @@ -994,33 +1108,35 @@ static int connect_check(uint32 ip) /// 1 or 2 : Connection Accepted static int connect_check_(uint32 ip) { - ConnectHistory* hist = NULL; + struct connect_history *hist = NULL; int i; int is_allowip = 0; int is_denyip = 0; int connect_ok = 0; // Search the allow list - for( i=0; i < access_allownum; ++i ){ - if (SUBNET_MATCH(ip, access_allow[i].ip, access_allow[i].mask)) { - if( access_debug ){ - ShowInfo("connect_check: Found match from allow list:%d.%d.%d.%d IP:%d.%d.%d.%d Mask:%d.%d.%d.%d\n", + for (i = 0; i < VECTOR_LENGTH(access_allow); ++i) { + struct access_control *entry = &VECTOR_INDEX(access_allow, i); + if (SUBNET_MATCH(ip, entry->ip, entry->mask)) { + if (access_debug) { + ShowInfo("connect_check: Found match from allow list:%u.%u.%u.%u IP:%u.%u.%u.%u Mask:%u.%u.%u.%u\n", CONVIP(ip), - CONVIP(access_allow[i].ip), - CONVIP(access_allow[i].mask)); + CONVIP(entry->ip), + CONVIP(entry->mask)); } is_allowip = 1; break; } } // Search the deny list - for( i=0; i < access_denynum; ++i ){ - if (SUBNET_MATCH(ip, access_deny[i].ip, access_deny[i].mask)) { - if( access_debug ){ - ShowInfo("connect_check: Found match from deny list:%d.%d.%d.%d IP:%d.%d.%d.%d Mask:%d.%d.%d.%d\n", + for (i = 0; i < VECTOR_LENGTH(access_deny); ++i) { + struct access_control *entry = &VECTOR_INDEX(access_deny, i); + if (SUBNET_MATCH(ip, entry->ip, entry->mask)) { + if (access_debug) { + ShowInfo("connect_check: Found match from deny list:%u.%u.%u.%u IP:%u.%u.%u.%u Mask:%u.%u.%u.%u\n", CONVIP(ip), - CONVIP(access_deny[i].ip), - CONVIP(access_deny[i].mask)); + CONVIP(entry->ip), + CONVIP(entry->mask)); } is_denyip = 1; break; @@ -1064,7 +1180,7 @@ static int connect_check_(uint32 ip) hist->tick = timer->gettick(); if( ++hist->count >= ddos_count ) {// DDoS attack detected hist->ddos = 1; - ShowWarning("connect_check: DDoS Attack detected from %d.%d.%d.%d!\n", CONVIP(ip)); + ShowWarning("connect_check: DDoS Attack detected from %u.%u.%u.%u!\n", CONVIP(ip)); return (connect_ok == 2 ? 1 : 0); } return connect_ok; @@ -1075,7 +1191,7 @@ static int connect_check_(uint32 ip) } } // IP not found, add to history - CREATE(hist, ConnectHistory, 1); + CREATE(hist, struct connect_history, 1); hist->ip = ip; hist->tick = timer->gettick(); uidb_put(connect_history, ip, hist); @@ -1084,11 +1200,12 @@ static int connect_check_(uint32 ip) /// Timer function. /// Deletes old connection history records. -static int connect_check_clear(int tid, int64 tick, int id, intptr_t data) { +static int connect_check_clear(int tid, int64 tick, int id, intptr_t data) +{ int clear = 0; int list = 0; - ConnectHistory *hist = NULL; - DBIterator *iter; + struct connect_history *hist = NULL; + struct DBIterator *iter; if( !db_size(connect_history) ) return 0; @@ -1115,11 +1232,14 @@ static int connect_check_clear(int tid, int64 tick, int id, intptr_t data) { /// Parses the ip address and mask and puts it into acc. /// Returns 1 is successful, 0 otherwise. -int access_ipmask(const char* str, AccessControl* acc) +int access_ipmask(const char *str, struct access_control *acc) { uint32 ip; uint32 mask; + nullpo_ret(str); + nullpo_ret(acc); + if( strcmp(str,"all") == 0 ) { ip = 0; mask = 0; @@ -1152,80 +1272,216 @@ int access_ipmask(const char* str, AccessControl* acc) } } if( access_debug ){ - ShowInfo("access_ipmask: Loaded IP:%d.%d.%d.%d mask:%d.%d.%d.%d\n", CONVIP(ip), CONVIP(mask)); + ShowInfo("access_ipmask: Loaded IP:%u.%u.%u.%u mask:%u.%u.%u.%u\n", CONVIP(ip), CONVIP(mask)); } acc->ip = ip; acc->mask = mask; return 1; } + +/** + * Adds an entry to the access list. + * + * @param setting The setting to read from. + * @param list_name The list name (used in error messages). + * @param access_list The access list to edit. + * + * @retval false in case of failure + */ +bool access_list_add(struct config_setting_t *setting, const char *list_name, struct access_control_list *access_list) +{ + const char *temp = NULL; + int i, setting_length; + + nullpo_retr(false, setting); + nullpo_retr(false, list_name); + nullpo_retr(false, access_list); + + if ((setting_length = libconfig->setting_length(setting)) <= 0) + return false; + + VECTOR_ENSURE(*access_list, setting_length, 1); + for (i = 0; i < setting_length; i++) { + struct access_control acc; + if ((temp = libconfig->setting_get_string_elem(setting, i)) == NULL) { + continue; + } + + if (!access_ipmask(temp, &acc)) { + ShowError("access_list_add: Invalid ip or ip range %s '%d'!\n", list_name, i); + continue; + } + VECTOR_PUSH(*access_list, acc); + } + + return true; +} + ////////////////////////////// -#endif +#endif // MINICORE ////////////////////////////// -int socket_config_read(const char* cfgName) +/** + * Reads 'socket_configuration/ip_rules' and initializes required variables. + * + * @param filename Path to configuration file (used in error and warning messages). + * @param config The current config being parsed. + * @param imported Whether the current config is imported from another file. + * + * @retval false in case of error. + */ +bool socket_config_read_iprules(const char *filename, struct config_t *config, bool imported) { - char line[1024],w1[1024],w2[1024]; - FILE *fp; +#ifndef MINICORE + struct config_setting_t *setting = NULL; + const char *temp = NULL; - fp = fopen(cfgName, "r"); - if(fp == NULL) { - ShowError("File not found: %s\n", cfgName); - return 1; + nullpo_retr(false, filename); + nullpo_retr(false, config); + + if ((setting = libconfig->lookup(config, "socket_configuration/ip_rules")) == NULL) { + if (imported) + return true; + ShowError("socket_config_read: socket_configuration/ip_rules was not found in %s!\n", filename); + return false; } + libconfig->setting_lookup_bool(setting, "enable", &ip_rules); - while (fgets(line, sizeof(line), fp)) { - if(line[0] == '/' && line[1] == '/') - continue; - if (sscanf(line, "%1023[^:]: %1023[^\r\n]", w1, w2) != 2) - continue; + if (!ip_rules) + return true; - if (!strcmpi(w1, "stall_time")) { - sockt->stall_time = atoi(w2); - if( sockt->stall_time < 3 ) - sockt->stall_time = 3;/* a minimum is required to refrain it from killing itself */ + if (libconfig->setting_lookup_string(setting, "order", &temp) == CONFIG_TRUE) { + if (strcmpi(temp, "deny,allow" ) == 0) { + access_order = ACO_DENY_ALLOW; + } else if (strcmpi(temp, "allow, deny") == 0) { + access_order = ACO_ALLOW_DENY; + } else if (strcmpi(temp, "mutual-failure") == 0) { + access_order = ACO_MUTUAL_FAILURE; + } else { + ShowWarning("socket_config_read: invalid value '%s' for socket_configuration/ip_rules/order.\n", temp); } + } + + if ((setting = libconfig->lookup(config, "socket_configuration/ip_rules/allow_list")) == NULL) { + if (!imported) + ShowError("socket_config_read: socket_configuration/ip_rules/allow_list was not found in %s!\n", filename); + } else { + access_list_add(setting, "allow_list", &access_allow); + } + + if ((setting = libconfig->lookup(config, "socket_configuration/ip_rules/deny_list")) == NULL) { + if (!imported) + ShowError("socket_config_read: socket_configuration/ip_rules/deny_list was not found in %s!\n", filename); + } else { + access_list_add(setting, "deny_list", &access_deny); + } +#endif // ! MINICORE + + return true; +} + +/** + * Reads 'socket_configuration/ddos' and initializes required variables. + * + * @param filename Path to configuration file (used in error and warning messages). + * @param config The current config being parsed. + * @param imported Whether the current config is imported from another file. + * + * @retval false in case of error. + */ +bool socket_config_read_ddos(const char *filename, struct config_t *config, bool imported) +{ #ifndef MINICORE - else if (!strcmpi(w1, "enable_ip_rules")) { - ip_rules = config_switch(w2); - } else if (!strcmpi(w1, "order")) { - if (!strcmpi(w2, "deny,allow")) - access_order = ACO_DENY_ALLOW; - else if (!strcmpi(w2, "allow,deny")) - access_order = ACO_ALLOW_DENY; - else if (!strcmpi(w2, "mutual-failure")) - access_order = ACO_MUTUAL_FAILURE; - } else if (!strcmpi(w1, "allow")) { - RECREATE(access_allow, AccessControl, access_allownum+1); - if (access_ipmask(w2, &access_allow[access_allownum])) - ++access_allownum; - else - ShowError("socket_config_read: Invalid ip or ip range '%s'!\n", line); - } else if (!strcmpi(w1, "deny")) { - RECREATE(access_deny, AccessControl, access_denynum+1); - if (access_ipmask(w2, &access_deny[access_denynum])) - ++access_denynum; - else - ShowError("socket_config_read: Invalid ip or ip range '%s'!\n", line); + struct config_setting_t *setting = NULL; + + nullpo_retr(false, filename); + nullpo_retr(false, config); + + if ((setting = libconfig->lookup(config, "socket_configuration/ddos")) == NULL) { + if (imported) + return true; + ShowError("socket_config_read: socket_configuration/ddos was not found in %s!\n", filename); + return false; + } + + libconfig->setting_lookup_int(setting, "interval", &ddos_interval); + libconfig->setting_lookup_int(setting, "count", &ddos_count); + libconfig->setting_lookup_int(setting, "autoreset", &ddos_autoreset); + +#endif // ! MINICORE + return true; +} + +/** + * Reads 'socket_configuration' and initializes required variables. + * + * @param filename Path to configuration file. + * @param imported Whether the current config is imported from another file. + * + * @retval false in case of error. + */ +bool socket_config_read(const char *filename, bool imported) +{ + struct config_t config; + struct config_setting_t *setting = NULL; + const char *import; + int i32 = 0; + bool retval = true; + + nullpo_retr(false, filename); + + if (!libconfig->load_file(&config, filename)) + return false; + + if ((setting = libconfig->lookup(&config, "socket_configuration")) == NULL) { + libconfig->destroy(&config); + if (imported) + return true; + ShowError("socket_config_read: socket_configuration was not found in %s!\n", filename); + return false; + } + + if (libconfig->setting_lookup_int(setting, "stall_time", &i32) == CONFIG_TRUE) { + if (i32 < 3) + i32 = 3; /* a minimum is required in order to refrain from killing itself */ + sockt->stall_time = i32; + } + +#ifdef SOCKET_EPOLL + if (libconfig->setting_lookup_int(setting, "epoll_maxevents", &i32) == CONFIG_TRUE) { + if (i32 < 16) + i32 = 16; // minimum that seems to be useful + epoll_maxevents = i32; + } +#endif // SOCKET_EPOLL + +#ifndef MINICORE + { + uint32 ui32 = 0; + libconfig->setting_lookup_bool(setting, "debug", &access_debug); + if (libconfig->setting_lookup_uint32(setting, "socket_max_client_packet", &ui32) == CONFIG_TRUE) { + socket_max_client_packet = ui32; } - else if (!strcmpi(w1,"ddos_interval")) - ddos_interval = atoi(w2); - else if (!strcmpi(w1,"ddos_count")) - ddos_count = atoi(w2); - else if (!strcmpi(w1,"ddos_autoreset")) - ddos_autoreset = atoi(w2); - else if (!strcmpi(w1,"debug")) - access_debug = config_switch(w2); - else if (!strcmpi(w1,"socket_max_client_packet")) - socket_max_client_packet = strtoul(w2, NULL, 0); -#endif - else if (!strcmpi(w1, "import")) - socket_config_read(w2); - else - ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName); - } - - fclose(fp); - return 0; + } + + if (!socket_config_read_iprules(filename, &config, imported)) + retval = false; + if (!socket_config_read_ddos(filename, &config, imported)) + retval = false; +#endif // MINICORE + + // import should overwrite any previous configuration, so it should be called last + if (libconfig->lookup_string(&config, "import", &import) == CONFIG_TRUE) { + if (strcmp(import, filename) == 0 || strcmp(import, SOCKET_CONF_FILENAME) == 0) { + ShowWarning("socket_config_read: Loop detected! Skipping 'import'...\n"); + } else { + if (!socket_config_read(import, true)) + retval = false; + } + } + + libconfig->destroy(&config); + return retval; } void socket_final(void) @@ -1234,11 +1490,9 @@ void socket_final(void) #ifndef MINICORE if( connect_history ) db_destroy(connect_history); - if( access_allow ) - aFree(access_allow); - if( access_deny ) - aFree(access_deny); -#endif + VECTOR_CLEAR(access_allow); + VECTOR_CLEAR(access_deny); +#endif // MINICORE for( i = 1; i < sockt->fd_max; i++ ) if(sockt->session[i]) @@ -1254,6 +1508,18 @@ void socket_final(void) VECTOR_CLEAR(sockt->lan_subnets); VECTOR_CLEAR(sockt->allowed_ips); VECTOR_CLEAR(sockt->trusted_ips); + +#ifdef SOCKET_EPOLL + if(epfd != SOCKET_ERROR){ + close(epfd); + epfd = SOCKET_ERROR; + } + if(epevents != NULL){ + aFree(epevents); + epevents = NULL; + } +#endif // SOCKET_EPOLL + } /// Closes a socket. @@ -1263,7 +1529,17 @@ void socket_close(int fd) return;// invalid sockt->flush(fd); // Try to send what's left (although it might not succeed since it's a nonblocking socket) + +#ifndef SOCKET_EPOLL + // Select based Event Dispatcher sFD_CLR(fd, &readfds);// this needs to be done before closing the socket +#else // SOCKET_EPOLL + // Epoll based Event Dispatcher + epevent.data.fd = fd; + epevent.events = EPOLLIN; + epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &epevent); // removing the socket from epoll when it's being closed is not required but recommended +#endif // SOCKET_EPOLL + sShutdown(fd, SHUT_RDWR); // Disallow further reads/writes sClose(fd); // We don't really care if these closing functions return an error, we are just shutting down and not reusing this socket. if (sockt->session[fd]) delete_session(fd); @@ -1354,7 +1630,6 @@ int socket_getips(uint32* ips, int max) void socket_init(void) { - char *SOCKET_CONF_FILENAME = "conf/packet.conf"; uint64 rlim_cur = FD_SETSIZE; #ifdef WIN32 @@ -1400,20 +1675,44 @@ void socket_init(void) } } } -#endif +#endif // defined(HAVE_SETRLIMIT) && !defined(CYGWIN) + +#ifndef MINICORE + VECTOR_INIT(access_allow); + VECTOR_INIT(access_deny); +#endif // ! MINICORE // Get initial local ips sockt->naddr_ = sockt->getips(sockt->addr_,16); + socket_config_read(SOCKET_CONF_FILENAME, false); + +#ifndef SOCKET_EPOLL + // Select based Event Dispatcher: sFD_ZERO(&readfds); + ShowInfo("Server uses '" CL_WHITE "select" CL_RESET "' as event dispatcher\n"); + +#else // SOCKET_EPOLL + // Epoll based Event Dispatcher: + epfd = epoll_create(FD_SETSIZE); // 2.6.8 or newer ignores the expected socket amount argument + if(epfd == SOCKET_ERROR){ + ShowError("Failed to Create Epoll Event Dispatcher: %s\n", error_msg()); + exit(EXIT_FAILURE); + } + + memset(&epevent, 0x00, sizeof(struct epoll_event)); + epevents = aCalloc(epoll_maxevents, sizeof(struct epoll_event)); + + ShowInfo("Server uses '" CL_WHITE "epoll" CL_RESET "' with up to " CL_WHITE "%d" CL_RESET " events per cycle as event dispatcher\n", epoll_maxevents); + +#endif // SOCKET_EPOLL + #if defined(SEND_SHORTLIST) memset(send_shortlist_set, 0, sizeof(send_shortlist_set)); -#endif +#endif // defined(SEND_SHORTLIST) CREATE(sockt->session, struct socket_data *, FD_SETSIZE); - socket_config_read(SOCKET_CONF_FILENAME); - // initialize last send-receive tick sockt->last_tick = time(NULL); @@ -1426,9 +1725,9 @@ void socket_init(void) connect_history = uidb_alloc(DB_OPT_RELEASE_DATA); timer->add_func_list(connect_check_clear, "connect_check_clear"); timer->add_interval(timer->gettick()+1000, connect_check_clear, 0, 0, 5*60*1000); -#endif +#endif // MINICORE - ShowInfo("Server supports up to '"CL_WHITE"%"PRId64""CL_RESET"' concurrent connections.\n", rlim_cur); + ShowInfo("Server supports up to '"CL_WHITE"%"PRIu64""CL_RESET"' concurrent connections.\n", rlim_cur); } bool session_is_valid(int fd) @@ -1442,9 +1741,11 @@ bool session_is_active(int fd) } // Resolves hostname into a numeric ip. -uint32 host2ip(const char* hostname) +uint32 host2ip(const char *hostname) { - struct hostent* h = gethostbyname(hostname); + struct hostent* h; + nullpo_ret(hostname); + h = gethostbyname(hostname); return (h != NULL) ? ntohl(*(uint32*)h->h_addr) : 0; } @@ -1477,7 +1778,8 @@ uint16 ntows(uint16 netshort) } /* [Ind/Hercules] - socket_datasync */ -void socket_datasync(int fd, bool send) { +void socket_datasync(int fd, bool send) +{ struct { unsigned int length;/* short is not enough for some */ } data_list[] = { @@ -1616,7 +1918,7 @@ void send_shortlist_do_sends(void) } } } -#endif +#endif // SEND_SHORTLIST /** * Checks whether the given IP comes from LAN or WAN. @@ -1686,7 +1988,7 @@ bool socket_trusted_ip_check(uint32 ip) * @param[in] groupname Current group name, for output/logging reasons. * @return The amount of entries read, zero in case of errors. */ -int socket_net_config_read_sub(config_setting_t *t, struct s_subnet_vector *list, const char *filename, const char *groupname) +int socket_net_config_read_sub(struct config_setting_t *t, struct s_subnet_vector *list, const char *filename, const char *groupname) { int i, len; char ipbuf[64], maskbuf[64]; @@ -1722,11 +2024,11 @@ int socket_net_config_read_sub(config_setting_t *t, struct s_subnet_vector *list */ void socket_net_config_read(const char *filename) { - config_t network_config; + struct config_t network_config; int i; nullpo_retv(filename); - if (libconfig->read_file(&network_config, filename)) { + if (!libconfig->load_file(&network_config, filename)) { ShowError("LAN Support configuration file is not found: '%s'. This server won't be able to accept connections from any servers.\n", filename); return; } @@ -1751,15 +2053,18 @@ void socket_net_config_read(const char *filename) ShowError("No allowed server IP ranges configured. This server won't be able to accept connections from any char servers.\n"); } ARR_FIND(0, VECTOR_LENGTH(sockt->allowed_ips), i, SUBNET_MATCH(0, VECTOR_INDEX(sockt->allowed_ips, i).ip, VECTOR_INDEX(sockt->allowed_ips, i).mask)); +#ifndef BUILDBOT if (i != VECTOR_LENGTH(sockt->allowed_ips)) { ShowWarning("Using a wildcard IP range in the allowed server IPs is NOT RECOMMENDED.\n"); ShowNotice("Please edit your '%s' allowed list to fit your network configuration.\n", filename); } +#endif // BUILDBOT libconfig->destroy(&network_config); return; } -void socket_defaults(void) { +void socket_defaults(void) +{ sockt = &sockt_s; sockt->fd_max = 0; |