separate class for extracting errors
This commit is contained in:
parent
2f6451a9aa
commit
3bc7b62567
|
|
@ -19,6 +19,7 @@
|
||||||
#include "poll.h"
|
#include "poll.h"
|
||||||
#include "sslwrapper.h"
|
#include "sslwrapper.h"
|
||||||
#include "sslshutdown.h"
|
#include "sslshutdown.h"
|
||||||
|
#include "sslerrorprinter.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up namespace
|
* Set up namespace
|
||||||
|
|
@ -163,15 +164,23 @@ private:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
{
|
||||||
// we are now in an error state
|
// we are now in an error state
|
||||||
_state = state_error;
|
_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
|
// 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
|
// ssl level error, we have to tear down the tcp connection
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -304,74 +313,6 @@ private:
|
||||||
// proceed with the write operation or the event loop
|
// proceed with the write operation or the event loop
|
||||||
return _out && isWritable() ? write(monitor) : proceed();
|
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:
|
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 "poll.h"
|
||||||
#include "sslwrapper.h"
|
#include "sslwrapper.h"
|
||||||
#include "sslcontext.h"
|
#include "sslcontext.h"
|
||||||
|
#include "sslerrorprinter.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up namespace
|
* Set up namespace
|
||||||
|
|
@ -80,12 +81,19 @@ private:
|
||||||
/**
|
/**
|
||||||
* Helper method to report an error
|
* Helper method to report an error
|
||||||
* @param monitor
|
* @param monitor
|
||||||
|
* @param retval return value of SSL_get_error
|
||||||
* @return TcpState*
|
* @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
|
// 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
|
// stop if connection is gone
|
||||||
if (!monitor.valid()) return nullptr;
|
if (!monitor.valid()) return nullptr;
|
||||||
|
|
@ -182,7 +190,7 @@ public:
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case SSL_ERROR_WANT_READ: return proceed(readable);
|
case SSL_ERROR_WANT_READ: return proceed(readable);
|
||||||
case SSL_ERROR_WANT_WRITE: return proceed(readable | writable);
|
case SSL_ERROR_WANT_WRITE: return proceed(readable | writable);
|
||||||
default: return reportError(monitor);
|
default: return reportError(monitor, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue