separate class for extracting errors
This commit is contained in:
parent
2f6451a9aa
commit
3bc7b62567
|
|
@ -19,6 +19,7 @@
|
|||
#include "poll.h"
|
||||
#include "sslwrapper.h"
|
||||
#include "sslshutdown.h"
|
||||
#include "sslerrorprinter.h"
|
||||
|
||||
/**
|
||||
* Set up namespace
|
||||
|
|
@ -163,15 +164,23 @@ private:
|
|||
return true;
|
||||
|
||||
default:
|
||||
{
|
||||
// we are now in an error state
|
||||
_state = state_error;
|
||||
|
||||
// get a human-readable error string
|
||||
const SslErrorPrinter printer{error};
|
||||
|
||||
// ensure it is null-terminated
|
||||
const std::string message{printer.data(), printer.size()};
|
||||
|
||||
// report an error to user-space
|
||||
_parent->onError(this, strerror(error).data());
|
||||
_parent->onError(this, message.data());
|
||||
|
||||
// ssl level error, we have to tear down the tcp connection
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -304,74 +313,6 @@ private:
|
|||
// proceed with the write operation or the event loop
|
||||
return _out && isWritable() ? write(monitor) : proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error description of the returnvalue of SSL_get_error
|
||||
* @param[in] returnvalue The return value of SSL_get_error
|
||||
* @param bp Pointer to a BIO in case the error is SSL_ERROR_SSL
|
||||
* @return A static string describing the error, or nullptr.
|
||||
* In the case of a nullptr, the passed-in BIO describes the error.
|
||||
*/
|
||||
static const char *strerror(int returnvalue, ::BIO *bp)
|
||||
{
|
||||
// check the return value of the SSL_get_error function, which has a very unfortunate name.
|
||||
switch (returnvalue)
|
||||
{
|
||||
// It can be a syscall error.
|
||||
case SSL_ERROR_SYSCALL:
|
||||
|
||||
// The SSL_ERROR_SYSCALL with errno value of 0 indicates unexpected
|
||||
// EOF from the peer. This will be properly reported as SSL_ERROR_SSL
|
||||
// with reason code SSL_R_UNEXPECTED_EOF_WHILE_READING in the
|
||||
// OpenSSL 3.0 release because it is truly a TLS protocol error to
|
||||
// terminate the connection without a SSL_shutdown().
|
||||
if (errno == 0) return "SSL_R_UNEXPECTED_EOF_WHILE_READING";
|
||||
|
||||
// Otherwise we ask the OS for a description of the error.
|
||||
return ::strerror(errno);
|
||||
|
||||
// It can be an error in OpenSSL. In that case the error stack contains
|
||||
// more information. The documentation notes: if this error occurs then
|
||||
// no further I/O operations should be performed on the connection and
|
||||
// SSL_shutdown() must not be called.
|
||||
case SSL_ERROR_SSL:
|
||||
|
||||
// invoke the convenience function to extract the whole error stack
|
||||
::ERR_print_errors(bp);
|
||||
|
||||
// we return nullptr because the error is described by the bio
|
||||
return nullptr;
|
||||
|
||||
default:
|
||||
// we don't know what kind of error this is
|
||||
return "unknown ssl error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable string from the return value of SSL_get_error in case it's an error.
|
||||
* @param[in] returnvalue The return value. Must not be a non-error.
|
||||
* @return std::string describing the error.
|
||||
*/
|
||||
static std::string strerror(int returnvalue)
|
||||
{
|
||||
// create a BIO so we can call our strerror overload
|
||||
std::unique_ptr<::BIO, decltype(&::BIO_free)> bio(::BIO_new(::BIO_s_mem()), &::BIO_free);
|
||||
|
||||
// deduce the error message, and return it if it's not null
|
||||
if (const char *message = strerror(returnvalue, bio.get())) return {message};
|
||||
|
||||
// the error is described in the BIO
|
||||
// get the buffer memory structure
|
||||
::BUF_MEM *bufmem;
|
||||
|
||||
// get it from the bio
|
||||
::BIO_get_mem_ptr(bio.get(), &bufmem);
|
||||
|
||||
// ensure it's null-terminated by copying it to std::string
|
||||
return {bufmem->data, bufmem->length};
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* SslErrorPrinter.h
|
||||
*
|
||||
* Flushes the SSL error stack to a BIO.
|
||||
* You can get at the string content via the data() and size() methods.
|
||||
* After constructing an instance of this class, the SSL error stack
|
||||
* is empty.
|
||||
*
|
||||
* @copyright 2021 copernica BV
|
||||
*/
|
||||
|
||||
/**
|
||||
* Include guard
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
#include "openssl.h"
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* Begin namespace
|
||||
*/
|
||||
namespace AMQP {
|
||||
|
||||
/**
|
||||
* Class declaration
|
||||
*/
|
||||
class SslErrorPrinter final
|
||||
{
|
||||
/**
|
||||
* pointer to the BIO
|
||||
* @var unique_ptr
|
||||
*/
|
||||
std::unique_ptr<::BIO, decltype(&::BIO_free)> _bio;
|
||||
|
||||
/**
|
||||
* pointer to the (data, size) tuple
|
||||
* @var ::BUF_MEM*
|
||||
*/
|
||||
::BUF_MEM *_bufmem = nullptr;
|
||||
|
||||
/**
|
||||
* In case of a syscall error, the static string pointing to the error
|
||||
* @var const char*
|
||||
*/
|
||||
const char *_strerror = nullptr;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
* @throw std::bad_alloc if the BIO couldn't be allocated
|
||||
*/
|
||||
SslErrorPrinter(int retval) : _bio(nullptr, &::BIO_free)
|
||||
{
|
||||
// check the return value of the SSL_get_error function, which has a very unfortunate name.
|
||||
switch (retval)
|
||||
{
|
||||
// It can be a syscall error.
|
||||
case SSL_ERROR_SYSCALL:
|
||||
{
|
||||
// The SSL_ERROR_SYSCALL with errno value of 0 indicates unexpected
|
||||
// EOF from the peer. This will be properly reported as SSL_ERROR_SSL
|
||||
// with reason code SSL_R_UNEXPECTED_EOF_WHILE_READING in the
|
||||
// OpenSSL 3.0 release because it is truly a TLS protocol error to
|
||||
// terminate the connection without a SSL_shutdown().
|
||||
if (errno == 0) _strerror = "SSL_R_UNEXPECTED_EOF_WHILE_READING";
|
||||
|
||||
// Otherwise we ask the OS for a description of the error.
|
||||
else _strerror = ::strerror(errno);
|
||||
|
||||
// done
|
||||
break;
|
||||
}
|
||||
// It can be an error in OpenSSL. In that case the error stack contains
|
||||
// more information. The documentation notes: if this error occurs then
|
||||
// no further I/O operations should be performed on the connection and
|
||||
// SSL_shutdown() must not be called.
|
||||
case SSL_ERROR_SSL:
|
||||
{
|
||||
// create a new bio
|
||||
_bio = decltype(_bio)(::BIO_new(::BIO_s_mem()), &::BIO_free);
|
||||
|
||||
// check if it was allocated
|
||||
if (!_bio) throw std::bad_alloc();
|
||||
|
||||
// invoke the convenience function to extract the whole error stack
|
||||
::ERR_print_errors(_bio.get());
|
||||
|
||||
// get it from the bio
|
||||
::BIO_get_mem_ptr(_bio.get(), &_bufmem);
|
||||
|
||||
// done
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// we don't know what kind of error this is
|
||||
_strerror = "unknown ssl error";
|
||||
|
||||
// done
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* data ptr
|
||||
* @return const char *
|
||||
*/
|
||||
const char *data() const noexcept { return _strerror ? _strerror : _bufmem->data; }
|
||||
|
||||
/**
|
||||
* length of the string
|
||||
* @return size_t
|
||||
*/
|
||||
std::size_t size() const noexcept { return _strerror ? std::strlen(_strerror) : _bufmem->length; }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
#include "poll.h"
|
||||
#include "sslwrapper.h"
|
||||
#include "sslcontext.h"
|
||||
#include "sslerrorprinter.h"
|
||||
|
||||
/**
|
||||
* Set up namespace
|
||||
|
|
@ -80,12 +81,19 @@ private:
|
|||
/**
|
||||
* Helper method to report an error
|
||||
* @param monitor
|
||||
* @param retval return value of SSL_get_error
|
||||
* @return TcpState*
|
||||
*/
|
||||
TcpState *reportError(const Monitor &monitor)
|
||||
TcpState *reportError(const Monitor &monitor, int retval)
|
||||
{
|
||||
// extract a human-readable error string
|
||||
const SslErrorPrinter printer{retval};
|
||||
|
||||
// ensure it's null-terminated
|
||||
const std::string message{printer.data(), printer.size()};
|
||||
|
||||
// we have an error - report this to the user
|
||||
_parent->onError(this, "failed to setup ssl connection");
|
||||
_parent->onError(this, message.data());
|
||||
|
||||
// stop if connection is gone
|
||||
if (!monitor.valid()) return nullptr;
|
||||
|
|
@ -182,7 +190,7 @@ public:
|
|||
switch (error) {
|
||||
case SSL_ERROR_WANT_READ: return proceed(readable);
|
||||
case SSL_ERROR_WANT_WRITE: return proceed(readable | writable);
|
||||
default: return reportError(monitor);
|
||||
default: return reportError(monitor, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue