[tor-commits] [stegotorus/master] Exponential backoff in chaff transmissions
zwol at torproject.org
zwol at torproject.org
Fri Jul 20 23:17:06 UTC 2012
commit 5ef7b8cb390d3aeb774c552357d4220edd4567e7
Author: Zack Weinberg <zackw at cmu.edu>
Date: Thu Jan 12 17:44:31 2012 +0000
Exponential backoff in chaff transmissions
git-svn-id: svn+ssh://spartan.csl.sri.com/svn/private/DEFIANCE@214 a58ff0ac-194c-e011-a152-003048836090
---
src/connections.cc | 12 +++--
src/protocol/chop.cc | 63 ++++++++++++------------
src/rng.cc | 128 +++++++++++++++++++++++++++++++++++++++++---------
src/test/test_tl.py | 2 +-
4 files changed, 145 insertions(+), 60 deletions(-)
diff --git a/src/connections.cc b/src/connections.cc
index f0967a2..6320746 100644
--- a/src/connections.cc
+++ b/src/connections.cc
@@ -362,9 +362,11 @@ circuit_recv_eof(circuit_t *ckt)
void
circuit_arm_flush_timer(circuit_t *ckt, unsigned int milliseconds)
{
+ log_debug(ckt, "flush within %u milliseconds", milliseconds);
+
struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = milliseconds * 1000;
+ tv.tv_sec = milliseconds / 1000;
+ tv.tv_usec = (milliseconds % 1000) * 1000;
if (!ckt->flush_timer)
ckt->flush_timer = evtimer_new(ckt->cfg->base, flush_timer_cb, ckt);
@@ -382,9 +384,11 @@ circuit_disarm_flush_timer(circuit_t *ckt)
void
circuit_arm_axe_timer(circuit_t *ckt, unsigned int milliseconds)
{
+ log_debug(ckt, "axe after %u milliseconds", milliseconds);
+
struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = milliseconds * 1000;
+ tv.tv_sec = milliseconds / 1000;
+ tv.tv_usec = (milliseconds % 1000) * 1000;
if (!ckt->axe_timer)
ckt->axe_timer = evtimer_new(ckt->cfg->base, axe_timer_cb, ckt);
diff --git a/src/protocol/chop.cc b/src/protocol/chop.cc
index 51d3195..6b0d6a4 100644
--- a/src/protocol/chop.cc
+++ b/src/protocol/chop.cc
@@ -94,6 +94,7 @@ namespace {
uint64_t circuit_id;
uint32_t send_offset;
uint32_t recv_offset;
+ uint32_t dead_cycles;
bool received_syn : 1;
bool received_fin : 1;
bool sent_syn : 1;
@@ -101,6 +102,19 @@ namespace {
bool upstream_eof : 1;
CIRCUIT_DECLARE_METHODS(chop);
+
+ uint32_t axe_interval() {
+ return rng_range_geom(30 * 60 * 1000,
+ std::min((1 << dead_cycles) * 1000,
+ 20 * 60 * 1000))
+ + 5 * 1000;
+ }
+ uint32_t flush_interval() {
+ return rng_range_geom(20 * 60 * 1000,
+ std::min((1 << dead_cycles) * 500,
+ 10 * 60 * 1000))
+ + 1000;
+ }
};
struct chop_config_t : config_t
@@ -252,7 +266,7 @@ chop_pick_connection(chop_circuit_t *ckt, size_t desired, size_t *blocksize)
room = 0;
else
room -= CHOP_BLOCK_OVERHD;
-
+
if (room > CHOP_MAX_DATA)
room = CHOP_MAX_DATA;
@@ -299,7 +313,6 @@ chop_send_block(conn_t *d,
chop_header hdr;
struct evbuffer_iovec v;
uint8_t *p;
- struct timeval *exp_time = NULL;
log_assert(evbuffer_get_length(block) == 0);
log_assert(evbuffer_get_length(source) >= length);
@@ -333,18 +346,6 @@ chop_send_block(conn_t *d,
if (evbuffer_commit_space(block, &v, 1))
goto fail;
- /* save the expiration time of the must_transmit_timer in case of failure */
- if (dest->must_transmit_timer) {
- exp_time = new struct timeval;
- if (evtimer_pending(dest->must_transmit_timer, exp_time)) {
- log_debug("saved must_transmit_timer value in case of failure");
- } else {
- delete exp_time;
- exp_time = NULL;
- }
- evtimer_del(dest->must_transmit_timer);
- }
-
if (dest->steg->transmit(block, dest))
goto fail_committed;
@@ -352,6 +353,10 @@ chop_send_block(conn_t *d,
/* this really should never happen, and we can't recover from it */
log_abort(dest, "evbuffer_drain failed"); /* does not return */
+ /* Cancel the must-transmit timer if it's pending; we have transmitted. */
+ if (dest->must_transmit_timer)
+ evtimer_del(dest->must_transmit_timer);
+
if (!(flags & CHOP_F_CHAFF))
ckt->send_offset += length;
if (flags & CHOP_F_SYN)
@@ -361,9 +366,6 @@ chop_send_block(conn_t *d,
log_debug(dest, "sent %lu+%u byte block [flags %04hx]",
(unsigned long)CHOP_WIRE_HDR_LEN, length, flags);
- if (exp_time != NULL)
- delete exp_time;
-
return 0;
fail:
@@ -373,17 +375,6 @@ chop_send_block(conn_t *d,
evbuffer_drain(block, evbuffer_get_length(block));
log_warn(dest, "allocation or buffer copy failed");
- /* restore timer if necessary */
- if (exp_time != NULL) {
- if (!evtimer_pending(dest->must_transmit_timer, NULL)) {
- struct timeval cur_time, timeout;
- gettimeofday(&cur_time, NULL);
- timeval_subtract(exp_time, &cur_time, &timeout);
- evtimer_add(dest->must_transmit_timer, &timeout);
- }
- delete exp_time;
- }
-
return -1;
}
@@ -746,12 +737,14 @@ chop_push_to_upstream(circuit_t *c)
chop_reassembly_elt *ready = ckt->reassembly_queue.next;
if (!ready->data || ckt->recv_offset != ready->offset) {
log_debug(c, "no data pushable to upstream yet");
+ ckt->dead_cycles++;
return 0;
}
if (!ckt->received_syn) {
if (!(ready->flags & CHOP_F_SYN)) {
log_debug(c, "waiting for SYN");
+ ckt->dead_cycles++;
return 0;
}
log_debug(c, "processed SYN");
@@ -765,6 +758,7 @@ chop_push_to_upstream(circuit_t *c)
return -1;
}
+ ckt->dead_cycles = 0;
ckt->recv_offset += ready->length;
if (ready->flags & CHOP_F_FIN) {
@@ -1110,7 +1104,7 @@ chop_circuit_t::send()
if (this->cfg->mode != LSN_SIMPLE_SERVER)
circuit_reopen_downstreams(this);
else
- circuit_arm_axe_timer(this, 5000);
+ circuit_arm_axe_timer(this, this->axe_interval());
return 0;
}
@@ -1118,9 +1112,11 @@ chop_circuit_t::send()
/* must-send timer expired and we still have nothing to say; send chaff */
if (chop_send_chaff(this))
return -1;
+ this->dead_cycles++;
} else {
if (chop_send_blocks(this))
return -1;
+ this->dead_cycles = 0;
}
/* If we're at EOF, close all connections (sending first if
@@ -1138,7 +1134,7 @@ chop_circuit_t::send()
}
} else {
if (this->cfg->mode != LSN_SIMPLE_SERVER)
- circuit_arm_flush_timer(this, 5);
+ circuit_arm_flush_timer(this, this->flush_interval());
}
return 0;
}
@@ -1332,8 +1328,11 @@ void
chop_conn_t::transmit_soon(unsigned long milliseconds)
{
struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = milliseconds * 1000;
+
+ log_debug(this, "must transmit within %lu milliseconds", milliseconds);
+
+ tv.tv_sec = milliseconds / 1000;
+ tv.tv_usec = (milliseconds % 1000) * 1000;
if (!this->must_transmit_timer)
this->must_transmit_timer = evtimer_new(this->cfg->base,
diff --git a/src/rng.cc b/src/rng.cc
index 95bea3a..a42c487 100644
--- a/src/rng.cc
+++ b/src/rng.cc
@@ -91,34 +91,106 @@ rng_range(unsigned int min, unsigned int max)
/** Internal use only (can be externalized if someone has a good use
* for it): generate a random double-precision floating-point number
- * in the range [0.0, 1.0). Implementation tactic from "Common Lisp
- * the Language, 2nd Edition", section 12.9. Assumes IEEE754.
+ * in the range (0.0, 1.0] (note that this is _not_ the usual convention,
+ * but it saves a call to nextafter() in the sole current user).
+ *
+ * For what we use this for, it is important that we can, at least
+ * potentially, generate _every_ representable real number in the
+ * desired interval, with genuine uniformity. The usual tactic of
+ * generating a random integer and dividing does not do this, because
+ * the rational numbers produced by random()/MAX are evenly spaced on
+ * the real line, but floating point numbers close to zero are *not*.
+ *
+ * For the same reason, the trick for avoiding division suggested
+ * e.g. by "Common Lisp, the Language", generating a random number in
+ * [1.0, 2.0) by overwriting the mantissa of a 1.0 and then
+ * subtracting 1.0, does not help -- you can do the first step
+ * precisely because the representable binary floating point numbers
+ * between 1.0 and 2.0 *are* evenly spaced on the real line.
+ *
+ * The more complicated, but correct, algorithm here was developed by
+ * Allen B. Downey: http://allendowney.com/research/rand/
+ *
*/
static double
rng_double()
{
+ class rngbit {
+ public:
+ rngbit(uint32_t bits, unsigned int n) : bits(bits), n(n) {}
+
+ bool get()
+ {
+ if (n == 0) {
+ bits = rng->GenerateByte();
+ n = CHAR_BIT;
+ }
+ bool rv = bits & 1;
+ bits >>= 1;
+ n -= 1;
+ return rv;
+ }
+ private:
+ uint32_t bits;
+ unsigned int n;
+ };
+
union ieee754_double {
double d;
uint64_t i;
};
- union ieee754_double n;
-
- /* This may waste up to 12 bits of randomness on each call,
- depending on how clever GenerateWord32 is internally; but the
- implementation is much simpler than if we used GenerateBlock. */
try {
rng_init();
- n.i = (0x3FF0000000000000ULL |
- (uint64_t(rng->GenerateWord32(0, 0x000FFFFFu)) << 32) |
- uint64_t(rng->GenerateWord32()));
- } CATCH_ALL_EXCEPTIONS(std::numeric_limits<double>::quiet_NaN());
- return n.d - 1.0;
+ /* Because of how the Crypto++ RNG works, it is convenient to
+ generate the mantissa first, contra Downey, and use the
+ leftover bits to seed the bit-generator that we use for the
+ exponent; this does not change the algorithm fundamentally,
+ because only the final adjustment step depends on both. */
+
+ uint64_t mantissa = rng->GenerateWord32();
+ uint32_t b = rng->GenerateWord32();
+
+ mantissa |= uint64_t(b & 0x000FFFFF) << 32;
+
+ /* This is the core of Downey's algorithm: 50% of the time we
+ should generate the highest exponent of a number in (0,1) (note
+ that _neither_ endpoint is included right now). 25% of the
+ time, we should generate the second highest exponent, 12.5% of
+ the time, we should generate the third highest, and so on. In
+ other words, we should start with the highest exponent, flip a
+ coin, and keep subtracting 1 until either we hit zero or the
+ coin comes up heads.
+
+ If anyone knows how to do this in _constant_ time, instead of
+ variable time bounded by a constant, please tell me.
+ */
+
+ rngbit bits((b & 0xFFF00000) >> 20, 12);
+ uint32_t exponent = 0x3FE; /* 1111111110 = 2^{-1} */
+ do {
+ if (bits.get()) break;
+ } while (--exponent);
+
+ /* Finally a slight adjustment: if the mantissa is zero, then
+ half the time we should increment the exponent by one.
+ Do this unconditionally if the exponent is also zero
+ (so we never generate 0.0). */
+ if (mantissa == 0 && (exponent == 0 || bits.get()))
+ exponent++;
+
+ /* Assemble and return the number. */
+ union ieee754_double n;
+ n.i = (uint64_t(exponent) << 52) | mantissa;
+ return n.d;
+ }
+ CATCH_ALL_EXCEPTIONS(std::numeric_limits<double>::quiet_NaN());
}
-/** Return a random integer in the range [0, hi), geometrically
- * distributed over that range, with expected value 'xv'.
+/** Return a random integer in the range [0, hi),
+ * from a truncated geometric distribution whose expected value
+ * (prior to truncation) is 'xv'.
* (The rate parameter 'lambda' that's usually used to characterize
* the geometric/exponential distribution is equal to 1/xv.)
* 'hi' must be no more than INT_MAX+1, as for 'rng_range'.
@@ -134,14 +206,24 @@ rng_range_geom(unsigned int hi, unsigned int xv)
if (isnan(U))
return -1;
- /* Inverse transform sampling:
- T = (-ln U)/lambda; lambda=1/(xv-lo); therefore T = (xv-lo) * -ln(U).
- Minor wrinkle: rng_double() produces [0, 1) but we want (0, 1] to
- avoid hitting the undefined log(0). This is what nextafter() is for. */
-
- double T = -log(nextafter(U, 2.0)) * xv;
-
- /* Technically we should rejection-sample here instead of clamping, but
- that would make this not a constant-time operation. */
+ /* The exponential distribution with expected value
+ xe = 1/log(1 + 1/xv)
+ can be converted to the desired geometric distribution by
+ floor(). See http://math.stackexchange.com/questions/97733 */
+ double xe = 1./log(1. + 1./xv);
+
+ /* To truncate in constant time, adjust U to be in the range
+ ( e^{-hi/xe}, 1 ]. Doing this with arithmetic introduces
+ a slight nonuniformity, but we really want to avoid rejection
+ sampling here. */
+ double ulo = exp(-hi/xe);
+ U = ulo + U * (1-ulo);
+
+ /* Inverse transform sampling gives us a value for the exponential
+ distribution with expected value 'xe'. */
+ double T = -log(U) * xe;
+
+ /* Round down for the geometric distribution, and clamp to [0, hi)
+ for great defensiveness. */
return std::min(hi-1, std::max(0U, (unsigned int)floor(T)));
}
diff --git a/src/test/test_tl.py b/src/test/test_tl.py
index c7899b7..2c54228 100644
--- a/src/test/test_tl.py
+++ b/src/test/test_tl.py
@@ -51,7 +51,7 @@ class TimelineTest(object):
("chop", "server", "127.0.0.1:5001",
"127.0.0.1:5010","127.0.0.1:5011",
"chop", "client", "127.0.0.1:4999",
- "127.0.0.1:5010","x_http","127.0.0.1:5011","x_http",
+ "127.0.0.1:5010","http","127.0.0.1:5011","http",
))
# Synthesize TimelineTest+TestCase subclasses for every 'tl_*' file in
More information about the tor-commits
mailing list