Skip to content

Commit db539a1

Browse files
committed
Make Parent Selection Hash configurable and add SipHash-1-3 and Wyhash v4.1.
1 parent cf60dcd commit db539a1

File tree

19 files changed

+1050
-174
lines changed

19 files changed

+1050
-174
lines changed

configs/records.yaml.default.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ records:
130130
##############################################################################
131131
parent_proxy:
132132
retry_time: 300
133+
consistent_hash_algorithm: siphash24
133134

134135
##############################################################################
135136
# Security. Docs:

doc/admin-guide/files/parent.config.en.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,13 @@ The following list shows the possible actions and their allowed values.
278278
The other traffic is unaffected. Once the downed parent becomes
279279
available, the traffic distribution returns to the pre-down
280280
state.
281+
282+
The hash algorithm used for consistent hashing can be configured via
283+
:ts:cv:`proxy.config.http.parent_proxy.consistent_hash_algorithm`. Available
284+
algorithms are ``siphash24`` (default), ``siphash13`` (faster), and ``wyhash``
285+
(fastest). See the records.yaml documentation for performance characteristics
286+
and migration considerations.
287+
281288
- ``latched`` - The first parent in the list is marked as primary and is
282289
always chosen until connection errors cause it to be marked down. When
283290
this occurs the next parent in the list then becomes primary. The primary

doc/admin-guide/files/records.yaml.en.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,28 @@ Parent Proxy Configuration
14711471
``2`` Mark the host down. This is the default.
14721472
===== ======================================================================
14731473

1474+
.. ts:cv:: CONFIG proxy.config.http.parent_proxy.consistent_hash_algorithm STRING siphash24
1475+
1476+
Selects the hash algorithm used for consistent hash parent selection. This setting
1477+
only affects parent selection when ``round_robin=consistent_hash`` is configured in
1478+
:file:`parent.config`. The hash algorithm determines how requests are distributed
1479+
across parent proxies.
1480+
1481+
============== ================================================================================
1482+
Value Description
1483+
============== ================================================================================
1484+
``siphash24`` SipHash-2-4 (default). Cryptographically strong, DoS-resistant hash function.
1485+
``siphash13`` SipHash-1-3. ~50% faster than SipHash-2-4, still DoS-resistant.
1486+
``wyhash`` Wyhash v4.1. ~3-5x faster than SipHash-2-4, DoS-resistant.
1487+
============== ================================================================================
1488+
1489+
.. warning::
1490+
1491+
Changing this setting will cause requests to be redistributed differently across
1492+
parent proxies. This can lead to cache churn and increased origin load during the
1493+
transition period. Plan the migration carefully and consider doing it during
1494+
low-traffic periods.
1495+
14741496
.. ts:cv:: CONFIG proxy.config.http.parent_proxy.enable_parent_timeout_markdowns INT 0
14751497
:reloadable:
14761498
:overridable:

include/proxy/ParentConsistentHash.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@
3838
//
3939
class ParentConsistentHash : public ParentSelectionStrategy
4040
{
41-
// there are two hashes PRIMARY parents
42-
// and SECONDARY parents.
43-
ATSHash64Sip24 hash[2];
41+
std::unique_ptr<ATSHash64> hash[2];
4442
std::unique_ptr<ATSConsistentHash> chash[2];
4543
pRecord *parents[2];
4644
bool foundParents[2][MAX_PARENTS];
4745
bool ignore_query;
4846
int secondary_mode;
47+
ParentHashAlgorithm selected_algorithm;
48+
49+
std::unique_ptr<ATSHash64> createHashInstance(ParentHashAlgorithm algo);
4950

5051
public:
5152
static const int PRIMARY = 0;

include/proxy/ParentSelection.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ enum class ParentRetry_t {
7373
BOTH = 3
7474
};
7575

76+
enum class ParentHashAlgorithm { SIPHASH24 = 0, SIPHASH13, WYHASH };
77+
7678
struct UnavailableServerResponseCodes {
7779
UnavailableServerResponseCodes(char *val);
7880
~UnavailableServerResponseCodes(){};
@@ -163,6 +165,7 @@ class ParentRecord : public ControlBase
163165
int max_unavailable_server_retries = 1;
164166
int secondary_mode = 1;
165167
bool ignore_self_detect = false;
168+
ParentHashAlgorithm consistent_hash_algorithm = ParentHashAlgorithm::SIPHASH24;
166169
};
167170

168171
// If the parent was set by the external customer api,

include/tscore/HashSip.h

Lines changed: 137 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,145 @@
2323

2424
#include "tscore/Hash.h"
2525
#include <cstdint>
26+
#include <cstring>
2627

2728
/*
28-
Siphash is a Hash Message Authentication Code and can take a key.
29+
SipHash is a Hash Message Authentication Code and can take a key.
2930
3031
If you don't care about MAC use the void constructor and it will use
3132
a zero key for you.
32-
*/
3333
34-
struct ATSHash64Sip24 : ATSHash64 {
35-
ATSHash64Sip24();
36-
ATSHash64Sip24(const unsigned char key[16]);
37-
ATSHash64Sip24(std::uint64_t key0, std::uint64_t key1);
38-
void update(const void *data, std::size_t len) override;
39-
void final() override;
40-
std::uint64_t get() const override;
41-
void clear() override;
34+
Template parameters:
35+
- c_rounds: number of compression rounds per message block
36+
- d_rounds: number of finalization rounds
37+
*/
38+
39+
#define SIP_BLOCK_SIZE 8
40+
41+
#define ROTL64(a, b) (((a) << (b)) | ((a) >> (64 - b)))
42+
43+
static inline std::uint64_t
44+
U8TO64_LE(const std::uint8_t *p)
45+
{
46+
std::uint64_t result;
47+
std::memcpy(&result, p, sizeof(result));
48+
return result;
49+
}
50+
51+
#define SIPCOMPRESS(x0, x1, x2, x3) \
52+
x0 += x1; \
53+
x2 += x3; \
54+
x1 = ROTL64(x1, 13); \
55+
x3 = ROTL64(x3, 16); \
56+
x1 ^= x0; \
57+
x3 ^= x2; \
58+
x0 = ROTL64(x0, 32); \
59+
x2 += x1; \
60+
x0 += x3; \
61+
x1 = ROTL64(x1, 17); \
62+
x3 = ROTL64(x3, 21); \
63+
x1 ^= x2; \
64+
x3 ^= x0; \
65+
x2 = ROTL64(x2, 32);
66+
67+
template <int c_rounds, int d_rounds> struct ATSHashSip : ATSHash64 {
68+
ATSHashSip() { this->clear(); }
69+
70+
ATSHashSip(const unsigned char key[16]) : k0(U8TO64_LE(key)), k1(U8TO64_LE(key + sizeof(k0))) { this->clear(); }
71+
72+
ATSHashSip(std::uint64_t key0, std::uint64_t key1) : k0(key0), k1(key1) { this->clear(); }
73+
74+
void
75+
update(const void *data, std::size_t len) override
76+
{
77+
std::size_t i, blocks;
78+
unsigned char *m;
79+
std::uint64_t mi;
80+
std::uint8_t block_off = 0;
81+
82+
if (!finalized) {
83+
m = (unsigned char *)data;
84+
total_len += len;
85+
86+
if (len + block_buffer_len < SIP_BLOCK_SIZE) {
87+
std::memcpy(block_buffer + block_buffer_len, m, len);
88+
block_buffer_len += len;
89+
} else {
90+
if (block_buffer_len > 0) {
91+
block_off = SIP_BLOCK_SIZE - block_buffer_len;
92+
std::memcpy(block_buffer + block_buffer_len, m, block_off);
93+
94+
mi = U8TO64_LE(block_buffer);
95+
v3 ^= mi;
96+
for (int r = 0; r < c_rounds; r++) {
97+
SIPCOMPRESS(v0, v1, v2, v3);
98+
}
99+
v0 ^= mi;
100+
}
101+
102+
for (i = block_off, blocks = ((len - block_off) & ~(SIP_BLOCK_SIZE - 1)); i < blocks; i += SIP_BLOCK_SIZE) {
103+
mi = U8TO64_LE(m + i);
104+
v3 ^= mi;
105+
for (int r = 0; r < c_rounds; r++) {
106+
SIPCOMPRESS(v0, v1, v2, v3);
107+
}
108+
v0 ^= mi;
109+
}
110+
111+
block_buffer_len = (len - block_off) & (SIP_BLOCK_SIZE - 1);
112+
std::memcpy(block_buffer, m + block_off + blocks, block_buffer_len);
113+
}
114+
}
115+
}
116+
117+
void
118+
final() override
119+
{
120+
std::uint64_t last7;
121+
int i;
122+
123+
if (!finalized) {
124+
last7 = static_cast<std::uint64_t>(total_len & 0xff) << 56;
125+
126+
for (i = block_buffer_len - 1; i >= 0; i--) {
127+
last7 |= static_cast<std::uint64_t>(block_buffer[i]) << (i * 8);
128+
}
129+
130+
v3 ^= last7;
131+
for (int r = 0; r < c_rounds; r++) {
132+
SIPCOMPRESS(v0, v1, v2, v3);
133+
}
134+
v0 ^= last7;
135+
v2 ^= 0xff;
136+
for (int r = 0; r < d_rounds; r++) {
137+
SIPCOMPRESS(v0, v1, v2, v3);
138+
}
139+
hfinal = v0 ^ v1 ^ v2 ^ v3;
140+
finalized = true;
141+
}
142+
}
143+
144+
std::uint64_t
145+
get() const override
146+
{
147+
if (finalized) {
148+
return hfinal;
149+
} else {
150+
return 0;
151+
}
152+
}
153+
154+
void
155+
clear() override
156+
{
157+
v0 = k0 ^ 0x736f6d6570736575ull;
158+
v1 = k1 ^ 0x646f72616e646f6dull;
159+
v2 = k0 ^ 0x6c7967656e657261ull;
160+
v3 = k1 ^ 0x7465646279746573ull;
161+
finalized = false;
162+
total_len = 0;
163+
block_buffer_len = 0;
164+
}
42165

43166
private:
44167
unsigned char block_buffer[8] = {0};
@@ -53,3 +176,7 @@ struct ATSHash64Sip24 : ATSHash64 {
53176
std::size_t total_len = 0;
54177
bool finalized = false;
55178
};
179+
180+
// Standard SipHash variants
181+
using ATSHash64Sip24 = ATSHashSip<2, 4>;
182+
using ATSHash64Sip13 = ATSHashSip<1, 3>;

include/tscore/HashWyhash.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/** @file
2+
3+
@section license License
4+
5+
Licensed to the Apache Software Foundation (ASF) under one
6+
or more contributor license agreements. See the NOTICE file
7+
distributed with this work for additional information
8+
regarding copyright ownership. The ASF licenses this file
9+
to you under the Apache License, Version 2.0 (the
10+
"License"); you may not use this file except in compliance
11+
with the License. You may obtain a copy of the License at
12+
13+
http://www.apache.org/licenses/LICENSE-2.0
14+
15+
Unless required by applicable law or agreed to in writing, software
16+
distributed under the License is distributed on an "AS IS" BASIS,
17+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
See the License for the specific language governing permissions and
19+
limitations under the License.
20+
*/
21+
22+
#pragma once
23+
24+
#include "tscore/Hash.h"
25+
#include <cstdint>
26+
27+
struct ATSHash64Wyhash : ATSHash64 {
28+
ATSHash64Wyhash();
29+
ATSHash64Wyhash(std::uint64_t seed);
30+
void update(const void *data, std::size_t len) override;
31+
void final() override;
32+
std::uint64_t get() const override;
33+
void clear() override;
34+
35+
private:
36+
std::uint64_t seed = 0;
37+
std::uint64_t state = 0;
38+
std::uint64_t total_len = 0;
39+
std::uint64_t hfinal = 0;
40+
bool finalized = false;
41+
42+
unsigned char buffer[32] = {0};
43+
std::uint8_t buffer_len = 0;
44+
};

src/proxy/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,8 @@ if(TS_USE_QUIC)
5555
add_subdirectory(http3)
5656
endif()
5757

58+
if(BUILD_TESTING)
59+
add_subdirectory(unit_tests)
60+
endif()
61+
5862
clang_tidy_check(proxy)

0 commit comments

Comments
 (0)