From: Amaury Denoyelle Date: Tue, 6 Jan 2026 09:45:40 +0000 (+0100) Subject: MEDIUM: proxy: do not select a backend if disabled X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=21fb0a3f58a9835a184a02669134a0adf42b2298;p=thirdparty%2Fhaproxy.git MEDIUM: proxy: do not select a backend if disabled A proxy can be marked as disabled using the keyword with the same name. The doc mentions that it won't process any traffic. However, this is not really the case for backends as they may still be selected via switching rules during stream processing. In fact, currently access to disabled backends will be conducted up to assign_server(). However, no eligible server is found at this stage, resulting in a connection closure or an HTTP 503, which is expected. So in the end, servers in disabled backends won't receive any traffic. But this is only because post-parsing steps are not performed on such backends. Thus, this can be considered as functional but only via side-effects. This patch clarifies the handling of disable backends, so that they are never selected via switching rules. Now, process_switching_rules() will ignore disable backends and continue rules evaluation. As this is a behavior change, this patch is labelled as medium. The documentation manuel for use_backend is updated accordingly. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 51496b65b..181303538 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -7103,6 +7103,9 @@ default_backend used when no rule has matched. It generally is the dynamic backend which will catch all undetermined requests. + If a backend used as default is disabled, no traffic will be redirected to + it. + Example : use_backend dynamic if url_dyn @@ -14752,14 +14755,17 @@ use_backend [{if | unless} ] There may be as many "use_backend" rules as desired. All of these rules are evaluated in their declaration order, and the first one which matches will - assign the backend. + assign the backend. This is even the case if the backend is considered as + down. However, if a matching rule targets a disabled backend, it is ignored + instead and rules evaluation continue. In the first form, the backend will be used if the condition is met. In the second form, the backend will be used if the condition is not met. If no - condition is valid, the backend defined with "default_backend" will be used. - If no default backend is defined, either the servers in the same section are - used (in case of a "listen" section) or, in case of a frontend, no server is - used and a 503 service unavailable response is returned. + condition is valid, the backend defined with "default_backend" will be used + unless it is disabled. If no default backend is defined, either the servers + in the same section are used (in case of a "listen" section) or, in case of a + frontend, no server is used and a 503 service unavailable response is + returned. Note that it is possible to switch from a TCP frontend to an HTTP backend. In this case, either the frontend has already checked that the protocol is HTTP, diff --git a/include/haproxy/backend.h b/include/haproxy/backend.h index bdb457e33..00414be2f 100644 --- a/include/haproxy/backend.h +++ b/include/haproxy/backend.h @@ -85,6 +85,16 @@ static inline int be_usable_srv(struct proxy *be) return be->srv_bck; } +/* Returns true if backend can be used as target to a switching rules. */ +static inline int be_is_eligible(const struct proxy *be) +{ + /* A disabled backend cannot be selected for traffic. Note that STOPPED + * state is ignored as there is a risk of breaking requests during + * soft-stop. + */ + return !(be->flags & PR_FL_DISABLED); +} + /* set the time of last session on the backend */ static inline void be_set_sess_last(struct proxy *be) { diff --git a/reg-tests/stream/test_content_switching.vtc b/reg-tests/stream/test_content_switching.vtc index d580ed0cc..c850faedf 100644 --- a/reg-tests/stream/test_content_switching.vtc +++ b/reg-tests/stream/test_content_switching.vtc @@ -34,11 +34,13 @@ haproxy h1 -conf { frontend fe bind "fd@${fe1S}" use_backend %[req.hdr("x-target")] if { req.hdr("x-dyn") "1" } + use_backend be if { req.hdr("x-target") "be" } frontend fe_default bind "fd@${fe2S}" use_backend %[req.hdr("x-target")] if { req.hdr("x-dyn") "1" } + use_backend be_disabled if { req.hdr("x-target") "be_disabled" } use_backend be default_backend be_default @@ -50,6 +52,10 @@ haproxy h1 -conf { backend be http-request return status 200 hdr "x-be" %[be_name] + backend be_disabled + disabled + http-request return status 200 hdr "x-be" %[be_name] + backend be_default http-request return status 200 hdr "x-be" %[be_name] } -start @@ -80,6 +86,12 @@ client c2 -connect ${h1_fe2S_sock} { rxresp expect resp.status == 200 expect resp.http.x-be == "be_default" + + # Static rule on disabled backend -> continue to next rule + txreq -hdr "x-target: be_disabled" + rxresp + expect resp.status == 200 + expect resp.http.x-be == "be" } -run # Connect to listen proxy type diff --git a/src/stream.c b/src/stream.c index 8941b1e19..5bb0c247d 100644 --- a/src/stream.c +++ b/src/stream.c @@ -1159,6 +1159,12 @@ static int process_switching_rules(struct stream *s, struct channel *req, int an backend = rule->be.backend; } + /* If backend is ineligible, continue rules processing. */ + if (backend && !be_is_eligible(backend)) { + backend = NULL; + continue; + } + /* Break the loop at the first matching rule found. If * the dynamic name resolution has fail, fallback will * be performed on the default backend. @@ -1178,7 +1184,11 @@ static int process_switching_rules(struct stream *s, struct channel *req, int an return 0; } - backend = fe->defbe.be ? fe->defbe.be : s->be; + /* Use default backend if possible or stay on the current proxy. */ + if (fe->defbe.be && be_is_eligible(fe->defbe.be)) + backend = fe->defbe.be; + else + backend = s->be; } if (!stream_set_backend(s, backend))