From f528fff46bc00426f0329b46cbf77a88c92d3f8d Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Thu, 23 Nov 2017 19:43:17 +0100 Subject: [PATCH] MEDIUM: cache: store sha1 for hashing the cache key The cache was relying on the txn->uri for creating its key, which was a big problem when there was no log activated. This patch does a sha1 of the host + uri, and stores it in the txn. When a object is stored, the eb32node uses the first 32 bits of the hash as a key, and the whole hash is stored in the cache entry. During a lookup, the truncated hash is used, and when it matches an entry we check the real sha1. --- include/types/proto_http.h | 1 + src/cache.c | 60 ++++++++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/include/types/proto_http.h b/include/types/proto_http.h index cf0fdb698..9de141031 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -319,6 +319,7 @@ struct http_txn { /* 1 unused byte here */ short status; /* HTTP status from the server, negative if from proxy */ + char cache_hash[20]; /* Store the cache hash */ char *uri; /* first line if log needed, NULL otherwise */ char *cli_cookie; /* cookie presented by the client, in capture mode */ char *srv_cookie; /* cookie presented by the server, in capture mode */ diff --git a/src/cache.c b/src/cache.c index 349101520..87c1da75f 100644 --- a/src/cache.c +++ b/src/cache.c @@ -11,6 +11,7 @@ */ #include +#include #include #include @@ -63,6 +64,7 @@ struct cache_entry { unsigned int latest_validation; /* latest validation date */ unsigned int expire; /* expiration date */ struct eb32_node eb; /* ebtree node used to hold the cache object */ + char hash[20]; unsigned char data[0]; }; @@ -71,16 +73,21 @@ struct cache_entry { static struct list caches = LIST_HEAD_INIT(caches); static struct cache *tmp_cache_config = NULL; -struct cache_entry *entry_exist(struct cache *cache, struct cache_entry *new_entry) +struct cache_entry *entry_exist(struct cache *cache, char *hash) { struct eb32_node *node; struct cache_entry *entry; - node = eb32_lookup(&cache->entries, new_entry->eb.key); + node = eb32_lookup(&cache->entries, (*(unsigned int *)hash)); if (!node) return NULL; entry = eb32_entry(node, struct cache_entry, eb); + + /* if that's not the right node */ + if (memcmp(entry->hash, hash, sizeof(entry->hash))) + return NULL; + if (entry->expire > now.tv_sec) { return entry; } else { @@ -443,11 +450,12 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px, cache_ctx->first_block = first; object = (struct cache_entry *)first->data; - object->eb.key = hash_djb2(txn->uri, strlen(txn->uri)); + object->eb.key = (*(unsigned int *)&txn->cache_hash); + memcpy(object->hash, txn->cache_hash, sizeof(object->hash)); /* Insert the node later on caching success */ shctx_lock(shctx); - if (entry_exist((struct cache *)rule->arg.act.p[0], object)) { + if (entry_exist((struct cache *)rule->arg.act.p[0], txn->cache_hash)) { shctx_unlock(shctx); if (filter->ctx) { object->eb.key = 0; @@ -592,19 +600,53 @@ err: return ACT_RET_PRS_ERR; } +/* This produces a sha1 hash of the concatenation of the first + * occurrence of the Host header followed by the path component if it + * begins with a slash ('/'). */ +int sha1_hosturi(struct http_txn *txn) +{ + struct hdr_ctx ctx; + + blk_SHA_CTX sha1_ctx; + struct chunk *trash; + char *path; + char *end; + trash = get_trash_chunk(); + + /* retrive the host */ + ctx.idx = 0; + if (!http_find_header2("Host", 4, txn->req.chn->buf->p, &txn->hdr_idx, &ctx)) + return 0; + chunk_strncat(trash, ctx.line + ctx.val, ctx.vlen); + + /* now retrieve the path */ + end = txn->req.chn->buf->p + txn->req.sl.rq.u + txn->req.sl.rq.u_l; + path = http_get_path(txn); + if (!path) + return 0; + chunk_strncat(trash, path, end - path); + + /* hash everything */ + blk_SHA1_Init(&sha1_ctx); + blk_SHA1_Update(&sha1_ctx, trash->str, trash->len); + blk_SHA1_Final((unsigned char *)txn->cache_hash, &sha1_ctx); + + return 1; +} + + enum act_return http_action_req_cache_use(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *s, int flags) { - struct cache_entry search_entry; struct cache_entry *res; - struct cache *cache = (struct cache *)rule->arg.act.p[0]; - search_entry.eb.key = hash_djb2(s->txn->uri, strlen(s->txn->uri)); + sha1_hosturi(s->txn); + shctx_lock(shctx_ptr(cache)); - res = entry_exist(cache, &search_entry); + res = entry_exist(cache, s->txn->cache_hash); if (res) { struct appctx *appctx; shctx_row_inc_hot(shctx_ptr(cache), block_ptr(res)); @@ -900,7 +942,7 @@ static int cli_io_handler_show_cache(struct appctx *appctx) } entry = container_of(node, struct cache_entry, eb); - chunk_appendf(&trash, "%p (size: %u (%u blocks), refcount:%u, expire: %d)\n", entry, block_ptr(entry)->len, block_ptr(entry)->block_count, block_ptr(entry)->refcount, entry->expire - (int)now.tv_sec); + chunk_appendf(&trash, "%p hash:%u size:%u (%u blocks), refcount:%u, expire:%d\n", entry, (*(unsigned int *)entry->hash), block_ptr(entry)->len, block_ptr(entry)->block_count, block_ptr(entry)->refcount, entry->expire - (int)now.tv_sec); next_key = node->key + 1; appctx->ctx.cli.i0 = next_key;