Use siphash on some non-critical hashtables just in case

This commit is contained in:
Ludvig Strigeus 2018-10-07 19:22:17 +02:00
parent 22b24a81d9
commit e65f05d29a
7 changed files with 1629 additions and 41 deletions

View file

@ -33,7 +33,7 @@
v1 ^= key->key[1]; \
v0 ^= key->key[0];
#define POSTAMBLE \
#define POSTAMBLE24 \
v3 ^= b; \
SIPROUND; \
SIPROUND; \
@ -45,6 +45,17 @@
SIPROUND; \
return (v0 ^ v1) ^ (v2 ^ v3);
#define POSTAMBLE13 \
v3 ^= b; \
SIPROUND; \
v0 ^= b; \
v2 ^= 0xff; \
SIPROUND; \
SIPROUND; \
SIPROUND; \
return (v0 ^ v1) ^ (v2 ^ v3);
uint64 siphash(const void *data, size_t len, const siphash_key_t *key) {
const uint8 *end = (uint8*)data + len - (len % sizeof(uint64));
const uint8 left = len & (sizeof(uint64) - 1);
@ -66,7 +77,7 @@ uint64 siphash(const void *data, size_t len, const siphash_key_t *key) {
case 2: b |= ReadLE16(data); break;
case 1: b |= end[0];
}
POSTAMBLE
POSTAMBLE24
}
/**
@ -81,7 +92,7 @@ uint64 siphash_1u64(const uint64 first, const siphash_key_t *key)
SIPROUND;
SIPROUND;
v0 ^= first;
POSTAMBLE
POSTAMBLE24
}
/**
@ -101,7 +112,7 @@ uint64 siphash_2u64(const uint64 first, const uint64 second, const siphash_key_t
SIPROUND;
SIPROUND;
v0 ^= second;
POSTAMBLE
POSTAMBLE24
}
/**
@ -127,7 +138,58 @@ uint64 siphash_3u64(const uint64 first, const uint64 second, const uint64 third,
SIPROUND;
SIPROUND;
v0 ^= third;
POSTAMBLE
POSTAMBLE24
}
/**
* siphash13_3u64 - compute 64-bit siphash13 PRF value of 3 uint64
* @first: first uint64
* @second: second uint64
* @third: third uint64
* @key: the siphash key
*/
uint64 siphash13_3u64(const uint64 first, const uint64 second, const uint64 third,
const siphash_key_t *key) {
PREAMBLE(24)
v3 ^= first;
SIPROUND;
v0 ^= first;
v3 ^= second;
SIPROUND;
v0 ^= second;
v3 ^= third;
SIPROUND;
v0 ^= third;
POSTAMBLE13
}
uint64 siphash13_2u64(const uint64 first, const uint64 second, const siphash_key_t *key) {
PREAMBLE(24)
v3 ^= first;
SIPROUND;
v0 ^= first;
v3 ^= second;
SIPROUND;
v0 ^= second;
POSTAMBLE13
}
uint64 siphash13_4u64(const uint64 first, const uint64 second, const uint64 third, const uint64 fourth,
const siphash_key_t *key) {
PREAMBLE(24)
v3 ^= first;
SIPROUND;
v0 ^= first;
v3 ^= second;
SIPROUND;
v0 ^= second;
v3 ^= third;
SIPROUND;
v0 ^= third;
v3 ^= fourth;
SIPROUND;
v0 ^= fourth;
POSTAMBLE13
}
/**
@ -158,14 +220,14 @@ uint64 siphash_4u64(const uint64 first, const uint64 second, const uint64 third,
SIPROUND;
SIPROUND;
v0 ^= forth;
POSTAMBLE
POSTAMBLE24
}
uint64 siphash_1u32(const uint32 first, const siphash_key_t *key)
{
PREAMBLE(4)
b |= first;
POSTAMBLE
POSTAMBLE24
}
uint64 siphash_3u32(const uint32 first, const uint32 second, const uint32 third,
@ -178,7 +240,7 @@ uint64 siphash_3u32(const uint32 first, const uint32 second, const uint32 third,
SIPROUND;
v0 ^= combined;
b |= third;
POSTAMBLE
POSTAMBLE24
}
uint64 siphash_u64_u32(const uint64 combined, const uint32 third, const siphash_key_t *key) {
@ -188,6 +250,6 @@ uint64 siphash_u64_u32(const uint64 combined, const uint32 third, const siphash_
SIPROUND;
v0 ^= combined;
b |= third;
POSTAMBLE
POSTAMBLE24
}

View file

@ -50,4 +50,11 @@ uint64 siphash_u64_u32(const uint64 combined, const uint32 third, const siphash_
*/
uint64 siphash(const void *data, size_t len, const siphash_key_t *key);
uint64 siphash13_2u64(const uint64 first, const uint64 second, const siphash_key_t *key);
uint64 siphash13_3u64(const uint64 first, const uint64 second, const uint64 third,
const siphash_key_t *key);
uint64 siphash13_4u64(const uint64 first, const uint64 second, const uint64 third,
const uint64 fourth, const siphash_key_t *key);
#endif // TUNSAFE_CRYPTO_SIPHASH_H_

File diff suppressed because it is too large Load diff

View file

@ -9,3 +9,6 @@
#define WITH_HEADER_OBFUSCATION 0
#define WITH_AVX512_OPTIMIZATIONS 0
#define WITH_BENCHMARK 0
// Use bytell hashmap instead. Only works in 64-bit builds
#define WITH_BYTELL_HASHMAP 0

View file

@ -766,9 +766,7 @@ void WireguardProcessor::HandleShortHeaderFormatPacket(uint32 tag, Packet *packe
data += 4, bytes_left -= 4;
keypair = dev_.LookupKeypairByKeyId(key_id);
} else {
// Lookup the packet source ip and port in the address mapping
uint64 addr_id = packet->addr.sin.sin_addr.s_addr | ((uint64)packet->addr.sin.sin_port << 32);
keypair = dev_.LookupKeypairInAddrEntryMap(addr_id, ((tag / WG_SHORT_HEADER_KEY_ID) & 3) - 1);
keypair = dev_.LookupKeypairInAddrEntryMap(packet->addr, ((tag & WG_SHORT_HEADER_KEY_ID_MASK) / WG_SHORT_HEADER_KEY_ID) - 1);
}
if (!keypair || !keypair->enabled_features[WG_FEATURE_ID_SHORT_HEADER])
@ -854,10 +852,8 @@ void WireguardProcessor::HandleShortHeaderFormatPacket(uint32 tag, Packet *packe
// Periodically broadcast out the short key
if ((tag & WG_SHORT_HEADER_KEY_ID_MASK) == 0x00 && !keypair->did_attempt_remember_ip_port) {
keypair->did_attempt_remember_ip_port = true;
if (keypair->enabled_features[WG_FEATURE_ID_SKIP_KEYID_IN]) {
uint64 addr_id = packet->addr.sin.sin_addr.s_addr | ((uint64)packet->addr.sin.sin_port << 32);
dev_.UpdateKeypairAddrEntry_Locked(addr_id, keypair);
}
if (keypair->enabled_features[WG_FEATURE_ID_SKIP_KEYID_IN])
dev_.UpdateKeypairAddrEntry_Locked(packet->addr, keypair);
}
// Ack header may also signal that we can omit the key id in packets from now on.
if (tag & WG_SHORT_HEADER_ACK)

View file

@ -263,20 +263,36 @@ void WgDevice::EraseKeypairAddrEntry_Locked(WgKeypair *kp) {
}
}
WgKeypair *WgDevice::LookupKeypairInAddrEntryMap(uint64 addr, uint32 slot) {
static WgAddrEntry::IpPort ConvertIpAddrToAddrX(const IpAddr &src) {
WgAddrEntry::IpPort r;
if (src.sin.sin_family == AF_INET) {
Write64(r.bytes, src.sin.sin_addr.s_addr);
Write64(r.bytes + 8, 0);
Write32(r.bytes + 16, src.sin.sin_port);
} else {
memcpy(r.bytes, &src.sin6.sin6_addr, 16);
Write32(r.bytes + 16, (AF_INET6 << 16) + src.sin6.sin6_port);
}
return r;
}
WgKeypair *WgDevice::LookupKeypairInAddrEntryMap(const IpAddr &addr, uint32 slot) {
// Convert IpAddr to WgAddrEntry::IpPort suitable for use in hash.
WgAddrEntry::IpPort addr_x = ConvertIpAddrToAddrX(addr);
WG_SCOPED_RWLOCK_SHARED(addr_entry_lookup_lock_);
auto it = addr_entry_lookup_.find(addr);
auto it = addr_entry_lookup_.find(addr_x);
if (it == addr_entry_lookup_.end())
return NULL;
WgAddrEntry *addr_entry = it->second;
return addr_entry->keys[slot];
}
void WgDevice::UpdateKeypairAddrEntry_Locked(uint64 addr_id, WgKeypair *keypair) {
void WgDevice::UpdateKeypairAddrEntry_Locked(const IpAddr &addr, WgKeypair *keypair) {
assert(keypair->peer->IsPeerLocked());
WgAddrEntry::IpPort addr_x = ConvertIpAddrToAddrX(addr);
{
WG_SCOPED_RWLOCK_SHARED(addr_entry_lookup_lock_);
if (keypair->addr_entry != NULL && keypair->addr_entry->addr_entry_id == addr_id) {
if (keypair->addr_entry != NULL && keypair->addr_entry->addr_entry_id == addr_x) {
keypair->broadcast_short_key = 1;
return;
}
@ -286,10 +302,10 @@ void WgDevice::UpdateKeypairAddrEntry_Locked(uint64 addr_id, WgKeypair *keypair)
if (keypair->addr_entry != NULL)
EraseKeypairAddrEntry_Locked(keypair);
WgAddrEntry **aep = &addr_entry_lookup_[addr_id], *ae;
WgAddrEntry **aep = &addr_entry_lookup_[addr_x], *ae;
if ((ae = *aep) == NULL) {
*aep = ae = new WgAddrEntry(addr_id);
*aep = ae = new WgAddrEntry(addr_x);
} else {
// Ensure we don't insert new things in this addr entry too often.
if (ae->time_of_last_insertion + 1000 * 60 > low_resolution_timestamp_)
@ -1452,3 +1468,20 @@ bool WgKeypairDecryptPayload(uint8 *dst, size_t src_len,
return memcmp_crypto(mac, dst + src_len, keypair->auth_tag_length) == 0;
}
}
// A random siphash key that can be used for hashing so it gets harder to induce hash collisions.
struct RandomSiphashKey {
RandomSiphashKey() { OsGetRandomBytes((uint8*)&key, sizeof(key)); }
siphash_key_t key;
};
static RandomSiphashKey random_siphash_key;
size_t WgAddrEntry::IpPortHasher::operator()(const WgAddrEntry::IpPort &a) const {
uint32 xx = Read32(a.bytes + 16);
return siphash13_2u64(Read64(a.bytes) + xx, Read64(a.bytes + 8) + xx, &random_siphash_key.key);
}
size_t WgPublicKeyHasher::operator()(const WgPublicKey&a) const {
return siphash13_4u64(a.u64[0], a.u64[1], a.u64[2], a.u64[3], &random_siphash_key.key);
}

View file

@ -6,12 +6,18 @@
#include "netapi.h"
#include "ipzip2/ipzip2.h"
#include "tunsafe_config.h"
#include "tunsafe_endian.h"
#include "tunsafe_threading.h"
#include "ip_to_peer_map.h"
#include <vector>
#include <unordered_map>
#include <atomic>
#include <string.h>
#if WITH_BYTELL_HASHMAP
#include "third_party/flat_hash_map/bytell_hash_map.hpp"
#endif // WITH_BYTELL_HASHMAP
// Threading macros that enable locks only in MT builds
#if WITH_WG_THREADING
#define WG_SCOPED_LOCK(name) ScopedLock scoped_lock(&name)
@ -41,6 +47,13 @@
#define WG_IF_LOCKS_ENABLED_ELSE(expr, def) (def)
#endif // WITH_WG_THREADING
// bytell hash is faster but more untested
#if WITH_BYTELL_HASHMAP
#define WG_HASHTABLE_IMPL ska::bytell_hash_map
#else
#define WG_HASHTABLE_IMPL std::unordered_map
#endif
enum ProtocolTimeouts {
COOKIE_SECRET_MAX_AGE_MS = 120000,
COOKIE_SECRET_LATENCY_MS = 5000,
@ -235,13 +248,23 @@ private:
};
struct WgAddrEntry {
// The id of the addr entry, so we can delete ourselves
uint64 addr_entry_id;
struct IpPort {
uint8 bytes[20];
// Ensure there's at least 1 minute between we allow registering
// a new key in this table. This means that each key will have
// a life time of at least 3 minutes.
uint64 time_of_last_insertion;
friend bool operator==(const IpPort &a, const IpPort &b) {
uint64 rv = Read64(a.bytes) ^ Read64(b.bytes);
rv |= Read64(a.bytes + 8) ^ Read64(b.bytes + 8);
rv |= Read32(a.bytes + 16) ^ Read32(b.bytes + 16);
return (rv == 0);
}
};
struct IpPortHasher {
size_t operator()(const IpPort &a) const;
};
// The id of the addr entry, so we can delete ourselves
IpPort addr_entry_id;
// This entry gets erased when there's no longer any key pointing at it.
uint8 ref_count;
@ -249,13 +272,19 @@ struct WgAddrEntry {
// Index of the next slot 0-2 where we'll insert the next key.
uint8 next_slot;
// Ensure there's at least 1 minute between we allow registering
// a new key in this table. This means that each key will have
// a life time of at least 3 minutes.
uint64 time_of_last_insertion;
// The three keys.
WgKeypair *keys[3];
WgAddrEntry(uint64 addr_entry_id) : addr_entry_id(addr_entry_id), ref_count(0), next_slot(0) {
WgAddrEntry(const IpPort &addr_entry_id)
: addr_entry_id(addr_entry_id), ref_count(0), next_slot(0), time_of_last_insertion(0) {
keys[0] = keys[1] = keys[2] = NULL;
time_of_last_insertion = 0x123456789123456;
}
};
struct ScramblerSiphashKeys {
@ -271,10 +300,7 @@ union WgPublicKey {
};
struct WgPublicKeyHasher {
size_t operator()(const WgPublicKey&a) const {
uint64 rv = a.u64[0] ^ a.u64[1] ^ a.u64[2] ^ a.u64[3];
return (size_t)(rv ^ (rv >> 32));
}
size_t operator()(const WgPublicKey&a) const;
};
class WgDevice {
@ -314,14 +340,12 @@ public:
bool CheckCookieMac2(Packet *packet);
void CreateCookieMessage(MessageHandshakeCookie *dst, Packet *packet, uint32 remote_key_id);
void UpdateKeypairAddrEntry_Locked(uint64 addr_id, WgKeypair *keypair);
void SecondLoop(uint64 now);
IpToPeerMap &ip_to_peer_map() { return ip_to_peer_map_; }
WgPeer *first_peer() { return peers_; }
const uint8 *public_key() const { return s_pub_; }
WgRateLimit *rate_limiter() { return &rate_limiter_; }
std::unordered_map<uint64, WgAddrEntry*> &addr_entry_map() { return addr_entry_lookup_; }
WgPacketCompressionVer01 *compression_header() { return &compression_header_; }
bool is_private_key_initialized() { return is_private_key_initialized_; }
@ -333,7 +357,9 @@ public:
private:
std::pair<WgPeer*, WgKeypair*> *LookupPeerInKeyIdLookup(uint32 key_id);
WgKeypair *LookupKeypairByKeyId(uint32 key_id);
WgKeypair *LookupKeypairInAddrEntryMap(uint64 addr, uint32 slot);
void UpdateKeypairAddrEntry_Locked(const IpAddr &addr, WgKeypair *keypair);
WgKeypair *LookupKeypairInAddrEntryMap(const IpAddr &addr, uint32 slot);
// Return the peer matching the |public_key| or NULL
WgPeer *GetPeerFromPublicKey(const WgPublicKey &pubkey);
// Create a cookie by inspecting the source address of the |packet|
@ -357,20 +383,26 @@ private:
// For hooking
Delegate *delegate_;
// Keypair IDs are generated randomly by us so no point in wasting cycles on
// hashing the random value.
struct KeyIdHasher {
size_t operator()(uint32 v) const { return v; }
};
// Lock that protects key_id_lookup_
WG_DECLARE_RWLOCK(key_id_lookup_lock_);
// Mapping from key-id to either an active keypair (if keypair is non-NULL),
// or to a handshake.
std::unordered_map<uint32, std::pair<WgPeer*, WgKeypair*> > key_id_lookup_;
WG_HASHTABLE_IMPL<uint32, std::pair<WgPeer*, WgKeypair*>, KeyIdHasher> key_id_lookup_;
// Mapping from IPV4 IP/PORT to WgPeer*, so we can find the peer when a key id is
// not explicitly included.
std::unordered_map<uint64, WgAddrEntry*> addr_entry_lookup_;
WG_HASHTABLE_IMPL<WgAddrEntry::IpPort, WgAddrEntry*, WgAddrEntry::IpPortHasher> addr_entry_lookup_;
WG_DECLARE_RWLOCK(addr_entry_lookup_lock_);
// Mapping from peer id to peer. This may be accessed only from MT.
std::unordered_map<WgPublicKey, WgPeer*, WgPublicKeyHasher> peer_id_lookup_;
WG_HASHTABLE_IMPL<WgPublicKey, WgPeer*, WgPublicKeyHasher> peer_id_lookup_;
// Queue of things scheduled to run on the main thread.
WG_DECLARE_LOCK(main_thread_scheduled_lock_);
WgPeer *main_thread_scheduled_, **main_thread_scheduled_last_;