Browse Source

Implement replaycache_t for bug 6177, and unit tests for the preceding

Andrea Shepard 11 years ago
parent
commit
8f63ef10ad
7 changed files with 472 additions and 0 deletions
  1. 3 0
      changes/bug6177
  2. 1 0
      src/or/Makefile.am
  3. 215 0
      src/or/replaycache.c
  4. 66 0
      src/or/replaycache.h
  5. 1 0
      src/test/Makefile.am
  6. 2 0
      src/test/test.c
  7. 184 0
      src/test/test_replay.c

+ 3 - 0
changes/bug6177

@@ -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.

+ 1 - 0
src/or/Makefile.am

@@ -48,6 +48,7 @@ libtor_a_SOURCES = \
 	rendmid.c				\
 	rendservice.c				\
 	rephist.c				\
+	replaycache.c				\
 	router.c				\
 	routerlist.c				\
 	routerparse.c				\

+ 215 - 0
src/or/replaycache.c

@@ -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 - 0
src/or/replaycache.h

@@ -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
+

+ 1 - 0
src/test/Makefile.am

@@ -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

+ 2 - 0
src/test/test.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 - 0
src/test/test_replay.c

@@ -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
+};
+