Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,33 @@ The one-shot `request_uri{ keepalive = { pool, pool_size, idle_timeout } }`
resolves the same defaults and shares the same pools, so a pool name reaches the
same connections from either entry point.

### TLS session resumption

An https connection offers the server the session from an earlier handshake with
the same peer. A resumed handshake sends no certificate and does no signature or
key-exchange work, and on TLS 1.2 it also saves a round trip. Both entry points
do it, and both take `ssl_session_reuse = false` to turn it off, the way nginx's
`proxy_ssl_session_reuse` does. It matters most where the connection pool cannot
help: a fresh connection per request, which is what the `tlsshort` benchmark
shape measures.

- The cache holds one session per TLS peer and lives for the worker, up to `256`
peers, evicting the least recently used. A session past its expiry is dropped
rather than offered.
- The cache key is the verify mode, the peer address, the SNI and the trusted CA
— every input the original handshake was judged on. A resumed handshake skips
the certificate exchange and answers with the verify result recorded in the
session, so a session established with verification off is never offered to a
request that asked for it on, and one trusting a given CA is never offered to a
request naming another.
- Only a handshake this client accepted leaves a session behind. A peer rejected
for a bad chain or a name mismatch leaves nothing for the next request.
- TLS 1.3 resumes as well as TLS 1.2: its tickets arrive after the handshake and
are picked up from the session callback rather than at handshake completion.
- A resumed handshake does not re-send the peer certificate. Nothing in this
client reads it outside the verify path, which reads it from the session, but
it is the observable difference to know about.

## Project status

This project is at an **early implementation stage**. The protocol direction is
Expand Down
1 change: 1 addition & 0 deletions config
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ngx_http_ffi_client_srcs="\
$ngx_addon_dir/src/ngx_http_ffi_client_module.c \
$ngx_addon_dir/src/ngx_http_ffi_client_lua.c \
$ngx_addon_dir/src/ngx_http_ffi_client_keepalive.c \
$ngx_addon_dir/src/ngx_http_ffi_client_ssl_session.c \
$ngx_addon_dir/src/ngx_http_ffi_client_request.c"

if [ "$ngx_http_ffi_client_use_llhttp" = "1" ]; then
Expand Down
14 changes: 13 additions & 1 deletion lib/resty/ngx_http_ffi_client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ if not pcall(ffi.typeof, "ngx_http_ffi_client_ffi_req_t") then
uintptr_t ssl_verify;
ngx_http_ffi_client_ffi_str_t ssl_server_name;
ngx_http_ffi_client_ffi_str_t ssl_trusted_certificate;
uintptr_t ssl_session_reuse;
} ngx_http_ffi_client_ffi_req_t;

typedef struct {
Expand Down Expand Up @@ -91,6 +92,7 @@ if not pcall(ffi.typeof, "ngx_http_ffi_client_ffi_req_t") then
uintptr_t ssl_verify;
ngx_http_ffi_client_ffi_str_t ssl_server_name;
ngx_http_ffi_client_ffi_str_t ssl_trusted_certificate;
uintptr_t ssl_session_reuse;
} ngx_http_ffi_client_ffi_connect_t;

typedef struct {
Expand Down Expand Up @@ -327,6 +329,9 @@ function _M.request_uri(opts)
-- verify defaults on for https; pass ssl_verify = false to disable
local ssl_verify = ssl and opts.ssl_verify ~= false

-- resumption defaults on; pass ssl_session_reuse = false to disable
local ssl_session_reuse = ssl and opts.ssl_session_reuse ~= false

if opts.ssl_server_name ~= nil
and (type(opts.ssl_server_name) ~= "string"
or opts.ssl_server_name == "")
Expand Down Expand Up @@ -412,6 +417,7 @@ function _M.request_uri(opts)
req.read_timeout = tonumber(opts.read_timeout or opts.timeout or 60000)
req.ssl = ssl and 1 or 0
req.ssl_verify = ssl_verify and 1 or 0
req.ssl_session_reuse = ssl_session_reuse and 1 or 0

local err = get_string_buf(ERR_BUF_SIZE)
local errlen = get_size_ptr()
Expand Down Expand Up @@ -543,7 +549,7 @@ end


-- connect{ scheme, host, port, pool, pool_size, ssl_verify, ssl_server_name,
-- ssl_trusted_certificate } or connect(host, port)
-- ssl_trusted_certificate, ssl_session_reuse } or connect(host, port)
function client.connect(self, opts, port_arg)
local r = get_request()
if not r then
Expand All @@ -552,6 +558,7 @@ function client.connect(self, opts, port_arg)

local host, port, scheme, pool, pool_size
local ssl_verify, ssl_server_name, ssl_trusted_certificate
local ssl_session_reuse

if type(opts) == "table" then
host = opts.host
Expand All @@ -562,6 +569,7 @@ function client.connect(self, opts, port_arg)
ssl_verify = opts.ssl_verify
ssl_server_name = opts.ssl_server_name
ssl_trusted_certificate = opts.ssl_trusted_certificate
ssl_session_reuse = opts.ssl_session_reuse

else
host = opts
Expand All @@ -587,6 +595,9 @@ function client.connect(self, opts, port_arg)
-- verify defaults on for https; pass ssl_verify = false to disable
ssl_verify = ssl and ssl_verify ~= false

-- resumption defaults on; pass ssl_session_reuse = false to disable
ssl_session_reuse = ssl and ssl_session_reuse ~= false

if ssl_server_name ~= nil
and (type(ssl_server_name) ~= "string" or ssl_server_name == "")
then
Expand Down Expand Up @@ -643,6 +654,7 @@ function client.connect(self, opts, port_arg)
cp.keepalive_idle_timeout = 0
cp.ssl = ssl and 1 or 0
cp.ssl_verify = ssl_verify and 1 or 0
cp.ssl_session_reuse = ssl_session_reuse and 1 or 0

local err, errlen = new_err_buf()
local rc = c_connect(r, self._op, cp, err, errlen)
Expand Down
25 changes: 25 additions & 0 deletions src/ngx_http_ffi_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#define NGX_HTTP_FFI_CLIENT_MAX_INTERIM 8
/* fallback size for a pool no caller sized */
#define NGX_HTTP_FFI_CLIENT_KEEPALIVE_POOL_SIZE 30
/* TLS sessions one worker keeps for resumption, evicted least recently used */
#define NGX_HTTP_FFI_CLIENT_SSL_SESSION_CACHE_SIZE 256


typedef struct {
Expand Down Expand Up @@ -47,6 +49,7 @@ typedef struct {
ngx_uint_t ssl_verify;
ngx_http_ffi_client_ffi_str_t ssl_server_name;
ngx_http_ffi_client_ffi_str_t ssl_trusted_certificate;
ngx_uint_t ssl_session_reuse;
} ngx_http_ffi_client_ffi_req_t;


Expand Down Expand Up @@ -77,6 +80,7 @@ typedef struct {
ngx_uint_t ssl_verify;
ngx_http_ffi_client_ffi_str_t ssl_server_name;
ngx_http_ffi_client_ffi_str_t ssl_trusted_certificate;
ngx_uint_t ssl_session_reuse;
} ngx_http_ffi_client_ffi_connect_t;


Expand Down Expand Up @@ -214,6 +218,10 @@ struct ngx_http_ffi_client_ssl_ctx_s {
unsigned verify:1;
ngx_ssl_t *ssl;
};

/* the worker's TLS session cache; its layout is private to the session file */
typedef struct ngx_http_ffi_client_ssl_session_cache_s
ngx_http_ffi_client_ssl_session_cache_t;
#endif


Expand All @@ -222,6 +230,8 @@ typedef struct {
ngx_rbtree_node_t sentinel;
#if (NGX_SSL)
ngx_http_ffi_client_ssl_ctx_t *ssl_ctxs;
ngx_http_ffi_client_ssl_session_cache_t
*ssl_sessions;
#endif
} ngx_http_ffi_client_main_conf_t;

Expand Down Expand Up @@ -302,6 +312,7 @@ struct ngx_http_ffi_client_op_s {
ngx_str_t ssl_trusted_certificate;
unsigned ssl:1;
unsigned ssl_verify:1;
unsigned ssl_session_reuse:1;
#endif
};

Expand Down Expand Up @@ -359,6 +370,20 @@ ngx_int_t ngx_http_ffi_client_response_eof(ngx_pool_t *pool,
ngx_uint_t ngx_http_ffi_client_has_token(ngx_str_t *value, const char *token,
size_t len);

#if (NGX_SSL)
/*
* TLS session resumption. The cache holds one session per TLS peer and lives
* for the worker; enable() arms an SSL_CTX to report new sessions, offer()
* hands the cached one to a fresh connection, and commit() publishes the
* session of a handshake this client accepted.
*/
ngx_int_t ngx_http_ffi_client_ssl_session_cache_enable(ngx_ssl_t *ssl,
ngx_log_t *log);
ngx_int_t ngx_http_ffi_client_ssl_session_offer(ngx_http_ffi_client_op_t *op,
ngx_connection_t *c);
void ngx_http_ffi_client_ssl_session_commit(ngx_connection_t *c);
#endif

ngx_int_t ngx_http_ffi_client_keepalive_bind(ngx_http_request_t *r,
ngx_http_ffi_client_op_t *op, ngx_http_ffi_client_ffi_req_t *req,
u_char *err, size_t *errlen);
Expand Down
20 changes: 20 additions & 0 deletions src/ngx_http_ffi_client_request.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ ngx_http_ffi_client_start(ngx_http_request_t *r,
#if (NGX_SSL)
op->ssl = 1;
op->ssl_verify = req->ssl_verify ? 1 : 0;
op->ssl_session_reuse = req->ssl_session_reuse ? 1 : 0;
if (req->ssl_server_name.len) {
if (req->ssl_server_name.data == NULL) {
return ngx_http_ffi_client_set_err(err, errlen,
Expand Down Expand Up @@ -1668,6 +1669,11 @@ ngx_http_ffi_client_ssl_ctx(ngx_http_ffi_client_main_conf_t *fmcf,
return NULL;
}

if (ngx_http_ffi_client_ssl_session_cache_enable(ssl, log) != NGX_OK) {
ngx_ssl_cleanup_ctx(ssl);
return NULL;
}

/* verify mode stays NONE so the handshake completes and we inspect the
* result ourselves. An unusable trust store is a hard init failure: don't
* cache a context that would fail every verify-on request. */
Expand Down Expand Up @@ -1865,6 +1871,15 @@ ngx_http_ffi_client_ssl_init_connection(ngx_http_ffi_client_op_t *op)
return NGX_ERROR;
}

/* resumption is an optimization, and what it fails back to is the full
* handshake this connection would have done anyway */
if (op->ssl_session_reuse
&& ngx_http_ffi_client_ssl_session_offer(op, c) != NGX_OK)
{
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"ffi client TLS session reuse unavailable");
}

op->state = NGX_HTTP_FFI_CLIENT_HANDSHAKING;

rc = ngx_ssl_handshake(c);
Expand Down Expand Up @@ -1925,6 +1940,10 @@ ngx_http_ffi_client_ssl_handshake_handler(ngx_connection_t *c)
}
}

/* the peer passed every check this caller asked for, so the session it
* established may now be offered to the next connection */
ngx_http_ffi_client_ssl_session_commit(c);

if (c->read->timer_set) {
ngx_del_timer(c->read);
}
Expand Down Expand Up @@ -2286,6 +2305,7 @@ ngx_http_ffi_client_ffi_connect(ngx_http_request_t *r,
#if (NGX_SSL)
op->ssl = 1;
op->ssl_verify = cp->ssl_verify ? 1 : 0;
op->ssl_session_reuse = cp->ssl_session_reuse ? 1 : 0;

if (cp->ssl_server_name.len) {
if (cp->ssl_server_name.data == NULL) {
Expand Down
Loading
Loading