You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by jo...@apache.org on 2019/12/18 22:34:36 UTC

[mesos] 07/11: SSL Socket: Implemented BIO for SSL socket.

This is an automated email from the ASF dual-hosted git repository.

josephwu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mesos.git

commit 7e78f75f2aa86daf75036187d914fb35ee4bb516
Author: Joseph Wu <jo...@apache.org>
AuthorDate: Tue Oct 15 17:52:00 2019 -0700

    SSL Socket: Implemented BIO for SSL socket.
    
    This implements the OpenSSL basic I/O abstraction (BIO) based on the
    libprocess event loop.  This BIO wraps a socket and handles the
    reading/writing, using io::read and io::write.
    
    This BIO can be passed into an SSL context to enable usage of
    SSL translation functions like SSL_read and SSL_write.
    
    Review: https://reviews.apache.org/r/71664
---
 3rdparty/libprocess/src/ssl/openssl_socket.cpp | 251 +++++++++++++++++++++++++
 1 file changed, 251 insertions(+)

diff --git a/3rdparty/libprocess/src/ssl/openssl_socket.cpp b/3rdparty/libprocess/src/ssl/openssl_socket.cpp
index 8c1ea2f..ce1f806 100644
--- a/3rdparty/libprocess/src/ssl/openssl_socket.cpp
+++ b/3rdparty/libprocess/src/ssl/openssl_socket.cpp
@@ -16,20 +16,271 @@
 #include <stout/windows.hpp>
 #endif // __WINDOWS__
 
+#include <openssl/bio.h>
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 
+#include <atomic>
+#include <queue>
+
+#include <process/io.hpp>
+#include <process/loop.hpp>
+#include <process/owned.hpp>
 #include <process/socket.hpp>
 
+#include <process/ssl/flags.hpp>
+
+#include <stout/synchronized.hpp>
 #include <stout/unimplemented.hpp>
 
 #include "openssl.hpp"
+
 #include "ssl/openssl_socket.hpp"
 
 namespace process {
 namespace network {
 namespace internal {
 
+// Contains the state of a source/sink BIO wrapper for an ordinary socket.
+struct SocketBIOData
+{
+  // Socket associated with this BIO.
+  // OpenSSLSocketImpl retains ownership of the socket.
+  int_fd socket;
+
+  // Stores the latest call to `BIO_write`.
+  struct SendRequest
+  {
+    SendRequest(Future<size_t> _future)
+      : future(_future) {}
+
+    Future<size_t> future;
+  };
+
+  // Stores the latest call to `BIO_read`.
+  struct RecvRequest
+  {
+    RecvRequest(
+        char* _data,
+        size_t _size,
+        Future<size_t> _future)
+      : data(_data),
+        size(_size),
+        future(_future) {}
+
+    char* data; // NOT owned by this object.
+    size_t size;
+    Future<size_t> future;
+  };
+
+  // Protects the following instance variables.
+  std::atomic_flag lock = ATOMIC_FLAG_INIT;
+  Owned<SendRequest> send_request;
+  Owned<RecvRequest> recv_request;
+  bool reached_eof;
+};
+
+
+// Called in response to `BIO_new()`.
+// We will need to perform some additional initialization to link
+// the OpenSSLSocketImpl to the BIO, outside of this function.
+//
+// See: https://www.openssl.org/docs/man1.1.1/man3/BIO_get_data.html
+int bio_libprocess_create(BIO* bio)
+{
+  // Indicate that initialization has not completed.
+  BIO_set_init(bio, 0);
+
+  // The caller will need to fill in the data with a copy of the `int_fd`
+  // associated with the OpenSSLSocketImpl, after BIO creation.
+  BIO_set_data(bio, new SocketBIOData());
+
+  return 1;
+}
+
+
+// Called in response to `BIO_free()`.
+int bio_libprocess_destroy(BIO* bio)
+{
+  SocketBIOData* data = reinterpret_cast<SocketBIOData*>(BIO_get_data(bio));
+  CHECK_NOTNULL(data);
+  delete data;
+
+  return 1;
+}
+
+
+// Called in response to `BIO_write()`.
+// This function will maintain a single pending write at any time
+// by swapping the contents of `send_request`.
+int bio_libprocess_write(BIO* bio, const char* input, int length)
+{
+  SocketBIOData* data = reinterpret_cast<SocketBIOData*>(BIO_get_data(bio));
+  CHECK_NOTNULL(data);
+
+  synchronized (data->lock) {
+    // Only a single write should be pending at any time.
+    if (data->send_request.get() == nullptr ||
+        data->send_request->future.isReady()) {
+      Owned<SocketBIOData::SendRequest> request(
+          new SocketBIOData::SendRequest(
+              io::write(data->socket, input, length)));
+
+      std::swap(request, data->send_request);
+      return length;
+    }
+
+    BIO_set_retry_write(bio);
+    return 0;
+  }
+}
+
+
+// Called in response to `BIO_read()`.
+// This function will maintain a single pending read at any time
+// by swapping the contents of `recv_request`.
+int bio_libprocess_read(BIO* bio, char* output, int length)
+{
+  SocketBIOData* data = reinterpret_cast<SocketBIOData*>(BIO_get_data(bio));
+  CHECK_NOTNULL(data);
+
+  synchronized (data->lock) {
+    // Only a single read should be pending at any time.
+    if (data->recv_request.get() == nullptr) {
+      Owned<SocketBIOData::RecvRequest> request(
+          new SocketBIOData::RecvRequest(
+              output, length, io::read(data->socket, output, length)));
+
+      std::swap(request, data->recv_request);
+    } else if (data->recv_request->future.isReady()) {
+      Owned<SocketBIOData::RecvRequest> completed_request;
+      std::swap(completed_request, data->recv_request);
+
+      // When retrying a read, the arguments passed in must be identical
+      // to the previous attempt. This is an API requirement of `SSL_read`.
+      // See: https://www.openssl.org/docs/man1.1.1/man3/SSL_read.html
+      //   "The calling process then must repeat the call after taking
+      //   appropriate action to satisfy the needs of the read function."
+      //
+      // This guarantee means we can read onto the same buffer between retries,
+      // confident that the same output buffer will be allocated and available
+      // each time.
+      CHECK_EQ(completed_request->data, output);
+      CHECK_EQ(completed_request->size, length);
+
+      if (completed_request->future.get() == 0u) {
+        data->reached_eof = true;
+      }
+
+      return completed_request->future.get();
+    }
+
+    BIO_set_retry_read(bio);
+    return 0;
+  }
+}
+
+
+// Called in response to `BIO_ctrl()`, which is usually wrapped by
+// different macros, i.e. `BIO_reset()`, `BIO_eof()`, `BIO_flush()`, etc.
+//
+// The enums implemented below were based on Libevent's BIO implementation
+// and OpenSSL's BSS (BIO Source Sink) Socket, found here:
+// https://github.com/openssl/openssl/blob/OpenSSL_1_1_1/crypto/bio/bss_sock.c
+//
+// See: https://www.openssl.org/docs/man1.1.1/man3/BIO_ctrl.html
+long bio_libprocess_ctrl(BIO* bio, int command, long, void*)
+{
+  SocketBIOData* data = reinterpret_cast<SocketBIOData*>(BIO_get_data(bio));
+  CHECK_NOTNULL(data);
+
+  switch (command) {
+    // Returns 1 when a read request has returned 0 bytes,
+    // and otherwise returns 0.
+    case BIO_CTRL_EOF: {
+      synchronized (data->lock) {
+        if (data->reached_eof) {
+          return 1;
+        }
+
+        return 0;
+      }
+    }
+
+    // NOTE: We choose not to implement BIO_CTRL_FLUSH because this call
+    // expects blocking behavior, and is sometimes called from within OpenSSL's
+    // library functions. When necessary, the retry-write behavior should be
+    // sufficient to make sure writes succeed.
+    case BIO_CTRL_FLUSH: {
+      // NOTE: We must return a successful result here, even though we
+      // have not flushed any data, because OpenSSL considers a failure
+      // here unrecoverable and will try to close the connection.
+      return 1;
+    }
+
+    // NOTE: Libevent implements BIO_CTRL_GET_CLOSE and BIO_CTRL_SET_CLOSE,
+    // which indicates that the underlying I/O stream should be closed when
+    // the BIO is freed. We opt to always close/free the socket/BIO.
+
+    // NOTE: We choose not to implement BIO_CTRL_PENDING and BIO_CTRL_WPENDING
+    // because this implementation only keeps a single read/write buffered
+    // at once. Also, these methods are not used by OpenSSL or by callers
+    // of our implementation.
+
+    default:
+      return 0; // Not implemented.
+  }
+}
+
+
+// Constructs a new BIO_METHOD wrapping the libprocess event loop.
+//
+// See: https://www.openssl.org/docs/man1.1.1/man3/BIO_meth_new.html
+static BIO_METHOD* libprocess_bio = nullptr;
+static BIO_METHOD* get_libprocess_BIO_METHOD()
+{
+  if (libprocess_bio != nullptr) {
+    return libprocess_bio;
+  }
+
+  // Get a unique index for our new type, and annotate the index
+  // to say this BIO is a source/sink with a file descriptor.
+  int type = BIO_get_new_index();
+  CHECK(type > 0) << "Failed to create a new BIO type";
+  type = type|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR;
+
+  libprocess_bio = BIO_meth_new(type, "libprocess");
+
+  BIO_meth_set_create(libprocess_bio, bio_libprocess_create);
+  BIO_meth_set_destroy(libprocess_bio, bio_libprocess_destroy);
+
+  BIO_meth_set_write(libprocess_bio, bio_libprocess_write);
+  BIO_meth_set_read(libprocess_bio, bio_libprocess_read);
+
+  BIO_meth_set_ctrl(libprocess_bio, bio_libprocess_ctrl);
+
+  return libprocess_bio;
+}
+
+
+// Constructs a new source/sink BIO around the given socket.
+static BIO* BIO_new_libprocess(int_fd socket)
+{
+  BIO* bio = BIO_new(get_libprocess_BIO_METHOD());
+  CHECK_NOTNULL(bio);
+
+  SocketBIOData* data = reinterpret_cast<SocketBIOData*>(BIO_get_data(bio));
+  CHECK_NOTNULL(data);
+
+  // Fill in the socket field of the new BIO.
+  data->socket = socket;
+
+  BIO_set_init(bio, 1);
+
+  return bio;
+}
+
+
 Try<std::shared_ptr<SocketImpl>> OpenSSLSocketImpl::create(int_fd s)
 {
   UNIMPLEMENTED;