Commit 68056cab authored by rtenneti@chromium.org's avatar rtenneti@chromium.org

Merge QUIC StrikeRegister code to chromium.

R=rch@chromium.org, jar@chromium.org

Review URL: https://codereview.chromium.org/13520010

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@192939 0039d316-1c4b-4281-b951-d872f2087c98
parent 10044349
......@@ -707,6 +707,8 @@
'quic/crypto/quic_random.cc',
'quic/crypto/quic_random.h',
'quic/crypto/scoped_evp_cipher_ctx.h',
'quic/crypto/strike_register.cc',
'quic/crypto/strike_register.h',
'quic/quic_bandwidth.cc',
'quic/quic_bandwidth.h',
'quic/quic_blocked_writer_interface.h',
......@@ -1555,6 +1557,7 @@
'quic/crypto/null_encrypter_test.cc',
'quic/crypto/p256_key_exchange_test.cc',
'quic/crypto/quic_random_test.cc',
'quic/crypto/strike_register_test.cc',
'quic/test_tools/crypto_test_utils.cc',
'quic/test_tools/crypto_test_utils.h',
'quic/test_tools/mock_clock.cc',
......
This diff is collapsed.
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_QUIC_CRYPTO_STRIKE_REGISTER_H_
#define NET_QUIC_CRYPTO_STRIKE_REGISTER_H_
#include <set>
#include <utility>
#include <vector>
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "net/base/net_export.h"
namespace net {
// A StrikeRegister is critbit tree which stores a set of observed nonces.
// We use a critbit tree because:
// 1) It's immune to algorithmic complexity attacks. If we had used a hash
// tree, an attacker could send us a series of values which stretch out one
// of the hash chains, causing us to do much more work than normal.
// 2) We can write it to use a fixed block of memory: avoiding fragmentation
// issues and so forth. (We might be able to do that with the STL
// algorithms and a custom allocator, but I don't want to go there.)
// 3) It's simple (compared to balanced binary trees) and doesn't involve
// bouncing nearly as many cache lines around.
// 4) It allows us to query for the oldest element in log(n) time.
//
// This code is based on djb's public domain critbit tree from qhasm.
//
// A critbit tree has external and internal nodes. External nodes are just the
// nonce values (which are stored without the orbit values included). Internal
// nodes contain the bit number at which the tree is branching and exactly two
// children. The critical bit is stored as a byte number and a byte
// (|otherbits|) which has all the bits set /except/ the one in question.
//
// Internal nodes have exactly two children: an internal node with only a
// single child would be useless.
//
// The branching bit number (considering the MSB to be the 1st bit) is
// monotonically increasing as you go down the tree.
class NET_EXPORT_PRIVATE StrikeRegister {
public:
// An external node takes 24 bytes as we don't record the orbit.
static const uint32 kExternalNodeSize;
// We address the nodes by their index in the array. This means that 0 is a
// valid index. Therefore this is our invalid index. It also has a one bit
// in the LSB position because we tend to store indexes shifted up 8 bits
// and this distinguishes kNil from (kExternalFlag | 0) << 8.
static const uint32 kNil;
// Our pointers from internal nodes can either point to an internal or
// external node. We flag the 24th bit to mark a pointer as external.
static const uint32 kExternalFlag;
// Construct a new set which can hold, at most, |max_entries| (which must be
// less than 2**23). Once constructed, all nonces created before
// |current_time| (in seconds) will be rejected. In the future, all nonces
// that are outside +/- |window_secs| from the current time will be rejected.
// Additionally, all nonces that have an orbit value other than |orbit| will
// be rejected.
//
// (Note that this code is independent of the actual units of time used, but
// you should use seconds.)
StrikeRegister(unsigned max_entries,
uint32 current_time,
uint32 window_secs,
const uint8 orbit[8]);
~StrikeRegister();
void Reset();
// |Insert| queries to see if |nonce| is
// a) for the wrong orbit
// b) before the current horizon
// c) outside of the valid time window
// d) already in the set of observed nonces
// and returns false if any of these are true. It is also free to return
// false for other reasons as it's always safe to reject an nonce.
//
// nonces are:
// 4 bytes of timestamp (UNIX epoch seconds)
// 8 bytes of orbit value (a cluster id)
// 20 bytes of random data
//
// Otherwise, it inserts |nonce| into the observed set and returns true.
bool Insert(const uint8 nonce[32], const uint32 current_time);
// This is a debugging aid which checks the tree for sanity.
void Validate();
private:
class InternalNode;
// TimeFromBytes returns a big-endian uint32 from |d|.
static uint32 TimeFromBytes(const uint8 d[4]);
// BestMatch returns either kNil, or an external node index which could
// possibly match |v|.
uint32 BestMatch(const uint8 v[24]) const;
// external_node_next_ptr returns the 'next' pointer embedded in external
// node |i|. This is used to thread a free list through the external nodes.
uint32& external_node_next_ptr(unsigned i);
uint8* external_node(unsigned i);
uint32 GetFreeExternalNode();
uint32 GetFreeInternalNode();
// DropNode removes the oldest node in the tree and updates |horizon_|
// accordingly.
void DropNode();
void FreeExternalNode(uint32 index);
void FreeInternalNode(uint32 index);
void ValidateTree(uint32 internal_node,
int last_bit,
const std::vector<std::pair<unsigned, bool> >& bits,
const std::set<uint32>& free_internal_nodes,
const std::set<uint32>& free_external_nodes,
std::set<uint32>* used_internal_nodes,
std::set<uint32>* used_external_nodes);
const uint32 max_entries_;
const uint32 window_secs_;
uint8 orbit_[8];
uint32 horizon_;
uint32 internal_node_free_head_;
uint32 external_node_free_head_;
uint32 internal_node_head_;
// internal_nodes_ can't be a scoped_ptr because the type isn't defined in
// this header.
InternalNode* internal_nodes_;
scoped_ptr<uint8[]> external_nodes_;
};
} // namespace net
#endif // NET_QUIC_CRYPTO_STRIKE_REGISTER_H_
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/quic/crypto/strike_register.h"
#include <set>
#include <string>
#include "base/basictypes.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using net::StrikeRegister;
using std::set;
using std::string;
const uint8 kOrbit[8] = {1, 2, 3, 4, 5, 6, 7, 8};
void
NonceSetTimeAndOrbit(uint8 nonce[32], unsigned time, const uint8 orbit[8]) {
nonce[0] = time >> 24;
nonce[1] = time >> 16;
nonce[2] = time >> 8;
nonce[3] = time;
memcpy(nonce + 4, orbit, 8);
}
TEST(StrikeRegisterTest, SimpleHorizon) {
// The set must reject values created on or before its own creation time.
StrikeRegister set(10 /* max size */, 1000 /* current time */,
100 /* window secs */, kOrbit);
uint8 nonce[32];
NonceSetTimeAndOrbit(nonce, 999, kOrbit);
ASSERT_FALSE(set.Insert(nonce, 1000));
NonceSetTimeAndOrbit(nonce, 1000, kOrbit);
ASSERT_FALSE(set.Insert(nonce, 1000));
}
TEST(StrikeRegisterTest, WindowFuture) {
// The set must reject values outside the window.
StrikeRegister set(10 /* max size */, 1000 /* current time */,
100 /* window secs */, kOrbit);
uint8 nonce[32];
NonceSetTimeAndOrbit(nonce, 1101, kOrbit);
ASSERT_FALSE(set.Insert(nonce, 1000));
NonceSetTimeAndOrbit(nonce, 999, kOrbit);
ASSERT_FALSE(set.Insert(nonce, 1100));
}
TEST(StrikeRegisterTest, BadOrbit) {
// The set must reject values with the wrong orbit
StrikeRegister set(10 /* max size */, 1000 /* current time */,
100 /* window secs */, kOrbit);
uint8 nonce[32];
static const uint8 kBadOrbit[8] = {0, 0, 0, 0, 1, 1, 1, 1};
NonceSetTimeAndOrbit(nonce, 1101, kBadOrbit);
ASSERT_FALSE(set.Insert(nonce, 1100));
}
TEST(StrikeRegisterTest, OneValue) {
StrikeRegister set(10 /* max size */, 1000 /* current time */,
100 /* window secs */, kOrbit);
uint8 nonce[32];
NonceSetTimeAndOrbit(nonce, 1101, kOrbit);
ASSERT_TRUE(set.Insert(nonce, 1100));
}
TEST(StrikeRegisterTest, RejectDuplicate) {
// The set must reject values with the wrong orbit
StrikeRegister set(10 /* max size */, 1000 /* current time */,
100 /* window secs */, kOrbit);
uint8 nonce[32];
memset(nonce, 0, sizeof(nonce));
NonceSetTimeAndOrbit(nonce, 1101, kOrbit);
ASSERT_TRUE(set.Insert(nonce, 1100));
ASSERT_FALSE(set.Insert(nonce, 1100));
}
TEST(StrikeRegisterTest, HorizonUpdating) {
StrikeRegister set(5 /* max size */, 1000 /* current time */,
100 /* window secs */, kOrbit);
uint8 nonce[6][32];
for (unsigned i = 0; i < 5; i++) {
NonceSetTimeAndOrbit(nonce[i], 1101, kOrbit);
memset(nonce[i] + 12, 0, 20);
nonce[i][31] = i;
ASSERT_TRUE(set.Insert(nonce[i], 1100));
}
// This should push the oldest value out and force the horizon to be updated.
NonceSetTimeAndOrbit(nonce[5], 1102, kOrbit);
memset(nonce[5] + 12, 0, 20);
ASSERT_TRUE(set.Insert(nonce[5], 1100));
// This should be behind the horizon now:
NonceSetTimeAndOrbit(nonce[5], 1101, kOrbit);
memset(nonce[5] + 12, 0, 20);
nonce[5][31] = 10;
ASSERT_FALSE(set.Insert(nonce[5], 1100));
}
TEST(StrikeRegisterTest, InsertMany) {
StrikeRegister set(5000 /* max size */, 1000 /* current time */,
500 /* window secs */, kOrbit);
uint8 nonce[32];
NonceSetTimeAndOrbit(nonce, 1101, kOrbit);
for (unsigned i = 0; i < 100000; i++) {
NonceSetTimeAndOrbit(nonce, 1101 + i/500, kOrbit);
memcpy(nonce + 12, &i, sizeof(i));
set.Insert(nonce, 1100);
}
}
// For the following test we create a slow, but simple, version of a
// StrikeRegister. The behaviour of this object is much easier to understand
// than the fully fledged version. We then create a test to show, empirically,
// that the two objects have identical behaviour.
// A SlowStrikeRegister has the same public interface as a StrikeRegister, but
// is much slower. Hopefully it is also more obviously correct and we can
// empirically test that their behaviours are identical.
class SlowStrikeRegister {
public:
SlowStrikeRegister(unsigned max_entries, uint32 current_time,
uint32 window_secs, const uint8 orbit[8])
: max_entries_(max_entries),
window_secs_(window_secs),
horizon_(current_time + window_secs) {
memcpy(orbit_, orbit, sizeof(orbit_));
}
bool Insert(const uint8 nonce_bytes[32], const uint32 current_time) {
// Same overflow and underflow check as the real StrikeRegister.
if (current_time < window_secs_ ||
current_time + window_secs_ < current_time) {
nonces_.clear();
horizon_ = current_time;
return false;
}
// Check to see if the orbit is correct.
if (memcmp(nonce_bytes + 4, orbit_, sizeof(orbit_))) {
return false;
}
const uint32 nonce_time = TimeFromBytes(nonce_bytes);
// We have dropped one or more nonces with a time value of |horizon_|, so
// we have to reject anything with a timestamp less than or equal to that.
if (nonce_time <= horizon_) {
return false;
}
// Check that the timestamp is in the current window.
if (nonce_time < (current_time - window_secs_) ||
nonce_time > (current_time + window_secs_)) {
return false;
}
const string nonce(reinterpret_cast<const char*>(nonce_bytes), 32);
set<string>::const_iterator it = nonces_.find(nonce);
if (it != nonces_.end()) {
return false;
}
if (nonces_.size() == max_entries_) {
DropOldestEntry();
}
nonces_.insert(nonce);
return true;
}
private:
// TimeFromBytes returns a big-endian uint32 from |d|.
static uint32 TimeFromBytes(const uint8 d[4]) {
return static_cast<uint32>(d[0]) << 24 |
static_cast<uint32>(d[1]) << 16 |
static_cast<uint32>(d[2]) << 8 |
static_cast<uint32>(d[3]);
}
void DropOldestEntry() {
set<string>::iterator oldest = nonces_.begin(), it;
uint32 oldest_time =
TimeFromBytes(reinterpret_cast<const uint8*>(oldest->data()));
for (it = oldest; it != nonces_.end(); it++) {
uint32 t = TimeFromBytes(reinterpret_cast<const uint8*>(it->data()));
if (t < oldest_time ||
(t == oldest_time && memcmp(it->data(), oldest->data(), 32) < 0)) {
oldest_time = t;
oldest = it;
}
}
nonces_.erase(oldest);
horizon_ = oldest_time;
}
const unsigned max_entries_;
const unsigned window_secs_;
uint8 orbit_[8];
uint32 horizon_;
set<string> nonces_;
};
TEST(StrikeRegisterStressTest, Stress) {
// Fixed seed gives reproducibility for this test.
srand(42);
unsigned max_entries = 64;
uint32 current_time = 10000, window = 200;
scoped_ptr<StrikeRegister> s1(new StrikeRegister(
max_entries, current_time, window, kOrbit));
scoped_ptr<SlowStrikeRegister> s2(new SlowStrikeRegister(
max_entries, current_time, window, kOrbit));
uint64 i;
// When making changes it's worth removing the limit on this test and running
// it for a while. For the initial development an opt binary was left running
// for 10 minutes.
const uint64 kMaxIterations = 10000;
for (i = 0; i < kMaxIterations; i++) {
if (rand() % 1000 == 0) {
// 0.1% chance of resetting the sets.
max_entries = rand() % 300 + 2;
current_time = rand() % 10000;
window = rand() % 500;
s1.reset(new StrikeRegister(max_entries, current_time, window, kOrbit));
s2.reset(new SlowStrikeRegister(max_entries, current_time, window,
kOrbit));
}
int32 time_delta = rand() % (window * 4);
time_delta -= window * 2;
const uint32 time = current_time + time_delta;
if (time_delta < 0 && time > current_time) {
continue; // overflow
}
uint8 nonce[32];
NonceSetTimeAndOrbit(nonce, time, kOrbit);
memset(nonce + 12, 0, 20);
// There are 2048 possible nonce values:
const uint32 v = rand() % 2048;
nonce[30] = v >> 8;
nonce[31] = v;
const bool r2 = s2->Insert(nonce, time);
const bool r1 = s1->Insert(nonce, time);
if (r1 != r2) {
break;
}
if (i % 10 == 0) {
s1->Validate();
}
}
if (i != kMaxIterations) {
FAIL() << "Failed after " << i << " iterations";
}
}
} // anonymous namespace
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment