Hi, I have just produced a patch against the upstream HEAD version, to seek a way to fight against DoS attack in openssl itself, the logic is simple, get client's ip address in BIO layer, and send this info to upper SSL layer; In SSL layer, according to the client ip and control policy to do control. And I have just finished the enhancement to use rb-tree as the main struct, the patch is attached,and have took a simple test from 2 machines.
the test script looks like this: #thc-ssl-dosit() { while :; do (while :; do echo R; done) | openssl s_client -ssl3 -connect hostname:4433 2>/dev/null; done } thc-ssl-dosit() { openssl s_client -ssl3 -connect hostname:4433 2>/dev/null;} for x in `seq 1 5000`; do thc-ssl-dosit & done and looks like it works ok,and perform very well. But, still, I can not give real pressure to the rb-tree, actually only 2 different ip address used, and 'openssl s_client' have no option to use a bonding ip address, so, still have no environment to make a real pressure test.Do you have any method to do this?If someone think add a bonding ip address option is a good idea, please response. The struct has been changed to use rb-tree, so, the performance can be estimated. Please response and comment! thanks, Guanjun
diff -Nupr openssl/ssl/s3_srvr.c openssl.1/ssl/s3_srvr.c --- openssl/ssl/s3_srvr.c 2011-09-05 21:36:22.000000000 +0800 +++ openssl.1/ssl/s3_srvr.c 2011-12-09 13:14:56.000000000 +0800 @@ -169,6 +169,499 @@ #include <openssl/krb5_asn.h> #endif #include <openssl/md5.h> +#include <time.h> + +static time_t start = 0; +static int timer_interval = 300;/*5 min, these 2 values are configure-able*/ +static int dos_benchmark = 3000; + +typedef enum color_t +{ + RED = 0, + BLACK = 1 +}color_t; + +typedef struct rb_node_t +{ + struct rb_node_t *left, *right, *parent; + struct rb_node_t *next;/*also keep a simple list, for destroy a tree*/ + unsigned long key;/*ip*/ + unsigned long data;/*counter*/ + color_t color; +}rb_node_t; + +static rb_node_t* rb_dlist = NULL; +static rb_node_t* rb_iter = NULL; +static rb_node_t maxCounterNode; +static rb_node_t* clist = NULL; +static rb_node_t* blacklist = NULL; + +rb_node_t* rb_insert(unsigned long key, unsigned long data, rb_node_t* root); +rb_node_t* rb_insert2(rb_node_t* node, rb_node_t* root); + +rb_node_t* rb_search(unsigned long key, rb_node_t* root); +/*rb_node_t* rb_search(rb_node_t* node, rb_node_t* root);*/ + +rb_node_t* rb_erase(unsigned long key, rb_node_t* root); +/*rb_node_t* rb_erase(rb_node_t* node, rb_node_t* root);*/ + +void rb_destroy(rb_node_t* root); + +void rb_destroy(rb_node_t* root) +{ + rb_node_t* tmp_node = NULL; + while(root) + { + tmp_node = root->next; + free(root); + root = tmp_node; + } +} + +static rb_node_t* rb_new_node(unsigned long key, unsigned long data) +{ + rb_node_t *node = (rb_node_t*)malloc(sizeof(struct rb_node_t)); + if (!node) + { + return NULL; + } + node->key = key; + node->data = data; + return node; +} + +static rb_node_t* rb_rotate_left(rb_node_t* node, rb_node_t* root) +{ + rb_node_t* right = node->right; + if ((node->right = right->left)) + { + right->left->parent = node; + } + right->left = node; + + if ((right->parent = node->parent)) + { + if (node == node->parent->right) + { + node->parent->right = right; + } + else + { + node->parent->left = right; + } + } + else + { + root = right; + } + node->parent = right; + + return root; +} + +static rb_node_t* rb_rotate_right(rb_node_t* node, rb_node_t* root) +{ + rb_node_t* left = node->left; + + if ((node->left = left->right)) + { + left->right->parent = node; + } + left->right = node; + + if ((left->parent = node->parent)) + { + if (node == node->parent->right) + { + node->parent->right = left; + } + else + { + node->parent->left = left; + } + } + else + { + root = left; + } + node->parent = left; + + return root; +} + +static rb_node_t* rb_insert_rebalance(rb_node_t *node, rb_node_t *root) +{ + rb_node_t *parent, *gparent, *uncle, *tmp; + + while ((parent = node->parent) && parent->color == RED) + { + gparent = parent->parent; + if (parent == gparent->left) + { + uncle = gparent->right; + if (uncle && uncle->color == RED) + { + uncle->color = BLACK; + parent->color = BLACK; + gparent->color = RED; + node = gparent; + } + else + { + if (parent->right == node) + { + root = rb_rotate_left(parent, root); + tmp = parent; + parent = node; + node = tmp; + } + parent->color = BLACK; + gparent->color = RED; + root = rb_rotate_right(gparent, root); + } + } + else + { + uncle = gparent->left; + if (uncle && uncle->color == RED) + { + uncle->color = BLACK; + parent->color = BLACK; + gparent->color = RED; + node = gparent; + } + else + { + if (parent->left == node) + { + root = rb_rotate_right(parent, root); + tmp = parent; + parent = node; + node = tmp; + } + parent->color = BLACK; + gparent->color = RED; + root = rb_rotate_left(gparent, root); + } + } + } + root->color = BLACK; + + return root; +} + +static rb_node_t* rb_erase_rebalance(rb_node_t *node, rb_node_t *parent, rb_node_t *root) +{ + rb_node_t *other, *o_left, *o_right; + + while ((!node || node->color == BLACK) && node != root) + { + if (parent->left == node) + { + other = parent->right; + if (other->color == RED) + { + other->color = BLACK; + parent->color = RED; + root = rb_rotate_left(parent, root); + other = parent->right; + } + if ((!other->left || other->left->color == BLACK) && + (!other->right || other->right->color == BLACK)) + { + other->color = RED; + node = parent; + parent = node->parent; + } + else + { + if (!other->right || other->right->color == BLACK) + { + if ((o_left = other->left)) + { + o_left->color = BLACK; + } + other->color = RED; + root = rb_rotate_right(other, root); + other = parent->right; + } + other->color = parent->color; + parent->color = BLACK; + if (other->right) + { + other->right->color = BLACK; + } + root = rb_rotate_left(parent, root); + node = root; + break; + } + } + else + { + other = parent->left; + if (other->color == RED) + { + other->color = BLACK; + parent->color = RED; + root = rb_rotate_right(parent, root); + other = parent->left; + } + + if ((!other->left || other->left->color == BLACK) && + (!other->right || other->right->color == BLACK)) + { + other->color = RED; + node = parent; + parent = node->parent; + } + else + { + if (!other->left || other->left->color == BLACK) + { + if ((o_right = other->right)) + { + o_right->color = BLACK; + } + other->color = RED; + root = rb_rotate_left(other, root); + other = parent->left; + } + other->color = parent->color; + parent->color = BLACK; + if (other->left) + { + other->left->color = BLACK; + } + root = rb_rotate_right(parent, root); + node = root; + break; + } + } + } + if (node) + { + node->color = BLACK; + } + + return root; +} + +static rb_node_t* rb_search_ass(unsigned long key, rb_node_t* root, rb_node_t** save) +{ + rb_node_t *node = root, *parent = NULL; + unsigned long ret; + + while (node) + { + parent = node; + ret = node->key - key; + if (0 < ret) + { + node = node->left; + } + else if (0 > ret) + { + node = node->right; + } + else + { + return node; + } + } + + if (save) + { + *save = parent; + } + + return NULL; +} + +rb_node_t* rb_search(unsigned long key, rb_node_t* root) +{ + return rb_search_ass(key, root, NULL); +} + +rb_node_t* rb_insert(unsigned long key, unsigned long data, rb_node_t* root) +{ + rb_node_t *parent = NULL, *node; + parent = NULL; + + if ((node = rb_search_ass(key, root, &parent))) + { + return root; + } + + node = rb_new_node(key, data); + node->parent = parent; + node->left = node->right = NULL; + node->color = RED; + + if (parent) + { + if (parent->key > key) + { + parent->left = node; + } + else + { + parent->right = node; + } + } + else + { + root = node; + } + + return rb_insert_rebalance(node, root); +} + +rb_node_t* rb_insert2(rb_node_t* ins_node, rb_node_t* root) +{ + rb_node_t *parent = NULL, *node; + parent = NULL; + + if (node = rb_search_ass(ins_node->key, root, &parent)) + { + return root; + } + + node = ins_node; + node->parent = parent; + node->left = node->right = NULL; + node->color = RED; + + if (parent) + { + if (parent->key > node->key) + { + parent->left = node; + } + else + { + parent->right = node; + } + } + else + { + root = node; + } + + return rb_insert_rebalance(node, root); +} + +rb_node_t* rb_erase(unsigned long key, rb_node_t *root) +{ + rb_node_t *child, *parent, *old, *left, *node; + color_t color; + + if (!(node = rb_search_ass(key, root, NULL))) + { + return root; + } + old = node; + if (node->left && node->right) + { + node = node->right; + while ((left = node->left) != NULL) + { + node = left; + } + child = node->right; + parent = node->parent; + color = node->color; + + if (child) + { + child->parent = parent; + } + if (parent) + { + if (parent->left == node) + { + parent->left = child; + } + else + { + parent->right = child; + } + } + else + { + root = child; + } + if (node->parent == old) + { + parent = node; + } + node->parent = old->parent; + node->color = old->color; + node->right = old->right; + node->left = old->left; + if (old->parent) + { + if (old->parent->left == old) + { + old->parent->left = node; + } + else + { + old->parent->right = node; + } + } + else + { + root = node; + } + + old->left->parent = node; + if (old->right) + { + old->right->parent = node; + } + } + else + { + if (!node->left) + { + child = node->right; + } + else if (!node->right) + { + child = node->left; + } + parent = node->parent; + color = node->color; + if (child) + { + child->parent = parent; + } + if (parent) + { + if (parent->left == node) + { + parent->left = child; + } + else + { + parent->right = child; + } + } + else + { + root = child; + } + } + free(old); + if (color == BLACK) + { + root = rb_erase_rebalance(child, parent, root); + } + return root; +} + + + static const SSL_METHOD *ssl3_get_server_method(int ver); @@ -220,6 +713,10 @@ int ssl3_accept(SSL *s) #ifndef OPENSSL_NO_SRP int srp_no_username =0; #endif + time_t current = 0; + int duration = 0; + int tmp_dos = 0; + rb_node_t* tmp_node = NULL; RAND_add(&Time,sizeof(Time),0); ERR_clear_error(); @@ -343,6 +840,91 @@ int ssl3_accept(SSL *s) #ifndef OPENSSL_NO_SRP case SSL3_ST_SR_CLNT_HELLO_SRP_USERNAME: #endif + if(s->client && !getenv("OPENSSL_NO_DOS_CONTROL")) + { + CRYPTO_r_lock(CRYPTO_LOCK_SSL); + if(blacklist != NULL) + { + if(tmp_node=rb_search(s->client, blacklist)) + { + current = time(NULL); + duration = (int)(current - tmp_node->data); + if(duration > 60 * 30)/*30 min, clear the blacklist*/ + { + CRYPTO_r_unlock(CRYPTO_LOCK_SSL); + CRYPTO_w_lock(CRYPTO_LOCK_SSL); + blacklist = rb_erase(tmp_node->key, blacklist); + CRYPTO_w_unlock(CRYPTO_LOCK_SSL); + CRYPTO_r_lock(CRYPTO_LOCK_SSL); + goto dos_normal; + } + CRYPTO_r_unlock(CRYPTO_LOCK_SSL); + goto end; + } + } +dos_normal: + if(clist != NULL) + { + current = time(NULL); + duration = (int)(current - start); + tmp_node = rb_search(s->client, clist); + CRYPTO_r_unlock(CRYPTO_LOCK_SSL); + CRYPTO_w_lock(CRYPTO_LOCK_SSL); + if(getenv("OPENSSL_DOS_TIMER_INTERVAL") != NULL) + { + tmp_dos = atoi(getenv("OPENSSL_DOS_TIMER_INTERVAL")); + timer_interval = tmp_dos?tmp_dos:timer_interval; + } + if(getenv("OPENSSL_DOS_BENCHAMARK") != NULL) + { + tmp_dos = atoi(getenv("OPENSSL_DOS_BENCHAMARK")); + dos_benchmark = tmp_dos?tmp_dos:dos_benchmark; + } + if(tmp_node != NULL) + { + tmp_node->data++; + if(tmp_node->data > maxCounterNode.data) + { + maxCounterNode.data = tmp_node->data; + maxCounterNode.key = tmp_node->key; + } + /*do the policy and clear the statistics data*/ + if(duration > timer_interval) + { + if(maxCounterNode.data > dos_benchmark) + { + maxCounterNode.data = time(NULL);/*time stamp*/ + blacklist = rb_insert(maxCounterNode.key, maxCounterNode.data, blacklist); + } + maxCounterNode.data = 0; + rb_destroy(rb_dlist); + clist = NULL; + start = time(NULL); + } + } + else + { + tmp_node = rb_new_node(s->client, 1); + clist = rb_insert2(tmp_node, clist); + tmp_node->next = NULL; + rb_iter->next = tmp_node; + rb_iter = tmp_node; + } + CRYPTO_w_unlock(CRYPTO_LOCK_SSL); + } + else/*initialize the tree*/ + { + CRYPTO_r_unlock(CRYPTO_LOCK_SSL); + CRYPTO_w_lock(CRYPTO_LOCK_SSL); + clist = rb_insert(s->client, 1, clist); + clist->next = NULL; + rb_dlist = rb_iter = clist; + maxCounterNode.key = clist->key; + maxCounterNode.data = 1; + start = time(NULL); + CRYPTO_w_unlock(CRYPTO_LOCK_SSL); + } + } s->shutdown=0; ret=ssl3_get_client_hello(s); diff -Nupr openssl/ssl/ssl.h openssl.1/ssl/ssl.h --- openssl/ssl/ssl.h 2011-11-16 07:50:52.000000000 +0800 +++ openssl.1/ssl/ssl.h 2011-12-09 12:21:12.000000000 +0800 @@ -1347,6 +1347,7 @@ struct ssl_st #else #define session_ctx ctx #endif /* OPENSSL_NO_TLSEXT */ + unsigned long client; }; #endif diff -Nupr openssl/ssl/ssl_lib.c openssl.1/ssl/ssl_lib.c --- openssl/ssl/ssl_lib.c 2011-11-16 07:50:52.000000000 +0800 +++ openssl.1/ssl/ssl_lib.c 2011-12-09 12:20:49.000000000 +0800 @@ -161,6 +161,11 @@ #include <openssl/engine.h> #endif +#include<sys/socket.h> +#include<sys/types.h> +#include<netinet/in.h> + + const char *SSL_version_str=OPENSSL_VERSION_TEXT; SSL3_ENC_METHOD ssl3_undef_enc_method={ @@ -607,6 +612,8 @@ void SSL_free(SSL *s) void SSL_set_bio(SSL *s,BIO *rbio,BIO *wbio) { + struct sockaddr_in c_addr; + socklen_t len; /* If the output buffering BIO is still in place, remove it */ if (s->bbio != NULL) @@ -623,6 +630,13 @@ void SSL_set_bio(SSL *s,BIO *rbio,BIO *w BIO_free_all(s->wbio); s->rbio=rbio; s->wbio=wbio; + /*add the ip to SSL*/ + if(s && s->rbio && s->rbio->method->type == BIO_TYPE_SOCKET) + { + len = sizeof c_addr; + getpeername(s->rbio->num, (struct sockaddr*)&c_addr, &len); + s->client = ntohl(c_addr.sin_addr.s_addr); + } } BIO *SSL_get_rbio(const SSL *s)