diff --git a/addons/promex/service-prometheus.c b/addons/promex/service-prometheus.c index d3a1f64428c0..6fe6c0e6c719 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; } diff --git a/doc/management.txt b/doc/management.txt index b2b1d539d307..aacbfab6c8a3 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -1725,6 +1725,30 @@ 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 ] [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. 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 + 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"). + add map [@] add map [@] Add an entry into the map to associate the value to the key diff --git a/include/haproxy/backend.h b/include/haproxy/backend.h index 12027073076d..6930ff256d3f 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/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h index 02359079f525..20c359126e89 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) */ +#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/include/haproxy/proxy.h b/include/haproxy/proxy.h index ed86925d2037..69860510f8ac 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[]; @@ -59,6 +61,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); @@ -74,8 +77,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, @@ -97,6 +99,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/reg-tests/proxy/cli_add_backend.vtc b/reg-tests/proxy/cli_add_backend.vtc new file mode 100644 index 000000000000..7a2c97f79e4c --- /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 diff --git a/reg-tests/server/cli_add_server.vtc b/reg-tests/server/cli_add_server.vtc index 8081312ba901..93331a2bf741 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 73b39306b9ba..b531843fc276 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/cfgparse-listen.c b/src/cfgparse-listen.c index bcb085936bf3..9a680ef09bfd 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) { @@ -707,23 +633,32 @@ 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; + 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/cfgparse.c b/src/cfgparse.c index 52a4cb8fe058..5dd10faf4639 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -2269,8 +2269,8 @@ int parse_cfg(const struct cfgfile *cfg) */ int check_config_validity() { - int cfgerr = 0; - struct proxy *init_proxies_list = NULL; + int cfgerr = 0, ret; + struct proxy *init_proxies_list = NULL, *defpx; struct stktable *t; struct server *newsrv = NULL; struct mt_list back; @@ -2358,17 +2358,34 @@ 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; 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. @@ -2403,1318 +2420,17 @@ 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); - 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; + ret = proxy_finalize(curproxy, &err_code); + if (ret) { + cfgerr += ret; + if (err_code & ERR_FATAL) 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; - 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; - } } } + /* 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 @@ -3860,7 +2576,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 +2652,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; @@ -3963,7 +2677,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 d3b7c0f93fff..4a5ce7e17ded 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +39,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -46,13 +54,15 @@ #include #include #include +#include #include #include -#include +#include #include #include #include #include +#include #include #include #include @@ -67,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 */ @@ -493,6 +505,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 @@ -1591,6 +1618,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. @@ -1698,18 +3026,118 @@ 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. */ -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 @@ -3355,6 +4783,188 @@ 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, *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"); + + 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; + } + + /* 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; + } + } + /* 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 ] [guid ].\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; + } + 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(); + + 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; + } + + 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; + } + + /* 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; + } + else { + for (next = proxies_list; next->next; next = next->next) + ; + next->next = px; + } + px->next = NULL; + + thread_release(); + + 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) { + 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; +} + /* Parses the "disable frontend" directive, it always returns 1. * * Grabs the proxy lock. @@ -3676,6 +5286,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 }, diff --git a/src/server.c b/src/server.c index d1bb43825cd7..a475c881bbdb 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; }