]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: proxy: do not select a backend if disabled
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Tue, 6 Jan 2026 09:45:40 +0000 (10:45 +0100)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 15 Jan 2026 08:08:18 +0000 (09:08 +0100)
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.

doc/configuration.txt
include/haproxy/backend.h
reg-tests/stream/test_content_switching.vtc
src/stream.c

index 51496b65bb1de54f48679f54661774673c3c1508..1813035385357a53158b274e00a129313ad21f5d 100644 (file)
@@ -7103,6 +7103,9 @@ default_backend <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 <backend> [{if | unless} <condition>]
 
   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,
index bdb457e33560c16b1b04044d3219db2bf4143bc0..00414be2fa756436bc0176d889916acf299dd341 100644 (file)
@@ -85,6 +85,16 @@ static inline int be_usable_srv(struct proxy *be)
                 return be->srv_bck;
 }
 
+/* Returns true if <be> 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)
 {
index d580ed0cc033afadfe10e670ac8e1600f990f07a..c850faedf1522ff2c5772faea6f0a8b51e60e5c9 100644 (file)
@@ -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
index 8941b1e19cc2b48a23f2240db73b7cce2a7d1e22..5bb0c247d12e6c351f3ac60c6cb5a9a8159258d6 100644 (file)
@@ -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))