From 11ad5d15c487ecc0a37f9747bb4bfa5bb96893c1 Mon Sep 17 00:00:00 2001 From: John Baldwin Date: Thu, 22 Aug 2019 12:18:32 -0700 Subject: [PATCH] Add support for using SSL_sendfile from OpenSSL. This uses kernel TLS on systems supported by OpenSSL to send files via sendfile() over TLS connections. --- auto/lib/openssl/conf | 8 ++ src/event/ngx_event_openssl.c | 172 ++++++++++++++++++++++++++++++++++ src/event/ngx_event_openssl.h | 7 ++ src/http/ngx_http_request.c | 14 ++- src/http/ngx_http_upstream.c | 5 + 5 files changed, 203 insertions(+), 3 deletions(-) diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index 4fb52df7fe..c4772248ae 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -123,6 +123,14 @@ else CORE_INCS="$CORE_INCS $ngx_feature_path" CORE_LIBS="$CORE_LIBS $ngx_feature_libs" OPENSSL=YES + + ngx_feature="SSL_sendfile()" + ngx_feature_name="NGX_SSL_SENDFILE" + ngx_feature_run=no + ngx_feature_test="SSL *ssl; + (void)BIO_get_ktls_send(SSL_get_wbio(ssl)); + SSL_sendfile(ssl, -1, 0, 0, 0);" + . auto/feature fi fi diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 93a6ae46ea..04759827fc 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -52,6 +52,10 @@ static void ngx_ssl_shutdown_handler(ngx_event_t *ev); static void ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, char *text); static void ngx_ssl_clear_error(ngx_log_t *log); +#if (NGX_SSL_SENDFILE) +static ssize_t ngx_ssl_sendfile(ngx_connection_t *c, int fd, off_t off, + size_t size, int flags); +#endif static ngx_int_t ngx_ssl_session_id_context(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, ngx_array_t *certificates); @@ -1712,7 +1716,11 @@ ngx_ssl_handshake(ngx_connection_t *c) c->recv = ngx_ssl_recv; c->send = ngx_ssl_write; c->recv_chain = ngx_ssl_recv_chain; +#if (NGX_SSL_SENDFILE) + c->send_chain = ngx_ssl_sendfile_chain; +#else c->send_chain = ngx_ssl_send_chain; +#endif #ifndef SSL_OP_NO_RENEGOTIATION #if OPENSSL_VERSION_NUMBER < 0x10100000L @@ -1741,6 +1749,13 @@ ngx_ssl_handshake(ngx_connection_t *c) c->ssl->handshaked = 1; +#if (NGX_SSL_SENDFILE) + c->ssl->can_use_sendfile = !!BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "BIO_get_ktls_send: %d", c->ssl->can_use_sendfile); + c->sendfile = c->ssl->can_use_sendfile ? 1 : 0; +#endif + return NGX_OK; } @@ -2609,6 +2624,163 @@ ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) return in; } +#if (NGX_SSL_SENDFILE) +ngx_chain_t * +ngx_ssl_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + int can_use_sendfile; + ssize_t n; + + can_use_sendfile = BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)); + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "Sending chain %p can_use_sendfile:%d c->sendfile:%d " \ + "c->ssl->buffer:%d limit:%O", + in, can_use_sendfile, c->sendfile, c->ssl->buffer, limit); + + if (! (can_use_sendfile && c->sendfile) || c->ssl->buffer) { + return ngx_ssl_send_chain(c, in, limit); + } + + /* the maximum limit size is the maximum int32_t value - the page size */ + if (limit == 0 || limit > (off_t) (NGX_MAX_INT32_VALUE - ngx_pagesize)) { + limit = NGX_MAX_INT32_VALUE - ngx_pagesize; + } + + while (in) { + if (ngx_buf_special(in->buf)) { + in = in->next; + continue; + } + + if (in->buf->in_file) { + ngx_chain_t *cl; + int sendfile_flags; + off_t sendfile_size; + + cl = in; +#ifdef __FreeBSD__ + sendfile_flags = /* in->buf->sendfile_flags |*/ SF_NODISKIO; +#else + sendfile_flags = in->buf->sendfile_flags; +#endif + sendfile_size = ngx_chain_coalesce_file(&cl, limit); + + n = ngx_ssl_sendfile(c, in->buf->file->fd, in->buf->file_pos, + sendfile_size, sendfile_flags); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_ssl_sendfile returns:%z", n); + } else { + n = ngx_ssl_write(c, in->buf->pos, in->buf->last - in->buf->pos); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_ssl_write returns:%z", n); + } + + if (n == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + if (n == NGX_AGAIN) { + return in; + } + if (n == NGX_BUSY) { + c->busy_count = 1; + c->write->delayed = 1; + ngx_add_timer(c->write, 10); + return in; + } + + in = ngx_chain_update_sent(in, (off_t) n); + } + + return in; +} + +static ssize_t +ngx_ssl_sendfile(ngx_connection_t *c, int fd, off_t off, size_t size, int flags) +{ + int n, sslerr; + ngx_err_t err; + + ngx_ssl_clear_error(c->log); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL to sendfile: %uz at %O with %Xd", size, off, flags); + + n = SSL_sendfile(c->ssl->connection, fd, off, size, flags); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_sendfile: %d", n); + + if (n > 0) { + + if (c->ssl->saved_read_handler) { + + c->read->handler = c->ssl->saved_read_handler; + c->ssl->saved_read_handler = NULL; + c->read->ready = 1; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(c->read, &ngx_posted_events); + } + + c->sent += n; + + return n; + } + + sslerr = SSL_get_error(c->ssl->connection, n); + +#ifdef __FreeBSD__ + if (sslerr == SSL_ERROR_WANT_WRITE && ngx_errno == EBUSY) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "bioerr=NGX_EBUSY, sslerr=%d", sslerr); + return NGX_BUSY; + } +#endif + + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); + + if (sslerr == SSL_ERROR_WANT_WRITE) { + c->write->ready = 0; + return NGX_AGAIN; + } + + if (sslerr == SSL_ERROR_WANT_READ) { + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "peer started SSL renegotiation"); + + c->read->ready = 0; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + /* + * we do not set the timer because there is already + * the write event timer + */ + + if (c->ssl->saved_read_handler == NULL) { + c->ssl->saved_read_handler = c->read->handler; + c->read->handler = ngx_ssl_read_handler; + } + + return NGX_AGAIN; + } + + c->ssl->no_wait_shutdown = 1; + c->ssl->no_send_shutdown = 1; + c->write->error = 1; + + ngx_ssl_connection_error(c, sslerr, err, "SSL_sendfile() failed"); + + return NGX_ERROR; +} +#endif ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size) diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 329760d093..233b7f20c8 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -106,6 +106,9 @@ struct ngx_ssl_connection_s { unsigned in_ocsp:1; unsigned early_preread:1; unsigned write_blocked:1; +#if (NGX_SSL_SENDFILE) + unsigned can_use_sendfile:1; +#endif }; @@ -289,6 +292,10 @@ ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size); ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit); ngx_chain_t *ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); +#if (NGX_SSL_SENDFILE) +ngx_chain_t *ngx_ssl_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, + off_t limit); +#endif void ngx_ssl_free_buffer(ngx_connection_t *c); ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c); void ngx_cdecl ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 68d81e9320..e4a922a83a 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -608,7 +608,10 @@ ngx_http_alloc_request(ngx_connection_t *c) #if (NGX_HTTP_SSL) if (c->ssl) { - r->main_filter_need_in_memory = 1; +#if (NGX_SSL_SENDFILE) + if (c->ssl->can_use_sendfile == 0) +#endif + r->main_filter_need_in_memory = 1; } #endif @@ -747,8 +750,13 @@ ngx_http_ssl_handshake(ngx_event_t *rev) sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER) - != NGX_OK) + if (ngx_ssl_create_connection(&sscf->ssl, c, +#if (NGX_SSL_SENDFILE) + 0 +#else + NGX_SSL_BUFFER +#endif + ) != NGX_OK) { ngx_http_close_connection(c); return; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index 9cbb5a3b0c..f93f2ae244 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -1715,6 +1715,11 @@ ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r, return; } +#if (NGX_SSL_SENDFILE) + c->sendfile = 0; + u->output.sendfile = 0; +#endif + ngx_http_upstream_ssl_handshake(r, u, c); }