ssl_scache_shmcb.c revision a64f647f3dfca4b531d4d624bdc806f550812eba
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* _ _
* _ __ ___ ___ __| | ___ ___| | mod_ssl
* | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
* | | | | | | (_) | (_| | \__ \__ \ |
* |_| |_| |_|\___/ \__,_|___|___/___/_|
* |_____|
* Session Cache via Shared Memory (Cyclic Buffer Variant)
*/
#include "ssl_private.h"
/*
* This shared memory based SSL session cache implementation was
* originally written by Geoff Thorpe <geoff geoffthorpe.net> for C2Net
* Europe as a contribution to Ralf Engelschall's mod_ssl project.
*
* Since rewritten by GT to not use alignment-fudging memcpys and reduce
* complexity.
*/
/*
* Header structure - the start of the shared-mem segment
*/
typedef struct {
/* Stats for cache operations */
unsigned long stat_stores;
unsigned long stat_expiries;
unsigned long stat_scrolled;
unsigned long stat_retrieves_hit;
unsigned long stat_retrieves_miss;
unsigned long stat_removes_hit;
unsigned long stat_removes_miss;
/* Number of subcaches */
unsigned int subcache_num;
/* How many indexes each subcache's queue has */
unsigned int index_num;
/* How large each subcache is, including the queue and data */
unsigned int subcache_size;
/* How far into each subcache the data area is (optimisation) */
unsigned int subcache_data_offset;
/* How large the data area in each subcache is (optimisation) */
unsigned int subcache_data_size;
} SHMCBHeader;
/*
* Subcache structure - the start of each subcache, followed by
* indexes then data
*/
typedef struct {
/* The start position and length of the cyclic buffer of indexes */
/* Same for the data area */
/*
* Index structure - each subcache has an array of these
*/
typedef struct {
/* absolute time this entry expires */
/* location within the subcache's data area */
unsigned int data_pos;
/* size (most logic ignores this, we keep it only to minimise memcpy) */
unsigned int data_used;
/* Optimisation to prevent ASN decoding unless a match is likely */
unsigned char s_id2;
/* Used to mark explicitly-removed sessions */
unsigned char removed;
} SHMCBIndex;
/* This macro takes a pointer to the header and a zero-based index and returns
* a pointer to the corresponding subcache. */
(SHMCBSubcache *)(((unsigned char *)(pHeader)) + \
sizeof(SHMCBHeader) + \
/* This macro takes a pointer to the header and a session id and returns a
* pointer to the corresponding subcache. */
/* This macro takes the same params as the last, generating two outputs for use
* in ap_log_error(...). */
/* This macro takes a pointer to a subcache and a zero-based index and returns
* a pointer to the corresponding SHMCBIndex. */
((SHMCBIndex *)(((unsigned char *)pSubcache) + \
sizeof(SHMCBSubcache)) + num)
/* This macro takes a pointer to the header and a subcache and returns a
* pointer to the corresponding data area. */
/*
* Cyclic functions - assists in "wrap-around"/modulo logic
*/
/* Addition modulo 'mod' */
/* Subtraction (or "distance between") modulo 'mod' */
/* A "normal-to-cyclic" memcpy. */
unsigned int dest_offset, unsigned char *src,
unsigned int src_len)
{
/* It be copied all in one go */
else {
/* Copy the two splits */
}
}
/* A "cyclic-to-normal" memcpy. */
unsigned char *data, unsigned int src_offset,
unsigned int src_len)
{
/* It be copied all in one go */
else {
/* Copy the two splits */
}
}
/* Prototypes for low-level subcache operations */
UCHAR *, unsigned int);
UCHAR *, unsigned int);
/*
* High-Level "handlers" as per ssl_scache.c
* subcache internals are deferred to shmcb_subcache_*** functions lower down
*/
{
void *shm_segment;
{
void *data;
const char *userdata_key = "ssl_scache_init";
if (!data) {
return;
}
}
/* Create shared memory segment */
"SSLSessionCache required");
ssl_die();
}
/* Use anonymous shm by default, fall back on name-based. */
if (APR_STATUS_IS_ENOTIMPL(rv)) {
/* For a name-based segment, remove it first in case of a
* previous unclean shutdown. */
}
if (rv != APR_SUCCESS) {
"could not allocate shared memory for shmcb "
"session cache");
ssl_die();
}
/* the segment is ridiculously small, bail out */
"shared memory segment too small");
ssl_die();
}
"shmcb_init allocated %" APR_SIZE_T_FMT
" bytes of shared memory",
/* Discount the header */
shm_segsize -= sizeof(SHMCBHeader);
/* Select the number of subcaches to create and how many indexes each
* should contain based on the size of the memory (the header has already
* around 150 bytes, so erring to division by 120 helps ensure we would
* exhaust data storage before index storage (except sslv2, where it's
* *slightly* the other way). From there, we select the number of subcaches
* to be a power of two, such that the number of indexes per subcache at
* least twice the number of subcaches. */
num_subcache = 256;
num_subcache /= 2;
num_idx /= num_subcache;
" including header), recommending %u subcaches, "
"%u indexes each", shm_segsize,
if (num_idx < 5) {
/* we're still too small, bail out */
"shared memory segment too small");
ssl_die();
}
/* OK, we're sorted */
header->stat_stores = 0;
header->stat_expiries = 0;
header->stat_scrolled = 0;
header->stat_retrieves_hit = 0;
header->stat_retrieves_miss = 0;
header->stat_removes_hit = 0;
header->stat_removes_miss = 0;
/* Convert the subcache size (in bytes) to a value that is suitable for
* structure alignment on the host platform, by rounding down if necessary.
* This assumes that sizeof(unsigned long) provides an appropriate
* alignment unit. */
~(size_t)(sizeof(unsigned long) - 1));
num_idx * sizeof(SHMCBIndex);
/* Output trace info */
"shmcb_init_memory choices follow");
/* The header is done, make the caches empty */
}
"Shared memory session cache initialised");
/* Success ... */
}
static void ssl_scache_shmcb_kill(server_rec *s)
{
}
return;
}
unsigned char *encoded,
unsigned int len_encoded)
{
ssl_mutex_on(s);
"ssl_scache_shmcb_store (0x%02x -> subcache %d)",
if (idlen < 4) {
"(%u bytes)", idlen);
goto done;
}
"can't store a session!");
goto done;
}
header->stat_stores++;
"leaving ssl_scache_shmcb_store successfully");
done:
ssl_mutex_off(s);
return to_return;
}
apr_pool_t *p)
{
ssl_mutex_on(s);
"ssl_scache_shmcb_retrieve (0x%02x -> subcache %d)",
if (idlen < 4) {
"(%u bytes)", idlen);
goto done;
}
/* Get the session corresponding to the session_id or NULL if it doesn't
* exist (or is flagged as "removed"). */
if (pSession)
else
"leaving ssl_scache_shmcb_retrieve successfully");
done:
ssl_mutex_off(s);
return pSession;
}
{
ssl_mutex_on(s);
"ssl_scache_shmcb_remove (0x%02x -> subcache %d)",
if (idlen < 4) {
"(%u bytes)", idlen);
goto done;
}
else
"leaving ssl_scache_shmcb_remove successfully");
done:
ssl_mutex_off(s);
}
{
server_rec *s = r->server;
double expiry_total = 0;
/* Perform the iteration inside the mutex to avoid corruption or invalid
* pointer arithmetic. The rest of our logic uses read-only header data so
* doesn't need the lock. */
ssl_mutex_on(s);
/* Iterate over the subcaches */
expiry_total += (double)idx_expiry;
if (!min_expiry)
else
}
}
ssl_mutex_off(s);
/* Generate HTML */
ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%d</b> "
"bytes, current sessions: <b>%d</b><br>",
ap_rprintf(r, "subcaches: <b>%d</b>, indexes per subcache: <b>%d</b><br>",
if (non_empty_subcaches) {
ap_rprintf(r, "time left on oldest entries' SSL sessions: ");
if (now < average_expiry)
ap_rprintf(r, "avg: <b>%d</b> seconds, (range: %d...%d)<br>",
(int)(average_expiry - now),
(int)(min_expiry - now),
(int)(max_expiry - now));
else
ap_rprintf(r, "expiry_threshold: <b>Calculation error!</b><br>");
}
ap_rprintf(r, "index usage: <b>%d%%</b>, cache usage: <b>%d%%</b><br>",
ap_rprintf(r, "total sessions stored since starting: <b>%lu</b><br>",
ap_rprintf(r, "total sessions expired since starting: <b>%lu</b><br>",
ap_rprintf(r, "total (pre-expiry) sessions scrolled out of the cache: "
ap_rprintf(r, "total retrieves since starting: <b>%lu</b> hit, "
ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, "
}
/*
* Subcache-level cache operations
*/
{
unsigned int loop = 0;
/* it hasn't expired yet, we're done iterating */
break;
loop++;
}
if (!loop)
/* Nothing to do */
return;
"will be expiring %u sessions", loop);
/* We're expiring everything, piece of cake */
} else {
/* There remain other indexes, so we can use idx to adjust 'data' */
/* Adjust the indexes */
/* Adjust the data area */
}
}
{
unsigned int new_offset, new_idx;
/* Sanity check the input */
"inserting session larger (%d) than subcache data area (%d)",
return FALSE;
}
/* If there are entries to expire, ditch them first. */
/* Loop until there is enough space to insert */
unsigned int loop = 0;
"about to force-expire, subcache: idx_used=%d, "
do {
/* Adjust the indexes by one */
/* There's nothing left */
break;
}
/* Adjust the data */
/* Stats */
header->stat_scrolled++;
/* Loop admin */
loop++;
"finished force-expire, subcache: idx_used=%d, "
}
/* HERE WE ASSUME THAT THE NEW SESSION SHOULD GO ON THE END! I'M NOT
* CHECKING WHETHER IT SHOULD BE GENUINELY "INSERTED" SOMEWHERE.
*
* We either fix that, or find out at a "higher" (read "mod_ssl")
* level whether it is possible to have distinct session caches for
* any attempted tomfoolery to do with different session timeouts.
* Knowing in advance that we can have a cache-wide constant timeout
* would make this stuff *MUCH* more efficient. Mind you, it's very
* efficient right now because I'm ignoring this problem!!!
*/
/* Insert the data */
/* Insert the index */
return TRUE;
}
unsigned int idlen)
{
unsigned int pos;
unsigned int loop = 0;
/* If there are entries to expire, ditch them first. */
/* Only consider 'idx' if;
* (a) the s_id2 byte matches
* (b) the "removed" flag isn't set.
*/
unsigned char *s_id;
unsigned int s_idlen;
unsigned char tempasn[SSL_SESSION_MAX_DER];
/* Copy the data */
/* Decode the session */
if (!pSession) {
"shmcb_subcache_retrieve internal error");
return NULL;
}
/* Found the matching session */
"shmcb_subcache_retrieve returning matching session");
return pSession;
}
}
/* Increment */
loop++;
}
"shmcb_subcache_retrieve found no match");
return NULL;
}
{
unsigned int pos;
unsigned int loop = 0;
/* Unlike the others, we don't do an expire-run first. This is to keep
* consistent statistics where a "remove" operation may actually be the
* higher layer spotting an expiry issue prior to us. Our caller is
* handling stats, so a failure return would be inconsistent if the
* intended session was in fact removed by an expiry run. */
/* Only consider 'idx' if the s_id2 byte matches and it's not already
* removed - easiest way to avoid costly ASN decodings. */
unsigned char *s_id;
unsigned int s_idlen;
unsigned char tempasn[SSL_SESSION_MAX_DER];
/* Copy the data */
/* Decode the session */
if (!pSession) {
"shmcb_subcache_remove internal error");
return FALSE;
}
/* Found the matching session, remove it quietly. */
"shmcb_subcache_remove removing matching session");
}
}
/* Increment */
loop++;
}
return to_return;
}
};