diff options
author | Ben Pfaff <blp@nicira.com> | 2010-08-11 10:24:40 -0700 |
---|---|---|
committer | Ben Pfaff <blp@nicira.com> | 2010-08-11 10:24:40 -0700 |
commit | f2f7be8696e030dbe6f7c859c4e2bd76fd363036 (patch) | |
tree | c59bc42f3d54e8f19d4b144886aa506f37d48d92 /lib | |
parent | da61d5732e5c8344a976275922c7d51c05c45781 (diff) |
stream-ssl: Enable SSL session caching.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/automake.mk | 1 | ||||
-rw-r--r-- | lib/hmap.c | 33 | ||||
-rw-r--r-- | lib/hmap.h | 2 | ||||
-rw-r--r-- | lib/shash.c | 11 | ||||
-rw-r--r-- | lib/shash.h | 1 | ||||
-rw-r--r-- | lib/stream-ssl.c | 104 |
6 files changed, 152 insertions, 0 deletions
diff --git a/lib/automake.mk b/lib/automake.mk index 49f4e2c7..16b8d021 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -255,6 +255,7 @@ COVERAGE_FILES = \ lib/rconn.c \ lib/rtnetlink.c \ lib/stream.c \ + lib/stream-ssl.c \ lib/timeval.c \ lib/unixctl.c \ lib/util.c \ @@ -19,6 +19,7 @@ #include <assert.h> #include <stdint.h> #include "coverage.h" +#include "random.h" #include "util.h" /* Initializes 'hmap' as an empty hash table. */ @@ -163,3 +164,35 @@ hmap_node_moved(struct hmap *hmap, *bucket = node; } +/* Chooses and returns a randomly selected node from 'hmap', which must not be + * empty. + * + * I wouldn't depend on this algorithm to be fair, since I haven't analyzed it. + * But it does at least ensure that any node in 'hmap' can be chosen. */ +struct hmap_node * +hmap_random_node(const struct hmap *hmap) +{ + struct hmap_node *bucket, *node; + size_t n, i; + + /* Choose a random non-empty bucket. */ + for (i = random_uint32(); ; i++) { + bucket = hmap->buckets[i & hmap->mask]; + if (bucket) { + break; + } + } + + /* Count nodes in bucket. */ + n = 0; + for (node = bucket; node; node = node->next) { + n++; + } + + /* Choose random node from bucket. */ + i = random_range(n); + for (node = bucket; i-- > 0; node = node->next) { + continue; + } + return node; +} @@ -89,6 +89,8 @@ void hmap_node_moved(struct hmap *, struct hmap_node *, struct hmap_node *); static inline void hmap_replace(struct hmap *, const struct hmap_node *old, struct hmap_node *new); +struct hmap_node *hmap_random_node(const struct hmap *); + /* Search. * * HMAP_FOR_EACH_WITH_HASH iterates NODE over all of the nodes in HMAP that diff --git a/lib/shash.c b/lib/shash.c index 1664baf6..8fd2eb18 100644 --- a/lib/shash.c +++ b/lib/shash.c @@ -279,3 +279,14 @@ shash_equal_keys(const struct shash *a, const struct shash *b) } return true; } + +/* Chooses and returns a randomly selected node from 'sh', which must not be + * empty. + * + * I wouldn't depend on this algorithm to be fair, since I haven't analyzed it. + * But it does at least ensure that any node in 'sh' can be chosen. */ +struct shash_node * +shash_random_node(struct shash *sh) +{ + return CONTAINER_OF(hmap_random_node(&sh->map), struct shash_node, node); +} diff --git a/lib/shash.h b/lib/shash.h index 19e4c5d6..eab0af45 100644 --- a/lib/shash.h +++ b/lib/shash.h @@ -64,6 +64,7 @@ void *shash_find_and_delete_assert(struct shash *, const char *); struct shash_node *shash_first(const struct shash *); const struct shash_node **shash_sort(const struct shash *); bool shash_equal_keys(const struct shash *, const struct shash *); +struct shash_node *shash_random_node(struct shash *); #ifdef __cplusplus } diff --git a/lib/stream-ssl.c b/lib/stream-ssl.c index 2bc9d1a8..69beab94 100644 --- a/lib/stream-ssl.c +++ b/lib/stream-ssl.c @@ -30,12 +30,14 @@ #include <sys/fcntl.h> #include <sys/stat.h> #include <unistd.h> +#include "coverage.h" #include "dynamic-string.h" #include "leak-checker.h" #include "ofpbuf.h" #include "openflow/openflow.h" #include "packets.h" #include "poll-loop.h" +#include "shash.h" #include "socket-util.h" #include "util.h" #include "stream-provider.h" @@ -133,6 +135,20 @@ struct ssl_stream /* SSL context created by ssl_init(). */ static SSL_CTX *ctx; +/* Maps from stream target (e.g. "127.0.0.1:1234") to SSL_SESSION *. The + * sessions are those from the last SSL connection to the given target. + * OpenSSL caches server-side sessions internally, so this cache is only used + * for client connections. + * + * The stream_ssl module owns a reference to each of the sessions in this + * table, so they must be freed with SSL_SESSION_free() when they are no + * longer needed. */ +static struct shash client_sessions = SHASH_INITIALIZER(&client_sessions); + +/* Maximum number of client sessions to cache. Ordinarily I'd expect that one + * session would be sufficient but this should cover it. */ +#define MAX_CLIENT_SESSION_CACHE 16 + struct ssl_config_file { bool read; /* Whether the file was successfully read. */ char *file_name; /* Configured file name, if any. */ @@ -416,6 +432,70 @@ do_ca_cert_bootstrap(struct stream *stream) return EPROTO; } +static void +ssl_delete_session(struct shash_node *node) +{ + SSL_SESSION *session = node->data; + SSL_SESSION_free(session); + shash_delete(&client_sessions, node); +} + +/* Find and free any previously cached session for 'stream''s target. */ +static void +ssl_flush_session(struct stream *stream) +{ + struct shash_node *node; + + node = shash_find(&client_sessions, stream_get_name(stream)); + if (node) { + ssl_delete_session(node); + } +} + +/* Add 'stream''s session to the cache for its target, so that it will be + * reused for future SSL connections to the same target. */ +static void +ssl_cache_session(struct stream *stream) +{ + struct ssl_stream *sslv = ssl_stream_cast(stream); + SSL_SESSION *session; + + /* Statistics. */ + COVERAGE_INC(ssl_session); + if (SSL_session_reused(sslv->ssl)) { + COVERAGE_INC(ssl_session_reused); + } + + /* Get session from stream. */ + session = SSL_get1_session(sslv->ssl); + if (session) { + SSL_SESSION *old_session; + + old_session = shash_replace(&client_sessions, stream_get_name(stream), + session); + if (old_session) { + /* Free the session that we replaced. (We might actually have + * session == old_session, but either way we have to free it to + * avoid leaking a reference.) */ + SSL_SESSION_free(old_session); + } else if (shash_count(&client_sessions) > MAX_CLIENT_SESSION_CACHE) { + for (;;) { + struct shash_node *node = shash_random_node(&client_sessions); + if (node->data != session) { + ssl_delete_session(node); + break; + } + } + } + } else { + /* There is no new session. This doesn't really make sense because + * this function is only called upon successful connection and there + * should always be a new session in that case. But I don't trust + * OpenSSL so I'd rather handle this case anyway. */ + ssl_flush_session(stream); + } +} + static int ssl_connect(struct stream *stream) { @@ -439,6 +519,15 @@ ssl_connect(struct stream *stream) MSG_PEEK); } + /* Grab SSL session information from the cache. */ + if (sslv->type == CLIENT) { + SSL_SESSION *session = shash_find_data(&client_sessions, + stream_get_name(stream)); + if (session) { + SSL_set_session(sslv->ssl, session); + } + } + retval = (sslv->type == CLIENT ? SSL_connect(sslv->ssl) : SSL_accept(sslv->ssl)); if (retval != 1) { @@ -447,6 +536,18 @@ ssl_connect(struct stream *stream) return EAGAIN; } else { int unused; + + if (sslv->type == CLIENT) { + /* Delete any cached session for this stream's target. + * Otherwise a single error causes recurring errors that + * don't resolve until the SSL client or server is + * restarted. (It can take dozens of reused connections to + * see this behavior, so this is difficult to test.) If we + * delete the session on the first error, though, the error + * only occurs once and then resolves itself. */ + ssl_flush_session(stream); + } + interpret_ssl_error((sslv->type == CLIENT ? "SSL_connect" : "SSL_accept"), retval, error, &unused); shutdown(sslv->fd, SHUT_RDWR); @@ -471,6 +572,9 @@ ssl_connect(struct stream *stream) VLOG_ERR("rejecting SSL connection during bootstrap race window"); return EPROTO; } else { + if (sslv->type == CLIENT) { + ssl_cache_session(stream); + } return 0; } } |