reverseproxy: validate on weighted_round_robin loadbalancing policy (#7807)
Some checks failed
Tests / test (./cmd/caddy/caddy, ~1.26.0, macos-14, 0, 1.26, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy, ~1.26.0, ubuntu-latest, 0, 1.26, linux) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.26.0, windows-latest, True, 1.26, windows) (push) Has been cancelled
Tests / test (s390x on IBM Z) (push) Has been cancelled
Tests / goreleaser-check (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, aix) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, darwin) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, dragonfly) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, freebsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, illumos) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, linux) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, netbsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, openbsd) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, solaris) (push) Has been cancelled
Cross-Build / build (~1.26.0, 1.26, windows) (push) Has been cancelled
Lint / lint (push) Has been cancelled
Lint / lint-1 (push) Has been cancelled
Lint / lint-2 (push) Has been cancelled
Lint / govulncheck (push) Has been cancelled
Lint / dependency-review (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled

* reverseproxy: validate on weighted_round_robin policy

Validate that weighted_round_robin has a non-zero total weight.
This prevents configurations such as:
    weighted_round_robin 0 0
from being accepted and causing a divide-by-zero panic during request handling.

* test: validation test on zero weight upstreams.

* test: provision called instead of totalweight setting

* reverseproxy: validate on negative upstream weights

* test: regression test on weighted_round_robin selection policy
This commit is contained in:
Rhul 2026-06-07 21:48:20 +05:30 committed by GitHub
parent d3986f824d
commit 55b3397a2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 123 additions and 5 deletions

View file

@ -199,7 +199,7 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
],
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [
{
@ -293,7 +293,7 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
],
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [
{
@ -374,7 +374,7 @@ func TestReverseProxyHealthCheck(t *testing.T) {
http://localhost:9080 {
reverse_proxy {
to localhost:2020
health_uri /health
health_port 2021
health_interval 10ms
@ -495,7 +495,7 @@ func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
http://localhost:9080 {
reverse_proxy {
to unix/%s
health_uri /health
health_port 2021
health_interval 2s
@ -553,7 +553,7 @@ func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
http://localhost:9080 {
reverse_proxy {
to unix/%s
health_uri /health
health_interval 2s
health_timeout 5s
@ -831,3 +831,65 @@ func TestReverseProxySNIPlaceHolder(t *testing.T) {
tester.AssertResponse(req, 200, "example.com")
}
}
func TestWeightedRoundRobinSelectionValidation(t *testing.T) {
configTemplate := `
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [":18080"],
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"load_balancing": {
"selection_policy": {
"policy": "weighted_round_robin",
"weights": %s
}
},
"upstreams": [
{"dial": "localhost:18081"},
{"dial": "localhost:18082"}
]
}
]
}
]
}
}
}
}
}`
tests := []struct {
name string
weights string
errMsg string
}{
{
name: "negative weight",
weights: "[-1, 2]",
errMsg: "weight of an upstream cannot be negative",
},
{
name: "zero total weight",
weights: "[0, 0]",
errMsg: "requires at least one upstream with a positive weight",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
caddytest.AssertLoadError(
t,
fmt.Sprintf(configTemplate, tc.weights),
"json",
tc.errMsg,
)
})
}
}

View file

@ -127,6 +127,19 @@ func (r *WeightedRoundRobinSelection) Provision(ctx caddy.Context) error {
return nil
}
// Validate ensures that r's configuration is valid
func (r *WeightedRoundRobinSelection) Validate() error {
if r.totalWeight <= 0 {
return fmt.Errorf("weighted_round_robin requires at least one upstream with a positive weight")
}
for _, weight := range r.Weights {
if weight < 0 {
return fmt.Errorf("weight of an upstream cannot be negative")
}
}
return nil
}
// Select returns an available host, if any.
func (r *WeightedRoundRobinSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream {
if len(pool) == 0 {
@ -891,6 +904,7 @@ var (
_ Selector = (*CookieHashSelection)(nil)
_ caddy.Validator = (*RandomChoiceSelection)(nil)
_ caddy.Validator = (*WeightedRoundRobinSelection)(nil)
_ caddy.Provisioner = (*RandomChoiceSelection)(nil)
_ caddy.Provisioner = (*WeightedRoundRobinSelection)(nil)

View file

@ -131,6 +131,48 @@ func TestWeightedRoundRobinPolicy(t *testing.T) {
}
}
func TestWeightedRoundRobinSelection_Validate(t *testing.T) {
tests := []struct {
name string
weights []int
wantErr bool
}{
{
name: "Valid 0 2 1 case",
weights: []int{0, 2, 1},
wantErr: false,
},
{
name: "Invalid 0 case (single)",
weights: []int{0},
wantErr: true,
},
{
name: "Invalid 0 0 case (multiple)",
weights: []int{0, 0},
wantErr: true,
},
{
name: "Valid weights",
weights: []int{1, 1, 1},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &WeightedRoundRobinSelection{
Weights: tt.weights,
}
_ = s.Provision(caddy.Context{})
err := s.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestWeightedRoundRobinPolicyWithZeroWeight(t *testing.T) {
pool := testPool()
wrrPolicy := WeightedRoundRobinSelection{