]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Initial OpenSSL BIO implementation, to be used to limit socket I/O
authorAlex Rousskov <rousskov@measurement-factory.com>
Sun, 3 Feb 2013 00:08:09 +0000 (17:08 -0700)
committerAlex Rousskov <rousskov@measurement-factory.com>
Sun, 3 Feb 2013 00:08:09 +0000 (17:08 -0700)
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.

configure.ac
src/client_side.cc
src/forward.cc
src/ssl/Makefile.am
src/ssl/bio.cc [new file with mode: 0644]
src/ssl/bio.h [new file with mode: 0644]
src/ssl/support.cc
src/ssl/support.h

index 21c960231ff3bff634f9c2f714b0679488bad496..5a0afb5b37a3569611c773fbbf7b605f5cda2924 100644 (file)
@@ -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 \
index b285d38b6ace1d66f2f87ae7660c770a4f7e0b3a..609973ff9e4f589c5255bfbed7bbe6e0d366e0f4 100644 (file)
@@ -3425,24 +3425,13 @@ httpAccept(const CommAcceptCbParams &params)
 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 */
index 0b11e0af477d3b5616ba09bd71bee9e646857877..6a15ed82a5b659f1d936ad2be05242c4bb73398a 100644 (file)
@@ -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);
 }
 
index df0c0f1ac670493a2069ce8fc17c8d8ee65474ae..27bc13d15140e1e0adda6d9e23616e379f3ae5dd 100644 (file)
@@ -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 (file)
index 0000000..f7a7be8
--- /dev/null
@@ -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 <openssl/ssl.h>
+#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<Ssl::Bio*>(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<Ssl::Bio*>(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<Ssl::Bio*>(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<int*>(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<Ssl::Bio*>(table->ptr);
+            assert(bio);
+            if (arg2)
+                *static_cast<int*>(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<Ssl::Bio*>(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<Ssl::Bio*>(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 (file)
index 0000000..2d0fa04
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef SQUID_SSL_BIO_H
+#define SQUID_SSL_BIO_H
+
+#if HAVE_OPENSSL_BIO_H
+#include <openssl/bio.h>
+#endif
+#if HAVE_STRING
+#include <string>
+#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 */
index c738c2fb425be618e2587672739025841d73510f..8918bd8f27699f92ac82c10fd289d325da5331ea 100644 (file)
 
 #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 */
index f0f24c6eb6a2981715f4533dc275d616d23af94a..c66a65b369f402326a578f2ae8f7c98fda0c91ce 100644 (file)
@@ -74,6 +74,10 @@ typedef int ssl_error_t;
 
 typedef CbDataList<Ssl::ssl_error_t> 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