From: Alex Rousskov Date: Sun, 3 Feb 2013 00:08:09 +0000 (-0700) Subject: Initial OpenSSL BIO implementation, to be used to limit socket I/O X-Git-Tag: SQUID_3_5_0_1~89^2~24 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b3a8ae1bbbe58bed00b003e617bf5662565b2486;p=thirdparty%2Fsquid.git Initial OpenSSL BIO implementation, to be used to limit socket I/O and remember raw data during peek phase of Peek and Splice. No buffering yet: All SSL read/write commands go directly to the socket, which probably creates some performance overhead because they use very small I/O sizes (often just a few bytes). Useful SSL connection state and I/O debugging. Moved SSL connection creation and I/O association code into new Ssl::Create(), used by client_side.cc and forward.cc. --- diff --git a/configure.ac b/configure.ac index 21c960231f..5a0afb5b37 100644 --- a/configure.ac +++ b/configure.ac @@ -2366,6 +2366,7 @@ AC_CHECK_HEADERS( \ netinet/in.h \ netinet/in_systm.h \ netinet/ip_fil_compat.h \ + openssl/bio.h \ openssl/err.h \ openssl/md5.h \ openssl/opensslv.h \ diff --git a/src/client_side.cc b/src/client_side.cc index b285d38b6a..609973ff9e 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -3425,24 +3425,13 @@ httpAccept(const CommAcceptCbParams ¶ms) static SSL * httpsCreate(const Comm::ConnectionPointer &conn, SSL_CTX *sslContext) { - SSL *ssl = SSL_new(sslContext); - - if (!ssl) { - const int ssl_error = ERR_get_error(); - debugs(83, DBG_IMPORTANT, "ERROR: httpsAccept: Error allocating handle: " << ERR_error_string(ssl_error, NULL) ); - conn->close(); - return NULL; + if (SSL *ssl = Ssl::Create(sslContext, conn->fd, "client https start")) { + debugs(33, 5, "httpsCreate: will negotate SSL on " << conn); + return ssl; } - SSL_set_fd(ssl, conn->fd); - fd_table[conn->fd].ssl = ssl; - fd_table[conn->fd].read_method = &ssl_read_method; - fd_table[conn->fd].write_method = &ssl_write_method; - - debugs(33, 5, "httpsCreate: will negotate SSL on " << conn); - fd_note(conn->fd, "client https start"); - - return ssl; + conn->close(); + return NULL; } /** negotiate an SSL connection */ diff --git a/src/forward.cc b/src/forward.cc index 0b11e0af47..6a15ed82a5 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -915,7 +915,6 @@ FwdState::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::E void FwdState::initiateSSL() { - SSL *ssl; SSL_CTX *sslContext = NULL; const CachePeer *peer = serverConnection()->getPeer(); int fd = serverConnection()->fd; @@ -929,8 +928,8 @@ FwdState::initiateSSL() assert(sslContext); - if ((ssl = SSL_new(sslContext)) == NULL) { - debugs(83, DBG_IMPORTANT, "fwdInitiateSSL: Error allocating handle: " << ERR_error_string(ERR_get_error(), NULL) ); + SSL *ssl = Ssl::Create(sslContext, fd, "server https start"); + if (!ssl) { ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request); // TODO: create Ssl::ErrorDetail with OpenSSL-supplied error code fail(anErr); @@ -938,8 +937,6 @@ FwdState::initiateSSL() return; } - SSL_set_fd(ssl, fd); - if (peer) { if (peer->ssldomain) SSL_set_ex_data(ssl, ssl_ex_index_server, peer->ssldomain); @@ -994,9 +991,6 @@ FwdState::initiateSSL() SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert); } - fd_table[fd].ssl = ssl; - fd_table[fd].read_method = &ssl_read_method; - fd_table[fd].write_method = &ssl_write_method; negotiateSSL(fd); } diff --git a/src/ssl/Makefile.am b/src/ssl/Makefile.am index df0c0f1ac6..27bc13d151 100644 --- a/src/ssl/Makefile.am +++ b/src/ssl/Makefile.am @@ -17,6 +17,8 @@ endif ## SSL stuff used by main Squid but not by ssl_crtd libsslsquid_la_SOURCES = \ + bio.cc \ + bio.h \ cert_validate_message.cc \ cert_validate_message.h \ context_storage.cc \ diff --git a/src/ssl/bio.cc b/src/ssl/bio.cc new file mode 100644 index 0000000000..f7a7be8086 --- /dev/null +++ b/src/ssl/bio.cc @@ -0,0 +1,256 @@ +/* + * DEBUG: section 83 SSL accelerator support + * + */ + +#include "squid.h" + +/* support.cc says this is needed */ +#if USE_SSL + +#include "comm.h" +#include "Mem.h" +#include "ssl/bio.h" +#if HAVE_OPENSSL_SSL_H +#include +#endif + +// TODO: fde.h should probably export these for wrappers like ours +extern int default_read_method(int, char *, int); +extern int default_write_method(int, const char *, int); +#if _SQUID_WINDOWS_ +extern int socket_read_method(int, char *, int); +extern int socket_write_method(int, const char *, int); +#endif + +/* BIO callbacks */ +static int squid_bio_write(BIO *h, const char *buf, int num); +static int squid_bio_read(BIO *h, char *buf, int size); +static int squid_bio_puts(BIO *h, const char *str); +//static int squid_bio_gets(BIO *h, char *str, int size); +static long squid_bio_ctrl(BIO *h, int cmd, long arg1, void *arg2); +static int squid_bio_create(BIO *h); +static int squid_bio_destroy(BIO *data); +/* SSL callbacks */ +static void squid_ssl_info(const SSL *ssl, int where, int ret); + +/// Initialization structure for the BIO table with +/// Squid-specific methods and BIO method wrappers. +static BIO_METHOD SquidMethods = { + BIO_TYPE_SOCKET, + "squid", + squid_bio_write, + squid_bio_read, + squid_bio_puts, + NULL, // squid_bio_gets not supported + squid_bio_ctrl, + squid_bio_create, + squid_bio_destroy, + NULL // squid_callback_ctrl not supported +}; + +BIO * +Ssl::Bio::Create(const int fd) +{ + if (BIO *bio = BIO_new(&SquidMethods)) { + BIO_int_ctrl(bio, BIO_C_SET_FD, BIO_NOCLOSE, fd); + return bio; + } + return NULL; +} + +void +Ssl::Bio::Link(SSL *ssl, BIO *bio) +{ + SSL_set_bio(ssl, bio, bio); // cannot fail + SSL_set_info_callback(ssl, &squid_ssl_info); // does not provide diagnostic +} + + +Ssl::Bio::Bio(const int anFd): fd_(anFd) +{ + debugs(83, 7, "Bio constructed, this=" << this << " FD " << fd_); +} + +Ssl::Bio::~Bio() +{ + debugs(83, 7, "Bio destructing, this=" << this << " FD " << fd_); + // XXX: seems wrong: we do not own this fd and callers do conn->close()! + comm_close(fd_); +} + +int Ssl::Bio::write(const char *buf, int size, BIO *table) +{ + errno = 0; +#if _SQUID_WINDOWS_ + const int result = socket_write_method(fd_, buf, size); +#else + const int result = default_write_method(fd_, buf, size); +#endif + const int xerrno = errno; + debugs(83, 5, "FD " << fd_ << " wrote " << result << " <= " << size); + + BIO_clear_retry_flags(table); + if (result < 0) { + const bool ignoreError = ignoreErrno(xerrno) != 0; + debugs(83, 5, "error: " << xerrno << " ignored: " << ignoreError); + if (ignoreError) + BIO_set_retry_write(table); + } + + return result; +} + +int +Ssl::Bio::read(char *buf, int size, BIO *table) +{ + errno = 0; +#if _SQUID_WINDOWS_ + const int result = socket_read_method(fd_, buf, size); +#else + const int result = default_read_method(fd_, buf, size); +#endif + const int xerrno = errno; + debugs(83, 5, "FD " << fd_ << " read " << result << " <= " << size); + + BIO_clear_retry_flags(table); + if (result < 0) { + const bool ignoreError = ignoreErrno(xerrno) != 0; + debugs(83, 5, "error: " << xerrno << " ignored: " << ignoreError); + if (ignoreError) + BIO_set_retry_read(table); + } + + return result; +} + +/// Called whenever the SSL connection state changes, an alert appears, or an +/// error occurs. See SSL_set_info_callback(). +void +Ssl::Bio::stateChanged(const SSL *ssl, int where, int ret) +{ + // Here we can use (where & STATE) to check the current state. + // Many STATE values are possible, including: SSL_CB_CONNECT_LOOP, + // SSL_CB_ACCEPT_LOOP, SSL_CB_HANDSHAKE_START, and SSL_CB_HANDSHAKE_DONE. + // For example: + // if (where & SSL_CB_HANDSHAKE_START) + // debugs(83, 9, "Trying to establish the SSL connection"); + // else if (where & SSL_CB_HANDSHAKE_DONE) + // debugs(83, 9, "SSL connection established"); + + debugs(83, 7, "FD " << fd_ << " now: " << where << ' ' << + SSL_state_string(ssl) << " (" << SSL_state_string_long(ssl) << ")"); +} + +/// initializes BIO table after allocation +static int +squid_bio_create(BIO *bi) +{ + bi->init = 0; // set when we store Bio object and socket fd (BIO_C_SET_FD) + bi->num = 0; + bi->ptr = NULL; + bi->flags = 0; + return 1; +} + +/// cleans BIO table before deallocation +static int +squid_bio_destroy(BIO *table) +{ + delete static_cast(table->ptr); + table->ptr = NULL; + return 1; +} + +/// wrapper for Bio::write() +static int +squid_bio_write(BIO *table, const char *buf, int size) +{ + Ssl::Bio *bio = static_cast(table->ptr); + assert(bio); + return bio->write(buf, size, table); +} + +/// wrapper for Bio::read() +static int +squid_bio_read(BIO *table, char *buf, int size) +{ + Ssl::Bio *bio = static_cast(table->ptr); + assert(bio); + return bio->read(buf, size, table); +} + +/// implements puts() via write() +static int +squid_bio_puts(BIO *table, const char *str) +{ + assert(str); + return squid_bio_write(table, str, strlen(str)); +} + +/// other BIO manipulations (those without dedicated callbacks in BIO table) +static long +squid_bio_ctrl(BIO *table, int cmd, long arg1, void *arg2) +{ + debugs(83, 5, table << ' ' << cmd << '(' << arg1 << ", " << arg2 << ')'); + + switch (cmd) { + case BIO_C_SET_FD: { + assert(arg2); + const int fd = *static_cast(arg2); + Ssl::Bio *bio = new Ssl::Bio(fd); + assert(!table->ptr); + table->ptr = bio; + table->init = 1; + return 0; + } + + case BIO_C_GET_FD: + if (table->init) { + Ssl::Bio *bio = static_cast(table->ptr); + assert(bio); + if (arg2) + *static_cast(arg2) = bio->fd(); + return bio->fd(); + } + return -1; + + case BIO_CTRL_DUP: // XXX: Should this really do what FLUSH does? + case BIO_CTRL_FLUSH: + if (table->init) { + Ssl::Bio *bio = static_cast(table->ptr); + assert(bio); + bio->flush(); + return 1; + } + return 0; + +/* we may also need to implement these: + case BIO_CTRL_RESET: + case BIO_C_FILE_SEEK: + case BIO_C_FILE_TELL: + case BIO_CTRL_INFO: + case BIO_CTRL_GET_CLOSE: + case BIO_CTRL_SET_CLOSE: + case BIO_CTRL_PENDING: + case BIO_CTRL_WPENDING: +*/ + default: + return 0; + + } + + return 0; /* NOTREACHED */ +} + +/// wrapper for Bio::stateChanged() +static void +squid_ssl_info(const SSL *ssl, int where, int ret) +{ + if (BIO *table = SSL_get_rbio(ssl)) { + if (Ssl::Bio *bio = static_cast(table->ptr)) + bio->stateChanged(ssl, where, ret); + } +} + +#endif /* USE_SSL */ diff --git a/src/ssl/bio.h b/src/ssl/bio.h new file mode 100644 index 0000000000..2d0fa04688 --- /dev/null +++ b/src/ssl/bio.h @@ -0,0 +1,41 @@ +#ifndef SQUID_SSL_BIO_H +#define SQUID_SSL_BIO_H + +#if HAVE_OPENSSL_BIO_H +#include +#endif +#if HAVE_STRING +#include +#endif + +namespace Ssl { + +/// BIO source and sink node, handling socket I/O and monitoring SSL state +class Bio { +public: + explicit Bio(const int anFd); + ~Bio(); + + int write(const char *buf, int size, BIO *table); + int read(char *buf, int size, BIO *table); + void flush() {} // we do not buffer (yet?) + + int fd() const { return fd_; } + + /// Called by linked SSL connection whenever state changes, an alert + /// appears, or an error occurs. See SSL_set_info_callback(). + void stateChanged(const SSL *ssl, int where, int ret); + + /// Creates a low-level BIO table, creates a high-level Ssl::Bio object + /// for a given socket, and then links the two together via BIO_C_SET_FD. + static BIO *Create(const int fd); + /// Tells ssl connection to use BIO and monitor state via stateChanged() + static void Link(SSL *ssl, BIO *bio); + +private: + const int fd_; ///< the SSL socket we are reading and writing +}; + +} // namespace Ssl + +#endif /* SQUID_SSL_BIO_H */ diff --git a/src/ssl/support.cc b/src/ssl/support.cc index c738c2fb42..8918bd8f27 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -40,9 +40,11 @@ #include "acl/FilledChecklist.h" #include "anyp/PortCfg.h" +#include "fd.h" #include "fde.h" #include "globals.h" #include "SquidConfig.h" +#include "ssl/bio.h" #include "ssl/Config.h" #include "ssl/ErrorDetail.h" #include "ssl/support.h" @@ -1579,4 +1581,34 @@ bool Ssl::generateUntrustedCert(X509_Pointer &untrustedCert, EVP_PKEY_Pointer &u return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties); } +SSL * +Ssl::Create(SSL_CTX *sslContext, const int fd, const char *squidCtx) +{ + const char *errAction = NULL; + int errCode = 0; + if (SSL *ssl = SSL_new(sslContext)) { + // without BIO, we would call SSL_set_fd(ssl, fd) instead + if (BIO *bio = Ssl::Bio::Create(fd)) { + Ssl::Bio::Link(ssl, bio); // cannot fail + + fd_table[fd].ssl = ssl; + fd_table[fd].read_method = &ssl_read_method; + fd_table[fd].write_method = &ssl_write_method; + fd_note(fd, squidCtx); + + return ssl; + } + errCode = ERR_get_error(); + errAction = "failed to initialize I/O"; + SSL_free(ssl); + } else { + errCode = ERR_get_error(); + errAction = "failed to allocate handle"; + } + + debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction << + ": " << ERR_error_string(errCode, NULL)); + return NULL; +} + #endif /* USE_SSL */ diff --git a/src/ssl/support.h b/src/ssl/support.h index f0f24c6eb6..c66a65b369 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -74,6 +74,10 @@ typedef int ssl_error_t; typedef CbDataList Errors; +/// Creates SSL connection structure and initializes SSL I/O (Comm and BIO). +/// On errors, emits DBG_IMPORTANT with details and returns NULL. +SSL *Create(SSL_CTX *sslContext, const int fd, const char *squidCtx); + } //namespace Ssl /// \ingroup ServerProtocolSSLAPI