separate class for extracting errors

This commit is contained in:
Raoul Wols 2021-07-13 11:23:08 +02:00
parent 2f6451a9aa
commit 3bc7b62567
No known key found for this signature in database
GPG Key ID: 9FFE06A0F6AAA2DF
3 changed files with 144 additions and 72 deletions

View File

@ -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:
/**

View File

@ -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; }
};
}

View File

@ -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);
}
}