Use MSG_MORE instead of 2 setsockopt calls on every AsyncSSLSocket write.
[folly.git] / folly / io / async / AsyncSSLSocket.cpp
index c972c87764a2884b3a8b84cd197686e13393f2ee..8a07c41d1a37f473ac880b70975f9ee2df441f32 100644 (file)
 #include <folly/io/async/AsyncSSLSocket.h>
 
 #include <folly/io/async/EventBase.h>
+#include <folly/portability/Sockets.h>
 
 #include <boost/noncopyable.hpp>
 #include <errno.h>
 #include <fcntl.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
 #include <openssl/err.h>
 #include <openssl/asn1.h>
 #include <openssl/ssl.h>
 #include <sys/types.h>
-#include <sys/socket.h>
 #include <chrono>
 
 #include <folly/Bits.h>
@@ -56,6 +54,7 @@ using folly::AsyncSocketException;
 using folly::AsyncSSLSocket;
 using folly::Optional;
 using folly::SSLContext;
+using folly::ssl::OpenSSLUtils;
 
 // We have one single dummy SSL context so that we can implement attach
 // and detach methods in a thread safe fashion without modifying opnessl.
@@ -115,7 +114,7 @@ class AsyncSSLSocketConnector: public AsyncSocket::ConnectCallback,
   }
 
   void connectErr(const AsyncSocketException& ex) noexcept override {
-    LOG(ERROR) << "TCP connect failed: " <<  ex.what();
+    VLOG(1) << "TCP connect failed: " << ex.what();
     fail(ex);
     delete this;
   }
@@ -130,7 +129,7 @@ class AsyncSSLSocketConnector: public AsyncSocket::ConnectCallback,
 
   void handshakeErr(AsyncSSLSocket* /* socket */,
                     const AsyncSocketException& ex) noexcept override {
-    LOG(ERROR) << "client handshakeErr: " << ex.what();
+    VLOG(1) << "client handshakeErr: " << ex.what();
     fail(ex);
     delete this;
   }
@@ -152,57 +151,6 @@ class AsyncSSLSocketConnector: public AsyncSocket::ConnectCallback,
   }
 };
 
-// XXX: implement an equivalent to corking for platforms with TCP_NOPUSH?
-#ifdef TCP_CORK // Linux-only
-/**
- * Utility class that corks a TCP socket upon construction or uncorks
- * the socket upon destruction
- */
-class CorkGuard : private boost::noncopyable {
- public:
-  CorkGuard(int fd, bool multipleWrites, bool haveMore, bool* corked):
-    fd_(fd), haveMore_(haveMore), corked_(corked) {
-    if (*corked_) {
-      // socket is already corked; nothing to do
-      return;
-    }
-    if (multipleWrites || haveMore) {
-      // We are performing multiple writes in this performWrite() call,
-      // and/or there are more calls to performWrite() that will be invoked
-      // later, so enable corking
-      int flag = 1;
-      setsockopt(fd_, IPPROTO_TCP, TCP_CORK, &flag, sizeof(flag));
-      *corked_ = true;
-    }
-  }
-
-  ~CorkGuard() {
-    if (haveMore_) {
-      // more data to come; don't uncork yet
-      return;
-    }
-    if (!*corked_) {
-      // socket isn't corked; nothing to do
-      return;
-    }
-
-    int flag = 0;
-    setsockopt(fd_, IPPROTO_TCP, TCP_CORK, &flag, sizeof(flag));
-    *corked_ = false;
-  }
-
- private:
-  int fd_;
-  bool haveMore_;
-  bool* corked_;
-};
-#else
-class CorkGuard : private boost::noncopyable {
- public:
-  CorkGuard(int, bool, bool, bool*) {}
-};
-#endif
-
 void setup_SSL_CTX(SSL_CTX *ctx) {
 #ifdef SSL_MODE_RELEASE_BUFFERS
   SSL_CTX_set_mode(ctx,
@@ -225,16 +173,17 @@ void setup_SSL_CTX(SSL_CTX *ctx) {
 
 }
 
-BIO_METHOD eorAwareBioMethod;
+BIO_METHOD sslWriteBioMethod;
 
-void* initEorBioMethod(void) {
-  memcpy(&eorAwareBioMethod, BIO_s_socket(), sizeof(eorAwareBioMethod));
+void* initsslWriteBioMethod(void) {
+  memcpy(&sslWriteBioMethod, BIO_s_socket(), sizeof(sslWriteBioMethod));
   // override the bwrite method for MSG_EOR support
-  eorAwareBioMethod.bwrite = AsyncSSLSocket::eorAwareBioWrite;
+  OpenSSLUtils::setCustomBioWriteMethod(
+      &sslWriteBioMethod, AsyncSSLSocket::bioWrite);
 
-  // Note that the eorAwareBioMethod.type and eorAwareBioMethod.name are not
+  // Note that the sslWriteBioMethod.type and sslWriteBioMethod.name are not
   // set here. openssl code seems to be checking ".type == BIO_TYPE_SOCKET" and
-  // then have specific handlings. The eorAwareBioWrite should be compatible
+  // then have specific handlings. The sslWriteBioWrite should be compatible
   // with the one in openssl.
 
   // Return something here to enable AsyncSSLSocket to call this method using
@@ -253,7 +202,8 @@ AsyncSSLSocket::AsyncSSLSocket(const shared_ptr<SSLContext> &ctx,
                                EventBase* evb, bool deferSecurityNegotiation) :
     AsyncSocket(evb),
     ctx_(ctx),
-    handshakeTimeout_(this, evb) {
+    handshakeTimeout_(this, evb),
+    connectionTimeout_(this, evb) {
   init();
   if (deferSecurityNegotiation) {
     sslState_ = STATE_UNENCRYPTED;
@@ -269,7 +219,8 @@ AsyncSSLSocket::AsyncSSLSocket(const shared_ptr<SSLContext>& ctx,
     AsyncSocket(evb, fd),
     server_(server),
     ctx_(ctx),
-    handshakeTimeout_(this, evb) {
+    handshakeTimeout_(this, evb),
+    connectionTimeout_(this, evb) {
   init();
   if (server) {
     SSL_CTX_set_info_callback(ctx_->getSSLCtx(),
@@ -316,8 +267,8 @@ AsyncSSLSocket::~AsyncSSLSocket() {
 void AsyncSSLSocket::init() {
   // Do this here to ensure we initialize this once before any use of
   // AsyncSSLSocket instances and not as part of library load.
-  static const auto eorAwareBioMethodInitializer = initEorBioMethod();
-  (void)eorAwareBioMethodInitializer;
+  static const auto sslWriteBioMethodInitializer = initsslWriteBioMethod();
+  (void)sslWriteBioMethodInitializer;
 
   setup_SSL_CTX(ctx_->getSSLCtx());
 }
@@ -403,44 +354,31 @@ std::string AsyncSSLSocket::getApplicationProtocol() noexcept {
 }
 
 bool AsyncSSLSocket::isEorTrackingEnabled() const {
-  if (ssl_ == nullptr) {
-    return false;
-  }
-  const BIO *wb = SSL_get_wbio(ssl_);
-  return wb && wb->method == &eorAwareBioMethod;
+  return trackEor_;
 }
 
 void AsyncSSLSocket::setEorTracking(bool track) {
-  BIO *wb = SSL_get_wbio(ssl_);
-  if (!wb) {
-    throw AsyncSocketException(AsyncSocketException::INVALID_STATE,
-                              "setting EOR tracking without an initialized "
-                              "BIO");
-  }
-
-  if (track) {
-    if (wb->method != &eorAwareBioMethod) {
-      // only do this if we didn't
-      wb->method = &eorAwareBioMethod;
-      BIO_set_app_data(wb, this);
-      appEorByteNo_ = 0;
-      minEorRawByteNo_ = 0;
-    }
-  } else if (wb->method == &eorAwareBioMethod) {
-    wb->method = BIO_s_socket();
-    BIO_set_app_data(wb, nullptr);
+  if (trackEor_ != track) {
+    trackEor_ = track;
     appEorByteNo_ = 0;
     minEorRawByteNo_ = 0;
-  } else {
-    CHECK(wb->method == BIO_s_socket());
   }
 }
 
 size_t AsyncSSLSocket::getRawBytesWritten() const {
+  // The bio(s) in the write path are in a chain
+  // each bio flushes to the next and finally written into the socket
+  // to get the rawBytesWritten on the socket,
+  // get the write bytes of the last bio
   BIO *b;
   if (!ssl_ || !(b = SSL_get_wbio(ssl_))) {
     return 0;
   }
+  BIO* next = BIO_next(b);
+  while (next != NULL) {
+    b = next;
+    next = BIO_next(b);
+  }
 
   return BIO_number_written(b);
 }
@@ -458,10 +396,10 @@ size_t AsyncSSLSocket::getRawBytesReceived() const {
 void AsyncSSLSocket::invalidState(HandshakeCB* callback) {
   LOG(ERROR) << "AsyncSSLSocket(this=" << this << ", fd=" << fd_
              << ", state=" << int(state_) << ", sslState=" << sslState_ << ", "
-             << "events=" << eventFlags_ << ", server=" << short(server_) << "): "
-             << "sslAccept/Connect() called in invalid "
-             << "state, handshake callback " << handshakeCallback_ << ", new callback "
-             << callback;
+             << "events=" << eventFlags_ << ", server=" << short(server_)
+             << "): " << "sslAccept/Connect() called in invalid "
+             << "state, handshake callback " << handshakeCallback_
+             << ", new callback " << callback;
   assert(!handshakeTimeout_.isScheduled());
   sslState_ = STATE_ERROR;
 
@@ -609,6 +547,12 @@ void AsyncSSLSocket::timeoutExpired() noexcept {
     // We are expecting a callback in restartSSLAccept.  The cache lookup
     // and rsa-call necessarily have pointers to this ssl socket, so delay
     // the cleanup until he calls us back.
+  } else if (state_ == StateEnum::CONNECTING) {
+    assert(sslState_ == STATE_CONNECTING);
+    DestructorGuard dg(this);
+    AsyncSocketException ex(AsyncSocketException::TIMED_OUT,
+                           "Fallback connect timed out during TFO");
+    failHandshake(__func__, ex);
   } else {
     assert(state_ == StateEnum::ESTABLISHED &&
            (sslState_ == STATE_CONNECTING || sslState_ == STATE_ACCEPTING));
@@ -705,6 +649,19 @@ void AsyncSSLSocket::applyVerificationOptions(SSL * ssl) {
   }
 }
 
+bool AsyncSSLSocket::setupSSLBio() {
+  auto wb = BIO_new(&sslWriteBioMethod);
+
+  if (!wb) {
+    return false;
+  }
+
+  OpenSSLUtils::setBioAppData(wb, this);
+  OpenSSLUtils::setBioFd(wb, fd_, BIO_NOCLOSE);
+  SSL_set_bio(ssl_, wb, wb);
+  return true;
+}
+
 void AsyncSSLSocket::sslConn(HandshakeCB* callback, uint64_t timeout,
         const SSLContext::SSLVerifyPeerEnum& verifyPeer) {
   DestructorGuard dg(this);
@@ -725,10 +682,6 @@ void AsyncSSLSocket::sslConn(HandshakeCB* callback, uint64_t timeout,
     return invalidState(callback);
   }
 
-  handshakeStartTime_ = std::chrono::steady_clock::now();
-  // Make end time at least >= start time.
-  handshakeEndTime_ = handshakeStartTime_;
-
   sslState_ = STATE_CONNECTING;
   handshakeCallback_ = callback;
 
@@ -743,10 +696,17 @@ void AsyncSSLSocket::sslConn(HandshakeCB* callback, uint64_t timeout,
     return failHandshake(__func__, ex);
   }
 
+  if (!setupSSLBio()) {
+    sslState_ = STATE_ERROR;
+    AsyncSocketException ex(
+        AsyncSocketException::INTERNAL_ERROR, "error creating SSL bio");
+    return failHandshake(__func__, ex);
+  }
+
   applyVerificationOptions(ssl_);
 
-  SSL_set_fd(ssl_, fd_);
   if (sslSession_ != nullptr) {
+    sessionResumptionAttempted_ = true;
     SSL_set_session(ssl_, sslSession_);
     SSL_SESSION_free(sslSession_);
     sslSession_ = nullptr;
@@ -759,10 +719,19 @@ void AsyncSSLSocket::sslConn(HandshakeCB* callback, uint64_t timeout,
 
   SSL_set_ex_data(ssl_, getSSLExDataIndex(), this);
 
-  if (timeout > 0) {
-    handshakeTimeout_.scheduleTimeout(timeout);
-  }
+  handshakeConnectTimeout_ = timeout;
+  startSSLConnect();
+}
 
+// This could be called multiple times, during normal ssl connections
+// and after TFO fallback.
+void AsyncSSLSocket::startSSLConnect() {
+  handshakeStartTime_ = std::chrono::steady_clock::now();
+  // Make end time at least >= start time.
+  handshakeEndTime_ = handshakeStartTime_;
+  if (handshakeConnectTimeout_ > 0) {
+    handshakeTimeout_.scheduleTimeout(handshakeConnectTimeout_);
+  }
   handleConnect();
 }
 
@@ -883,6 +852,10 @@ int AsyncSSLSocket::getSSLCertSize() const {
   return certSize;
 }
 
+const X509* AsyncSSLSocket::getSelfCert() const {
+  return (ssl_ != nullptr) ? SSL_get_certificate(ssl_) : nullptr;
+}
+
 bool AsyncSSLSocket::willBlock(int ret,
                                int* sslErrorOut,
                                unsigned long* errErrorOut) noexcept {
@@ -966,16 +939,15 @@ void AsyncSSLSocket::checkForImmediateRead() noexcept {
 void
 AsyncSSLSocket::restartSSLAccept()
 {
-  VLOG(3) << "AsyncSSLSocket::restartSSLAccept() this=" << this << ", fd=" << fd_
-          << ", state=" << int(state_) << ", "
+  VLOG(3) << "AsyncSSLSocket::restartSSLAccept() this=" << this
+          << ", fd=" << fd_ << ", state=" << int(state_) << ", "
           << "sslState=" << sslState_ << ", events=" << eventFlags_;
   DestructorGuard dg(this);
   assert(
     sslState_ == STATE_CACHE_LOOKUP ||
     sslState_ == STATE_ASYNC_PENDING ||
     sslState_ == STATE_ERROR ||
-    sslState_ == STATE_CLOSED
-  );
+    sslState_ == STATE_CLOSED);
   if (sslState_ == STATE_CLOSED) {
     // I sure hope whoever closed this socket didn't delete it already,
     // but this is not strictly speaking an error
@@ -1012,7 +984,14 @@ AsyncSSLSocket::handleAccept() noexcept {
                  << ", fd=" << fd_ << "): " << e.what();
       return failHandshake(__func__, ex);
     }
-    SSL_set_fd(ssl_, fd_);
+
+    if (!setupSSLBio()) {
+      sslState_ = STATE_ERROR;
+      AsyncSocketException ex(
+          AsyncSocketException::INTERNAL_ERROR, "error creating write bio");
+      return failHandshake(__func__, ex);
+    }
+
     SSL_set_ex_data(ssl_, getSSLExDataIndex(), this);
 
     applyVerificationOptions(ssl_);
@@ -1082,16 +1061,25 @@ AsyncSSLSocket::handleConnect() noexcept {
     return AsyncSocket::handleConnect();
   }
 
-  assert(state_ == StateEnum::ESTABLISHED &&
-         sslState_ == STATE_CONNECTING);
+  assert(
+      (state_ == StateEnum::FAST_OPEN || state_ == StateEnum::ESTABLISHED) &&
+      sslState_ == STATE_CONNECTING);
   assert(ssl_);
 
+  auto originalState = state_;
   int ret = SSL_connect(ssl_);
   if (ret <= 0) {
     int sslError;
     unsigned long errError;
     int errnoCopy = errno;
     if (willBlock(ret, &sslError, &errError)) {
+      // We fell back to connecting state due to TFO
+      if (state_ == StateEnum::CONNECTING) {
+        DCHECK_EQ(StateEnum::FAST_OPEN, originalState);
+        if (handshakeTimeout_.isScheduled()) {
+          handshakeTimeout_.cancelTimeout();
+        }
+      }
       return;
     } else {
       sslState_ = STATE_ERROR;
@@ -1136,6 +1124,45 @@ AsyncSSLSocket::handleConnect() noexcept {
   AsyncSocket::handleInitialReadWrite();
 }
 
+void AsyncSSLSocket::invokeConnectErr(const AsyncSocketException& ex) {
+  connectionTimeout_.cancelTimeout();
+  AsyncSocket::invokeConnectErr(ex);
+}
+
+void AsyncSSLSocket::invokeConnectSuccess() {
+  connectionTimeout_.cancelTimeout();
+  if (sslState_ == SSLStateEnum::STATE_CONNECTING) {
+    // If we failed TFO, we'd fall back to trying to connect the socket,
+    // to setup things like timeouts.
+    startSSLConnect();
+  }
+  // still invoke the base class since it re-sets the connect time.
+  AsyncSocket::invokeConnectSuccess();
+}
+
+void AsyncSSLSocket::scheduleConnectTimeout() {
+  if (sslState_ == SSLStateEnum::STATE_CONNECTING) {
+    // We fell back from TFO, and need to set the timeouts.
+    // We will not have a connect callback in this case, thus if the timer
+    // expires we would have no-one to notify.
+    // Thus we should reset even the connect timers to point to the handshake
+    // timeouts.
+    assert(connectCallback_ == nullptr);
+    // We use a different connect timeout here than the handshake timeout, so
+    // that we can disambiguate the 2 timers.
+    int timeout = connectTimeout_.count();
+    if (timeout > 0) {
+      if (!connectionTimeout_.scheduleTimeout(timeout)) {
+        throw AsyncSocketException(
+            AsyncSocketException::INTERNAL_ERROR,
+            withAddr("failed to schedule AsyncSSLSocket connect timeout"));
+      }
+    }
+    return;
+  }
+  AsyncSocket::scheduleConnectTimeout();
+}
+
 void AsyncSSLSocket::setReadCB(ReadCallback *callback) {
 #ifdef SSL_MODE_MOVE_BUFFER_OWNERSHIP
   // turn on the buffer movable in openssl
@@ -1331,9 +1358,6 @@ AsyncSocket::WriteResult AsyncSSLSocket::performWrite(
         WRITE_ERROR, folly::make_unique<SSLException>(SSLError::EARLY_WRITE));
   }
 
-  bool cork = isSet(flags, WriteFlags::CORK);
-  CorkGuard guard(fd_, count > 1, cork, &corked_);
-
   // Declare a buffer used to hold small write requests.  It could point to a
   // memory block either on stack or on heap. If it is on heap, we release it
   // manually when scope exits
@@ -1363,6 +1387,7 @@ AsyncSocket::WriteResult AsyncSSLSocket::performWrite(
 
     ssize_t bytes;
     uint32_t buffersStolen = 0;
+    auto sslWriteBuf = buf;
     if ((len < minWriteSize_) && ((i + 1) < count)) {
       // Combine this buffer with part or all of the next buffers in
       // order to avoid really small-grained calls to SSL_write().
@@ -1383,6 +1408,7 @@ AsyncSocket::WriteResult AsyncSSLSocket::performWrite(
         }
       }
       assert(combinedBuf != nullptr);
+      sslWriteBuf = combinedBuf;
 
       memcpy(combinedBuf, buf, len);
       do {
@@ -1401,15 +1427,24 @@ AsyncSocket::WriteResult AsyncSSLSocket::performWrite(
           buffersStolen++;
         }
       } while ((i + buffersStolen + 1) < count && (len < minWriteSize_));
-      bytes = eorAwareSSLWrite(
-        ssl_, combinedBuf, len,
-        (isSet(flags, WriteFlags::EOR) && i + buffersStolen + 1 == count));
+    }
 
-    } else {
-      bytes = eorAwareSSLWrite(ssl_, buf, len,
-                           (isSet(flags, WriteFlags::EOR) && i + 1 == count));
+    // Advance any empty buffers immediately after.
+    if (bytesStolenFromNextBuffer == 0) {
+      while ((i + buffersStolen + 1) < count &&
+             vec[i + buffersStolen + 1].iov_len == 0) {
+        buffersStolen++;
+      }
     }
 
+    corkCurrentWrite_ =
+        isSet(flags, WriteFlags::CORK) || (i + buffersStolen + 1 < count);
+    bytes = eorAwareSSLWrite(
+        ssl_,
+        sslWriteBuf,
+        len,
+        (isSet(flags, WriteFlags::EOR) && i + buffersStolen + 1 == count));
+
     if (bytes <= 0) {
       int error = SSL_get_error(ssl_, bytes);
       if (error == SSL_ERROR_WANT_WRITE) {
@@ -1450,7 +1485,7 @@ AsyncSocket::WriteResult AsyncSSLSocket::performWrite(
 
 int AsyncSSLSocket::eorAwareSSLWrite(SSL *ssl, const void *buf, int n,
                                       bool eor) {
-  if (eor && SSL_get_wbio(ssl)->method == &eorAwareBioMethod) {
+  if (eor && trackEor_) {
     if (appEorByteNo_) {
       // cannot track for more than one app byte EOR
       CHECK(appEorByteNo_ == appBytesWritten_ + n);
@@ -1495,38 +1530,53 @@ void AsyncSSLSocket::sslInfoCallback(const SSL* ssl, int where, int ret) {
   }
 }
 
-int AsyncSSLSocket::eorAwareBioWrite(BIO *b, const char *in, int inl) {
-  int ret;
+int AsyncSSLSocket::bioWrite(BIO* b, const char* in, int inl) {
   struct msghdr msg;
   struct iovec iov;
   int flags = 0;
-  AsyncSSLSocket *tsslSock;
+  AsyncSSLSockettsslSock;
 
-  iov.iov_base = const_cast<char *>(in);
+  iov.iov_base = const_cast<char*>(in);
   iov.iov_len = inl;
   memset(&msg, 0, sizeof(msg));
   msg.msg_iov = &iov;
   msg.msg_iovlen = 1;
 
-  tsslSock =
-    reinterpret_cast<AsyncSSLSocket*>(BIO_get_app_data(b));
-  if (tsslSock &&
-      tsslSock->minEorRawByteNo_ &&
+  auto appData = OpenSSLUtils::getBioAppData(b);
+  CHECK(appData);
+
+  tsslSock = reinterpret_cast<AsyncSSLSocket*>(appData);
+  CHECK(tsslSock);
+
+  if (tsslSock->trackEor_ && tsslSock->minEorRawByteNo_ &&
       tsslSock->minEorRawByteNo_ <= BIO_number_written(b) + inl) {
     flags = MSG_EOR;
   }
 
-  ret = sendmsg(b->num, &msg, flags);
+#ifdef MSG_NOSIGNAL
+  flags |= MSG_NOSIGNAL;
+#endif
+
+#ifdef MSG_MORE
+  if (tsslSock->corkCurrentWrite_) {
+    flags |= MSG_MORE;
+  }
+#endif
+
+  auto result = tsslSock->sendSocketMessage(
+      OpenSSLUtils::getBioFd(b, nullptr), &msg, flags);
   BIO_clear_retry_flags(b);
-  if (ret <= 0) {
-    if (BIO_sock_should_retry(ret))
+  if (!result.exception && result.writeReturn <= 0) {
+    if (OpenSSLUtils::getBioShouldRetryWrite(result.writeReturn)) {
       BIO_set_retry_write(b);
+    }
   }
-  return(ret);
+  return result.writeReturn;
 }
 
-int AsyncSSLSocket::sslVerifyCallback(int preverifyOk,
-                                       X509_STORE_CTX* x509Ctx) {
+int AsyncSSLSocket::sslVerifyCallback(
+    int preverifyOk,
+    X509_STORE_CTX* x509Ctx) {
   SSL* ssl = (SSL*) X509_STORE_CTX_get_ex_data(
     x509Ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
   AsyncSSLSocket* self = AsyncSSLSocket::getFromSSL(ssl);
@@ -1624,6 +1674,7 @@ void AsyncSSLSocket::clientHelloParsingCallback(int written,
         extensionsLength -= 2;
         uint16_t extensionDataLength = cursor.readBE<uint16_t>();
         extensionsLength -= 2;
+        extensionsLength -= extensionDataLength;
 
         if (extensionType == ssl::TLSExtension::SIGNATURE_ALGORITHMS) {
           cursor.skip(2);
@@ -1639,7 +1690,6 @@ void AsyncSSLSocket::clientHelloParsingCallback(int written,
           }
         } else {
           cursor.skip(extensionDataLength);
-          extensionsLength -= extensionDataLength;
         }
       }
     }
@@ -1652,4 +1702,114 @@ void AsyncSSLSocket::clientHelloParsingCallback(int written,
   sock->resetClientHelloParsing(ssl);
 }
 
+void AsyncSSLSocket::getSSLClientCiphers(
+    std::string& clientCiphers,
+    bool convertToString) const {
+  std::string ciphers;
+
+  if (parseClientHello_ == false
+      || clientHelloInfo_->clientHelloCipherSuites_.empty()) {
+    clientCiphers = "";
+    return;
+  }
+
+  bool first = true;
+  for (auto originalCipherCode : clientHelloInfo_->clientHelloCipherSuites_)
+  {
+    if (first) {
+      first = false;
+    } else {
+      ciphers +=  ":";
+    }
+
+    bool nameFound = convertToString;
+
+    if (convertToString) {
+      const auto& name = OpenSSLUtils::getCipherName(originalCipherCode);
+      if (name.empty()) {
+        nameFound = false;
+      } else {
+        ciphers += name;
+      }
+    }
+
+    if (!nameFound) {
+      folly::hexlify(
+          std::array<uint8_t, 2>{{
+              static_cast<uint8_t>((originalCipherCode >> 8) & 0xffL),
+              static_cast<uint8_t>(originalCipherCode & 0x00ffL) }},
+          ciphers,
+          /* append to ciphers = */ true);
+    }
+  }
+
+  clientCiphers = std::move(ciphers);
+}
+
+std::string AsyncSSLSocket::getSSLClientComprMethods() const {
+  if (!parseClientHello_) {
+    return "";
+  }
+  return folly::join(":", clientHelloInfo_->clientHelloCompressionMethods_);
+}
+
+std::string AsyncSSLSocket::getSSLClientExts() const {
+  if (!parseClientHello_) {
+    return "";
+  }
+  return folly::join(":", clientHelloInfo_->clientHelloExtensions_);
+}
+
+std::string AsyncSSLSocket::getSSLClientSigAlgs() const {
+  if (!parseClientHello_) {
+    return "";
+  }
+
+  std::string sigAlgs;
+  sigAlgs.reserve(clientHelloInfo_->clientHelloSigAlgs_.size() * 4);
+  for (size_t i = 0; i < clientHelloInfo_->clientHelloSigAlgs_.size(); i++) {
+    if (i) {
+      sigAlgs.push_back(':');
+    }
+    sigAlgs.append(folly::to<std::string>(
+        clientHelloInfo_->clientHelloSigAlgs_[i].first));
+    sigAlgs.push_back(',');
+    sigAlgs.append(folly::to<std::string>(
+        clientHelloInfo_->clientHelloSigAlgs_[i].second));
+  }
+
+  return sigAlgs;
+}
+
+std::string AsyncSSLSocket::getSSLAlertsReceived() const {
+  std::string ret;
+
+  for (const auto& alert : alertsReceived_) {
+    if (!ret.empty()) {
+      ret.append(",");
+    }
+    ret.append(folly::to<std::string>(alert.first, ": ", alert.second));
+  }
+
+  return ret;
+}
+
+void AsyncSSLSocket::getSSLSharedCiphers(std::string& sharedCiphers) const {
+  char ciphersBuffer[1024];
+  ciphersBuffer[0] = '\0';
+  SSL_get_shared_ciphers(ssl_, ciphersBuffer, sizeof(ciphersBuffer) - 1);
+  sharedCiphers = ciphersBuffer;
+}
+
+void AsyncSSLSocket::getSSLServerCiphers(std::string& serverCiphers) const {
+  serverCiphers = SSL_get_cipher_list(ssl_, 0);
+  int i = 1;
+  const char *cipher;
+  while ((cipher = SSL_get_cipher_list(ssl_, i)) != nullptr) {
+    serverCiphers.append(":");
+    serverCiphers.append(cipher);
+    i++;
+  }
+}
+
 } // namespace