DCHECK(ctx->getSSLCtx());
ctx_ = ctx;
+ // It's possible this could be attached before ssl_ is set up
+ if (!ssl_) {
+ return;
+ }
+
// In order to call attachSSLContext, detachSSLContext must have been
- // previously called which sets the socket's context to the dummy
- // context. Thus we must acquire this lock.
+ // previously called.
+ // We need to update the initial_ctx if necessary
+ auto sslCtx = ctx->getSSLCtx();
+#ifndef OPENSSL_NO_TLSEXT
+ CRYPTO_add(&sslCtx->references, 1, CRYPTO_LOCK_SSL_CTX);
+ // note that detachSSLContext has already freed ssl_->initial_ctx
+ ssl_->initial_ctx = sslCtx;
+#endif
+ // Detach sets the socket's context to the dummy context. Thus we must acquire
+ // this lock.
SpinLockGuard guard(dummyCtxLock);
- SSL_set_SSL_CTX(ssl_, ctx->getSSLCtx());
+ SSL_set_SSL_CTX(ssl_, sslCtx);
}
void AsyncSSLSocket::detachSSLContext() {
DCHECK(ctx_);
ctx_.reset();
- // We aren't using the initial_ctx for now, and it can introduce race
- // conditions in the destructor of the SSL object.
+ // It's possible for this to be called before ssl_ has been
+ // set up
+ if (!ssl_) {
+ return;
+ }
+// Detach the initial_ctx as well. Internally w/ OPENSSL_NO_TLSEXT
+// it is used for session info. It will be reattached in attachSSLContext
#ifndef OPENSSL_NO_TLSEXT
if (ssl_->initial_ctx) {
SSL_CTX_free(ssl_->initial_ctx);
#include <pthread.h>
+#include <folly/futures/Promise.h>
#include <folly/io/async/AsyncSSLSocket.h>
#include <folly/io/async/EventBase.h>
#include <folly/io/async/SSLContext.h>
+#include <folly/io/async/ScopedEventBaseThread.h>
#include <folly/portability/GTest.h>
using std::string;
namespace folly {
+struct EvbAndContext {
+ EvbAndContext() {
+ ctx_.reset(new SSLContext());
+ ctx_->setOptions(SSL_OP_NO_TICKET);
+ ctx_->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+ }
+
+ std::shared_ptr<AsyncSSLSocket> createSocket() {
+ return AsyncSSLSocket::newSocket(ctx_, getEventBase());
+ }
+
+ EventBase* getEventBase() {
+ return evb_.getEventBase();
+ }
+
+ void attach(AsyncSSLSocket& socket) {
+ socket.attachEventBase(getEventBase());
+ socket.attachSSLContext(ctx_);
+ }
+
+ folly::ScopedEventBaseThread evb_;
+ std::shared_ptr<SSLContext> ctx_;
+};
+
class AttachDetachClient : public AsyncSocket::ConnectCallback,
public AsyncTransportWrapper::WriteCallback,
public AsyncTransportWrapper::ReadCallback {
private:
- EventBase *eventBase_;
+ // two threads here - we'll create the socket in one, connect
+ // in the other, and then read/write in the initial one
+ EvbAndContext t1_;
+ EvbAndContext t2_;
std::shared_ptr<AsyncSSLSocket> sslSocket_;
- std::shared_ptr<SSLContext> ctx_;
folly::SocketAddress address_;
char buf_[128];
char readbuf_[128];
uint32_t bytesRead_;
+ // promise to fulfill when done
+ folly::Promise<bool> promise_;
+
+ void detach() {
+ sslSocket_->detachEventBase();
+ sslSocket_->detachSSLContext();
+ }
+
public:
- AttachDetachClient(EventBase *eventBase, const folly::SocketAddress& address)
- : eventBase_(eventBase), address_(address), bytesRead_(0) {
- ctx_.reset(new SSLContext());
- ctx_->setOptions(SSL_OP_NO_TICKET);
- ctx_->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+ explicit AttachDetachClient(const folly::SocketAddress& address)
+ : address_(address), bytesRead_(0) {}
+
+ Future<bool> getFuture() {
+ return promise_.getFuture();
}
void connect() {
- sslSocket_ = AsyncSSLSocket::newSocket(ctx_, eventBase_);
- sslSocket_->connect(this, address_);
+ // create in one and then move to another
+ auto t1Evb = t1_.getEventBase();
+ t1Evb->runInEventBaseThread([this] {
+ sslSocket_ = t1_.createSocket();
+ // ensure we can detach and reattach the context before connecting
+ for (int i = 0; i < 1000; ++i) {
+ sslSocket_->detachSSLContext();
+ sslSocket_->attachSSLContext(t1_.ctx_);
+ }
+ // detach from t1 and connect in t2
+ detach();
+ auto t2Evb = t2_.getEventBase();
+ t2Evb->runInEventBaseThread([this] {
+ t2_.attach(*sslSocket_);
+ sslSocket_->connect(this, address_);
+ });
+ });
}
void connectSuccess() noexcept override {
+ auto t2Evb = t2_.getEventBase();
+ EXPECT_TRUE(t2Evb->isInEventBaseThread());
cerr << "client SSL socket connected" << endl;
-
for (int i = 0; i < 1000; ++i) {
sslSocket_->detachSSLContext();
- sslSocket_->attachSSLContext(ctx_);
+ sslSocket_->attachSSLContext(t2_.ctx_);
}
- EXPECT_EQ(ctx_->getSSLCtx()->references, 2);
-
- sslSocket_->write(this, buf_, sizeof(buf_));
- sslSocket_->setReadCB(this);
- memset(readbuf_, 'b', sizeof(readbuf_));
- bytesRead_ = 0;
+ // detach from t2 and then read/write in t1
+ t2Evb->runInEventBaseThread([this] {
+ detach();
+ auto t1Evb = t1_.getEventBase();
+ t1Evb->runInEventBaseThread([this] {
+ t1_.attach(*sslSocket_);
+ sslSocket_->write(this, buf_, sizeof(buf_));
+ sslSocket_->setReadCB(this);
+ memset(readbuf_, 'b', sizeof(readbuf_));
+ bytesRead_ = 0;
+ });
+ });
}
void connectErr(const AsyncSocketException& ex) noexcept override
void readErr(const AsyncSocketException& ex) noexcept override {
cerr << "client readError: " << ex.what() << endl;
+ promise_.setException(ex);
}
void readDataAvailable(size_t len) noexcept override {
+ EXPECT_TRUE(t1_.getEventBase()->isInEventBaseThread());
+ EXPECT_EQ(sslSocket_->getEventBase(), t1_.getEventBase());
cerr << "client read data: " << len << endl;
bytesRead_ += len;
if (len == sizeof(buf_)) {
EXPECT_EQ(memcmp(buf_, readbuf_, bytesRead_), 0);
sslSocket_->closeNow();
+ sslSocket_.reset();
+ promise_.setValue(true);
}
}
};
SSLServerAcceptCallbackDelay acceptCallback(&handshakeCallback);
TestSSLServer server(&acceptCallback);
- EventBase eventBase;
- EventBaseAborter eba(&eventBase, 3000);
std::shared_ptr<AttachDetachClient> client(
- new AttachDetachClient(&eventBase, server.getAddress()));
+ new AttachDetachClient(server.getAddress()));
+ auto f = client->getFuture();
client->connect();
- eventBase.loop();
+ EXPECT_TRUE(f.within(std::chrono::seconds(3)).get());
}
} // folly