@marcel91200 / caddy Mirror · read-only / commits / 74949fb0
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 12, 14:30 UTC

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

Hayder committed Mar 29, 2024 at 12:56 UTC 74949fb0914d7d496efadf51ef2dd81e64b1b7d0
2 files changed +88 -87
modules/caddyhttp/reverseproxy/selectionpolicies.go
+6 -5
@@ -20,7 +20,6 @@ import (
20 "encoding/hex"
21 "encoding/json"
22 "fmt"
23 - "hash/fnv"
23 weakrand "math/rand"
24 "net"
25 "net/http"
@@ -28,6 +27,8 @@ import (
27 "strings"
28 "sync/atomic"
29
30 + "github.com/cespare/xxhash/v2"
31 +
32 "github.com/caddyserver/caddy/v2"
33 "github.com/caddyserver/caddy/v2/caddyconfig"
34 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
@@ -807,7 +808,7 @@ func hostByHashing(pool []*Upstream, s string) *Upstream {
808 // see https://medium.com/i0exception/rendezvous-hashing-8c00e2fb58b0,
809 // https://randorithms.com/2020/12/26/rendezvous-hashing.html,
810 // and https://en.wikipedia.org/wiki/Rendezvous_hashing.
810 - var highestHash uint32
811 + var highestHash uint64
812 var upstream *Upstream
813 for _, up := range pool {
814 if !up.Available() {
@@ -823,10 +824,10 @@ func hostByHashing(pool []*Upstream, s string) *Upstream {
824 }
825
826 // hash calculates a fast hash based on s.
826 -func hash(s string) uint32 {
827 - h := fnv.New32a()
827 +func hash(s string) uint64 {
828 + h := xxhash.New()
829 _, _ = h.Write([]byte(s))
829 - return h.Sum32()
830 + return h.Sum64()
831 }
832
833 func loadFallbackPolicy(d *caddyfile.Dispenser) (json.RawMessage, error) {
modules/caddyhttp/reverseproxy/selectionpolicies_test.go
+82 -82
@@ -157,8 +157,8 @@ func TestIPHashPolicy(t *testing.T) {
157 // We should be able to predict where every request is routed.
158 req.RemoteAddr = "172.0.0.1:80"
159 h := ipHash.Select(pool, req, nil)
160 - if h != pool[1] {
161 - t.Error("Expected ip hash policy host to be the second host.")
160 + if h != pool[0] {
161 + t.Error("Expected ip hash policy host to be the first host.")
162 }
163 req.RemoteAddr = "172.0.0.2:80"
164 h = ipHash.Select(pool, req, nil)
@@ -167,8 +167,8 @@ func TestIPHashPolicy(t *testing.T) {
167 }
168 req.RemoteAddr = "172.0.0.3:80"
169 h = ipHash.Select(pool, req, nil)
170 - if h != pool[1] {
171 - t.Error("Expected ip hash policy host to be the second host.")
170 + if h != pool[0] {
171 + t.Error("Expected ip hash policy host to be the first host.")
172 }
173 req.RemoteAddr = "172.0.0.4:80"
174 h = ipHash.Select(pool, req, nil)
@@ -179,8 +179,8 @@ func TestIPHashPolicy(t *testing.T) {
179 // we should get the same results without a port
180 req.RemoteAddr = "172.0.0.1"
181 h = ipHash.Select(pool, req, nil)
182 - if h != pool[1] {
183 - t.Error("Expected ip hash policy host to be the second host.")
182 + if h != pool[0] {
183 + t.Error("Expected ip hash policy host to be the first host.")
184 }
185 req.RemoteAddr = "172.0.0.2"
186 h = ipHash.Select(pool, req, nil)
@@ -189,8 +189,8 @@ func TestIPHashPolicy(t *testing.T) {
189 }
190 req.RemoteAddr = "172.0.0.3"
191 h = ipHash.Select(pool, req, nil)
192 - if h != pool[1] {
193 - t.Error("Expected ip hash policy host to be the second host.")
192 + if h != pool[0] {
193 + t.Error("Expected ip hash policy host to be the first host.")
194 }
195 req.RemoteAddr = "172.0.0.4"
196 h = ipHash.Select(pool, req, nil)
@@ -203,8 +203,8 @@ func TestIPHashPolicy(t *testing.T) {
203 req.RemoteAddr = "172.0.0.4"
204 pool[1].setHealthy(false)
205 h = ipHash.Select(pool, req, nil)
206 - if h != pool[0] {
207 - t.Error("Expected ip hash policy host to be the first host.")
206 + if h != pool[2] {
207 + t.Error("Expected ip hash policy host to be the third host.")
208 }
209
210 req.RemoteAddr = "172.0.0.2"
@@ -217,8 +217,8 @@ func TestIPHashPolicy(t *testing.T) {
217 req.RemoteAddr = "172.0.0.3"
218 pool[2].setHealthy(false)
219 h = ipHash.Select(pool, req, nil)
220 - if h != pool[1] {
221 - t.Error("Expected ip hash policy host to be the second host.")
220 + if h != pool[0] {
221 + t.Error("Expected ip hash policy host to be the first host.")
222 }
223 req.RemoteAddr = "172.0.0.4"
224 h = ipHash.Select(pool, req, nil)
@@ -300,8 +300,8 @@ func TestClientIPHashPolicy(t *testing.T) {
300 // We should be able to predict where every request is routed.
301 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1:80")
302 h := ipHash.Select(pool, req, nil)
303 - if h != pool[1] {
304 - t.Error("Expected ip hash policy host to be the second host.")
303 + if h != pool[0] {
304 + t.Error("Expected ip hash policy host to be the first host.")
305 }
306 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2:80")
307 h = ipHash.Select(pool, req, nil)
@@ -310,8 +310,8 @@ func TestClientIPHashPolicy(t *testing.T) {
310 }
311 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3:80")
312 h = ipHash.Select(pool, req, nil)
313 - if h != pool[1] {
314 - t.Error("Expected ip hash policy host to be the second host.")
313 + if h != pool[0] {
314 + t.Error("Expected ip hash policy host to be the first host.")
315 }
316 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4:80")
317 h = ipHash.Select(pool, req, nil)
@@ -322,8 +322,8 @@ func TestClientIPHashPolicy(t *testing.T) {
322 // we should get the same results without a port
323 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1")
324 h = ipHash.Select(pool, req, nil)
325 - if h != pool[1] {
326 - t.Error("Expected ip hash policy host to be the second host.")
325 + if h != pool[0] {
326 + t.Error("Expected ip hash policy host to be the first host.")
327 }
328 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
329 h = ipHash.Select(pool, req, nil)
@@ -332,8 +332,8 @@ func TestClientIPHashPolicy(t *testing.T) {
332 }
333 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
334 h = ipHash.Select(pool, req, nil)
335 - if h != pool[1] {
336 - t.Error("Expected ip hash policy host to be the second host.")
335 + if h != pool[0] {
336 + t.Error("Expected ip hash policy host to be the first host.")
337 }
338 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
339 h = ipHash.Select(pool, req, nil)
@@ -346,8 +346,8 @@ func TestClientIPHashPolicy(t *testing.T) {
346 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
347 pool[1].setHealthy(false)
348 h = ipHash.Select(pool, req, nil)
349 - if h != pool[0] {
350 - t.Error("Expected ip hash policy host to be the first host.")
349 + if h != pool[2] {
350 + t.Error("Expected ip hash policy host to be the third host.")
351 }
352
353 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
@@ -360,8 +360,8 @@ func TestClientIPHashPolicy(t *testing.T) {
360 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
361 pool[2].setHealthy(false)
362 h = ipHash.Select(pool, req, nil)
363 - if h != pool[1] {
364 - t.Error("Expected ip hash policy host to be the second host.")
363 + if h != pool[0] {
364 + t.Error("Expected ip hash policy host to be the first host.")
365 }
366 caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
367 h = ipHash.Select(pool, req, nil)
@@ -470,21 +470,21 @@ func TestQueryHashPolicy(t *testing.T) {
470
471 request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
472 h = queryPolicy.Select(pool, request, nil)
473 - if h != pool[0] {
474 - t.Error("Expected query policy host to be the first host.")
473 + if h != pool[1] {
474 + t.Error("Expected query policy host to be the second host.")
475 }
476
477 request = httptest.NewRequest(http.MethodGet, "/?foo=1", nil)
478 pool[0].setHealthy(false)
479 h = queryPolicy.Select(pool, request, nil)
480 - if h != pool[1] {
481 - t.Error("Expected query policy host to be the second host.")
480 + if h != pool[2] {
481 + t.Error("Expected query policy host to be the third host.")
482 }
483
484 request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
485 h = queryPolicy.Select(pool, request, nil)
486 - if h != pool[2] {
487 - t.Error("Expected query policy host to be the third host.")
486 + if h != pool[1] {
487 + t.Error("Expected query policy host to be the second host.")
488 }
489
490 // We should be able to resize the host pool and still be able to predict
@@ -533,14 +533,14 @@ func TestURIHashPolicy(t *testing.T) {
533
534 request := httptest.NewRequest(http.MethodGet, "/test", nil)
535 h := uriPolicy.Select(pool, request, nil)
536 - if h != pool[1] {
537 - t.Error("Expected uri policy host to be the second host.")
536 + if h != pool[2] {
537 + t.Error("Expected uri policy host to be the third host.")
538 }
539
540 pool[2].setHealthy(false)
541 h = uriPolicy.Select(pool, request, nil)
542 - if h != pool[1] {
543 - t.Error("Expected uri policy host to be the second host.")
542 + if h != pool[0] {
543 + t.Error("Expected uri policy host to be the first host.")
544 }
545
546 request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
@@ -691,54 +691,54 @@ func TestCookieHashPolicy(t *testing.T) {
691 }
692
693 func TestCookieHashPolicyWithSecureRequest(t *testing.T) {
694 - ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
695 - defer cancel()
696 - cookieHashPolicy := CookieHashSelection{}
697 - if err := cookieHashPolicy.Provision(ctx); err != nil {
698 - t.Errorf("Provision error: %v", err)
699 - t.FailNow()
700 - }
701 -
702 - pool := testPool()
703 - pool[0].Dial = "localhost:8080"
704 - pool[1].Dial = "localhost:8081"
705 - pool[2].Dial = "localhost:8082"
706 - pool[0].setHealthy(true)
707 - pool[1].setHealthy(false)
708 - pool[2].setHealthy(false)
709 -
710 - // Create a test server that serves HTTPS requests
711 - ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
712 - h := cookieHashPolicy.Select(pool, r, w)
713 - if h != pool[0] {
714 - t.Error("Expected cookieHashPolicy host to be the first only available host.")
715 - }
716 - }))
717 - defer ts.Close()
718 -
719 - // Make a new HTTPS request to the test server
720 - client := ts.Client()
721 - request, err := http.NewRequest(http.MethodGet, ts.URL+"/test", nil)
722 - if err != nil {
723 - t.Fatal(err)
724 - }
725 - response, err := client.Do(request)
726 - if err != nil {
727 - t.Fatal(err)
728 - }
729 -
730 - // Check if the cookie set is Secure and has SameSiteNone mode
731 - cookies := response.Cookies()
732 - if len(cookies) == 0 {
733 - t.Fatal("Expected a cookie to be set")
734 - }
735 - cookie := cookies[0]
736 - if !cookie.Secure {
737 - t.Error("Expected cookie Secure attribute to be true when request is secure")
738 - }
739 - if cookie.SameSite != http.SameSiteNoneMode {
740 - t.Error("Expected cookie SameSite attribute to be None when request is secure")
741 - }
694 + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
695 + defer cancel()
696 + cookieHashPolicy := CookieHashSelection{}
697 + if err := cookieHashPolicy.Provision(ctx); err != nil {
698 + t.Errorf("Provision error: %v", err)
699 + t.FailNow()
700 + }
701 +
702 + pool := testPool()
703 + pool[0].Dial = "localhost:8080"
704 + pool[1].Dial = "localhost:8081"
705 + pool[2].Dial = "localhost:8082"
706 + pool[0].setHealthy(true)
707 + pool[1].setHealthy(false)
708 + pool[2].setHealthy(false)
709 +
710 + // Create a test server that serves HTTPS requests
711 + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
712 + h := cookieHashPolicy.Select(pool, r, w)
713 + if h != pool[0] {
714 + t.Error("Expected cookieHashPolicy host to be the first only available host.")
715 + }
716 + }))
717 + defer ts.Close()
718 +
719 + // Make a new HTTPS request to the test server
720 + client := ts.Client()
721 + request, err := http.NewRequest(http.MethodGet, ts.URL+"/test", nil)
722 + if err != nil {
723 + t.Fatal(err)
724 + }
725 + response, err := client.Do(request)
726 + if err != nil {
727 + t.Fatal(err)
728 + }
729 +
730 + // Check if the cookie set is Secure and has SameSiteNone mode
731 + cookies := response.Cookies()
732 + if len(cookies) == 0 {
733 + t.Fatal("Expected a cookie to be set")
734 + }
735 + cookie := cookies[0]
736 + if !cookie.Secure {
737 + t.Error("Expected cookie Secure attribute to be true when request is secure")
738 + }
739 + if cookie.SameSite != http.SameSiteNoneMode {
740 + t.Error("Expected cookie SameSite attribute to be None when request is secure")
741 + }
742 }
743
744 func TestCookieHashPolicyWithFirstFallback(t *testing.T) {