mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 14:13:48 +03:00
reverseproxy: Use xxhash instead of fnv32 for LB (#6203)
* Added Faster Non-cryptographic Hash Function for Load Balancing * Ran golangci-lint * Updated hash version and hash return type
This commit is contained in:
parent
ddb1d2c2b1
commit
74949fb091
2 changed files with 84 additions and 83 deletions
|
@ -20,7 +20,6 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
|
||||||
weakrand "math/rand"
|
weakrand "math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -28,6 +27,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/cespare/xxhash/v2"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
|
@ -807,7 +808,7 @@ func hostByHashing(pool []*Upstream, s string) *Upstream {
|
||||||
// see https://medium.com/i0exception/rendezvous-hashing-8c00e2fb58b0,
|
// see https://medium.com/i0exception/rendezvous-hashing-8c00e2fb58b0,
|
||||||
// https://randorithms.com/2020/12/26/rendezvous-hashing.html,
|
// https://randorithms.com/2020/12/26/rendezvous-hashing.html,
|
||||||
// and https://en.wikipedia.org/wiki/Rendezvous_hashing.
|
// and https://en.wikipedia.org/wiki/Rendezvous_hashing.
|
||||||
var highestHash uint32
|
var highestHash uint64
|
||||||
var upstream *Upstream
|
var upstream *Upstream
|
||||||
for _, up := range pool {
|
for _, up := range pool {
|
||||||
if !up.Available() {
|
if !up.Available() {
|
||||||
|
@ -823,10 +824,10 @@ func hostByHashing(pool []*Upstream, s string) *Upstream {
|
||||||
}
|
}
|
||||||
|
|
||||||
// hash calculates a fast hash based on s.
|
// hash calculates a fast hash based on s.
|
||||||
func hash(s string) uint32 {
|
func hash(s string) uint64 {
|
||||||
h := fnv.New32a()
|
h := xxhash.New()
|
||||||
_, _ = h.Write([]byte(s))
|
_, _ = h.Write([]byte(s))
|
||||||
return h.Sum32()
|
return h.Sum64()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadFallbackPolicy(d *caddyfile.Dispenser) (json.RawMessage, error) {
|
func loadFallbackPolicy(d *caddyfile.Dispenser) (json.RawMessage, error) {
|
||||||
|
|
|
@ -157,8 +157,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
// We should be able to predict where every request is routed.
|
// We should be able to predict where every request is routed.
|
||||||
req.RemoteAddr = "172.0.0.1:80"
|
req.RemoteAddr = "172.0.0.1:80"
|
||||||
h := ipHash.Select(pool, req, nil)
|
h := ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.2:80"
|
req.RemoteAddr = "172.0.0.2:80"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -167,8 +167,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.3:80"
|
req.RemoteAddr = "172.0.0.3:80"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.4:80"
|
req.RemoteAddr = "172.0.0.4:80"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -179,8 +179,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
// we should get the same results without a port
|
// we should get the same results without a port
|
||||||
req.RemoteAddr = "172.0.0.1"
|
req.RemoteAddr = "172.0.0.1"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.2"
|
req.RemoteAddr = "172.0.0.2"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -189,8 +189,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.3"
|
req.RemoteAddr = "172.0.0.3"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.4"
|
req.RemoteAddr = "172.0.0.4"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -203,8 +203,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
req.RemoteAddr = "172.0.0.4"
|
req.RemoteAddr = "172.0.0.4"
|
||||||
pool[1].setHealthy(false)
|
pool[1].setHealthy(false)
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[0] {
|
if h != pool[2] {
|
||||||
t.Error("Expected ip hash policy host to be the first host.")
|
t.Error("Expected ip hash policy host to be the third host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
req.RemoteAddr = "172.0.0.2"
|
req.RemoteAddr = "172.0.0.2"
|
||||||
|
@ -217,8 +217,8 @@ func TestIPHashPolicy(t *testing.T) {
|
||||||
req.RemoteAddr = "172.0.0.3"
|
req.RemoteAddr = "172.0.0.3"
|
||||||
pool[2].setHealthy(false)
|
pool[2].setHealthy(false)
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
req.RemoteAddr = "172.0.0.4"
|
req.RemoteAddr = "172.0.0.4"
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -300,8 +300,8 @@ func TestClientIPHashPolicy(t *testing.T) {
|
||||||
// We should be able to predict where every request is routed.
|
// We should be able to predict where every request is routed.
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1:80")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1:80")
|
||||||
h := ipHash.Select(pool, req, nil)
|
h := ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2:80")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2:80")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -310,8 +310,8 @@ func TestClientIPHashPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3:80")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3:80")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4:80")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4:80")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -322,8 +322,8 @@ func TestClientIPHashPolicy(t *testing.T) {
|
||||||
// we should get the same results without a port
|
// we should get the same results without a port
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -332,8 +332,8 @@ func TestClientIPHashPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -346,8 +346,8 @@ func TestClientIPHashPolicy(t *testing.T) {
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
||||||
pool[1].setHealthy(false)
|
pool[1].setHealthy(false)
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[0] {
|
if h != pool[2] {
|
||||||
t.Error("Expected ip hash policy host to be the first host.")
|
t.Error("Expected ip hash policy host to be the third host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
|
||||||
|
@ -360,8 +360,8 @@ func TestClientIPHashPolicy(t *testing.T) {
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
|
||||||
pool[2].setHealthy(false)
|
pool[2].setHealthy(false)
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected ip hash policy host to be the second host.")
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
}
|
}
|
||||||
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
|
||||||
h = ipHash.Select(pool, req, nil)
|
h = ipHash.Select(pool, req, nil)
|
||||||
|
@ -470,21 +470,21 @@ func TestQueryHashPolicy(t *testing.T) {
|
||||||
|
|
||||||
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
|
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
|
||||||
h = queryPolicy.Select(pool, request, nil)
|
h = queryPolicy.Select(pool, request, nil)
|
||||||
if h != pool[0] {
|
if h != pool[1] {
|
||||||
t.Error("Expected query policy host to be the first host.")
|
t.Error("Expected query policy host to be the second host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
request = httptest.NewRequest(http.MethodGet, "/?foo=1", nil)
|
request = httptest.NewRequest(http.MethodGet, "/?foo=1", nil)
|
||||||
pool[0].setHealthy(false)
|
pool[0].setHealthy(false)
|
||||||
h = queryPolicy.Select(pool, request, nil)
|
h = queryPolicy.Select(pool, request, nil)
|
||||||
if h != pool[1] {
|
if h != pool[2] {
|
||||||
t.Error("Expected query policy host to be the second host.")
|
t.Error("Expected query policy host to be the third host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
|
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
|
||||||
h = queryPolicy.Select(pool, request, nil)
|
h = queryPolicy.Select(pool, request, nil)
|
||||||
if h != pool[2] {
|
if h != pool[1] {
|
||||||
t.Error("Expected query policy host to be the third host.")
|
t.Error("Expected query policy host to be the second host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should be able to resize the host pool and still be able to predict
|
// We should be able to resize the host pool and still be able to predict
|
||||||
|
@ -533,14 +533,14 @@ func TestURIHashPolicy(t *testing.T) {
|
||||||
|
|
||||||
request := httptest.NewRequest(http.MethodGet, "/test", nil)
|
request := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||||
h := uriPolicy.Select(pool, request, nil)
|
h := uriPolicy.Select(pool, request, nil)
|
||||||
if h != pool[1] {
|
if h != pool[2] {
|
||||||
t.Error("Expected uri policy host to be the second host.")
|
t.Error("Expected uri policy host to be the third host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
pool[2].setHealthy(false)
|
pool[2].setHealthy(false)
|
||||||
h = uriPolicy.Select(pool, request, nil)
|
h = uriPolicy.Select(pool, request, nil)
|
||||||
if h != pool[1] {
|
if h != pool[0] {
|
||||||
t.Error("Expected uri policy host to be the second host.")
|
t.Error("Expected uri policy host to be the first host.")
|
||||||
}
|
}
|
||||||
|
|
||||||
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
|
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
|
||||||
|
@ -691,54 +691,54 @@ func TestCookieHashPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCookieHashPolicyWithSecureRequest(t *testing.T) {
|
func TestCookieHashPolicyWithSecureRequest(t *testing.T) {
|
||||||
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
||||||
defer cancel()
|
defer cancel()
|
||||||
cookieHashPolicy := CookieHashSelection{}
|
cookieHashPolicy := CookieHashSelection{}
|
||||||
if err := cookieHashPolicy.Provision(ctx); err != nil {
|
if err := cookieHashPolicy.Provision(ctx); err != nil {
|
||||||
t.Errorf("Provision error: %v", err)
|
t.Errorf("Provision error: %v", err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
pool := testPool()
|
pool := testPool()
|
||||||
pool[0].Dial = "localhost:8080"
|
pool[0].Dial = "localhost:8080"
|
||||||
pool[1].Dial = "localhost:8081"
|
pool[1].Dial = "localhost:8081"
|
||||||
pool[2].Dial = "localhost:8082"
|
pool[2].Dial = "localhost:8082"
|
||||||
pool[0].setHealthy(true)
|
pool[0].setHealthy(true)
|
||||||
pool[1].setHealthy(false)
|
pool[1].setHealthy(false)
|
||||||
pool[2].setHealthy(false)
|
pool[2].setHealthy(false)
|
||||||
|
|
||||||
// Create a test server that serves HTTPS requests
|
// Create a test server that serves HTTPS requests
|
||||||
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
h := cookieHashPolicy.Select(pool, r, w)
|
h := cookieHashPolicy.Select(pool, r, w)
|
||||||
if h != pool[0] {
|
if h != pool[0] {
|
||||||
t.Error("Expected cookieHashPolicy host to be the first only available host.")
|
t.Error("Expected cookieHashPolicy host to be the first only available host.")
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
// Make a new HTTPS request to the test server
|
// Make a new HTTPS request to the test server
|
||||||
client := ts.Client()
|
client := ts.Client()
|
||||||
request, err := http.NewRequest(http.MethodGet, ts.URL+"/test", nil)
|
request, err := http.NewRequest(http.MethodGet, ts.URL+"/test", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
response, err := client.Do(request)
|
response, err := client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the cookie set is Secure and has SameSiteNone mode
|
// Check if the cookie set is Secure and has SameSiteNone mode
|
||||||
cookies := response.Cookies()
|
cookies := response.Cookies()
|
||||||
if len(cookies) == 0 {
|
if len(cookies) == 0 {
|
||||||
t.Fatal("Expected a cookie to be set")
|
t.Fatal("Expected a cookie to be set")
|
||||||
}
|
}
|
||||||
cookie := cookies[0]
|
cookie := cookies[0]
|
||||||
if !cookie.Secure {
|
if !cookie.Secure {
|
||||||
t.Error("Expected cookie Secure attribute to be true when request is secure")
|
t.Error("Expected cookie Secure attribute to be true when request is secure")
|
||||||
}
|
}
|
||||||
if cookie.SameSite != http.SameSiteNoneMode {
|
if cookie.SameSite != http.SameSiteNoneMode {
|
||||||
t.Error("Expected cookie SameSite attribute to be None when request is secure")
|
t.Error("Expected cookie SameSite attribute to be None when request is secure")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCookieHashPolicyWithFirstFallback(t *testing.T) {
|
func TestCookieHashPolicyWithFirstFallback(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue