#include #include #include #include #include #include #define SESS_CACHE_NOT_FOUND -1 #define SESS_CACHE_FOUND 0 #define SESS_CACHE_UPDATE_OLD 1 #define SESS_CACHE_ADD_NEW 2 #define SESS_CACHE_INVALID 3 struct asn1_sess { unsigned char * buff; size_t size; int version; }; struct sess_set_args { MESA_htable_handle hash; struct asn1_sess * new_sess; }; struct sess_get_args { SSL_SESSION *sess; int version; }; struct sess_cache { enum tfe_conn_dir served_for; MESA_htable_handle hash; long long hit_cnt, miss_cnt, del_err; }; static void ssl_sess_free_serialized(void * data) { struct asn1_sess * p = (struct asn1_sess *) data; free(p->buff); p->size = 0; free(p); return; } static struct asn1_sess * ssl_sess_serialize(SSL_SESSION * sess, int version) { struct asn1_sess * result = ALLOC(struct asn1_sess, 1); int i = i2d_SSL_SESSION(sess, NULL); UNUSED int j=0; result->size = (size_t) i; unsigned char* temp=NULL; assert(i > 0); /* When using i2d_SSL_SESSION(), the memory location pointed to by pp must be large enough to * hold the binary representation of the session. There is no known limit on the size of the * created ASN1 representation, so the necessary amount of space should be obtained by first * calling i2d_SSL_SESSION() with pp=NULL, and obtain the size needed, * then allocate the memory and call i2d_SSL_SESSION() again. *Note that this will advance the value contained in *pp so it is necessary to save a copy of the original allocation.*/ result->buff = temp = ALLOC(unsigned char, result->size); j=i2d_SSL_SESSION(sess, &(temp)); assert(i == j); assert(result->buff + i == temp); result->version=version; return result; } static SSL_SESSION * ssl_sess_deserialize(const struct asn1_sess * asn1) { SSL_SESSION * sess = NULL; const unsigned char *p=asn1->buff; /* i2d_SSL_SESSION increments the pointer pointed to by p to point one byte after the saved data * We save the pointer first.*/ sess=d2i_SSL_SESSION(NULL, &(p), (long) asn1->size); /* increments asn1 */ assert(sess!=NULL); return sess; } static int ssl_sess_verify_cb(void * data, int eliminate_type) { const struct asn1_sess * asn1 = (struct asn1_sess *) data; if (eliminate_type == ELIMINATE_TYPE_NUM) { return 1; //direct expired. } SSL_SESSION * sess = ssl_sess_deserialize(asn1); int ret = ssl_session_is_valid(sess); SSL_SESSION_free(sess); if (ret == 0) { return 1; //should be expired (deleted). } else { return 0; } } static long sess_cache_get_cb(void * data, const uchar * key, uint size, void * user_arg) { struct sess_get_args *result=(struct sess_get_args *)user_arg; SSL_SESSION * sess = NULL; int is_valid = 0; if (data == NULL) { return SESS_CACHE_NOT_FOUND; } const struct asn1_sess * asn1 = (struct asn1_sess *) data; sess = ssl_sess_deserialize(asn1); is_valid = ssl_session_is_valid(sess); if (is_valid == 0) { SSL_SESSION_free(sess); return SESS_CACHE_INVALID; } else { result->sess=sess; result->version=asn1->version; return SESS_CACHE_FOUND; } } static long sess_cache_set_cb(void * data, const uchar * key, uint size, void * user_arg) { struct sess_set_args * args = (struct sess_set_args *) user_arg; struct asn1_sess * new_asn1 = args->new_sess; struct asn1_sess * cur_asn1 = (struct asn1_sess *) data; UNUSED int ret = 0; if (cur_asn1 != NULL) { free(cur_asn1->buff); cur_asn1->size = new_asn1->size; cur_asn1->buff = ALLOC(unsigned char, cur_asn1->size); memcpy(cur_asn1->buff, new_asn1->buff, cur_asn1->size); cur_asn1->version=new_asn1->version; return SESS_CACHE_UPDATE_OLD; } else { ret = MESA_htable_add(args->hash, key, size, new_asn1); assert(ret >= 0); return SESS_CACHE_ADD_NEW; } } static size_t upsess_mk_key(struct sockaddr * res, socklen_t addrlen, const char * sni, unsigned char ** key_buf) { size_t key_size = 0; unsigned short port=0; char *s = NULL; switch(res->sa_family) { case AF_INET: { struct sockaddr_in *addr_in = (struct sockaddr_in *)res; s = (char*) malloc(INET_ADDRSTRLEN); inet_ntop(AF_INET, &(addr_in->sin_addr), s, INET_ADDRSTRLEN); port = ntohs(addr_in->sin_port); break; } case AF_INET6: { struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)res; s = (char*) malloc(INET6_ADDRSTRLEN); inet_ntop(AF_INET6, &(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN); port = ntohs(addr_in6->sin6_port); break; } default: break; } key_size=asprintf((char**)key_buf,"%s:%u:%s",s, port, sni?sni:"null"); free(s); return key_size; } void up_session_set(struct sess_cache * cache, struct sockaddr * addr, socklen_t addr_len, const char * sni, int version, SSL_SESSION * sess) { unsigned char * key = NULL; size_t key_size = 0; long cb_ret = 0; assert(cache->served_for == CONN_DIR_UPSTREAM); if(!SSL_SESSION_is_resumable(sess)) { return; } key_size = upsess_mk_key(addr, addr_len, sni, &key); struct asn1_sess * asn1 = NULL; asn1 = ssl_sess_serialize(sess, version); struct sess_set_args set_args={.hash = cache->hash, .new_sess = asn1}; MESA_htable_search_cb(cache->hash, key, key_size, sess_cache_set_cb, &set_args, &cb_ret); // printf("%s %s\n", __FUNCTION__, key); if (cb_ret == SESS_CACHE_UPDATE_OLD) { ssl_sess_free_serialized(asn1); } free(key); return; } SSL_SESSION * up_session_get(struct sess_cache * cache, struct sockaddr * addr, socklen_t addr_len, const char * sni, int min_ver, int max_ver) { struct sess_get_args args={NULL, 0}; long cb_ret = 0; size_t key_size = 0; assert(cache->served_for == CONN_DIR_UPSTREAM); unsigned char * key = NULL; key_size = upsess_mk_key(addr, addr_len, sni, &key); MESA_htable_search_cb(cache->hash, key, key_size, sess_cache_get_cb, &args, &cb_ret); // printf("%s %s\n", __FUNCTION__, key); free(key); key = NULL; if (cb_ret == SESS_CACHE_FOUND && args.version>=min_ver && args.version<=max_ver) { ATOMIC_INC(&(cache->hit_cnt)); return args.sess; } else { ATOMIC_INC(&(cache->miss_cnt)); return NULL; } } void down_session_set(struct sess_cache * cache, const SSL_SESSION * sess) { unsigned int idlen = 0; struct asn1_sess * asn1 = NULL; long cb_ret = 0; assert(cache->served_for == CONN_DIR_DOWNSTREAM); asn1 = ssl_sess_serialize((SSL_SESSION *) sess, 0); /* * SSL_SESSION_get_id() returns a pointer to the internal session id value for the session s. * The length of the id in bytes is stored in *idlen. The length may be 0. * The caller should not free the returned pointer directly. */ const unsigned char * id = SSL_SESSION_get_id(sess, &idlen); struct sess_set_args set_args={.hash = cache->hash, .new_sess = asn1}; MESA_htable_search_cb(cache->hash, id, idlen, sess_cache_set_cb, &set_args, &cb_ret); if (cb_ret == SESS_CACHE_UPDATE_OLD) { ssl_sess_free_serialized(asn1); } return; } SSL_SESSION * down_session_get(struct sess_cache * cache, const unsigned char * id, int idlen) { struct sess_get_args result; memset(&result, 0, sizeof(result)); long cb_ret = 0; assert(cache->served_for == CONN_DIR_DOWNSTREAM); MESA_htable_search_cb(cache->hash, id, (unsigned int) idlen, sess_cache_get_cb, &result, &cb_ret); if (cb_ret == SESS_CACHE_FOUND) { ATOMIC_INC(&(cache->hit_cnt)); return result.sess; } else { ATOMIC_INC(&(cache->miss_cnt)); return NULL; } } void down_session_del(struct sess_cache * cache, const SSL_SESSION * sess) { assert(cache->served_for == CONN_DIR_DOWNSTREAM); unsigned int len = 0; const unsigned char * id = SSL_SESSION_get_id(sess, &len); int ret = MESA_htable_del(cache->hash, id, len, NULL); if (ret != MESA_HTABLE_RET_OK) { ATOMIC_INC(&(cache->del_err)); } return; } struct sess_cache * ssl_sess_cache_create(unsigned int slot_size, unsigned int expire_seconds, enum tfe_conn_dir served) { struct sess_cache * cache = ALLOC(struct sess_cache, 1); unsigned max_num = slot_size * 4; UNUSED int ret = 0; int opt_val=0; MESA_htable_handle htable = MESA_htable_born(); opt_val=0; ret = MESA_htable_set_opt(htable, MHO_SCREEN_PRINT_CTRL, &opt_val, sizeof(opt_val)); opt_val=1; ret = MESA_htable_set_opt(htable, MHO_THREAD_SAFE, &opt_val, sizeof(opt_val)); opt_val=16; ret = MESA_htable_set_opt(htable, MHO_MUTEX_NUM, &opt_val, sizeof(opt_val)); ret = MESA_htable_set_opt(htable, MHO_HASH_SLOT_SIZE, &slot_size, sizeof(slot_size)); ret = MESA_htable_set_opt(htable, MHO_HASH_MAX_ELEMENT_NUM, &max_num, sizeof(max_num)); ret = MESA_htable_set_opt(htable, MHO_EXPIRE_TIME, &expire_seconds, sizeof(expire_seconds)); opt_val=HASH_ELIMINATE_ALGO_FIFO; ret = MESA_htable_set_opt(htable, MHO_ELIMIMINATE_TYPE, &opt_val, sizeof(int)); ret = MESA_htable_set_opt(htable, MHO_CBFUN_DATA_FREE, (void *)ssl_sess_free_serialized, sizeof(&ssl_sess_free_serialized)); ret = MESA_htable_set_opt(htable, MHO_CBFUN_DATA_EXPIRE_NOTIFY, (void *)ssl_sess_verify_cb, sizeof(&ssl_sess_verify_cb)); ret = MESA_htable_mature(htable); assert(ret == 0); cache->hash = htable; cache->served_for = served; return cache; } void ssl_sess_cache_destroy(struct sess_cache * cache) { MESA_htable_destroy(cache->hash, NULL); cache->hash = NULL; free(cache); return; } void ssl_sess_cache_stat(struct sess_cache * cache, long long *size, long long *n_query, long long* n_hit) { *size=MESA_htable_get_elem_num(cache->hash); *n_hit=cache->hit_cnt; *n_query=cache->hit_cnt+cache->miss_cnt; return; }