SMP SSL session cache implementation This patch implement SSL session cache sharing across SMP workers using shared memory. The following new squid configuration options added: - The "sslproxy_session_cache_size" option which sets the cache size to use for ssl session. Example usage: sslproxy_session_cache_size 4 MB - The "sslproxy_session_ttl" option which defines the time in seconds the ssl session is valid. Example usage: sslproxy_session_ttl 600 This is a Measurement Factory project === modified file 'src/Makefile.am' --- src/Makefile.am 2014-01-03 10:32:53 +0000 +++ src/Makefile.am 2014-01-08 11:03:19 +0000 @@ -610,58 +610,58 @@ swap_log_op.cc CLEANFILES += $(BUILT_SOURCES) nodist_squid_SOURCES = \ $(DISKIO_GEN_SOURCE) \ $(BUILT_SOURCES) squid_LDADD = \ $(AUTH_ACL_LIBS) \ ident/libident.la \ acl/libacls.la \ acl/libstate.la \ $(AUTH_LIBS) \ $(DISK_LIBS) \ acl/libapi.la \ base/libbase.la \ libsquid.la \ ip/libip.la \ fs/libfs.la \ + $(SSL_LIBS) \ ipc/libipc.la \ mgr/libmgr.la \ anyp/libanyp.la \ comm/libcomm.la \ eui/libeui.la \ http/libsquid-http.la \ icmp/libicmp.la icmp/libicmp-core.la \ log/liblog.la \ format/libformat.la \ $(XTRA_OBJS) \ $(DISK_LINKOBJS) \ $(REPL_OBJS) \ $(DISK_OS_LIBS) \ $(CRYPTLIB) \ $(REGEXLIB) \ $(ADAPTATION_LIBS) \ $(ESI_LIBS) \ - $(SSL_LIBS) \ $(SNMP_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(SSLLIB) \ $(EPOLL_LIBS) \ $(MINGW_LIBS) \ $(KRB5LIBS) \ $(COMPAT_LIB) \ $(XTRA_LIBS) squid_DEPENDENCIES = \ $(DISK_LIBS) \ $(DISK_LINKOBJS) \ $(REPL_OBJS) \ $(ADAPTATION_LIBS) \ $(ESI_LOCAL_LIBS) \ $(SSL_LIBS) \ $(AUTH_ACL_LIBS) \ ident/libident.la \ acl/libacls.la \ @@ -1776,43 +1776,43 @@ swap_log_op.cc tests_testDiskIO_LDADD = \ http/libsquid-http.la \ SquidConfig.o \ CommCalls.o \ DnsLookupDetails.o \ ident/libident.la \ acl/libacls.la \ acl/libstate.la \ libsquid.la \ comm/libcomm.la \ anyp/libanyp.la \ ip/libip.la \ fs/libfs.la \ ipc/libipc.la \ $(REPL_OBJS) \ $(DISK_LIBS) \ $(DISK_OS_LIBS) \ acl/libapi.la \ mgr/libmgr.la \ + $(SSL_LIBS) \ ipc/libipc.la \ base/libbase.la \ - $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(REGEXLIB) \ $(SQUID_CPPUNIT_LIBS) \ $(SSLLIB) \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testDiskIO_LDFLAGS = $(LIBADD_DL) tests_testDiskIO_DEPENDENCIES = \ $(DISK_LIBS) \ $(SWAP_TEST_DS) \ $(SQUID_CPPUNIT_LA) ## Tests of the Even module. tests_testEvent_SOURCES = \ AccessLogEntry.cc \ BodyPipe.cc \ CacheDigest.h \ @@ -2790,54 +2790,54 @@ urn.h \ urn.cc \ wccp2.h \ tests/stub_wccp2.cc \ whois.h \ tests/stub_whois.cc \ FadingCounter.cc \ $(WIN32_SOURCE) \ wordlist.h \ wordlist.cc nodist_tests_testHttpRequest_SOURCES = \ $(BUILT_SOURCES) tests_testHttpRequest_LDADD = \ ident/libident.la \ acl/libacls.la \ acl/libstate.la \ acl/libapi.la \ libsquid.la \ ip/libip.la \ fs/libfs.la \ + $(SSL_LIBS) \ ipc/libipc.la \ base/libbase.la \ mgr/libmgr.la \ anyp/libanyp.la \ $(SNMP_LIBS) \ icmp/libicmp.la icmp/libicmp-core.la \ comm/libcomm.la \ log/liblog.la \ format/libformat.la \ http/libsquid-http.la \ $(REPL_OBJS) \ $(ADAPTATION_LIBS) \ $(ESI_LIBS) \ - $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(DISK_OS_LIBS) \ $(REGEXLIB) \ $(SQUID_CPPUNIT_LIBS) \ $(SQUID_CPPUNIT_LA) \ $(SSLLIB) \ $(KRB5LIBS) \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testHttpRequest_LDFLAGS = $(LIBADD_DL) tests_testHttpRequest_DEPENDENCIES = \ $(REPL_OBJS) \ $(SQUID_CPPUNIT_LA) ## why so many sources? well httpHeaderTools requites ACLChecklist & friends. ## first line - what we are testing. tests_testStore_SOURCES= \ CacheDigest.h \ @@ -2977,43 +2977,43 @@ wordlist.h \ wordlist.cc nodist_tests_testStore_SOURCES= \ $(TESTSOURCES) \ SquidMath.cc \ SquidMath.h \ swap_log_op.cc tests_testStore_LDADD= \ http/libsquid-http.la \ ident/libident.la \ acl/libacls.la \ acl/libstate.la \ acl/libapi.la \ base/libbase.la \ libsquid.la \ ip/libip.la \ fs/libfs.la \ mgr/libmgr.la \ + $(SSL_LIBS) \ ipc/libipc.la \ anyp/libanyp.la \ - $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(REGEXLIB) \ $(SQUID_CPPUNIT_LIBS) \ $(SSLLIB) \ CommCalls.o \ DnsLookupDetails.o \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testStore_LDFLAGS = $(LIBADD_DL) tests_testStore_DEPENDENCIES = \ $(SQUID_CPPUNIT_LA) ## string needs mem.cc. ## mem.cc needs ClientInfo.h ## libsquid pulls in SquidConfig and children. stub them. tests_testString_SOURCES = \ ClientInfo.h \ Mem.h \ @@ -3213,42 +3213,42 @@ SquidMath.cc \ SquidMath.h \ swap_log_op.cc tests_testUfs_LDADD = \ http/libsquid-http.la \ CommCalls.o \ DnsLookupDetails.o \ ident/libident.la \ acl/libacls.la \ acl/libstate.la \ acl/libapi.la \ libsquid.la \ ip/libip.la \ fs/libfs.la \ mgr/libmgr.la \ $(REPL_OBJS) \ acl/libacls.la \ $(DISK_LIBS) \ $(DISK_OS_LIBS) \ acl/libapi.la \ - ipc/libipc.la \ $(SSL_LIBS) \ + ipc/libipc.la \ comm/libcomm.la \ anyp/libanyp.la \ base/libbase.la \ ip/libip.la \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(REGEXLIB) \ $(SQUID_CPPUNIT_LIBS) \ $(SSLLIB) \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testUfs_LDFLAGS = $(LIBADD_DL) tests_testUfs_DEPENDENCIES = \ $(SWAP_TEST_DS) check_PROGRAMS += testRefCount testRefCount_SOURCES= \ base/Lock.h \ base/RefCount.h \ @@ -3394,43 +3394,44 @@ nodist_tests_testRock_SOURCES = \ $(DISKIO_GEN_SOURCE) \ swap_log_op.cc \ SquidMath.cc \ SquidMath.h \ $(TESTSOURCES) tests_testRock_LDADD = \ http/libsquid-http.la \ libsquid.la \ comm/libcomm.la \ anyp/libanyp.la \ ip/libip.la \ fs/libfs.la \ $(COMMON_LIBS) \ $(REPL_OBJS) \ $(DISK_LIBS) \ $(DISK_OS_LIBS) \ acl/libacls.la \ acl/libapi.la \ acl/libstate.la \ + eui/libeui.la \ + $(SSL_LIBS) \ ipc/libipc.la \ base/libbase.la \ - $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(REGEXLIB) \ $(SQUID_CPPUNIT_LIBS) \ $(SSLLIB) \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testRock_LDFLAGS = $(INCLUDES) $(LIBADD_DL) tests_testRock_DEPENDENCIES = \ $(SWAP_TEST_DS) ## Tests of the URL module. ## TODO: Trim this down once the insanity is over. tests_testURL_SOURCES = \ AccessLogEntry.cc \ BodyPipe.cc \ cache_cf.h \ AuthReg.h \ YesNoNone.h \ @@ -3630,53 +3631,53 @@ whois.h \ tests/stub_whois.cc \ FadingCounter.cc \ $(WIN32_SOURCE) \ wordlist.h \ wordlist.cc nodist_tests_testURL_SOURCES = \ $(BUILT_SOURCES) tests_testURL_LDADD = \ http/libsquid-http.la \ anyp/libanyp.la \ ident/libident.la \ acl/libacls.la \ eui/libeui.la \ acl/libstate.la \ acl/libapi.la \ base/libbase.la \ libsquid.la \ ip/libip.la \ fs/libfs.la \ + $(SSL_LIBS) \ ipc/libipc.la \ mgr/libmgr.la \ $(SNMP_LIBS) \ icmp/libicmp.la icmp/libicmp-core.la \ comm/libcomm.la \ log/liblog.la \ $(DISK_OS_LIBS) \ format/libformat.la \ $(REGEXLIB) \ $(REPL_OBJS) \ $(ADAPTATION_LIBS) \ $(ESI_LIBS) \ - $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(COMPAT_LIB) \ $(SQUID_CPPUNIT_LIBS) \ $(SQUID_CPPUNIT_LA) \ $(SSLLIB) \ $(KRB5LIBS) \ $(COMPAT_LIB) \ $(XTRA_LIBS) tests_testURL_LDFLAGS = $(LIBADD_DL) tests_testURL_DEPENDENCIES = \ $(REPL_OBJS) \ $(SQUID_CPPUNIT_LA) tests_testSBuf_SOURCES= \ tests/testSBuf.h \ tests/testSBuf.cc \ tests/testMain.cc \ tests/SBufFindTest.h \ === modified file 'src/SquidConfig.h' --- src/SquidConfig.h 2014-01-01 19:20:49 +0000 +++ src/SquidConfig.h 2014-01-08 11:01:04 +0000 @@ -475,40 +475,42 @@ HeaderWithAclList *request_header_add; ///note Notes notes; char *coredump_dir; char *chroot_dir; #if USE_CACHE_DIGESTS struct { int bits_per_entry; time_t rebuild_period; time_t rewrite_period; size_t swapout_chunk_size; int rebuild_chunk_percentage; } digest; #endif #if USE_SSL struct { int unclean_shutdown; char *ssl_engine; + int session_ttl; + size_t sessionCacheSize; } SSL; #endif wordlist *ext_methods; struct { int high_rptm; int high_pf; size_t high_memory; } warnings; char *store_dir_select_algorithm; int sleep_after_fork; /* microseconds */ time_t minimum_expiry_time; /* seconds */ external_acl *externalAclHelperList; #if USE_SSL struct { char *cert; char *key; === modified file 'src/cf.data.pre' --- src/cf.data.pre 2014-01-05 02:56:31 +0000 +++ src/cf.data.pre 2014-01-08 11:01:04 +0000 @@ -2359,40 +2359,58 @@ NAME: sslproxy_cafile IFDEF: USE_SSL DEFAULT: none LOC: Config.ssl_client.cafile TYPE: string DOC_START file containing CA certificates to use when verifying server certificates while proxying https:// URLs DOC_END NAME: sslproxy_capath IFDEF: USE_SSL DEFAULT: none LOC: Config.ssl_client.capath TYPE: string DOC_START directory containing CA certificates to use when verifying server certificates while proxying https:// URLs DOC_END +NAME: sslproxy_session_ttl +IFDEF: USE_SSL +DEFAULT: 300 +LOC: Config.SSL.session_ttl +TYPE: int +DOC_START + Sets the timeout value for SSL sessions +DOC_END + +NAME: sslproxy_session_cache_size +IFDEF: USE_SSL +DEFAULT: 2 MB +LOC: Config.SSL.sessionCacheSize +TYPE: b_size_t +DOC_START + Sets the cache size to use for ssl session +DOC_END + NAME: ssl_bump IFDEF: USE_SSL TYPE: sslproxy_ssl_bump LOC: Config.accessList.ssl_bump DEFAULT_DOC: Does not bump unless rules are present in squid.conf DEFAULT: none DOC_START This option is consulted when a CONNECT request is received on an http_port (or a new connection is intercepted at an https_port), provided that port was configured with an ssl-bump flag. The subsequent data on the connection is either treated as HTTPS and decrypted OR tunneled at TCP level without decryption, depending on the first bumping "mode" which ACLs match. ssl_bump [!]acl ... The following bumping modes are supported: client-first Allow bumping of the connection. Establish a secure connection === modified file 'src/ipc/Makefile.am' --- src/ipc/Makefile.am 2013-01-17 04:25:35 +0000 +++ src/ipc/Makefile.am 2014-01-08 10:17:42 +0000 @@ -1,35 +1,37 @@ include $(top_srcdir)/src/Common.am include $(top_srcdir)/src/TestHeaders.am noinst_LTLIBRARIES = libipc.la libipc_la_SOURCES = \ AtomicWord.cc \ AtomicWord.h \ FdNotes.cc \ FdNotes.h \ Kid.cc \ Kid.h \ Kids.cc \ Kids.h \ Messages.h \ + MemMap.cc \ + MemMap.h \ Queue.cc \ Queue.h \ ReadWriteLock.cc \ ReadWriteLock.h \ StartListening.cc \ StartListening.h \ StoreMap.cc \ StoreMap.h \ StrandCoord.cc \ StrandCoord.h \ StrandCoords.h \ StrandSearch.cc \ StrandSearch.h \ SharedListen.cc \ SharedListen.h \ TypedMsgHdr.cc \ TypedMsgHdr.h \ Coordinator.cc \ Coordinator.h \ UdsOp.cc \ === added file 'src/ipc/MemMap.cc' --- src/ipc/MemMap.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/MemMap.cc 2014-01-08 10:17:42 +0000 @@ -0,0 +1,350 @@ +/* + * DEBUG: section 54 Interprocess Communication + */ + +#include "squid.h" +#include "ipc/MemMap.h" +#include "store_key_md5.h" +#include "tools.h" + +Ipc::MemMap::MemMap(const char *const aPath): cleaner(NULL), path(aPath), + shared(shm_old(Shared)(aPath)) +{ + assert(shared->limit > 0); // we should not be created otherwise + debugs(54, 5, HERE << "attached map [" << path << "] created: " << + shared->limit); +} + +Ipc::MemMap::Owner * +Ipc::MemMap::Init(const char *const path, const int limit, const size_t extrasSize) +{ + assert(limit > 0); // we should not be created otherwise + Owner *const owner = shm_new(Shared)(path, limit, extrasSize); + debugs(54, 5, HERE << "new map [" << path << "] created: " << limit); + return owner; +} + +Ipc::MemMap::Owner * +Ipc::MemMap::Init(const char *const path, const int limit) +{ + return Init(path, limit, 0); +} + + +Ipc::MemMap::Slot * +Ipc::MemMap::openForWriting(const cache_key *const key, sfileno &fileno) +{ + Slot *slots = shared->slots(); + + debugs(54, 5, HERE << " trying to open slot for key " << storeKeyText(key) + << " for writing in map [" << path << ']'); + const int idx = slotIndexByKey(key); + + Slot &s = slots[idx]; + ReadWriteLock &lock = s.lock; + + if (lock.lockExclusive()) { + assert(s.state != Slot::Writeable); // until we start breaking locks + + // free if the entry was used, keeping the entry locked + if (s.waitingToBeFreed || s.state == Slot::Readable) + freeLocked(s, true); + + assert(s.state == Slot::Empty); + ++shared->count; + s.state = Slot::Writeable; + fileno = idx; + //s.setKey(key); // XXX: the caller should do that + debugs(54, 5, HERE << " opened slot at " << idx << + " for writing in map [" << path << ']'); + return &s; // and keep the entry locked + } + + debugs(54, 5, HERE << " failed to open slot at " << idx << + " for writing in map [" << path << ']'); + return NULL; +} + +void +Ipc::MemMap::closeForWriting(const sfileno fileno, bool lockForReading) +{ + debugs(54, 5, HERE << " closing slot at " << fileno << " for writing and " + "openning for reading in map [" << path << ']'); + assert(valid(fileno)); + Slot *slots = shared->slots(); + Slot &s = slots[fileno]; + assert(s.state == Slot::Writeable); + s.state = Slot::Readable; + if (lockForReading) + s.lock.switchExclusiveToShared(); + else + s.lock.unlockExclusive(); +} + +/// terminate writing the entry, freeing its slot for others to use +void +Ipc::MemMap::abortWriting(const sfileno fileno) +{ + debugs(54, 5, HERE << " abort writing slot at " << fileno << + " in map [" << path << ']'); + assert(valid(fileno)); + Slot *slots = shared->slots(); + Slot &s = slots[fileno]; + assert(s.state == Slot::Writeable); + freeLocked(s, false); +} + +void +Ipc::MemMap::abortIo(const sfileno fileno) +{ + debugs(54, 5, HERE << " abort I/O for slot at " << fileno << + " in map [" << path << ']'); + assert(valid(fileno)); + Slot *slots = shared->slots(); + Slot &s = slots[fileno]; + + // The caller is a lock holder. Thus, if we are Writeable, then the + // caller must be the writer; otherwise the caller must be the reader. + if (s.state == Slot::Writeable) + abortWriting(fileno); + else + closeForReading(fileno); +} + +const Ipc::MemMap::Slot * +Ipc::MemMap::peekAtReader(const sfileno fileno) const +{ + assert(valid(fileno)); + Slot *slots = shared->slots(); + const Slot &s = slots[fileno]; + switch (s.state) { + case Slot::Readable: + return &s; // immediate access by lock holder so no locking + case Slot::Writeable: + return NULL; // cannot read the slot when it is being written + case Slot::Empty: + assert(false); // must be locked for reading or writing + } + assert(false); // not reachable + return NULL; +} + +void +Ipc::MemMap::free(const sfileno fileno) +{ + debugs(54, 5, HERE << " marking slot at " << fileno << " to be freed in" + " map [" << path << ']'); + + assert(valid(fileno)); + Slot *slots = shared->slots(); + Slot &s = slots[fileno]; + + if (s.lock.lockExclusive()) + freeLocked(s, false); + else + s.waitingToBeFreed = true; // mark to free it later +} + +const Ipc::MemMap::Slot * +Ipc::MemMap::openForReading(const cache_key *const key, sfileno &fileno) +{ + debugs(54, 5, HERE << " trying to open slot for key " << storeKeyText(key) + << " for reading in map [" << path << ']'); + const int idx = slotIndexByKey(key); + if (const Slot *slot = openForReadingAt(idx)) { + if (slot->sameKey(key)) { + fileno = idx; + debugs(54, 5, HERE << " opened slot at " << fileno << " for key " + << storeKeyText(key) << " for reading in map [" << path << + ']'); + return slot; // locked for reading + } + slot->lock.unlockShared(); + } + debugs(54, 5, HERE << " failed to open slot for key " << storeKeyText(key) + << " for reading in map [" << path << ']'); + return NULL; +} + +const Ipc::MemMap::Slot * +Ipc::MemMap::openForReadingAt(const sfileno fileno) +{ + Slot *slots = shared->slots(); + + debugs(54, 5, HERE << " trying to open slot at " << fileno << " for " + "reading in map [" << path << ']'); + assert(valid(fileno)); + Slot &s = slots[fileno]; + + if (!s.lock.lockShared()) { + debugs(54, 5, HERE << " failed to lock slot at " << fileno << " for " + "reading in map [" << path << ']'); + return NULL; + } + + if (s.state == Slot::Empty) { + s.lock.unlockShared(); + debugs(54, 7, HERE << " empty slot at " << fileno << " for " + "reading in map [" << path << ']'); + return NULL; + } + + if (s.waitingToBeFreed) { + s.lock.unlockShared(); + debugs(54, 7, HERE << " dirty slot at " << fileno << " for " + "reading in map [" << path << ']'); + return NULL; + } + + // cannot be Writing here if we got shared lock and checked Empty above + assert(s.state == Slot::Readable); + debugs(54, 5, HERE << " opened slot at " << fileno << " for reading in" + " map [" << path << ']'); + return &s; +} + +void +Ipc::MemMap::closeForReading(const sfileno fileno) +{ + debugs(54, 5, HERE << " closing slot at " << fileno << " for reading in " + "map [" << path << ']'); + assert(valid(fileno)); + Slot *slots = shared->slots(); + Slot &s = slots[fileno]; + assert(s.state == Slot::Readable); + s.lock.unlockShared(); +} + +int +Ipc::MemMap::entryLimit() const +{ + return shared->limit; +} + +int +Ipc::MemMap::entryCount() const +{ + return shared->count; +} + +bool +Ipc::MemMap::full() const +{ + return entryCount() >= entryLimit(); +} + +void +Ipc::MemMap::updateStats(ReadWriteLockStats &stats) const +{ + Slot *slots = shared->slots(); + for (int i = 0; i < shared->limit; ++i) + slots[i].lock.updateStats(stats); +} + +bool +Ipc::MemMap::valid(const int pos) const +{ + return 0 <= pos && pos < entryLimit(); +} + +static +unsigned int +hash_key(const unsigned char *data, unsigned int len, unsigned int hashSize) +{ + unsigned int n; + unsigned int j; + for(j = 0, n = 0; j < len; j++ ) { + n ^= 271 * *data; + ++data; + } + return (n ^ (j * 271)) % hashSize; +} + +int +Ipc::MemMap::slotIndexByKey(const cache_key *const key) const +{ + const unsigned char *k = reinterpret_cast(key); + return hash_key(k, SSL_SESSION_ID_SIZE, shared->limit); +} + +Ipc::MemMap::Slot & +Ipc::MemMap::slotByKey(const cache_key *const key) +{ + Slot *slots = shared->slots(); + return slots[slotIndexByKey(key)]; +} + +/// unconditionally frees the already exclusively locked slot and releases lock +void +Ipc::MemMap::freeLocked(Slot &s, bool keepLocked) +{ + Slot *slots = shared->slots(); + if (s.state == Slot::Readable && cleaner) + cleaner->cleanReadable(&s - slots); + + s.waitingToBeFreed = false; + s.state = Slot::Empty; + if (!keepLocked) + s.lock.unlockExclusive(); + --shared->count; + debugs(54, 5, HERE << " freed slot at " << (&s - slots) << + " in map [" << path << ']'); +} + +/* Ipc::MemMapSlot */ +Ipc::MemMapSlot::MemMapSlot(): state(Empty) +{ + memset(key, 0, sizeof(key)); + memset(p, 0, sizeof(p)); + pSize = 0; +} + +void +Ipc::MemMapSlot::set(const unsigned char *aKey, const void *block, size_t blockSize, time_t expireOn) +{ + memcpy(key, aKey, sizeof(key)); + if (block) + memcpy(p, block, blockSize); + pSize = blockSize; + expire = expireOn; +} + +bool +Ipc::MemMapSlot::sameKey(const cache_key *const aKey) const +{ + return (memcmp(key, aKey, sizeof(key)) == 0); +} + +/* Ipc::MemMap::Shared */ + +Ipc::MemMap::Shared::Shared(const int aLimit, const size_t anExtrasSize): + limit(aLimit), extrasSize(anExtrasSize), count(0) +{ + // All of the Slot members should initialized to zero. + // XXX: use FlexibleArray instead of this hack + memset(slots(), 0, aLimit * sizeof(Shared)); +} + +Ipc::MemMap::Shared::~Shared() +{ +} + +size_t +Ipc::MemMap::Shared::sharedMemorySize() const +{ + return SharedMemorySize(limit, extrasSize); +} + +size_t +Ipc::MemMap::Shared::SharedMemorySize(const int limit, const size_t extrasSize) +{ + return sizeof(Shared) + limit * (sizeof(Slot) + extrasSize); +} + +Ipc::MemMap::Slot * +Ipc::MemMap::Shared::slots() +{ + // XXX: use FlexibleArray instead of this hack + void *p = this; + return (Slot *)((size_t)p + sizeof(Shared)); +} === added file 'src/ipc/MemMap.h' --- src/ipc/MemMap.h 1970-01-01 00:00:00 +0000 +++ src/ipc/MemMap.h 2014-01-08 10:17:42 +0000 @@ -0,0 +1,139 @@ +#ifndef SQUID_IPC_STORE_MAP_H +#define SQUID_IPC_STORE_MAP_H + +#include "Debug.h" +#include "ipc/ReadWriteLock.h" +#include "ipc/mem/Pointer.h" +#include "tools.h" +#include "typedefs.h" + +namespace Ipc +{ + +#define SSL_SESSION_ID_SIZE 32 +#define SSL_SESSION_MAX_SIZE 10*1024 + +/// a MemMap basic element, holding basic shareable memory block info +class MemMapSlot +{ +public: + mutable ReadWriteLock lock; ///< protects slot data below + Atomic::WordT waitingToBeFreed; ///< may be accessed w/o a lock + + /// possible persistent states + typedef enum { + Empty, ///< ready for writing, with nothing of value + Writeable, ///< transitions from Empty to Readable + Readable, ///< ready for reading + } State; + State state; ///< current state + + unsigned char key[SSL_SESSION_ID_SIZE]; ///< The entry key + unsigned char p[SSL_SESSION_MAX_SIZE]; ///< The memory block; + size_t pSize; + time_t expire; + + MemMapSlot(); + size_t size() const {return sizeof(MemMapSlot);} + size_t keySize() const {return sizeof(key);} + bool sameKey(const cache_key *const aKey) const; + void set(const unsigned char *aKey, const void *block, size_t blockSize, time_t expire = 0); +}; + +class MemMapCleaner; + +/// map of MemMapSlots indexed by their keys, with read/write slot locking +/// kids extend to store custom data +class MemMap +{ +public: + typedef MemMapSlot Slot; + + /// data shared across maps in different processes + class Shared + { + public: + Shared(const int aLimit, const size_t anExtrasSize); + size_t sharedMemorySize() const; + static size_t SharedMemorySize(const int limit, const size_t anExtrasSize); + Slot *slots(); + ~Shared(); + + const int limit; ///< maximum number of map slots + const size_t extrasSize; ///< size of slot extra data + Atomic::Word count; ///< current number of map slots + private: + Shared(); //disabled + Shared &operator=(const Shared&); //disabled + Shared(const Shared&); //disabled + }; + +public: + typedef Mem::Owner Owner; + + /// initialize shared memory + static Owner *Init(const char *const path, const int limit); + + MemMap(const char *const aPath); + + /// finds, reservers space for writing a new entry or returns nil + Slot *openForWriting(const cache_key *const key, sfileno &fileno); + /// successfully finish writing the entry + void closeForWriting(const sfileno fileno, bool lockForReading = false); + + /// only works on locked entries; returns nil unless the slot is readable + const Slot *peekAtReader(const sfileno fileno) const; + + /// mark the slot as waiting to be freed and, if possible, free it + void free(const sfileno fileno); + + /// open slot for reading, increments read level + const Slot *openForReading(const cache_key *const key, sfileno &fileno); + /// open slot for reading, increments read level + const Slot *openForReadingAt(const sfileno fileno); + /// close slot after reading, decrements read level + void closeForReading(const sfileno fileno); + + /// called by lock holder to terminate either slot writing or reading + void abortIo(const sfileno fileno); + + bool full() const; ///< there are no empty slots left + bool valid(const int n) const; ///< whether n is a valid slot coordinate + int entryCount() const; ///< number of used slots + int entryLimit() const; ///< maximum number of slots that can be used + + /// adds approximate current stats to the supplied ones + void updateStats(ReadWriteLockStats &stats) const; + + MemMapCleaner *cleaner; ///< notified before a readable entry is freed + +protected: + static Owner *Init(const char *const path, const int limit, const size_t extrasSize); + + const String path; ///< cache_dir path, used for logging + Mem::Pointer shared; + int ttl; + +private: + int slotIndexByKey(const cache_key *const key) const; + Slot &slotByKey(const cache_key *const key); + + Slot *openForReading(Slot &s); + void abortWriting(const sfileno fileno); + void freeIfNeeded(Slot &s); + void freeLocked(Slot &s, bool keepLocked); +}; + +/// API for adjusting external state when dirty map slot is being freed +class MemMapCleaner +{ +public: + virtual ~MemMapCleaner() {} + + /// adjust slot-linked state before a locked Readable slot is erased + virtual void cleanReadable(const sfileno fileno) = 0; +}; + +} // namespace Ipc + +#endif /* SQUID_IPC_STORE_MAP_H */ === modified file 'src/main.cc' --- src/main.cc 2013-11-29 19:47:54 +0000 +++ src/main.cc 2014-01-08 11:01:04 +0000 @@ -1038,40 +1038,44 @@ if (WIN32_OS_version > _WIN_OS_WINNT) { WIN32_IpAddrChangeMonitorInit(); } #endif if (!configured_once) disk_init(); /* disk_init must go before ipcache_init() */ ipcache_init(); fqdncache_init(); parseEtcHosts(); dnsInit(); #if USE_SSL_CRTD Ssl::Helper::GetInstance()->Init(); #endif +#if USE_SSL + if (!configured_once) + Ssl::initialize_session_cache(); +#endif #if USE_SSL if (Ssl::CertValidationHelper::GetInstance()) Ssl::CertValidationHelper::GetInstance()->Init(); #endif redirectInit(); #if USE_AUTH authenticateInit(&Auth::TheConfig); #endif externalAclInit(); httpHeaderInitModule(); /* must go before any header processing (e.g. the one in errorInitialize) */ httpReplyInitModule(); /* must go before accepting replies */ errorInitialize(); accessLogInit(); === modified file 'src/ssl/support.cc' --- src/ssl/support.cc 2013-10-25 00:13:46 +0000 +++ src/ssl/support.cc 2014-01-08 11:05:21 +0000 @@ -24,52 +24,58 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #include "squid.h" /* MS Visual Studio Projects are monolithic, so we need the following * #if to exclude the SSL code from compile process when not needed. */ #if USE_SSL #include "acl/FilledChecklist.h" #include "anyp/PortCfg.h" #include "fde.h" +#include "ipc/MemMap.h" #include "globals.h" #include "SquidConfig.h" +#include "SquidTime.h" #include "ssl/Config.h" #include "ssl/ErrorDetail.h" #include "ssl/gadgets.h" #include "ssl/support.h" #include "URL.h" #if HAVE_ERRNO_H #include #endif +static void setSessionCallbacks(SSL_CTX *ctx); +Ipc::MemMap *SslSessionCache = NULL; +const char *SslSessionCacheName = "ssl_session_cache"; + const char *Ssl::BumpModeStr[] = { "none", "client-first", "server-first", NULL }; /** \defgroup ServerProtocolSSLInternal Server-Side SSL Internals \ingroup ServerProtocolSSLAPI */ /// \ingroup ServerProtocolSSLInternal static int ssl_ask_password_cb(char *buf, int size, int rwflag, void *userdata) { FILE *in; int len = 0; char cmdline[1024]; @@ -716,41 +722,40 @@ if (Config.SSL.ssl_engine) { ENGINE *e; if (!(e = ENGINE_by_id(Config.SSL.ssl_engine))) { fatalf("Unable to find SSL engine '%s'\n", Config.SSL.ssl_engine); } if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) { int ssl_error = ERR_get_error(); fatalf("Failed to initialise SSL engine: %s\n", ERR_error_string(ssl_error, NULL)); } } #else if (Config.SSL.ssl_engine) { fatalf("Your OpenSSL has no SSL engine support\n"); } #endif - } ssl_ex_index_server = SSL_get_ex_new_index(0, (void *) "server", NULL, NULL, NULL); ssl_ctx_ex_index_dont_verify_domain = SSL_CTX_get_ex_new_index(0, (void *) "dont_verify_domain", NULL, NULL, NULL); ssl_ex_index_cert_error_check = SSL_get_ex_new_index(0, (void *) "cert_error_check", NULL, &ssl_dupAclChecklist, &ssl_freeAclChecklist); ssl_ex_index_ssl_error_detail = SSL_get_ex_new_index(0, (void *) "ssl_error_detail", NULL, NULL, &ssl_free_ErrorDetail); ssl_ex_index_ssl_peeked_cert = SSL_get_ex_new_index(0, (void *) "ssl_peeked_cert", NULL, NULL, &ssl_free_X509); ssl_ex_index_ssl_errors = SSL_get_ex_new_index(0, (void *) "ssl_errors", NULL, NULL, &ssl_free_SslErrors); ssl_ex_index_ssl_cert_chain = SSL_get_ex_new_index(0, (void *) "ssl_cert_chain", NULL, NULL, &ssl_free_CertChain); ssl_ex_index_ssl_validation_counter = SSL_get_ex_new_index(0, (void *) "ssl_validation_counter", NULL, NULL, &ssl_free_int); } /// \ingroup ServerProtocolSSLInternal static int ssl_load_crl(SSL_CTX *sslContext, const char *CRLfile) { X509_STORE *st = SSL_CTX_get_cert_store(sslContext); X509_CRL *crl; BIO *in = BIO_new_file(CRLfile, "r"); int count = 0; @@ -897,40 +902,42 @@ #if X509_V_FLAG_CRL_CHECK if (port.sslContextFlags & SSL_FLAG_VERIFY_CRL_ALL) X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); else if (port.sslContextFlags & SSL_FLAG_VERIFY_CRL) X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK); #endif } else { debugs(83, 9, "Not requiring any client certificates"); SSL_CTX_set_verify(sslContext, SSL_VERIFY_NONE, NULL); } if (port.dhParams.get()) { SSL_CTX_set_tmp_dh(sslContext, port.dhParams.get()); } if (port.sslContextFlags & SSL_FLAG_DONT_VERIFY_DOMAIN) SSL_CTX_set_ex_data(sslContext, ssl_ctx_ex_index_dont_verify_domain, (void *) -1); + setSessionCallbacks(sslContext); + return true; } SSL_CTX * sslCreateServerContext(AnyP::PortCfg &port) { int ssl_error; SSL_CTX *sslContext; const char *keyfile, *certfile; certfile = port.cert; keyfile = port.key; ssl_initialize(); if (!keyfile) keyfile = certfile; if (!certfile) certfile = keyfile; @@ -1656,21 +1663,204 @@ Ssl::CertError & Ssl::CertError::operator = (const CertError &old) { code = old.code; cert.resetAndLock(old.cert.get()); return *this; } bool Ssl::CertError::operator == (const CertError &ce) const { return code == ce.code && cert.get() == ce.cert.get(); } bool Ssl::CertError::operator != (const CertError &ce) const { return code != ce.code || cert.get() != ce.cert.get(); } +int +store_session_cb(SSL *ssl, SSL_SESSION *session) +{ + if (!SslSessionCache) + return 0; + + debugs(83, 5, HERE << "Request to store SSL Session "); + + SSL_SESSION_set_timeout(session, Config.SSL.session_ttl); + + unsigned char *id = session->session_id; + unsigned int idlen = session->session_id_length; + unsigned char key[SSL_SESSION_ID_SIZE]; + memset(key, 0, sizeof(key)); + memcpy(key, id, idlen); + int pos; + Ipc::MemMap::Slot *slotW = SslSessionCache->openForWriting((const cache_key*)key, pos); + if (slotW) { + int lenRequired = i2d_SSL_SESSION(session, NULL); + if (lenRequired < SSL_SESSION_MAX_SIZE) { + unsigned char *p = (unsigned char *)slotW->p; + lenRequired = i2d_SSL_SESSION(session, &p); + slotW->set(key, NULL, lenRequired, squid_curtime + Config.SSL.session_ttl); + } + SslSessionCache->closeForWriting(pos); + debugs(83, 5, HERE << " wrote an ssl session entry of size " << lenRequired << " at pos " << pos); + } + return 0; +} + +void +remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID) +{ + if (!SslSessionCache) + return ; + + debugs(83, 5, HERE << "Request to remove corrupted or not valid SSL Session "); + int pos; + Ipc::MemMap::Slot const *slot = SslSessionCache->openForReading((const cache_key*)sessionID, pos); + if (slot == NULL) + return; + SslSessionCache->closeForReading(pos); + // What if we are not able to remove the session? + // Maybe schedule a job to remove it later? + // For now we just have an invalid entry in cache until will be expired + // The opneSSL will reject it when we try to use it + SslSessionCache->free(pos); +} + +SSL_SESSION * +get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy) +{ + if (!SslSessionCache) + return NULL; + + + SSL_SESSION *session = NULL; + const unsigned int *p; + p = (unsigned int *)sessionID; + debugs(83, 5, HERE << "Request to search for SSL Session of len:" << + len << p[0] << ":" << p[1]); + + int pos; + Ipc::MemMap::Slot const *slot = SslSessionCache->openForReading((const cache_key*)sessionID, pos); + if (slot != NULL) { + if (slot->expire > squid_curtime) { + const unsigned char *ptr = slot->p; + session = d2i_SSL_SESSION(NULL, &ptr, slot->pSize); + debugs(83, 5, HERE << "Session retrieved from cache at pos " << pos); + } else + debugs(83, 5, HERE << "Session in cache expired"); + SslSessionCache->closeForReading(pos); + } + + if (!session) + debugs(83, 5, HERE << "Failed to retrieved from cache\n"); + + // With the parameter copy the callback can require the SSL engine + // to increment the reference count of the SSL_SESSION object, Normally + // the reference count is not incremented and therefore the session must + // not be explicitly freed with SSL_SESSION_free(3). + *copy = 0; + return session; +} + +static +void +setSessionCallbacks(SSL_CTX *ctx) +{ + if (SslSessionCache) { + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ctx, store_session_cb); + SSL_CTX_sess_set_remove_cb(ctx, remove_session_cb); + SSL_CTX_sess_set_get_cb(ctx, get_session_cb); + } +} + +static +bool +isSslServer() +{ + if (Config.Sockaddr.https) + return true; + + for (AnyP::PortCfg *s = Config.Sockaddr.http; s; s = s->next) { + if (s->flags.tunnelSslBumping) + return true; + } + + return false; +} + +void +Ssl::initialize_session_cache() +{ + + if (!isSslServer()) //no need to configure ssl session cache. + return; + + int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot); + if (IamWorkerProcess() && configuredItems) + SslSessionCache = new Ipc::MemMap(SslSessionCacheName); + else + SslSessionCache = NULL; + + for (AnyP::PortCfg *s = ::Config.Sockaddr.https; s; s = s->next) { + if (s->staticSslContext.get() != NULL) + setSessionCallbacks(s->staticSslContext.get()); + } + + for (AnyP::PortCfg *s = ::Config.Sockaddr.http; s; s = s->next) { + if (s->staticSslContext.get() != NULL) + setSessionCallbacks(s->staticSslContext.get()); + } +} + +void +destruct_session_cache() +{ + delete SslSessionCache; +} + +/// initializes shared memory segments used by MemStore +class SharedSessionCacheRr: public Ipc::Mem::RegisteredRunner +{ +public: + /* RegisteredRunner API */ + SharedSessionCacheRr(): owner(NULL) {} + virtual void run(const RunnerRegistry &); + virtual ~SharedSessionCacheRr(); + +protected: + virtual void create(const RunnerRegistry &); + +private: + Ipc::MemMap::Owner *owner; +}; + +RunnerRegistrationEntry(rrAfterConfig, SharedSessionCacheRr); + +void +SharedSessionCacheRr::run(const RunnerRegistry &r) +{ + Ipc::Mem::RegisteredRunner::run(r); +} + +void +SharedSessionCacheRr::create(const RunnerRegistry &) +{ + if (!isSslServer()) //no need to configure ssl session cache. + return; + + int items; + items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot); + if (items) + owner = Ipc::MemMap::Init(SslSessionCacheName, items); +} + +SharedSessionCacheRr::~SharedSessionCacheRr() +{ + delete owner; +} + #endif /* USE_SSL */ === modified file 'src/ssl/support.h' --- src/ssl/support.h 2013-07-27 13:37:29 +0000 +++ src/ssl/support.h 2014-01-08 11:01:04 +0000 @@ -260,40 +260,52 @@ */ bool checkX509ServerValidity(X509 *cert, const char *server); /** \ingroup ServerProtocolSSLAPI * Convert a given ASN1_TIME to a string form. \param tm the time in ASN1_TIME form \param buf the buffer to write the output \param len write at most len bytes \return The number of bytes written */ int asn1timeToString(ASN1_TIME *tm, char *buf, int len); /** \ingroup ServerProtocolSSLAPI * Sets the hostname for the Server Name Indication (SNI) TLS extension * if supported by the used openssl toolkit. \return true if SNI set false otherwise */ bool setClientSNI(SSL *ssl, const char *fqdn); + +/** + \ingroup ServerProtocolSSLAPI + * Initializes the shared session cache if configured +*/ +void initialize_session_cache(); + +/** + \ingroup ServerProtocolSSLAPI + * Destroy the shared session cache if configured +*/ +void destruct_session_cache(); } //namespace Ssl #if _SQUID_WINDOWS_ #if defined(__cplusplus) /** \cond AUTODOCS-IGNORE */ namespace Squid { /** \endcond */ /// \ingroup ServerProtocolSSLAPI inline int SSL_set_fd(SSL *ssl, int fd) { return ::SSL_set_fd(ssl, _get_osfhandle(fd)); } /// \ingroup ServerProtocolSSLAPI #define SSL_set_fd(ssl,fd) Squid::SSL_set_fd(ssl,fd)