Skip to content

Commit

Permalink
Support reading additional data from underlying BIO for each call to …
Browse files Browse the repository at this point in the history
…SSL_read:

* SSL_set_read_ahead
* SSL_CTX_set_read_ahead
* SSL_set_default_read_buffer_len
* SSL_CTX_set_default_read_buffer_len

By enabling read ahead the system may reduce the number of calls to the SSL read BIO
by reading more data than is specified in the TLS record header.
  • Loading branch information
andrewhop committed Apr 2, 2024
1 parent a1f73bc commit 4d01669
Show file tree
Hide file tree
Showing 12 changed files with 307 additions and 70 deletions.
60 changes: 50 additions & 10 deletions include/openssl/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -409,11 +409,16 @@ OPENSSL_EXPORT int SSL_pending(const SSL *ssl);
// for read, or if |ssl| has buffered data from the transport that has not yet
// been decrypted. If |ssl| has neither, this function returns zero.
//
// In TLS, BoringSSL does not implement read-ahead, so this function returns one
// if and only if |SSL_pending| would return a non-zero value. In DTLS, it is
// possible for this function to return one while |SSL_pending| returns zero.
// For example, |SSL_read| may read a datagram with two records, decrypt the
// first, and leave the second buffered for a subsequent call to |SSL_read|.
// If read-ahead has been enabled with |SSL_CTX_set_read_ahead| or
// |SSL_set_read_ahead|, the behavior of |SSL_pending| will change, it may return
// 1 and a call to |SSL_read| to return no data. This can happen when a partial
// record has been read but can not be decrypted without more data from the read
// BIO.
//
// In DTLS, it is possible for this function to return one while |SSL_pending|
// returns zero. For example, |SSL_read| may read a datagram with two records,
// decrypt the first, and leave the second buffered for a subsequent call to
// |SSL_read|.
//
// As a result, if this function returns one, the next call to |SSL_read| may
// still fail, read from the transport, or both. The buffered, undecrypted data
Expand Down Expand Up @@ -4652,7 +4657,7 @@ OPENSSL_EXPORT int SSL_CTX_set_max_send_fragment(SSL_CTX *ctx,
// SSL_set_max_send_fragment sets the maximum length, in bytes, of records sent
// by |ssl|. Beyond this length, handshake messages and application data will
// be split into multiple records. It returns one on success or zero on
// error.
// error. The minimum is 512, and the max is 16384.
OPENSSL_EXPORT int SSL_set_max_send_fragment(SSL *ssl,
size_t max_send_fragment);

Expand Down Expand Up @@ -5070,18 +5075,53 @@ OPENSSL_EXPORT int SSL_CTX_set_tmp_rsa(SSL_CTX *ctx, const RSA *rsa);
// SSL_set_tmp_rsa returns one.
OPENSSL_EXPORT int SSL_set_tmp_rsa(SSL *ssl, const RSA *rsa);

// SSL_CTX_get_read_ahead returns zero.
// SSL_CTX_get_read_ahead returns 1 if read ahead is enabled, and 0 otherwise.
OPENSSL_EXPORT int SSL_CTX_get_read_ahead(const SSL_CTX *ctx);

// SSL_CTX_set_read_ahead returns one.
// SSL_CTX_set_read_ahead enables or disables read ahead on |ctx|:
// if |yes| is 1 it enables read ahead and returns 1,
// if |yes| is 0 it disables read ahead and returns 1,
// if |yes| is any other value nothing is changed and 0 is returned.
//
// When read ahead is enabled all future reads will be up to the buffer size configured
// with |SSL_CTX_set_default_read_buffer_len|, the default buffer size is
// |SSL3_RT_MAX_PLAIN_LENGTH| + |SSL3_RT_MAX_ENCRYPTED_OVERHEAD| = 16704 bytes.
//
// Read ahead should only be enabled on non-blocking IO sources configured with |SSL_set_bio|.
// When read ahead is enabled AWS-LC will make reads for potentially more data than is
// avaliable in the BIO with the assumption a partial read will be returned. If
// a blocking BIO is used and never returns the read could get stuck forever.
OPENSSL_EXPORT int SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes);

// SSL_get_read_ahead returns zero.
// SSL_get_read_ahead returns 1 if read ahead is enabled or 0 if it is not.
OPENSSL_EXPORT int SSL_get_read_ahead(const SSL *ssl);

// SSL_set_read_ahead returns one.
// SSL_set_read_ahead enables or disables read ahead on |ssl|:
// if |yes| is 1 it enables read ahead and returns 1,
// if |yes| is 0 it disables read ahead and returns 1,
// if |yes| is any other value nothing is changed and 0 is returned.
//
// When read ahead is enabled all future reads will be for up to the buffer size configured
// with |SSL_CTX_set_default_read_buffer_len|. The default buffer size is
// |SSL3_RT_MAX_PLAIN_LENGTH| + |SSL3_RT_MAX_ENCRYPTED_OVERHEAD| = 16704 bytes
//
// Read ahead should only be enabled on non-blocking IO sources configured with |SSL_set_bio|,
// when read ahead is enabled AWS-LC will make reads for potentially more data than is
// available in the BIO.
OPENSSL_EXPORT int SSL_set_read_ahead(SSL *ssl, int yes);

// SSL_CTX_set_default_read_buffer_len sets the size of the buffer reads will use on
// |ctx| if read ahead has been enabled. 0 is the minimum and 65535 is the maximum.
// A |len| of 0 is the default behavior with read ahead turned off: each call to
// |SSL_read| reads the amount specified in the TLS Record Header.
OPENSSL_EXPORT void SSL_CTX_set_default_read_buffer_len(SSL_CTX *ctx, size_t len);

// SSL_CTX_set_default_read_buffer_len sets the size of the buffer reads will use on
// |ssl| if read ahead has been enabled. 0 is the minimum and 65535 is the maximum.
// A |len| of 0 is the default behavior with read ahead turned off: each call to
// |SSL_read| reads the amount specified in the TLS Record Header.
OPENSSL_EXPORT void SSL_set_default_read_buffer_len(SSL *ssl, size_t len);

// SSL_set_state does nothing.
OPENSSL_EXPORT void SSL_set_state(SSL *ssl, int state);

Expand Down
18 changes: 18 additions & 0 deletions ssl/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,8 @@ void ssl_do_msg_callback(const SSL *ssl, int is_write, int content_type,

// Transport buffers.

#define SSLBUFFER_READ_AHEAD_MIN_CAPACITY 512
#define SSLBUFFER_MAX_CAPACITY UINT16_MAX
class SSLBuffer {
public:
SSLBuffer() {}
Expand Down Expand Up @@ -3658,6 +3660,7 @@ struct ssl_method_st {
const bssl::SSL_X509_METHOD *x509_method;
};

#define MIN_SAFE_FRAGMENT_SIZE 512
struct ssl_ctx_st {
explicit ssl_ctx_st(const SSL_METHOD *ssl_method);
ssl_ctx_st(const ssl_ctx_st &) = delete;
Expand All @@ -3684,6 +3687,9 @@ struct ssl_ctx_st {
/// in case the client makes several connections before getting a renewal.
uint8_t num_tickets = 2;

// read_ahead_buffer_size is the amount of data to read if |enable_read_ahead| is true
size_t read_ahead_buffer_size = SSL3_RT_MAX_PLAIN_LENGTH + SSL3_RT_MAX_ENCRYPTED_OVERHEAD;

// quic_method is the method table corresponding to the QUIC hooks.
const SSL_QUIC_METHOD *quic_method = nullptr;

Expand Down Expand Up @@ -3981,6 +3987,11 @@ struct ssl_ctx_st {
// If enable_early_data is true, early data can be sent and accepted.
bool enable_early_data : 1;

// enable_read_ahead indicates whether the |SSL_CTX| is configured to read as much
// as will fit in the SSLBuffer from the BIO, or just enough to read the record
// header and then the length of the body
bool enable_read_ahead : 1;

// aes_hw_override if set indicates we should override checking for AES
// hardware support, and use the value in aes_hw_override_value instead.
bool aes_hw_override : 1;
Expand Down Expand Up @@ -4030,6 +4041,8 @@ struct ssl_st {

uint16_t max_send_fragment = 0;

size_t read_ahead_buffer_size = SSL3_RT_MAX_PLAIN_LENGTH + SSL3_RT_MAX_ENCRYPTED_OVERHEAD;

// There are 2 BIO's even though they are normally both the same. This is so
// data can be read and written to different handlers

Expand Down Expand Up @@ -4101,6 +4114,11 @@ struct ssl_st {

// If enable_early_data is true, early data can be sent and accepted.
bool enable_early_data : 1;

// enable_read_ahead indicates whether the |SSL_CTX| is configured to read as much
// as will fit in the SSLBuffer from the BIO, or just enough to read the record
// header and then the length of the body
bool enable_read_ahead : 1;
};

struct ssl_session_st {
Expand Down
31 changes: 23 additions & 8 deletions ssl/ssl_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ BSSL_NAMESPACE_BEGIN

// BIO uses int instead of size_t. No lengths will exceed uint16_t, so this will
// not overflow.
static_assert(0xffff <= INT_MAX, "uint16_t does not fit in int");
static_assert(SSLBUFFER_MAX_CAPACITY <= INT_MAX, "uint16_t does not fit in int");

static_assert((SSL3_ALIGN_PAYLOAD & (SSL3_ALIGN_PAYLOAD - 1)) == 0,
"SSL3_ALIGN_PAYLOAD must be a power of 2");
Expand All @@ -49,7 +49,7 @@ void SSLBuffer::Clear() {
}

bool SSLBuffer::EnsureCap(size_t header_len, size_t new_cap) {
if (new_cap > 0xffff) {
if (new_cap > SSLBUFFER_MAX_CAPACITY) {
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
return false;
}
Expand Down Expand Up @@ -235,8 +235,16 @@ static int tls_read_buffer_extend_to(SSL *ssl, size_t len) {
while (buf->size() < len) {
// The amount of data to read is bounded by |buf->cap|, which must fit in an
// int.

// If enable_read_ahead we want to attempt to fill the entire buffer, but
// the while loop will bail once we have at least the requested len amount
// of data. If not enable_read_ahead, only read as much to get to len bytes,
// at this point we know len is less than the overall size of the buffer.
size_t read_amount = ssl->enable_read_ahead ? buf->cap() - buf->size() :
len - buf->size();
assert(read_amount <= buf->cap() - buf->size());
int ret = BIO_read(ssl->rbio.get(), buf->data() + buf->size(),
static_cast<int>(len - buf->size()));
static_cast<int>(read_amount));
if (ret <= 0) {
ssl->s3->rwstate = SSL_ERROR_WANT_READ;
return ret;
Expand All @@ -250,17 +258,24 @@ static int tls_read_buffer_extend_to(SSL *ssl, size_t len) {
int ssl_read_buffer_extend_to(SSL *ssl, size_t len) {
// |ssl_read_buffer_extend_to| implicitly discards any consumed data.
ssl->s3->read_buffer.DiscardConsumed();

size_t buffer_size = len;
if (SSL_is_dtls(ssl)) {
static_assert(
DTLS1_RT_HEADER_LENGTH + SSL3_RT_MAX_ENCRYPTED_LENGTH <= 0xffff,
DTLS1_RT_HEADER_LENGTH + SSL3_RT_MAX_ENCRYPTED_LENGTH <= SSLBUFFER_MAX_CAPACITY,
"DTLS read buffer is too large");

// The |len| parameter is ignored in DTLS.
len = DTLS1_RT_HEADER_LENGTH + SSL3_RT_MAX_ENCRYPTED_LENGTH;
buffer_size = len;
} else {
if (ssl->ctx.get()->enable_read_ahead) {
// If we're reading ahead allocate read_ahead_buffer size or the requested
// len whichever is bigger
buffer_size = std::max(len, ssl->read_ahead_buffer_size);
}
}

if (!ssl->s3->read_buffer.EnsureCap(ssl_record_prefix_len(ssl), len)) {
if (!ssl->s3->read_buffer.EnsureCap(ssl_record_prefix_len(ssl), buffer_size)) {
return -1;
}

Expand Down Expand Up @@ -330,12 +345,12 @@ int ssl_handle_open_record(SSL *ssl, bool *out_retry, ssl_open_record_t ret,
static_assert(SSL3_RT_HEADER_LENGTH * 2 +
SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD * 2 +
SSL3_RT_MAX_PLAIN_LENGTH <=
0xffff,
SSLBUFFER_MAX_CAPACITY,
"maximum TLS write buffer is too large");

static_assert(DTLS1_RT_HEADER_LENGTH + SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD +
SSL3_RT_MAX_PLAIN_LENGTH <=
0xffff,
SSLBUFFER_MAX_CAPACITY,
"maximum DTLS write buffer is too large");

static int tls_write_buffer_flush(SSL *ssl) {
Expand Down
67 changes: 58 additions & 9 deletions ssl/ssl_lib.cc
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ ssl_ctx_st::ssl_ctx_st(const SSL_METHOD *ssl_method)
false_start_allowed_without_alpn(false),
handoff(false),
enable_early_data(false),
enable_read_ahead(false),
aes_hw_override(false),
aes_hw_override_value(false),
conf_max_version_use_default(true),
Expand Down Expand Up @@ -599,6 +600,7 @@ SSL_CTX *SSL_CTX_new(const SSL_METHOD *method) {
// defer to the protocol method's default min/max values in that case.
ret->conf_max_version_use_default = true;
ret->conf_min_version_use_default = true;
ret->enable_read_ahead = false;

return ret.release();
}
Expand All @@ -620,6 +622,7 @@ void SSL_CTX_free(SSL_CTX *ctx) {
ssl_st::ssl_st(SSL_CTX *ctx_arg)
: method(ctx_arg->method),
max_send_fragment(ctx_arg->max_send_fragment),
read_ahead_buffer_size(ctx_arg->read_ahead_buffer_size),
msg_callback(ctx_arg->msg_callback),
msg_callback_arg(ctx_arg->msg_callback_arg),
ctx(UpRef(ctx_arg)),
Expand All @@ -629,7 +632,8 @@ ssl_st::ssl_st(SSL_CTX *ctx_arg)
max_cert_list(ctx->max_cert_list),
server(false),
quiet_shutdown(ctx->quiet_shutdown),
enable_early_data(ctx->enable_early_data) {
enable_early_data(ctx->enable_early_data),
enable_read_ahead(ctx->enable_read_ahead) {
CRYPTO_new_ex_data(&ex_data);
}

Expand Down Expand Up @@ -1759,13 +1763,58 @@ int SSL_get_extms_support(const SSL *ssl) {
return 0;
}

int SSL_CTX_get_read_ahead(const SSL_CTX *ctx) { return 0; }
int SSL_CTX_get_read_ahead(const SSL_CTX *ctx) {
return ctx->enable_read_ahead;
}

int SSL_get_read_ahead(const SSL *ssl) {
return ssl->enable_read_ahead;
}

int SSL_get_read_ahead(const SSL *ssl) { return 0; }
void SSL_CTX_set_default_read_buffer_len(SSL_CTX *ctx, size_t len) {
// SSLBUFFER_MAX_CAPACITY(0xffff) is the maximum SSLBuffer supports reading at one time
if (len > SSLBUFFER_MAX_CAPACITY) {
len = SSLBUFFER_MAX_CAPACITY;
}
// Setting a very small read buffer won't cause issue because the SSLBuffer
// will always read at least the amount of data specified in the TLS record
// header
ctx->read_ahead_buffer_size = len;
}

int SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes) { return 1; }
void SSL_set_default_read_buffer_len(SSL *ssl, size_t len) {
// SSLBUFFER_MAX_CAPACITY(0xffff) is the maximum SSLBuffer supports reading at one time
if (len > SSLBUFFER_MAX_CAPACITY) {
len = SSLBUFFER_MAX_CAPACITY;
}
// Setting a very small read buffer won't cause issue because the SSLBuffer
// will always read at least the amount of data specified in the TLS record
// header
ssl->read_ahead_buffer_size = len;
}

int SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes) {
if (yes == 0) {
ctx->enable_read_ahead = 0;
return 1;
} else if (yes == 1) {
ctx->enable_read_ahead = 1;
return 1;
} else {
return 0;
}
}

int SSL_set_read_ahead(SSL *ssl, int yes) { return 1; }
int SSL_set_read_ahead(SSL *ssl, int yes) {
if (yes == 0) {
ssl->enable_read_ahead = 0;
return 1;
} else if (yes == 1) {
ssl->enable_read_ahead = 1;
return 1;
} else {
return 0;
}}

int SSL_pending(const SSL *ssl) {
return static_cast<int>(ssl->s3->pending_app_data.size());
Expand Down Expand Up @@ -1876,8 +1925,8 @@ void SSL_set_max_cert_list(SSL *ssl, size_t max_cert_list) {
}

int SSL_CTX_set_max_send_fragment(SSL_CTX *ctx, size_t max_send_fragment) {
if (max_send_fragment < 512) {
max_send_fragment = 512;
if (max_send_fragment < MIN_SAFE_FRAGMENT_SIZE) {
max_send_fragment = MIN_SAFE_FRAGMENT_SIZE;
}
if (max_send_fragment > SSL3_RT_MAX_PLAIN_LENGTH) {
max_send_fragment = SSL3_RT_MAX_PLAIN_LENGTH;
Expand All @@ -1888,8 +1937,8 @@ int SSL_CTX_set_max_send_fragment(SSL_CTX *ctx, size_t max_send_fragment) {
}

int SSL_set_max_send_fragment(SSL *ssl, size_t max_send_fragment) {
if (max_send_fragment < 512) {
max_send_fragment = 512;
if (max_send_fragment < MIN_SAFE_FRAGMENT_SIZE) {
max_send_fragment = MIN_SAFE_FRAGMENT_SIZE;
}
if (max_send_fragment > SSL3_RT_MAX_PLAIN_LENGTH) {
max_send_fragment = SSL3_RT_MAX_PLAIN_LENGTH;
Expand Down
Loading

0 comments on commit 4d01669

Please sign in to comment.