Skip to content

Instantly share code, notes, and snippets.

@nojima
Last active February 23, 2023 19:44
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nojima/daac8c5710a2766bd638 to your computer and use it in GitHub Desktop.
Save nojima/daac8c5710a2766bd638 to your computer and use it in GitHub Desktop.

nginx-ssl-scache-sync-module

nginx-ssl-scache-sync-module は nginx の SSL セッションキャッシュを異なるホスト上の nginx と共有するためのパッチです。

利用するときは nginx をビルドする際に patch -p0 で適用してください。

ディレクティブ

ssl_session_cache_peers

セッションキャッシュを共有する nginx の IP アドレスとポートを指定します。 ポートは ssl_session_cache_receiveon になっている virtual server のものを指定してください。 ピアはスペース区切りで複数個指定できます。

例:

ssl_session_cache_peers 192.168.0.2:9090 192.168.0.3:9090;

ssl_session_cache_receive

セッションキャッシュの受け取りに使用する virtual server を指定するためのディレクティブです。 Server コンテキストで ssl_session_cache_receive on と指定すると、その virtual server で SSL セッションキャッシュを受け取ることができます。 このディレクティブを使用する virtual server はセッションキャッシュの受け取り専用にすることを強くおすすめします。

例:

server {
    listen 9090;

    ssl_session_cache_receive on;

    keepalive_requests 10000;
    access_log off;
}

注意

scache sync モジュールを使うためには SSL セッションを共有メモリに保存する設定を有効にする必要があります。

例:

ssl_session_cache shared:SSL:256m;

ライセンス

2-clause BSD license

diff --git a/auto/sources b/auto/sources
index 3d89e2d..8969d61 100644
--- a/auto/sources
+++ b/auto/sources
@@ -86,7 +86,8 @@ REGEX_SRCS=src/core/ngx_regex.c
OPENSSL_MODULE=ngx_openssl_module
OPENSSL_DEPS=src/event/ngx_event_openssl.h
OPENSSL_SRCS="src/event/ngx_event_openssl.c \
- src/event/ngx_event_openssl_stapling.c"
+ src/event/ngx_event_openssl_stapling.c \
+ src/event/ngx_event_scache_sync.c"
EVENT_MODULES="ngx_events_module ngx_event_core_module"
@@ -419,9 +420,10 @@ HTTP_REWRITE_MODULE=ngx_http_rewrite_module
HTTP_REWRITE_SRCS=src/http/modules/ngx_http_rewrite_module.c
-HTTP_SSL_MODULE=ngx_http_ssl_module
+HTTP_SSL_MODULE="ngx_http_ssl_module ngx_http_scache_sync_module"
HTTP_SSL_DEPS=src/http/modules/ngx_http_ssl_module.h
-HTTP_SSL_SRCS=src/http/modules/ngx_http_ssl_module.c
+HTTP_SSL_SRCS="src/http/modules/ngx_http_ssl_module.c \
+ src/http/modules/ngx_http_scache_sync_module.c"
HTTP_PROXY_MODULE=ngx_http_proxy_module
diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c
index 1b789e6..a562674 100644
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -41,8 +41,6 @@ static int ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn,
static ngx_ssl_session_t *ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn,
u_char *id, int len, int *copy);
static void ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess);
-static void ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
- ngx_slab_pool_t *shpool, ngx_uint_t n);
static void ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
@@ -103,6 +101,7 @@ int ngx_ssl_session_cache_index;
int ngx_ssl_session_ticket_keys_index;
int ngx_ssl_certificate_index;
int ngx_ssl_stapling_index;
+int ngx_ssl_scache_sync_index;
ngx_int_t
@@ -184,6 +183,13 @@ ngx_ssl_init(ngx_log_t *log)
return NGX_ERROR;
}
+ ngx_ssl_scache_sync_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+ if (ngx_ssl_scache_sync_index == -1) {
+ ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+ "SSL_CTX_get_ex_new_index() failed");
+ return NGX_ERROR;
+ }
+
return NGX_OK;
}
@@ -2401,6 +2407,14 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)
ngx_rbtree_insert(&cache->session_rbtree, &sess_id->node);
+ {
+ SSL_SESSION_set_app_data(sess, (char*)ssl_conn);
+
+ ngx_str_t key = { session_id_length, id };
+ ngx_str_t value = { len, cached_sess };
+ ngx_ssl_scache_sync_add_session(ssl_conn, &key, &value);
+ }
+
ngx_shmtx_unlock(&shpool->mutex);
return 0;
@@ -2497,6 +2511,8 @@ ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn, u_char *id, int len,
p = buf;
sess = d2i_SSL_SESSION(NULL, &p, sess_id->len);
+ SSL_SESSION_set_app_data(sess, (char*)ssl_conn);
+
return sess;
}
@@ -2617,11 +2633,19 @@ ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess)
done:
+ {
+ ngx_ssl_conn_t* ssl_conn = (ngx_ssl_conn_t*)SSL_SESSION_get_app_data(sess);
+ if (ssl_conn != NULL) {
+ ngx_str_t key = { len, id };
+ ngx_ssl_scache_sync_remove_session(ssl_conn, &key);
+ }
+ }
+
ngx_shmtx_unlock(&shpool->mutex);
}
-static void
+void
ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
ngx_slab_pool_t *shpool, ngx_uint_t n)
{
diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h
index 08eff64..d5d11cf 100644
--- a/src/event/ngx_event_openssl.h
+++ b/src/event/ngx_event_openssl.h
@@ -145,6 +145,15 @@ ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl,
ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data);
ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c,
ngx_uint_t flags);
+void ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
+ ngx_slab_pool_t *shpool, ngx_uint_t n);
+ngx_int_t ngx_ssl_scache_sync_init(ngx_conf_t *cf, SSL_CTX *ssl_ctx);
+ngx_int_t ngx_ssl_scache_sync_add_peer(ngx_conf_t *cf, SSL_CTX *ssl_ctx,
+ ngx_str_t *peer_url);
+void ngx_ssl_scache_sync_add_session(ngx_ssl_conn_t *ssl_conn, ngx_str_t *key,
+ ngx_str_t *value);
+void ngx_ssl_scache_sync_remove_session(ngx_ssl_conn_t *ssl_conn,
+ ngx_str_t *key);
void ngx_ssl_remove_cached_session(SSL_CTX *ssl, ngx_ssl_session_t *sess);
ngx_int_t ngx_ssl_set_session(ngx_connection_t *c, ngx_ssl_session_t *session);
@@ -210,6 +219,7 @@ extern int ngx_ssl_session_cache_index;
extern int ngx_ssl_session_ticket_keys_index;
extern int ngx_ssl_certificate_index;
extern int ngx_ssl_stapling_index;
+extern int ngx_ssl_scache_sync_index;
#endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
diff --git a/src/event/ngx_event_scache_sync.c b/src/event/ngx_event_scache_sync.c
new file mode 100644
index 0000000..2a4e94b
--- /dev/null
+++ b/src/event/ngx_event_scache_sync.c
@@ -0,0 +1,947 @@
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+
+
+typedef enum {
+ NGX_SSL_SCACHE_SYNC_OP_UNKNOWN,
+ NGX_SSL_SCACHE_SYNC_OP_ADD,
+ NGX_SSL_SCACHE_SYNC_OP_REMOVE,
+} ngx_ssl_scache_sync_operation_t;
+
+
+typedef struct ngx_ssl_scache_sync_peer_s ngx_ssl_scache_sync_peer_t;
+
+
+typedef struct {
+ ngx_ssl_scache_sync_peer_t *sp;
+
+ ngx_buf_t *request;
+ ngx_buf_t *response;
+
+ ngx_pool_t *pool;
+ ngx_log_t *log;
+} ngx_ssl_scache_sync_ctx_t;
+
+
+typedef struct {
+ ngx_ssl_scache_sync_operation_t op;
+ in_addr_t addr;
+ in_port_t port;
+ ngx_str_t server;
+ ngx_str_t key;
+ ngx_str_t value;
+
+ ngx_queue_t queue;
+
+ ngx_pool_t *pool;
+ ngx_log_t *log;
+} ngx_ssl_scache_sync_command_t;
+
+
+struct ngx_ssl_scache_sync_peer_s {
+ ngx_addr_t *addrs;
+ ngx_str_t host;
+ ngx_str_t uri;
+ in_port_t port;
+ ngx_msec_t timeout;
+
+ ngx_peer_connection_t peer;
+
+ ngx_queue_t commands;
+ size_t n_commands;
+ size_t max_commands;
+
+ ngx_queue_t queue;
+};
+
+
+typedef struct {
+ ngx_queue_t peers;
+} ngx_ssl_scache_sync_t;
+
+
+static void ngx_ssl_scache_sync_start(ngx_ssl_conn_t *ssl_conn, ngx_str_t *key,
+ ngx_str_t *value, ngx_ssl_scache_sync_operation_t op);
+static ngx_ssl_scache_sync_command_t * ngx_ssl_scache_sync_create_command(void);
+static ngx_ssl_scache_sync_ctx_t * ngx_ssl_scache_sync_create_context(
+ ngx_ssl_scache_sync_peer_t* sp);
+static u_char * ngx_ssl_scache_sync_log_error(ngx_log_t *log, u_char *buf,
+ size_t len);
+static ngx_int_t ngx_ssl_scache_sync_create_request(
+ ngx_ssl_scache_sync_ctx_t *ctx);
+static void ngx_ssl_scache_sync_post_request(ngx_ssl_scache_sync_peer_t *sp);
+static void ngx_ssl_scache_sync_connect(ngx_ssl_scache_sync_ctx_t *ctx);
+static void ngx_ssl_scache_sync_setup_connection(ngx_ssl_scache_sync_ctx_t *ctx);
+static void ngx_ssl_scache_sync_write_handler(ngx_event_t *wev);
+static void ngx_ssl_scache_sync_read_handler(ngx_event_t *rev);
+static ngx_int_t ngx_ssl_scache_sync_process_response(
+ ngx_ssl_scache_sync_ctx_t *ctx);
+static void ngx_ssl_scache_sync_keep_connection(ngx_ssl_scache_sync_peer_t *sp);
+static void ngx_ssl_scache_sync_dummy_handler(ngx_event_t *ev);
+static int ngx_ssl_scache_sync_is_closed(ngx_connection_t *c);
+static void ngx_ssl_scache_sync_close_check_handler(ngx_event_t *ev);
+static void ngx_ssl_scache_sync_error(ngx_ssl_scache_sync_ctx_t *ctx);
+static void ngx_ssl_scache_sync_destroy_command(
+ ngx_ssl_scache_sync_command_t *cmd);
+static void ngx_ssl_scache_sync_destroy_context(ngx_ssl_scache_sync_ctx_t *ctx);
+static void ngx_ssl_scache_sync_close_connection(
+ ngx_ssl_scache_sync_peer_t *sp);
+
+
+ngx_int_t
+ngx_ssl_scache_sync_init(ngx_conf_t *cf, SSL_CTX *ssl_ctx)
+{
+ ngx_ssl_scache_sync_t *scache;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cf->log, 0,
+ "ngx_ssl_scache_sync_init");
+
+ scache = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_scache_sync_t));
+ if (scache == NULL)
+ return NGX_ERROR;
+
+ ngx_queue_init(&scache->peers);
+
+ if (SSL_CTX_set_ex_data(ssl_ctx, ngx_ssl_scache_sync_index, scache) == 0) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "SSL_CTX_set_ex_data() failed");
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_scache_sync_add_peer(ngx_conf_t *cf, SSL_CTX *ssl_ctx, ngx_str_t *peer_url)
+{
+ ngx_ssl_scache_sync_t *scache;
+ ngx_url_t url;
+ ngx_ssl_scache_sync_peer_t *sp;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cf->log, 0,
+ "ngx_ssl_scache_sync_add_peer: %V",
+ peer_url);
+
+ scache = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_scache_sync_index);
+
+ ngx_memzero(&url, sizeof(ngx_url_t));
+
+ url.url = *peer_url;
+ url.default_port = 80;
+
+ if (ngx_parse_url(cf->pool, &url) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ERR, cf->log, 0,
+ "failed to parse URL in scache_sync_peer: \"%V\"",
+ &url.url);
+ return NGX_ERROR;
+ }
+
+ sp = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_scache_sync_peer_t));
+ if (sp == NULL)
+ return NGX_ERROR;
+
+ sp->addrs = url.addrs;
+ sp->host = url.host;
+ sp->uri = url.uri;
+ sp->port = url.port;
+ sp->timeout = 1000;
+ sp->max_commands = 1000;
+
+ if (sp->uri.len == 0) {
+ ngx_str_set(&sp->uri, "/");
+ }
+
+ ngx_queue_init(&sp->commands);
+ ngx_queue_insert_tail(&scache->peers, &sp->queue);
+
+ return NGX_OK;
+}
+
+
+void
+ngx_ssl_scache_sync_add_session(ngx_ssl_conn_t *ssl_conn,
+ ngx_str_t *key,
+ ngx_str_t *value)
+{
+ ngx_ssl_scache_sync_start(ssl_conn, key, value,
+ NGX_SSL_SCACHE_SYNC_OP_ADD);
+}
+
+
+void
+ngx_ssl_scache_sync_remove_session(ngx_ssl_conn_t *ssl_conn,
+ ngx_str_t *key)
+{
+ ngx_str_t value = ngx_null_string;
+ ngx_ssl_scache_sync_start(ssl_conn, key, &value,
+ NGX_SSL_SCACHE_SYNC_OP_REMOVE);
+}
+
+
+static void
+ngx_ssl_scache_sync_start(ngx_ssl_conn_t *ssl_conn,
+ ngx_str_t *key,
+ ngx_str_t *value,
+ ngx_ssl_scache_sync_operation_t op)
+{
+ SSL_CTX *ssl_ctx;
+ ngx_ssl_scache_sync_t *scache;
+ ngx_ssl_scache_sync_peer_t *sp;
+ ngx_queue_t *q;
+ ngx_str_t servername;
+ ngx_connection_t *c;
+ ngx_ssl_scache_sync_command_t *cmd;
+
+ ssl_ctx = SSL_get_SSL_CTX(ssl_conn);
+ scache = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_scache_sync_index);
+ if (scache == NULL)
+ return;
+
+ /* Get address:port */
+ c = ngx_ssl_get_connection(ssl_conn);
+ if (c->local_sockaddr->sa_family != AF_INET)
+ return;
+ struct sockaddr_in *sockaddr = (struct sockaddr_in *)c->local_sockaddr;
+ in_addr_t addr = sockaddr->sin_addr.s_addr;
+ in_port_t port = sockaddr->sin_port;
+
+ /* Get server name */
+ servername.data = (u_char *)SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name);
+ if (servername.data != NULL)
+ servername.len = ngx_strlen(servername.data);
+ else
+ servername.len = 0;
+
+ /* Post request */
+ for (q = ngx_queue_head(&scache->peers);
+ q != ngx_queue_sentinel(&scache->peers);
+ q = ngx_queue_next(q))
+ {
+ sp = ngx_queue_data(q, ngx_ssl_scache_sync_peer_t, queue);
+
+ if (sp->n_commands == sp->max_commands) {
+ ngx_log_error(NGX_LOG_WARN, c->log, 0,
+ "Dropping scache sync command: queue is full");
+ continue;
+ }
+
+ cmd = ngx_ssl_scache_sync_create_command();
+ if (cmd == NULL)
+ continue;
+
+ cmd->op = op;
+
+ cmd->addr = addr;
+ cmd->port = port;
+
+ cmd->server.len = servername.len;
+ cmd->server.data = ngx_pstrdup(cmd->pool, &servername);
+ if (cmd->server.data == NULL) {
+ ngx_ssl_scache_sync_destroy_command(cmd);
+ return;
+ }
+
+ cmd->key.len = key->len;
+ cmd->key.data = ngx_pstrdup(cmd->pool, key);
+ if (cmd->key.data == NULL) {
+ ngx_ssl_scache_sync_destroy_command(cmd);
+ return;
+ }
+
+ cmd->value.len = value->len;
+ cmd->value.data = ngx_pstrdup(cmd->pool, value);
+ if (cmd->value.data == NULL) {
+ ngx_ssl_scache_sync_destroy_command(cmd);
+ return;
+ }
+
+ ngx_queue_insert_tail(&sp->commands, &cmd->queue);
+ sp->n_commands += 1;
+ ngx_ssl_scache_sync_post_request(sp);
+ }
+}
+
+
+static ngx_ssl_scache_sync_command_t *
+ngx_ssl_scache_sync_create_command(void)
+{
+ ngx_pool_t *pool;
+ ngx_log_t *log;
+ ngx_ssl_scache_sync_command_t *cmd;
+
+ pool = ngx_create_pool(1024, ngx_cycle->log);
+ if (pool == NULL)
+ return NULL;
+
+ cmd = ngx_pcalloc(pool, sizeof(ngx_ssl_scache_sync_command_t));
+ if (cmd == NULL) {
+ ngx_destroy_pool(pool);
+ return NULL;
+ }
+
+ log = ngx_palloc(pool, sizeof(ngx_log_t));
+ if (log == NULL) {
+ ngx_destroy_pool(pool);
+ return NULL;
+ }
+
+ cmd->pool = pool;
+
+ *log = *cmd->pool->log;
+
+ cmd->pool->log = log;
+ cmd->log = log;
+
+ log->handler = ngx_ssl_scache_sync_log_error;
+ log->data = cmd;
+ log->action = "ssl session cache sync";
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cmd->log, 0,
+ "ngx_ssl_scache_sync_create_command");
+ return cmd;
+}
+
+
+static u_char *
+ngx_ssl_scache_sync_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+ u_char *p = buf;
+
+ if (log->action) {
+ p = ngx_snprintf(buf, len, " while %s", log->action);
+ }
+
+ return p;
+}
+
+
+static void
+ngx_ssl_scache_sync_post_request(ngx_ssl_scache_sync_peer_t *sp)
+{
+ ngx_ssl_scache_sync_ctx_t *ctx;
+
+ if (ngx_queue_empty(&sp->commands)) {
+ /* No commands */
+ return;
+ }
+
+ if (sp->peer.connection == NULL) {
+ /* peer is not connected */
+
+ ctx = ngx_ssl_scache_sync_create_context(sp);
+ if (ctx == NULL)
+ return;
+ ngx_ssl_scache_sync_connect(ctx);
+ return;
+ }
+
+ if (sp->peer.connection->idle) {
+ /* peer.connection is inactive */
+
+ ctx = ngx_ssl_scache_sync_create_context(sp);
+ if (ctx == NULL)
+ return;
+
+ ngx_connection_t *c = sp->peer.connection;
+
+ c->idle = 0;
+ ngx_ssl_scache_sync_setup_connection(ctx);
+ ngx_ssl_scache_sync_write_handler(c->write);
+ return;
+ }
+
+ /* Here, peer.connection is active.
+ * Nothing to do */
+ return;
+}
+
+
+static ngx_ssl_scache_sync_ctx_t *
+ngx_ssl_scache_sync_create_context(ngx_ssl_scache_sync_peer_t* sp)
+{
+ ngx_pool_t *pool;
+ ngx_log_t *log;
+ ngx_ssl_scache_sync_ctx_t *ctx;
+
+ pool = ngx_create_pool(1024, ngx_cycle->log);
+ if (pool == NULL)
+ return NULL;
+
+ ctx = ngx_pcalloc(pool, sizeof(ngx_ssl_scache_sync_ctx_t));
+ if (ctx == NULL) {
+ ngx_destroy_pool(pool);
+ return NULL;
+ }
+
+ log = ngx_palloc(pool, sizeof(ngx_log_t));
+ if (log == NULL) {
+ ngx_destroy_pool(pool);
+ return NULL;
+ }
+
+ ctx->pool = pool;
+
+ *log = *ctx->pool->log;
+
+ ctx->pool->log = log;
+ ctx->log = log;
+
+ log->handler = ngx_ssl_scache_sync_log_error;
+ log->data = ctx;
+ log->action = "ssl session cache sync";
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ngx_ssl_scache_sync_create_context");
+
+ ctx->sp = sp;
+
+ if (ngx_ssl_scache_sync_create_request(ctx) != NGX_OK) {
+ ngx_destroy_pool(pool);
+ return NULL;
+ }
+
+ return ctx;
+}
+
+
+static size_t
+ngx_num_digits(size_t n)
+{
+ size_t d = 1;
+ while (n >= 10) {
+ n /= 10;
+ ++d;
+ }
+ return d;
+}
+
+
+static ngx_int_t
+ngx_ssl_scache_sync_create_request(ngx_ssl_scache_sync_ctx_t *ctx)
+{
+ static const char REQUEST_LINE[] = "POST / HTTP/1.1";
+ static const char HOST[] = "Host: ";
+ static const char CONTENT_LENGTH[] = "Content-Length: ";
+
+ ngx_ssl_scache_sync_peer_t *sp = ctx->sp;
+ ngx_str_t *host = &sp->host;
+ u_char *p;
+ ngx_buf_t *buf;
+ ngx_queue_t *q;
+ ngx_ssl_scache_sync_command_t *cmd;
+ size_t body_len, request_len;
+
+ /* Calculate length */
+ body_len = 0;
+ for (q = ngx_queue_head(&sp->commands);
+ q != ngx_queue_sentinel(&sp->commands);
+ q = ngx_queue_next(q))
+ {
+ cmd = ngx_queue_data(q, ngx_ssl_scache_sync_command_t, queue);
+
+ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ngx_ssl_scache_sync_create_request: "
+ "op=%d, addr=%uxD, port=%d, server=\"%V\", key=\"%V\"",
+ (int)cmd->op, cmd->addr, (int)cmd->port,
+ &cmd->server, &cmd->key);
+
+ body_len += 1; /* opcode */
+ body_len += 4; /* addr */
+ body_len += 2; /* port */
+ body_len += 4; /* server_len */
+ body_len += 4; /* key_len */
+ body_len += 4; /* value_len */
+ body_len += cmd->server.len;
+ body_len += cmd->key.len;
+ body_len += cmd->value.len;
+ }
+
+ request_len = 0;
+ request_len += sizeof(REQUEST_LINE) - 1 + sizeof(CRLF) - 1;
+ request_len += sizeof(HOST) - 1 + host->len + sizeof(CRLF) - 1;
+ request_len += sizeof(CONTENT_LENGTH) - 1 + ngx_num_digits(body_len) + sizeof(CRLF) - 1;
+ request_len += sizeof(CRLF) - 1;
+ request_len += body_len;
+
+ buf = ngx_create_temp_buf(ctx->pool, request_len);
+ if (buf == NULL)
+ return NGX_ERROR;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ngx_ssl_scache_sync_create_request: body_len=%uz",
+ body_len);
+
+ p = buf->last;
+
+ /* Request line */
+ p = ngx_cpymem(p, REQUEST_LINE, sizeof(REQUEST_LINE) - 1);
+ *p++ = CR; *p++ = LF;
+
+ /* Host */
+ p = ngx_cpymem(p, HOST, sizeof(HOST) - 1);
+ p = ngx_cpymem(p, host->data, host->len);
+ *p++ = CR; *p++ = LF;
+
+ /* Content-Length */
+ p = ngx_cpymem(p, CONTENT_LENGTH, sizeof(CONTENT_LENGTH) - 1);
+ p = ngx_sprintf(p, "%uz", body_len);
+ *p++ = CR; *p++ = LF;
+
+ /* End Of Header */
+ *p++ = CR; *p++ = LF;
+
+ /* Body */
+ for (q = ngx_queue_head(&sp->commands);
+ q != ngx_queue_sentinel(&sp->commands);
+ q = ngx_queue_next(q))
+ {
+ ngx_ssl_scache_sync_command_t *cmd;
+ cmd = ngx_queue_data(q, ngx_ssl_scache_sync_command_t, queue);
+ uint32_t n;
+
+ *p++ = (uint8_t)cmd->op;
+
+ n = htonl(cmd->addr);
+ p = ngx_cpymem(p, &n, sizeof(uint32_t));
+
+ p = ngx_cpymem(p, &cmd->port, sizeof(uint16_t));
+
+ n = htonl(cmd->server.len);
+ p = ngx_cpymem(p, &n, sizeof(uint32_t));
+
+ n = htonl(cmd->key.len);
+ p = ngx_cpymem(p, &n, sizeof(uint32_t));
+
+ n = htonl(cmd->value.len);
+ p = ngx_cpymem(p, &n, sizeof(uint32_t));
+
+ p = ngx_cpymem(p, cmd->server.data, cmd->server.len);
+ p = ngx_cpymem(p, cmd->key.data, cmd->key.len);
+ p = ngx_cpymem(p, cmd->value.data, cmd->value.len);
+ }
+
+ buf->last = p;
+ ctx->request = buf;
+
+ /* Remove commands */
+ q = ngx_queue_head(&sp->commands);
+ while (q != ngx_queue_sentinel(&sp->commands)) {
+ ngx_ssl_scache_sync_command_t *cmd;
+ cmd = ngx_queue_data(q, ngx_ssl_scache_sync_command_t, queue);
+ q = ngx_queue_next(q);
+ ngx_ssl_scache_sync_destroy_command(cmd);
+ }
+ ngx_queue_init(&sp->commands);
+ sp->n_commands = 0;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_ssl_scache_sync_connect(ngx_ssl_scache_sync_ctx_t *ctx)
+{
+ ngx_int_t rc;
+ ngx_ssl_scache_sync_peer_t *sp = ctx->sp;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ngx_ssl_scache_sync_connect");
+
+ ngx_memzero(&sp->peer, sizeof(sp->peer));
+ sp->peer.sockaddr = sp->addrs[0].sockaddr;
+ sp->peer.socklen = sp->addrs[0].socklen;
+ sp->peer.name = &sp->addrs[0].name;
+ sp->peer.get = ngx_event_get_peer;
+ sp->peer.log = ctx->log;
+ sp->peer.log_error = NGX_ERROR_ERR;
+
+ rc = ngx_event_connect_peer(&sp->peer);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ngx_ssl_scache_sync_connect: rc=%d, fd=%d",
+ (int)rc,
+ sp->peer.connection ? sp->peer.connection->fd : -1);
+
+ if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
+ ngx_ssl_scache_sync_destroy_context(ctx);
+ return;
+ }
+
+ ngx_ssl_scache_sync_setup_connection(ctx);
+
+ if (rc != NGX_OK) {
+ /* rc == NGX_AGAIN */
+ return;
+ }
+
+ ngx_ssl_scache_sync_write_handler(sp->peer.connection->write);
+}
+
+
+static void
+ngx_ssl_scache_sync_setup_connection(ngx_ssl_scache_sync_ctx_t *ctx)
+{
+ ngx_ssl_scache_sync_peer_t *sp = ctx->sp;
+ ngx_connection_t *c = sp->peer.connection;
+
+ c->data = ctx;
+ c->pool = ctx->pool;
+ c->log = ctx->log;
+
+ c->read->handler = ngx_ssl_scache_sync_read_handler;
+ c->read->log = ctx->log;
+ c->write->handler = ngx_ssl_scache_sync_write_handler;
+ c->write->log = ctx->log;
+
+ if (!c->read->timer_set)
+ ngx_add_timer(c->read, sp->timeout);
+ if (!c->read->timer_set)
+ ngx_add_timer(c->write, sp->timeout);
+}
+
+
+static void
+ngx_ssl_scache_sync_write_handler(ngx_event_t *wev)
+{
+ ssize_t n, size;
+ ngx_connection_t *c;
+ ngx_ssl_scache_sync_ctx_t *ctx;
+
+ c = wev->data;
+ ctx = c->data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, wev->log, 0,
+ "ngx_ssl_scache_sync_write_handler: fd=%d",
+ c->fd);
+
+ if (wev->timedout) {
+ ngx_log_error(NGX_LOG_ERR, wev->log, NGX_ETIMEDOUT,
+ "SSL session cache sync timed out");
+ ngx_ssl_scache_sync_error(ctx);
+ return;
+ }
+
+ size = ctx->request->last - ctx->request->pos;
+
+ n = ngx_send(c, ctx->request->pos, size);
+
+ if (n == NGX_ERROR) {
+ ngx_ssl_scache_sync_error(ctx);
+ return;
+ }
+
+ if (n > 0) {
+ ctx->request->pos += n;
+
+ if (n == size) {
+ wev->handler = ngx_ssl_scache_sync_dummy_handler;
+
+ if (wev->timer_set)
+ ngx_del_timer(wev);
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ ngx_ssl_scache_sync_error(ctx);
+ return;
+ }
+
+ if (c->read->ready)
+ ngx_ssl_scache_sync_read_handler(c->read);
+ return;
+ }
+ }
+
+ if (!wev->timer_set)
+ ngx_add_timer(wev, ctx->sp->timeout);
+}
+
+
+static void
+ngx_ssl_scache_sync_read_handler(ngx_event_t *rev)
+{
+ ssize_t n, size;
+ ngx_int_t rc;
+ ngx_ssl_scache_sync_ctx_t *ctx;
+ ngx_connection_t *c;
+
+ c = rev->data;
+ ctx = c->data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, rev->log, 0,
+ "ngx_ssl_scache_sync_read_handler: fd=%d",
+ c->fd);
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT,
+ "SSL session cache sync timed out");
+ ngx_ssl_scache_sync_error(ctx);
+ return;
+ }
+
+ if (ctx->response == NULL) {
+ ctx->response = ngx_create_temp_buf(ctx->pool, 512);
+ if (ctx->response == NULL) {
+ ngx_ssl_scache_sync_error(ctx);
+ return;
+ }
+ }
+
+ for (;;) {
+ size = ctx->response->end - ctx->response->last;
+
+ n = ngx_recv(c, ctx->response->last, size);
+
+ if (n > 0) {
+ ctx->response->last += n;
+
+ rc = ngx_ssl_scache_sync_process_response(ctx);
+
+ if (rc == NGX_OK) {
+ /* Connection: keep-alive */
+ ngx_ssl_scache_sync_peer_t* sp = ctx->sp;
+ ngx_ssl_scache_sync_keep_connection(sp);
+ ngx_ssl_scache_sync_destroy_context(ctx);
+ ngx_ssl_scache_sync_post_request(sp);
+ return;
+ }
+
+ if (rc == NGX_DONE) {
+ /* Connection: close */
+ ngx_ssl_scache_sync_peer_t* sp = ctx->sp;
+
+ ngx_ssl_scache_sync_close_connection(sp);
+ ngx_ssl_scache_sync_destroy_context(ctx);
+ if (ngx_queue_empty(&sp->commands)) {
+ /* No waiting commands */
+ return;
+ }
+ /* Reconnect to process the next request */
+ ctx = ngx_ssl_scache_sync_create_context(sp);
+ if (ctx == NULL)
+ return;
+ ngx_ssl_scache_sync_connect(ctx);
+ return;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_ssl_scache_sync_error(ctx);
+ return;
+ }
+
+ /* rc == NGX_AGAIN */
+ continue;
+ }
+
+ if (n == NGX_AGAIN) {
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_ssl_scache_sync_error(ctx);
+ return;
+ }
+ return;
+ }
+
+ /* n == 0 */
+ break;
+ }
+
+ /* connection closed */
+
+ /* rc == NGX_ERROR or NGX_AGAIN */
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "SSL session cache sync peer prematurely closed connection");
+ ngx_ssl_scache_sync_error(ctx);
+}
+
+
+static ngx_int_t
+ngx_ssl_scache_sync_process_response(ngx_ssl_scache_sync_ctx_t *ctx)
+{
+ static char END_OF_HEADER[] = CRLF CRLF;
+ static char CONNECTION[] = CRLF "Connection:";
+ static char CONTENT_LENGTH[] = CRLF "Content-Length:";
+ static u_char CLOSE[] = "close";
+ u_char *p = NULL;
+ size_t content_len = 0;
+
+ size_t len = ctx->response->last - ctx->response->pos;
+ u_char* eoh = ngx_strnstr(ctx->response->pos, END_OF_HEADER, len);
+ if (eoh == NULL)
+ return NGX_AGAIN;
+ *eoh = '\0';
+
+ size_t header_len = eoh - ctx->response->pos + sizeof(END_OF_HEADER) - 1;
+
+ u_char* len_pos = ngx_strcasestrn(
+ ctx->response->pos, CONTENT_LENGTH, sizeof(CONTENT_LENGTH) - 1);
+ if (len_pos != NULL) {
+ p = len_pos + sizeof(CONTENT_LENGTH) - 1;
+ while (*p == ' ' || *p == '\t')
+ ++p;
+ for (; isdigit(*p); ++p)
+ content_len = content_len * 10 + (*p - '0');
+ }
+
+ if (len < header_len + content_len) {
+ *eoh = CR;
+ return NGX_AGAIN;
+ }
+
+ u_char* conn_pos = ngx_strcasestrn(
+ ctx->response->pos, CONNECTION, sizeof(CONNECTION) - 1);
+ if (conn_pos == NULL)
+ goto keep;
+ p = conn_pos + sizeof(CONNECTION) - 1;
+ while (*p == ' ' || *p == '\t')
+ ++p;
+ if (ngx_strncasecmp(p, CLOSE, sizeof(CLOSE) - 1) != 0)
+ goto keep;
+ p += sizeof(CLOSE) - 1;
+ while (*p == ' ' || *p == '\t')
+ ++p;
+ if (*p != '\0' && *p != CR && *p != LF)
+ goto keep;
+
+ /* Connection: close */
+ ctx->response->pos += header_len + content_len;
+ return NGX_DONE;
+
+keep:
+ /* Connection: keep-alive */
+ ctx->response->pos += header_len + content_len;
+ return NGX_OK;
+}
+
+
+static void
+ngx_ssl_scache_sync_keep_connection(ngx_ssl_scache_sync_peer_t *sp)
+{
+ ngx_connection_t *c;
+
+ c = sp->peer.connection;
+ c->data = sp;
+
+ if (c->read->timer_set)
+ ngx_del_timer(c->read);
+ if (c->write->timer_set)
+ ngx_del_timer(c->write);
+
+ c->write->handler = ngx_ssl_scache_sync_dummy_handler;
+ c->write->log = ngx_cycle->log;
+ c->read->handler = ngx_ssl_scache_sync_close_check_handler;
+ c->read->log = ngx_cycle->log;
+
+ c->log = ngx_cycle->log;
+ c->write->log = ngx_cycle->log;
+ c->pool->log = ngx_cycle->log;
+ c->idle = 1;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, sp->peer.connection->log, 0,
+ "ngx_ssl_scache_sync_keep_connection: fd=%d",
+ sp->peer.connection->fd);
+
+ if (c->read->ready)
+ c->read->handler(c->read);
+}
+
+
+static void
+ngx_ssl_scache_sync_dummy_handler(ngx_event_t *ev)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "ngx_ssl_scache_sync_dummy_handler");
+}
+
+
+static int
+ngx_ssl_scache_sync_is_closed(ngx_connection_t *c)
+{
+ ssize_t n;
+ char buf[1];
+
+ if (c->close)
+ return 1;
+
+ n = recv(c->fd, buf, 1, MSG_PEEK);
+
+ if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
+ /* stale event */
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK)
+ return 1;
+
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static void
+ngx_ssl_scache_sync_close_check_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c = ev->data;
+ ngx_ssl_scache_sync_peer_t *sp = c->data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "ngx_ssl_scache_sync_close_check_handler_for_cache: "
+ "fd=%d",
+ c->fd);
+
+ if (!ngx_ssl_scache_sync_is_closed(c))
+ return;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "ngx_ssl_scache_sync_close_check_handler_for_cache: "
+ "connection closed");
+
+ ngx_ssl_scache_sync_close_connection(sp);
+}
+
+
+static void
+ngx_ssl_scache_sync_error(ngx_ssl_scache_sync_ctx_t *ctx)
+{
+ ngx_ssl_scache_sync_peer_t *sp = ctx->sp;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ngx_ssl_scache_sync_error: fd=%d",
+ sp->peer.connection->fd);
+
+ ngx_ssl_scache_sync_close_connection(sp);
+ ngx_ssl_scache_sync_destroy_context(ctx);
+
+ if (ngx_queue_empty(&sp->commands)) {
+ /* No waiting requests */
+ return;
+ }
+ /* Reconnect for next request */
+ ngx_ssl_scache_sync_post_request(sp);
+}
+
+
+static void
+ngx_ssl_scache_sync_destroy_command(ngx_ssl_scache_sync_command_t *cmd)
+{
+ ngx_destroy_pool(cmd->pool);
+}
+
+
+static void
+ngx_ssl_scache_sync_destroy_context(ngx_ssl_scache_sync_ctx_t *ctx)
+{
+ ngx_destroy_pool(ctx->pool);
+}
+
+
+static void
+ngx_ssl_scache_sync_close_connection(ngx_ssl_scache_sync_peer_t *sp)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, sp->peer.connection->log, 0,
+ "ngx_ssl_scache_sync_close_connection: fd=%d",
+ sp->peer.connection->fd);
+
+ ngx_close_connection(sp->peer.connection);
+ ngx_memzero(&sp->peer, sizeof(sp->peer));
+}
diff --git a/src/http/modules/ngx_http_scache_sync_module.c b/src/http/modules/ngx_http_scache_sync_module.c
new file mode 100644
index 0000000..c428ce5
--- /dev/null
+++ b/src/http/modules/ngx_http_scache_sync_module.c
@@ -0,0 +1,553 @@
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_flag_t receive;
+} ngx_http_scache_sync_srv_conf_t;
+
+
+typedef enum {
+ NGX_HTTP_SCACHE_SYNC_OP_UNKNOWN,
+ NGX_HTTP_SCACHE_SYNC_OP_ADD,
+ NGX_HTTP_SCACHE_SYNC_OP_REMOVE,
+} ngx_http_scache_sync_operation_t;
+
+
+static ngx_int_t ngx_http_scache_sync_handler(ngx_http_request_t *r);
+static ngx_buf_t *ngx_http_scache_sync_get_request_body_buf(
+ ngx_http_request_t *r);
+static void ngx_http_scache_sync_body_handler(ngx_http_request_t *r);
+static void ngx_http_scache_sync_do_operation(ngx_http_request_t *r,
+ ngx_http_scache_sync_operation_t op, in_addr_t addr, in_port_t port,
+ ngx_str_t *server, ngx_str_t *key, ngx_str_t *value);
+static ngx_http_conf_addr_t * ngx_http_scache_sync_find_default_server(
+ ngx_http_request_t *r, in_addr_t addr, in_port_t port);
+static ngx_ssl_sess_id_t *ngx_http_scache_sync_find_session(
+ ngx_str_t* key, uint32_t hash, ngx_ssl_session_cache_t *cache);
+static void ngx_http_scache_sync_add_session(ngx_connection_t *c,
+ SSL_CTX* ssl_ctx, ngx_str_t *key, ngx_str_t *value,
+ ngx_shm_zone_t* shm_zone, ngx_ssl_session_cache_t* cache, uint32_t hash,
+ ngx_slab_pool_t *shpool);
+static ngx_int_t ngx_http_scache_sync_postconfig(ngx_conf_t *cf);
+static void *ngx_http_scache_sync_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_scache_sync_merge_srv_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+
+static ngx_command_t ngx_http_scache_sync_commands[] = {
+
+ { ngx_string("ssl_session_cache_receive"),
+ NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_scache_sync_srv_conf_t, receive),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_scache_sync_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_scache_sync_postconfig, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_http_scache_sync_create_srv_conf, /* create server configuration */
+ ngx_http_scache_sync_merge_srv_conf, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_scache_sync_module = {
+ NGX_MODULE_V1,
+ &ngx_http_scache_sync_module_ctx, /* module context */
+ ngx_http_scache_sync_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+#define ngx_http_scache_sync_str_equal(str, literal) \
+ ((str)->len == sizeof(literal) - 1 && \
+ ngx_strncasecmp((str)->data, (u_char *)(literal), sizeof(literal) - 1) == 0)
+
+
+static ngx_int_t
+ngx_http_scache_sync_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_scache_sync_srv_conf_t *scache_conf;
+
+ scache_conf = ngx_http_get_module_srv_conf(r, ngx_http_scache_sync_module);
+ if (!scache_conf->receive)
+ return NGX_DECLINED;
+
+ r->request_body_in_single_buf = 1;
+ r->request_body_in_file_only = 0;
+
+ rc = ngx_http_read_client_request_body(r, ngx_http_scache_sync_body_handler);
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE)
+ return rc;
+
+ return NGX_DONE;
+}
+
+
+static ngx_buf_t *
+ngx_http_scache_sync_get_request_body_buf(ngx_http_request_t *r)
+{
+ u_char *p;
+ size_t len;
+ ngx_buf_t *buf;
+ ngx_chain_t *cl;
+ ngx_buf_t *dest;
+
+ if (r->request_body == NULL
+ || r->request_body->bufs == NULL
+ || r->request_body->temp_file)
+ {
+ return NULL;
+ }
+
+ cl = r->request_body->bufs;
+ buf = cl->buf;
+
+ if (cl->next == NULL) {
+ return buf;
+ }
+
+ len = buf->last - buf->pos;
+ cl = cl->next;
+
+ for ( /* void */ ; cl; cl = cl->next) {
+ buf = cl->buf;
+ len += buf->last - buf->pos;
+ }
+
+ dest = ngx_create_temp_buf(r->pool, len);
+ if (dest == NULL) {
+ return NULL;
+ }
+
+ p = dest->start;
+ cl = r->request_body->bufs;
+
+ for ( /* void */ ; cl; cl = cl->next) {
+ buf = cl->buf;
+ p = ngx_cpymem(p, buf->pos, buf->last - buf->pos);
+ }
+
+ dest->last = p;
+ return dest;
+}
+
+
+static void
+ngx_http_scache_sync_body_handler(ngx_http_request_t *r)
+{
+ static const int COMMAND_HEADER_SIZE = 19;
+
+ ngx_buf_t *b;
+
+ b = ngx_http_scache_sync_get_request_body_buf(r);
+ if (b == NULL) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ while (b->pos < b->last) {
+ uint32_t n;
+ ngx_http_scache_sync_operation_t op = NGX_HTTP_SCACHE_SYNC_OP_UNKNOWN;
+ in_addr_t addr = 0;
+ in_port_t port = 0;
+ ngx_str_t server = ngx_null_string;
+ ngx_str_t key = ngx_null_string;
+ ngx_str_t value = ngx_null_string;
+
+ if (b->last - b->pos < COMMAND_HEADER_SIZE) {
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return;
+ }
+
+ op = b->pos[0];
+
+ memcpy(&n, &b->pos[1], sizeof(in_addr_t));
+ addr = ntohl(n);
+
+ memcpy(&port, &b->pos[5], sizeof(in_port_t));
+
+ memcpy(&n, &b->pos[7], sizeof(uint32_t));
+ server.len = ntohl(n);
+
+ memcpy(&n, &b->pos[11], sizeof(uint32_t));
+ key.len = ntohl(n);
+
+ memcpy(&n, &b->pos[15], sizeof(uint32_t));
+ value.len = ntohl(n);
+
+ b->pos += COMMAND_HEADER_SIZE;
+
+ if (b->last - b->pos < (ssize_t)server.len + (ssize_t)key.len + (ssize_t)value.len) {
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return;
+ }
+
+ server.data = b->pos;
+ b->pos += server.len;
+
+ key.data = b->pos;
+ b->pos += key.len;
+
+ value.data = b->pos;
+ b->pos += value.len;
+
+ ngx_http_scache_sync_do_operation(r, op, addr, port, &server, &key, &value);
+ }
+
+ ngx_http_finalize_request(r, NGX_HTTP_NO_CONTENT);
+}
+
+
+static void
+ngx_http_scache_sync_do_operation(ngx_http_request_t *r,
+ ngx_http_scache_sync_operation_t op,
+ in_addr_t addr,
+ in_port_t port,
+ ngx_str_t* server,
+ ngx_str_t* key,
+ ngx_str_t* value)
+{
+ ngx_http_ssl_srv_conf_t *sscf;
+ ngx_shm_zone_t *shm_zone;
+ ngx_ssl_session_cache_t *cache;
+ uint32_t hash;
+ ngx_slab_pool_t *shpool;
+ ngx_ssl_sess_id_t *sess_id;
+
+ /* find virtual server */
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ngx_http_scache_sync_handler: "
+ "op=%d, addr=%d, port=%d, key='%V', value.len=%d",
+ (int)op, (int)addr, (int)ntohs(port), key, (int)value->len);
+
+ ngx_connection_t *c = r->connection;
+ ngx_http_core_srv_conf_t *cscf = NULL;
+
+ ngx_http_conf_addr_t *conf_addr;
+ conf_addr = ngx_http_scache_sync_find_default_server(r, addr, port);
+
+ if (conf_addr == NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ngx_http_scache_sync_handler: not found");
+ return;
+ }
+
+ if (server->len != 0) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ngx_http_scache_sync_handler: server='%V'",
+ server);
+
+ ngx_http_virtual_names_t virtual_names;
+
+ ngx_memzero(&virtual_names, sizeof(ngx_http_virtual_names_t));
+ virtual_names.names.hash = conf_addr->hash;
+ virtual_names.names.wc_head = conf_addr->wc_head;
+ virtual_names.names.wc_tail = conf_addr->wc_tail;
+#if (NGX_PCRE)
+ virtual_names.nregex = conf_addr->nregex;
+ virtual_names.regex = conf_addr->regex;
+#endif
+
+ if (ngx_http_find_virtual_server(c, &virtual_names, server, r, &cscf)
+ == NGX_OK)
+ {
+ goto found;
+ }
+ }
+
+ /* fallback to default server */
+ cscf = conf_addr->default_server;
+
+found:
+ sscf = ngx_http_get_module_srv_conf(cscf->ctx, ngx_http_ssl_module);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ngx_http_scache_sync_handler: cscf=%p, sscf=%p",
+ cscf, sscf);
+
+ if (sscf->ssl.ctx == NULL || sscf->shm_zone == NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ngx_http_scache_sync_handler: ssl.ctx or shm_zone is null");
+ return;
+ }
+
+ shm_zone = sscf->shm_zone;
+
+ cache = shm_zone->data;
+ hash = ngx_crc32_short(key->data, key->len);
+ shpool = (ngx_slab_pool_t*)shm_zone->shm.addr;
+
+ ngx_shmtx_lock(&shpool->mutex);
+
+ sess_id = ngx_http_scache_sync_find_session(key, hash, cache);
+
+ if (sess_id != NULL) {
+ /* Session cache found */
+ if (op == NGX_HTTP_SCACHE_SYNC_OP_REMOVE) {
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "ngx_http_scache_sync_module: remove ssl session: %08XD:%ud:%d",
+ hash, key->len, value->len);
+
+ ngx_queue_remove(&sess_id->queue);
+ ngx_rbtree_delete(&cache->session_rbtree, (ngx_rbtree_node_t*)sess_id);
+ ngx_slab_free_locked(shpool, sess_id->session);
+#if (NGX_PTR_SIZE == 4)
+ ngx_slab_free_locked(shpool, sess_id->id);
+#endif
+ ngx_slab_free_locked(shpool, sess_id);
+ } else {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ngx_http_scache_sync_handler: session cache already exists");
+ }
+ } else {
+ /* Session cache not found */
+ if (op == NGX_HTTP_SCACHE_SYNC_OP_ADD) {
+ ngx_http_scache_sync_add_session(
+ c, sscf->ssl.ctx, key, value, shm_zone, cache, hash, shpool);
+ } else {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ngx_http_scache_sync_handler: session cache not found");
+ }
+ }
+
+ ngx_shmtx_unlock(&shpool->mutex);
+}
+
+
+static ngx_http_conf_addr_t *
+ngx_http_scache_sync_find_default_server(ngx_http_request_t *r, in_addr_t addr, in_port_t port)
+{
+ size_t i, n;
+ ngx_http_core_main_conf_t *cmcf;
+ ngx_http_conf_port_t *ports;
+ ngx_http_conf_addr_t *conf_addrs;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ngx_http_scache_sync_find_default_server: "
+ "addr=%uxD, port=%d",
+ addr, (int)ntohs(port));
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ ports = cmcf->ports->elts;
+ for (i = 0; i < cmcf->ports->nelts; i++) {
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ngx_http_scache_sync_handler: ports[%d]=%d, req.port=%d",
+ (int)i, (int)ntohs(ports[i].port), (int)ntohs(port));
+
+ if (ports[i].port == port)
+ break;
+ }
+
+ if (i == cmcf->ports->nelts) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "Default server for %uxD:%d is not found.",
+ addr, (int)ntohs(port));
+ return NULL;
+ }
+
+ conf_addrs = ports[i].addrs.elts;
+ n = ports[i].addrs.nelts;
+ for (i = 0; i < n; ++i) {
+ struct sockaddr *s = &conf_addrs[i].opt.u.sockaddr;
+ if (s->sa_family != AF_INET)
+ continue;
+ struct sockaddr_in* sin = (struct sockaddr_in*)s;
+ in_addr_t s_addr = sin->sin_addr.s_addr;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ngx_http_scache_sync_handler: addr=%uxD, req.addr=%uxD",
+ s_addr, addr);
+
+ if (s_addr == 0 || s_addr == addr)
+ return &conf_addrs[i];
+ }
+
+ return NULL;
+}
+
+
+static ngx_ssl_sess_id_t *
+ngx_http_scache_sync_find_session(ngx_str_t* key, uint32_t hash,
+ ngx_ssl_session_cache_t *cache)
+{
+ ngx_int_t rc;
+ ngx_rbtree_node_t *node, *sentinel;
+ ngx_ssl_sess_id_t* sess_id;
+
+ node = cache->session_rbtree.root;
+ sentinel = cache->session_rbtree.sentinel;
+
+ while (node != sentinel) {
+ if (hash < node->key) {
+ node = node->left;
+ continue;
+ }
+ if (hash > node->key) {
+ node = node->right;
+ continue;
+ }
+
+ /* hash == node->key */
+ sess_id = (ngx_ssl_sess_id_t *)node;
+ rc = ngx_memn2cmp(key->data, sess_id->id, key->len, (size_t)node->data);
+ if (rc == 0)
+ return sess_id;
+
+ node = (rc < 0) ? node->left : node->right;
+ }
+
+ return NULL;
+}
+
+
+static void
+ngx_http_scache_sync_add_session(ngx_connection_t* c, SSL_CTX *ssl_ctx,
+ ngx_str_t *key, ngx_str_t *value,
+ ngx_shm_zone_t* shm_zone,
+ ngx_ssl_session_cache_t* cache,
+ uint32_t hash, ngx_slab_pool_t *shpool)
+{
+ u_char *id = NULL;
+ u_char *cached_sess = NULL;
+ ngx_ssl_sess_id_t *sess_id = NULL;
+
+ /* drop one or two expired sessions */
+ ngx_ssl_expire_sessions(cache, shpool, 1);
+
+ cached_sess = ngx_slab_alloc_locked(shpool, value->len);
+ if (cached_sess == NULL) {
+ /* drop the oldest non-expired session and try once more */
+ ngx_ssl_expire_sessions(cache, shpool, 0);
+ cached_sess = ngx_slab_alloc_locked(shpool, value->len);
+ if (cached_sess == NULL)
+ goto failed;
+ }
+
+ sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
+ if (sess_id == NULL) {
+ /* drop the oldest non-expired session and try once more */
+ ngx_ssl_expire_sessions(cache, shpool, 0);
+ sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
+ if (sess_id == NULL)
+ goto failed;
+ }
+
+#if (NGX_PTR_SIZE == 8)
+ id = sess_id->sess_id;
+#else
+ id = ngx_slab_alloc_locked(shpool, session_id_length);
+ if (id == NULL) {
+ /* drop the oldest non-expired session and try once more */
+ ngx_ssl_expire_sessions(cache, shpool, 0);
+ id = ngx_slab_alloc_locked(shpool, session_id_length);
+ if (id == NULL)
+ goto failed;
+ }
+#endif
+
+ ngx_memcpy(cached_sess, value->data, value->len);
+ ngx_memcpy(id, key->data, key->len);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "ngx_http_scache_sync_module: add ssl session: %08XD:%ud:%d",
+ hash, key->len, value->len);
+
+ sess_id->node.key = hash;
+ sess_id->node.data = (u_char)key->len;
+ sess_id->id = id;
+ sess_id->len = value->len;
+ sess_id->session = cached_sess;
+ sess_id->expire = ngx_time() + SSL_CTX_get_timeout(ssl_ctx);
+
+ ngx_queue_insert_head(&cache->expire_queue, &sess_id->queue);
+ ngx_rbtree_insert(&cache->session_rbtree, &sess_id->node);
+
+ return;
+
+failed:
+
+#if (NGX_PTR_SIZE != 8)
+ if (id != NULL)
+ ngx_slab_free_locked(shpool, id);
+#endif
+
+ if (cached_sess != NULL)
+ ngx_slab_free_locked(shpool, cached_sess);
+
+ if (sess_id != NULL)
+ ngx_slab_free_locked(shpool, sess_id);
+}
+
+
+static ngx_int_t
+ngx_http_scache_sync_postconfig(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ /* add phase handler */
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_scache_sync_handler;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_scache_sync_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_scache_sync_srv_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_scache_sync_srv_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->receive = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_scache_sync_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_scache_sync_srv_conf_t *prev = parent;
+ ngx_http_scache_sync_srv_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->receive, prev->receive, 0);
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c
index d6a1794..fa9b0f8 100644
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -47,6 +47,8 @@ static char *ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
+static char *ngx_http_ssl_session_cache_peers(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf);
@@ -177,6 +179,13 @@ static ngx_command_t ngx_http_ssl_commands[] = {
0,
NULL },
+ { ngx_string("ssl_session_cache_peers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+ ngx_http_ssl_session_cache_peers,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
{ ngx_string("ssl_session_tickets"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
@@ -530,6 +539,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
sscf->session_ticket_keys = NGX_CONF_UNSET_PTR;
sscf->stapling = NGX_CONF_UNSET;
sscf->stapling_verify = NGX_CONF_UNSET;
+ sscf->scache_sync_peers = NGX_CONF_UNSET_PTR;
return sscf;
}
@@ -771,6 +781,23 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
}
+ ngx_conf_merge_ptr_value(conf->scache_sync_peers,
+ prev->scache_sync_peers, NULL);
+ if (conf->scache_sync_peers != NULL) {
+ size_t i;
+ ngx_array_t* peers = conf->scache_sync_peers;
+
+ if (ngx_ssl_scache_sync_init(cf, conf->ssl.ctx) != NGX_OK)
+ return NGX_CONF_ERROR;
+
+ ngx_str_t* names = peers->elts;
+ for (i = 0; i < peers->nelts; i++) {
+ ngx_str_t* name = &names[i];
+ if (ngx_ssl_scache_sync_add_peer(cf, conf->ssl.ctx, name) != NGX_OK)
+ return NGX_CONF_ERROR;
+ }
+ }
+
return NGX_CONF_OK;
}
@@ -930,6 +957,37 @@ invalid:
}
+static char *
+ngx_http_ssl_session_cache_peers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ size_t i;
+ ngx_str_t *values;
+ ngx_http_ssl_srv_conf_t *sscf = conf;
+ ngx_array_t *peers = sscf->scache_sync_peers;
+
+ if (peers != NGX_CONF_UNSET_PTR)
+ return "is duplicate";
+
+ peers = ngx_array_create(cf->pool, cf->args->nelts - 1, sizeof(ngx_str_t));
+ if (peers == NULL)
+ return NGX_CONF_ERROR;
+
+ values = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ ngx_str_t* s = ngx_array_push(peers);
+ if (s == NULL)
+ return NGX_CONF_ERROR;
+
+ *s = values[i];
+ }
+
+ sscf->scache_sync_peers = peers;
+
+ return NGX_CONF_OK;
+}
+
+
static ngx_int_t
ngx_http_ssl_init(ngx_conf_t *cf)
{
diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h
index 8e69e9e..eb39bd2 100644
--- a/src/http/modules/ngx_http_ssl_module.h
+++ b/src/http/modules/ngx_http_ssl_module.h
@@ -56,6 +56,8 @@ typedef struct {
u_char *file;
ngx_uint_t line;
+
+ ngx_array_t *scache_sync_peers;
} ngx_http_ssl_srv_conf_t;
diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c
index 6b0a48f..02a80b0 100644
--- a/src/http/ngx_http.c
+++ b/src/http/ngx_http.c
@@ -1161,7 +1161,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
if (cmcf->ports == NULL) {
- cmcf->ports = ngx_array_create(cf->temp_pool, 2,
+ cmcf->ports = ngx_array_create(cf->pool, 2,
sizeof(ngx_http_conf_port_t));
if (cmcf->ports == NULL) {
return NGX_ERROR;
@@ -1349,7 +1349,7 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_conf_addr_t *addr;
if (port->addrs.elts == NULL) {
- if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
+ if (ngx_array_init(&port->addrs, cf->pool, 4,
sizeof(ngx_http_conf_addr_t))
!= NGX_OK)
{
@@ -1398,7 +1398,7 @@ ngx_http_add_server(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_core_srv_conf_t **server;
if (addr->servers.elts == NULL) {
- if (ngx_array_init(&addr->servers, cf->temp_pool, 4,
+ if (ngx_array_init(&addr->servers, cf->pool, 4,
sizeof(ngx_http_core_srv_conf_t *))
!= NGX_OK)
{
diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h
index 844f502..4c7b8ec 100644
--- a/src/http/ngx_http.h
+++ b/src/http/ngx_http.h
@@ -92,6 +92,10 @@ void ngx_http_close_connection(ngx_connection_t *c);
int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg);
#endif
+ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c,
+ ngx_http_virtual_names_t *virtual_names, ngx_str_t *host,
+ ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp);
+
ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b);
ngx_int_t ngx_http_parse_uri(ngx_http_request_t *r);
ngx_int_t ngx_http_parse_complex_uri(ngx_http_request_t *r,
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c
index cd5f302..1b88f3c 100644
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -34,9 +34,6 @@ static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
ngx_uint_t alloc);
static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
ngx_str_t *host);
-static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c,
- ngx_http_virtual_names_t *virtual_names, ngx_str_t *host,
- ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp);
static void ngx_http_request_handler(ngx_event_t *ev);
static void ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc);
@@ -2079,7 +2076,7 @@ ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host)
}
-static ngx_int_t
+ngx_int_t
ngx_http_find_virtual_server(ngx_connection_t *c,
ngx_http_virtual_names_t *virtual_names, ngx_str_t *host,
ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp)
Copyright (C) 2016 Cybozu.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment