Merge pull request #275 from CopernicaMarketingSoftware/ssl-eliminate-copy
Don't buffer SSL unless necessary
This commit is contained in:
commit
964398292a
|
|
@ -310,6 +310,23 @@ int SSL_use_certificate_file(SSL *ssl, const char *file, int type)
|
|||
return func(ssl, file, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Control the SSL context
|
||||
* @param ctx
|
||||
* @param cmd
|
||||
* @param larg
|
||||
* @param parg
|
||||
* @return long
|
||||
*/
|
||||
long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg)
|
||||
{
|
||||
// create a function
|
||||
static Function<decltype(::SSL_CTX_ctrl)> func(handle, "SSL_CTX_ctrl");
|
||||
|
||||
// call the openssl function
|
||||
return func(ctx, cmd, larg, parg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the SSL error queue
|
||||
* @return void
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ void SSL_set_connect_state(SSL *ssl);
|
|||
void SSL_CTX_free(SSL_CTX *ctx);
|
||||
void SSL_free(SSL *ssl);
|
||||
long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg);
|
||||
long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg);
|
||||
void ERR_clear_error(void);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -56,7 +56,8 @@ private:
|
|||
enum State {
|
||||
state_idle,
|
||||
state_sending,
|
||||
state_receiving
|
||||
state_receiving,
|
||||
state_error
|
||||
} _state;
|
||||
|
||||
/**
|
||||
|
|
@ -101,13 +102,35 @@ private:
|
|||
}
|
||||
|
||||
/**
|
||||
* Method to repeat the previous call\
|
||||
* Method to repeat the previous call
|
||||
* @param monitor monitor to check if connection object still exists
|
||||
* @param state the state that we were in
|
||||
* @param result result of an earlier SSL_get_error call
|
||||
* @return TcpState*
|
||||
*/
|
||||
TcpState *repeat(const Monitor &monitor, enum State state, int error)
|
||||
{
|
||||
// if we are able to repeat the call, we are still in the correct state
|
||||
if (repeat(state, error))
|
||||
{
|
||||
// if the socket was closed in the meantime and we are not sending anything any more, we should initialize the shutdown sequence
|
||||
if (_closed && _state == state_idle) return new SslShutdown(this, std::move(_ssl));
|
||||
|
||||
// otherwise, we just continue as we were, since the calls should be repeated in the future
|
||||
else return this;
|
||||
}
|
||||
|
||||
// if the monitor is still valid, we should tear down the connection because we are unable to repeat the call
|
||||
return monitor.valid() ? new TcpClosed(this) : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to repeat the previous call, which has then been
|
||||
* @param state the state that we were in
|
||||
* @param result result of an earlier SSL_get_error call
|
||||
* @return bool
|
||||
*/
|
||||
bool repeat(enum State state, int error)
|
||||
{
|
||||
// check the error
|
||||
switch (error) {
|
||||
|
|
@ -119,7 +142,7 @@ private:
|
|||
_parent->onIdle(this, _socket, readable);
|
||||
|
||||
// allow chaining
|
||||
return this;
|
||||
return true;
|
||||
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
// remember state
|
||||
|
|
@ -128,22 +151,30 @@ private:
|
|||
// wait until socket becomes writable again
|
||||
_parent->onIdle(this, _socket, readable | writable);
|
||||
|
||||
// allow chaining
|
||||
return this;
|
||||
// we are done
|
||||
return true;
|
||||
|
||||
// this case doesn't actually happen when repeat is called, since it will only be returned when
|
||||
// the result > 0 and therefore there is no error. it is here just to be sure.
|
||||
case SSL_ERROR_NONE:
|
||||
// we're ready for the next instruction from userspace
|
||||
_state = state_idle;
|
||||
|
||||
// if already closed, proceed to next state
|
||||
return proceed();
|
||||
// if we still have an outgoing buffer we want to send out data, otherwise we just read
|
||||
_parent->onIdle(this, _socket, _out ? readable | writable : readable);
|
||||
|
||||
// nothing is wrong, we are done
|
||||
return true;
|
||||
|
||||
default:
|
||||
// we are now in an error state
|
||||
_state = state_error;
|
||||
|
||||
// report an error to user-space
|
||||
_parent->onError(this, "ssl protocol error");
|
||||
|
||||
// ssl level error, we have to tear down the tcp connection
|
||||
return monitor.valid() ? new TcpClosed(this) : nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,10 +251,6 @@ private:
|
|||
// assume default state
|
||||
_state = state_idle;
|
||||
|
||||
// we are going to check for errors after the openssl operations, so we make
|
||||
// sure that the error queue is currently completely empty
|
||||
OpenSSL::ERR_clear_error();
|
||||
|
||||
// because the output buffer contains a lot of small buffers, we can do multiple
|
||||
// operations till the buffer is empty (but only if the socket is not also
|
||||
// readable, because then we want to read that data first instead of endless writes
|
||||
|
|
@ -330,6 +357,9 @@ public:
|
|||
// same is true for read operations, they should also be repeated
|
||||
if (_state == state_receiving) return receive(monitor);
|
||||
|
||||
// if we are in an error state, we close the tcp connection
|
||||
if (_state == state_error) return new TcpClosed(this);
|
||||
|
||||
// if the socket is readable, we are going to receive data
|
||||
if (flags & readable) return receive(monitor);
|
||||
|
||||
|
|
@ -352,15 +382,32 @@ public:
|
|||
// do nothing if already busy closing
|
||||
if (_closed) return;
|
||||
|
||||
// if we're not idle, we can just add bytes to the buffer and we're done
|
||||
if (_state != state_idle) return _out.add(buffer, size);
|
||||
|
||||
// clear ssl-level error
|
||||
OpenSSL::ERR_clear_error();
|
||||
|
||||
// get the result
|
||||
int result = OpenSSL::SSL_write(_ssl, buffer, size);
|
||||
|
||||
// if the result is larger than zero, we are successful
|
||||
if (result > 0) return;
|
||||
|
||||
// check for error
|
||||
auto error = OpenSSL::SSL_get_error(_ssl, result);
|
||||
|
||||
// put the data in the outgoing buffer
|
||||
_out.add(buffer, size);
|
||||
|
||||
// if we're already busy with sending or receiving, we first have to wait
|
||||
// for that operation to complete before we can move on
|
||||
if (_state != state_idle) return;
|
||||
// the operation failed, we may have to repeat our call. this may detect that
|
||||
// ssl is in an error state, however that is ok because it will set an internal
|
||||
// state to the error state so that on the next calls to state-changing objects,
|
||||
// the tcp socket will be torn down
|
||||
if (repeat(state_sending, error)) return;
|
||||
|
||||
// let's wait until the socket becomes writable
|
||||
_parent->onIdle(this, _socket, readable | writable);
|
||||
// the repeat call failed, so we are going to find out with a readable file descriptor
|
||||
_parent->onIdle(this, _socket, readable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ public:
|
|||
{
|
||||
// report error
|
||||
if (_ctx == nullptr) throw std::runtime_error("failed to construct ssl context");
|
||||
|
||||
// set the context to accept a moving write buffer. note that SSL_CTX_set_mode is a macro
|
||||
// that expands to SSL_CTX_ctrl, so that is the real function that is used
|
||||
OpenSSL::SSL_CTX_set_mode(_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -296,6 +296,9 @@ public:
|
|||
// just to be sure we do this check
|
||||
if (buffers == 0) return 0;
|
||||
|
||||
// make sure that the error queue is currently completely empty, so the error queue can be checked
|
||||
OpenSSL::ERR_clear_error();
|
||||
|
||||
// send the data
|
||||
auto result = OpenSSL::SSL_write(ssl, buffer[0].iov_base, buffer[0].iov_len);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue