From 2c8ad11b7302d846f1cb201965cd2e5903858cc9 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Tue, 3 Feb 2026 11:09:42 +0100 Subject: [PATCH 01/14] MINOR: cfgparse: validate defaults proxies separately Default proxies validation occurs during post-parsing. The objective is to report any tcp/http-rules which could not behave as expected. Previously, this was performed while looping over standard proxies list, when such proxy is referencing a default instance. This was enough as only named referenced proxies were kept after parsing. However, this is not the case anymore in the context of dynamic backends creation at runtime. As such, this patch now performs validation on every named defaults outside of the standard proxies list loop. This should not cause any behavior difference, as defaults are validated without using the proxy which relies on it. Along with this change, PR_FL_READY proxy flag is now removed. Its usage was only really needed for defaults, to avoid validating a same instance multiple times. With the validation of defaults in their own loop, it is now redundant. --- include/haproxy/proxy-t.h | 2 +- src/cfgparse.c | 58 ++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h index 02359079f5257..4f4b6c45d0834 100644 --- a/include/haproxy/proxy-t.h +++ b/include/haproxy/proxy-t.h @@ -242,7 +242,7 @@ enum PR_SRV_STATE_FILE { /* Proxy flags */ #define PR_FL_DISABLED 0x01 /* The proxy was disabled in the configuration (not at runtime) */ #define PR_FL_STOPPED 0x02 /* The proxy was stopped */ -#define PR_FL_READY 0x04 /* The proxy is ready to be used (initialized and configured) */ +/* 0x04 unused */ #define PR_FL_EXPLICIT_REF 0x08 /* The default proxy is explicitly referenced by another proxy */ #define PR_FL_IMPLICIT_REF 0x10 /* The default proxy is implicitly referenced by another proxy */ #define PR_FL_PAUSED 0x20 /* The proxy was paused at run time (reversible) */ diff --git a/src/cfgparse.c b/src/cfgparse.c index 52a4cb8fe0581..ea2eb32d94d6b 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -2270,7 +2270,7 @@ int parse_cfg(const struct cfgfile *cfg) int check_config_validity() { int cfgerr = 0; - struct proxy *init_proxies_list = NULL; + struct proxy *init_proxies_list = NULL, *defpx; struct stktable *t; struct server *newsrv = NULL; struct mt_list back; @@ -2358,6 +2358,29 @@ int check_config_validity() goto out; } + list_for_each_entry(defpx, &defaults_list, el) { + /* check validity for 'tcp-request' layer 4/5/6/7 rules */ + cfgerr += check_action_rules(&defpx->tcp_req.l4_rules, defpx, &err_code); + cfgerr += check_action_rules(&defpx->tcp_req.l5_rules, defpx, &err_code); + cfgerr += check_action_rules(&defpx->tcp_req.inspect_rules, defpx, &err_code); + cfgerr += check_action_rules(&defpx->tcp_rep.inspect_rules, defpx, &err_code); + cfgerr += check_action_rules(&defpx->http_req_rules, defpx, &err_code); + cfgerr += check_action_rules(&defpx->http_res_rules, defpx, &err_code); + cfgerr += check_action_rules(&defpx->http_after_res_rules, defpx, &err_code); + + err = NULL; + i = smp_resolve_args(defpx, &err); + cfgerr += i; + if (i) { + indent_msg(&err, 8); + ha_alert("%s%s\n", i > 1 ? "multiple argument resolution errors:" : "", err); + ha_free(&err); + } + else { + cfgerr += acl_find_targets(defpx); + } + } + /* starting to initialize the main proxies list */ init_proxies_list = proxies_list; @@ -2403,37 +2426,6 @@ int check_config_validity() continue; } - /* The current proxy is referencing a default proxy. We must - * finalize its config, but only once. If the default proxy is - * ready (PR_FL_READY) it means it was already fully configured. - */ - if (curproxy->defpx) { - if (!(curproxy->defpx->flags & PR_FL_READY)) { - /* check validity for 'tcp-request' layer 4/5/6/7 rules */ - cfgerr += check_action_rules(&curproxy->defpx->tcp_req.l4_rules, curproxy->defpx, &err_code); - cfgerr += check_action_rules(&curproxy->defpx->tcp_req.l5_rules, curproxy->defpx, &err_code); - cfgerr += check_action_rules(&curproxy->defpx->tcp_req.inspect_rules, curproxy->defpx, &err_code); - cfgerr += check_action_rules(&curproxy->defpx->tcp_rep.inspect_rules, curproxy->defpx, &err_code); - cfgerr += check_action_rules(&curproxy->defpx->http_req_rules, curproxy->defpx, &err_code); - cfgerr += check_action_rules(&curproxy->defpx->http_res_rules, curproxy->defpx, &err_code); - cfgerr += check_action_rules(&curproxy->defpx->http_after_res_rules, curproxy->defpx, &err_code); - - err = NULL; - i = smp_resolve_args(curproxy->defpx, &err); - cfgerr += i; - if (i) { - indent_msg(&err, 8); - ha_alert("%s%s\n", i > 1 ? "multiple argument resolution errors:" : "", err); - ha_free(&err); - } - else - cfgerr += acl_find_targets(curproxy->defpx); - - /* default proxy is now ready. Set the right FE/BE capabilities */ - curproxy->defpx->flags |= PR_FL_READY; - } - } - /* check and reduce the bind-proc of each listener */ list_for_each_entry(bind_conf, &curproxy->conf.bind, by_fe) { int mode = conn_pr_mode_to_proto_mode(curproxy->mode); @@ -3860,7 +3852,6 @@ int check_config_validity() if (curproxy->task) { curproxy->task->context = curproxy; curproxy->task->process = manage_proxy; - curproxy->flags |= PR_FL_READY; } else { ha_alert("Proxy '%s': no more memory when trying to allocate the management task\n", @@ -3937,7 +3928,6 @@ int check_config_validity() * Note that ->srv is used by the local peer of a new process to connect to the local peer * of an old process. */ - curpeers->peers_fe->flags |= PR_FL_READY; p = curpeers->remote; while (p) { struct peer *other_peer; From a8bc83bea5f63d224b9b7686d1eb076c7efffbe7 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Fri, 30 Jan 2026 16:31:04 +0100 Subject: [PATCH 02/14] MINOR: cfgparse: move proxy post-init in a dedicated function A lot of proxies initialization code is delayed on post-parsing stage, as it depends on the configuration fully parsed. This is performed via a loop on proxies_list. Extract this code in a dedicated function proxy_finalize(). This patch will be useful for dynamic backends creation. Note that for the moment the code has been extracted as-is. With each new features, some init code was added there. This has become a giant loop with no real ordering. A future patch may provide some cleanup in order to reorganize this. --- include/haproxy/proxy.h | 1 + src/cfgparse.c | 1290 +------------------------------------- src/proxy.c | 1309 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1315 insertions(+), 1285 deletions(-) diff --git a/include/haproxy/proxy.h b/include/haproxy/proxy.h index ed86925d20377..afa6297c3bc6c 100644 --- a/include/haproxy/proxy.h +++ b/include/haproxy/proxy.h @@ -97,6 +97,7 @@ int resolve_stick_rule(struct proxy *curproxy, struct sticking_rule *mrule); void free_stick_rules(struct list *rules); void free_server_rules(struct list *srules); int proxy_init_per_thr(struct proxy *px); +int proxy_finalize(struct proxy *px, int *err_code); /* * This function returns a string containing the type of the proxy in a format diff --git a/src/cfgparse.c b/src/cfgparse.c index ea2eb32d94d6b..39cabdf6bcd1e 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -2269,7 +2269,7 @@ int parse_cfg(const struct cfgfile *cfg) */ int check_config_validity() { - int cfgerr = 0; + int cfgerr = 0, ret; struct proxy *init_proxies_list = NULL, *defpx; struct stktable *t; struct server *newsrv = NULL; @@ -2386,12 +2386,6 @@ int check_config_validity() init_proxies_list_stage1: for (curproxy = init_proxies_list; curproxy; curproxy = curproxy->next) { - struct switching_rule *rule; - struct server_rule *srule; - struct sticking_rule *mrule; - struct logger *tmplogger; - unsigned int next_id; - proxy_init_per_thr(curproxy); /* Assign automatic UUID if unset except for internal proxies. @@ -2426,1284 +2420,11 @@ int check_config_validity() continue; } - /* check and reduce the bind-proc of each listener */ - list_for_each_entry(bind_conf, &curproxy->conf.bind, by_fe) { - int mode = conn_pr_mode_to_proto_mode(curproxy->mode); - const struct mux_proto_list *mux_ent; - int ret; - - - /* Check the mux protocols, if any; before the check the ALPN */ - if (bind_conf->xprt && bind_conf->xprt == xprt_get(XPRT_QUIC)) { - if (!bind_conf->mux_proto) { - /* No protocol was specified. If we're using QUIC at the transport - * layer, we'll instantiate it as a mux as well. If QUIC is not - * compiled in, this will remain NULL. - */ - bind_conf->mux_proto = get_mux_proto(ist("quic")); - } - if (bind_conf->options & BC_O_ACC_PROXY) { - ha_alert("Binding [%s:%d] for %s %s: QUIC protocol does not support PROXY protocol yet." - " 'accept-proxy' option cannot be used with a QUIC listener.\n", - bind_conf->file, bind_conf->line, - proxy_type_str(curproxy), curproxy->id); - cfgerr++; - } - } - - if (bind_conf->mux_proto) { - /* it is possible that an incorrect mux was referenced - * due to the proxy's mode not being taken into account - * on first pass. Let's adjust it now. - */ - mux_ent = conn_get_best_mux_entry(bind_conf->mux_proto->token, PROTO_SIDE_FE, mode); - - if (!mux_ent || !isteq(mux_ent->token, bind_conf->mux_proto->token)) { - ha_alert("%s '%s' : MUX protocol '%.*s' is not usable for 'bind %s' at [%s:%d].\n", - proxy_type_str(curproxy), curproxy->id, - (int)bind_conf->mux_proto->token.len, - bind_conf->mux_proto->token.ptr, - bind_conf->arg, bind_conf->file, bind_conf->line); - cfgerr++; - } else { - if ((mux_ent->mux->flags & MX_FL_FRAMED) && !(bind_conf->options & BC_O_USE_SOCK_DGRAM)) { - ha_alert("%s '%s' : frame-based MUX protocol '%.*s' is incompatible with stream transport of 'bind %s' at [%s:%d].\n", - proxy_type_str(curproxy), curproxy->id, - (int)bind_conf->mux_proto->token.len, - bind_conf->mux_proto->token.ptr, - bind_conf->arg, bind_conf->file, bind_conf->line); - cfgerr++; - } - else if (!(mux_ent->mux->flags & MX_FL_FRAMED) && !(bind_conf->options & BC_O_USE_SOCK_STREAM)) { - ha_alert("%s '%s' : stream-based MUX protocol '%.*s' is incompatible with framed transport of 'bind %s' at [%s:%d].\n", - proxy_type_str(curproxy), curproxy->id, - (int)bind_conf->mux_proto->token.len, - bind_conf->mux_proto->token.ptr, - bind_conf->arg, bind_conf->file, bind_conf->line); - cfgerr++; - } - } - - /* update the mux */ - bind_conf->mux_proto = mux_ent; - } - - - /* HTTP frontends with "h2" as ALPN/NPN will work in - * HTTP/2 and absolutely require buffers 16kB or larger. - */ -#ifdef USE_OPENSSL - /* no-alpn ? If so, it's the right moment to remove it */ - if (bind_conf->ssl_conf.alpn_str && !bind_conf->ssl_conf.alpn_len) { - ha_free(&bind_conf->ssl_conf.alpn_str); - } -#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation - else if (!bind_conf->ssl_conf.alpn_str && !bind_conf->ssl_conf.npn_str && - ((bind_conf->options & BC_O_USE_SSL) || bind_conf->xprt == xprt_get(XPRT_QUIC)) && - curproxy->mode == PR_MODE_HTTP && global.tune.bufsize >= 16384) { - - /* Neither ALPN nor NPN were explicitly set nor disabled, we're - * in HTTP mode with an SSL or QUIC listener, we can enable ALPN. - * Note that it's in binary form. First we try to set the ALPN from - * mux proto if set. Otherwise rely on the default ALPN. - */ - if (bind_conf->mux_proto && bind_conf->mux_proto->alpn) - bind_conf->ssl_conf.alpn_str = strdup(bind_conf->mux_proto->alpn); - else if (bind_conf->xprt == xprt_get(XPRT_QUIC)) - bind_conf->ssl_conf.alpn_str = strdup("\002h3"); - else - bind_conf->ssl_conf.alpn_str = strdup("\002h2\010http/1.1"); - - if (!bind_conf->ssl_conf.alpn_str) { - ha_alert("Proxy '%s': out of memory while trying to allocate a default alpn string in 'bind %s' at [%s:%d].\n", - curproxy->id, bind_conf->arg, bind_conf->file, bind_conf->line); - cfgerr++; - err_code |= ERR_FATAL | ERR_ALERT; - goto out; - } - bind_conf->ssl_conf.alpn_len = strlen(bind_conf->ssl_conf.alpn_str); - } -#endif - - if (curproxy->mode == PR_MODE_HTTP && global.tune.bufsize < 16384) { -#ifdef OPENSSL_NPN_NEGOTIATED - /* check NPN */ - if (bind_conf->ssl_conf.npn_str && strstr(bind_conf->ssl_conf.npn_str, "\002h2")) { - ha_alert("HTTP frontend '%s' enables HTTP/2 via NPN at [%s:%d], so global.tune.bufsize must be at least 16384 bytes (%d now).\n", - curproxy->id, bind_conf->file, bind_conf->line, global.tune.bufsize); - cfgerr++; - } -#endif -#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation - /* check ALPN */ - if (bind_conf->ssl_conf.alpn_str && strstr(bind_conf->ssl_conf.alpn_str, "\002h2")) { - ha_alert("HTTP frontend '%s' enables HTTP/2 via ALPN at [%s:%d], so global.tune.bufsize must be at least 16384 bytes (%d now).\n", - curproxy->id, bind_conf->file, bind_conf->line, global.tune.bufsize); - cfgerr++; - } -#endif - } /* HTTP && bufsize < 16384 */ -#endif - -#ifdef USE_QUIC - if (bind_conf->xprt == xprt_get(XPRT_QUIC)) { - const struct quic_cc_algo *cc_algo = bind_conf->quic_cc_algo ? - bind_conf->quic_cc_algo : default_quic_cc_algo; - - if (!(cc_algo->flags & QUIC_CC_ALGO_FL_OPT_PACING) && - !(quic_tune.fe.fb_opts & QUIC_TUNE_FB_TX_PACING)) { - ha_warning("Binding [%s:%d] for %s %s: using the selected congestion algorithm without pacing may cause slowdowns or high loss rates during transfers.\n", - bind_conf->file, bind_conf->line, - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - } -#endif /* USE_QUIC */ - - /* finish the bind setup */ - ret = bind_complete_thread_setup(bind_conf, &err_code); - if (ret != 0) { - cfgerr += ret; - if (err_code & ERR_FATAL) - goto out; - } - - if (bind_generate_guid(bind_conf)) { - cfgerr++; - err_code |= ERR_FATAL | ERR_ALERT; - goto out; - } - } - - switch (curproxy->mode) { - case PR_MODE_TCP: - cfgerr += proxy_cfg_ensure_no_http(curproxy); - cfgerr += proxy_cfg_ensure_no_log(curproxy); - break; - - case PR_MODE_HTTP: - cfgerr += proxy_cfg_ensure_no_log(curproxy); - curproxy->http_needed = 1; - break; - - case PR_MODE_CLI: - cfgerr += proxy_cfg_ensure_no_http(curproxy); - cfgerr += proxy_cfg_ensure_no_log(curproxy); - break; - - case PR_MODE_SYSLOG: - /* this mode is initialized as the classic tcp proxy */ - cfgerr += proxy_cfg_ensure_no_http(curproxy); - break; - - case PR_MODE_SPOP: - cfgerr += proxy_cfg_ensure_no_http(curproxy); - cfgerr += proxy_cfg_ensure_no_log(curproxy); - break; - - case PR_MODE_PEERS: - case PR_MODES: - /* should not happen, bug gcc warn missing switch statement */ - ha_alert("%s '%s' cannot initialize this proxy mode (peers) in this way. NOTE: PLEASE REPORT THIS TO DEVELOPERS AS YOU'RE NOT SUPPOSED TO BE ABLE TO CREATE A CONFIGURATION TRIGGERING THIS!\n", - proxy_type_str(curproxy), curproxy->id); - cfgerr++; - break; - } - - if (!(curproxy->cap & PR_CAP_INT) && (curproxy->cap & PR_CAP_FE) && LIST_ISEMPTY(&curproxy->conf.listeners)) { - ha_warning("%s '%s' has no 'bind' directive. Please declare it as a backend if this was intended.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - if (curproxy->cap & PR_CAP_BE) { - if (curproxy->lbprm.algo & BE_LB_KIND) { - if (curproxy->options & PR_O_TRANSP) { - ha_alert("%s '%s' cannot use both transparent and balance mode.\n", - proxy_type_str(curproxy), curproxy->id); - cfgerr++; - } - else if (curproxy->options & PR_O_DISPATCH) { - ha_warning("dispatch address of %s '%s' will be ignored in balance mode.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - } - else if (!(curproxy->options & (PR_O_TRANSP | PR_O_DISPATCH))) { - /* If no LB algo is set in a backend, and we're not in - * transparent mode, dispatch mode nor proxy mode, we - * want to use balance random by default. - */ - curproxy->lbprm.algo &= ~BE_LB_ALGO; - curproxy->lbprm.algo |= BE_LB_ALGO_RND; - } - } - - if (curproxy->options & PR_O_DISPATCH) - curproxy->options &= ~PR_O_TRANSP; - else if (curproxy->options & PR_O_TRANSP) - curproxy->options &= ~PR_O_DISPATCH; - - if ((curproxy->tcpcheck_rules.flags & TCPCHK_RULES_UNUSED_HTTP_RS)) { - ha_warning("%s '%s' uses http-check rules without 'option httpchk', so the rules are ignored.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - if ((curproxy->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK && - (curproxy->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_HTTP_CHK) { - if (curproxy->options & PR_O_DISABLE404) { - ha_warning("'%s' will be ignored for %s '%s' (requires 'option httpchk').\n", - "disable-on-404", proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - curproxy->options &= ~PR_O_DISABLE404; - } - if (curproxy->options2 & PR_O2_CHK_SNDST) { - ha_warning("'%s' will be ignored for %s '%s' (requires 'option httpchk').\n", - "send-state", proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - curproxy->options2 &= ~PR_O2_CHK_SNDST; - } - } - - if ((curproxy->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) { - if (!global.external_check) { - ha_alert("Proxy '%s' : '%s' unable to find required 'global.external-check'.\n", - curproxy->id, "option external-check"); - cfgerr++; - } - if (!curproxy->check_command) { - ha_alert("Proxy '%s' : '%s' unable to find required 'external-check command'.\n", - curproxy->id, "option external-check"); - cfgerr++; - } - if (!(global.tune.options & GTUNE_INSECURE_FORK)) { - ha_warning("Proxy '%s' : 'insecure-fork-wanted' not enabled in the global section, '%s' will likely fail.\n", - curproxy->id, "option external-check"); - err_code |= ERR_WARN; - } - } - - if (curproxy->email_alert.flags & PR_EMAIL_ALERT_SET) { - if (!(curproxy->email_alert.mailers.name && curproxy->email_alert.from && curproxy->email_alert.to)) { - ha_warning("'email-alert' will be ignored for %s '%s' (the presence any of " - "'email-alert from', 'email-alert level' 'email-alert mailers', " - "'email-alert myhostname', or 'email-alert to' " - "requires each of 'email-alert from', 'email-alert mailers' and 'email-alert to' " - "to be present).\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - free_email_alert(curproxy); - } - if (!curproxy->email_alert.myhostname) - curproxy->email_alert.myhostname = strdup(hostname); - } - - if (curproxy->check_command) { - int clear = 0; - if ((curproxy->options2 & PR_O2_CHK_ANY) != PR_O2_EXT_CHK) { - ha_warning("'%s' will be ignored for %s '%s' (requires 'option external-check').\n", - "external-check command", proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - clear = 1; - } - if (curproxy->check_command[0] != '/' && !curproxy->check_path) { - ha_alert("Proxy '%s': '%s' does not have a leading '/' and 'external-check path' is not set.\n", - curproxy->id, "external-check command"); - cfgerr++; - } - if (clear) { - ha_free(&curproxy->check_command); - } - } - - if (curproxy->check_path) { - if ((curproxy->options2 & PR_O2_CHK_ANY) != PR_O2_EXT_CHK) { - ha_warning("'%s' will be ignored for %s '%s' (requires 'option external-check').\n", - "external-check path", proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - ha_free(&curproxy->check_path); - } - } - - /* if a default backend was specified, let's find it */ - if (curproxy->defbe.name) { - struct proxy *target; - - target = proxy_be_by_name(curproxy->defbe.name); - if (!target) { - ha_alert("Proxy '%s': unable to find required default_backend: '%s'.\n", - curproxy->id, curproxy->defbe.name); - cfgerr++; - } else if (target == curproxy) { - ha_alert("Proxy '%s': loop detected for default_backend: '%s'.\n", - curproxy->id, curproxy->defbe.name); - cfgerr++; - } else if (target->mode != curproxy->mode && - !(curproxy->mode == PR_MODE_TCP && target->mode == PR_MODE_HTTP)) { - - ha_alert("%s %s '%s' (%s:%d) tries to use incompatible %s %s '%s' (%s:%d) as its default backend (see 'mode').\n", - proxy_mode_str(curproxy->mode), proxy_type_str(curproxy), curproxy->id, - curproxy->conf.file, curproxy->conf.line, - proxy_mode_str(target->mode), proxy_type_str(target), target->id, - target->conf.file, target->conf.line); - cfgerr++; - } else { - free(curproxy->defbe.name); - curproxy->defbe.be = target; - /* Emit a warning if this proxy also has some servers */ - if (curproxy->srv) { - ha_warning("In proxy '%s', the 'default_backend' rule always has precedence over the servers, which will never be used.\n", - curproxy->id); - err_code |= ERR_WARN; - } - if (target->mode == PR_MODE_HTTP) { - /* at least one of the used backends will provoke an - * HTTP upgrade - */ - curproxy->options |= PR_O_HTTP_UPG; - } - } - } - - /* find the target proxy for 'use_backend' rules */ - list_for_each_entry(rule, &curproxy->switching_rules, list) { - struct proxy *target; - struct logformat_node *node; - char *pxname; - - /* Try to parse the string as a log format expression. If the result - * of the parsing is only one entry containing a simple string, then - * it's a standard string corresponding to a static rule, thus the - * parsing is cancelled and be.name is restored to be resolved. - */ - pxname = rule->be.name; - lf_expr_init(&rule->be.expr); - curproxy->conf.args.ctx = ARGC_UBK; - curproxy->conf.args.file = rule->file; - curproxy->conf.args.line = rule->line; - err = NULL; - if (!parse_logformat_string(pxname, curproxy, &rule->be.expr, 0, SMP_VAL_FE_HRQ_HDR, &err)) { - ha_alert("Parsing [%s:%d]: failed to parse use_backend rule '%s' : %s.\n", - rule->file, rule->line, pxname, err); - free(err); - cfgerr++; - continue; - } - node = LIST_NEXT(&rule->be.expr.nodes.list, struct logformat_node *, list); - - if (!lf_expr_isempty(&rule->be.expr)) { - if (node->type != LOG_FMT_TEXT || node->list.n != &rule->be.expr.nodes.list) { - rule->dynamic = 1; - free(pxname); - /* backend is not yet known so we cannot assume its type, - * thus we should consider that at least one of the used - * backends may provoke HTTP upgrade - */ - curproxy->options |= PR_O_HTTP_UPG; - continue; - } - /* Only one element in the list, a simple string: free the expression and - * fall back to static rule - */ - lf_expr_deinit(&rule->be.expr); - } - - rule->dynamic = 0; - rule->be.name = pxname; - - target = proxy_be_by_name(rule->be.name); - if (!target) { - ha_alert("Proxy '%s': unable to find required use_backend: '%s'.\n", - curproxy->id, rule->be.name); - cfgerr++; - } else if (target == curproxy) { - ha_alert("Proxy '%s': loop detected for use_backend: '%s'.\n", - curproxy->id, rule->be.name); - cfgerr++; - } else if (target->mode != curproxy->mode && - !(curproxy->mode == PR_MODE_TCP && target->mode == PR_MODE_HTTP)) { - - ha_alert("%s %s '%s' (%s:%d) tries to use incompatible %s %s '%s' (%s:%d) in a 'use_backend' rule (see 'mode').\n", - proxy_mode_str(curproxy->mode), proxy_type_str(curproxy), curproxy->id, - curproxy->conf.file, curproxy->conf.line, - proxy_mode_str(target->mode), proxy_type_str(target), target->id, - target->conf.file, target->conf.line); - cfgerr++; - } else { - ha_free(&rule->be.name); - rule->be.backend = target; - if (target->mode == PR_MODE_HTTP) { - /* at least one of the used backends will provoke an - * HTTP upgrade - */ - curproxy->options |= PR_O_HTTP_UPG; - } - } - err_code |= warnif_tcp_http_cond(curproxy, rule->cond); - } - - /* find the target server for 'use_server' rules */ - list_for_each_entry(srule, &curproxy->server_rules, list) { - struct server *target; - struct logformat_node *node; - char *server_name; - - /* We try to parse the string as a log format expression. If the result of the parsing - * is only one entry containing a single string, then it's a standard string corresponding - * to a static rule, thus the parsing is cancelled and we fall back to setting srv.ptr. - */ - server_name = srule->srv.name; - lf_expr_init(&srule->expr); - curproxy->conf.args.ctx = ARGC_USRV; - err = NULL; - if (!parse_logformat_string(server_name, curproxy, &srule->expr, 0, SMP_VAL_FE_HRQ_HDR, &err)) { - ha_alert("Parsing [%s:%d]; use-server rule failed to parse log-format '%s' : %s.\n", - srule->file, srule->line, server_name, err); - free(err); - cfgerr++; - continue; - } - node = LIST_NEXT(&srule->expr.nodes.list, struct logformat_node *, list); - - if (!lf_expr_isempty(&srule->expr)) { - if (node->type != LOG_FMT_TEXT || node->list.n != &srule->expr.nodes.list) { - srule->dynamic = 1; - free(server_name); - continue; - } - /* Only one element in the list, a simple string: free the expression and - * fall back to static rule - */ - lf_expr_deinit(&srule->expr); - } - - srule->dynamic = 0; - srule->srv.name = server_name; - target = server_find_by_name(curproxy, srule->srv.name); - err_code |= warnif_tcp_http_cond(curproxy, srule->cond); - - if (!target) { - ha_alert("%s '%s' : unable to find server '%s' referenced in a 'use-server' rule.\n", - proxy_type_str(curproxy), curproxy->id, srule->srv.name); - cfgerr++; - continue; - } - ha_free(&srule->srv.name); - srule->srv.ptr = target; - target->flags |= SRV_F_NON_PURGEABLE; - } - - /* find the target table for 'stick' rules */ - list_for_each_entry(mrule, &curproxy->sticking_rules, list) { - curproxy->be_req_ana |= AN_REQ_STICKING_RULES; - if (mrule->flags & STK_IS_STORE) - curproxy->be_rsp_ana |= AN_RES_STORE_RULES; - - if (!resolve_stick_rule(curproxy, mrule)) - cfgerr++; - - err_code |= warnif_tcp_http_cond(curproxy, mrule->cond); - } - - /* find the target table for 'store response' rules */ - list_for_each_entry(mrule, &curproxy->storersp_rules, list) { - curproxy->be_rsp_ana |= AN_RES_STORE_RULES; - - if (!resolve_stick_rule(curproxy, mrule)) - cfgerr++; - } - - /* check validity for 'tcp-request' layer 4/5/6/7 rules */ - cfgerr += check_action_rules(&curproxy->tcp_req.l4_rules, curproxy, &err_code); - cfgerr += check_action_rules(&curproxy->tcp_req.l5_rules, curproxy, &err_code); - cfgerr += check_action_rules(&curproxy->tcp_req.inspect_rules, curproxy, &err_code); - cfgerr += check_action_rules(&curproxy->tcp_rep.inspect_rules, curproxy, &err_code); - cfgerr += check_action_rules(&curproxy->http_req_rules, curproxy, &err_code); - cfgerr += check_action_rules(&curproxy->http_res_rules, curproxy, &err_code); - cfgerr += check_action_rules(&curproxy->http_after_res_rules, curproxy, &err_code); - - /* Warn is a switch-mode http is used on a TCP listener with servers but no backend */ - if (!curproxy->defbe.name && LIST_ISEMPTY(&curproxy->switching_rules) && curproxy->srv) { - if ((curproxy->options & PR_O_HTTP_UPG) && curproxy->mode == PR_MODE_TCP) - ha_warning("Proxy '%s' : 'switch-mode http' configured for a %s %s with no backend. " - "Incoming connections upgraded to HTTP cannot be routed to TCP servers\n", - curproxy->id, proxy_mode_str(curproxy->mode), proxy_type_str(curproxy)); - } - - if (curproxy->table && curproxy->table->peers.name) { - struct peers *curpeers; - - for (curpeers = cfg_peers; curpeers; curpeers = curpeers->next) { - if (strcmp(curpeers->id, curproxy->table->peers.name) == 0) { - ha_free(&curproxy->table->peers.name); - curproxy->table->peers.p = curpeers; - break; - } - } - - if (!curpeers) { - ha_alert("Proxy '%s': unable to find sync peers '%s'.\n", - curproxy->id, curproxy->table->peers.name); - ha_free(&curproxy->table->peers.name); - curproxy->table->peers.p = NULL; - cfgerr++; - } - else if (curpeers->disabled) { - /* silently disable this peers section */ - curproxy->table->peers.p = NULL; - } - else if (!curpeers->peers_fe) { - ha_alert("Proxy '%s': unable to find local peer '%s' in peers section '%s'.\n", - curproxy->id, localpeer, curpeers->id); - curproxy->table->peers.p = NULL; - cfgerr++; - } - } - - - if (curproxy->email_alert.mailers.name) { - struct mailers *curmailers = mailers; - - for (curmailers = mailers; curmailers; curmailers = curmailers->next) { - if (strcmp(curmailers->id, curproxy->email_alert.mailers.name) == 0) - break; - } - if (!curmailers) { - ha_alert("Proxy '%s': unable to find mailers '%s'.\n", - curproxy->id, curproxy->email_alert.mailers.name); - free_email_alert(curproxy); - cfgerr++; - } - else { - err = NULL; - if (init_email_alert(curmailers, curproxy, &err)) { - ha_alert("Proxy '%s': %s.\n", curproxy->id, err); - free(err); - cfgerr++; - } - } - } - - if (curproxy->uri_auth && !(curproxy->uri_auth->flags & STAT_F_CONVDONE) && - !LIST_ISEMPTY(&curproxy->uri_auth->http_req_rules) && - (curproxy->uri_auth->userlist || curproxy->uri_auth->auth_realm )) { - ha_alert("%s '%s': stats 'auth'/'realm' and 'http-request' can't be used at the same time.\n", - "proxy", curproxy->id); - cfgerr++; - goto out_uri_auth_compat; - } - - if (curproxy->uri_auth && curproxy->uri_auth->userlist && - (!(curproxy->uri_auth->flags & STAT_F_CONVDONE) || - LIST_ISEMPTY(&curproxy->uri_auth->http_req_rules))) { - const char *uri_auth_compat_req[10]; - struct act_rule *rule; - i = 0; - - /* build the ACL condition from scratch. We're relying on anonymous ACLs for that */ - uri_auth_compat_req[i++] = "auth"; - - if (curproxy->uri_auth->auth_realm) { - uri_auth_compat_req[i++] = "realm"; - uri_auth_compat_req[i++] = curproxy->uri_auth->auth_realm; - } - - uri_auth_compat_req[i++] = "unless"; - uri_auth_compat_req[i++] = "{"; - uri_auth_compat_req[i++] = "http_auth(.internal-stats-userlist)"; - uri_auth_compat_req[i++] = "}"; - uri_auth_compat_req[i++] = ""; - - rule = parse_http_req_cond(uri_auth_compat_req, "internal-stats-auth-compat", 0, curproxy); - if (!rule) { - cfgerr++; - break; - } - - LIST_APPEND(&curproxy->uri_auth->http_req_rules, &rule->list); - - if (curproxy->uri_auth->auth_realm) { - ha_free(&curproxy->uri_auth->auth_realm); - } - curproxy->uri_auth->flags |= STAT_F_CONVDONE; - } -out_uri_auth_compat: - - /* check whether we have a logger that uses RFC5424 log format */ - list_for_each_entry(tmplogger, &curproxy->loggers, list) { - if (tmplogger->format == LOG_FORMAT_RFC5424) { - if (!curproxy->logformat_sd.str) { - /* set the default logformat_sd_string */ - curproxy->logformat_sd.str = default_rfc5424_sd_log_format; - } - break; - } - } - - /* compile the log format */ - if (!(curproxy->cap & PR_CAP_FE)) { - lf_expr_deinit(&curproxy->logformat); - lf_expr_deinit(&curproxy->logformat_sd); - } - - if (curproxy->logformat.str) { - curproxy->conf.args.ctx = ARGC_LOG; - curproxy->conf.args.file = curproxy->logformat.conf.file; - curproxy->conf.args.line = curproxy->logformat.conf.line; - err = NULL; - if (!lf_expr_compile(&curproxy->logformat, &curproxy->conf.args, - LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES, - SMP_VAL_FE_LOG_END, &err) || - !lf_expr_postcheck(&curproxy->logformat, curproxy, &err)) { - ha_alert("Parsing [%s:%d]: failed to parse log-format : %s.\n", - curproxy->logformat.conf.file, curproxy->logformat.conf.line, err); - free(err); - cfgerr++; - } - curproxy->conf.args.file = NULL; - curproxy->conf.args.line = 0; - } - - if (curproxy->logformat_sd.str) { - curproxy->conf.args.ctx = ARGC_LOGSD; - curproxy->conf.args.file = curproxy->logformat_sd.conf.file; - curproxy->conf.args.line = curproxy->logformat_sd.conf.line; - err = NULL; - if (!lf_expr_compile(&curproxy->logformat_sd, &curproxy->conf.args, - LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES, - SMP_VAL_FE_LOG_END, &err) || - !add_to_logformat_list(NULL, NULL, LF_SEPARATOR, &curproxy->logformat_sd, &err) || - !lf_expr_postcheck(&curproxy->logformat_sd, curproxy, &err)) { - ha_alert("Parsing [%s:%d]: failed to parse log-format-sd : %s.\n", - curproxy->logformat_sd.conf.file, curproxy->logformat_sd.conf.line, err); - free(err); - cfgerr++; - } - curproxy->conf.args.file = NULL; - curproxy->conf.args.line = 0; - } - - if (curproxy->format_unique_id.str) { - int where = 0; - - curproxy->conf.args.ctx = ARGC_UIF; - curproxy->conf.args.file = curproxy->format_unique_id.conf.file; - curproxy->conf.args.line = curproxy->format_unique_id.conf.line; - err = NULL; - if (curproxy->cap & PR_CAP_FE) - where |= SMP_VAL_FE_HRQ_HDR; - if (curproxy->cap & PR_CAP_BE) - where |= SMP_VAL_BE_HRQ_HDR; - if (!lf_expr_compile(&curproxy->format_unique_id, &curproxy->conf.args, - LOG_OPT_HTTP|LOG_OPT_MERGE_SPACES, where, &err) || - !lf_expr_postcheck(&curproxy->format_unique_id, curproxy, &err)) { - ha_alert("Parsing [%s:%d]: failed to parse unique-id : %s.\n", - curproxy->format_unique_id.conf.file, curproxy->format_unique_id.conf.line, err); - free(err); - cfgerr++; - } - curproxy->conf.args.file = NULL; - curproxy->conf.args.line = 0; - } - - if (curproxy->logformat_error.str) { - curproxy->conf.args.ctx = ARGC_LOG; - curproxy->conf.args.file = curproxy->logformat_error.conf.file; - curproxy->conf.args.line = curproxy->logformat_error.conf.line; - err = NULL; - if (!lf_expr_compile(&curproxy->logformat_error, &curproxy->conf.args, - LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES, - SMP_VAL_FE_LOG_END, &err) || - !lf_expr_postcheck(&curproxy->logformat_error, curproxy, &err)) { - ha_alert("Parsing [%s:%d]: failed to parse error-log-format : %s.\n", - curproxy->logformat_error.conf.file, curproxy->logformat_error.conf.line, err); - free(err); - cfgerr++; - } - curproxy->conf.args.file = NULL; - curproxy->conf.args.line = 0; - } - - /* "balance hash" needs to compile its expression - * (log backends will handle this in proxy log postcheck) - */ - if (curproxy->mode != PR_MODE_SYSLOG && - (curproxy->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_SMP) { - int idx = 0; - const char *args[] = { - curproxy->lbprm.arg_str, - NULL, - }; - - err = NULL; - curproxy->conf.args.ctx = ARGC_USRV; // same context as use_server. - curproxy->lbprm.expr = - sample_parse_expr((char **)args, &idx, - curproxy->conf.file, curproxy->conf.line, - &err, &curproxy->conf.args, NULL); - - if (!curproxy->lbprm.expr) { - ha_alert("%s '%s' [%s:%d]: failed to parse 'balance hash' expression '%s' in : %s.\n", - proxy_type_str(curproxy), curproxy->id, - curproxy->conf.file, curproxy->conf.line, - curproxy->lbprm.arg_str, err); - ha_free(&err); - cfgerr++; - } - else if (!(curproxy->lbprm.expr->fetch->val & SMP_VAL_BE_SET_SRV)) { - ha_alert("%s '%s' [%s:%d]: error detected while parsing 'balance hash' expression '%s' " - "which requires information from %s, which is not available here.\n", - proxy_type_str(curproxy), curproxy->id, - curproxy->conf.file, curproxy->conf.line, - curproxy->lbprm.arg_str, sample_src_names(curproxy->lbprm.expr->fetch->use)); - cfgerr++; - } - else if (curproxy->mode == PR_MODE_HTTP && (curproxy->lbprm.expr->fetch->use & SMP_USE_L6REQ)) { - ha_warning("%s '%s' [%s:%d]: L6 sample fetch <%s> will be ignored in 'balance hash' expression in HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id, - curproxy->conf.file, curproxy->conf.line, - curproxy->lbprm.arg_str); - } - else - curproxy->http_needed |= !!(curproxy->lbprm.expr->fetch->use & SMP_USE_HTTP_ANY); - } - - /* only now we can check if some args remain unresolved. - * This must be done after the users and groups resolution. - */ - err = NULL; - i = smp_resolve_args(curproxy, &err); - cfgerr += i; - if (i) { - indent_msg(&err, 8); - ha_alert("%s%s\n", i > 1 ? "multiple argument resolution errors:" : "", err); - ha_free(&err); - } else - cfgerr += acl_find_targets(curproxy); - - if (!(curproxy->cap & PR_CAP_INT) && (curproxy->mode == PR_MODE_TCP || curproxy->mode == PR_MODE_HTTP) && - (((curproxy->cap & PR_CAP_FE) && !curproxy->timeout.client) || - ((curproxy->cap & PR_CAP_BE) && (curproxy->srv) && - (!curproxy->timeout.connect || - (!curproxy->timeout.server && (curproxy->mode == PR_MODE_HTTP || !curproxy->timeout.tunnel)))))) { - ha_warning("missing timeouts for %s '%s'.\n" - " | While not properly invalid, you will certainly encounter various problems\n" - " | with such a configuration. To fix this, please ensure that all following\n" - " | timeouts are set to a non-zero value: 'client', 'connect', 'server'.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - /* Historically, the tarpit and queue timeouts were inherited from contimeout. - * We must still support older configurations, so let's find out whether those - * parameters have been set or must be copied from contimeouts. - */ - if (!curproxy->timeout.tarpit) - curproxy->timeout.tarpit = curproxy->timeout.connect; - if ((curproxy->cap & PR_CAP_BE) && !curproxy->timeout.queue) - curproxy->timeout.queue = curproxy->timeout.connect; - - if ((curproxy->tcpcheck_rules.flags & TCPCHK_RULES_UNUSED_TCP_RS)) { - ha_warning("%s '%s' uses tcp-check rules without 'option tcp-check', so the rules are ignored.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - /* ensure that cookie capture length is not too large */ - if (curproxy->capture_len >= global.tune.cookie_len) { - ha_warning("truncating capture length to %d bytes for %s '%s'.\n", - global.tune.cookie_len - 1, proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - curproxy->capture_len = global.tune.cookie_len - 1; - } - - /* The small pools required for the capture lists */ - if (curproxy->nb_req_cap) { - curproxy->req_cap_pool = create_pool("ptrcap", - curproxy->nb_req_cap * sizeof(char *), - MEM_F_SHARED); - } - - if (curproxy->nb_rsp_cap) { - curproxy->rsp_cap_pool = create_pool("ptrcap", - curproxy->nb_rsp_cap * sizeof(char *), - MEM_F_SHARED); - } - - switch (curproxy->load_server_state_from_file) { - case PR_SRV_STATE_FILE_UNSPEC: - curproxy->load_server_state_from_file = PR_SRV_STATE_FILE_NONE; - break; - case PR_SRV_STATE_FILE_GLOBAL: - if (!global.server_state_file) { - ha_warning("backend '%s' configured to load server state file from global section 'server-state-file' directive. Unfortunately, 'server-state-file' is not set!\n", - curproxy->id); - err_code |= ERR_WARN; - } - break; - } - - /* first, we will invert the servers list order */ - newsrv = NULL; - while (curproxy->srv) { - struct server *next; - - next = curproxy->srv->next; - curproxy->srv->next = newsrv; - newsrv = curproxy->srv; - if (!next) - break; - curproxy->srv = next; - } - - /* Check that no server name conflicts. This causes trouble in the stats. - * We only emit an error for the first conflict affecting each server, - * in order to avoid combinatory explosion if all servers have the same - * name. Since servers names are stored in a tree before landing here, - * we simply have to check for the current server's duplicates to spot - * conflicts. - */ - for (newsrv = curproxy->srv; newsrv; newsrv = newsrv->next) { - struct server *other_srv; - - /* Note: internal servers are not always registered and - * they do not conflict. - */ - if (!ceb_intree(&newsrv->conf.name_node)) - continue; - - for (other_srv = newsrv; - (other_srv = cebis_item_prev_dup(&curproxy->conf.used_server_name, conf.name_node, id, other_srv)); ) { - ha_alert("parsing [%s:%d] : %s '%s', another server named '%s' was already defined at line %d, please use distinct names.\n", - newsrv->conf.file, newsrv->conf.line, - proxy_type_str(curproxy), curproxy->id, - newsrv->id, other_srv->conf.line); - cfgerr++; - break; - } - } - - /* assign automatic UIDs to servers which don't have one yet */ - next_id = 1; - newsrv = curproxy->srv; - while (newsrv != NULL) { - if (!newsrv->puid) { - /* server ID not set, use automatic numbering with first - * spare entry starting with next_svid. - */ - next_id = server_get_next_id(curproxy, next_id); - newsrv->puid = next_id; - server_index_id(curproxy, newsrv); - } - - next_id++; - newsrv = newsrv->next; - } - - curproxy->lbprm.wmult = 1; /* default weight multiplier */ - curproxy->lbprm.wdiv = 1; /* default weight divider */ - - /* - * If this server supports a maxconn parameter, it needs a dedicated - * tasks to fill the emptied slots when a connection leaves. - * Also, resolve deferred tracking dependency if needed. - */ - newsrv = curproxy->srv; - while (newsrv != NULL) { - set_usermsgs_ctx(newsrv->conf.file, newsrv->conf.line, &newsrv->obj_type); - - srv_minmax_conn_apply(newsrv); - - /* this will also properly set the transport layer for - * prod and checks - * if default-server have use_ssl, prerare ssl init - * without activating it */ - if (newsrv->use_ssl == 1 || newsrv->check.use_ssl == 1 || - (newsrv->proxy->options & PR_O_TCPCHK_SSL) || - ((newsrv->flags & SRV_F_DEFSRV_USE_SSL) && newsrv->use_ssl != 1)) { - if (xprt_get(XPRT_SSL) && xprt_get(XPRT_SSL)->prepare_srv) - cfgerr += xprt_get(XPRT_SSL)->prepare_srv(newsrv); - else if (xprt_get(XPRT_QUIC) && xprt_get(XPRT_QUIC)->prepare_srv) - cfgerr += xprt_get(XPRT_QUIC)->prepare_srv(newsrv); - } - - if (newsrv->use_ssl == 1 || ((newsrv->flags & SRV_F_DEFSRV_USE_SSL) && newsrv->use_ssl != 1)) { - /* In HTTP only, if the SNI is not set and we can rely on the host - * header value, fill the sni expression accordingly - */ - if (!newsrv->sni_expr && newsrv->proxy->mode == PR_MODE_HTTP && - !(newsrv->ssl_ctx.options & SRV_SSL_O_NO_AUTO_SNI)) { - newsrv->sni_expr = strdup("req.hdr(host),field(1,:)"); - - err = NULL; - if (server_parse_exprs(newsrv, curproxy, &err)) { - ha_alert("parsing [%s:%d]: failed to parse auto SNI expression: %s\n", - newsrv->conf.file, newsrv->conf.line, err); - free(err); - ++cfgerr; - goto next_srv; - } - } - } - - - if ((newsrv->flags & SRV_F_FASTOPEN) && - ((curproxy->retry_type & (PR_RE_DISCONNECTED | PR_RE_TIMEOUT)) != - (PR_RE_DISCONNECTED | PR_RE_TIMEOUT))) - ha_warning("server has tfo activated, the backend should be configured with at least 'conn-failure', 'empty-response' and 'response-timeout' or we wouldn't be able to retry the connection on failure.\n"); - - if (newsrv->trackit) { - if (srv_apply_track(newsrv, curproxy)) { - ++cfgerr; - goto next_srv; - } - } - - next_srv: - reset_usermsgs_ctx(); - newsrv = newsrv->next; - } - - /* - * Try to generate dynamic cookies for servers now. - * It couldn't be done earlier, since at the time we parsed - * the server line, we may not have known yet that we - * should use dynamic cookies, or the secret key may not - * have been provided yet. - */ - if (curproxy->ck_opts & PR_CK_DYNAMIC) { - newsrv = curproxy->srv; - while (newsrv != NULL) { - srv_set_dyncookie(newsrv); - newsrv = newsrv->next; - } - - } - /* We have to initialize the server lookup mechanism depending - * on what LB algorithm was chosen. - */ - - curproxy->lbprm.algo &= ~(BE_LB_LKUP | BE_LB_PROP_DYN); - switch (curproxy->lbprm.algo & BE_LB_KIND) { - case BE_LB_KIND_RR: - if ((curproxy->lbprm.algo & BE_LB_PARM) == BE_LB_RR_STATIC) { - curproxy->lbprm.algo |= BE_LB_LKUP_MAP; - init_server_map(curproxy); - } else if ((curproxy->lbprm.algo & BE_LB_PARM) == BE_LB_RR_RANDOM) { - curproxy->lbprm.algo |= BE_LB_LKUP_CHTREE | BE_LB_PROP_DYN; - if (chash_init_server_tree(curproxy) < 0) { - cfgerr++; - } - } else { - curproxy->lbprm.algo |= BE_LB_LKUP_RRTREE | BE_LB_PROP_DYN; - fwrr_init_server_groups(curproxy); - } - break; - - case BE_LB_KIND_CB: - if ((curproxy->lbprm.algo & BE_LB_PARM) == BE_LB_CB_LC) { - curproxy->lbprm.algo |= BE_LB_LKUP_LCTREE | BE_LB_PROP_DYN; - fwlc_init_server_tree(curproxy); - } else { - curproxy->lbprm.algo |= BE_LB_LKUP_FSTREE | BE_LB_PROP_DYN; - fas_init_server_tree(curproxy); - } - break; - - case BE_LB_KIND_HI: - if ((curproxy->lbprm.algo & BE_LB_HASH_TYPE) == BE_LB_HASH_CONS) { - curproxy->lbprm.algo |= BE_LB_LKUP_CHTREE | BE_LB_PROP_DYN; - if (chash_init_server_tree(curproxy) < 0) { - cfgerr++; - } - } else { - curproxy->lbprm.algo |= BE_LB_LKUP_MAP; - init_server_map(curproxy); - } - break; - case BE_LB_KIND_SA: - if ((curproxy->lbprm.algo & BE_LB_PARM) == BE_LB_SA_SS) { - curproxy->lbprm.algo |= BE_LB_PROP_DYN; - init_server_ss(curproxy); - } - break; - } - HA_RWLOCK_INIT(&curproxy->lbprm.lock); - - if (curproxy->options & PR_O_LOGASAP) - curproxy->to_log &= ~LW_BYTES; - - if (!(curproxy->cap & PR_CAP_INT) && (curproxy->mode == PR_MODE_TCP || curproxy->mode == PR_MODE_HTTP) && - (curproxy->cap & PR_CAP_FE) && LIST_ISEMPTY(&curproxy->loggers) && - (!lf_expr_isempty(&curproxy->logformat) || !lf_expr_isempty(&curproxy->logformat_sd))) { - ha_warning("log format ignored for %s '%s' since it has no log address.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - if (curproxy->mode != PR_MODE_HTTP && !(curproxy->options & PR_O_HTTP_UPG)) { - int optnum; - - if (curproxy->uri_auth) { - ha_warning("'stats' statement ignored for %s '%s' as it requires HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - stats_uri_auth_drop(curproxy->uri_auth); - curproxy->uri_auth = NULL; - } - - if (curproxy->capture_name) { - ha_warning("'capture' statement ignored for %s '%s' as it requires HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - if (isttest(curproxy->monitor_uri)) { - ha_warning("'monitor-uri' statement ignored for %s '%s' as it requires HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - if (!LIST_ISEMPTY(&curproxy->http_req_rules)) { - ha_warning("'http-request' rules ignored for %s '%s' as they require HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - if (!LIST_ISEMPTY(&curproxy->http_res_rules)) { - ha_warning("'http-response' rules ignored for %s '%s' as they require HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - if (!LIST_ISEMPTY(&curproxy->http_after_res_rules)) { - ha_warning("'http-after-response' rules ignored for %s '%s' as they require HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - if (!LIST_ISEMPTY(&curproxy->redirect_rules)) { - ha_warning("'redirect' rules ignored for %s '%s' as they require HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - } - - for (optnum = 0; cfg_opts[optnum].name; optnum++) { - if (cfg_opts[optnum].mode == PR_MODE_HTTP && - (curproxy->cap & cfg_opts[optnum].cap) && - (curproxy->options & cfg_opts[optnum].val)) { - ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n", - cfg_opts[optnum].name, proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - curproxy->options &= ~cfg_opts[optnum].val; - } - } - - for (optnum = 0; cfg_opts2[optnum].name; optnum++) { - if (cfg_opts2[optnum].mode == PR_MODE_HTTP && - (curproxy->cap & cfg_opts2[optnum].cap) && - (curproxy->options2 & cfg_opts2[optnum].val)) { - ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n", - cfg_opts2[optnum].name, proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - curproxy->options2 &= ~cfg_opts2[optnum].val; - } - } - -#if defined(CONFIG_HAP_TRANSPARENT) - if (curproxy->conn_src.bind_hdr_occ) { - curproxy->conn_src.bind_hdr_occ = 0; - ha_warning("%s '%s' : ignoring use of header %s as source IP in non-HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id, curproxy->conn_src.bind_hdr_name); - err_code |= ERR_WARN; - } -#endif - } - - /* - * ensure that we're not cross-dressing a TCP server into HTTP. - */ - newsrv = curproxy->srv; - while (newsrv != NULL) { - if ((curproxy->mode != PR_MODE_HTTP) && newsrv->rdr_len) { - ha_alert("%s '%s' : server cannot have cookie or redirect prefix in non-HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id); - cfgerr++; - } - - if ((curproxy->mode != PR_MODE_HTTP) && newsrv->cklen) { - ha_warning("%s '%s' : ignoring cookie for server '%s' as HTTP mode is disabled.\n", - proxy_type_str(curproxy), curproxy->id, newsrv->id); - err_code |= ERR_WARN; - } - - if ((newsrv->flags & SRV_F_MAPPORTS) && (curproxy->options2 & PR_O2_RDPC_PRST)) { - ha_warning("%s '%s' : RDP cookie persistence will not work for server '%s' because it lacks an explicit port number.\n", - proxy_type_str(curproxy), curproxy->id, newsrv->id); - err_code |= ERR_WARN; - } - -#if defined(CONFIG_HAP_TRANSPARENT) - if (curproxy->mode != PR_MODE_HTTP && newsrv->conn_src.bind_hdr_occ) { - newsrv->conn_src.bind_hdr_occ = 0; - ha_warning("%s '%s' : server %s cannot use header %s as source IP in non-HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id, newsrv->id, newsrv->conn_src.bind_hdr_name); - err_code |= ERR_WARN; - } -#endif - - if ((curproxy->mode != PR_MODE_HTTP) && (curproxy->options & PR_O_REUSE_MASK) != PR_O_REUSE_NEVR) - curproxy->options &= ~PR_O_REUSE_MASK; - if (curproxy->mode == PR_MODE_SPOP) - curproxy->options |= PR_O_REUSE_ALWS; - - if ((curproxy->mode != PR_MODE_HTTP) && newsrv->flags & SRV_F_RHTTP) { - ha_alert("%s '%s' : server %s uses reverse HTTP addressing which can only be used with HTTP mode.\n", - proxy_type_str(curproxy), curproxy->id, newsrv->id); - cfgerr++; - err_code |= ERR_FATAL | ERR_ALERT; + ret = proxy_finalize(curproxy, &err_code); + if (ret) { + cfgerr += ret; + if (err_code & ERR_FATAL) goto out; - } - - newsrv = newsrv->next; - } - - /* Check filter configuration, if any */ - cfgerr += flt_check(curproxy); - - if (curproxy->cap & PR_CAP_FE) { - if (!curproxy->accept) - curproxy->accept = frontend_accept; - - if (!LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules) || - (curproxy->defpx && !LIST_ISEMPTY(&curproxy->defpx->tcp_req.inspect_rules))) - curproxy->fe_req_ana |= AN_REQ_INSPECT_FE; - - if (curproxy->mode == PR_MODE_HTTP) { - curproxy->fe_req_ana |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_PROCESS_FE; - curproxy->fe_rsp_ana |= AN_RES_WAIT_HTTP | AN_RES_HTTP_PROCESS_FE; - } - - if (curproxy->mode == PR_MODE_CLI) { - curproxy->fe_req_ana |= AN_REQ_WAIT_CLI; - curproxy->fe_rsp_ana |= AN_RES_WAIT_CLI; - } - - /* both TCP and HTTP must check switching rules */ - curproxy->fe_req_ana |= AN_REQ_SWITCHING_RULES; - - /* Add filters analyzers if needed */ - if (!LIST_ISEMPTY(&curproxy->filter_configs)) { - curproxy->fe_req_ana |= AN_REQ_FLT_START_FE | AN_REQ_FLT_XFER_DATA | AN_REQ_FLT_END; - curproxy->fe_rsp_ana |= AN_RES_FLT_START_FE | AN_RES_FLT_XFER_DATA | AN_RES_FLT_END; - } - } - - if (curproxy->cap & PR_CAP_BE) { - if (!LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules) || - (curproxy->defpx && !LIST_ISEMPTY(&curproxy->defpx->tcp_req.inspect_rules))) - curproxy->be_req_ana |= AN_REQ_INSPECT_BE; - - if (!LIST_ISEMPTY(&curproxy->tcp_rep.inspect_rules) || - (curproxy->defpx && !LIST_ISEMPTY(&curproxy->defpx->tcp_rep.inspect_rules))) - curproxy->be_rsp_ana |= AN_RES_INSPECT; - - if (curproxy->mode == PR_MODE_HTTP) { - curproxy->be_req_ana |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_INNER | AN_REQ_HTTP_PROCESS_BE; - curproxy->be_rsp_ana |= AN_RES_WAIT_HTTP | AN_RES_HTTP_PROCESS_BE; - } - - /* If the backend does requires RDP cookie persistence, we have to - * enable the corresponding analyser. - */ - if (curproxy->options2 & PR_O2_RDPC_PRST) - curproxy->be_req_ana |= AN_REQ_PRST_RDP_COOKIE; - - /* Add filters analyzers if needed */ - if (!LIST_ISEMPTY(&curproxy->filter_configs)) { - curproxy->be_req_ana |= AN_REQ_FLT_START_BE | AN_REQ_FLT_XFER_DATA | AN_REQ_FLT_END; - curproxy->be_rsp_ana |= AN_RES_FLT_START_BE | AN_RES_FLT_XFER_DATA | AN_RES_FLT_END; - } - } - - /* Check the mux protocols, if any, for each server attached to - * the current proxy */ - for (newsrv = curproxy->srv; newsrv; newsrv = newsrv->next) { - int mode = conn_pr_mode_to_proto_mode(curproxy->mode); - const struct mux_proto_list *mux_ent; - - if (srv_is_quic(newsrv)) { - if (!newsrv->mux_proto) { - /* Force QUIC as mux-proto on server with quic addresses, similarly to bind on FE side. */ - newsrv->mux_proto = get_mux_proto(ist("quic")); - } - } - - if (!newsrv->mux_proto) - continue; - - /* it is possible that an incorrect mux was referenced - * due to the proxy's mode not being taken into account - * on first pass. Let's adjust it now. - */ - mux_ent = conn_get_best_mux_entry(newsrv->mux_proto->token, PROTO_SIDE_BE, mode); - - if (!mux_ent || !isteq(mux_ent->token, newsrv->mux_proto->token)) { - ha_alert("%s '%s' : MUX protocol '%.*s' is not usable for server '%s' at [%s:%d].\n", - proxy_type_str(curproxy), curproxy->id, - (int)newsrv->mux_proto->token.len, - newsrv->mux_proto->token.ptr, - newsrv->id, newsrv->conf.file, newsrv->conf.line); - cfgerr++; - } - else { - if ((mux_ent->mux->flags & MX_FL_FRAMED) && !srv_is_quic(newsrv)) { - ha_alert("%s '%s' : MUX protocol '%.*s' is incompatible with stream transport used by server '%s' at [%s:%d].\n", - proxy_type_str(curproxy), curproxy->id, - (int)newsrv->mux_proto->token.len, - newsrv->mux_proto->token.ptr, - newsrv->id, newsrv->conf.file, newsrv->conf.line); - cfgerr++; - } - else if (!(mux_ent->mux->flags & MX_FL_FRAMED) && srv_is_quic(newsrv)) { - ha_alert("%s '%s' : MUX protocol '%.*s' is incompatible with framed transport used by server '%s' at [%s:%d].\n", - proxy_type_str(curproxy), curproxy->id, - (int)newsrv->mux_proto->token.len, - newsrv->mux_proto->token.ptr, - newsrv->id, newsrv->conf.file, newsrv->conf.line); - cfgerr++; - } - } - - /* update the mux */ - newsrv->mux_proto = mux_ent; - } - - /* Allocate default tcp-check rules for proxies without - * explicit rules. - */ - if (curproxy->cap & PR_CAP_BE) { - if (!(curproxy->options2 & PR_O2_CHK_ANY)) { - struct tcpcheck_ruleset *rs = NULL; - struct tcpcheck_rules *rules = &curproxy->tcpcheck_rules; - - curproxy->options2 |= PR_O2_TCPCHK_CHK; - - rs = find_tcpcheck_ruleset("*tcp-check"); - if (!rs) { - rs = create_tcpcheck_ruleset("*tcp-check"); - if (rs == NULL) { - ha_alert("config: %s '%s': out of memory.\n", - proxy_type_str(curproxy), curproxy->id); - cfgerr++; - } - } - - free_tcpcheck_vars(&rules->preset_vars); - rules->list = &rs->rules; - rules->flags = 0; - } } } @@ -3953,7 +2674,6 @@ int check_config_validity() if (!LIST_ISEMPTY(&curpeers->peers_fe->conf.bind)) { struct list *l; struct bind_conf *bind_conf; - int ret; l = &curpeers->peers_fe->conf.bind; bind_conf = LIST_ELEM(l->n, typeof(bind_conf), by_fe); diff --git a/src/proxy.c b/src/proxy.c index d3b7c0f93fffe..7f573659e1795 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -46,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -1591,6 +1599,1307 @@ int proxy_init_per_thr(struct proxy *px) return 0; } +int proxy_finalize(struct proxy *px, int *err_code) +{ + struct bind_conf *bind_conf; + struct server *newsrv; + struct switching_rule *rule; + struct server_rule *srule; + struct sticking_rule *mrule; + struct logger *tmplogger; + unsigned int next_id; + int cfgerr = 0; + char *err = NULL; + int i; + + /* check and reduce the bind-proc of each listener */ + list_for_each_entry(bind_conf, &px->conf.bind, by_fe) { + int mode = conn_pr_mode_to_proto_mode(px->mode); + const struct mux_proto_list *mux_ent; + int ret; + + /* Check the mux protocols, if any; before the check the ALPN */ + if (bind_conf->xprt && bind_conf->xprt == xprt_get(XPRT_QUIC)) { + if (!bind_conf->mux_proto) { + /* No protocol was specified. If we're using QUIC at the transport + * layer, we'll instantiate it as a mux as well. If QUIC is not + * compiled in, this will remain NULL. + */ + bind_conf->mux_proto = get_mux_proto(ist("quic")); + } + if (bind_conf->options & BC_O_ACC_PROXY) { + ha_alert("Binding [%s:%d] for %s %s: QUIC protocol does not support PROXY protocol yet." + " 'accept-proxy' option cannot be used with a QUIC listener.\n", + bind_conf->file, bind_conf->line, + proxy_type_str(px), px->id); + cfgerr++; + } + } + + if (bind_conf->mux_proto) { + /* it is possible that an incorrect mux was referenced + * due to the proxy's mode not being taken into account + * on first pass. Let's adjust it now. + */ + mux_ent = conn_get_best_mux_entry(bind_conf->mux_proto->token, PROTO_SIDE_FE, mode); + + if (!mux_ent || !isteq(mux_ent->token, bind_conf->mux_proto->token)) { + ha_alert("%s '%s' : MUX protocol '%.*s' is not usable for 'bind %s' at [%s:%d].\n", + proxy_type_str(px), px->id, + (int)bind_conf->mux_proto->token.len, + bind_conf->mux_proto->token.ptr, + bind_conf->arg, bind_conf->file, bind_conf->line); + cfgerr++; + } + else { + if ((mux_ent->mux->flags & MX_FL_FRAMED) && !(bind_conf->options & BC_O_USE_SOCK_DGRAM)) { + ha_alert("%s '%s' : frame-based MUX protocol '%.*s' is incompatible with stream transport of 'bind %s' at [%s:%d].\n", + proxy_type_str(px), px->id, + (int)bind_conf->mux_proto->token.len, + bind_conf->mux_proto->token.ptr, + bind_conf->arg, bind_conf->file, bind_conf->line); + cfgerr++; + } + else if (!(mux_ent->mux->flags & MX_FL_FRAMED) && !(bind_conf->options & BC_O_USE_SOCK_STREAM)) { + ha_alert("%s '%s' : stream-based MUX protocol '%.*s' is incompatible with framed transport of 'bind %s' at [%s:%d].\n", + proxy_type_str(px), px->id, + (int)bind_conf->mux_proto->token.len, + bind_conf->mux_proto->token.ptr, + bind_conf->arg, bind_conf->file, bind_conf->line); + cfgerr++; + } + } + + /* update the mux */ + bind_conf->mux_proto = mux_ent; + } + + + /* HTTP frontends with "h2" as ALPN/NPN will work in + * HTTP/2 and absolutely require buffers 16kB or larger. + */ +#ifdef USE_OPENSSL + /* no-alpn ? If so, it's the right moment to remove it */ + if (bind_conf->ssl_conf.alpn_str && !bind_conf->ssl_conf.alpn_len) { + ha_free(&bind_conf->ssl_conf.alpn_str); + } +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + else if (!bind_conf->ssl_conf.alpn_str && !bind_conf->ssl_conf.npn_str && + ((bind_conf->options & BC_O_USE_SSL) || bind_conf->xprt == xprt_get(XPRT_QUIC)) && + px->mode == PR_MODE_HTTP && global.tune.bufsize >= 16384) { + + /* Neither ALPN nor NPN were explicitly set nor disabled, we're + * in HTTP mode with an SSL or QUIC listener, we can enable ALPN. + * Note that it's in binary form. First we try to set the ALPN from + * mux proto if set. Otherwise rely on the default ALPN. + */ + if (bind_conf->mux_proto && bind_conf->mux_proto->alpn) + bind_conf->ssl_conf.alpn_str = strdup(bind_conf->mux_proto->alpn); + else if (bind_conf->xprt == xprt_get(XPRT_QUIC)) + bind_conf->ssl_conf.alpn_str = strdup("\002h3"); + else + bind_conf->ssl_conf.alpn_str = strdup("\002h2\010http/1.1"); + + if (!bind_conf->ssl_conf.alpn_str) { + ha_alert("Proxy '%s': out of memory while trying to allocate a default alpn string in 'bind %s' at [%s:%d].\n", + px->id, bind_conf->arg, bind_conf->file, bind_conf->line); + cfgerr++; + *err_code |= ERR_FATAL | ERR_ALERT; + goto out; + } + bind_conf->ssl_conf.alpn_len = strlen(bind_conf->ssl_conf.alpn_str); + } +#endif /* TLSEXT_TYPE_application_layer_protocol_negotiation */ + + + if (px->mode == PR_MODE_HTTP && global.tune.bufsize < 16384) { +#ifdef OPENSSL_NPN_NEGOTIATED + /* check NPN */ + if (bind_conf->ssl_conf.npn_str && strstr(bind_conf->ssl_conf.npn_str, "\002h2")) { + ha_alert("HTTP frontend '%s' enables HTTP/2 via NPN at [%s:%d], so global.tune.bufsize must be at least 16384 bytes (%d now).\n", + px->id, bind_conf->file, bind_conf->line, global.tune.bufsize); + cfgerr++; + } +#endif /* OPENSSL_NPN_NEGOTIATED */ +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + /* check ALPN */ + if (bind_conf->ssl_conf.alpn_str && strstr(bind_conf->ssl_conf.alpn_str, "\002h2")) { + ha_alert("HTTP frontend '%s' enables HTTP/2 via ALPN at [%s:%d], so global.tune.bufsize must be at least 16384 bytes (%d now).\n", + px->id, bind_conf->file, bind_conf->line, global.tune.bufsize); + cfgerr++; + } +#endif /* TLSEXT_TYPE_application_layer_protocol_negotiation */ + } /* HTTP && bufsize < 16384 */ +#endif /* USE_OPENSSL */ + +#ifdef USE_QUIC + if (bind_conf->xprt == xprt_get(XPRT_QUIC)) { + const struct quic_cc_algo *cc_algo = bind_conf->quic_cc_algo ? + bind_conf->quic_cc_algo : default_quic_cc_algo; + + if (!(cc_algo->flags & QUIC_CC_ALGO_FL_OPT_PACING) && + !(quic_tune.fe.fb_opts & QUIC_TUNE_FB_TX_PACING)) { + ha_warning("Binding [%s:%d] for %s %s: using the selected congestion algorithm without pacing may cause slowdowns or high loss rates during transfers.\n", + bind_conf->file, bind_conf->line, + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + } +#endif /* USE_QUIC */ + + /* finish the bind setup */ + ret = bind_complete_thread_setup(bind_conf, err_code); + if (ret != 0) { + cfgerr += ret; + if (*err_code & ERR_FATAL) + goto out; + } + + if (bind_generate_guid(bind_conf)) { + cfgerr++; + *err_code |= ERR_FATAL | ERR_ALERT; + goto out; + } + } + + switch (px->mode) { + case PR_MODE_TCP: + cfgerr += proxy_cfg_ensure_no_http(px); + cfgerr += proxy_cfg_ensure_no_log(px); + break; + + case PR_MODE_HTTP: + cfgerr += proxy_cfg_ensure_no_log(px); + px->http_needed = 1; + break; + + case PR_MODE_CLI: + cfgerr += proxy_cfg_ensure_no_http(px); + cfgerr += proxy_cfg_ensure_no_log(px); + break; + + case PR_MODE_SYSLOG: + /* this mode is initialized as the classic tcp proxy */ + cfgerr += proxy_cfg_ensure_no_http(px); + break; + + case PR_MODE_SPOP: + cfgerr += proxy_cfg_ensure_no_http(px); + cfgerr += proxy_cfg_ensure_no_log(px); + break; + + case PR_MODE_PEERS: + case PR_MODES: + /* should not happen, bug gcc warn missing switch statement */ + ha_alert("%s '%s' cannot initialize this proxy mode (peers) in this way. NOTE: PLEASE REPORT THIS TO DEVELOPERS AS YOU'RE NOT SUPPOSED TO BE ABLE TO CREATE A CONFIGURATION TRIGGERING THIS!\n", + proxy_type_str(px), px->id); + cfgerr++; + break; + } + + if (!(px->cap & PR_CAP_INT) && (px->cap & PR_CAP_FE) && LIST_ISEMPTY(&px->conf.listeners)) { + ha_warning("%s '%s' has no 'bind' directive. Please declare it as a backend if this was intended.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + if (px->cap & PR_CAP_BE) { + if (px->lbprm.algo & BE_LB_KIND) { + if (px->options & PR_O_TRANSP) { + ha_alert("%s '%s' cannot use both transparent and balance mode.\n", + proxy_type_str(px), px->id); + cfgerr++; + } + else if (px->options & PR_O_DISPATCH) { + ha_warning("dispatch address of %s '%s' will be ignored in balance mode.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + } + else if (!(px->options & (PR_O_TRANSP | PR_O_DISPATCH))) { + /* If no LB algo is set in a backend, and we're not in + * transparent mode, dispatch mode nor proxy mode, we + * want to use balance random by default. + */ + px->lbprm.algo &= ~BE_LB_ALGO; + px->lbprm.algo |= BE_LB_ALGO_RND; + } + } + + if (px->options & PR_O_DISPATCH) + px->options &= ~PR_O_TRANSP; + else if (px->options & PR_O_TRANSP) + px->options &= ~PR_O_DISPATCH; + + if ((px->tcpcheck_rules.flags & TCPCHK_RULES_UNUSED_HTTP_RS)) { + ha_warning("%s '%s' uses http-check rules without 'option httpchk', so the rules are ignored.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK && + (px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_HTTP_CHK) { + if (px->options & PR_O_DISABLE404) { + ha_warning("'%s' will be ignored for %s '%s' (requires 'option httpchk').\n", + "disable-on-404", proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + px->options &= ~PR_O_DISABLE404; + } + if (px->options2 & PR_O2_CHK_SNDST) { + ha_warning("'%s' will be ignored for %s '%s' (requires 'option httpchk').\n", + "send-state", proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + px->options2 &= ~PR_O2_CHK_SNDST; + } + } + + if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) { + if (!global.external_check) { + ha_alert("Proxy '%s' : '%s' unable to find required 'global.external-check'.\n", + px->id, "option external-check"); + cfgerr++; + } + if (!px->check_command) { + ha_alert("Proxy '%s' : '%s' unable to find required 'external-check command'.\n", + px->id, "option external-check"); + cfgerr++; + } + if (!(global.tune.options & GTUNE_INSECURE_FORK)) { + ha_warning("Proxy '%s' : 'insecure-fork-wanted' not enabled in the global section, '%s' will likely fail.\n", + px->id, "option external-check"); + *err_code |= ERR_WARN; + } + } + + if (px->email_alert.flags & PR_EMAIL_ALERT_SET) { + if (!(px->email_alert.mailers.name && px->email_alert.from && px->email_alert.to)) { + ha_warning("'email-alert' will be ignored for %s '%s' (the presence any of " + "'email-alert from', 'email-alert level' 'email-alert mailers', " + "'email-alert myhostname', or 'email-alert to' " + "requires each of 'email-alert from', 'email-alert mailers' and 'email-alert to' " + "to be present).\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + free_email_alert(px); + } + if (!px->email_alert.myhostname) + px->email_alert.myhostname = strdup(hostname); + } + + if (px->check_command) { + int clear = 0; + if ((px->options2 & PR_O2_CHK_ANY) != PR_O2_EXT_CHK) { + ha_warning("'%s' will be ignored for %s '%s' (requires 'option external-check').\n", + "external-check command", proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + clear = 1; + } + if (px->check_command[0] != '/' && !px->check_path) { + ha_alert("Proxy '%s': '%s' does not have a leading '/' and 'external-check path' is not set.\n", + px->id, "external-check command"); + cfgerr++; + } + if (clear) { + ha_free(&px->check_command); + } + } + + if (px->check_path) { + if ((px->options2 & PR_O2_CHK_ANY) != PR_O2_EXT_CHK) { + ha_warning("'%s' will be ignored for %s '%s' (requires 'option external-check').\n", + "external-check path", proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + ha_free(&px->check_path); + } + } + + /* if a default backend was specified, let's find it */ + if (px->defbe.name) { + struct proxy *target; + + target = proxy_be_by_name(px->defbe.name); + if (!target) { + ha_alert("Proxy '%s': unable to find required default_backend: '%s'.\n", + px->id, px->defbe.name); + cfgerr++; + } else if (target == px) { + ha_alert("Proxy '%s': loop detected for default_backend: '%s'.\n", + px->id, px->defbe.name); + cfgerr++; + } else if (target->mode != px->mode && + !(px->mode == PR_MODE_TCP && target->mode == PR_MODE_HTTP)) { + + ha_alert("%s %s '%s' (%s:%d) tries to use incompatible %s %s '%s' (%s:%d) as its default backend (see 'mode').\n", + proxy_mode_str(px->mode), proxy_type_str(px), px->id, + px->conf.file, px->conf.line, + proxy_mode_str(target->mode), proxy_type_str(target), target->id, + target->conf.file, target->conf.line); + cfgerr++; + } else { + free(px->defbe.name); + px->defbe.be = target; + /* Emit a warning if this proxy also has some servers */ + if (px->srv) { + ha_warning("In proxy '%s', the 'default_backend' rule always has precedence over the servers, which will never be used.\n", + px->id); + *err_code |= ERR_WARN; + } + if (target->mode == PR_MODE_HTTP) { + /* at least one of the used backends will provoke an + * HTTP upgrade + */ + px->options |= PR_O_HTTP_UPG; + } + } + } + + /* find the target proxy for 'use_backend' rules */ + list_for_each_entry(rule, &px->switching_rules, list) { + struct proxy *target; + struct logformat_node *node; + char *pxname; + + /* Try to parse the string as a log format expression. If the result + * of the parsing is only one entry containing a simple string, then + * it's a standard string corresponding to a static rule, thus the + * parsing is cancelled and be.name is restored to be resolved. + */ + pxname = rule->be.name; + lf_expr_init(&rule->be.expr); + px->conf.args.ctx = ARGC_UBK; + px->conf.args.file = rule->file; + px->conf.args.line = rule->line; + err = NULL; + if (!parse_logformat_string(pxname, px, &rule->be.expr, 0, SMP_VAL_FE_HRQ_HDR, &err)) { + ha_alert("Parsing [%s:%d]: failed to parse use_backend rule '%s' : %s.\n", + rule->file, rule->line, pxname, err); + free(err); + cfgerr++; + continue; + } + node = LIST_NEXT(&rule->be.expr.nodes.list, struct logformat_node *, list); + + if (!lf_expr_isempty(&rule->be.expr)) { + if (node->type != LOG_FMT_TEXT || node->list.n != &rule->be.expr.nodes.list) { + rule->dynamic = 1; + free(pxname); + /* backend is not yet known so we cannot assume its type, + * thus we should consider that at least one of the used + * backends may provoke HTTP upgrade + */ + px->options |= PR_O_HTTP_UPG; + continue; + } + /* Only one element in the list, a simple string: free the expression and + * fall back to static rule + */ + lf_expr_deinit(&rule->be.expr); + } + + rule->dynamic = 0; + rule->be.name = pxname; + + target = proxy_be_by_name(rule->be.name); + if (!target) { + ha_alert("Proxy '%s': unable to find required use_backend: '%s'.\n", + px->id, rule->be.name); + cfgerr++; + } else if (target == px) { + ha_alert("Proxy '%s': loop detected for use_backend: '%s'.\n", + px->id, rule->be.name); + cfgerr++; + } else if (target->mode != px->mode && + !(px->mode == PR_MODE_TCP && target->mode == PR_MODE_HTTP)) { + + ha_alert("%s %s '%s' (%s:%d) tries to use incompatible %s %s '%s' (%s:%d) in a 'use_backend' rule (see 'mode').\n", + proxy_mode_str(px->mode), proxy_type_str(px), px->id, + px->conf.file, px->conf.line, + proxy_mode_str(target->mode), proxy_type_str(target), target->id, + target->conf.file, target->conf.line); + cfgerr++; + } else { + ha_free(&rule->be.name); + rule->be.backend = target; + if (target->mode == PR_MODE_HTTP) { + /* at least one of the used backends will provoke an + * HTTP upgrade + */ + px->options |= PR_O_HTTP_UPG; + } + } + *err_code |= warnif_tcp_http_cond(px, rule->cond); + } + + /* find the target server for 'use_server' rules */ + list_for_each_entry(srule, &px->server_rules, list) { + struct server *target; + struct logformat_node *node; + char *server_name; + + /* We try to parse the string as a log format expression. If the result of the parsing + * is only one entry containing a single string, then it's a standard string corresponding + * to a static rule, thus the parsing is cancelled and we fall back to setting srv.ptr. + */ + server_name = srule->srv.name; + lf_expr_init(&srule->expr); + px->conf.args.ctx = ARGC_USRV; + err = NULL; + if (!parse_logformat_string(server_name, px, &srule->expr, 0, SMP_VAL_FE_HRQ_HDR, &err)) { + ha_alert("Parsing [%s:%d]; use-server rule failed to parse log-format '%s' : %s.\n", + srule->file, srule->line, server_name, err); + free(err); + cfgerr++; + continue; + } + node = LIST_NEXT(&srule->expr.nodes.list, struct logformat_node *, list); + + if (!lf_expr_isempty(&srule->expr)) { + if (node->type != LOG_FMT_TEXT || node->list.n != &srule->expr.nodes.list) { + srule->dynamic = 1; + free(server_name); + continue; + } + /* Only one element in the list, a simple string: free the expression and + * fall back to static rule + */ + lf_expr_deinit(&srule->expr); + } + + srule->dynamic = 0; + srule->srv.name = server_name; + target = server_find_by_name(px, srule->srv.name); + *err_code |= warnif_tcp_http_cond(px, srule->cond); + + if (!target) { + ha_alert("%s '%s' : unable to find server '%s' referenced in a 'use-server' rule.\n", + proxy_type_str(px), px->id, srule->srv.name); + cfgerr++; + continue; + } + ha_free(&srule->srv.name); + srule->srv.ptr = target; + target->flags |= SRV_F_NON_PURGEABLE; + } + + /* find the target table for 'stick' rules */ + list_for_each_entry(mrule, &px->sticking_rules, list) { + px->be_req_ana |= AN_REQ_STICKING_RULES; + if (mrule->flags & STK_IS_STORE) + px->be_rsp_ana |= AN_RES_STORE_RULES; + + if (!resolve_stick_rule(px, mrule)) + cfgerr++; + + *err_code |= warnif_tcp_http_cond(px, mrule->cond); + } + + /* find the target table for 'store response' rules */ + list_for_each_entry(mrule, &px->storersp_rules, list) { + px->be_rsp_ana |= AN_RES_STORE_RULES; + + if (!resolve_stick_rule(px, mrule)) + cfgerr++; + } + + /* check validity for 'tcp-request' layer 4/5/6/7 rules */ + cfgerr += check_action_rules(&px->tcp_req.l4_rules, px, err_code); + cfgerr += check_action_rules(&px->tcp_req.l5_rules, px, err_code); + cfgerr += check_action_rules(&px->tcp_req.inspect_rules, px, err_code); + cfgerr += check_action_rules(&px->tcp_rep.inspect_rules, px, err_code); + cfgerr += check_action_rules(&px->http_req_rules, px, err_code); + cfgerr += check_action_rules(&px->http_res_rules, px, err_code); + cfgerr += check_action_rules(&px->http_after_res_rules, px, err_code); + + /* Warn is a switch-mode http is used on a TCP listener with servers but no backend */ + if (!px->defbe.name && LIST_ISEMPTY(&px->switching_rules) && px->srv) { + if ((px->options & PR_O_HTTP_UPG) && px->mode == PR_MODE_TCP) + ha_warning("Proxy '%s' : 'switch-mode http' configured for a %s %s with no backend. " + "Incoming connections upgraded to HTTP cannot be routed to TCP servers\n", + px->id, proxy_mode_str(px->mode), proxy_type_str(px)); + } + + if (px->table && px->table->peers.name) { + struct peers *curpeers; + + for (curpeers = cfg_peers; curpeers; curpeers = curpeers->next) { + if (strcmp(curpeers->id, px->table->peers.name) == 0) { + ha_free(&px->table->peers.name); + px->table->peers.p = curpeers; + break; + } + } + + if (!curpeers) { + ha_alert("Proxy '%s': unable to find sync peers '%s'.\n", + px->id, px->table->peers.name); + ha_free(&px->table->peers.name); + px->table->peers.p = NULL; + cfgerr++; + } + else if (curpeers->disabled) { + /* silently disable this peers section */ + px->table->peers.p = NULL; + } + else if (!curpeers->peers_fe) { + ha_alert("Proxy '%s': unable to find local peer '%s' in peers section '%s'.\n", + px->id, localpeer, curpeers->id); + px->table->peers.p = NULL; + cfgerr++; + } + } + + + if (px->email_alert.mailers.name) { + struct mailers *curmailers = mailers; + + for (curmailers = mailers; curmailers; curmailers = curmailers->next) { + if (strcmp(curmailers->id, px->email_alert.mailers.name) == 0) + break; + } + if (!curmailers) { + ha_alert("Proxy '%s': unable to find mailers '%s'.\n", + px->id, px->email_alert.mailers.name); + free_email_alert(px); + cfgerr++; + } + else { + err = NULL; + if (init_email_alert(curmailers, px, &err)) { + ha_alert("Proxy '%s': %s.\n", px->id, err); + free(err); + cfgerr++; + } + } + } + + if (px->uri_auth && !(px->uri_auth->flags & STAT_F_CONVDONE) && + !LIST_ISEMPTY(&px->uri_auth->http_req_rules) && + (px->uri_auth->userlist || px->uri_auth->auth_realm )) { + ha_alert("%s '%s': stats 'auth'/'realm' and 'http-request' can't be used at the same time.\n", + "proxy", px->id); + cfgerr++; + goto out_uri_auth_compat; + } + + if (px->uri_auth && px->uri_auth->userlist && + (!(px->uri_auth->flags & STAT_F_CONVDONE) || + LIST_ISEMPTY(&px->uri_auth->http_req_rules))) { + const char *uri_auth_compat_req[10]; + struct act_rule *rule; + i = 0; + + /* build the ACL condition from scratch. We're relying on anonymous ACLs for that */ + uri_auth_compat_req[i++] = "auth"; + + if (px->uri_auth->auth_realm) { + uri_auth_compat_req[i++] = "realm"; + uri_auth_compat_req[i++] = px->uri_auth->auth_realm; + } + + uri_auth_compat_req[i++] = "unless"; + uri_auth_compat_req[i++] = "{"; + uri_auth_compat_req[i++] = "http_auth(.internal-stats-userlist)"; + uri_auth_compat_req[i++] = "}"; + uri_auth_compat_req[i++] = ""; + + rule = parse_http_req_cond(uri_auth_compat_req, "internal-stats-auth-compat", 0, px); + if (!rule) { + cfgerr++; + goto out; + } + + LIST_APPEND(&px->uri_auth->http_req_rules, &rule->list); + + if (px->uri_auth->auth_realm) { + ha_free(&px->uri_auth->auth_realm); + } + px->uri_auth->flags |= STAT_F_CONVDONE; + } + out_uri_auth_compat: + + /* check whether we have a logger that uses RFC5424 log format */ + list_for_each_entry(tmplogger, &px->loggers, list) { + if (tmplogger->format == LOG_FORMAT_RFC5424) { + if (!px->logformat_sd.str) { + /* set the default logformat_sd_string */ + px->logformat_sd.str = default_rfc5424_sd_log_format; + } + break; + } + } + + /* compile the log format */ + if (!(px->cap & PR_CAP_FE)) { + lf_expr_deinit(&px->logformat); + lf_expr_deinit(&px->logformat_sd); + } + + if (px->logformat.str) { + px->conf.args.ctx = ARGC_LOG; + px->conf.args.file = px->logformat.conf.file; + px->conf.args.line = px->logformat.conf.line; + err = NULL; + if (!lf_expr_compile(&px->logformat, &px->conf.args, + LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES, + SMP_VAL_FE_LOG_END, &err) || + !lf_expr_postcheck(&px->logformat, px, &err)) { + ha_alert("Parsing [%s:%d]: failed to parse log-format : %s.\n", + px->logformat.conf.file, px->logformat.conf.line, err); + free(err); + cfgerr++; + } + px->conf.args.file = NULL; + px->conf.args.line = 0; + } + + if (px->logformat_sd.str) { + px->conf.args.ctx = ARGC_LOGSD; + px->conf.args.file = px->logformat_sd.conf.file; + px->conf.args.line = px->logformat_sd.conf.line; + err = NULL; + if (!lf_expr_compile(&px->logformat_sd, &px->conf.args, + LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES, + SMP_VAL_FE_LOG_END, &err) || + !add_to_logformat_list(NULL, NULL, LF_SEPARATOR, &px->logformat_sd, &err) || + !lf_expr_postcheck(&px->logformat_sd, px, &err)) { + ha_alert("Parsing [%s:%d]: failed to parse log-format-sd : %s.\n", + px->logformat_sd.conf.file, px->logformat_sd.conf.line, err); + free(err); + cfgerr++; + } + px->conf.args.file = NULL; + px->conf.args.line = 0; + } + + if (px->format_unique_id.str) { + int where = 0; + + px->conf.args.ctx = ARGC_UIF; + px->conf.args.file = px->format_unique_id.conf.file; + px->conf.args.line = px->format_unique_id.conf.line; + err = NULL; + if (px->cap & PR_CAP_FE) + where |= SMP_VAL_FE_HRQ_HDR; + if (px->cap & PR_CAP_BE) + where |= SMP_VAL_BE_HRQ_HDR; + if (!lf_expr_compile(&px->format_unique_id, &px->conf.args, + LOG_OPT_HTTP|LOG_OPT_MERGE_SPACES, where, &err) || + !lf_expr_postcheck(&px->format_unique_id, px, &err)) { + ha_alert("Parsing [%s:%d]: failed to parse unique-id : %s.\n", + px->format_unique_id.conf.file, px->format_unique_id.conf.line, err); + free(err); + cfgerr++; + } + px->conf.args.file = NULL; + px->conf.args.line = 0; + } + + if (px->logformat_error.str) { + px->conf.args.ctx = ARGC_LOG; + px->conf.args.file = px->logformat_error.conf.file; + px->conf.args.line = px->logformat_error.conf.line; + err = NULL; + if (!lf_expr_compile(&px->logformat_error, &px->conf.args, + LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES, + SMP_VAL_FE_LOG_END, &err) || + !lf_expr_postcheck(&px->logformat_error, px, &err)) { + ha_alert("Parsing [%s:%d]: failed to parse error-log-format : %s.\n", + px->logformat_error.conf.file, px->logformat_error.conf.line, err); + free(err); + cfgerr++; + } + px->conf.args.file = NULL; + px->conf.args.line = 0; + } + + /* "balance hash" needs to compile its expression + * (log backends will handle this in proxy log postcheck) + */ + if (px->mode != PR_MODE_SYSLOG && + (px->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_SMP) { + int idx = 0; + const char *args[] = { + px->lbprm.arg_str, + NULL, + }; + + err = NULL; + px->conf.args.ctx = ARGC_USRV; // same context as use_server. + px->lbprm.expr = + sample_parse_expr((char **)args, &idx, + px->conf.file, px->conf.line, + &err, &px->conf.args, NULL); + + if (!px->lbprm.expr) { + ha_alert("%s '%s' [%s:%d]: failed to parse 'balance hash' expression '%s' in : %s.\n", + proxy_type_str(px), px->id, + px->conf.file, px->conf.line, + px->lbprm.arg_str, err); + ha_free(&err); + cfgerr++; + } + else if (!(px->lbprm.expr->fetch->val & SMP_VAL_BE_SET_SRV)) { + ha_alert("%s '%s' [%s:%d]: error detected while parsing 'balance hash' expression '%s' " + "which requires information from %s, which is not available here.\n", + proxy_type_str(px), px->id, + px->conf.file, px->conf.line, + px->lbprm.arg_str, sample_src_names(px->lbprm.expr->fetch->use)); + cfgerr++; + } + else if (px->mode == PR_MODE_HTTP && (px->lbprm.expr->fetch->use & SMP_USE_L6REQ)) { + ha_warning("%s '%s' [%s:%d]: L6 sample fetch <%s> will be ignored in 'balance hash' expression in HTTP mode.\n", + proxy_type_str(px), px->id, + px->conf.file, px->conf.line, + px->lbprm.arg_str); + } + else + px->http_needed |= !!(px->lbprm.expr->fetch->use & SMP_USE_HTTP_ANY); + } + + /* only now we can check if some args remain unresolved. + * This must be done after the users and groups resolution. + */ + err = NULL; + i = smp_resolve_args(px, &err); + cfgerr += i; + if (i) { + indent_msg(&err, 8); + ha_alert("%s%s\n", i > 1 ? "multiple argument resolution errors:" : "", err); + ha_free(&err); + } else + cfgerr += acl_find_targets(px); + + if (!(px->cap & PR_CAP_INT) && (px->mode == PR_MODE_TCP || px->mode == PR_MODE_HTTP) && + (((px->cap & PR_CAP_FE) && !px->timeout.client) || + ((px->cap & PR_CAP_BE) && (px->srv) && + (!px->timeout.connect || + (!px->timeout.server && (px->mode == PR_MODE_HTTP || !px->timeout.tunnel)))))) { + ha_warning("missing timeouts for %s '%s'.\n" + " | While not properly invalid, you will certainly encounter various problems\n" + " | with such a configuration. To fix this, please ensure that all following\n" + " | timeouts are set to a non-zero value: 'client', 'connect', 'server'.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + /* Historically, the tarpit and queue timeouts were inherited from contimeout. + * We must still support older configurations, so let's find out whether those + * parameters have been set or must be copied from contimeouts. + */ + if (!px->timeout.tarpit) + px->timeout.tarpit = px->timeout.connect; + if ((px->cap & PR_CAP_BE) && !px->timeout.queue) + px->timeout.queue = px->timeout.connect; + + if ((px->tcpcheck_rules.flags & TCPCHK_RULES_UNUSED_TCP_RS)) { + ha_warning("%s '%s' uses tcp-check rules without 'option tcp-check', so the rules are ignored.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + /* ensure that cookie capture length is not too large */ + if (px->capture_len >= global.tune.cookie_len) { + ha_warning("truncating capture length to %d bytes for %s '%s'.\n", + global.tune.cookie_len - 1, proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + px->capture_len = global.tune.cookie_len - 1; + } + + /* The small pools required for the capture lists */ + if (px->nb_req_cap) { + px->req_cap_pool = create_pool("ptrcap", + px->nb_req_cap * sizeof(char *), + MEM_F_SHARED); + } + + if (px->nb_rsp_cap) { + px->rsp_cap_pool = create_pool("ptrcap", + px->nb_rsp_cap * sizeof(char *), + MEM_F_SHARED); + } + + switch (px->load_server_state_from_file) { + case PR_SRV_STATE_FILE_UNSPEC: + px->load_server_state_from_file = PR_SRV_STATE_FILE_NONE; + break; + case PR_SRV_STATE_FILE_GLOBAL: + if (!global.server_state_file) { + ha_warning("backend '%s' configured to load server state file from global section 'server-state-file' directive. Unfortunately, 'server-state-file' is not set!\n", + px->id); + *err_code |= ERR_WARN; + } + break; + } + + /* first, we will invert the servers list order */ + newsrv = NULL; + while (px->srv) { + struct server *next; + + next = px->srv->next; + px->srv->next = newsrv; + newsrv = px->srv; + if (!next) + break; + px->srv = next; + } + + /* Check that no server name conflicts. This causes trouble in the stats. + * We only emit an error for the first conflict affecting each server, + * in order to avoid combinatory explosion if all servers have the same + * name. Since servers names are stored in a tree before landing here, + * we simply have to check for the current server's duplicates to spot + * conflicts. + */ + for (newsrv = px->srv; newsrv; newsrv = newsrv->next) { + struct server *other_srv; + + /* Note: internal servers are not always registered and + * they do not conflict. + */ + if (!ceb_intree(&newsrv->conf.name_node)) + continue; + + for (other_srv = newsrv; + (other_srv = cebis_item_prev_dup(&px->conf.used_server_name, conf.name_node, id, other_srv)); ) { + ha_alert("parsing [%s:%d] : %s '%s', another server named '%s' was already defined at line %d, please use distinct names.\n", + newsrv->conf.file, newsrv->conf.line, + proxy_type_str(px), px->id, + newsrv->id, other_srv->conf.line); + cfgerr++; + break; + } + } + + /* assign automatic UIDs to servers which don't have one yet */ + next_id = 1; + newsrv = px->srv; + while (newsrv != NULL) { + if (!newsrv->puid) { + /* server ID not set, use automatic numbering with first + * spare entry starting with next_svid. + */ + next_id = server_get_next_id(px, next_id); + newsrv->puid = next_id; + server_index_id(px, newsrv); + } + + next_id++; + newsrv = newsrv->next; + } + + px->lbprm.wmult = 1; /* default weight multiplier */ + px->lbprm.wdiv = 1; /* default weight divider */ + + /* + * If this server supports a maxconn parameter, it needs a dedicated + * tasks to fill the emptied slots when a connection leaves. + * Also, resolve deferred tracking dependency if needed. + */ + newsrv = px->srv; + while (newsrv != NULL) { + set_usermsgs_ctx(newsrv->conf.file, newsrv->conf.line, &newsrv->obj_type); + + srv_minmax_conn_apply(newsrv); + + /* this will also properly set the transport layer for + * prod and checks + * if default-server have use_ssl, prerare ssl init + * without activating it */ + if (newsrv->use_ssl == 1 || newsrv->check.use_ssl == 1 || + (newsrv->proxy->options & PR_O_TCPCHK_SSL) || + ((newsrv->flags & SRV_F_DEFSRV_USE_SSL) && newsrv->use_ssl != 1)) { + if (xprt_get(XPRT_SSL) && xprt_get(XPRT_SSL)->prepare_srv) + cfgerr += xprt_get(XPRT_SSL)->prepare_srv(newsrv); + else if (xprt_get(XPRT_QUIC) && xprt_get(XPRT_QUIC)->prepare_srv) + cfgerr += xprt_get(XPRT_QUIC)->prepare_srv(newsrv); + } + + if (newsrv->use_ssl == 1 || ((newsrv->flags & SRV_F_DEFSRV_USE_SSL) && newsrv->use_ssl != 1)) { + /* In HTTP only, if the SNI is not set and we can rely on the host + * header value, fill the sni expression accordingly + */ + if (!newsrv->sni_expr && newsrv->proxy->mode == PR_MODE_HTTP && + !(newsrv->ssl_ctx.options & SRV_SSL_O_NO_AUTO_SNI)) { + newsrv->sni_expr = strdup("req.hdr(host),field(1,:)"); + + err = NULL; + if (server_parse_exprs(newsrv, px, &err)) { + ha_alert("parsing [%s:%d]: failed to parse auto SNI expression: %s\n", + newsrv->conf.file, newsrv->conf.line, err); + free(err); + ++cfgerr; + goto next_srv; + } + } + } + + + if ((newsrv->flags & SRV_F_FASTOPEN) && + ((px->retry_type & (PR_RE_DISCONNECTED | PR_RE_TIMEOUT)) != + (PR_RE_DISCONNECTED | PR_RE_TIMEOUT))) + ha_warning("server has tfo activated, the backend should be configured with at least 'conn-failure', 'empty-response' and 'response-timeout' or we wouldn't be able to retry the connection on failure.\n"); + + if (newsrv->trackit) { + if (srv_apply_track(newsrv, px)) { + ++cfgerr; + goto next_srv; + } + } + + next_srv: + reset_usermsgs_ctx(); + newsrv = newsrv->next; + } + + /* + * Try to generate dynamic cookies for servers now. + * It couldn't be done earlier, since at the time we parsed + * the server line, we may not have known yet that we + * should use dynamic cookies, or the secret key may not + * have been provided yet. + */ + if (px->ck_opts & PR_CK_DYNAMIC) { + newsrv = px->srv; + while (newsrv != NULL) { + srv_set_dyncookie(newsrv); + newsrv = newsrv->next; + } + + } + /* We have to initialize the server lookup mechanism depending + * on what LB algorithm was chosen. + */ + + px->lbprm.algo &= ~(BE_LB_LKUP | BE_LB_PROP_DYN); + switch (px->lbprm.algo & BE_LB_KIND) { + case BE_LB_KIND_RR: + if ((px->lbprm.algo & BE_LB_PARM) == BE_LB_RR_STATIC) { + px->lbprm.algo |= BE_LB_LKUP_MAP; + init_server_map(px); + } else if ((px->lbprm.algo & BE_LB_PARM) == BE_LB_RR_RANDOM) { + px->lbprm.algo |= BE_LB_LKUP_CHTREE | BE_LB_PROP_DYN; + if (chash_init_server_tree(px) < 0) { + cfgerr++; + } + } else { + px->lbprm.algo |= BE_LB_LKUP_RRTREE | BE_LB_PROP_DYN; + fwrr_init_server_groups(px); + } + break; + + case BE_LB_KIND_CB: + if ((px->lbprm.algo & BE_LB_PARM) == BE_LB_CB_LC) { + px->lbprm.algo |= BE_LB_LKUP_LCTREE | BE_LB_PROP_DYN; + fwlc_init_server_tree(px); + } else { + px->lbprm.algo |= BE_LB_LKUP_FSTREE | BE_LB_PROP_DYN; + fas_init_server_tree(px); + } + break; + + case BE_LB_KIND_HI: + if ((px->lbprm.algo & BE_LB_HASH_TYPE) == BE_LB_HASH_CONS) { + px->lbprm.algo |= BE_LB_LKUP_CHTREE | BE_LB_PROP_DYN; + if (chash_init_server_tree(px) < 0) { + cfgerr++; + } + } else { + px->lbprm.algo |= BE_LB_LKUP_MAP; + init_server_map(px); + } + break; + case BE_LB_KIND_SA: + if ((px->lbprm.algo & BE_LB_PARM) == BE_LB_SA_SS) { + px->lbprm.algo |= BE_LB_PROP_DYN; + init_server_ss(px); + } + break; + } + HA_RWLOCK_INIT(&px->lbprm.lock); + + if (px->options & PR_O_LOGASAP) + px->to_log &= ~LW_BYTES; + + if (!(px->cap & PR_CAP_INT) && (px->mode == PR_MODE_TCP || px->mode == PR_MODE_HTTP) && + (px->cap & PR_CAP_FE) && LIST_ISEMPTY(&px->loggers) && + (!lf_expr_isempty(&px->logformat) || !lf_expr_isempty(&px->logformat_sd))) { + ha_warning("log format ignored for %s '%s' since it has no log address.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + if (px->mode != PR_MODE_HTTP && !(px->options & PR_O_HTTP_UPG)) { + int optnum; + + if (px->uri_auth) { + ha_warning("'stats' statement ignored for %s '%s' as it requires HTTP mode.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + stats_uri_auth_drop(px->uri_auth); + px->uri_auth = NULL; + } + + if (px->capture_name) { + ha_warning("'capture' statement ignored for %s '%s' as it requires HTTP mode.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + if (isttest(px->monitor_uri)) { + ha_warning("'monitor-uri' statement ignored for %s '%s' as it requires HTTP mode.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + if (!LIST_ISEMPTY(&px->http_req_rules)) { + ha_warning("'http-request' rules ignored for %s '%s' as they require HTTP mode.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + if (!LIST_ISEMPTY(&px->http_res_rules)) { + ha_warning("'http-response' rules ignored for %s '%s' as they require HTTP mode.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + if (!LIST_ISEMPTY(&px->http_after_res_rules)) { + ha_warning("'http-after-response' rules ignored for %s '%s' as they require HTTP mode.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + if (!LIST_ISEMPTY(&px->redirect_rules)) { + ha_warning("'redirect' rules ignored for %s '%s' as they require HTTP mode.\n", + proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + } + + for (optnum = 0; cfg_opts[optnum].name; optnum++) { + if (cfg_opts[optnum].mode == PR_MODE_HTTP && + (px->cap & cfg_opts[optnum].cap) && + (px->options & cfg_opts[optnum].val)) { + ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n", + cfg_opts[optnum].name, proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + px->options &= ~cfg_opts[optnum].val; + } + } + + for (optnum = 0; cfg_opts2[optnum].name; optnum++) { + if (cfg_opts2[optnum].mode == PR_MODE_HTTP && + (px->cap & cfg_opts2[optnum].cap) && + (px->options2 & cfg_opts2[optnum].val)) { + ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n", + cfg_opts2[optnum].name, proxy_type_str(px), px->id); + *err_code |= ERR_WARN; + px->options2 &= ~cfg_opts2[optnum].val; + } + } + +#if defined(CONFIG_HAP_TRANSPARENT) + if (px->conn_src.bind_hdr_occ) { + px->conn_src.bind_hdr_occ = 0; + ha_warning("%s '%s' : ignoring use of header %s as source IP in non-HTTP mode.\n", + proxy_type_str(px), px->id, px->conn_src.bind_hdr_name); + *err_code |= ERR_WARN; + } +#endif /* CONFIG_HAP_TRANSPARENT */ + } + + /* + * ensure that we're not cross-dressing a TCP server into HTTP. + */ + newsrv = px->srv; + while (newsrv != NULL) { + if ((px->mode != PR_MODE_HTTP) && newsrv->rdr_len) { + ha_alert("%s '%s' : server cannot have cookie or redirect prefix in non-HTTP mode.\n", + proxy_type_str(px), px->id); + cfgerr++; + } + + if ((px->mode != PR_MODE_HTTP) && newsrv->cklen) { + ha_warning("%s '%s' : ignoring cookie for server '%s' as HTTP mode is disabled.\n", + proxy_type_str(px), px->id, newsrv->id); + *err_code |= ERR_WARN; + } + + if ((newsrv->flags & SRV_F_MAPPORTS) && (px->options2 & PR_O2_RDPC_PRST)) { + ha_warning("%s '%s' : RDP cookie persistence will not work for server '%s' because it lacks an explicit port number.\n", + proxy_type_str(px), px->id, newsrv->id); + *err_code |= ERR_WARN; + } + +#if defined(CONFIG_HAP_TRANSPARENT) + if (px->mode != PR_MODE_HTTP && newsrv->conn_src.bind_hdr_occ) { + newsrv->conn_src.bind_hdr_occ = 0; + ha_warning("%s '%s' : server %s cannot use header %s as source IP in non-HTTP mode.\n", + proxy_type_str(px), px->id, newsrv->id, newsrv->conn_src.bind_hdr_name); + *err_code |= ERR_WARN; + } +#endif /* CONFIG_HAP_TRANSPARENT */ + + if ((px->mode != PR_MODE_HTTP) && (px->options & PR_O_REUSE_MASK) != PR_O_REUSE_NEVR) + px->options &= ~PR_O_REUSE_MASK; + if (px->mode == PR_MODE_SPOP) + px->options |= PR_O_REUSE_ALWS; + + if ((px->mode != PR_MODE_HTTP) && newsrv->flags & SRV_F_RHTTP) { + ha_alert("%s '%s' : server %s uses reverse HTTP addressing which can only be used with HTTP mode.\n", + proxy_type_str(px), px->id, newsrv->id); + cfgerr++; + *err_code |= ERR_FATAL | ERR_ALERT; + goto out; + } + + newsrv = newsrv->next; + } + + /* Check filter configuration, if any */ + cfgerr += flt_check(px); + + if (px->cap & PR_CAP_FE) { + if (!px->accept) + px->accept = frontend_accept; + + if (!LIST_ISEMPTY(&px->tcp_req.inspect_rules) || + (px->defpx && !LIST_ISEMPTY(&px->defpx->tcp_req.inspect_rules))) + px->fe_req_ana |= AN_REQ_INSPECT_FE; + + if (px->mode == PR_MODE_HTTP) { + px->fe_req_ana |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_PROCESS_FE; + px->fe_rsp_ana |= AN_RES_WAIT_HTTP | AN_RES_HTTP_PROCESS_FE; + } + + if (px->mode == PR_MODE_CLI) { + px->fe_req_ana |= AN_REQ_WAIT_CLI; + px->fe_rsp_ana |= AN_RES_WAIT_CLI; + } + + /* both TCP and HTTP must check switching rules */ + px->fe_req_ana |= AN_REQ_SWITCHING_RULES; + + /* Add filters analyzers if needed */ + if (!LIST_ISEMPTY(&px->filter_configs)) { + px->fe_req_ana |= AN_REQ_FLT_START_FE | AN_REQ_FLT_XFER_DATA | AN_REQ_FLT_END; + px->fe_rsp_ana |= AN_RES_FLT_START_FE | AN_RES_FLT_XFER_DATA | AN_RES_FLT_END; + } + } + + if (px->cap & PR_CAP_BE) { + if (!LIST_ISEMPTY(&px->tcp_req.inspect_rules) || + (px->defpx && !LIST_ISEMPTY(&px->defpx->tcp_req.inspect_rules))) + px->be_req_ana |= AN_REQ_INSPECT_BE; + + if (!LIST_ISEMPTY(&px->tcp_rep.inspect_rules) || + (px->defpx && !LIST_ISEMPTY(&px->defpx->tcp_rep.inspect_rules))) + px->be_rsp_ana |= AN_RES_INSPECT; + + if (px->mode == PR_MODE_HTTP) { + px->be_req_ana |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_INNER | AN_REQ_HTTP_PROCESS_BE; + px->be_rsp_ana |= AN_RES_WAIT_HTTP | AN_RES_HTTP_PROCESS_BE; + } + + /* If the backend does requires RDP cookie persistence, we have to + * enable the corresponding analyser. + */ + if (px->options2 & PR_O2_RDPC_PRST) + px->be_req_ana |= AN_REQ_PRST_RDP_COOKIE; + + /* Add filters analyzers if needed */ + if (!LIST_ISEMPTY(&px->filter_configs)) { + px->be_req_ana |= AN_REQ_FLT_START_BE | AN_REQ_FLT_XFER_DATA | AN_REQ_FLT_END; + px->be_rsp_ana |= AN_RES_FLT_START_BE | AN_RES_FLT_XFER_DATA | AN_RES_FLT_END; + } + } + + /* Check the mux protocols, if any, for each server attached to + * the current proxy */ + for (newsrv = px->srv; newsrv; newsrv = newsrv->next) { + int mode = conn_pr_mode_to_proto_mode(px->mode); + const struct mux_proto_list *mux_ent; + + if (srv_is_quic(newsrv)) { + if (!newsrv->mux_proto) { + /* Force QUIC as mux-proto on server with quic addresses, similarly to bind on FE side. */ + newsrv->mux_proto = get_mux_proto(ist("quic")); + } + } + + if (!newsrv->mux_proto) + continue; + + /* it is possible that an incorrect mux was referenced + * due to the proxy's mode not being taken into account + * on first pass. Let's adjust it now. + */ + mux_ent = conn_get_best_mux_entry(newsrv->mux_proto->token, PROTO_SIDE_BE, mode); + + if (!mux_ent || !isteq(mux_ent->token, newsrv->mux_proto->token)) { + ha_alert("%s '%s' : MUX protocol '%.*s' is not usable for server '%s' at [%s:%d].\n", + proxy_type_str(px), px->id, + (int)newsrv->mux_proto->token.len, + newsrv->mux_proto->token.ptr, + newsrv->id, newsrv->conf.file, newsrv->conf.line); + cfgerr++; + } + else { + if ((mux_ent->mux->flags & MX_FL_FRAMED) && !srv_is_quic(newsrv)) { + ha_alert("%s '%s' : MUX protocol '%.*s' is incompatible with stream transport used by server '%s' at [%s:%d].\n", + proxy_type_str(px), px->id, + (int)newsrv->mux_proto->token.len, + newsrv->mux_proto->token.ptr, + newsrv->id, newsrv->conf.file, newsrv->conf.line); + cfgerr++; + } + else if (!(mux_ent->mux->flags & MX_FL_FRAMED) && srv_is_quic(newsrv)) { + ha_alert("%s '%s' : MUX protocol '%.*s' is incompatible with framed transport used by server '%s' at [%s:%d].\n", + proxy_type_str(px), px->id, + (int)newsrv->mux_proto->token.len, + newsrv->mux_proto->token.ptr, + newsrv->id, newsrv->conf.file, newsrv->conf.line); + cfgerr++; + } + } + + /* update the mux */ + newsrv->mux_proto = mux_ent; + } + + /* Allocate default tcp-check rules for proxies without + * explicit rules. + */ + if (px->cap & PR_CAP_BE) { + if (!(px->options2 & PR_O2_CHK_ANY)) { + struct tcpcheck_ruleset *rs = NULL; + struct tcpcheck_rules *rules = &px->tcpcheck_rules; + + px->options2 |= PR_O2_TCPCHK_CHK; + + rs = find_tcpcheck_ruleset("*tcp-check"); + if (!rs) { + rs = create_tcpcheck_ruleset("*tcp-check"); + if (rs == NULL) { + ha_alert("config: %s '%s': out of memory.\n", + proxy_type_str(px), px->id); + cfgerr++; + } + } + + free_tcpcheck_vars(&rules->preset_vars); + rules->list = &rs->rules; + rules->flags = 0; + } + } + + out: + if (cfgerr) + *err_code |= ERR_ALERT | ERR_FATAL; + + return cfgerr; +} + /* Frees all dynamic settings allocated on a default proxy that's about to be * destroyed. Note that most of the fields are not even reset, so extreme care * is required here. From 87ea407cce6a9993609d808fe0edd1466cae924a Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Mon, 22 Dec 2025 11:59:33 +0100 Subject: [PATCH 03/14] MINOR: proxy: refactor proxy inheritance of a defaults section If a proxy is referencing a defaults instance, some checks must be performed to ensure that inheritance will be compatible. Refcount of the defaults instance may also be incremented if some settings cannot be copied. This operation is performed when parsing a new proxy of defaults section which references a defaults, either implicitely or explicitely. This patch extracts this code into a dedicated function named proxy_ref_defaults(). This in turn may call defaults_px_ref() (previously called proxy_ref_defaults()) to increment its refcount. The objective of this patch is to be able to reuse defaults inheritance validation for dynamic backends created at runtime, outside of the parsing code. --- include/haproxy/proxy.h | 3 +- src/cfgparse-listen.c | 82 ++------------------------------ src/proxy.c | 102 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 105 insertions(+), 82 deletions(-) diff --git a/include/haproxy/proxy.h b/include/haproxy/proxy.h index afa6297c3bc6c..089ab9e222dfe 100644 --- a/include/haproxy/proxy.h +++ b/include/haproxy/proxy.h @@ -74,8 +74,7 @@ void defaults_px_destroy_all_unref(void); void defaults_px_detach(struct proxy *px); void defaults_px_ref_all(void); void defaults_px_unref_all(void); - -void proxy_ref_defaults(struct proxy *px, struct proxy *defpx); +int proxy_ref_defaults(struct proxy *px, struct proxy *defpx, char **errmsg); void proxy_unref_defaults(struct proxy *px); int setup_new_proxy(struct proxy *px, const char *name, unsigned int cap, char **errmsg); struct proxy *alloc_new_proxy(const char *name, unsigned int cap, diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index bcb085936bf36..68b2eba88ba56 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -501,84 +501,10 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) curproxy->conf.file_prev = file_prev; curproxy->conf.line_prev = line_prev; - if (curr_defproxy && (!LIST_ISEMPTY(&curr_defproxy->http_req_rules) || - !LIST_ISEMPTY(&curr_defproxy->http_res_rules) || - !LIST_ISEMPTY(&curr_defproxy->http_after_res_rules) || - !LIST_ISEMPTY(&curr_defproxy->tcp_req.l4_rules) || - !LIST_ISEMPTY(&curr_defproxy->tcp_req.l5_rules) || - !LIST_ISEMPTY(&curr_defproxy->tcp_req.inspect_rules) || - !LIST_ISEMPTY(&curr_defproxy->tcp_rep.inspect_rules))) { - /* If the current default proxy defines TCP/HTTP rules, the - * current proxy will keep a reference on it. But some sanity - * checks are performed first: - * - * - It cannot be used to init a defaults section - * - It cannot be used to init a listen section - * - It cannot be used to init backend and frontend sections at - * same time. It can be used to init several sections of the - * same type only. - * - It cannot define L4/L5 TCP rules if it is used to init - * backend sections. - * - It cannot define 'tcp-response content' rules if it - * is used to init frontend sections. - * - * If no error is found, refcount of the default proxy is incremented. - */ - - /* Note: Add tcpcheck_rules too if unresolve args become allowed in defaults section */ - if (rc & PR_CAP_DEF) { - ha_alert("parsing [%s:%d]: a defaults section cannot inherit from a defaults section defining TCP/HTTP rules (defaults section at %s:%d).\n", - file, linenum, curr_defproxy->conf.file, curr_defproxy->conf.line); - err_code |= ERR_ALERT | ERR_ABORT; - } - else if ((rc & PR_CAP_LISTEN) == PR_CAP_LISTEN) { - ha_alert("parsing [%s:%d]: a listen section cannot inherit from a defaults section defining TCP/HTTP rules.\n", - file, linenum); - err_code |= ERR_ALERT | ERR_ABORT; - } - else { - char defcap = (curr_defproxy->cap & PR_CAP_LISTEN); - - if ((defcap == PR_CAP_BE || defcap == PR_CAP_FE) && (rc & PR_CAP_LISTEN) != defcap) { - ha_alert("parsing [%s:%d]: frontends and backends cannot inherit from the same defaults section" - " if it defines TCP/HTTP rules (defaults section at %s:%d).\n", - file, linenum, curr_defproxy->conf.file, curr_defproxy->conf.line); - err_code |= ERR_ALERT | ERR_ABORT; - } - else if (!(rc & PR_CAP_FE) && (!LIST_ISEMPTY(&curr_defproxy->tcp_req.l4_rules) || - !LIST_ISEMPTY(&curr_defproxy->tcp_req.l5_rules))) { - ha_alert("parsing [%s:%d]: a backend section cannot inherit from a defaults section defining" - " 'tcp-request connection' or 'tcp-request session' rules (defaults section at %s:%d).\n", - file, linenum, curr_defproxy->conf.file, curr_defproxy->conf.line); - err_code |= ERR_ALERT | ERR_ABORT; - } - else if (!(rc & PR_CAP_BE) && !LIST_ISEMPTY(&curr_defproxy->tcp_rep.inspect_rules)) { - ha_alert("parsing [%s:%d]: a frontend section cannot inherit from a defaults section defining" - " 'tcp-response content' rules (defaults section at %s:%d).\n", - file, linenum, curr_defproxy->conf.file, curr_defproxy->conf.line); - err_code |= ERR_ALERT | ERR_ABORT; - } - else { - curr_defproxy->cap = (curr_defproxy->cap & ~PR_CAP_LISTEN) | (rc & PR_CAP_LISTEN); - proxy_ref_defaults(curproxy, curr_defproxy); - } - } - } - - if (curr_defproxy && (curr_defproxy->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) && - (curproxy->cap & PR_CAP_LISTEN) == PR_CAP_BE) { - /* If the current default proxy defines tcpcheck rules, the - * current proxy will keep a reference on it. but only if the - * current proxy has the backend capability. - */ - proxy_ref_defaults(curproxy, curr_defproxy); - } - - if ((rc & PR_CAP_BE) && curr_defproxy && (curr_defproxy->nb_req_cap || curr_defproxy->nb_rsp_cap)) { - ha_alert("parsing [%s:%d]: backend or defaults sections cannot inherit from a defaults section defining" - " capptures (defaults section at %s:%d).\n", - file, linenum, curr_defproxy->conf.file, curr_defproxy->conf.line); - err_code |= ERR_ALERT | ERR_ABORT; + if (curr_defproxy) { + err_code = proxy_ref_defaults(curproxy, curr_defproxy, &errmsg); + if (err_code) + ha_alert("parsing [%s:%d]: %s.\n", file, linenum, errmsg); } if (rc & PR_CAP_DEF) { diff --git a/src/proxy.c b/src/proxy.c index 7f573659e1795..fc65f3c0668f8 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -3010,15 +3010,113 @@ void defaults_px_unref_all(void) * refcount is incremented by one. For now, this operation is not thread safe * and is perform during init stage only. */ -void proxy_ref_defaults(struct proxy *px, struct proxy *defpx) +static inline void defaults_px_ref(struct proxy *defpx, struct proxy *px) { if (px->defpx == defpx) return; - BUG_ON(px->defpx != NULL); + /* is already referencing another defaults. */ + BUG_ON(px->defpx); + px->defpx = defpx; defpx->conf.refcount++; } +/* Check that can inherits from default proxy. If some settings + * cannot be copied, refcount of the defaults instance is incremented. + * Inheritance may be impossible due to incompatibility issues. In this case, + * will be allocated to point to a textual description of the error. + * + * Returns ERR_NONE on success and a combination of ERR_CODE on failure + */ +int proxy_ref_defaults(struct proxy *px, struct proxy *defpx, char **errmsg) +{ + char defcap = defpx->cap & PR_CAP_LISTEN; + int err_code = ERR_NONE; + + if ((px->cap & PR_CAP_BE) && (defpx->nb_req_cap || defpx->nb_rsp_cap)) { + memprintf(errmsg, "backend or defaults sections cannot inherit from a defaults section defining" + " captures (defaults section at %s:%d)", + defpx->conf.file, defpx->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + /* If the current default proxy defines TCP/HTTP rules, the + * current proxy will keep a reference on it. But some sanity + * checks are performed first: + * + * - It cannot be used to init a defaults section + * - It cannot be used to init a listen section + * - It cannot be used to init backend and frontend sections at + * same time. It can be used to init several sections of the + * same type only. + * - It cannot define L4/L5 TCP rules if it is used to init + * backend sections. + * - It cannot define 'tcp-response content' rules if it + * is used to init frontend sections. + * + * If no error is found, refcount of the default proxy is incremented. + */ + if ((!LIST_ISEMPTY(&defpx->http_req_rules) || + !LIST_ISEMPTY(&defpx->http_res_rules) || + !LIST_ISEMPTY(&defpx->http_after_res_rules) || + !LIST_ISEMPTY(&defpx->tcp_req.l4_rules) || + !LIST_ISEMPTY(&defpx->tcp_req.l5_rules) || + !LIST_ISEMPTY(&defpx->tcp_req.inspect_rules) || + !LIST_ISEMPTY(&defpx->tcp_rep.inspect_rules))) { + + /* Note: Add tcpcheck_rules too if unresolve args become allowed in defaults section */ + if (px->cap & PR_CAP_DEF) { + memprintf(errmsg, "a defaults section cannot inherit from a defaults section defining TCP/HTTP rules (defaults section at %s:%d)", + defpx->conf.file, defpx->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + else if ((px->cap & PR_CAP_LISTEN) == PR_CAP_LISTEN) { + memprintf(errmsg, "a listen section cannot inherit from a defaults section defining TCP/HTTP rules"); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + else if ((defcap == PR_CAP_BE || defcap == PR_CAP_FE) && (px->cap & PR_CAP_LISTEN) != defcap) { + memprintf(errmsg, "frontends and backends cannot inherit from the same defaults section" + " if it defines TCP/HTTP rules (defaults section at %s:%d)", + defpx->conf.file, defpx->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + else if (!(px->cap & PR_CAP_FE) && (!LIST_ISEMPTY(&defpx->tcp_req.l4_rules) || + !LIST_ISEMPTY(&defpx->tcp_req.l5_rules))) { + memprintf(errmsg, "a backend section cannot inherit from a defaults section defining" + " 'tcp-request connection' or 'tcp-request session' rules (defaults section at %s:%d)", + defpx->conf.file, defpx->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + else if (!(px->cap & PR_CAP_BE) && !LIST_ISEMPTY(&defpx->tcp_rep.inspect_rules)) { + memprintf(errmsg, "a frontend section cannot inherit from a defaults section defining" + " 'tcp-response content' rules (defaults section at %s:%d)", + defpx->conf.file, defpx->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + defpx->cap = (defpx->cap & ~PR_CAP_LISTEN) | (px->cap & PR_CAP_LISTEN); + defaults_px_ref(defpx, px); + } + + if ((defpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) && + (px->cap & PR_CAP_LISTEN) == PR_CAP_BE) { + /* If the current default proxy defines tcpcheck rules, the + * current proxy will keep a reference on it. but only if the + * current proxy has the backend capability. + */ + defaults_px_ref(defpx, px); + } + + out: + return err_code; +} + /* proxy removes its reference on its default proxy. The default proxy * refcount is decremented by one. If it was the last reference, the * corresponding default proxy is destroyed. For now this operation is not From dc6cf224dde9dc2de9d693bff968a478c216ad7f Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Mon, 12 Jan 2026 14:47:51 +0100 Subject: [PATCH 04/14] MINOR: proxy: refactor mode parsing Define a new utility function str_to_proxy_mode() which is able to convert a string into the corresponding proxy mode if possible. This new function is used for the parsing of "mode" configuration proxy keyword. This patch will be reused for dynamic backend implementation, in order to parse a similar "mode" argument via a CLI handler. --- include/haproxy/proxy.h | 1 + src/cfgparse-listen.c | 19 +++++++++++++------ src/proxy.c | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/include/haproxy/proxy.h b/include/haproxy/proxy.h index 089ab9e222dfe..57dbbd9ff727e 100644 --- a/include/haproxy/proxy.h +++ b/include/haproxy/proxy.h @@ -59,6 +59,7 @@ void deinit_proxy(struct proxy *p); void free_proxy(struct proxy *p); const char *proxy_cap_str(int cap); const char *proxy_mode_str(int mode); +enum pr_mode str_to_proxy_mode(const char *mode); const char *proxy_find_best_option(const char *word, const char **extra); uint proxy_get_next_id(uint from); void proxy_store_name(struct proxy *px); diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index 68b2eba88ba56..3c27a638e620f 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -633,23 +633,30 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) goto out; } else if (strcmp(args[0], "mode") == 0) { /* sets the proxy mode */ + enum pr_mode mode; if (alertif_too_many_args(1, file, linenum, args, &err_code)) goto out; - if (strcmp(args[1], "http") == 0) curproxy->mode = PR_MODE_HTTP; - else if (strcmp(args[1], "tcp") == 0) curproxy->mode = PR_MODE_TCP; - else if (strcmp(args[1], "log") == 0 && (curproxy->cap & PR_CAP_BE)) curproxy->mode = PR_MODE_SYSLOG; - else if (strcmp(args[1], "spop") == 0 && (curproxy->cap & PR_CAP_BE)) curproxy->mode = PR_MODE_SPOP; - else if (strcmp(args[1], "health") == 0) { + if (unlikely(strcmp(args[1], "health") == 0)) { ha_alert("parsing [%s:%d] : 'mode health' doesn't exist anymore. Please use 'http-request return status 200' instead.\n", file, linenum); err_code |= ERR_ALERT | ERR_FATAL; goto out; } - else { + + mode = str_to_proxy_mode(args[1]); + if (!mode) { ha_alert("parsing [%s:%d] : unknown proxy mode '%s'.\n", file, linenum, args[1]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } + else if ((mode == PR_MODE_SYSLOG || mode == PR_MODE_SPOP) && + !(curproxy->cap & PR_CAP_BE)) { + ha_alert("parsing [%s:%d] : mode %s is only applicable on proxies with backend capability.\n", file, linenum, proxy_mode_str(mode)); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + curproxy->mode = mode; } else if (strcmp(args[0], "id") == 0) { struct proxy *conflict; diff --git a/src/proxy.c b/src/proxy.c index fc65f3c0668f8..207f85a98d744 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -501,6 +501,21 @@ const char *proxy_mode_str(int mode) { return "unknown"; } +/* Convert string into proxy mode type. PR_MODES is returned for unknown values. */ +enum pr_mode str_to_proxy_mode(const char *mode) +{ + if (strcmp(mode, "http") == 0) + return PR_MODE_HTTP; + else if (strcmp(mode, "tcp") == 0) + return PR_MODE_TCP; + else if (strcmp(mode, "log") == 0) + return PR_MODE_SYSLOG; + else if (strcmp(mode, "spop") == 0) + return PR_MODE_SPOP; + + return PR_MODES; +} + /* try to find among known options the one that looks closest to by * counting transitions between letters, digits and other characters. Will * return the best matching word if found, otherwise NULL. An optional array From 817003aa310e918c64082655bf268edb399faf59 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Fri, 6 Feb 2026 07:40:59 +0100 Subject: [PATCH 05/14] MINOR: backend: add function to check support for dynamic servers Move backend compatibility checks performed during 'add server' in a dedicated function be_supports_dynamic_srv(). This should simplify addition of future restriction. This function will be reused when implementing backend creation at runtime. --- include/haproxy/backend.h | 1 + reg-tests/server/cli_add_server.vtc | 2 +- src/backend.c | 22 ++++++++++++++++++++++ src/server.c | 12 ++++-------- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/include/haproxy/backend.h b/include/haproxy/backend.h index 12027073076d6..6930ff256d3f5 100644 --- a/include/haproxy/backend.h +++ b/include/haproxy/backend.h @@ -69,6 +69,7 @@ int backend_parse_balance(const char **args, char **err, struct proxy *curproxy) int tcp_persist_rdp_cookie(struct stream *s, struct channel *req, int an_bit); int be_downtime(struct proxy *px); +int be_supports_dynamic_srv(struct proxy *px, char **msg); void recount_servers(struct proxy *px); void update_backend_weight(struct proxy *px); diff --git a/reg-tests/server/cli_add_server.vtc b/reg-tests/server/cli_add_server.vtc index 8081312ba901f..93331a2bf7416 100644 --- a/reg-tests/server/cli_add_server.vtc +++ b/reg-tests/server/cli_add_server.vtc @@ -51,7 +51,7 @@ haproxy h1 -cli { # invalid load-balancing algo send "add server other/s1 ${s1_addr}:${s1_port}" - expect ~ "Backend must use a dynamic load balancing to support dynamic servers." + expect ~ "backend 'other' uses a non dynamic load balancing method" # invalid mux proto send "add server other2/s1 ${s1_addr}:${s1_port} proto h2" diff --git a/src/backend.c b/src/backend.c index 73b39306b9bac..b531843fc2761 100644 --- a/src/backend.c +++ b/src/backend.c @@ -59,6 +59,7 @@ #include #include #include +#include #include #define TRACE_SOURCE &trace_strm @@ -3055,6 +3056,27 @@ int be_downtime(struct proxy *px) { return ns_to_sec(now_ns) - px->last_change + px->down_time; } +/* Checks if backend supports the addition of servers at runtime. Either a + * backend or a defaults proxy are supported. If proxy is incompatible, + * will be allocated to contain a textual explaination. + */ +int be_supports_dynamic_srv(struct proxy *px, char **msg) +{ + if (px->lbprm.algo && !(px->lbprm.algo & BE_LB_PROP_DYN)) { + memprintf(msg, "%s '%s' uses a non dynamic load balancing method", + proxy_cap_str(px->cap), px->id); + return 0; + } + + if (px->mode == PR_MODE_SYSLOG) { + memprintf(msg, "%s '%s' uses mode log", + proxy_cap_str(px->cap), px->id); + return 0; + } + + return 1; +} + /* * This function returns a string containing the balancing * mode of the proxy in a format suitable for stats. diff --git a/src/server.c b/src/server.c index d1bb43825cd7a..a475c881bbdb2 100644 --- a/src/server.c +++ b/src/server.c @@ -6104,7 +6104,7 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct struct add_srv_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); struct proxy *be; struct server *srv; - char *be_name, *sv_name; + char *be_name, *sv_name, *errmsg; int errcode, argc; int next_id; const int parse_flags = SRV_PARSE_DYNAMIC|SRV_PARSE_PARSE_ADDR; @@ -6140,13 +6140,9 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct if (!be) return cli_err(appctx, "No such backend.\n"); - if (!(be->lbprm.algo & BE_LB_PROP_DYN)) { - cli_err(appctx, "Backend must use a dynamic load balancing to support dynamic servers.\n"); - return 1; - } - - if (be->mode == PR_MODE_SYSLOG) { - cli_err(appctx," Dynamic servers cannot be used with log backends.\n"); + errmsg = NULL; + if (!be_supports_dynamic_srv(be, &errmsg)) { + cli_dynerr(appctx, memprintf(&errmsg, "Backend does not support dynamic servers : %s.\n", errmsg)); return 1; } From 7ac5088c50446f4e94cff0ff8e3f99df6aed9cb4 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Wed, 17 Dec 2025 10:57:40 +0100 Subject: [PATCH 06/14] MINOR: proxy: define "add backend" handler Define a basic CLI handler for "add backend". For now, this handler only performs a parsing of the name argument and return an error if a duplicate already exists. It runs under thread isolation, to guarantee thread safety during the proxy creation. This feature is considered in development. CLI command requires to set experimental-mode. --- doc/management.txt | 13 +++++++++ src/proxy.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/doc/management.txt b/doc/management.txt index b2b1d539d3078..b07f287baa790 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -1725,6 +1725,19 @@ add acl [@] This command cannot be used if the reference is a name also used with a map. In this case, the "add map" command must be used instead. +add backend from [ EXPERIMENTAL ] + Instantiate a new backend proxy with the name . + + Only TCP or HTTP proxies can be created. All of the settings are inherited + from default proxy instance. Servers can be added via the command + "add server". The backend is initialized in the unpublished state. Once + considered ready for traffic, use "publish backend" to expose the newly + created instance. + + This command is restricted and can only be issued on sockets configured for + level "admin". Moreover, this feature is still considered in development so it + also requires experimental mode (see "experimental-mode on"). + add map [@] add map [@] Add an entry into the map to associate the value to the key diff --git a/src/proxy.c b/src/proxy.c index 207f85a98d744..3d9f9440ba329 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -4777,6 +4778,70 @@ static int cli_parse_shutdown_frontend(char **args, char *payload, struct appctx return 1; } +/* Parses a "add backend" CLI command to allocate a new backend instance, + * derived from a default proxy instance. This operation is performed under + * thread isolation. + * + * Always returns 1. + */ +static int cli_parse_add_backend(char **args, char *payload, struct appctx *appctx, void *private) +{ + struct proxy *px, *defpx; + const char *be_name, *def_name, *err; + char *msg = NULL; + + usermsgs_clr("CLI"); + + if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) + return 1; + + ++args; + be_name = args[1]; + if (!*be_name) { + cli_err(appctx, "Require backend name.\n"); + return 1; + } + if ((err = invalid_char(be_name))) { + cli_dynerr(appctx, memprintf(&msg, "Invalid character '%c' in backend name.\n", *err)); + return 1; + } + + ++args; + def_name = args[2]; + if (!*args[1] || !*def_name || strcmp(args[1], "from") != 0) { + cli_err(appctx, "Usage: add backend from .\n"); + return 1; + } + + defpx = proxy_find_by_name(def_name, PR_CAP_DEF, 0); + if (!defpx) { + cli_dynerr(appctx, memprintf(&msg, "Cannot find default proxy '%s'.\n", def_name)); + return 1; + } + + thread_isolate(); + + if ((px = proxy_find_by_name(be_name, PR_CAP_NONE, 0)) || + (px = proxy_find_by_name(be_name, PR_CAP_DEF, 0))) { + memprintf(&msg, + "name is already used by other proxy '%s %s'", + proxy_cap_str(px->cap), be_name); + px = NULL; + goto err; + } + + thread_release(); + ha_notice("New backend registered.\n"); + cli_umsg(appctx, LOG_INFO); + return 1; + + err: + thread_release(); + if (msg) + cli_dynerr(appctx, msg); + return 1; +} + /* Parses the "disable frontend" directive, it always returns 1. * * Grabs the proxy lock. @@ -5098,6 +5163,7 @@ static int cli_io_handler_show_errors(struct appctx *appctx) /* register cli keywords */ static struct cli_kw_list cli_kws = {{ },{ + { { "add", "backend", NULL }, "add backend : add a new backend", cli_parse_add_backend, NULL, NULL, NULL, ACCESS_EXPERIMENTAL }, { { "disable", "frontend", NULL }, "disable frontend : temporarily disable specific frontend", cli_parse_disable_frontend, NULL, NULL }, { { "enable", "frontend", NULL }, "enable frontend : re-enable specific frontend", cli_parse_enable_frontend, NULL, NULL }, { { "publish", "backend", NULL }, "publish backend : mark backend as ready for traffic", cli_parse_publish_backend, NULL, NULL }, From e15291332797bc71562b4a5706561ce0d26a21a0 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Mon, 12 Jan 2026 15:25:52 +0100 Subject: [PATCH 07/14] MINOR: proxy: parse mode on dynamic backend creation Add an optional "mode" argument to "add backend" CLI command. This argument allows to specify if the backend is in TCP or HTTP mode. By default, it is mandatory, unless the inherited default proxy already explicitely specifies the mode. To differentiate if TCP mode is implicit or explicit, a new proxy flag PR_FL_DEF_EXPLICIT_MODE is defined. It is set for every defaults instances which explicitely defined their mode. --- doc/management.txt | 13 ++++++++----- include/haproxy/proxy-t.h | 2 +- src/cfgparse-listen.c | 2 ++ src/proxy.c | 29 +++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/doc/management.txt b/doc/management.txt index b07f287baa790..43eed45076a60 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -1725,14 +1725,17 @@ add acl [@] This command cannot be used if the reference is a name also used with a map. In this case, the "add map" command must be used instead. -add backend from [ EXPERIMENTAL ] +add backend from [mode ] [ EXPERIMENTAL ] Instantiate a new backend proxy with the name . Only TCP or HTTP proxies can be created. All of the settings are inherited - from default proxy instance. Servers can be added via the command - "add server". The backend is initialized in the unpublished state. Once - considered ready for traffic, use "publish backend" to expose the newly - created instance. + from default proxy instance. By default, it is mandatory to + specify the backend mode via the argument of the same name, unless + already defines it explicitely. + + Servers can be added via the command "add server". The backend is initialized + in the unpublished state. Once considered ready for traffic, use "publish + backend" to expose the newly created instance. This command is restricted and can only be issued on sockets configured for level "admin". Moreover, this feature is still considered in development so it diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h index 4f4b6c45d0834..20c359126e894 100644 --- a/include/haproxy/proxy-t.h +++ b/include/haproxy/proxy-t.h @@ -242,7 +242,7 @@ enum PR_SRV_STATE_FILE { /* Proxy flags */ #define PR_FL_DISABLED 0x01 /* The proxy was disabled in the configuration (not at runtime) */ #define PR_FL_STOPPED 0x02 /* The proxy was stopped */ -/* 0x04 unused */ +#define PR_FL_DEF_EXPLICIT_MODE 0x04 /* Proxy mode is explicitely defined - only used for defaults instance */ #define PR_FL_EXPLICIT_REF 0x08 /* The default proxy is explicitly referenced by another proxy */ #define PR_FL_IMPLICIT_REF 0x10 /* The default proxy is implicitly referenced by another proxy */ #define PR_FL_PAUSED 0x20 /* The proxy was paused at run time (reversible) */ diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index 3c27a638e620f..9a680ef09bfd2 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -657,6 +657,8 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) } curproxy->mode = mode; + if (curproxy->cap & PR_CAP_DEF) + curproxy->flags |= PR_FL_DEF_EXPLICIT_MODE; } else if (strcmp(args[0], "id") == 0) { struct proxy *conflict; diff --git a/src/proxy.c b/src/proxy.c index 3d9f9440ba329..43651b51570b1 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -4789,6 +4789,7 @@ static int cli_parse_add_backend(char **args, char *payload, struct appctx *appc struct proxy *px, *defpx; const char *be_name, *def_name, *err; char *msg = NULL; + enum pr_mode mode = 0; usermsgs_clr("CLI"); @@ -4813,11 +4814,39 @@ static int cli_parse_add_backend(char **args, char *payload, struct appctx *appc return 1; } + /* Parse optional arguments */ + args += 2; + while (*args[1]) { + /* "mode" */ + if (*args[2] && !mode && strcmp(args[1], "mode") == 0) { + mode = str_to_proxy_mode(args[2]); + if (mode == PR_MODES) { + cli_err(appctx, "Unknown proxy mode.\n"); + return 1; + } + if (mode != PR_MODE_TCP && mode != PR_MODE_HTTP) { + cli_err(appctx, "Dynamic backends are compatible with only TCP or HTTP mode.\n"); + return 1; + } + } + /* unknown, malformed or duplicate argument */ + else { + cli_err(appctx, "Usage: add backend from [mode ].\n"); + return 1; + } + + args += 2; + } + defpx = proxy_find_by_name(def_name, PR_CAP_DEF, 0); if (!defpx) { cli_dynerr(appctx, memprintf(&msg, "Cannot find default proxy '%s'.\n", def_name)); return 1; } + if (!(defpx->flags & PR_FL_DEF_EXPLICIT_MODE) && !mode) { + cli_dynerr(appctx, memprintf(&msg, "Mode is required as '%s' default proxy does not explicitely defines it.\n", def_name)); + return 1; + } thread_isolate(); From a603811aac719814d5b24ca84f60751f0f3040eb Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Tue, 13 Jan 2026 16:13:27 +0100 Subject: [PATCH 08/14] MINOR: proxy: parse guid on dynamic backend creation Defines an extra optional GUID argument for "add backend" command. This can be useful as it is not possible to define it via a default proxy instance. --- doc/management.txt | 5 +++-- src/proxy.c | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/management.txt b/doc/management.txt index 43eed45076a60..5a256a8e714d0 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -1725,13 +1725,14 @@ add acl [@] This command cannot be used if the reference is a name also used with a map. In this case, the "add map" command must be used instead. -add backend from [mode ] [ EXPERIMENTAL ] +add backend from [mode ] [guid ] [ EXPERIMENTAL ] Instantiate a new backend proxy with the name . Only TCP or HTTP proxies can be created. All of the settings are inherited from default proxy instance. By default, it is mandatory to specify the backend mode via the argument of the same name, unless - already defines it explicitely. + already defines it explicitely. It is also possible to use an optional GUID + argument if wanted. Servers can be added via the command "add server". The backend is initialized in the unpublished state. Once considered ready for traffic, use "publish diff --git a/src/proxy.c b/src/proxy.c index 43651b51570b1..eeed469b5f007 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -4787,7 +4787,7 @@ static int cli_parse_shutdown_frontend(char **args, char *payload, struct appctx static int cli_parse_add_backend(char **args, char *payload, struct appctx *appctx, void *private) { struct proxy *px, *defpx; - const char *be_name, *def_name, *err; + const char *be_name, *def_name, *guid = NULL, *err; char *msg = NULL; enum pr_mode mode = 0; @@ -4829,9 +4829,13 @@ static int cli_parse_add_backend(char **args, char *payload, struct appctx *appc return 1; } } + /* guid */ + else if (*args[2] && !guid && strcmp(args[1], "guid") == 0) { + guid = args[2]; + } /* unknown, malformed or duplicate argument */ else { - cli_err(appctx, "Usage: add backend from [mode ].\n"); + cli_err(appctx, "Usage: add backend from [mode ] [guid ].\n"); return 1; } From 07195a1af442aa93979f2452e42ff0209fbca3e9 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Fri, 6 Feb 2026 13:51:02 +0100 Subject: [PATCH 09/14] MINOR: proxy: check default proxy compatibility on "add backend" This commits completes "add backend" handler with some checks performed on the specified default proxy instance. These are additional checks outside of the already existing inheritance rules, specific to dynamic backends. For now, a default proxy is considered not compatible if it is not in mode TCP/HTTP. Also, a default proxy is rejected if it references HTTP errors. This limitation may be lifted in the future, when HTTP errors are partiallay reworked. --- doc/management.txt | 7 +++++++ src/proxy.c | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/doc/management.txt b/doc/management.txt index 5a256a8e714d0..aacbfab6c8a35 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -1738,6 +1738,13 @@ add backend from [mode ] [guid ] [ EXPERIMENTAL ] in the unpublished state. Once considered ready for traffic, use "publish backend" to expose the newly created instance. + All named default proxies can be used, given that they validate the same + inheritance rules applied during configuration parsing. There is some + exceptions though, for example when the mode is neither TCP nor HTTP. Another + exception is that it is not yet possible to use a default proxies which + reference custom HTTP errors, for example via the errorfiles or http-rules + keywords. + This command is restricted and can only be issued on sockets configured for level "admin". Moreover, this feature is still considered in development so it also requires experimental mode (see "experimental-mode on"). diff --git a/src/proxy.c b/src/proxy.c index eeed469b5f007..292e72f8db069 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -4851,6 +4851,15 @@ static int cli_parse_add_backend(char **args, char *payload, struct appctx *appc cli_dynerr(appctx, memprintf(&msg, "Mode is required as '%s' default proxy does not explicitely defines it.\n", def_name)); return 1; } + if (defpx->mode != PR_MODE_TCP && defpx->mode != PR_MODE_HTTP) { + cli_dynerr(appctx, memprintf(&msg, "Dynamic backends only support TCP or HTTP mode, whereas default proxy '%s' uses 'mode %s'.\n", + def_name, proxy_mode_str(defpx->mode))); + return 1; + } + if (!LIST_ISEMPTY(&defpx->conf.errors)) { + cli_dynerr(appctx, memprintf(&msg, "Dynamic backends cannot inherit from default proxy '%s' because it references HTTP errors.\n", def_name)); + return 1; + } thread_isolate(); From 3115eb82a694d5dd37e67386f909f1a90fd5554e Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Thu, 18 Dec 2025 09:51:27 +0100 Subject: [PATCH 10/14] MEDIUM: proxy: implement dynamic backend creation Implement the required operations for "add backend" handler. This requires a new proxy allocation, settings copy from the specified default instance and proxy config finalization. All handlers registered via REGISTER_POST_PROXY_CHECK() are also called on the newly created instance. If no error were encountered, the newly created proxy is finally attached in the proxies list. --- src/proxy.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/src/proxy.c b/src/proxy.c index 292e72f8db069..695470c069b0a 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -56,7 +57,7 @@ #include #include #include -#include +#include #include #include #include @@ -3023,8 +3024,10 @@ void defaults_px_unref_all(void) /* Add a reference on the default proxy for the proxy Nothing is * done if already references . Otherwise, the default proxy - * refcount is incremented by one. For now, this operation is not thread safe - * and is perform during init stage only. + * refcount is incremented by one. + * + * This operation is not thread safe. It must only be performed during init + * stage or under thread isolation. */ static inline void defaults_px_ref(struct proxy *defpx, struct proxy *px) { @@ -4786,10 +4789,12 @@ static int cli_parse_shutdown_frontend(char **args, char *payload, struct appctx */ static int cli_parse_add_backend(char **args, char *payload, struct appctx *appctx, void *private) { - struct proxy *px, *defpx; + struct proxy *px, *defpx, *next; + struct post_proxy_check_fct *ppcf; const char *be_name, *def_name, *guid = NULL, *err; char *msg = NULL; enum pr_mode mode = 0; + int err_code = ERR_NONE; usermsgs_clr("CLI"); @@ -4872,15 +4877,81 @@ static int cli_parse_add_backend(char **args, char *payload, struct appctx *appc goto err; } + px = alloc_new_proxy(be_name, PR_CAP_BE, &msg); + if (!px) + goto err; + + if (guid && guid_insert(&px->obj_type, guid, &msg)) { + memprintf(&msg, "GUID insertion : %s", msg); + goto err; + } + + if (proxy_defproxy_cpy(px, defpx, &msg)) + goto err; + + /* Override default-proxy mode if defined. */ + if (mode) + px->mode = mode; + + if (proxy_ref_defaults(px, defpx, &msg)) + goto err; + + proxy_init_per_thr(px); + + if (proxy_finalize(px, &err_code)) + goto err; + + list_for_each_entry(ppcf, &post_proxy_check_list, list) { + err_code |= ppcf->fct(px); + if (err_code & (ERR_ABORT|ERR_FATAL)) + goto err; + } + + px->flags |= PR_FL_BE_UNPUBLISHED; + + if (!stats_allocate_proxy_counters_internal(&px->extra_counters_be, + COUNTERS_BE, + STATS_PX_CAP_BE)) { + memprintf(&msg, "failed to allocate extra counters"); + goto err; + } + + if (!proxies_list) { + proxies_list->next = px; + } + else { + for (next = proxies_list; next->next; next = next->next) + ; + next->next = px; + } + px->next = NULL; + thread_release(); - ha_notice("New backend registered.\n"); + + if (unlikely(!be_supports_dynamic_srv(px, &msg))) + memprintf(&msg, "New backend registered (no support for dynamic servers: %s).\n", msg); + else + memprintf(&msg, "New backend registered.\n"); + ha_notice(msg); + ha_free(&msg); cli_umsg(appctx, LOG_INFO); + return 1; err: + /* free_proxy() ensures any potential refcounting on defpx is decremented. */ + free_proxy(px); thread_release(); - if (msg) + + if (msg) { + memprintf(&msg, "Error during backend creation : %s.\n", msg); cli_dynerr(appctx, msg); + } + else { + ha_alert("Error during backend creation.\n"); + cli_umsgerr(appctx); + } + return 1; } From 5753c14e8412540524de4394c830e125509d2a33 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Tue, 23 Dec 2025 16:15:47 +0100 Subject: [PATCH 11/14] MINOR: proxy: assign dynamic proxy ID Implement proxy ID generation for dynamic backends. This is performed through the already function existing proxy_get_next_id(). As an optimization, lookup will performed starting from a global variable . It is initialized to the greatest ID assigned after parsing, and updated each time a backend instance is created. When backend deletion will be implemented, it could be lowered to the newly available slot. --- include/haproxy/proxy.h | 2 ++ src/cfgparse.c | 3 +++ src/proxy.c | 10 ++++++++++ 3 files changed, 15 insertions(+) diff --git a/include/haproxy/proxy.h b/include/haproxy/proxy.h index 57dbbd9ff727e..69860510f8ac2 100644 --- a/include/haproxy/proxy.h +++ b/include/haproxy/proxy.h @@ -41,6 +41,8 @@ extern unsigned int error_snapshot_id; /* global ID assigned to each error then extern struct ceb_root *proxy_by_name; /* tree of proxies sorted by name */ extern struct list defaults_list; /* all defaults proxies list */ +extern unsigned int dynpx_next_id; + extern const struct cfg_opt cfg_opts[]; extern const struct cfg_opt cfg_opts2[]; extern const struct cfg_opt cfg_opts3[]; diff --git a/src/cfgparse.c b/src/cfgparse.c index 39cabdf6bcd1e..5dd10faf4639d 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -2428,6 +2428,9 @@ int check_config_validity() } } + /* Dynamic proxies IDs will never be lowered than this value. */ + dynpx_next_id = next_pxid; + /* * We have just initialized the main proxies list * we must also configure the log-forward proxies list diff --git a/src/proxy.c b/src/proxy.c index 695470c069b0a..4a5ce7e17deda 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -77,6 +77,8 @@ struct ceb_root *defproxy_by_name = NULL; /* tree of default proxies sorted by n struct list defaults_list = LIST_HEAD_INIT(defaults_list); /* list of all defaults proxies */ unsigned int error_snapshot_id = 0; /* global ID assigned to each error then incremented */ +unsigned int dynpx_next_id = 0; /* lowest ID assigned to dynamic proxies */ + /* CLI context used during "show servers {state|conn}" */ struct show_srv_ctx { struct proxy *px; /* current proxy to dump or NULL */ @@ -4916,6 +4918,14 @@ static int cli_parse_add_backend(char **args, char *payload, struct appctx *appc goto err; } + /* Assign automatically proxy ID. */ + px->uuid = proxy_get_next_id(dynpx_next_id); + if (!px->uuid) { + memprintf(&msg, "no spare proxy ID available"); + goto err; + } + dynpx_next_id = px->uuid; + if (!proxies_list) { proxies_list->next = px; } From d7cdd2c7f40458d53832b05374808f86055d17c8 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Mon, 2 Feb 2026 10:52:07 +0100 Subject: [PATCH 12/14] REGTESTS: add dynamic backend creation test Add a new regtests to validate backend creation at runtime. A server is then added and requests made to validate the newly created instance before (with force-be-switch) and after publishing. --- reg-tests/proxy/cli_add_backend.vtc | 84 +++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 reg-tests/proxy/cli_add_backend.vtc diff --git a/reg-tests/proxy/cli_add_backend.vtc b/reg-tests/proxy/cli_add_backend.vtc new file mode 100644 index 0000000000000..7a2c97f79e4cd --- /dev/null +++ b/reg-tests/proxy/cli_add_backend.vtc @@ -0,0 +1,84 @@ +varnishtest "Add backend via cli" + +feature ignore_unknown_macro + +haproxy hsrv -conf { + global + .if feature(THREAD) + thread-groups 1 + .endif + + defaults + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + frontend fe + bind "fd@${fe}" + http-request return status 200 +} -start + +haproxy h1 -conf { + global + .if feature(THREAD) + thread-groups 1 + .endif + + defaults + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + frontend fe + bind "fd@${feS}" + force-be-switch if { req.hdr("x-admin") "1" } + use_backend %[req.hdr(x-be)] + + defaults def + + defaults def_http + mode http +} -start + +client c1 -connect ${h1_feS_sock} { + txreq -hdr "x-be: be" + rxresp + expect resp.status == 503 +} -run + +haproxy h1 -cli { + # non existent backend + send "experimental-mode on; add backend be from def" + expect ~ "Mode is required" + + send "experimental-mode on; add backend be from def_http" + expect ~ "New backend registered." + + send "add server be/srv ${hsrv_fe_addr}:${hsrv_fe_port}" + expect ~ "New server registered." + send "enable server be/srv" + expect ~ ".*" +} + +client c1 -connect ${h1_feS_sock} { + txreq -hdr "x-be: be" + rxresp + expect resp.status == 503 + + txreq -hdr "x-be: be" -hdr "x-admin: 1" + rxresp + expect resp.status == 200 +} -run + +haproxy h1 -cli { + send "publish backend be" + expect ~ "Backend published." +} + +client c1 -connect ${h1_feS_sock} { + txreq -hdr "x-be: be" + rxresp + expect resp.status == 200 +} -run From d13370d5265e7d7056d6a48e68ccff990fa78229 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 19:34:10 +0000 Subject: [PATCH 13/14] Initial plan From b7e82bf39bde08a8c2180399f5f98b2d7b3bf4b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 19:43:53 +0000 Subject: [PATCH 14/14] Fix prometheus metrics to skip zlib metrics when built with libslz Co-authored-by: chipitsine <2217296+chipitsine@users.noreply.github.com> --- addons/promex/service-prometheus.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/addons/promex/service-prometheus.c b/addons/promex/service-prometheus.c index d3a1f64428c0c..6fe6c0e6c719e 100644 --- a/addons/promex/service-prometheus.c +++ b/addons/promex/service-prometheus.c @@ -473,6 +473,13 @@ static int promex_dump_global_metrics(struct appctx *appctx, struct htx *htx) val = mkf_u32(FN_GAUGE, 1); break; +#ifndef USE_ZLIB + case ST_I_INF_ZLIB_MEM_USAGE: + case ST_I_INF_MAX_ZLIB_MEM_USAGE: + /* Skip zlib metrics when built with libslz */ + continue; +#endif + default: break; }