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-10 16:28:32 +0000 @@ -0,0 +1,330 @@ +/* + * 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, "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, "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) +{ + debugs(54, 5, "trying to open slot for key " << storeKeyText(key) + << " for writing in map [" << path << ']'); + const int idx = slotIndexByKey(key); + + if (Slot *slot = openForWritingAt(idx)) { + fileno = idx; + return slot; + } + + return NULL; +} + +Ipc::MemMap::Slot * +Ipc::MemMap::openForWritingAt(const sfileno fileno, bool overwriteExisting) +{ + Slot &s = shared->slots[fileno]; + ReadWriteLock &lock = s.lock; + + if (lock.lockExclusive()) { + assert(s.writing() && !s.reading()); + + // bail if we cannot empty this position + if (!s.waitingToBeFreed && !s.empty() && !overwriteExisting) { + lock.unlockExclusive(); + debugs(54, 5, "cannot open existing entry " << fileno << + " for writing " << path); + return NULL; + } + + // free if the entry was used, keeping the entry locked + if (s.waitingToBeFreed || !s.empty()) + freeLocked(s, true); + + assert(s.empty()); + ++shared->count; + + debugs(54, 5, "opened slot at " << fileno << + " for writing in map [" << path << ']'); + return &s; // and keep the entry locked + } + + debugs(54, 5, "failed to open slot at " << fileno << + " for writing in map [" << path << ']'); + return NULL; +} + +void +Ipc::MemMap::closeForWriting(const sfileno fileno, bool lockForReading) +{ + debugs(54, 5, "closing slot at " << fileno << " for writing and " + "openning for reading in map [" << path << ']'); + assert(valid(fileno)); + Slot &s = shared->slots[fileno]; + assert(s.writing()); + 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, "abort writing slot at " << fileno << + " in map [" << path << ']'); + assert(valid(fileno)); + Slot &s = shared->slots[fileno]; + assert(s.writing()); + freeLocked(s, false); +} + +const Ipc::MemMap::Slot * +Ipc::MemMap::peekAtReader(const sfileno fileno) const +{ + assert(valid(fileno)); + const Slot &s = shared->slots[fileno]; + if (s.reading()) + return &s; // immediate access by lock holder so no locking + if (s.writing()) + return NULL; // cannot read the slot when it is being written + assert(false); // must be locked for reading or writing + return NULL; +} + +void +Ipc::MemMap::free(const sfileno fileno) +{ + debugs(54, 5, "marking slot at " << fileno << " to be freed in" + " map [" << path << ']'); + + assert(valid(fileno)); + Slot &s = shared->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, "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, "opened slot at " << fileno << " for key " + << storeKeyText(key) << " for reading in map [" << path << + ']'); + return slot; // locked for reading + } + slot->lock.unlockShared(); + } + debugs(54, 5, "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) +{ + debugs(54, 5, "trying to open slot at " << fileno << " for " + "reading in map [" << path << ']'); + assert(valid(fileno)); + Slot &s = shared->slots[fileno]; + + if (!s.lock.lockShared()) { + debugs(54, 5, "failed to lock slot at " << fileno << " for " + "reading in map [" << path << ']'); + return NULL; + } + + if (s.empty()) { + s.lock.unlockShared(); + debugs(54, 7, "empty slot at " << fileno << " for " + "reading in map [" << path << ']'); + return NULL; + } + + if (s.waitingToBeFreed) { + s.lock.unlockShared(); + debugs(54, 7, "dirty slot at " << fileno << " for " + "reading in map [" << path << ']'); + return NULL; + } + + debugs(54, 5, "opened slot at " << fileno << " for reading in" + " map [" << path << ']'); + return &s; +} + +void +Ipc::MemMap::closeForReading(const sfileno fileno) +{ + debugs(54, 5, "closing slot at " << fileno << " for reading in " + "map [" << path << ']'); + assert(valid(fileno)); + Slot &s = shared->slots[fileno]; + assert(s.reading()); + 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 +{ + for (int i = 0; i < shared->limit; ++i) + shared->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, MEMMAP_SLOT_KEY_SIZE, shared->limit); +} + +Ipc::MemMap::Slot & +Ipc::MemMap::slotByKey(const cache_key *const key) +{ + return shared->slots[slotIndexByKey(key)]; +} + +/// unconditionally frees the already exclusively locked slot and releases lock +void +Ipc::MemMap::freeLocked(Slot &s, bool keepLocked) +{ + if (!s.empty() && cleaner) + cleaner->noteFreeMapSlot(&s - shared->slots.raw()); + + s.waitingToBeFreed = false; + memset(s.key, 0, sizeof(s.key)); + if (!keepLocked) + s.lock.unlockExclusive(); + --shared->count; + debugs(54, 5, "freed slot at " << (&s - shared->slots.raw()) << + " in map [" << path << ']'); +} + +/* Ipc::MemMapSlot */ +Ipc::MemMapSlot::MemMapSlot() +{ + 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 expireAt) +{ + memcpy(key, aKey, sizeof(key)); + if (block) + memcpy(p, block, blockSize); + pSize = blockSize; + expire = expireAt; +} + +bool +Ipc::MemMapSlot::sameKey(const cache_key *const aKey) const +{ + return (memcmp(key, aKey, sizeof(key)) == 0); +} + +bool +Ipc::MemMapSlot::empty() const +{ + for (unsigned char const*u = key; u < key + sizeof(key); ++u) { + if (*u) + return false; + } + return true; +} + +/* Ipc::MemMap::Shared */ + +Ipc::MemMap::Shared::Shared(const int aLimit, const size_t anExtrasSize): + limit(aLimit), extrasSize(anExtrasSize), count(0), slots(aLimit) +{ +} + +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); +} === added file 'src/ipc/MemMap.h' --- src/ipc/MemMap.h 1970-01-01 00:00:00 +0000 +++ src/ipc/MemMap.h 2014-01-10 17:05:57 +0000 @@ -0,0 +1,140 @@ +#ifndef SQUID_IPC_STORE_MAP_H +#define SQUID_IPC_STORE_MAP_H + +#include "Debug.h" +#include "ipc/mem/FlexibleArray.h" +#include "ipc/mem/Pointer.h" +#include "ipc/ReadWriteLock.h" +#include "SBuf.h" +#include "tools.h" +#include "typedefs.h" + +namespace Ipc +{ + +// The MEMMAP_SLOT_KEY_SIZE and MEMMAP_SLOT_DATA_SIZE must be enough big +// to hold cached keys and data. Currently MemMap used only to store SSL +// shared session data which have keys of 32bytes and at most 10K data +#define MEMMAP_SLOT_KEY_SIZE 32 +#define MEMMAP_SLOT_DATA_SIZE 10*1024 + +/// a MemMap basic element, holding basic shareable memory block info +class MemMapSlot +{ +public: + 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); + bool empty() const; + bool reading() const { return lock.readers; } + bool writing() const { return lock.writing; } + + Atomic::WordT waitingToBeFreed; ///< may be accessed w/o a lock + mutable ReadWriteLock lock; ///< protects slot data below + unsigned char key[MEMMAP_SLOT_KEY_SIZE]; ///< The entry key + unsigned char p[MEMMAP_SLOT_DATA_SIZE]; ///< The memory block; + size_t pSize; + time_t expire; +}; + +class MemMapCleaner; + +/// A map of MemMapSlots indexed by their keys, with read/write slot locking. +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); + ~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 + Ipc::Mem::FlexibleArray slots; ///< storage + }; + +public: + typedef Mem::Owner Owner; + + /// initialize shared memory + static Owner *Init(const char *const path, const int limit); + + MemMap(const char *const aPath); + + /// finds, locks and return a slot for an empty key position, + /// erasing the old entry (if any) + Slot *openForWriting(const cache_key *const key, sfileno &fileno); + + /// locks and returns a slot for the empty fileno position; if + /// overwriteExisting is false and the position is not empty, returns nil + Slot *openForWritingAt(sfileno fileno, bool overwriteExisting = true); + + /// 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); + + 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; + + /// The cleaner MemMapCleaner::noteFreeMapSlot method called when a + /// readable entry is freed. + MemMapCleaner *cleaner; + +protected: + static Owner *Init(const char *const path, const int limit, const size_t extrasSize); + + const SBuf 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 noteFreeMapSlot(const sfileno slotId) = 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-10 17:16:18 +0000 @@ -1040,40 +1040,43 @@ } #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(); + 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(); #if ICAP_CLIENT icapLogOpen(); === modified file 'src/ssl/support.cc' --- src/ssl/support.cc 2013-10-25 00:13:46 +0000 +++ src/ssl/support.cc 2014-01-10 17:26:38 +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,216 @@ 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(); } +static int +store_session_cb(SSL *ssl, SSL_SESSION *session) +{ + if (!SslSessionCache) + return 0; + + debugs(83, 5, "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[MEMMAP_SLOT_KEY_SIZE]; + // Session ids are of size 32bytes. They should always fit to a + // MemMap::Slot::key + assert(idlen <= MEMMAP_SLOT_KEY_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 < MEMMAP_SLOT_DATA_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, "wrote an ssl session entry of size " << lenRequired << " at pos " << pos); + } + return 0; +} + +static void +remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID) +{ + if (!SslSessionCache) + return ; + + debugs(83, 5, "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); + // TODO: + // 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 openSSL will reject it when we try to use it + SslSessionCache->free(pos); +} + +static 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, "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, "Session retrieved from cache at pos " << pos); + } else + debugs(83, 5, "Session in cache expired"); + SslSessionCache->closeForReading(pos); + } + + if (!session) + debugs(83, 5, "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; +} + +#define SSL_SESSION_ID_SIZE 32 +#define SSL_SESSION_MAX_SIZE 10*1024 + +void +Ssl::initialize_session_cache() +{ + + if (!isSslServer()) //no need to configure ssl session cache. + return; + + // Check if the MemMap keys and data are enough big to hold + // session ids and session data + assert(SSL_SESSION_ID_SIZE >= MEMMAP_SLOT_KEY_SIZE); + assert(SSL_SESSION_MAX_SIZE >= MEMMAP_SLOT_DATA_SIZE); + + + int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot); + if (IamWorkerProcess() && configuredItems) + SslSessionCache = new Ipc::MemMap(SslSessionCacheName); + else { + SslSessionCache = NULL; + return; + } + + 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)