ssl_scache_shmcb.c [plain text]
#include "ssl_private.h"
typedef struct {
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;
unsigned int subcache_num;
unsigned int index_num;
unsigned int subcache_size;
unsigned int subcache_data_offset;
unsigned int subcache_data_size;
} SHMCBHeader;
typedef struct {
unsigned int idx_pos, idx_used;
unsigned int data_pos, data_used;
} SHMCBSubcache;
typedef struct {
time_t expires;
unsigned int data_pos;
unsigned int data_used;
unsigned char s_id2;
unsigned char removed;
} SHMCBIndex;
#define SHMCB_SUBCACHE(pHeader, num) \
(SHMCBSubcache *)(((unsigned char *)(pHeader)) + \
sizeof(SHMCBHeader) + \
(num) * ((pHeader)->subcache_size))
#define SHMCB_MASK(pHeader, id) \
SHMCB_SUBCACHE((pHeader), *(id) & ((pHeader)->subcache_num - 1))
#define SHMCB_MASK_DBG(pHeader, id) \
*(id), (*(id) & ((pHeader)->subcache_num - 1))
#define SHMCB_INDEX(pSubcache, num) \
((SHMCBIndex *)(((unsigned char *)pSubcache) + \
sizeof(SHMCBSubcache)) + num)
#define SHMCB_DATA(pHeader, pSubcache) \
((unsigned char *)(pSubcache) + (pHeader)->subcache_data_offset)
#define SHMCB_CYCLIC_INCREMENT(val,inc,mod) \
(((val) + (inc)) % (mod))
#define SHMCB_CYCLIC_SPACE(val1,val2,mod) \
((val2) >= (val1) ? ((val2) - (val1)) : \
((val2) + (mod) - (val1)))
static void shmcb_cyclic_ntoc_memcpy(unsigned int buf_size, unsigned char *data,
unsigned int dest_offset, unsigned char *src,
unsigned int src_len)
{
if (dest_offset + src_len < buf_size)
memcpy(data + dest_offset, src, src_len);
else {
memcpy(data + dest_offset, src, buf_size - dest_offset);
memcpy(data, src + buf_size - dest_offset,
src_len + dest_offset - buf_size);
}
}
static void shmcb_cyclic_cton_memcpy(unsigned int buf_size, unsigned char *dest,
unsigned char *data, unsigned int src_offset,
unsigned int src_len)
{
if (src_offset + src_len < buf_size)
memcpy(dest, data + src_offset, src_len);
else {
memcpy(dest, data + src_offset, buf_size - src_offset);
memcpy(dest + buf_size - src_offset, data,
src_len + src_offset - buf_size);
}
}
static void shmcb_subcache_expire(server_rec *, SHMCBHeader *, SHMCBSubcache *);
static BOOL shmcb_subcache_store(server_rec *, SHMCBHeader *, SHMCBSubcache *,
UCHAR *, unsigned int, UCHAR *, time_t);
static SSL_SESSION *shmcb_subcache_retrieve(server_rec *, SHMCBHeader *, SHMCBSubcache *,
UCHAR *, unsigned int);
static BOOL shmcb_subcache_remove(server_rec *, SHMCBHeader *, SHMCBSubcache *,
UCHAR *, unsigned int);
void ssl_scache_shmcb_init(server_rec *s, apr_pool_t *p)
{
SSLModConfigRec *mc = myModConfig(s);
void *shm_segment;
apr_size_t shm_segsize;
apr_status_t rv;
SHMCBHeader *header;
unsigned int num_subcache, num_idx, loop;
if (mc->szSessionCacheDataFile == NULL) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"SSLSessionCache required");
ssl_die();
}
rv = apr_shm_create(&(mc->pSessionCacheDataMM),
mc->nSessionCacheDataSize,
NULL, mc->pPool);
if (APR_STATUS_IS_ENOTIMPL(rv)) {
apr_shm_remove(mc->szSessionCacheDataFile, mc->pPool);
rv = apr_shm_create(&(mc->pSessionCacheDataMM),
mc->nSessionCacheDataSize,
mc->szSessionCacheDataFile,
mc->pPool);
}
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
"could not allocate shared memory for shmcb "
"session cache");
ssl_die();
}
shm_segment = apr_shm_baseaddr_get(mc->pSessionCacheDataMM);
shm_segsize = apr_shm_size_get(mc->pSessionCacheDataMM);
if (shm_segsize < (5 * sizeof(SHMCBHeader))) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"shared memory segment too small");
ssl_die();
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"shmcb_init allocated %" APR_SIZE_T_FMT
" bytes of shared memory",
shm_segsize);
shm_segsize -= sizeof(SHMCBHeader);
num_idx = (shm_segsize) / 120;
num_subcache = 256;
while ((num_idx / num_subcache) < (2 * num_subcache))
num_subcache /= 2;
num_idx /= num_subcache;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"for %" APR_SIZE_T_FMT " bytes (%" APR_SIZE_T_FMT
" including header), recommending %u subcaches, "
"%u indexes each", shm_segsize,
shm_segsize + sizeof(SHMCBHeader), num_subcache, num_idx);
if (num_idx < 5) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"shared memory segment too small");
ssl_die();
}
header = shm_segment;
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;
header->subcache_num = num_subcache;
header->subcache_size = ((size_t)(shm_segsize / num_subcache) &
~(size_t)(sizeof(unsigned long) - 1));
header->subcache_data_offset = sizeof(SHMCBSubcache) +
num_idx * sizeof(SHMCBIndex);
header->subcache_data_size = header->subcache_size -
header->subcache_data_offset;
header->index_num = num_idx;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"shmcb_init_memory choices follow");
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"subcache_num = %u", header->subcache_num);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"subcache_size = %u", header->subcache_size);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"subcache_data_offset = %u", header->subcache_data_offset);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"subcache_data_size = %u", header->subcache_data_size);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"index_num = %u", header->index_num);
for (loop = 0; loop < header->subcache_num; loop++) {
SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
subcache->idx_pos = subcache->idx_used = 0;
subcache->data_pos = subcache->data_used = 0;
}
ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
"Shared memory session cache initialised");
mc->tSessionCacheDataTable = shm_segment;
}
void ssl_scache_shmcb_kill(server_rec *s)
{
SSLModConfigRec *mc = myModConfig(s);
if (mc->pSessionCacheDataMM != NULL) {
apr_shm_destroy(mc->pSessionCacheDataMM);
mc->pSessionCacheDataMM = NULL;
}
return;
}
BOOL ssl_scache_shmcb_store(server_rec *s, UCHAR *id, int idlen,
time_t timeout, SSL_SESSION * pSession)
{
SSLModConfigRec *mc = myModConfig(s);
BOOL to_return = FALSE;
unsigned char encoded[SSL_SESSION_MAX_DER];
unsigned char *ptr_encoded;
unsigned int len_encoded;
SHMCBHeader *header = mc->tSessionCacheDataTable;
SHMCBSubcache *subcache = SHMCB_MASK(header, id);
ssl_mutex_on(s);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"ssl_scache_shmcb_store (0x%02x -> subcache %d)",
SHMCB_MASK_DBG(header, id));
if (idlen < 4) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
"(%u bytes)", idlen);
goto done;
}
len_encoded = i2d_SSL_SESSION(pSession, NULL);
if (len_encoded > SSL_SESSION_MAX_DER) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"session is too big (%u bytes)", len_encoded);
goto done;
}
ptr_encoded = encoded;
len_encoded = i2d_SSL_SESSION(pSession, &ptr_encoded);
if (!shmcb_subcache_store(s, header, subcache, encoded,
len_encoded, id, timeout)) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"can't store a session!");
goto done;
}
header->stat_stores++;
to_return = TRUE;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"leaving ssl_scache_shmcb_store successfully");
done:
ssl_mutex_off(s);
return to_return;
}
SSL_SESSION *ssl_scache_shmcb_retrieve(server_rec *s, UCHAR *id, int idlen)
{
SSLModConfigRec *mc = myModConfig(s);
SSL_SESSION *pSession = NULL;
SHMCBHeader *header = mc->tSessionCacheDataTable;
SHMCBSubcache *subcache = SHMCB_MASK(header, id);
ssl_mutex_on(s);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"ssl_scache_shmcb_retrieve (0x%02x -> subcache %d)",
SHMCB_MASK_DBG(header, id));
if (idlen < 4) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
"(%u bytes)", idlen);
goto done;
}
pSession = shmcb_subcache_retrieve(s, header, subcache, id, idlen);
if (pSession)
header->stat_retrieves_hit++;
else
header->stat_retrieves_miss++;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"leaving ssl_scache_shmcb_retrieve successfully");
done:
ssl_mutex_off(s);
return pSession;
}
void ssl_scache_shmcb_remove(server_rec *s, UCHAR *id, int idlen)
{
SSLModConfigRec *mc = myModConfig(s);
SHMCBHeader *header = mc->tSessionCacheDataTable;
SHMCBSubcache *subcache = SHMCB_MASK(header, id);
ssl_mutex_on(s);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"ssl_scache_shmcb_remove (0x%02x -> subcache %d)",
SHMCB_MASK_DBG(header, id));
if (idlen < 4) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
"(%u bytes)", idlen);
goto done;
}
if (shmcb_subcache_remove(s, header, subcache, id, idlen))
header->stat_removes_hit++;
else
header->stat_removes_miss++;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"leaving ssl_scache_shmcb_remove successfully");
done:
ssl_mutex_off(s);
}
void ssl_scache_shmcb_status(request_rec *r, int flags, apr_pool_t *p)
{
server_rec *s = r->server;
SSLModConfigRec *mc = myModConfig(s);
void *shm_segment = apr_shm_baseaddr_get(mc->pSessionCacheDataMM);
SHMCBHeader *header = shm_segment;
unsigned int loop, total = 0, cache_total = 0, non_empty_subcaches = 0;
time_t idx_expiry, min_expiry = 0, max_expiry = 0, average_expiry = 0;
time_t now = time(NULL);
double expiry_total = 0;
int index_pct, cache_pct;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "inside shmcb_status");
ssl_mutex_on(s);
for (loop = 0; loop < header->subcache_num; loop++) {
SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
shmcb_subcache_expire(s, header, subcache);
total += subcache->idx_used;
cache_total += subcache->data_used;
if (subcache->idx_used) {
SHMCBIndex *idx = SHMCB_INDEX(subcache, subcache->idx_pos);
non_empty_subcaches++;
idx_expiry = idx->expires;
expiry_total += (double)idx_expiry;
max_expiry = ((idx_expiry > max_expiry) ? idx_expiry : max_expiry);
if (!min_expiry)
min_expiry = idx_expiry;
else
min_expiry = ((idx_expiry < min_expiry) ? idx_expiry : min_expiry);
}
}
ssl_mutex_off(s);
index_pct = (100 * total) / (header->index_num *
header->subcache_num);
cache_pct = (100 * cache_total) / (header->subcache_data_size *
header->subcache_num);
ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%d</b> "
"bytes, current sessions: <b>%d</b><br>",
mc->nSessionCacheDataSize, total);
ap_rprintf(r, "subcaches: <b>%d</b>, indexes per subcache: <b>%d</b><br>",
header->subcache_num, header->index_num);
if (non_empty_subcaches) {
average_expiry = (time_t)(expiry_total / (double)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>",
index_pct, cache_pct);
ap_rprintf(r, "total sessions stored since starting: <b>%lu</b><br>",
header->stat_stores);
ap_rprintf(r, "total sessions expired since starting: <b>%lu</b><br>",
header->stat_expiries);
ap_rprintf(r, "total (pre-expiry) sessions scrolled out of the cache: "
"<b>%lu</b><br>", header->stat_scrolled);
ap_rprintf(r, "total retrieves since starting: <b>%lu</b> hit, "
"<b>%lu</b> miss<br>", header->stat_retrieves_hit,
header->stat_retrieves_miss);
ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, "
"<b>%lu</b> miss<br>", header->stat_removes_hit,
header->stat_removes_miss);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "leaving shmcb_status");
}
static void shmcb_subcache_expire(server_rec *s, SHMCBHeader *header,
SHMCBSubcache *subcache)
{
time_t now = time(NULL);
unsigned int loop = 0;
unsigned int new_idx_pos = subcache->idx_pos;
SHMCBIndex *idx = NULL;
while (loop < subcache->idx_used) {
idx = SHMCB_INDEX(subcache, new_idx_pos);
if (idx->expires > now)
break;
loop++;
new_idx_pos = SHMCB_CYCLIC_INCREMENT(new_idx_pos, 1, header->index_num);
}
if (!loop)
return;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"will be expiring %u sessions", loop);
if (loop == subcache->idx_used) {
subcache->idx_used = 0;
subcache->data_used = 0;
} else {
unsigned int diff = SHMCB_CYCLIC_SPACE(subcache->data_pos,
idx->data_pos,
header->subcache_data_size);
subcache->idx_used -= loop;
subcache->idx_pos = new_idx_pos;
subcache->data_used -= diff;
subcache->data_pos = idx->data_pos;
}
header->stat_expiries += loop;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"we now have %u sessions", subcache->idx_used);
}
static BOOL shmcb_subcache_store(server_rec *s, SHMCBHeader *header,
SHMCBSubcache *subcache,
UCHAR *data, unsigned int data_len,
UCHAR *id, time_t expiry)
{
unsigned int new_offset, new_idx;
SHMCBIndex *idx;
if ((data_len > header->subcache_data_size) || (data_len > SSL_SESSION_MAX_DER)) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"inserting session larger (%d) than subcache data area (%d)",
data_len, header->subcache_data_size);
return FALSE;
}
shmcb_subcache_expire(s, header, subcache);
if (header->subcache_data_size - subcache->data_used < data_len
|| subcache->idx_used == header->index_num) {
unsigned int loop = 0;
idx = SHMCB_INDEX(subcache, subcache->idx_pos);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"about to force-expire, subcache: idx_used=%d, "
"data_used=%d", subcache->idx_used, subcache->data_used);
do {
SHMCBIndex *idx2;
subcache->idx_pos = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, 1,
header->index_num);
subcache->idx_used--;
if (!subcache->idx_used) {
subcache->data_used = 0;
break;
}
idx2 = SHMCB_INDEX(subcache, subcache->idx_pos);
subcache->data_used -= SHMCB_CYCLIC_SPACE(idx->data_pos, idx2->data_pos,
header->subcache_data_size);
subcache->data_pos = idx2->data_pos;
header->stat_scrolled++;
idx = idx2;
loop++;
} while (header->subcache_data_size - subcache->data_used < data_len);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"finished force-expire, subcache: idx_used=%d, "
"data_used=%d", subcache->idx_used, subcache->data_used);
}
new_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used,
header->subcache_data_size);
shmcb_cyclic_ntoc_memcpy(header->subcache_data_size,
SHMCB_DATA(header, subcache), new_offset,
data, data_len);
subcache->data_used += data_len;
new_idx = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, subcache->idx_used,
header->index_num);
idx = SHMCB_INDEX(subcache, new_idx);
idx->expires = expiry;
idx->data_pos = new_offset;
idx->data_used = data_len;
idx->s_id2 = id[1];
idx->removed = 0;
subcache->idx_used++;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"insert happened at idx=%d, data=%d", new_idx, new_offset);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"finished insert, subcache: idx_pos/idx_used=%d/%d, "
"data_pos/data_used=%d/%d",
subcache->idx_pos, subcache->idx_used,
subcache->data_pos, subcache->data_used);
return TRUE;
}
static SSL_SESSION *shmcb_subcache_retrieve(server_rec *s, SHMCBHeader *header,
SHMCBSubcache *subcache, UCHAR *id,
unsigned int idlen)
{
unsigned int pos;
unsigned int loop = 0;
shmcb_subcache_expire(s, header, subcache);
pos = subcache->idx_pos;
while (loop < subcache->idx_used) {
SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
if ((idx->s_id2 == id[1]) && !idx->removed) {
SSL_SESSION *pSession;
unsigned char *s_id;
unsigned int s_idlen;
unsigned char tempasn[SSL_SESSION_MAX_DER];
MODSSL_D2I_SSL_SESSION_CONST unsigned char *ptr = tempasn;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"possible match at idx=%d, data=%d", pos, idx->data_pos);
shmcb_cyclic_cton_memcpy(header->subcache_data_size,
tempasn, SHMCB_DATA(header, subcache),
idx->data_pos, idx->data_used);
pSession = d2i_SSL_SESSION(NULL, &ptr, idx->data_used);
if (!pSession) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"shmcb_subcache_retrieve internal error");
return NULL;
}
s_id = SSL_SESSION_get_session_id(pSession);
s_idlen = SSL_SESSION_get_session_id_length(pSession);
if (s_idlen == idlen && memcmp(s_id, id, idlen) == 0) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"shmcb_subcache_retrieve returning matching session");
return pSession;
}
SSL_SESSION_free(pSession);
}
loop++;
pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"shmcb_subcache_retrieve found no match");
return NULL;
}
static BOOL shmcb_subcache_remove(server_rec *s, SHMCBHeader *header,
SHMCBSubcache *subcache,
UCHAR *id, unsigned int idlen)
{
unsigned int pos;
unsigned int loop = 0;
BOOL to_return = FALSE;
pos = subcache->idx_pos;
while (!to_return && (loop < subcache->idx_used)) {
SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
if ((idx->s_id2 == id[1]) && !idx->removed) {
SSL_SESSION *pSession;
unsigned char *s_id;
unsigned int s_idlen;
unsigned char tempasn[SSL_SESSION_MAX_DER];
MODSSL_D2I_SSL_SESSION_CONST unsigned char *ptr = tempasn;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"possible match at idx=%d, data=%d", pos, idx->data_pos);
shmcb_cyclic_cton_memcpy(header->subcache_data_size,
tempasn, SHMCB_DATA(header, subcache),
idx->data_pos, idx->data_used);
pSession = d2i_SSL_SESSION(NULL, &ptr, idx->data_used);
if (!pSession) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"shmcb_subcache_remove internal error");
return FALSE;
}
s_id = SSL_SESSION_get_session_id(pSession);
s_idlen = SSL_SESSION_get_session_id_length(pSession);
if (s_idlen == idlen && memcmp(s_id, id, idlen) == 0) {
idx->removed = 1;
to_return = TRUE;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"shmcb_subcache_remove removing matching session");
}
SSL_SESSION_free(pSession);
}
loop++;
pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
}
return to_return;
}