mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-27 22:03:31 +01:00
Implement replaycache_t for bug 6177, and unit tests for the preceding
This commit is contained in:
parent
43b81325b5
commit
8f63ef10ad
3
changes/bug6177
Normal file
3
changes/bug6177
Normal file
@ -0,0 +1,3 @@
|
||||
o Minor features:
|
||||
- Add replaycache_t structure, functions and unit tests, for future use
|
||||
in refactoring rend_service_introduce() for bug 6177.
|
@ -48,6 +48,7 @@ libtor_a_SOURCES = \
|
||||
rendmid.c \
|
||||
rendservice.c \
|
||||
rephist.c \
|
||||
replaycache.c \
|
||||
router.c \
|
||||
routerlist.c \
|
||||
routerparse.c \
|
||||
|
215
src/or/replaycache.c
Normal file
215
src/or/replaycache.c
Normal file
@ -0,0 +1,215 @@
|
||||
/* Copyright (c) 2012, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/*
|
||||
* \file replaycache.c
|
||||
*
|
||||
* \brief Self-scrubbing replay cache for rendservice.c
|
||||
*/
|
||||
|
||||
#define REPLAYCACHE_PRIVATE
|
||||
|
||||
#include "or.h"
|
||||
#include "replaycache.h"
|
||||
|
||||
/** Free the replaycache r and all of its entries.
|
||||
*/
|
||||
|
||||
void
|
||||
replaycache_free(replaycache_t *r)
|
||||
{
|
||||
if (!r) {
|
||||
log_info(LD_BUG, "replaycache_free() called on NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
if (r->digests_seen) digestmap_free(r->digests_seen, _tor_free);
|
||||
|
||||
tor_free(r);
|
||||
}
|
||||
|
||||
/** Allocate a new, empty replay detection cache, where horizon is the time
|
||||
* for entries to age out and interval is the time after which the cache
|
||||
* should be scrubbed for old entries.
|
||||
*/
|
||||
|
||||
replaycache_t *
|
||||
replaycache_new(time_t horizon, time_t interval)
|
||||
{
|
||||
replaycache_t *r = NULL;
|
||||
|
||||
if (horizon < 0) {
|
||||
log_info(LD_BUG, "replaycache_new() called with negative"
|
||||
" horizon parameter");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (interval < 0) {
|
||||
log_info(LD_BUG, "replaycache_new() called with negative interval"
|
||||
" parameter");
|
||||
interval = 0;
|
||||
}
|
||||
|
||||
r = tor_malloc(sizeof(*r));
|
||||
r->scrub_interval = interval;
|
||||
r->scrubbed = 0;
|
||||
r->horizon = horizon;
|
||||
r->digests_seen = digestmap_new();
|
||||
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
/** See documentation for replaycache_add_and_test()
|
||||
*/
|
||||
|
||||
int
|
||||
replaycache_add_and_test_internal(
|
||||
time_t present, replaycache_t *r, const void *data, int len,
|
||||
time_t *elapsed)
|
||||
{
|
||||
int rv = 0;
|
||||
char digest[DIGEST_LEN];
|
||||
time_t *access_time;
|
||||
|
||||
/* sanity check */
|
||||
if (present <= 0 || !r || !data || len <= 0) {
|
||||
log_info(LD_BUG, "replaycache_add_and_test_internal() called with stupid"
|
||||
" parameters; please fix this.");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* compute digest */
|
||||
crypto_digest(digest, (const char *)data, len);
|
||||
|
||||
/* check map */
|
||||
access_time = digestmap_get(r->digests_seen, digest);
|
||||
|
||||
/* seen before? */
|
||||
if (access_time != NULL) {
|
||||
/*
|
||||
* If it's far enough in the past, no hit. If the horizon is zero, we
|
||||
* never expire.
|
||||
*/
|
||||
if (*access_time >= present - r->horizon || r->horizon == 0) {
|
||||
/* replay cache hit, return 1 */
|
||||
rv = 1;
|
||||
/* If we want to output an elapsed time, do so */
|
||||
if (elapsed) {
|
||||
if (present >= *access_time) {
|
||||
*elapsed = present - *access_time;
|
||||
} else {
|
||||
/* We shouldn't really be seeing hits from the future, but... */
|
||||
*elapsed = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If it's ahead of the cached time, update
|
||||
*/
|
||||
if (*access_time < present) {
|
||||
*access_time = present;
|
||||
}
|
||||
} else {
|
||||
/* No, so no hit and update the digest map with the current time */
|
||||
access_time = tor_malloc(sizeof(*access_time));
|
||||
*access_time = present;
|
||||
digestmap_set(r->digests_seen, digest, access_time);
|
||||
}
|
||||
|
||||
/* now scrub the cache if it's time */
|
||||
replaycache_scrub_if_needed_internal(present, r);
|
||||
|
||||
done:
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** See documentation for replaycache_scrub_if_needed()
|
||||
*/
|
||||
|
||||
void
|
||||
replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r)
|
||||
{
|
||||
digestmap_iter_t *itr = NULL;
|
||||
const char *digest;
|
||||
void *valp;
|
||||
time_t *access_time;
|
||||
char scrub_this;
|
||||
|
||||
/* sanity check */
|
||||
if (!r || !(r->digests_seen)) {
|
||||
log_info(LD_BUG, "replaycache_scrub_if_needed_internal() called with"
|
||||
" stupid parameters; please fix this.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* scrub time yet? (scrubbed == 0 indicates never scrubbed before) */
|
||||
if (present - r->scrubbed < r->scrub_interval && r->scrubbed > 0) return;
|
||||
|
||||
/* if we're never expiring, don't bother scrubbing */
|
||||
if (r->horizon == 0) return;
|
||||
|
||||
/* okay, scrub time */
|
||||
itr = digestmap_iter_init(r->digests_seen);
|
||||
while (!digestmap_iter_done(itr)) {
|
||||
scrub_this = 0;
|
||||
digestmap_iter_get(itr, &digest, &valp);
|
||||
access_time = (time_t *)valp;
|
||||
if (access_time) {
|
||||
/* aged out yet? */
|
||||
if (*access_time < present - r->horizon) scrub_this = 1;
|
||||
} else {
|
||||
/* Buh? Get rid of it, anyway */
|
||||
log_info(LD_BUG, "replaycache_scrub_if_needed_internal() saw a NULL"
|
||||
" entry in the digestmap.");
|
||||
scrub_this = 1;
|
||||
}
|
||||
|
||||
if (scrub_this) {
|
||||
/* Advance the iterator and remove this one */
|
||||
itr = digestmap_iter_next_rmv(r->digests_seen, itr);
|
||||
/* Free the value removed */
|
||||
tor_free(access_time);
|
||||
} else {
|
||||
/* Just advance the iterator */
|
||||
itr = digestmap_iter_next(r->digests_seen, itr);
|
||||
}
|
||||
}
|
||||
|
||||
/* update scrubbed timestamp */
|
||||
if (present > r->scrubbed) r->scrubbed = present;
|
||||
}
|
||||
|
||||
/** Test the buffer of length len point to by data against the replay cache r;
|
||||
* the digest of the buffer will be added to the cache at the current time,
|
||||
* and the function will return 1 if it was already seen within the cache's
|
||||
* horizon, or 0 otherwise.
|
||||
*/
|
||||
|
||||
int
|
||||
replaycache_add_and_test(replaycache_t *r, const void *data, int len)
|
||||
{
|
||||
return replaycache_add_and_test_internal(time(NULL), r, data, len, NULL);
|
||||
}
|
||||
|
||||
/** Like replaycache_add_and_test(), but if it's a hit also return the time
|
||||
* elapsed since this digest was last seen.
|
||||
*/
|
||||
|
||||
int
|
||||
replaycache_add_test_and_elapsed(
|
||||
replaycache_t *r, const void *data, int len, time_t *elapsed)
|
||||
{
|
||||
return replaycache_add_and_test_internal(time(NULL), r, data, len, elapsed);
|
||||
}
|
||||
|
||||
/** Scrub aged entries out of r if sufficiently long has elapsed since r was
|
||||
* last scrubbed.
|
||||
*/
|
||||
|
||||
void
|
||||
replaycache_scrub_if_needed(replaycache_t *r)
|
||||
{
|
||||
replaycache_scrub_if_needed_internal(time(NULL), r);
|
||||
}
|
||||
|
66
src/or/replaycache.h
Normal file
66
src/or/replaycache.h
Normal file
@ -0,0 +1,66 @@
|
||||
/* Copyright (c) 2012, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file replaycache.h
|
||||
* \brief Header file for replaycache.c.
|
||||
**/
|
||||
|
||||
#ifndef _TOR_REPLAYCACHE_H
|
||||
#define _TOR_REPLAYCACHE_H
|
||||
|
||||
typedef struct replaycache_s replaycache_t;
|
||||
|
||||
#ifdef REPLAYCACHE_PRIVATE
|
||||
|
||||
struct replaycache_s {
|
||||
/* Scrub interval */
|
||||
time_t scrub_interval;
|
||||
/* Last scrubbed */
|
||||
time_t scrubbed;
|
||||
/*
|
||||
* Horizon
|
||||
* (don't return true on digests in the cache but older than this)
|
||||
*/
|
||||
time_t horizon;
|
||||
/*
|
||||
* Digest map: keys are digests, values are times the digest was last seen
|
||||
*/
|
||||
digestmap_t *digests_seen;
|
||||
};
|
||||
|
||||
#endif /* REPLAYCACHE_PRIVATE */
|
||||
|
||||
/* replaycache_t free/new */
|
||||
|
||||
void replaycache_free(replaycache_t *r);
|
||||
replaycache_t * replaycache_new(time_t horizon, time_t interval);
|
||||
|
||||
#ifdef REPLAYCACHE_PRIVATE
|
||||
|
||||
/*
|
||||
* replaycache_t internal functions:
|
||||
*
|
||||
* These take the time to treat as the present as an argument for easy unit
|
||||
* testing. For everything else, use the wrappers below instead.
|
||||
*/
|
||||
|
||||
int replaycache_add_and_test_internal(
|
||||
time_t present, replaycache_t *r, const void *data, int len,
|
||||
time_t *elapsed);
|
||||
void replaycache_scrub_if_needed_internal(
|
||||
time_t present, replaycache_t *r);
|
||||
|
||||
#endif /* REPLAYCACHE_PRIVATE */
|
||||
|
||||
/*
|
||||
* replaycache_t methods
|
||||
*/
|
||||
|
||||
int replaycache_add_and_test(replaycache_t *r, const void *data, int len);
|
||||
int replaycache_add_test_and_elapsed(
|
||||
replaycache_t *r, const void *data, int len, time_t *elapsed);
|
||||
void replaycache_scrub_if_needed(replaycache_t *r);
|
||||
|
||||
#endif
|
||||
|
@ -20,6 +20,7 @@ test_SOURCES = \
|
||||
test_dir.c \
|
||||
test_microdesc.c \
|
||||
test_pt.c \
|
||||
test_replay.c \
|
||||
test_util.c \
|
||||
test_config.c \
|
||||
tinytest.c
|
||||
|
@ -1863,6 +1863,7 @@ extern struct testcase_t dir_tests[];
|
||||
extern struct testcase_t microdesc_tests[];
|
||||
extern struct testcase_t pt_tests[];
|
||||
extern struct testcase_t config_tests[];
|
||||
extern struct testcase_t replaycache_tests[];
|
||||
|
||||
static struct testgroup_t testgroups[] = {
|
||||
{ "", test_array },
|
||||
@ -1875,6 +1876,7 @@ static struct testgroup_t testgroups[] = {
|
||||
{ "dir/md/", microdesc_tests },
|
||||
{ "pt/", pt_tests },
|
||||
{ "config/", config_tests },
|
||||
{ "replaycache/", replaycache_tests },
|
||||
END_OF_GROUPS
|
||||
};
|
||||
|
||||
|
184
src/test/test_replay.c
Normal file
184
src/test/test_replay.c
Normal file
@ -0,0 +1,184 @@
|
||||
/* Copyright (c) 2012, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
#define REPLAYCACHE_PRIVATE
|
||||
|
||||
#include "orconfig.h"
|
||||
#include "or.h"
|
||||
#include "replaycache.h"
|
||||
#include "test.h"
|
||||
|
||||
static const char *test_buffer =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipisici elit, sed do eiusmod"
|
||||
" tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim"
|
||||
" veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea"
|
||||
" commodo consequat. Duis aute irure dolor in reprehenderit in voluptate"
|
||||
" velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint"
|
||||
" occaecat cupidatat non proident, sunt in culpa qui officia deserunt"
|
||||
" mollit anim id est laborum.";
|
||||
|
||||
static void
|
||||
test_replaycache_alloc(void)
|
||||
{
|
||||
replaycache_t *r = NULL;
|
||||
|
||||
r = replaycache_new(600, 300);
|
||||
test_assert(r != NULL);
|
||||
if (!r) goto done;
|
||||
|
||||
done:
|
||||
if (r) replaycache_free(r);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
test_replaycache_miss(void)
|
||||
{
|
||||
replaycache_t *r = NULL;
|
||||
int result;
|
||||
|
||||
r = replaycache_new(600, 300);
|
||||
test_assert(r != NULL);
|
||||
if (!r) goto done;
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(1200, r, test_buffer,
|
||||
strlen(test_buffer), NULL);
|
||||
test_eq(result, 0);
|
||||
|
||||
done:
|
||||
if (r) replaycache_free(r);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
test_replaycache_hit(void)
|
||||
{
|
||||
replaycache_t *r = NULL;
|
||||
int result;
|
||||
|
||||
r = replaycache_new(600, 300);
|
||||
test_assert(r != NULL);
|
||||
if (!r) goto done;
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(1200, r, test_buffer,
|
||||
strlen(test_buffer), NULL);
|
||||
test_eq(result, 0);
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(1300, r, test_buffer,
|
||||
strlen(test_buffer), NULL);
|
||||
test_eq(result, 1);
|
||||
|
||||
done:
|
||||
if (r) replaycache_free(r);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
test_replaycache_age(void)
|
||||
{
|
||||
replaycache_t *r = NULL;
|
||||
int result;
|
||||
|
||||
r = replaycache_new(600, 300);
|
||||
test_assert(r != NULL);
|
||||
if (!r) goto done;
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(1200, r, test_buffer,
|
||||
strlen(test_buffer), NULL);
|
||||
test_eq(result, 0);
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(1300, r, test_buffer,
|
||||
strlen(test_buffer), NULL);
|
||||
test_eq(result, 1);
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(3000, r, test_buffer,
|
||||
strlen(test_buffer), NULL);
|
||||
test_eq(result, 0);
|
||||
|
||||
done:
|
||||
if (r) replaycache_free(r);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
test_replaycache_elapsed(void)
|
||||
{
|
||||
replaycache_t *r = NULL;
|
||||
int result;
|
||||
time_t elapsed;
|
||||
|
||||
r = replaycache_new(600, 300);
|
||||
test_assert(r != NULL);
|
||||
if (!r) goto done;
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(1200, r, test_buffer,
|
||||
strlen(test_buffer), NULL);
|
||||
test_eq(result, 0);
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(1300, r, test_buffer,
|
||||
strlen(test_buffer), &elapsed);
|
||||
test_eq(result, 1);
|
||||
test_eq(elapsed, 100);
|
||||
|
||||
done:
|
||||
if (r) replaycache_free(r);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
test_replaycache_noexpire(void)
|
||||
{
|
||||
replaycache_t *r = NULL;
|
||||
int result;
|
||||
|
||||
r = replaycache_new(0, 0);
|
||||
test_assert(r != NULL);
|
||||
if (!r) goto done;
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(1200, r, test_buffer,
|
||||
strlen(test_buffer), NULL);
|
||||
test_eq(result, 0);
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(1300, r, test_buffer,
|
||||
strlen(test_buffer), NULL);
|
||||
test_eq(result, 1);
|
||||
|
||||
result =
|
||||
replaycache_add_and_test_internal(3000, r, test_buffer,
|
||||
strlen(test_buffer), NULL);
|
||||
test_eq(result, 1);
|
||||
|
||||
done:
|
||||
if (r) replaycache_free(r);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#define REPLAYCACHE_LEGACY(name) \
|
||||
{ #name, legacy_test_helper, 0, &legacy_setup, test_replaycache_ ## name }
|
||||
|
||||
struct testcase_t replaycache_tests[] = {
|
||||
REPLAYCACHE_LEGACY(alloc),
|
||||
REPLAYCACHE_LEGACY(miss),
|
||||
REPLAYCACHE_LEGACY(hit),
|
||||
REPLAYCACHE_LEGACY(age),
|
||||
REPLAYCACHE_LEGACY(elapsed),
|
||||
REPLAYCACHE_LEGACY(noexpire),
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user