From 8671b898609777c95aedf33743419a523874e6e8 Mon Sep 17 00:00:00 2001 From: Ben Laurie Date: Tue, 3 Jun 2008 02:48:34 +0000 Subject: [PATCH] Memory saving patch. --- CHANGES | 4 ++ doc/ssl/SSL_CTX_set_mode.pod | 10 +++ ssl/s23_clnt.c | 3 + ssl/s23_srvr.c | 4 ++ ssl/s3_both.c | 136 +++++++++++++++++++++++++++++++++-- ssl/s3_lib.c | 4 +- ssl/s3_pkt.c | 23 +++++- ssl/ssl.h | 12 +++- ssl/ssl_lib.c | 44 +++++++++++- ssl/ssl_locl.h | 18 +++++ 10 files changed, 246 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index c5c50787cd..78272fac5b 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,10 @@ Changes between 0.9.8g and 0.9.9 [xx XXX xxxx] + *) Add a new SSL_MODE_RELEASE_BUFFERS mode flag to release unused buffer + RAM on SSL connections. This option can save about 34k per idle SSL. + [Nick Mathewson] + *) Expand ENGINE to support engine supplied SSL client certificate functions. [Steve Henson] diff --git a/doc/ssl/SSL_CTX_set_mode.pod b/doc/ssl/SSL_CTX_set_mode.pod index 9822544e5e..8cb669daeb 100644 --- a/doc/ssl/SSL_CTX_set_mode.pod +++ b/doc/ssl/SSL_CTX_set_mode.pod @@ -61,6 +61,16 @@ deal with read/write operations returning without success report. The flag SSL_MODE_AUTO_RETRY will cause read/write operations to only return after the handshake and successful completion. +=item SSL_MODE_RELEASE_BUFFERS + +When we no longer need a read buffer or a write buffer for a given SSL, +then release the memory we were using to hold it. Released memory is +either appended to a list of unused RAM chunks on the SSL_CTX, or simply +freed if the list of unused chunks would become longer than +SSL_CTX->freelist_max_len, which defaults to 32. Using this flag can +save around 34k per idle SSL connection. +This flag has no effect on SSL v2 connections, or on DTLS connections. + =back =head1 RETURN VALUES diff --git a/ssl/s23_clnt.c b/ssl/s23_clnt.c index 78c39d9af5..f90e107c9f 100644 --- a/ssl/s23_clnt.c +++ b/ssl/s23_clnt.c @@ -640,6 +640,9 @@ static int ssl23_get_server_hello(SSL *s) * for SSLv3 */ s->rstate=SSL_ST_READ_HEADER; s->packet_length=n; + if (s->s3->rbuf.buf == NULL) + if (!ssl3_setup_read_buffer(s)) + goto err; s->packet= &(s->s3->rbuf.buf[0]); memcpy(s->packet,buf,n); s->s3->rbuf.left=n; diff --git a/ssl/s23_srvr.c b/ssl/s23_srvr.c index ddf5d4c3f5..9d5481cd0e 100644 --- a/ssl/s23_srvr.c +++ b/ssl/s23_srvr.c @@ -545,6 +545,10 @@ int ssl23_get_client_hello(SSL *s) * for SSLv3 */ s->rstate=SSL_ST_READ_HEADER; s->packet_length=n; + if (s->s3->rbuf.buf == NULL) + if (!ssl3_setup_read_buffer(s)) + goto err; + s->packet= &(s->s3->rbuf.buf[0]); memcpy(s->packet,buf,n); s->s3->rbuf.left=n; diff --git a/ssl/s3_both.c b/ssl/s3_both.c index 1a45e677a4..88bed0fa6b 100644 --- a/ssl/s3_both.c +++ b/ssl/s3_both.c @@ -591,7 +591,81 @@ int ssl_verify_alarm_type(long type) return(al); } -int ssl3_setup_buffers(SSL *s) +#if !defined(OPENSSL_NO_BUF_FREELISTS) && !defined(OPENSSL_NO_RELEASE_BUFFERS) +/* On some platforms, malloc() performance is bad enough that you can't just + * free() and malloc() buffers all the time, so we need to use freelists from + * unused buffers. Currently, each freelist holds memory chunks of only a + * given size (list->chunklen); other sized chunks are freed and malloced. + * This doesn't help much if you're using many different SSL option settings + * with a given context. (The options affecting buffer size are + * max_send_fragment, read buffer vs write buffer, + * SSL_OP_MICROSOFT_BIG_WRITE_BUFFER, SSL_OP_NO_COMPRESSION, and + * SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS.) Using a separate freelist for every + * possible size is not an option, since max_send_fragment can take on many + * different values. + * + * If you are on a platform with a slow malloc(), and you're using SSL + * connections with many different settings for these options, and you need to + * use the SSL_MOD_RELEASE_BUFFERS feature, you have a few options: + * - Link against a faster malloc implementation. + * - Use a separate SSL_CTX for each option set. + * - Improve this code. + */ +static void * +freelist_extract(SSL_CTX *ctx, int for_read, int sz) + { + SSL3_BUF_FREELIST *list; + SSL3_BUF_FREELIST_ENTRY *ent = NULL; + void *result = NULL; + + CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX); + list = for_read ? ctx->rbuf_freelist : ctx->wbuf_freelist; + if (list != NULL && sz == list->chunklen) + ent = list->head; + if (ent != NULL) + { + list->head = ent->next; + result = ent; + if (--list->len == 0) + list->chunklen = 0; + } + CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX); + if (!result) + result = OPENSSL_malloc(sz); + return result; +} + +static void +freelist_insert(SSL_CTX *ctx, int for_read, size_t sz, void *mem) + { + SSL3_BUF_FREELIST *list; + SSL3_BUF_FREELIST_ENTRY *ent; + + CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX); + list = for_read ? ctx->rbuf_freelist : ctx->wbuf_freelist; + if (list != NULL && + (sz == list->chunklen || list->chunklen == 0) && + list->len < ctx->freelist_max_len && + sz >= sizeof(*ent)) + { + list->chunklen = sz; + ent = mem; + ent->next = list->head; + list->head = ent; + ++list->len; + mem = NULL; + } + + CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX); + if (mem) + OPENSSL_free(mem); + } +#else +#define freelist_extract(c,fr,sz) OPENSSL_malloc(sz) +#define freelist_insert(c,fr,sz,m) OPENSSL_free(m) +#endif + +int ssl3_setup_read_buffer(SSL *s) { unsigned char *p; size_t len,align=0; @@ -614,12 +688,29 @@ int ssl3_setup_buffers(SSL *s) if (!(s->options & SSL_OP_NO_COMPRESSION)) len += SSL3_RT_MAX_COMPRESSED_OVERHEAD; #endif - if ((p=OPENSSL_malloc(len)) == NULL) + if ((p=freelist_extract(s->ctx, 1, len)) == NULL) goto err; s->s3->rbuf.buf = p; s->s3->rbuf.len = len; } + s->packet= &(s->s3->rbuf.buf[0]); + return 1; + +err: + SSLerr(SSL_F_SSL3_SETUP_BUFFERS,ERR_R_MALLOC_FAILURE); + return 0; + } + +int ssl3_setup_write_buffer(SSL *s) + { + unsigned char *p; + size_t len,align=0; + +#if defined(SSL3_ALIGN_PAYLOAD) && SSL3_ALIGN_PAYLOAD!=0 + align = (-SSL3_RT_HEADER_LENGTH)&(SSL3_ALIGN_PAYLOAD-1); +#endif + if (s->s3->wbuf.buf == NULL) { len = s->max_send_fragment @@ -632,14 +723,47 @@ int ssl3_setup_buffers(SSL *s) if (!(s->options & SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)) len += SSL3_RT_HEADER_LENGTH + align + SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD; - if ((p=OPENSSL_malloc(len)) == NULL) + + if ((p=freelist_extract(s->ctx, 0, len)) == NULL) goto err; s->s3->wbuf.buf = p; s->s3->wbuf.len = len; } - s->packet= &(s->s3->rbuf.buf[0]); - return(1); + + return 1; + err: SSLerr(SSL_F_SSL3_SETUP_BUFFERS,ERR_R_MALLOC_FAILURE); - return(0); + return 0; + } + + +int ssl3_setup_buffers(SSL *s) + { + if (!ssl3_setup_read_buffer(s)) + return 0; + if (!ssl3_setup_write_buffer(s)) + return 0; + return 1; + } + +int ssl3_release_write_buffer(SSL *s) + { + if (s->s3->wbuf.buf != NULL) + { + freelist_insert(s->ctx, 0, s->s3->wbuf.len, s->s3->wbuf.buf); + s->s3->wbuf.buf = NULL; + } + return 1; } + +int ssl3_release_read_buffer(SSL *s) + { + if (s->s3->rbuf.buf != NULL) + { + freelist_insert(s->ctx, 1, s->s3->rbuf.len, s->s3->rbuf.buf); + s->s3->rbuf.buf = NULL; + } + return 1; + } + diff --git a/ssl/s3_lib.c b/ssl/s3_lib.c index 6844a43b9e..2c0dc7ab14 100644 --- a/ssl/s3_lib.c +++ b/ssl/s3_lib.c @@ -2148,9 +2148,9 @@ void ssl3_free(SSL *s) ssl3_cleanup_key_block(s); if (s->s3->rbuf.buf != NULL) - OPENSSL_free(s->s3->rbuf.buf); + ssl3_release_read_buffer(s); if (s->s3->wbuf.buf != NULL) - OPENSSL_free(s->s3->wbuf.buf); + ssl3_release_write_buffer(s); if (s->s3->rrec.comp != NULL) OPENSSL_free(s->s3->rrec.comp); #ifndef OPENSSL_NO_DH diff --git a/ssl/s3_pkt.c b/ssl/s3_pkt.c index 3e5ce0c5b9..b4a1629853 100644 --- a/ssl/s3_pkt.c +++ b/ssl/s3_pkt.c @@ -137,6 +137,10 @@ int ssl3_read_n(SSL *s, int n, int max, int extend) if (n <= 0) return n; rb = &(s->s3->rbuf); + if (rb->buf == NULL) + if (!ssl3_setup_read_buffer(s)) + return -1; + left = rb->left; #if defined(SSL3_ALIGN_PAYLOAD) && SSL3_ALIGN_PAYLOAD!=0 align = (long)rb->buf + SSL3_RT_HEADER_LENGTH; @@ -234,6 +238,11 @@ int ssl3_read_n(SSL *s, int n, int max, int extend) if (i <= 0) { rb->left = left; +#ifndef OPENSSL_NO_RELEASE_BUFFERS + if (len+left == 0 && + (s->mode & SSL_MODE_RELEASE_BUFFERS)) + ssl3_release_read_buffer(s); +#endif return(i); } left+=i; @@ -609,6 +618,10 @@ static int do_ssl3_write(SSL *s, int type, const unsigned char *buf, SSL3_BUFFER *wb=&(s->s3->wbuf); SSL_SESSION *sess; + if (wb->buf == NULL) + if (!ssl3_setup_write_buffer(s)) + return -1; + /* first check if there is a SSL3_BUFFER still being written * out. This will happen with non blocking IO */ if (wb->left != 0) @@ -812,6 +825,10 @@ int ssl3_write_pending(SSL *s, int type, const unsigned char *buf, { wb->left=0; wb->offset+=i; +#ifndef OPENSSL_NO_RELEASE_BUFFERS + if (s->mode & SSL_MODE_RELEASE_BUFFERS) + ssl3_release_write_buffer(s); +#endif s->rwstate=SSL_NOTHING; return(s->s3->wpend_ret); } @@ -857,7 +874,7 @@ int ssl3_read_bytes(SSL *s, int type, unsigned char *buf, int len, int peek) void (*cb)(const SSL *ssl,int type2,int val)=NULL; if (s->s3->rbuf.buf == NULL) /* Not initialized yet */ - if (!ssl3_setup_buffers(s)) + if (!ssl3_setup_read_buffer(s)) return(-1); if ((type && (type != SSL3_RT_APPLICATION_DATA) && (type != SSL3_RT_HANDSHAKE) && type) || @@ -966,6 +983,10 @@ start: { s->rstate=SSL_ST_READ_HEADER; rr->off=0; +#ifndef OPENSSL_NO_RELEASE_BUFFERS + if ((s->mode & SSL_MODE_RELEASE_BUFFERS)) + ssl3_release_read_buffer(s); +#endif } } return(n); diff --git a/ssl/ssl.h b/ssl/ssl.h index b43fc0da0d..a9de499e0b 100644 --- a/ssl/ssl.h +++ b/ssl/ssl.h @@ -587,7 +587,10 @@ typedef struct ssl_session_st #define SSL_MODE_AUTO_RETRY 0x00000004L /* Don't attempt to automatically build certificate chain */ #define SSL_MODE_NO_AUTO_CHAIN 0x00000008L - +/* Save RAM by releasing read and write buffers when they're empty. (SSL3 and + * TLS only.) "Released" buffers are put onto a free-list in the context + * or just freed (depending on the context's setting for freelist_max_len). */ +#define SSL_MODE_RELEASE_BUFFERS 0x00000010L /* Note: SSL[_CTX]_set_{options,mode} use |= op on the previous value, * they cannot be used to clear bits. */ @@ -836,6 +839,13 @@ struct ssl_ctx_st unsigned int (*psk_server_callback)(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len); #endif + +#if !defined(OPENSSL_NO_BUF_FREELISTS) || !defined(OPENSSL_NO_RELEASE_BUFFERS) +#define SSL_MAX_BUF_FREELIST_LEN_DEFAULT 32 + unsigned int freelist_max_len; + struct ssl3_buf_freelist_st *wbuf_freelist; + struct ssl3_buf_freelist_st *rbuf_freelist; +#endif }; #define SSL_SESS_CACHE_OFF 0x0000 diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index fac080c19d..ebff8e23e6 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -1449,7 +1449,7 @@ static IMPLEMENT_LHASH_COMP_FN(ssl_session, SSL_SESSION) SSL_CTX *SSL_CTX_new(const SSL_METHOD *meth) { SSL_CTX *ret=NULL; - + if (meth == NULL) { SSLerr(SSL_F_SSL_CTX_NEW,SSL_R_NULL_SSL_METHOD_PASSED); @@ -1580,6 +1580,24 @@ SSL_CTX *SSL_CTX_new(const SSL_METHOD *meth) ret->psk_identity_hint=NULL; ret->psk_client_callback=NULL; ret->psk_server_callback=NULL; +#endif +#if !defined(OPENSSL_NO_BUF_FREELISTS) && !defined(OPENSSL_NO_RELEASE_BUFFERS) + ret->freelist_max_len = SSL_MAX_BUF_FREELIST_LEN_DEFAULT; + ret->rbuf_freelist = OPENSSL_malloc(sizeof(SSL3_BUF_FREELIST)); + if (!ret->rbuf_freelist) + goto err; + ret->rbuf_freelist->chunklen = 0; + ret->rbuf_freelist->len = 0; + ret->rbuf_freelist->head = NULL; + ret->wbuf_freelist = OPENSSL_malloc(sizeof(SSL3_BUF_FREELIST)); + if (!ret->wbuf_freelist) + { + OPENSSL_free(ret->rbuf_freelist); + goto err; + } + ret->wbuf_freelist->chunklen = 0; + ret->wbuf_freelist->len = 0; + ret->wbuf_freelist->head = NULL; #endif return(ret); err: @@ -1594,6 +1612,20 @@ static void SSL_COMP_free(SSL_COMP *comp) { OPENSSL_free(comp); } #endif +#if !defined(OPENSSL_NO_BUF_FREELISTS) && !defined(OPENSSL_NO_RELEASE_BUFFERS) +static void +ssl_buf_freelist_free(SSL3_BUF_FREELIST *list) + { + SSL3_BUF_FREELIST_ENTRY *ent, *next; + for (ent = list->head; ent; ent = next) + { + next = ent->next; + OPENSSL_free(ent); + } + OPENSSL_free(list); + } +#endif + void SSL_CTX_free(SSL_CTX *a) { int i; @@ -1660,6 +1692,14 @@ void SSL_CTX_free(SSL_CTX *a) if (a->client_cert_engine) ENGINE_finish(a->client_cert_engine); #endif + +#if !defined(OPENSSL_NO_BUF_FREELISTS) && !defined(OPENSSL_NO_RELEASE_BUFFERS) + if (a->wbuf_freelist) + ssl_buf_freelist_free(a->wbuf_freelist); + if (a->rbuf_freelist) + ssl_buf_freelist_free(a->rbuf_freelist); +#endif + OPENSSL_free(a); } @@ -2901,7 +2941,7 @@ void SSL_set_msg_callback(SSL *ssl, void (*cb)(int write_p, int version, int con * vairable, freeing EVP_MD_CTX previously stored in that variable, if * any. If EVP_MD pointer is passed, initializes ctx with this md * Returns newly allocated ctx; - */ + */ EVP_MD_CTX *ssl_replace_hash(EVP_MD_CTX **hash,const EVP_MD *md) { diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h index 55b23304c1..0238676ff3 100644 --- a/ssl/ssl_locl.h +++ b/ssl/ssl_locl.h @@ -561,6 +561,20 @@ typedef struct ssl3_comp_st COMP_METHOD *method; /* The method :-) */ } SSL3_COMP; +#if !defined(OPENSSL_NO_BUF_FREELISTS) && !defined(OPENSSL_NO_RELEASE_BUFFERS) +typedef struct ssl3_buf_freelist_st + { + size_t chunklen; + int len; + struct ssl3_buf_freelist_entry_st *head; + } SSL3_BUF_FREELIST; + +typedef struct ssl3_buf_freelist_entry_st + { + struct ssl3_buf_freelist_entry_st *next; + } SSL3_BUF_FREELIST_ENTRY; +#endif + extern SSL3_ENC_METHOD ssl3_undef_enc_method; OPENSSL_EXTERN SSL_CIPHER ssl2_ciphers[]; OPENSSL_EXTERN SSL_CIPHER ssl3_ciphers[]; @@ -859,6 +873,10 @@ unsigned long ssl3_output_cert_chain(SSL *s, X509 *x); SSL_CIPHER *ssl3_choose_cipher(SSL *ssl,STACK_OF(SSL_CIPHER) *clnt, STACK_OF(SSL_CIPHER) *srvr); int ssl3_setup_buffers(SSL *s); +int ssl3_setup_read_buffer(SSL *s); +int ssl3_setup_write_buffer(SSL *s); +int ssl3_release_read_buffer(SSL *s); +int ssl3_release_write_buffer(SSL *s); void ssl3_digest_cached_records(SSL *s); int ssl3_new(SSL *s); void ssl3_free(SSL *s); -- 2.34.1