├── anddos ├── anddos.conf ├── .gitignore ├── config ├── test_ngx_http_anddos.c └── ngx_http_anddos_module.c ├── .gitignore ├── anddos_go ├── Dockerfile ├── README.md ├── anddos.go ├── anddos_handler.go └── anddos_structs.go ├── README.md └── nginx.conf /anddos/anddos.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /anddos/ 3 | *_dump.json -------------------------------------------------------------------------------- /anddos/.gitignore: -------------------------------------------------------------------------------- 1 | nbproject 2 | .DSStore 3 | a.out -------------------------------------------------------------------------------- /anddos_go/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.7 2 | COPY anddos_go /usr/bin 3 | ENTRYPOINT ["anddos_go"] -------------------------------------------------------------------------------- /anddos/config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_anddos_module 2 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_anddos_module" 3 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_anddos_module.c" 4 | -------------------------------------------------------------------------------- /anddos_go/README.md: -------------------------------------------------------------------------------- 1 | # Standalone ANDDOS 2 | 3 | An attempt to rewrite, extend and distribute ANDDOS as a standalone service. It should rely on ReverseProxy from golang net/httputil package. 4 | 5 | Under development. 6 | 7 | ## Usage 8 | 9 | Command line 10 | 11 | ``` 12 | $ anddos_go --help 13 | Usage of ./anddos_go: 14 | -listenPort string 15 | Port where ANDDOS should listen (default ":8080") 16 | -upstreamURL string 17 | URL to protected server (default "http://localhost:80") 18 | $ anddos_go 19 | ``` 20 | 21 | ### Statically linked (mostly) build 22 | 23 | ``` 24 | CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' . 25 | ``` 26 | 27 | ### Docker Image 28 | 29 | TODO -------------------------------------------------------------------------------- /anddos_go/anddos.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | "net/http/httputil" 8 | "net/url" 9 | ) 10 | 11 | // AnddosStatus keeps global status 12 | var AnddosStatus AnddosState 13 | 14 | // AnddosClients keeps list of all known clients based on IP 15 | var AnddosClients map[string]*AnddosClient 16 | 17 | func main() { 18 | // Parse CLI arguments 19 | var listenPort = flag.String("listenPort", ":8080", "Port where ANDDOS should listen") 20 | var upstreamURL = flag.String("upstreamURL", "http://localhost:80", "URL to protected server") 21 | flag.Parse() 22 | 23 | log.Println("Starting ANDDOS...") 24 | 25 | // Bootstrap Anddos 26 | AnddosStatus.Threshold = 1000 27 | AnddosClients = make(map[string]*AnddosClient) 28 | 29 | // Setup reverse proxy 30 | remote, err := url.Parse(*upstreamURL) 31 | if err != nil { 32 | panic(err) 33 | } 34 | proxy := httputil.NewSingleHostReverseProxy(remote) 35 | http.HandleFunc("/", AnddosHandler(proxy)) 36 | 37 | // Serve the traffic 38 | err = http.ListenAndServe(*listenPort, nil) 39 | if err != nil { 40 | panic(err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About ANDDOS 2 | ------------ 3 | Anti-DDOS module for nginx webserver. The aim is to restrict impact DDOS attacks to your webserver on HTTP level. 4 | 5 | For more information visit https://github.com/aufi/anddos/wiki 6 | 7 | ***Under development*** 8 | 9 | Installation 10 | ------------ 11 | 12 | Install/compile anddos to nginx (development enviroment) 13 | 14 | $ cd src/nginx 15 | 16 | $ make clean && ./configure --add-module=../anddos/anddos && make -j2 17 | 18 | # make install 19 | or 20 | # /usr/local/nginx/sbin/nginx -s stop; make install && rm /usr/local/nginx/logs/* && /usr/local/nginx/sbin/nginx 21 | 22 | 23 | Setup config files - nginx.conf 24 | 25 | location / { 26 | 27 | anddos; #add this line to enable anddos 28 | 29 | proxy_pass http://127.0.0.1:80; #your app server 30 | proxy_redirect off; 31 | 32 | proxy_set_header Host $host; 33 | proxy_set_header X-Real-IP $remote_addr; 34 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 35 | proxy_max_temp_file_size 0; 36 | } 37 | 38 | (Re)start nginx 39 | 40 | # /usr/local/nginx/sbin/nginx -s stop 41 | # /usr/local/nginx/sbin/nginx 42 | or 43 | # /usr/local/nginx/sbin/nginx -s reload 44 | 45 | Author 46 | ------ 47 | Marek Aufart, aufi.cz@gmail.com, http://twitter.com/auficz 48 | -------------------------------------------------------------------------------- /anddos_go/anddos_handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "net" 8 | "net/http" 9 | "net/http/httputil" 10 | "time" 11 | ) 12 | 13 | // AnddosHandler passes HTTP connections via Reverse Proxy 14 | func AnddosHandler(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) { 15 | return func(w http.ResponseWriter, r *http.Request) { 16 | log.Println(r.Method) 17 | log.Println(r.URL) 18 | timeStart := time.Now() 19 | remoteIP, _, _ := net.SplitHostPort(r.RemoteAddr) 20 | 21 | log.Println(r.RemoteAddr) 22 | // find or init a client 23 | if AnddosClients[remoteIP] == nil { 24 | AnddosClients[remoteIP] = &AnddosClient{} 25 | } 26 | 27 | // decide if block or serve 28 | if AnddosClients[remoteIP].Score >= AnddosStatus.Threshold { 29 | // blocked TODO 30 | } else { 31 | // Serve the request 32 | p.ServeHTTP(w, r) 33 | 34 | // update client stats 35 | AnddosClients[remoteIP].AddRequest(200, r.Method, w.Header().Get("Content-type"), float32(time.Since(timeStart))/1000, r.RequestURI) 36 | 37 | // TODO: print less often later.. 38 | log.Printf("%+v\n", AnddosStatus) 39 | clientsDump, _ := json.Marshal(AnddosClients) 40 | ioutil.WriteFile("/tmp/anddos_clients_dump.json", clientsDump, 0600) 41 | 42 | //log.Println(w.Header().Get("Content-length")) add to scoring? 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | #user nobody; 3 | worker_processes 1; 4 | 5 | #error_log logs/error.log; 6 | 7 | #pid logs/nginx.pid; 8 | 9 | 10 | events { 11 | worker_connections 1024; 12 | } 13 | 14 | 15 | http { 16 | include mime.types; 17 | default_type application/octet-stream; 18 | 19 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 20 | # '$status $body_bytes_sent "$http_referer" ' 21 | # '"$http_user_agent" "$http_x_forwarded_for"'; 22 | 23 | #access_log logs/access.log main; 24 | 25 | sendfile on; 26 | #tcp_nopush on; 27 | 28 | #keepalive_timeout 0; 29 | keepalive_timeout 65; 30 | 31 | #gzip on; 32 | 33 | server { 34 | 35 | listen 8080; 36 | server_name localhost; 37 | 38 | #charset koi8-r; 39 | 40 | #access_log logs/host.access.log main; 41 | 42 | 43 | error_log logs/info.log info; 44 | 45 | 46 | location / { 47 | 48 | anddos; 49 | 50 | proxy_pass http://127.0.0.1:80; 51 | proxy_redirect off; 52 | 53 | proxy_set_header Host $host; 54 | proxy_set_header X-Real-IP $remote_addr; 55 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 56 | proxy_max_temp_file_size 0; 57 | 58 | #root html; 59 | #index index.html index.htm; 60 | } 61 | 62 | 63 | 64 | 65 | } 66 | 67 | 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /anddos_go/anddos_structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "strings" 4 | 5 | // AnddosState keep overall server status and list of clients 6 | type AnddosState struct { 7 | Level int //(0)Normal, (10)Attack, (100)Overload; not used yet 8 | Threshold int 9 | //clients map[string]AnddosClient 10 | AvgClient AnddosClient 11 | RequestsCount int64 12 | BlockedRequestsCount int64 13 | //StartedAt Time 14 | } 15 | 16 | // AnddosClient keeps information about a client 17 | type AnddosClient struct { 18 | // missing: set, key 19 | ip string 20 | ua string 21 | 22 | RequestsCount int 23 | NotModCount int 24 | HTTP1Count int 25 | HTTP2Count int 26 | HTTP3Count int 27 | HTTP4Count int 28 | HTTP5Count int 29 | 30 | HTTPGetCount int 31 | HTTPPostCount int 32 | 33 | AvgTime float32 34 | AvgHTMLTime float32 35 | 36 | HTMLCount int 37 | CSSCount int 38 | JavasriptCount int 39 | ImageCount int 40 | OtherCount int 41 | // json etc.? 42 | 43 | HTTPCodeScore int 44 | MimeTypeScore int 45 | TimeScore int 46 | PassSeqScore int 47 | Score int 48 | 49 | PassSeq []byte 50 | 51 | // Created at 52 | // FirstRequestAt Time 53 | // LastRequestAt Time 54 | } 55 | 56 | // AddRequest adds new requests information to its client 57 | func (c *AnddosClient) AddRequest(HTTPCode int, HTTPMethod string, MimeType string, Time float32, Path string) { 58 | c.RequestsCount++ 59 | 60 | // NotModCount 61 | if HTTPCode == 304 { 62 | c.NotModCount++ 63 | } 64 | 65 | // HTTPCode 66 | if HTTPCode < 200 { 67 | c.HTTP1Count++ 68 | } else if HTTPCode < 300 { 69 | c.HTTP2Count++ 70 | } else if HTTPCode < 400 { 71 | c.HTTP3Count++ 72 | } else if HTTPCode < 500 { 73 | c.HTTP4Count++ 74 | } else { 75 | c.HTTP5Count++ 76 | } 77 | 78 | // HTTPMethod 79 | switch HTTPMethod { 80 | case "GET": 81 | c.HTTPGetCount++ 82 | case "POST": 83 | c.HTTPPostCount++ 84 | } 85 | 86 | // MimeType TODO 87 | if strings.Contains(MimeType, "html") { 88 | c.HTMLCount++ 89 | } else if strings.Contains(MimeType, "image") { 90 | c.ImageCount++ 91 | } else if strings.Contains(MimeType, "javascript") { 92 | c.JavasriptCount++ 93 | } else if strings.Contains(MimeType, "css") { 94 | c.CSSCount++ 95 | } else { 96 | c.OtherCount++ 97 | } 98 | 99 | // Time 100 | c.AvgTime = (c.AvgTime*float32(c.RequestsCount-1) + Time) / float32(c.RequestsCount) 101 | 102 | // PassSeq 103 | // c.PassSeq[length(c.PassSeq)] = 104 | } 105 | -------------------------------------------------------------------------------- /anddos/test_ngx_http_anddos.c: -------------------------------------------------------------------------------- 1 | /* 2 | * File: test_ngx_http_anddos.c 3 | * Author: aufi 4 | * 5 | * Created on 20. únor 2012, 18:34 6 | * / 7 | 8 | #include 9 | #include 10 | #define HASHKEYLEN 50 11 | 12 | #define ngx_hash(key, c) 13 | 14 | 15 | unsigned int 16 | ngx_hash_key(char *data, size_t len) 17 | { 18 | unsigned int i, key; 19 | 20 | key = 0; 21 | 22 | for (i = 0; i < len; i++) { 23 | key = ((unsigned int) key * 31 + data[i]); 24 | } 25 | 26 | return key; 27 | } 28 | 29 | typedef struct { 30 | int set; //bool 31 | int score; 32 | //ngx_str_t browser; 33 | int request_count; 34 | int notmod_count; 35 | //float avg_time; 36 | //ngx_uint_t avg_time_count; 37 | char key[6]; 38 | } ngx_http_anddos_client_t; 39 | 40 | typedef struct { 41 | int request_count; 42 | int notmod_count; 43 | //float avg_time; 44 | } ngx_http_anddos_state_t; 45 | 46 | 47 | 48 | 49 | //data init 50 | ngx_http_anddos_client_t *ngx_http_anddos_clients; 51 | ngx_http_anddos_state_t ngx_http_anddos_state; 52 | * 53 | * 54 | * 55 | * 56 | /* 57 | * 58 | */ 59 | inline float 60 | ngx_http_count_fdiff(float global, float client) { 61 | //what about attack by many very fast and not "heavy" reqs ..no reason to do that, but better block both extrems 62 | 63 | //global = 100 * global + 1; 64 | //client = 100 * client + 1; 65 | 66 | if (global == 0) return 0; 67 | 68 | if (client > global) 69 | return (client - global) / global; //or non-linear math function => exp 70 | else 71 | return (global - client) / global; 72 | } 73 | 74 | 75 | inline unsigned int 76 | ngx_http_count_score_httpcode(unsigned int cnt, unsigned int c1, unsigned int c2, unsigned int c3, unsigned int c4, unsigned int c5) { 77 | 78 | //cnt = 50 79 | 80 | float w1 = ngx_http_count_fdiff((float)0 / 70, (float)c1 / cnt); //0 81 | float w2 = ngx_http_count_fdiff((float)30 / 70, (float)c2 / cnt); //20 82 | float w3 = ngx_http_count_fdiff((float)36 / 70, (float)c3 / cnt); //28 83 | float w4 = ngx_http_count_fdiff((float)3 / 70, (float)c4 / cnt); //2 84 | float w5 = ngx_http_count_fdiff((float)1 / 70, (float)c5 / cnt); //0 85 | 86 | //or weighted sum ? 87 | 88 | printf("%f %f %f %f %f\n", w1, w2, w3, w4, w5); 89 | 90 | return (int) (100 * (w1 + w2 + w3 + w4 + w5)); 91 | } 92 | 93 | 94 | 95 | int main(int argc, char** argv) { 96 | 97 | /* 98 | //client stats update 99 | char text_key[HASHKEYLEN]; 100 | //host IP 101 | strncpy(text_key, "asdasd", strlen("asdasd")); 102 | //user_agent header 103 | if (0) { 104 | int ua_max_len; 105 | if (HASHKEYLEN >= r->headers_in.user_agent->value.len + r->connection->addr_text.len) { 106 | ua_max_len = r->headers_in.user_agent->value.len; 107 | } else { 108 | ua_max_len = HASHKEYLEN - r->connection->addr_text.len; 109 | } 110 | strncpy(text_key, r->headers_in.user_agent->value.data, ua_max_len); 111 | } 112 | 113 | */ 114 | 115 | printf("nazdar\n"); 116 | /* 117 | ngx_http_anddos_clients = (ngx_http_anddos_client_t *) calloc(1000, sizeof(ngx_http_anddos_client_t)); 118 | 119 | char * t = "asdasd"; 120 | char * tu = "asd"; 121 | 122 | printf("hash key: %u\n", ngx_hash_key(t, sizeof(t))); 123 | 124 | printf("hash key charc: %u\n", ngx_hash_key(t, sizeof(t)/sizeof(char))); 125 | 126 | printf("hash key u: %u\n", ngx_hash_key(tu, sizeof(tu))); 127 | 128 | printf("hash key charc u: %u\n", ngx_hash_key(tu, sizeof(tu)/sizeof(char))); 129 | 130 | printf("ngx_http_anddos_client_t: %lu\n", sizeof(ngx_http_anddos_client_t)); 131 | 132 | printf("ngx_http_anddos_clients: %lu\n", sizeof(ngx_http_anddos_clients)); 133 | 134 | printf("ngx_http_anddos_clients[0].key: %lu\n", sizeof(ngx_http_anddos_clients[0].key)); 135 | 136 | printf("ngx_http_anddos_clients[0].key[0]: %lu\n", sizeof(ngx_http_anddos_clients[0].key[0])); 137 | 138 | printf("ngx_http_anddos_state: %lu\n", sizeof(ngx_http_anddos_state)); 139 | 140 | printf("ngx_http_anddos_state_t: %lu\n", sizeof(ngx_http_anddos_state_t)); 141 | 142 | printf("ngx_http_anddos_state.request_count: %lu\n", sizeof(ngx_http_anddos_state.request_count)); 143 | 144 | printf("*ngx_http_anddos_state.request_count: %lu\n", sizeof(&ngx_http_anddos_state.request_count)); 145 | */ 146 | 147 | float x = ngx_http_count_score_httpcode(50, 0, 20, 28, 2, 0); 148 | 149 | printf("%f\n", x); 150 | 151 | 152 | float test0 = ngx_http_count_fdiff(0, 0); 153 | printf("rozdil nul %f\n", test0); 154 | 155 | return (0); 156 | } 157 | 158 | -------------------------------------------------------------------------------- /anddos/ngx_http_anddos_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * HTTP anti-ddos nginx module 3 | * 4 | * Marek Aufart, aufi.cz@gmail.com 5 | * 6 | * Visit project page: https://github.com/aufi/anddos 7 | * 8 | * license: GNU GPL v3 9 | * 10 | * resources: http://wiki.nginx.org/3rdPartyModules, http://www.evanmiller.org/nginx-modules-guide.html, http://blog.zhuzhaoyuan.com/2009/08/creating-a-hello-world-nginx-module/ 11 | * 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | //#include "modules/ngx_http_proxy_module.c" 18 | 19 | #define COOKIENAME "anddos_key" 20 | #define HASHKEYLEN 150 21 | #define HASHTABLESIZE 10240 //100k in production 22 | #define STATE_FILE "/tmp/anddos_state" 23 | #define SEQSIZE 32 24 | #define INITTHRESHOLD 9999 25 | #define DEBUG 1 26 | 27 | static u_char ngx_anddos_fail_string[] = "Blocked!

You have been blocked by ANDDOS!

"; 28 | 29 | //function declarations 30 | static ngx_int_t ngx_http_anddos_request_handler(ngx_http_request_t *r); 31 | static char * ngx_http_anddos(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 32 | 33 | static ngx_int_t ngx_http_anddos_learn_filter(ngx_http_request_t *r); 34 | static ngx_int_t ngx_http_anddos_filter_init(ngx_conf_t *cf); 35 | 36 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 37 | 38 | ngx_int_t set_custom_header_in_headers_out(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value); 39 | unsigned int ngx_http_anddos_get_client_index(ngx_http_request_t *r); 40 | 41 | void ngx_http_anddos_get_client_text(u_char * text_key, ngx_http_request_t *r); 42 | //static char * ngx_http_anddos_rnd_text(); 43 | //static int ngx_http_anddos_hash_get_adr(char * t); //or ngx_hash_key(u_char *data, size_t len) 44 | 45 | //data store 46 | //struct ngx_http_anddos_client; 47 | //struct ngx_http_anddos_state; 48 | 49 | //anddos internal functions 50 | //static char * ngx_http_anddos_get_state(); 51 | //static char * ngx_http_anddos_get_client(); 52 | 53 | //datatypes 54 | static ngx_command_t ngx_http_anddos_commands[] = { 55 | { ngx_string("anddos"), 56 | NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, 57 | ngx_http_anddos, 58 | 0, 59 | 0, 60 | NULL}, 61 | 62 | ngx_null_command 63 | }; 64 | 65 | static ngx_http_module_t ngx_http_anddos_module_ctx = { 66 | NULL, /* preconfiguration */ 67 | ngx_http_anddos_filter_init, /* postconfiguration */ 68 | NULL, /* create main configuration */ 69 | NULL, /* init main configuration */ 70 | NULL, /* create server configuration */ 71 | NULL, /* merge server configuration */ 72 | NULL, /* create location configuration */ 73 | NULL /* merge location configuration */ 74 | }; 75 | 76 | ngx_module_t ngx_http_anddos_module = { 77 | NGX_MODULE_V1, 78 | &ngx_http_anddos_module_ctx, /* module context */ 79 | ngx_http_anddos_commands, /* module directives */ 80 | NGX_HTTP_MODULE, /* module type */ 81 | NULL, /* init master */ 82 | NULL, /* init module */ 83 | NULL, /* init process */ 84 | NULL, /* init thread */ 85 | NULL, /* exit thread */ 86 | NULL, /* exit process */ 87 | NULL, /* exit master */ 88 | NGX_MODULE_V1_PADDING 89 | }; 90 | 91 | typedef struct { //FIX keep IP somewhere for blocking all clients from the IP 92 | unsigned char set; //->bool 93 | 94 | unsigned int request_count; //ensure that int overflow will not occur errors 95 | unsigned int notmod_count; 96 | unsigned int http1_count; 97 | unsigned int http2_count; 98 | unsigned int http3_count; 99 | unsigned int http4_count; 100 | unsigned int http5_count; 101 | unsigned int avg_time; //rounding is OK 102 | unsigned int html_avg_time; 103 | unsigned int key; 104 | 105 | //mimetypes count 106 | unsigned int html_count; 107 | unsigned int css_count; //or just text? 108 | unsigned int javascript_count; 109 | unsigned int image_count; 110 | unsigned int other_count; //FIX necesarry? 111 | 112 | //scores 113 | unsigned int httpcode_score; 114 | unsigned int mimetype_score; 115 | unsigned int time_score; 116 | unsigned int passeq_score; 117 | 118 | u_char pass_seq[SEQSIZE]; 119 | u_char ua[HASHKEYLEN]; 120 | 121 | } ngx_http_anddos_client_t; 122 | 123 | typedef struct { 124 | unsigned char level; //(0)Normal, (10)Attack, (100)Overload; not used yet 125 | 126 | unsigned int threshold; 127 | 128 | unsigned int request_count; 129 | unsigned int notmod_count; 130 | unsigned int http1_count; 131 | unsigned int http2_count; 132 | unsigned int http3_count; 133 | unsigned int http4_count; 134 | unsigned int http5_count; 135 | unsigned int client_count; 136 | unsigned int avg_time; 137 | unsigned int html_avg_time; 138 | 139 | //mimetypes count 140 | unsigned int html_count; 141 | unsigned int css_count; 142 | unsigned int javascript_count; 143 | unsigned int image_count; 144 | unsigned int other_count; 145 | 146 | } ngx_http_anddos_state_t; 147 | 148 | 149 | //data init 150 | ngx_http_anddos_client_t ngx_http_anddos_clients[HASHTABLESIZE]; 151 | ngx_http_anddos_state_t ngx_http_anddos_state; 152 | 153 | 154 | //http://wiki.nginx.org/HeadersManagement 155 | 156 | ngx_int_t 157 | set_custom_header_in_headers_out(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value) { 158 | ngx_table_elt_t *h; 159 | 160 | h = ngx_list_push(&r->headers_out.headers); 161 | if (h == NULL) { 162 | return NGX_ERROR; 163 | } 164 | h->key = *key; 165 | h->value = *value; 166 | h->hash = 1; 167 | 168 | return NGX_OK; 169 | } 170 | /* 171 | static ngx_int_t 172 | ngx_http_anddos_request_handler_create(ngx_http_request_t *r) { 173 | 174 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "anddos processing request CREATE"); 175 | 176 | return NGX_OK; 177 | }*/ 178 | 179 | static ngx_int_t 180 | ngx_http_anddos_request_handler(ngx_http_request_t *r) { 181 | 182 | //disabled while using proxy_pass directive, due to nginx architecture 183 | //this handler can block requests only for static content on local server 184 | //FIX spread blocking to all requests 185 | if (DEBUG) ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "ANDDOS processing request"); 186 | 187 | ngx_int_t rc; 188 | ngx_buf_t *b; 189 | ngx_chain_t out; 190 | 191 | //DECIDE whether is request bot or not 192 | //KISS, one condition, rest logic is moved to the learn_filter 193 | 194 | u_char text_key[HASHKEYLEN]; 195 | memset(text_key, 0, HASHKEYLEN); 196 | ngx_http_anddos_get_client_text(text_key, r); 197 | unsigned int key = ngx_hash_key(text_key, ngx_strlen(text_key)) % HASHTABLESIZE; 198 | 199 | if (1 || (int) ngx_http_anddos_clients[key].set < 2) { // 1 || -> only monitor 200 | 201 | return NGX_DECLINED; // 0,1 OK; 2,3,.. BLOCK 202 | 203 | } 204 | 205 | rc = ngx_http_discard_request_body(r); 206 | if (rc != NGX_OK) { 207 | return rc; 208 | } 209 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "ANDDOS: request blocked"); 210 | //FIX necessary ? 211 | /* set the 'Content-type' header */ 212 | r->headers_out.content_type_len = sizeof ("text/html") - 1; 213 | r->headers_out.content_type.data = (u_char *) "text/html"; 214 | 215 | //FIX remove or keep? 216 | /* send the header only, if the request type is http 'HEAD' */ 217 | if (r->method == NGX_HTTP_HEAD) { 218 | r->headers_out.status = NGX_HTTP_OK; 219 | r->headers_out.content_length_n = sizeof (ngx_anddos_fail_string) - 1; 220 | return ngx_http_send_header(r); 221 | } 222 | 223 | /* allocate a buffer for your response body */ 224 | b = ngx_pcalloc(r->pool, sizeof (ngx_buf_t)); 225 | if (b == NULL) { 226 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 227 | } 228 | /* attach this buffer to the buffer chain */ 229 | out.buf = b; 230 | out.next = NULL; 231 | /* adjust the pointers of the buffer */ 232 | b->pos = ngx_anddos_fail_string; 233 | b->last = ngx_anddos_fail_string + sizeof (ngx_anddos_fail_string) - 1; 234 | b->memory = 1; /* this buffer is in memory */ 235 | b->last_buf = 1; /* this is the last buffer in the buffer chain */ 236 | /* set the status line */ 237 | r->headers_out.status = NGX_HTTP_PRECONDITION_FAILED; //for dev, FIX to NGX_HTTP_SERVICE_UNAVAILABLE in production 238 | r->headers_out.content_length_n = sizeof (ngx_anddos_fail_string) - 1; 239 | 240 | /* send the headers of your response */ 241 | rc = ngx_http_send_header(r); 242 | if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { 243 | return rc; 244 | } 245 | 246 | //FIX improve fail responses 247 | ngx_http_complex_value_t cv; 248 | ngx_memzero(&cv, sizeof (ngx_http_complex_value_t)); 249 | cv.value.len = sizeof (ngx_anddos_fail_string) - 1; 250 | cv.value.data = ngx_anddos_fail_string; 251 | 252 | //FIX ensure which return is faster 253 | //return ngx_http_output_filter(r, &out); 254 | return ngx_http_send_response(r, r->headers_out.status, (ngx_str_t *) r->headers_out.content_type.data, &cv); 255 | 256 | } 257 | 258 | void 259 | ngx_http_anddos_get_client_text(u_char * text_key, ngx_http_request_t *r) { 260 | 261 | //FIX IP or UA header can be longer than HASHKEYLEN 262 | //only IP is used, np 263 | 264 | if (0) { //(r->headers_in.user_agent) { 265 | //user_agent HEADER 266 | u_char header_ua[HASHKEYLEN]; 267 | memset(header_ua, 0, HASHKEYLEN); 268 | u_char header_ip[HASHKEYLEN]; 269 | memset(header_ip, 0, HASHKEYLEN); 270 | ngx_snprintf(header_ip, (int) r->connection->addr_text.len, "%s", r->connection->addr_text.data); 271 | ngx_snprintf(header_ua, (int) r->headers_in.user_agent->value.len, "%s", r->headers_in.user_agent->value.data); 272 | ngx_snprintf(text_key, HASHKEYLEN, "%s%s", header_ip, header_ua); 273 | 274 | } else { 275 | //host IP 276 | ngx_snprintf(text_key, (int) r->connection->addr_text.len, "%s", r->connection->addr_text.data); 277 | } 278 | 279 | //ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "ANDDOS HASH TEXT KEY: %s, LEN: %d", text_key, ngx_strlen(text_key)); 280 | 281 | } 282 | 283 | void 284 | ngx_http_anddos_clients_stats(ngx_http_request_t *r) { 285 | 286 | int i; 287 | 288 | //log 289 | if (DEBUG) { 290 | for (i = 0; i < HASHTABLESIZE; i++) { 291 | if (ngx_http_anddos_clients[i].set > 0) { 292 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "ANDDOS client[%d]: request_count: %d; http200_count: %d, key: %d, avg_time: %d, pass_seq: %s", i, ngx_http_anddos_clients[i].request_count, ngx_http_anddos_clients[i].http2_count, ngx_http_anddos_clients[i].key, ngx_http_anddos_clients[i].avg_time, (char *) ngx_http_anddos_clients[i].pass_seq); 293 | } 294 | } 295 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "ANDDOS state[%d]: request_count: %d; http200_count: %d, client_count: %d, avg_time: %d", ngx_http_anddos_state.level, ngx_http_anddos_state.request_count, ngx_http_anddos_state.http2_count, ngx_http_anddos_state.client_count, ngx_http_anddos_state.avg_time); 296 | //ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "ANDDOS mimetypes: html: %d; css: %d, js: %d, images: %d, other: %d", ngx_http_anddos_state.html_count, ngx_http_anddos_state.css_count, ngx_http_anddos_state.javascript_count, ngx_http_anddos_state.image_count, ngx_http_anddos_state.other_count); 297 | } 298 | 299 | //DEV logging anddos state to file (after 1/100reqs) 300 | if ((ngx_http_anddos_state.request_count % 100) != 2) return; 301 | 302 | //else stats to file 303 | FILE *f; 304 | if (!(f = freopen(STATE_FILE, "w", stdout))) { 305 | ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "ANDDOS error: save to file failed"); 306 | } else { 307 | printf("ANDDOS state\nlevel threshold clients reqs 304cnt http1cnt http2cnt http3cnt http4cnt http5cnt avgtime htmlavgtime html css javascript image other\n"); 308 | printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", 309 | ngx_http_anddos_state.level, ngx_http_anddos_state.threshold, ngx_http_anddos_state.client_count, ngx_http_anddos_state.request_count, ngx_http_anddos_state.notmod_count, 310 | ngx_http_anddos_state.http1_count, ngx_http_anddos_state.http2_count, ngx_http_anddos_state.http3_count, ngx_http_anddos_state.http4_count, ngx_http_anddos_state.http5_count, 311 | ngx_http_anddos_state.avg_time, ngx_http_anddos_state.html_avg_time, ngx_http_anddos_state.html_count, ngx_http_anddos_state.css_count, ngx_http_anddos_state.javascript_count, ngx_http_anddos_state.image_count, ngx_http_anddos_state.other_count); 312 | 313 | printf("ANDDOS clients\nset index httpscore mimescore timescore seqscore reqs 304_cnt http1cnt http2cnt http3cnt http4cnt http5cnt avgtime htmlavgtime html css javascript images other pass_seq ip_ua\n"); 314 | for (i = 0; i < HASHTABLESIZE; i++) { 315 | if ((int) ngx_http_anddos_clients[i].set > 0) { 316 | printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%s\t%s\n", 317 | (int) ngx_http_anddos_clients[i].set, i, ngx_http_anddos_clients[i].httpcode_score, ngx_http_anddos_clients[i].mimetype_score, ngx_http_anddos_clients[i].time_score, ngx_http_anddos_clients[i].passeq_score, 318 | ngx_http_anddos_clients[i].request_count, ngx_http_anddos_clients[i].notmod_count, ngx_http_anddos_clients[i].http1_count, ngx_http_anddos_clients[i].http2_count, ngx_http_anddos_clients[i].http3_count, ngx_http_anddos_clients[i].http4_count, ngx_http_anddos_clients[i].http5_count, 319 | ngx_http_anddos_clients[i].avg_time, ngx_http_anddos_clients[i].html_avg_time, ngx_http_anddos_clients[i].html_count, ngx_http_anddos_clients[i].css_count, ngx_http_anddos_clients[i].javascript_count, ngx_http_anddos_clients[i].javascript_count, ngx_http_anddos_clients[i].other_count, 320 | (char *) ngx_http_anddos_clients[i].pass_seq, (char *) ngx_http_anddos_clients[i].ua); 321 | } 322 | } 323 | fclose(f); 324 | } 325 | } 326 | 327 | void 328 | ngx_http_anddos_set_cookie(ngx_http_request_t *r, int key) { 329 | 330 | u_char cookie_value_str[50]; 331 | memset(cookie_value_str, 0, 50); 332 | 333 | //assemble cookie text 334 | ngx_snprintf(cookie_value_str, 50, "%s=%d", COOKIENAME, key); 335 | ngx_str_t cookie_name = ngx_string("Set-Cookie"); 336 | ngx_str_t cookie_value = ngx_string(cookie_value_str); 337 | 338 | //set a cookie 339 | ngx_int_t hr = set_custom_header_in_headers_out(r, &cookie_name, &cookie_value); 340 | if (hr != NGX_OK) { 341 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ANDDOS client error: failed to set a cookie"); 342 | } 343 | } 344 | 345 | int 346 | ngx_http_anddos_get_msec(ngx_http_request_t *r) { 347 | //inspired by logmodule msec function 348 | int ms; 349 | ngx_time_t *tp; 350 | 351 | tp = ngx_timeofday(); 352 | 353 | ms = ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec)); 354 | ms = ngx_max(ms, 0); 355 | 356 | return ms; 357 | } 358 | 359 | inline void 360 | ngx_http_anddos_set_mimetype_stats(ngx_http_request_t *r, int key, int request_time) { 361 | 362 | if ((int) r->headers_out.status >= 300 || (int) r->headers_out.status < 200) { //exclude no success (<>200) responses 363 | return; 364 | } 365 | 366 | int cnt = 0; 367 | u_char mime_type[32]; 368 | memset(mime_type, 0, 32); 369 | ngx_snprintf(mime_type, r->headers_out.content_type.len, "%s", r->headers_out.content_type.data); 370 | 371 | if (DEBUG) ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ANDDOS mime: %s", mime_type); 372 | 373 | 374 | 375 | if (ngx_strstr(mime_type, "html") != NULL) { 376 | ngx_http_anddos_clients[key].html_count += 1; 377 | ngx_http_anddos_state.html_count += 1; 378 | 379 | ngx_http_anddos_state.html_avg_time = (ngx_http_anddos_state.html_avg_time * (ngx_http_anddos_state.html_count - 1) + request_time ) / ngx_http_anddos_state.html_count; 380 | ngx_http_anddos_clients[key].html_avg_time = (ngx_http_anddos_clients[key].html_avg_time * (ngx_http_anddos_clients[key].html_count - 1) + request_time ) / ngx_http_anddos_clients[key].html_count; 381 | 382 | 383 | } else { 384 | //not count all request_count, but only non 304 and non html 385 | //ngx_http_anddos_state.avg_time = ngx_http_anddos_state.avg_time * (ngx_http_anddos_state.request_count - ngx_http_anddos_state.notmod_count - 1) / (ngx_http_anddos_state.request_count - ngx_http_anddos_state.notmod_count) + request_time / (ngx_http_anddos_state.request_count - ngx_http_anddos_state.notmod_count); 386 | //ngx_http_anddos_clients[key].avg_time = ngx_http_anddos_clients[key].avg_time * (ngx_http_anddos_clients[key].request_count - 1) / ngx_http_anddos_clients[key].request_count + request_time / ngx_http_anddos_clients[key].request_count; 387 | //is better to risk overflow or rounding ? :) 388 | cnt = ngx_http_anddos_state.http2_count - ngx_http_anddos_state.html_count; 389 | if (cnt == 0) { 390 | ngx_http_anddos_state.avg_time = 0; 391 | } else { 392 | ngx_http_anddos_state.avg_time = (ngx_http_anddos_state.avg_time * (cnt - 1) + request_time) / cnt; 393 | } 394 | cnt = ngx_http_anddos_clients[key].http2_count - ngx_http_anddos_clients[key].html_count; 395 | if (cnt == 0) { 396 | ngx_http_anddos_clients[key].avg_time = 0; 397 | } else { 398 | ngx_http_anddos_clients[key].avg_time = (ngx_http_anddos_clients[key].avg_time * (cnt - 1) + request_time ) / cnt; 399 | } 400 | 401 | //what about browser's cache, maybe understand to http headers ? 402 | 403 | if (ngx_strstr(mime_type, "image") != NULL) { 404 | ngx_http_anddos_clients[key].image_count += 1; 405 | ngx_http_anddos_state.image_count += 1; 406 | } else 407 | if (ngx_strstr(mime_type, "javascript") != NULL) { 408 | ngx_http_anddos_clients[key].javascript_count += 1; 409 | ngx_http_anddos_state.javascript_count += 1; 410 | } else 411 | if (ngx_strstr(mime_type, "css") != NULL) { 412 | ngx_http_anddos_clients[key].css_count += 1; 413 | ngx_http_anddos_state.css_count += 1; 414 | } else { 415 | ngx_http_anddos_clients[key].other_count += 1; 416 | ngx_http_anddos_state.other_count += 1; 417 | } 418 | } 419 | } 420 | 421 | inline void 422 | ngx_http_anddos_set_httpcode_stats(ngx_http_request_t *r, int key) { 423 | 424 | //FIX 3xx or keep 304 as a special code, which proofs that client has a local cache? 2012-03-28 keep! 425 | 426 | int code = (int) r->headers_out.status; 427 | 428 | if (code == 304) { 429 | ngx_http_anddos_clients[key].notmod_count += 1; 430 | ngx_http_anddos_state.notmod_count += 1; 431 | } 432 | 433 | if (code < 200) { 434 | ngx_http_anddos_clients[key].http1_count += 1; 435 | ngx_http_anddos_state.http1_count += 1; 436 | } else 437 | if (code < 300) { //we keep all 3xx (incl.304) 438 | ngx_http_anddos_clients[key].http2_count += 1; 439 | ngx_http_anddos_state.http2_count += 1; 440 | } else 441 | if (code < 400) { 442 | ngx_http_anddos_clients[key].http3_count += 1; 443 | ngx_http_anddos_state.http3_count += 1; 444 | } else 445 | if (code < 500) { 446 | ngx_http_anddos_clients[key].http4_count += 1; 447 | ngx_http_anddos_state.http4_count += 1; 448 | } else { 449 | ngx_http_anddos_clients[key].http5_count += 1; 450 | ngx_http_anddos_state.http5_count += 1; 451 | } 452 | 453 | } 454 | 455 | inline float 456 | ngx_http_anddos_count_fdiff(float global, float client) { 457 | //what about attack by many very fast and not "heavy" reqs ..no reason to do that, but better block both extrems 458 | 459 | if (global == 0) return 0; 460 | 461 | if (client > global) 462 | return (client - global) / global; //or non-linear math function => exp ? 463 | else 464 | return (global - client) / global; 465 | } 466 | 467 | inline unsigned int 468 | ngx_http_anddos_count_diff(unsigned int global, unsigned int client) { 469 | 470 | //if (global == 0) return 0; 471 | //return abs(client - global) / global; //or non-linear math function - log/exp ? 472 | 473 | return (int) 100 * ngx_http_anddos_count_fdiff((float) global, (float) client); 474 | } 475 | 476 | void 477 | ngx_http_anddos_undo_stats(int key) { 478 | //FIX before production deployment 479 | } 480 | 481 | int 482 | ngx_http_anddos_decide(ngx_http_request_t *r, int key) { 483 | //make a decision 484 | //if client's param differs to global param by more than threshold, block 485 | //threshold depends on reqs count, global state, statistic function 486 | //scores are kept from time, when last request was served (->first client) 487 | 488 | int dec; 489 | dec = 1; 490 | 491 | unsigned int score = ngx_http_anddos_clients[key].httpcode_score + ngx_http_anddos_clients[key].mimetype_score + ngx_http_anddos_clients[key].time_score; 492 | 493 | if (score > ngx_http_anddos_state.threshold && ngx_http_anddos_clients[key].request_count > 5) dec = 2; 494 | 495 | //when block some client compensate global stats by opposite values of his params 496 | if (ngx_http_anddos_clients[key].set == 1 && dec == 2) { 497 | ngx_http_anddos_undo_stats(key); 498 | } 499 | 500 | return dec; 501 | } 502 | 503 | 504 | inline unsigned int 505 | ngx_http_anddos_count_score_time(unsigned int c_avg, unsigned int c_html) { 506 | return (int) ((ngx_http_anddos_count_diff(ngx_http_anddos_state.avg_time, c_avg) + ngx_http_anddos_count_diff(ngx_http_anddos_state.html_avg_time, c_html)) / 2 ); 507 | } 508 | 509 | inline unsigned int 510 | ngx_http_anddos_count_score_mimetype(unsigned int cnt, unsigned int html, unsigned int css, unsigned int javascript, unsigned int image, unsigned int other) { 511 | 512 | float w1 = ngx_http_anddos_count_fdiff((float)ngx_http_anddos_state.html_count / ngx_http_anddos_state.request_count, (float)html / cnt); 513 | float w2 = ngx_http_anddos_count_fdiff((float)ngx_http_anddos_state.css_count / ngx_http_anddos_state.request_count, (float)css / cnt); 514 | float w3 = ngx_http_anddos_count_fdiff((float)ngx_http_anddos_state.javascript_count / ngx_http_anddos_state.request_count, (float)javascript / cnt); 515 | float w4 = ngx_http_anddos_count_fdiff((float)ngx_http_anddos_state.image_count / ngx_http_anddos_state.request_count, (float)image / cnt); 516 | float w5 = ngx_http_anddos_count_fdiff((float)ngx_http_anddos_state.other_count / ngx_http_anddos_state.request_count, (float)other / cnt); 517 | 518 | //or weighted sum ? 519 | //ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ANDDOS score[%d]: %d %d %d %d %d", w1, w2, w3, w4, w5); 520 | 521 | return (int) (100 * (w1 + w2 + w3 + w4 + w5)); //lost precision? 522 | } 523 | 524 | inline unsigned int 525 | ngx_http_anddos_count_score_httpcode(unsigned int cnt, unsigned int c1, unsigned int c2, unsigned int c3, unsigned int c4, unsigned int c5) { 526 | 527 | float w1 = ngx_http_anddos_count_fdiff((float)ngx_http_anddos_state.http1_count / ngx_http_anddos_state.request_count, (float)c1 / cnt); 528 | float w2 = ngx_http_anddos_count_fdiff((float)ngx_http_anddos_state.http2_count / ngx_http_anddos_state.request_count, (float)c2 / cnt); 529 | float w3 = ngx_http_anddos_count_fdiff((float)ngx_http_anddos_state.http3_count / ngx_http_anddos_state.request_count, (float)c3 / cnt); 530 | float w4 = ngx_http_anddos_count_fdiff((float)ngx_http_anddos_state.http4_count / ngx_http_anddos_state.request_count, (float)c4 / cnt); 531 | float w5 = ngx_http_anddos_count_fdiff((float)ngx_http_anddos_state.http5_count / ngx_http_anddos_state.request_count, (float)c5 / cnt); 532 | 533 | //or weighted sum ? 534 | //ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ANDDOS score[%d]: %d %d %d %d %d", w1, w2, w3, w4, w5); 535 | 536 | return (int) (100 * (w1 + w2 + w3 + w4 + w5)); //lost precision? 537 | } 538 | 539 | void 540 | ngx_http_anddos_count_scores(ngx_http_request_t *r, int key) { 541 | 542 | //httpcode 543 | ngx_http_anddos_clients[key].httpcode_score = ngx_http_anddos_count_score_httpcode( 544 | ngx_http_anddos_clients[key].request_count, ngx_http_anddos_clients[key].http1_count, ngx_http_anddos_clients[key].http2_count, 545 | ngx_http_anddos_clients[key].http3_count, ngx_http_anddos_clients[key].http4_count, ngx_http_anddos_clients[key].http5_count 546 | ); 547 | 548 | //mimetype 549 | ngx_http_anddos_clients[key].mimetype_score = ngx_http_anddos_count_score_mimetype( 550 | ngx_http_anddos_clients[key].request_count, ngx_http_anddos_clients[key].html_count, ngx_http_anddos_clients[key].css_count, 551 | ngx_http_anddos_clients[key].javascript_count, ngx_http_anddos_clients[key].image_count, ngx_http_anddos_clients[key].other_count 552 | ); 553 | 554 | //time 555 | ngx_http_anddos_clients[key].time_score = ngx_http_anddos_count_score_time( ngx_http_anddos_clients[key].avg_time, ngx_http_anddos_clients[key].html_avg_time ); 556 | 557 | //passeq 558 | //count of unique paths, what globally?? 559 | } 560 | 561 | inline int 562 | ngx_http_anddos_count_threshold() { 563 | 564 | if (ngx_http_anddos_state.request_count < 37 || ngx_http_anddos_state.client_count < 5) return INITTHRESHOLD; 565 | 566 | int i, min, max, clients; 567 | float avg; 568 | min = INITTHRESHOLD; 569 | max = 0; 570 | avg = 0; 571 | clients = 0; 572 | 573 | for (i = 0; i < HASHTABLESIZE; i++) { 574 | 575 | if ((int) ngx_http_anddos_clients[i].set == 1 && (int) ngx_http_anddos_clients[i].request_count > 1) { 576 | clients += 1; 577 | int score = ngx_http_anddos_clients[i].httpcode_score + ngx_http_anddos_clients[i].mimetype_score + ngx_http_anddos_clients[i].time_score; 578 | if (score < min) min = score; 579 | if (score > max) max = score; 580 | avg = (avg * (clients - 1) + score) / clients; 581 | } 582 | } 583 | //FIX maybe naive? 584 | //FIX2 also global state (normal/attack) can be concerned 585 | return 150 + avg; // 2x is too much, 100+ seems to be ok, update: 150 + avg seems to be the best (measures) 586 | 587 | } 588 | 589 | static ngx_int_t 590 | ngx_http_anddos_learn_filter(ngx_http_request_t *r) { 591 | 592 | //the client data 593 | u_char text_key[HASHKEYLEN]; 594 | memset(text_key, 0, HASHKEYLEN); 595 | ngx_http_anddos_get_client_text(text_key, r); 596 | unsigned int key = ngx_hash_key(text_key, ngx_strlen(text_key)) % HASHTABLESIZE; 597 | int request_time = ngx_http_anddos_get_msec(r); 598 | 599 | //server stats update 600 | ngx_http_anddos_state.request_count += 1; 601 | 602 | //r->headers_in.cookies.elts //FIX find clients cookie 603 | //FIX compare cookie content 604 | //first req let pass (in normal conditions) 605 | 606 | 607 | if ((int) ngx_http_anddos_clients[key].set == 0) { 608 | //generate cookie key ---------disabled--------- 609 | //int client_key = rand(); //FIX predictable 610 | //send a cookie key 611 | //ngx_http_anddos_set_cookie(r, client_key); 612 | 613 | //setup in client hashtable 614 | ngx_http_anddos_clients[key].set = 1; 615 | ngx_http_anddos_clients[key].request_count = 1; 616 | //ngx_http_anddos_clients[key].key = client_key; 617 | ngx_http_anddos_get_client_text(ngx_http_anddos_clients[key].ua, r); 618 | ngx_http_anddos_clients[key].pass_seq[0] = (u_char) (ngx_hash_key(r->uri.data, r->uri.len) % 94 + 33); //printable chars from ascii //circ.register will differ same sequentions (longer than SEQSTEPS) 619 | 620 | if (DEBUG) ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "ANDDOS client[%d]: step id: %c for uri: %s", 621 | key, 622 | (char) ngx_http_anddos_clients[key].pass_seq[0], 623 | (char*) r->uri.data); 624 | 625 | ngx_http_anddos_set_httpcode_stats(r, key); 626 | 627 | ngx_http_anddos_set_mimetype_stats(r, key, request_time); 628 | 629 | ngx_http_anddos_state.client_count += 1; 630 | 631 | } else { 632 | 633 | //dont count to stats already blocked clients 634 | //FIX (should not be here in production, but useful for development and testing purposes) 635 | if (ngx_http_anddos_clients[key].set > 1) return ngx_http_next_header_filter(r); //DEV FIX 636 | 637 | 638 | //web-pass sequence 639 | //ngx_http_anddos_clients[key].pass_seq[ngx_http_anddos_clients[key].request_count % (SEQSIZE - 1)] = (u_char) (ngx_hash_key(r->uri.data, r->uri.len) % 94 + 33); //circ.register will differ same sequentions (longer than SEQSTEPS) 640 | if (ngx_http_anddos_clients[key].request_count < (SEQSIZE - 1)) { //register for first n requested url hashes 641 | ngx_http_anddos_clients[key].pass_seq[ngx_http_anddos_clients[key].request_count] = (u_char) (ngx_hash_key(r->uri.data, r->uri.len) % 94 + 33); 642 | } 643 | 644 | ngx_http_anddos_clients[key].request_count += 1; 645 | 646 | if (DEBUG) ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "ANDDOS client[%d]: step id: %c for uri: %s", 647 | key, 648 | (char) ngx_http_anddos_clients[key].pass_seq[ngx_http_anddos_clients[key].request_count % SEQSIZE], 649 | (char*) r->uri.data); 650 | 651 | ngx_http_anddos_set_httpcode_stats(r, key); 652 | 653 | ngx_http_anddos_set_mimetype_stats(r, key, request_time); 654 | 655 | ngx_http_anddos_count_scores(r, key); 656 | 657 | //DECIDE to BLOCK 658 | //and export blocked IP somewhere? 659 | ngx_http_anddos_clients[key].set = ngx_http_anddos_decide(r, key); 660 | 661 | } 662 | 663 | //if ((ngx_http_anddos_state.request_count % 100) != 37) 664 | ngx_http_anddos_state.threshold = ngx_http_anddos_count_threshold(); //always in dev/test env 665 | 666 | ngx_http_anddos_clients_stats(r); 667 | 668 | return ngx_http_next_header_filter(r); 669 | } 670 | 671 | //initializers 672 | 673 | static char * 674 | ngx_http_anddos(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { 675 | ngx_http_core_loc_conf_t *clcf; 676 | 677 | clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); 678 | clcf->handler = ngx_http_anddos_request_handler; 679 | 680 | return NGX_OK; 681 | } 682 | 683 | static ngx_int_t 684 | ngx_http_anddos_filter_init(ngx_conf_t *cf) { 685 | //FIX handles all requests (incl.blocked)! 686 | ngx_http_next_header_filter = ngx_http_top_header_filter; 687 | ngx_http_top_header_filter = ngx_http_anddos_learn_filter; 688 | 689 | //dont reinit table in case of worker process fail 690 | if ((int) ngx_http_anddos_state.client_count > 0) { 691 | return NGX_OK; 692 | } 693 | 694 | //basic server stats 695 | ngx_http_anddos_state.level = 0; 696 | ngx_http_anddos_state.threshold = INITTHRESHOLD; 697 | ngx_http_anddos_state.client_count = 0; 698 | /* 699 | ngx_http_anddos_state.notmod_count = 0; 700 | ngx_http_anddos_state.request_count = 0; 701 | ngx_http_anddos_state.avg_time = 0; 702 | ngx_http_anddos_state.html_avg_time = 0; 703 | ngx_http_anddos_state.html_count = 0; 704 | ngx_http_anddos_state.css_count = 0; 705 | ngx_http_anddos_state.javascript_count = 0; 706 | ngx_http_anddos_state.image_count = 0; 707 | ngx_http_anddos_state.other_count = 0;*/ 708 | 709 | //clean clients list 710 | int i; 711 | for (i = 0; i < HASHTABLESIZE; i++) { 712 | ngx_http_anddos_clients[i].set = 0; 713 | /* 714 | ngx_http_anddos_clients[i].request_count = 0; 715 | ngx_http_anddos_clients[i].notmod_count = 0; 716 | ngx_http_anddos_clients[i].avg_time = 0; 717 | ngx_http_anddos_clients[i].html_avg_time = 0; 718 | ngx_http_anddos_clients[i].key = 0;*/ 719 | memset(ngx_http_anddos_clients[i].ua, 0, HASHKEYLEN); 720 | memset(ngx_http_anddos_clients[i].pass_seq, 0, SEQSIZE); 721 | /*ngx_http_anddos_clients[i].html_count = 0; 722 | ngx_http_anddos_clients[i].css_count = 0; 723 | ngx_http_anddos_clients[i].javascript_count = 0; 724 | ngx_http_anddos_clients[i].image_count = 0; 725 | ngx_http_anddos_clients[i].other_count = 0;*/ 726 | } 727 | 728 | //dev print hashtable size 729 | printf("ANDDOS hashtable size: %ld B\n", (long int) sizeof (ngx_http_anddos_clients)); 730 | 731 | return NGX_OK; 732 | } 733 | --------------------------------------------------------------------------------