From 7c3a7561b536264b282f604efc959edad18807d7 Mon Sep 17 00:00:00 2001 From: Boris Pismenny Date: Sat, 13 Apr 2019 17:20:35 +0300 Subject: [PATCH] ssl: Add SSL_sendfile This commit adds the SSL_sendfile call, which allows KTLS sockets to transmit file using zero-copy semantics. Signed-off-by: Boris Pismenny Reviewed-by: Paul Dale Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/8727) --- crypto/err/openssl.txt | 1 + doc/man3/SSL_write.pod | 31 ++++++++++- include/openssl/err.h | 1 + include/openssl/ssl.h | 2 + include/openssl/sslerr.h | 1 + ssl/ssl_err.c | 1 + ssl/ssl_lib.c | 67 ++++++++++++++++++++++++ test/sslapitest.c | 109 ++++++++++++++++++++++++++++++++++++++- util/libssl.num | 1 + 9 files changed, 211 insertions(+), 3 deletions(-) diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index 8ad85f5025..14a7e3b5b9 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -1437,6 +1437,7 @@ SSL_F_SSL_RENEGOTIATE:516:SSL_renegotiate SSL_F_SSL_RENEGOTIATE_ABBREVIATED:546:SSL_renegotiate_abbreviated SSL_F_SSL_SCAN_CLIENTHELLO_TLSEXT:320:* SSL_F_SSL_SCAN_SERVERHELLO_TLSEXT:321:* +SSL_F_SSL_SENDFILE:639:SSL_sendfile SSL_F_SSL_SESSION_DUP:348:ssl_session_dup SSL_F_SSL_SESSION_NEW:189:SSL_SESSION_new SSL_F_SSL_SESSION_PRINT_FP:190:SSL_SESSION_print_fp diff --git a/doc/man3/SSL_write.pod b/doc/man3/SSL_write.pod index a73bc062ce..b18c215cac 100644 --- a/doc/man3/SSL_write.pod +++ b/doc/man3/SSL_write.pod @@ -2,12 +2,13 @@ =head1 NAME -SSL_write_ex, SSL_write - write bytes to a TLS/SSL connection +SSL_write_ex, SSL_write, SSL_sendfile - write bytes to a TLS/SSL connection =head1 SYNOPSIS #include + ossl_ssize_t SSL_sendfile(SSL *s, int fd, off_t offset, size_t size, int flags); int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written); int SSL_write(SSL *ssl, const void *buf, int num); @@ -17,6 +18,14 @@ SSL_write_ex() and SSL_write() write B bytes from the buffer B into the specified B connection. On success SSL_write_ex() will store the number of bytes written in B<*written>. +SSL_sendfile() writes B bytes from offset B in the file +descriptor B to the specified SSL connection B. This function provides +efficient zero-copy semantics. SSL_sendfile() is available only when +Kernel TLS is enabled, which can be checked by calling BIO_get_ktls_send(). +It is provided here to allow users to maintain the same interface. +The meaning of B is platform dependent. +Currently, under Linux it is ignored. + =head1 NOTES In the paragraphs below a "write function" is defined as one of either @@ -104,17 +113,35 @@ You should instead call SSL_get_error() to find out if it's retryable. =back +For SSL_sendfile(), the following return values can occur: + +=over 4 + +=item Z<>>= 0 + +The write operation was successful, the return value is the number +of bytes of the file written to the TLS/SSL connection. + +=item E 0 + +The write operation was not successful, because either the connection was +closed, an error occured or action must be taken by the calling process. +Call SSL_get_error() with the return value to find out the reason. + +=back + =head1 SEE ALSO L, L, L L, L, L, L -L, +L, L, L, L =head1 HISTORY The SSL_write_ex() function was added in OpenSSL 1.1.1. +The SSL_sendfile() function was added in OpenSSL 3.0.0. =head1 COPYRIGHT diff --git a/include/openssl/err.h b/include/openssl/err.h index 8fcdfb4b7a..7398029bee 100644 --- a/include/openssl/err.h +++ b/include/openssl/err.h @@ -177,6 +177,7 @@ typedef struct err_state_st { # define SYS_F_STAT 22 # define SYS_F_FCNTL 23 # define SYS_F_FSTAT 24 +# define SYS_F_SENDFILE 25 /* reasons */ # define ERR_R_SYS_LIB ERR_LIB_SYS/* 2 */ diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index f4b17f1beb..60712d069e 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -1848,6 +1848,8 @@ __owur int SSL_read_early_data(SSL *s, void *buf, size_t num, size_t *readbytes); __owur int SSL_peek(SSL *ssl, void *buf, int num); __owur int SSL_peek_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); +__owur ossl_ssize_t SSL_sendfile(SSL *s, int fd, off_t offset, size_t size, + int flags); __owur int SSL_write(SSL *ssl, const void *buf, int num); __owur int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written); __owur int SSL_write_early_data(SSL *s, const void *buf, size_t num, diff --git a/include/openssl/sslerr.h b/include/openssl/sslerr.h index 7f776f97f7..385fda37a4 100644 --- a/include/openssl/sslerr.h +++ b/include/openssl/sslerr.h @@ -217,6 +217,7 @@ int ERR_load_SSL_strings(void); # define SSL_F_SSL_RENEGOTIATE_ABBREVIATED 546 # define SSL_F_SSL_SCAN_CLIENTHELLO_TLSEXT 320 # define SSL_F_SSL_SCAN_SERVERHELLO_TLSEXT 321 +# define SSL_F_SSL_SENDFILE 639 # define SSL_F_SSL_SESSION_DUP 348 # define SSL_F_SSL_SESSION_NEW 189 # define SSL_F_SSL_SESSION_PRINT_FP 190 diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index afe1b58214..daeee1ecc4 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c @@ -311,6 +311,7 @@ static const ERR_STRING_DATA SSL_str_functs[] = { "SSL_renegotiate_abbreviated"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_SCAN_CLIENTHELLO_TLSEXT, 0), ""}, {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_SCAN_SERVERHELLO_TLSEXT, 0), ""}, + {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_SENDFILE, 0), "SSL_sendfile"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_SESSION_DUP, 0), "ssl_session_dup"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_SESSION_NEW, 0), "SSL_SESSION_new"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_SESSION_PRINT_FP, 0), diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 89a410057b..910f82be43 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -11,6 +11,7 @@ #include #include "ssl_locl.h" +#include "e_os.h" #include #include #include @@ -2008,6 +2009,72 @@ int ssl_write_internal(SSL *s, const void *buf, size_t num, size_t *written) } } +ossl_ssize_t SSL_sendfile(SSL *s, int fd, off_t offset, size_t size, int flags) +{ + ossl_ssize_t ret; + + if (s->handshake_func == NULL) { + SSLerr(SSL_F_SSL_SENDFILE, SSL_R_UNINITIALIZED); + return -1; + } + + if (s->shutdown & SSL_SENT_SHUTDOWN) { + s->rwstate = SSL_NOTHING; + SSLerr(SSL_F_SSL_SENDFILE, SSL_R_PROTOCOL_IS_SHUTDOWN); + return -1; + } + + if (!BIO_get_ktls_send(s->wbio)) { + SSLerr(SSL_F_SSL_SENDFILE, SSL_R_UNINITIALIZED); + return -1; + } + + /* If we have an alert to send, lets send it */ + if (s->s3.alert_dispatch) { + ret = (ossl_ssize_t)s->method->ssl_dispatch_alert(s); + if (ret <= 0) { + /* SSLfatal() already called if appropriate */ + return ret; + } + /* if it went, fall through and send more stuff */ + } + + s->rwstate = SSL_WRITING; + if (BIO_flush(s->wbio) <= 0) { + if (!BIO_should_retry(s->wbio)) { + s->rwstate = SSL_NOTHING; + } else { +#ifdef EAGAIN + set_sys_error(EAGAIN); +#endif + } + return -1; + } + +#ifndef OPENSSL_NO_KTLS + ret = ktls_sendfile(SSL_get_wfd(s), fd, offset, size, flags); +#else + ret = -1; +#endif + if (ret < 0) { +#if defined(EAGAIN) && defined(EINTR) && defined(EBUSY) + if ((get_last_sys_error() == EAGAIN) || + (get_last_sys_error() == EINTR) || + (get_last_sys_error() == EBUSY)) + BIO_set_retry_write(s->wbio); + else +#endif +#ifdef OPENSSL_NO_KTLS + SYSerr(SYS_F_SENDFILE, get_last_sys_error()); +#else + SSLerr(SSL_F_SSL_SENDFILE, SSL_R_UNINITIALIZED); +#endif + return ret; + } + s->rwstate = SSL_NOTHING; + return ret; +} + int SSL_write(SSL *s, const void *buf, int num) { int ret; diff --git a/test/sslapitest.c b/test/sslapitest.c index 7ca8c7592b..3504eea63e 100644 --- a/test/sslapitest.c +++ b/test/sslapitest.c @@ -7,6 +7,7 @@ * https://www.openssl.org/source/license.html */ +#include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include "ssltestlib.h" #include "testutil.h" @@ -918,6 +920,111 @@ end: return testresult; } +#define SENDFILE_SZ (16 * 4096) +#define SENDFILE_CHUNK (4 * 4096) +#define min(a,b) ((a) > (b) ? (b) : (a)) + +static int test_ktls_sendfile(void) +{ + SSL_CTX *cctx = NULL, *sctx = NULL; + SSL *clientssl = NULL, *serverssl = NULL; + unsigned char *buf, *buf_dst; + BIO *out = NULL, *in = NULL; + int cfd, sfd, ffd, err; + ssize_t chunk_size = 0; + off_t chunk_off = 0; + int testresult = 0; + FILE *ffdp; + + buf = OPENSSL_zalloc(SENDFILE_SZ); + buf_dst = OPENSSL_zalloc(SENDFILE_SZ); + if (!TEST_ptr(buf) || !TEST_ptr(buf_dst) + || !TEST_true(create_test_sockets(&cfd, &sfd))) + goto end; + + /* Skip this test if the platform does not support ktls */ + if (!ktls_chk_platform(sfd)) { + testresult = 1; + goto end; + } + + /* Create a session based on SHA-256 */ + if (!TEST_true(create_ssl_ctx_pair(TLS_server_method(), + TLS_client_method(), + TLS1_2_VERSION, TLS1_2_VERSION, + &sctx, &cctx, cert, privkey)) + || !TEST_true(SSL_CTX_set_cipher_list(cctx, + "AES128-GCM-SHA256")) + || !TEST_true(create_ssl_objects2(sctx, cctx, &serverssl, + &clientssl, sfd, cfd))) + goto end; + + if (!TEST_true(create_ssl_connection(serverssl, clientssl, + SSL_ERROR_NONE)) + || !TEST_true(BIO_get_ktls_send(serverssl->wbio))) + goto end; + + RAND_bytes(buf, SENDFILE_SZ); + out = BIO_new_file(tmpfilename, "wb"); + if (!TEST_ptr(out)) + goto end; + + if (BIO_write(out, buf, SENDFILE_SZ) != SENDFILE_SZ) + goto end; + + BIO_free(out); + out = NULL; + in = BIO_new_file(tmpfilename, "rb"); + BIO_get_fp(in, &ffdp); + ffd = fileno(ffdp); + + while (chunk_off < SENDFILE_SZ) { + chunk_size = min(SENDFILE_CHUNK, SENDFILE_SZ - chunk_off); + while ((err = SSL_sendfile(serverssl, + ffd, + chunk_off, + chunk_size, + 0)) != chunk_size) { + if (SSL_get_error(serverssl, err) != SSL_ERROR_WANT_WRITE) + goto end; + } + while ((err = SSL_read(clientssl, + buf_dst + chunk_off, + chunk_size)) != chunk_size) { + if (SSL_get_error(clientssl, err) != SSL_ERROR_WANT_READ) + goto end; + } + + /* verify the payload */ + if (!TEST_mem_eq(buf_dst + chunk_off, + chunk_size, + buf + chunk_off, + chunk_size)) + goto end; + + chunk_off += chunk_size; + } + + testresult = 1; +end: + if (clientssl) { + SSL_shutdown(clientssl); + SSL_free(clientssl); + } + if (serverssl) { + SSL_shutdown(serverssl); + SSL_free(serverssl); + } + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + serverssl = clientssl = NULL; + BIO_free(out); + BIO_free(in); + OPENSSL_free(buf); + OPENSSL_free(buf_dst); + return testresult; +} + static int test_ktls_no_txrx_client_no_txrx_server(void) { return execute_test_ktls(0, 0, 0, 0); @@ -997,7 +1104,6 @@ static int test_ktls_client_server(void) { return execute_test_ktls(1, 1, 1, 1); } - #endif static int test_large_message_tls(void) @@ -6280,6 +6386,7 @@ int setup_tests(void) ADD_TEST(test_ktls_no_rx_client_server); ADD_TEST(test_ktls_no_tx_client_server); ADD_TEST(test_ktls_client_server); + ADD_TEST(test_ktls_sendfile); #endif ADD_TEST(test_large_message_tls); ADD_TEST(test_large_message_tls_read_ahead); diff --git a/util/libssl.num b/util/libssl.num index d59ccf9974..c34200fb5d 100644 --- a/util/libssl.num +++ b/util/libssl.num @@ -503,3 +503,4 @@ SSL_CTX_set_async_callback_arg 503 3_0_0 EXIST::FUNCTION: SSL_set_async_callback 504 3_0_0 EXIST::FUNCTION: SSL_set_async_callback_arg 505 3_0_0 EXIST::FUNCTION: SSL_get_async_status 506 3_0_0 EXIST::FUNCTION: +SSL_sendfile 507 3_0_0 EXIST::FUNCTION: -- 2.34.1