diff options
-rw-r--r-- | Changelog-SVN.txt | 5 | ||||
-rw-r--r-- | conf-tmpl/inter_athena.conf | 3 | ||||
-rw-r--r-- | conf-tmpl/packet_athena.conf | 46 | ||||
-rw-r--r-- | src/common/socket.c | 341 | ||||
-rw-r--r-- | src/common/socket.h | 16 | ||||
-rw-r--r-- | src/map/clif.c | 6 | ||||
-rw-r--r-- | src/map/map.c | 9 |
7 files changed, 391 insertions, 35 deletions
diff --git a/Changelog-SVN.txt b/Changelog-SVN.txt index b6b7e0867..4647f89d5 100644 --- a/Changelog-SVN.txt +++ b/Changelog-SVN.txt @@ -1,5 +1,10 @@ Date Added +03/16 + * Updated jA's dummy socket to mod1137 [celest] + * Added jA's ddos protection system -- check packet_athena.conf [celest] + * Moved stall_time's reading from inter_athena.conf to packet_athena.conf + 03/15 * Fixed a compile warning in pc.c [celest] * Updated Soul Breaker's damage display, by DracoRPG [celest] diff --git a/conf-tmpl/inter_athena.conf b/conf-tmpl/inter_athena.conf index b73865f8f..a2155a5ac 100644 --- a/conf-tmpl/inter_athena.conf +++ b/conf-tmpl/inter_athena.conf @@ -32,9 +32,6 @@ inter_log_filename: log/inter.log // Level range for sharing within a party party_share_level: 10 -// How long can a socket stall before closing the connection -stall_time: 60 - // SQL version options only diff --git a/conf-tmpl/packet_athena.conf b/conf-tmpl/packet_athena.conf new file mode 100644 index 000000000..b8e1f31f7 --- /dev/null +++ b/conf-tmpl/packet_athena.conf @@ -0,0 +1,46 @@ +// ソケット関連の設定です。 (Untranslated yet)
+
+
+// How long can a socket stall before closing the connection (in seconds)
+stall_time: 60
+
+//---- Ddos Protection Settings ----
+
+// デバッグ情報の表示(バグ報告の際にコピペして頂けると助かります)
+// debug: 1
+
+// ddos攻撃と判断する為のルール設定
+// ddos_interval msec以内の接続要求がddos_count回続いた場合に、
+// ddos攻撃されたと判定します。
+
+// 接続間隔(msec)
+ddos_interval: 3000
+
+// 接続回数
+ddos_count: 5
+
+// ddos制限を解除する間隔(msec)
+// この時間経過すると、接続制限が解除されます。
+ddos_autoreset: 600000
+
+// アクセス制限の判定順序(Apacheと同じ)
+// deny,allow が標準になっています。
+
+order: deny,allow
+// order: allow,deny
+// order: mutual-failture
+
+// アクセスコントロールするIPリスト
+// allow : ddosチェックの結果に関係なく許可
+// deny : 不許可
+// 指定無し : ddosチェックの結果で許可 / 不許可を決定
+// ただし、mutual-failture の場合は不許可になります。
+
+// allow: 127.0.0.1
+// allow: 192.168.0.0/16
+// allow: 10.0.0.0/255.0.0.0
+allow: all
+
+// deny: 127.0.0.1
+
+import: conf/import/packet_conf.txt
\ No newline at end of file diff --git a/src/common/socket.c b/src/common/socket.c index ad4e7d2e2..19e4275a8 100644 --- a/src/common/socket.c +++ b/src/common/socket.c @@ -29,9 +29,10 @@ typedef int socklen_t; #include <fcntl.h> #include <string.h> -#include "mmo.h" // [Valaris] thanks to fov #include "socket.h" -#include "utils.h" +#include "../common/mmo.h" // [Valaris] thanks to fov +#include "../common/timer.h" +#include "../common/utils.h" #ifdef MEMWATCH #include "memwatch.h" @@ -54,11 +55,9 @@ struct socket_data *session[FD_SETSIZE]; static int null_parse(int fd); static int (*default_func_parse)(int) = null_parse; -// fdが不正な時に代わりに読み書きするバッファ -unsigned char socket_dummy[SOCKET_DUMMY_SIZE]; - static int null_console_parse(char *buf); static int (*default_console_parse)(char*) = null_console_parse; +static int connect_check(unsigned int ip); /*====================================== * CORE : Set function @@ -195,16 +194,20 @@ static int connect_client(int listen_fd) setsocketopts(fd); - if(fd==-1) + if(fd==-1) { perror("accept"); - else + return -1; + } else if (!connect_check(*(unsigned int*)(&client_address.sin_addr))) { + close(fd); + return -1; + } else FD_SET(fd,&readfds); #ifdef _WIN32 - { - unsigned long val = 1; - ioctlsocket(fd, FIONBIO, &val); - } + { + unsigned long val = 1; + ioctlsocket(fd, FIONBIO, &val); + } #else result = fcntl(fd, F_SETFL, O_NONBLOCK); #endif @@ -415,7 +418,7 @@ int make_connection(long ip,int port) int delete_session(int fd) { - if(fd<0 || fd>=FD_SETSIZE) + if(fd<=0 || fd>=FD_SETSIZE) return -1; FD_CLR(fd,&readfds); if(session[fd]){ @@ -533,9 +536,269 @@ int do_parsepacket(void) return 0; } -void do_socket(void) +/* DDoS 攻撃対策 */ + +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; +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; + int status; + int count; + unsigned int ip; + unsigned int tick; +}; +static struct _connect_history *connect_history[0x10000]; +static int connect_check_(unsigned int ip); + +// 接続できるかどうかの確認 +// false : 接続OK +// true : 接続NG +static int connect_check(unsigned int ip) { + int result = connect_check_(ip); + if(access_debug) { + printf("connect_check: connection from %08x %s\n", + ip,result ? "allowed" : "denied"); + } + return result; +} + +static int connect_check_(unsigned int ip) { + struct _connect_history *hist = connect_history[ip & 0xFFFF]; + struct _connect_history *hist_new; + int i,is_allowip = 0,is_denyip = 0,connect_ok = 0; + + // allow , deny リストに入っているか確認 + for(i = 0;i < access_allownum; i++) { + if((ip & access_allow[i].mask) == (access_allow[i].ip & access_allow[i].mask)) { + if(access_debug) { + printf("connect_check: match allow list from:%08x ip:%08x mask:%08x\n", + ip,access_allow[i].ip,access_allow[i].mask); + } + is_allowip = 1; + break; + } + } + for(i = 0;i < access_denynum; i++) { + if((ip & access_deny[i].mask) == (access_deny[i].ip & access_deny[i].mask)) { + if(access_debug) { + printf("connect_check: match deny list from:%08x ip:%08x mask:%08x\n", + ip,access_deny[i].ip,access_deny[i].mask); + } + is_denyip = 1; + break; + } + } + // コネクト出来るかどうか確認 + // connect_ok + // 0 : 無条件に拒否 + // 1 : 田代砲チェックの結果次第 + // 2 : 無条件に許可 + switch(access_order) { + case ACO_DENY_ALLOW: + default: + if(is_allowip) { + connect_ok = 2; + } else if(is_denyip) { + connect_ok = 0; + } else { + connect_ok = 1; + } + break; + case ACO_ALLOW_DENY: + if(is_denyip) { + connect_ok = 0; + } else if(is_allowip) { + connect_ok = 2; + } else { + connect_ok = 1; + } + break; + case ACO_MUTUAL_FAILTURE: + if(is_allowip) { + connect_ok = 2; + } else { + connect_ok = 0; + } + break; + } + + // 接続履歴を調べる + while(hist) { + if(ip == hist->ip) { + // 同じIP発見 + if(hist->status) { + // ban フラグが立ってる + return (connect_ok == 2 ? 1 : 0); + } else if(DIFF_TICK(gettick(),hist->tick) < ddos_interval) { + // ddos_interval秒以内にリクエスト有り + hist->tick = gettick(); + if(hist->count++ >= ddos_count) { + // ddos 攻撃を検出 + hist->status = 1; + printf("connect_check: ddos attack detected (%d.%d.%d.%d)\n", + ip & 0xFF,(ip >> 8) & 0xFF,(ip >> 16) & 0xFF,ip >> 24); + return (connect_ok == 2 ? 1 : 0); + } else { + return connect_ok; + } + } else { + // ddos_interval秒以内にリクエスト無いのでタイマークリア + hist->tick = gettick(); + hist->count = 0; + return connect_ok; + } + } + hist = hist->next; + } + // IPリストに無いので新規作成 + hist_new = aCalloc(1,sizeof(struct _connect_history)); + hist_new->ip = ip; + hist_new->tick = gettick(); + if(connect_history[ip & 0xFFFF] != NULL) { + hist = connect_history[ip & 0xFFFF]; + hist->prev = hist_new; + hist_new->next = hist; + } + connect_history[ip & 0xFFFF] = hist_new; + return connect_ok; +} + +static int connect_check_clear(int tid,unsigned int tick,int id,int data) { + int i; + int clear = 0; + int list = 0; + struct _connect_history *hist , *hist2; + for(i = 0;i < 0x10000 ; i++) { + hist = connect_history[i]; + while(hist) { + if( + (DIFF_TICK(tick,hist->tick) > ddos_interval * 3 && !hist->status) || + (DIFF_TICK(tick,hist->tick) > ddos_autoreset && hist->status) + ) { + // clear data + hist2 = hist->next; + if(hist->prev) { + hist->prev->next = hist->next; + } else { + connect_history[i] = hist->next; + } + if(hist->next) { + hist->next->prev = hist->prev; + } + aFree(hist); + hist = hist2; + clear++; + } else { + hist = hist->next; + list++; + } + } + } + if(access_debug) { + printf("connect_check_clear: clear = %d list = %d\n",clear,list); + } + return list; +} + +// IPマスクチェック +int access_ipmask(const char *str,struct _access_control* acc) { - FD_ZERO(&readfds); + unsigned int mask=0,i=0,m,ip, a0,a1,a2,a3; + if( !strcmp(str,"all") ) { + ip = 0; + mask = 0; + } else { + if( sscanf(str,"%d.%d.%d.%d%n",&a0,&a1,&a2,&a3,&i)!=4 || i==0) { + printf("access_ipmask: unknown format %s\n",str); + return 0; + } + ip = (a3 << 24) | (a2 << 16) | (a1 << 8) | a0; + + if(sscanf(str+i,"/%d.%d.%d.%d",&a0,&a1,&a2,&a3)==4 ){ + mask = (a3 << 24) | (a2 << 16) | (a1 << 8) | a0; + } else if(sscanf(str+i,"/%d",&m) == 1) { + for(i=0;i<m;i++) { + mask = (mask >> 1) | 0x80000000; + } + mask = ntohl(mask); + } else { + mask = 0xFFFFFFFF; + } + } + if(access_debug) { + printf("access_ipmask: ip:%08x mask:%08x %s\n",ip,mask,str); + } + acc->ip = ip; + acc->mask = mask; + return 1; +} + +int socket_config_read(const char *cfgName) { + int i; + char line[1024],w1[1024],w2[1024]; + FILE *fp; + + fp=fopen(cfgName, "r"); + if(fp==NULL){ + printf("File not found: %s\n", cfgName); + return 1; + } + while(fgets(line,1020,fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + if(strcmpi(w1,"stall_time")==0){ + stall_time_ = atoi(w2); + } else if(strcmpi(w1,"order")==0){ + access_order=atoi(w2); + if(strcmpi(w2,"deny,allow")==0) access_order=ACO_DENY_ALLOW; + if(strcmpi(w2,"allow,deny")==0) access_order=ACO_ALLOW_DENY; + if(strcmpi(w2,"mutual-failture")==0) access_order=ACO_MUTUAL_FAILTURE; + } else if(strcmpi(w1,"allow")==0){ + access_allow = aRealloc(access_allow,(access_allownum+1)*sizeof(struct _access_control)); + if(access_ipmask(w2,&access_allow[access_allownum])) { + access_allownum++; + } + } else if(strcmpi(w1,"deny")==0){ + access_deny = aRealloc(access_deny,(access_denynum+1)*sizeof(struct _access_control)); + if(access_ipmask(w2,&access_deny[access_denynum])) { + access_denynum++; + } + } 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 = atoi(w2); + } else if (strcmpi(w1, "import") == 0) + socket_config_read(w2); + } + fclose(fp); + return 0; } int RFIFOSKIP(int fd,int len) @@ -638,3 +901,53 @@ int Net_Init(void) return(0); } + +void do_final_socket(void) +{ + int i; + struct _connect_history *hist , *hist2; + for(i=0; i<fd_max; i++) { + if(session[i]) { + delete_session(i); + } + } + for(i=0; i<0x10000; i++) { + hist = connect_history[i]; + while(hist) { + hist2 = hist->next; + aFree(hist); + hist = hist2; + } + } + if (access_allow) + aFree(access_allow); + if (access_deny) + aFree(access_deny); + + // session[0] のダミーデータを削除 + if (session[0]) { + aFree(session[0]->rdata); + aFree(session[0]->wdata); + aFree(session[0]); + } +} + +void do_socket(void) +{ + char *SOCKET_CONF_FILENAME = "conf/packet_athena.conf"; + + FD_ZERO(&readfds); + + atexit(do_final_socket); + socket_config_read(SOCKET_CONF_FILENAME); + + // session[0] にダミーデータを確保する + CREATE(session[0], struct socket_data, 1); + CREATE_A(session[0]->rdata, unsigned char, rfifo_size); + CREATE_A(session[0]->wdata, unsigned char, wfifo_size); + session[0]->max_rdata = rfifo_size; + session[0]->max_wdata = wfifo_size; + + // とりあえず5分ごとに不要なデータを削除する + add_timer_interval(gettick()+1000,connect_check_clear,0,0,300*1000); +} diff --git a/src/common/socket.h b/src/common/socket.h index 52581faf7..130c33d0c 100644 --- a/src/common/socket.h +++ b/src/common/socket.h @@ -22,18 +22,14 @@ extern time_t stall_time_; // define declaration -// fdが不正な時に代わりに読み書きするバッファ -#define SOCKET_DUMMY_SIZE 32768 -extern unsigned char socket_dummy[SOCKET_DUMMY_SIZE]; - -#define RFIFOSPACE(fd) (fd <= 0 ? SOCKET_DUMMY_SIZE : session[fd]->max_rdata-session[fd]->rdata_size) -#define RFIFOP(fd,pos) (fd <= 0 ? socket_dummy : session[fd]->rdata+session[fd]->rdata_pos+(pos)) +#define RFIFOSPACE(fd) (session[fd]->max_rdata-session[fd]->rdata_size) +#define RFIFOP(fd,pos) (session[fd]->rdata+session[fd]->rdata_pos+(pos)) // use function instead of macro. #define RFIFOB(fd,pos) (*(unsigned char*)RFIFOP(fd,pos)) #define RFIFOW(fd,pos) (*(unsigned short*)RFIFOP(fd,pos)) #define RFIFOL(fd,pos) (*(unsigned int*)RFIFOP(fd,pos)) -#define RFIFOREST(fd) (fd <= 0 ? 0 : session[fd]->rdata_size-session[fd]->rdata_pos) -#define RFIFOFLUSH(fd) (fd <= 0 ? 0 : memmove(session[fd]->rdata,RFIFOP(fd,0),RFIFOREST(fd)),session[fd]->rdata_size=RFIFOREST(fd),session[fd]->rdata_pos=0) +#define RFIFOREST(fd) (session[fd]->rdata_size-session[fd]->rdata_pos) +#define RFIFOFLUSH(fd) (memmove(session[fd]->rdata,RFIFOP(fd,0),RFIFOREST(fd)),session[fd]->rdata_size=RFIFOREST(fd),session[fd]->rdata_pos=0) //#define RFIFOSKIP(fd,len) ((session[fd]->rdata_size-session[fd]->rdata_pos-(len)<0) ? (fprintf(stderr,"too many skip\n"),exit(1)) : (session[fd]->rdata_pos+=(len))) #define RBUFP(p,pos) (((unsigned char*)(p))+(pos)) @@ -41,8 +37,8 @@ extern unsigned char socket_dummy[SOCKET_DUMMY_SIZE]; #define RBUFW(p,pos) (*(unsigned short*)RBUFP((p),(pos))) #define RBUFL(p,pos) (*(unsigned int*)RBUFP((p),(pos))) -#define WFIFOSPACE(fd) (fd <= 0 ? SOCKET_DUMMY_SIZE : session[fd]->max_wdata-session[fd]->wdata_size) -#define WFIFOP(fd,pos) (fd <= 0 ? socket_dummy : session[fd]->wdata+session[fd]->wdata_size+(pos)) +#define WFIFOSPACE(fd) (session[fd]->max_wdata-session[fd]->wdata_size) +#define WFIFOP(fd,pos) (session[fd]->wdata+session[fd]->wdata_size+(pos)) #define WFIFOB(fd,pos) (*(unsigned char*)WFIFOP(fd,pos)) #define WFIFOW(fd,pos) (*(unsigned short*)WFIFOP(fd,pos)) #define WFIFOL(fd,pos) (*(unsigned int*)WFIFOP(fd,pos)) diff --git a/src/map/clif.c b/src/map/clif.c index a44979ff4..2e1a7e1e7 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -973,7 +973,7 @@ int clif_mob_equip(struct mob_data *md, int nameid) { */ static int clif_mob0078(struct mob_data *md, unsigned char *buf) { - int level; + int level, i; memset(buf,0,packet_len_table[0x78]); @@ -986,7 +986,7 @@ static int clif_mob0078(struct mob_data *md, unsigned char *buf) WBUFW(buf,10)=md->opt2; WBUFW(buf,12)=md->option; WBUFW(buf,14)=mob_get_viewclass(md->class_); - if((mob_get_viewclass(md->class_) <= 23) || (mob_get_viewclass(md->class_) == 812) || (mob_get_viewclass(md->class_) >= 4001)) { + if((i=mob_get_viewclass(md->class_)) <= 23 || i == 812 || i >= 4001) { WBUFW(buf,12)|=mob_db[md->class_].option; WBUFW(buf,16)=mob_get_hair(md->class_); WBUFW(buf,18)=mob_get_weapon(md->class_); @@ -1360,7 +1360,7 @@ int clif_spawnmob(struct mob_data *md) nullpo_retr(0, md); - if (mob_get_viewclass(md->class_) > 23 ) { + if (mob_get_viewclass(md->class_) > 23) { memset(buf,0,packet_len_table[0x7c]); WBUFW(buf,0)=0x7c; diff --git a/src/map/map.c b/src/map/map.c index 7c8275cf9..3a6a29fde 100644 --- a/src/map/map.c +++ b/src/map/map.c @@ -2896,8 +2896,10 @@ int inter_config_read(char *cfgName) i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); if(i!=2) continue; - if(strcmpi(w1,"stall_time")==0){ - stall_time_ = atoi(w2); + //support the import command, just like any other config + if(strcmpi(w1,"import")==0){ + inter_config_read(w2); + } #ifndef TXT_ONLY } else if(strcmpi(w1,"item_db_db")==0){ strcpy(item_db_db,w2); @@ -2959,9 +2961,6 @@ int inter_config_read(char *cfgName) } else if(strcmpi(w1,"log_db_port")==0) { log_db_port = atoi(w2); #endif - //support the import command, just like any other config - } else if(strcmpi(w1,"import")==0){ - inter_config_read(w2); } } fclose(fp); |