/* * Proxy variables and functions. * * Copyright 2000-2009 Willy Tarreau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int listeners; /* # of proxy listeners, set by cfgparse */ struct proxy *proxies_list = NULL; /* list of all existing proxies */ struct eb_root used_proxy_id = EB_ROOT; /* list of proxy IDs in use */ struct eb_root proxy_by_name = EB_ROOT; /* tree of proxies sorted by name */ struct eb_root defproxy_by_name = EB_ROOT; /* tree of default proxies sorted by name (dups possible) */ unsigned int error_snapshot_id = 0; /* global ID assigned to each error then incremented */ /* CLI context used during "show servers {state|conn}" */ struct show_srv_ctx { struct proxy *px; /* current proxy to dump or NULL */ struct server *sv; /* current server to dump or NULL */ uint only_pxid; /* dump only this proxy ID when explicit */ int show_conn; /* non-zero = "conn" otherwise "state" */ enum { SHOW_SRV_HEAD = 0, SHOW_SRV_LIST, } state; }; /* proxy->options */ const struct cfg_opt cfg_opts[] = { { "abortonclose", PR_O_ABRT_CLOSE, PR_CAP_BE, 0, 0 }, { "allbackups", PR_O_USE_ALL_BK, PR_CAP_BE, 0, 0 }, { "checkcache", PR_O_CHK_CACHE, PR_CAP_BE, 0, PR_MODE_HTTP }, { "clitcpka", PR_O_TCP_CLI_KA, PR_CAP_FE, 0, 0 }, { "contstats", PR_O_CONTSTATS, PR_CAP_FE, 0, 0 }, { "dontlognull", PR_O_NULLNOLOG, PR_CAP_FE, 0, 0 }, { "http-buffer-request", PR_O_WREQ_BODY, PR_CAP_FE | PR_CAP_BE, 0, PR_MODE_HTTP }, { "http-ignore-probes", PR_O_IGNORE_PRB, PR_CAP_FE, 0, PR_MODE_HTTP }, { "idle-close-on-response", PR_O_IDLE_CLOSE_RESP, PR_CAP_FE, 0, PR_MODE_HTTP }, { "prefer-last-server", PR_O_PREF_LAST, PR_CAP_BE, 0, PR_MODE_HTTP }, { "logasap", PR_O_LOGASAP, PR_CAP_FE, 0, 0 }, { "nolinger", PR_O_TCP_NOLING, PR_CAP_FE | PR_CAP_BE, 0, 0 }, { "persist", PR_O_PERSIST, PR_CAP_BE, 0, 0 }, { "srvtcpka", PR_O_TCP_SRV_KA, PR_CAP_BE, 0, 0 }, #ifdef USE_TPROXY { "transparent", PR_O_TRANSP, PR_CAP_BE, 0, 0 }, #else { "transparent", 0, 0, 0, 0 }, #endif { NULL, 0, 0, 0, 0 } }; /* proxy->options2 */ const struct cfg_opt cfg_opts2[] = { #ifdef USE_LINUX_SPLICE { "splice-request", PR_O2_SPLIC_REQ, PR_CAP_FE|PR_CAP_BE, 0, 0 }, { "splice-response", PR_O2_SPLIC_RTR, PR_CAP_FE|PR_CAP_BE, 0, 0 }, { "splice-auto", PR_O2_SPLIC_AUT, PR_CAP_FE|PR_CAP_BE, 0, 0 }, #else { "splice-request", 0, 0, 0, 0 }, { "splice-response", 0, 0, 0, 0 }, { "splice-auto", 0, 0, 0, 0 }, #endif { "accept-invalid-http-request", PR_O2_REQBUG_OK, PR_CAP_FE, 0, PR_MODE_HTTP }, { "accept-invalid-http-response", PR_O2_RSPBUG_OK, PR_CAP_BE, 0, PR_MODE_HTTP }, { "dontlog-normal", PR_O2_NOLOGNORM, PR_CAP_FE, 0, 0 }, { "log-separate-errors", PR_O2_LOGERRORS, PR_CAP_FE, 0, 0 }, { "log-health-checks", PR_O2_LOGHCHKS, PR_CAP_BE, 0, 0 }, { "socket-stats", PR_O2_SOCKSTAT, PR_CAP_FE, 0, 0 }, { "tcp-smart-accept", PR_O2_SMARTACC, PR_CAP_FE, 0, 0 }, { "tcp-smart-connect", PR_O2_SMARTCON, PR_CAP_BE, 0, 0 }, { "independent-streams", PR_O2_INDEPSTR, PR_CAP_FE|PR_CAP_BE, 0, 0 }, { "http-use-proxy-header", PR_O2_USE_PXHDR, PR_CAP_FE, 0, PR_MODE_HTTP }, { "http-pretend-keepalive", PR_O2_FAKE_KA, PR_CAP_BE, 0, PR_MODE_HTTP }, { "http-no-delay", PR_O2_NODELAY, PR_CAP_FE|PR_CAP_BE, 0, PR_MODE_HTTP }, {"h1-case-adjust-bogus-client", PR_O2_H1_ADJ_BUGCLI, PR_CAP_FE, 0, 0 }, {"h1-case-adjust-bogus-server", PR_O2_H1_ADJ_BUGSRV, PR_CAP_BE, 0, 0 }, {"disable-h2-upgrade", PR_O2_NO_H2_UPGRADE, PR_CAP_FE, 0, PR_MODE_HTTP }, { NULL, 0, 0, 0 } }; /* Helper function to resolve a single sticking rule after config parsing. * Returns 1 for success and 0 for failure */ int resolve_stick_rule(struct proxy *curproxy, struct sticking_rule *mrule) { struct stktable *target; if (mrule->table.name) target = stktable_find_by_name(mrule->table.name); else target = curproxy->table; if (!target) { ha_alert("Proxy '%s': unable to find stick-table '%s'.\n", curproxy->id, mrule->table.name ? mrule->table.name : curproxy->id); return 0; } else if (!stktable_compatible_sample(mrule->expr, target->type)) { ha_alert("Proxy '%s': type of fetch not usable with type of stick-table '%s'.\n", curproxy->id, mrule->table.name ? mrule->table.name : curproxy->id); return 0; } /* success */ ha_free(&mrule->table.name); mrule->table.t = target; stktable_alloc_data_type(target, STKTABLE_DT_SERVER_ID, NULL, NULL); stktable_alloc_data_type(target, STKTABLE_DT_SERVER_KEY, NULL, NULL); if (!in_proxies_list(target->proxies_list, curproxy)) { curproxy->next_stkt_ref = target->proxies_list; target->proxies_list = curproxy; } return 1; } void free_stick_rules(struct list *rules) { struct sticking_rule *rule, *ruleb; list_for_each_entry_safe(rule, ruleb, rules, list) { LIST_DELETE(&rule->list); free_acl_cond(rule->cond); release_sample_expr(rule->expr); free(rule); } } static void free_logformat_list(struct list *lfs) { struct logformat_node *lf, *lfb; list_for_each_entry_safe(lf, lfb, lfs, list) { LIST_DELETE(&lf->list); release_sample_expr(lf->expr); free(lf->arg); free(lf); } } void free_server_rules(struct list *srules) { struct server_rule *srule, *sruleb; list_for_each_entry_safe(srule, sruleb, srules, list) { LIST_DELETE(&srule->list); free_acl_cond(srule->cond); free_logformat_list(&srule->expr); free(srule->file); free(srule); } } void free_proxy(struct proxy *p) { struct server *s; struct cap_hdr *h,*h_next; struct listener *l,*l_next; struct bind_conf *bind_conf, *bind_back; struct acl_cond *cond, *condb; struct acl *acl, *aclb; struct switching_rule *rule, *ruleb; struct redirect_rule *rdr, *rdrb; struct logger *log, *logb; struct proxy_deinit_fct *pxdf; struct server_deinit_fct *srvdf; if (!p) return; free(p->conf.file); free(p->id); free(p->cookie_name); free(p->cookie_domain); free(p->cookie_attrs); free(p->lbprm.arg_str); release_sample_expr(p->lbprm.expr); free(p->server_state_file_name); free(p->capture_name); istfree(&p->monitor_uri); free(p->rdp_cookie_name); free(p->invalid_rep); free(p->invalid_req); #if defined(CONFIG_HAP_TRANSPARENT) free(p->conn_src.bind_hdr_name); #endif if (p->conf.logformat_string != default_http_log_format && p->conf.logformat_string != default_tcp_log_format && p->conf.logformat_string != clf_http_log_format && p->conf.logformat_string != default_https_log_format && p->conf.logformat_string != httpclient_log_format) free(p->conf.logformat_string); free(p->conf.lfs_file); free(p->conf.uniqueid_format_string); istfree(&p->header_unique_id); free(p->conf.uif_file); if ((p->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_MAP) free(p->lbprm.map.srv); if (p->mode == PR_MODE_SYSLOG) free(p->lbprm.log.srv); if (p->conf.logformat_sd_string != default_rfc5424_sd_log_format) free(p->conf.logformat_sd_string); free(p->conf.lfsd_file); free(p->conf.error_logformat_string); free(p->conf.elfs_file); list_for_each_entry_safe(cond, condb, &p->mon_fail_cond, list) { LIST_DELETE(&cond->list); free_acl_cond(cond); } EXTRA_COUNTERS_FREE(p->extra_counters_fe); EXTRA_COUNTERS_FREE(p->extra_counters_be); list_for_each_entry_safe(acl, aclb, &p->acl, list) { LIST_DELETE(&acl->list); prune_acl(acl); free(acl); } free_server_rules(&p->server_rules); list_for_each_entry_safe(rule, ruleb, &p->switching_rules, list) { LIST_DELETE(&rule->list); free_acl_cond(rule->cond); if (rule->dynamic) free_logformat_list(&rule->be.expr); free(rule->file); free(rule); } list_for_each_entry_safe(rdr, rdrb, &p->redirect_rules, list) { LIST_DELETE(&rdr->list); http_free_redirect_rule(rdr); } list_for_each_entry_safe(log, logb, &p->loggers, list) { LIST_DEL_INIT(&log->list); free_logger(log); } free_logformat_list(&p->logformat); free_logformat_list(&p->logformat_sd); free_logformat_list(&p->format_unique_id); free_logformat_list(&p->logformat_error); free_act_rules(&p->tcp_req.inspect_rules); free_act_rules(&p->tcp_rep.inspect_rules); free_act_rules(&p->tcp_req.l4_rules); free_act_rules(&p->tcp_req.l5_rules); free_act_rules(&p->http_req_rules); free_act_rules(&p->http_res_rules); free_act_rules(&p->http_after_res_rules); free_stick_rules(&p->storersp_rules); free_stick_rules(&p->sticking_rules); h = p->req_cap; while (h) { if (p->defpx && h == p->defpx->req_cap) break; h_next = h->next; free(h->name); pool_destroy(h->pool); free(h); h = h_next; }/* end while(h) */ h = p->rsp_cap; while (h) { if (p->defpx && h == p->defpx->rsp_cap) break; h_next = h->next; free(h->name); pool_destroy(h->pool); free(h); h = h_next; }/* end while(h) */ s = p->srv; while (s) { list_for_each_entry(srvdf, &server_deinit_list, list) srvdf->fct(s); s = srv_drop(s); }/* end while(s) */ /* also free default-server parameters since some of them might have * been dynamically allocated (e.g.: config hints, cookies, ssl..) */ srv_free_params(&p->defsrv); list_for_each_entry_safe(l, l_next, &p->conf.listeners, by_fe) { LIST_DELETE(&l->by_fe); LIST_DELETE(&l->by_bind); free(l->name); free(l->per_thr); free(l->counters); task_destroy(l->rx.rhttp.task); EXTRA_COUNTERS_FREE(l->extra_counters); free(l); } /* Release unused SSL configs. */ list_for_each_entry_safe(bind_conf, bind_back, &p->conf.bind, by_fe) { if (bind_conf->xprt->destroy_bind_conf) bind_conf->xprt->destroy_bind_conf(bind_conf); free(bind_conf->file); free(bind_conf->arg); free(bind_conf->settings.interface); LIST_DELETE(&bind_conf->by_fe); free(bind_conf->rhttp_srvname); free(bind_conf); } flt_deinit(p); list_for_each_entry(pxdf, &proxy_deinit_list, list) pxdf->fct(p); free(p->desc); http_ext_clean(p); task_destroy(p->task); pool_destroy(p->req_cap_pool); pool_destroy(p->rsp_cap_pool); stktable_deinit(p->table); ha_free(&p->table); HA_RWLOCK_DESTROY(&p->lbprm.lock); HA_RWLOCK_DESTROY(&p->lock); proxy_unref_defaults(p); ha_free(&p); } /* * This function returns a string containing a name describing capabilities to * report comprehensible error messages. Specifically, it will return the words * "frontend", "backend" when appropriate, "defaults" if it corresponds to a * defaults section, or "proxy" for all other cases including the proxies * declared in "listen" mode. */ const char *proxy_cap_str(int cap) { if (cap & PR_CAP_DEF) return "defaults"; if ((cap & PR_CAP_LISTEN) != PR_CAP_LISTEN) { if (cap & PR_CAP_FE) return "frontend"; else if (cap & PR_CAP_BE) return "backend"; } return "proxy"; } /* * This function returns a string containing the mode of the proxy in a format * suitable for error messages. */ const char *proxy_mode_str(int mode) { if (mode == PR_MODE_TCP) return "tcp"; else if (mode == PR_MODE_HTTP) return "http"; else if (mode == PR_MODE_CLI) return "cli"; else if (mode == PR_MODE_SYSLOG) return "syslog"; else if (mode == PR_MODE_PEERS) return "peers"; else return "unknown"; } /* 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 * of extra words to compare may be passed in , but it must then be * terminated by a NULL entry. If unused it may be NULL. */ const char *proxy_find_best_option(const char *word, const char **extra) { uint8_t word_sig[1024]; uint8_t list_sig[1024]; const char *best_ptr = NULL; int dist, best_dist = INT_MAX; int index; make_word_fingerprint(word_sig, word); for (index = 0; cfg_opts[index].name; index++) { make_word_fingerprint(list_sig, cfg_opts[index].name); dist = word_fingerprint_distance(word_sig, list_sig); if (dist < best_dist) { best_dist = dist; best_ptr = cfg_opts[index].name; } } for (index = 0; cfg_opts2[index].name; index++) { make_word_fingerprint(list_sig, cfg_opts2[index].name); dist = word_fingerprint_distance(word_sig, list_sig); if (dist < best_dist) { best_dist = dist; best_ptr = cfg_opts2[index].name; } } while (extra && *extra) { make_word_fingerprint(list_sig, *extra); dist = word_fingerprint_distance(word_sig, list_sig); if (dist < best_dist) { best_dist = dist; best_ptr = *extra; } extra++; } if (best_dist > 2 * strlen(word) || (best_ptr && best_dist > 2 * strlen(best_ptr))) best_ptr = NULL; return best_ptr; } /* This function parses a "timeout" statement in a proxy section. It returns * -1 if there is any error, 1 for a warning, otherwise zero. If it does not * return zero, it will write an error or warning message into a preallocated * buffer returned at . The trailing is not be written. The function must * be called with pointing to the first command line word, with * pointing to the proxy being parsed, and to the default proxy or NULL. * As a special case for compatibility with older configs, it also accepts * "{cli|srv|con}timeout" in args[0]. */ static int proxy_parse_timeout(char **args, int section, struct proxy *proxy, const struct proxy *defpx, const char *file, int line, char **err) { unsigned timeout; int retval, cap; const char *res, *name; int *tv = NULL; const int *td = NULL; retval = 0; /* simply skip "timeout" but remain compatible with old form */ if (strcmp(args[0], "timeout") == 0) args++; name = args[0]; if (strcmp(args[0], "client") == 0) { name = "client"; tv = &proxy->timeout.client; td = &defpx->timeout.client; cap = PR_CAP_FE; } else if (strcmp(args[0], "tarpit") == 0) { tv = &proxy->timeout.tarpit; td = &defpx->timeout.tarpit; cap = PR_CAP_FE | PR_CAP_BE; } else if (strcmp(args[0], "client-hs") == 0) { tv = &proxy->timeout.client_hs; td = &defpx->timeout.client_hs; cap = PR_CAP_FE; } else if (strcmp(args[0], "http-keep-alive") == 0) { tv = &proxy->timeout.httpka; td = &defpx->timeout.httpka; cap = PR_CAP_FE | PR_CAP_BE; } else if (strcmp(args[0], "http-request") == 0) { tv = &proxy->timeout.httpreq; td = &defpx->timeout.httpreq; cap = PR_CAP_FE | PR_CAP_BE; } else if (strcmp(args[0], "server") == 0) { name = "server"; tv = &proxy->timeout.server; td = &defpx->timeout.server; cap = PR_CAP_BE; } else if (strcmp(args[0], "connect") == 0) { name = "connect"; tv = &proxy->timeout.connect; td = &defpx->timeout.connect; cap = PR_CAP_BE; } else if (strcmp(args[0], "check") == 0) { tv = &proxy->timeout.check; td = &defpx->timeout.check; cap = PR_CAP_BE; } else if (strcmp(args[0], "queue") == 0) { tv = &proxy->timeout.queue; td = &defpx->timeout.queue; cap = PR_CAP_BE; } else if (strcmp(args[0], "tunnel") == 0) { tv = &proxy->timeout.tunnel; td = &defpx->timeout.tunnel; cap = PR_CAP_BE; } else if (strcmp(args[0], "client-fin") == 0) { tv = &proxy->timeout.clientfin; td = &defpx->timeout.clientfin; cap = PR_CAP_FE; } else if (strcmp(args[0], "server-fin") == 0) { tv = &proxy->timeout.serverfin; td = &defpx->timeout.serverfin; cap = PR_CAP_BE; } else if (strcmp(args[0], "clitimeout") == 0) { memprintf(err, "the '%s' directive is not supported anymore since HAProxy 2.1. Use 'timeout client'.", args[0]); return -1; } else if (strcmp(args[0], "srvtimeout") == 0) { memprintf(err, "the '%s' directive is not supported anymore since HAProxy 2.1. Use 'timeout server'.", args[0]); return -1; } else if (strcmp(args[0], "contimeout") == 0) { memprintf(err, "the '%s' directive is not supported anymore since HAProxy 2.1. Use 'timeout connect'.", args[0]); return -1; } else { memprintf(err, "'timeout' supports 'client', 'server', 'connect', 'check', " "'queue', 'handshake', 'http-keep-alive', 'http-request', 'tunnel', 'tarpit', " "'client-fin' and 'server-fin' (got '%s')", args[0]); return -1; } if (*args[1] == 0) { memprintf(err, "'timeout %s' expects an integer value (in milliseconds)", name); return -1; } res = parse_time_err(args[1], &timeout, TIME_UNIT_MS); if (res == PARSE_TIME_OVER) { memprintf(err, "timer overflow in argument '%s' to 'timeout %s' (maximum value is 2147483647 ms or ~24.8 days)", args[1], name); return -1; } else if (res == PARSE_TIME_UNDER) { memprintf(err, "timer underflow in argument '%s' to 'timeout %s' (minimum non-null value is 1 ms)", args[1], name); return -1; } else if (res) { memprintf(err, "unexpected character '%c' in 'timeout %s'", *res, name); return -1; } if (!(proxy->cap & cap)) { memprintf(err, "'timeout %s' will be ignored because %s '%s' has no %s capability", name, proxy_type_str(proxy), proxy->id, (cap & PR_CAP_BE) ? "backend" : "frontend"); retval = 1; } else if (defpx && *tv != *td) { memprintf(err, "overwriting 'timeout %s' which was already specified", name); retval = 1; } if (*args[2] != 0) { memprintf(err, "'timeout %s' : unexpected extra argument '%s' after value '%s'.", name, args[2], args[1]); retval = -1; } *tv = MS_TO_TICKS(timeout); return retval; } /* This function parses a "rate-limit" statement in a proxy section. It returns * -1 if there is any error, 1 for a warning, otherwise zero. If it does not * return zero, it will write an error or warning message into a preallocated * buffer returned at . The function must be called with pointing * to the first command line word, with pointing to the proxy being * parsed, and to the default proxy or NULL. */ static int proxy_parse_rate_limit(char **args, int section, struct proxy *proxy, const struct proxy *defpx, const char *file, int line, char **err) { int retval; char *res; unsigned int *tv = NULL; const unsigned int *td = NULL; unsigned int val; retval = 0; if (strcmp(args[1], "sessions") == 0) { tv = &proxy->fe_sps_lim; td = &defpx->fe_sps_lim; } else { memprintf(err, "'%s' only supports 'sessions' (got '%s')", args[0], args[1]); return -1; } if (*args[2] == 0) { memprintf(err, "'%s %s' expects expects an integer value (in sessions/second)", args[0], args[1]); return -1; } val = strtoul(args[2], &res, 0); if (*res) { memprintf(err, "'%s %s' : unexpected character '%c' in integer value '%s'", args[0], args[1], *res, args[2]); return -1; } if (!(proxy->cap & PR_CAP_FE)) { memprintf(err, "%s %s will be ignored because %s '%s' has no frontend capability", args[0], args[1], proxy_type_str(proxy), proxy->id); retval = 1; } else if (defpx && *tv != *td) { memprintf(err, "overwriting %s %s which was already specified", args[0], args[1]); retval = 1; } *tv = val; return retval; } /* This function parses a "max-keep-alive-queue" statement in a proxy section. * It returns -1 if there is any error, 1 for a warning, otherwise zero. If it * does not return zero, it will write an error or warning message into a * preallocated buffer returned at . The function must be called with * pointing to the first command line word, with pointing to * the proxy being parsed, and to the default proxy or NULL. */ static int proxy_parse_max_ka_queue(char **args, int section, struct proxy *proxy, const struct proxy *defpx, const char *file, int line, char **err) { int retval; char *res; unsigned int val; retval = 0; if (*args[1] == 0) { memprintf(err, "'%s' expects expects an integer value (or -1 to disable)", args[0]); return -1; } val = strtol(args[1], &res, 0); if (*res) { memprintf(err, "'%s' : unexpected character '%c' in integer value '%s'", args[0], *res, args[1]); return -1; } if (!(proxy->cap & PR_CAP_BE)) { memprintf(err, "%s will be ignored because %s '%s' has no backend capability", args[0], proxy_type_str(proxy), proxy->id); retval = 1; } /* we store so that a user-facing value of -1 is stored as zero (default) */ proxy->max_ka_queue = val + 1; return retval; } /* This function parses a "declare" statement in a proxy section. It returns -1 * if there is any error, 1 for warning, otherwise 0. If it does not return zero, * it will write an error or warning message into a preallocated buffer returned * at . The function must be called with pointing to the first command * line word, with pointing to the proxy being parsed, and to the * default proxy or NULL. */ static int proxy_parse_declare(char **args, int section, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { /* Capture keyword wannot be declared in a default proxy. */ if (curpx == defpx) { memprintf(err, "'%s' not available in default section", args[0]); return -1; } /* Capture keyword is only available in frontend. */ if (!(curpx->cap & PR_CAP_FE)) { memprintf(err, "'%s' only available in frontend or listen section", args[0]); return -1; } /* Check mandatory second keyword. */ if (!args[1] || !*args[1]) { memprintf(err, "'%s' needs a second keyword that specify the type of declaration ('capture')", args[0]); return -1; } /* Actually, declare is only available for declaring capture * slot, but in the future it can declare maps or variables. * So, this section permits to check and switch according with * the second keyword. */ if (strcmp(args[1], "capture") == 0) { char *error = NULL; long len; struct cap_hdr *hdr; /* Check the next keyword. */ if (!args[2] || !*args[2] || (strcmp(args[2], "response") != 0 && strcmp(args[2], "request") != 0)) { memprintf(err, "'%s %s' requires a direction ('request' or 'response')", args[0], args[1]); return -1; } /* Check the 'len' keyword. */ if (!args[3] || !*args[3] || strcmp(args[3], "len") != 0) { memprintf(err, "'%s %s' requires a capture length ('len')", args[0], args[1]); return -1; } /* Check the length value. */ if (!args[4] || !*args[4]) { memprintf(err, "'%s %s': 'len' requires a numeric value that represents the " "capture length", args[0], args[1]); return -1; } /* convert the length value. */ len = strtol(args[4], &error, 10); if (*error != '\0') { memprintf(err, "'%s %s': cannot parse the length '%s'.", args[0], args[1], args[3]); return -1; } /* check length. */ if (len <= 0) { memprintf(err, "length must be > 0"); return -1; } /* register the capture. */ hdr = calloc(1, sizeof(*hdr)); if (!hdr) { memprintf(err, "proxy '%s': out of memory while registering a capture", curpx->id); return -1; } hdr->name = NULL; /* not a header capture */ hdr->namelen = 0; hdr->len = len; hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED); if (strcmp(args[2], "request") == 0) { hdr->next = curpx->req_cap; hdr->index = curpx->nb_req_cap++; curpx->req_cap = hdr; } if (strcmp(args[2], "response") == 0) { hdr->next = curpx->rsp_cap; hdr->index = curpx->nb_rsp_cap++; curpx->rsp_cap = hdr; } return 0; } else { memprintf(err, "unknown declaration type '%s' (supports 'capture')", args[1]); return -1; } } /* This function parses a "retry-on" statement */ static int proxy_parse_retry_on(char **args, int section, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { int i; if (!(*args[1])) { memprintf(err, "'%s' needs at least one keyword to specify when to retry", args[0]); return -1; } if (!(curpx->cap & PR_CAP_BE)) { memprintf(err, "'%s' only available in backend or listen section", args[0]); return -1; } curpx->retry_type = 0; for (i = 1; *(args[i]); i++) { if (strcmp(args[i], "conn-failure") == 0) curpx->retry_type |= PR_RE_CONN_FAILED; else if (strcmp(args[i], "empty-response") == 0) curpx->retry_type |= PR_RE_DISCONNECTED; else if (strcmp(args[i], "response-timeout") == 0) curpx->retry_type |= PR_RE_TIMEOUT; else if (strcmp(args[i], "401") == 0) curpx->retry_type |= PR_RE_401; else if (strcmp(args[i], "403") == 0) curpx->retry_type |= PR_RE_403; else if (strcmp(args[i], "404") == 0) curpx->retry_type |= PR_RE_404; else if (strcmp(args[i], "408") == 0) curpx->retry_type |= PR_RE_408; else if (strcmp(args[i], "425") == 0) curpx->retry_type |= PR_RE_425; else if (strcmp(args[i], "500") == 0) curpx->retry_type |= PR_RE_500; else if (strcmp(args[i], "501") == 0) curpx->retry_type |= PR_RE_501; else if (strcmp(args[i], "502") == 0) curpx->retry_type |= PR_RE_502; else if (strcmp(args[i], "503") == 0) curpx->retry_type |= PR_RE_503; else if (strcmp(args[i], "504") == 0) curpx->retry_type |= PR_RE_504; else if (strcmp(args[i], "0rtt-rejected") == 0) curpx->retry_type |= PR_RE_EARLY_ERROR; else if (strcmp(args[i], "junk-response") == 0) curpx->retry_type |= PR_RE_JUNK_REQUEST; else if (!(strcmp(args[i], "all-retryable-errors"))) curpx->retry_type |= PR_RE_CONN_FAILED | PR_RE_DISCONNECTED | PR_RE_TIMEOUT | PR_RE_500 | PR_RE_502 | PR_RE_503 | PR_RE_504 | PR_RE_EARLY_ERROR | PR_RE_JUNK_REQUEST; else if (strcmp(args[i], "none") == 0) { if (i != 1 || *args[i + 1]) { memprintf(err, "'%s' 'none' keyworld only usable alone", args[0]); return -1; } } else { memprintf(err, "'%s': unknown keyword '%s'", args[0], args[i]); return -1; } } return 0; } #ifdef TCP_KEEPCNT /* This function parses "{cli|srv}tcpka-cnt" statements */ static int proxy_parse_tcpka_cnt(char **args, int section, struct proxy *proxy, const struct proxy *defpx, const char *file, int line, char **err) { int retval; char *res; unsigned int tcpka_cnt; retval = 0; if (*args[1] == 0) { memprintf(err, "'%s' expects an integer value", args[0]); return -1; } tcpka_cnt = strtol(args[1], &res, 0); if (*res) { memprintf(err, "'%s' : unexpected character '%c' in integer value '%s'", args[0], *res, args[1]); return -1; } if (strcmp(args[0], "clitcpka-cnt") == 0) { if (!(proxy->cap & PR_CAP_FE)) { memprintf(err, "%s will be ignored because %s '%s' has no frontend capability", args[0], proxy_type_str(proxy), proxy->id); retval = 1; } proxy->clitcpka_cnt = tcpka_cnt; } else if (strcmp(args[0], "srvtcpka-cnt") == 0) { if (!(proxy->cap & PR_CAP_BE)) { memprintf(err, "%s will be ignored because %s '%s' has no backend capability", args[0], proxy_type_str(proxy), proxy->id); retval = 1; } proxy->srvtcpka_cnt = tcpka_cnt; } else { /* unreachable */ memprintf(err, "'%s': unknown keyword", args[0]); return -1; } return retval; } #endif #ifdef TCP_KEEPIDLE /* This function parses "{cli|srv}tcpka-idle" statements */ static int proxy_parse_tcpka_idle(char **args, int section, struct proxy *proxy, const struct proxy *defpx, const char *file, int line, char **err) { int retval; const char *res; unsigned int tcpka_idle; retval = 0; if (*args[1] == 0) { memprintf(err, "'%s' expects an integer value", args[0]); return -1; } res = parse_time_err(args[1], &tcpka_idle, TIME_UNIT_S); if (res == PARSE_TIME_OVER) { memprintf(err, "timer overflow in argument '%s' to '%s' (maximum value is 2147483647 ms or ~24.8 days)", args[1], args[0]); return -1; } else if (res == PARSE_TIME_UNDER) { memprintf(err, "timer underflow in argument '%s' to '%s' (minimum non-null value is 1 ms)", args[1], args[0]); return -1; } else if (res) { memprintf(err, "unexpected character '%c' in argument to <%s>.\n", *res, args[0]); return -1; } if (strcmp(args[0], "clitcpka-idle") == 0) { if (!(proxy->cap & PR_CAP_FE)) { memprintf(err, "%s will be ignored because %s '%s' has no frontend capability", args[0], proxy_type_str(proxy), proxy->id); retval = 1; } proxy->clitcpka_idle = tcpka_idle; } else if (strcmp(args[0], "srvtcpka-idle") == 0) { if (!(proxy->cap & PR_CAP_BE)) { memprintf(err, "%s will be ignored because %s '%s' has no backend capability", args[0], proxy_type_str(proxy), proxy->id); retval = 1; } proxy->srvtcpka_idle = tcpka_idle; } else { /* unreachable */ memprintf(err, "'%s': unknown keyword", args[0]); return -1; } return retval; } #endif #ifdef TCP_KEEPINTVL /* This function parses "{cli|srv}tcpka-intvl" statements */ static int proxy_parse_tcpka_intvl(char **args, int section, struct proxy *proxy, const struct proxy *defpx, const char *file, int line, char **err) { int retval; const char *res; unsigned int tcpka_intvl; retval = 0; if (*args[1] == 0) { memprintf(err, "'%s' expects an integer value", args[0]); return -1; } res = parse_time_err(args[1], &tcpka_intvl, TIME_UNIT_S); if (res == PARSE_TIME_OVER) { memprintf(err, "timer overflow in argument '%s' to '%s' (maximum value is 2147483647 ms or ~24.8 days)", args[1], args[0]); return -1; } else if (res == PARSE_TIME_UNDER) { memprintf(err, "timer underflow in argument '%s' to '%s' (minimum non-null value is 1 ms)", args[1], args[0]); return -1; } else if (res) { memprintf(err, "unexpected character '%c' in argument to <%s>.\n", *res, args[0]); return -1; } if (strcmp(args[0], "clitcpka-intvl") == 0) { if (!(proxy->cap & PR_CAP_FE)) { memprintf(err, "%s will be ignored because %s '%s' has no frontend capability", args[0], proxy_type_str(proxy), proxy->id); retval = 1; } proxy->clitcpka_intvl = tcpka_intvl; } else if (strcmp(args[0], "srvtcpka-intvl") == 0) { if (!(proxy->cap & PR_CAP_BE)) { memprintf(err, "%s will be ignored because %s '%s' has no backend capability", args[0], proxy_type_str(proxy), proxy->id); retval = 1; } proxy->srvtcpka_intvl = tcpka_intvl; } else { /* unreachable */ memprintf(err, "'%s': unknown keyword", args[0]); return -1; } return retval; } #endif /* This function inserts proxy into the tree of known proxies (regular * ones or defaults depending on px->cap & PR_CAP_DEF). The proxy's name is * used as the storing key so it must already have been initialized. */ void proxy_store_name(struct proxy *px) { struct eb_root *root = (px->cap & PR_CAP_DEF) ? &defproxy_by_name : &proxy_by_name; px->conf.by_name.key = px->id; ebis_insert(root, &px->conf.by_name); } /* Returns a pointer to the first proxy matching capabilities and id * . NULL is returned if no match is found. If is non-zero, it * only considers proxies having a table. */ struct proxy *proxy_find_by_id(int id, int cap, int table) { struct eb32_node *n; for (n = eb32_lookup(&used_proxy_id, id); n; n = eb32_next(n)) { struct proxy *px = container_of(n, struct proxy, conf.id); if (px->uuid != id) break; if ((px->cap & cap) != cap) continue; if (table && (!px->table || !px->table->size)) continue; return px; } return NULL; } /* Returns a pointer to the first proxy matching either name , or id * if begins with a '#'. NULL is returned if no match is found. * If
is non-zero, it only considers proxies having a table. The search * is made into the regular proxies, unless has PR_CAP_DEF set in which * case it's searched into the defproxy tree. */ struct proxy *proxy_find_by_name(const char *name, int cap, int table) { struct proxy *curproxy; if (*name == '#' && !(cap & PR_CAP_DEF)) { curproxy = proxy_find_by_id(atoi(name + 1), cap, table); if (curproxy) return curproxy; } else { struct eb_root *root; struct ebpt_node *node; root = (cap & PR_CAP_DEF) ? &defproxy_by_name : &proxy_by_name; for (node = ebis_lookup(root, name); node; node = ebpt_next(node)) { curproxy = container_of(node, struct proxy, conf.by_name); if (strcmp(curproxy->id, name) != 0) break; if ((curproxy->cap & cap) != cap) continue; if (table && (!curproxy->table || !curproxy->table->size)) continue; return curproxy; } } return NULL; } /* Finds the best match for a proxy with capabilities , name and id * . At most one of or may be different provided that is * valid. Either or may be left unspecified (0). The purpose is to * find a proxy based on some information from a previous configuration, across * reloads or during information exchange between peers. * * Names are looked up first if present, then IDs are compared if present. In * case of an inexact match whatever is forced in the configuration has * precedence in the following order : * - 1) forced ID (proves a renaming / change of proxy type) * - 2) proxy name+type (may indicate a move if ID differs) * - 3) automatic ID+type (may indicate a renaming) * * Depending on what is found, we can end up in the following situations : * * name id cap | possible causes * -------------+----------------- * -- -- -- | nothing found * -- -- ok | nothing found * -- ok -- | proxy deleted, ID points to next one * -- ok ok | proxy renamed, or deleted with ID pointing to next one * ok -- -- | proxy deleted, but other half with same name still here (before) * ok -- ok | proxy's ID changed (proxy moved in the config file) * ok ok -- | proxy deleted, but other half with same name still here (after) * ok ok ok | perfect match * * Upon return if is not NULL, it is zeroed then filled with up to 3 bits : * - PR_FBM_MISMATCH_ID : proxy was found but ID differs * (and ID was not zero) * - PR_FBM_MISMATCH_NAME : proxy was found by ID but name differs * (and name was not NULL) * - PR_FBM_MISMATCH_PROXYTYPE : a proxy of different type was found with * the same name and/or id * * Only a valid proxy is returned. If capabilities do not match, NULL is * returned. The caller can check to report detailed warnings / errors, * and decide whether or not to use what was found. */ struct proxy *proxy_find_best_match(int cap, const char *name, int id, int *diff) { struct proxy *byname; struct proxy *byid; if (!name && !id) return NULL; if (diff) *diff = 0; byname = byid = NULL; if (name) { byname = proxy_find_by_name(name, cap, 0); if (byname && (!id || byname->uuid == id)) return byname; } /* remaining possibilities : * - name not set * - name set but not found * - name found, but ID doesn't match. */ if (id) { byid = proxy_find_by_id(id, cap, 0); if (byid) { if (byname) { /* id+type found, name+type found, but not all 3. * ID wins only if forced, otherwise name wins. */ if (byid->options & PR_O_FORCED_ID) { if (diff) *diff |= PR_FBM_MISMATCH_NAME; return byid; } else { if (diff) *diff |= PR_FBM_MISMATCH_ID; return byname; } } /* remaining possibilities : * - name not set * - name set but not found */ if (name && diff) *diff |= PR_FBM_MISMATCH_NAME; return byid; } /* ID not found */ if (byname) { if (diff) *diff |= PR_FBM_MISMATCH_ID; return byname; } } /* All remaining possibilities will lead to NULL. If we can report more * detailed information to the caller about changed types and/or name, * we'll do it. For example, we could detect that "listen foo" was * split into "frontend foo_ft" and "backend foo_bk" if IDs are forced. * - name not set, ID not found * - name not found, ID not set * - name not found, ID not found */ if (!diff) return NULL; if (name) { byname = proxy_find_by_name(name, 0, 0); if (byname && (!id || byname->uuid == id)) *diff |= PR_FBM_MISMATCH_PROXYTYPE; } if (id) { byid = proxy_find_by_id(id, 0, 0); if (byid) { if (!name) *diff |= PR_FBM_MISMATCH_PROXYTYPE; /* only type changed */ else if (byid->options & PR_O_FORCED_ID) *diff |= PR_FBM_MISMATCH_NAME | PR_FBM_MISMATCH_PROXYTYPE; /* name and type changed */ /* otherwise it's a different proxy that was returned */ } } return NULL; } /* * This function finds a server with matching name within selected proxy. * It also checks if there are more matching servers with * requested name as this often leads into unexpected situations. */ struct server *findserver(const struct proxy *px, const char *name) { struct server *cursrv, *target = NULL; if (!px) return NULL; for (cursrv = px->srv; cursrv; cursrv = cursrv->next) { if (strcmp(cursrv->id, name) != 0) continue; if (!target) { target = cursrv; continue; } ha_alert("Refusing to use duplicated server '%s' found in proxy: %s!\n", name, px->id); return NULL; } return target; } /* * This function finds a server with matching " x " within * selected proxy . * Using the combination of proxy-uid + revision id ensures that the function * will either return the server we're expecting or NULL if it has been removed * from the proxy. */ struct server *findserver_unique_id(const struct proxy *px, int puid, uint32_t rid) { struct server *cursrv; if (!px) return NULL; for (cursrv = px->srv; cursrv; cursrv = cursrv->next) { if (cursrv->puid == puid && cursrv->rid == rid) return cursrv; } return NULL; } /* * This function finds a server with matching " x " within * selected proxy . * Using the combination of name + revision id ensures that the function will * either return the server we're expecting or NULL if it has been removed * from the proxy. */ struct server *findserver_unique_name(const struct proxy *px, const char *name, uint32_t rid) { struct server *cursrv; if (!px) return NULL; for (cursrv = px->srv; cursrv; cursrv = cursrv->next) { if (!strcmp(cursrv->id, name) && cursrv->rid == rid) return cursrv; } return NULL; } /* This function checks that the designated proxy has no http directives * enabled. It will output a warning if there are, and will fix some of them. * It returns the number of fatal errors encountered. This should be called * at the end of the configuration parsing if the proxy is not in http mode. * The argument is used to construct the error message. */ int proxy_cfg_ensure_no_http(struct proxy *curproxy) { if (curproxy->cookie_name != NULL) { ha_warning("cookie will be ignored for %s '%s' (needs 'mode http').\n", proxy_type_str(curproxy), curproxy->id); } if (isttest(curproxy->monitor_uri)) { ha_warning("monitor-uri will be ignored for %s '%s' (needs 'mode http').\n", proxy_type_str(curproxy), curproxy->id); } if (curproxy->lbprm.algo & BE_LB_NEED_HTTP) { curproxy->lbprm.algo &= ~BE_LB_ALGO; curproxy->lbprm.algo |= BE_LB_ALGO_RR; ha_warning("Layer 7 hash not possible for %s '%s' (needs 'mode http'). Falling back to round robin.\n", proxy_type_str(curproxy), curproxy->id); } if (curproxy->to_log & (LW_REQ | LW_RESP)) { curproxy->to_log &= ~(LW_REQ | LW_RESP); ha_warning("parsing [%s:%d] : HTTP log/header format not usable with %s '%s' (needs 'mode http').\n", curproxy->conf.lfs_file, curproxy->conf.lfs_line, proxy_type_str(curproxy), curproxy->id); } if (curproxy->conf.logformat_string == default_http_log_format || curproxy->conf.logformat_string == clf_http_log_format) { /* Note: we don't change the directive's file:line number */ curproxy->conf.logformat_string = default_tcp_log_format; ha_warning("parsing [%s:%d] : 'option httplog' not usable with %s '%s' (needs 'mode http'). Falling back to 'option tcplog'.\n", curproxy->conf.lfs_file, curproxy->conf.lfs_line, proxy_type_str(curproxy), curproxy->id); } else if (curproxy->conf.logformat_string == default_https_log_format) { /* Note: we don't change the directive's file:line number */ curproxy->conf.logformat_string = default_tcp_log_format; ha_warning("parsing [%s:%d] : 'option httpslog' not usable with %s '%s' (needs 'mode http'). Falling back to 'option tcplog'.\n", curproxy->conf.lfs_file, curproxy->conf.lfs_line, proxy_type_str(curproxy), curproxy->id); } return 0; } /* This function checks that the designated proxy has no log directives * enabled. It will output a warning if there are, and will fix some of them. * It returns the number of fatal errors encountered. This should be called * at the end of the configuration parsing if the proxy is not in log mode. * The argument is used to construct the error message. */ int proxy_cfg_ensure_no_log(struct proxy *curproxy) { if (curproxy->lbprm.algo & BE_LB_NEED_LOG) { curproxy->lbprm.algo &= ~BE_LB_ALGO; curproxy->lbprm.algo |= BE_LB_ALGO_RR; ha_warning("Unusable balance algorithm for %s '%s' (needs 'mode log'). Falling back to round robin.\n", proxy_type_str(curproxy), curproxy->id); } return 0; } /* Perform the most basic initialization of a proxy : * memset(), list_init(*), reset_timeouts(*). * Any new proxy or peer should be initialized via this function. */ void init_new_proxy(struct proxy *p) { memset(p, 0, sizeof(struct proxy)); p->obj_type = OBJ_TYPE_PROXY; queue_init(&p->queue, p, NULL); LIST_INIT(&p->acl); LIST_INIT(&p->http_req_rules); LIST_INIT(&p->http_res_rules); LIST_INIT(&p->http_after_res_rules); LIST_INIT(&p->redirect_rules); LIST_INIT(&p->mon_fail_cond); LIST_INIT(&p->switching_rules); LIST_INIT(&p->server_rules); LIST_INIT(&p->persist_rules); LIST_INIT(&p->sticking_rules); LIST_INIT(&p->storersp_rules); LIST_INIT(&p->tcp_req.inspect_rules); LIST_INIT(&p->tcp_rep.inspect_rules); LIST_INIT(&p->tcp_req.l4_rules); LIST_INIT(&p->tcp_req.l5_rules); MT_LIST_INIT(&p->listener_queue); LIST_INIT(&p->loggers); LIST_INIT(&p->logformat); LIST_INIT(&p->logformat_sd); LIST_INIT(&p->format_unique_id); LIST_INIT(&p->logformat_error); LIST_INIT(&p->conf.bind); LIST_INIT(&p->conf.listeners); LIST_INIT(&p->conf.errors); LIST_INIT(&p->conf.args.list); LIST_INIT(&p->filter_configs); LIST_INIT(&p->tcpcheck_rules.preset_vars); p->defsrv.id = "default-server"; p->conf.used_listener_id = EB_ROOT; p->conf.used_server_id = EB_ROOT; p->used_server_addr = EB_ROOT_UNIQUE; /* Timeouts are defined as -1 */ proxy_reset_timeouts(p); p->tcp_rep.inspect_delay = TICK_ETERNITY; /* initial uuid is unassigned (-1) */ p->uuid = -1; /* Default to only allow L4 retries */ p->retry_type = PR_RE_CONN_FAILED; p->extra_counters_fe = NULL; p->extra_counters_be = NULL; HA_RWLOCK_INIT(&p->lock); /* initialize the default settings */ proxy_preset_defaults(p); } /* Preset default settings onto proxy . */ void proxy_preset_defaults(struct proxy *defproxy) { defproxy->mode = PR_MODE_TCP; defproxy->flags = 0; if (!(defproxy->cap & PR_CAP_INT)) { defproxy->maxconn = cfg_maxpconn; defproxy->conn_retries = CONN_RETRIES; } defproxy->redispatch_after = 0; defproxy->options = PR_O_REUSE_SAFE; if (defproxy->cap & PR_CAP_INT) defproxy->options2 |= PR_O2_INDEPSTR; defproxy->max_out_conns = MAX_SRV_LIST; defproxy->defsrv.check.inter = DEF_CHKINTR; defproxy->defsrv.check.fastinter = 0; defproxy->defsrv.check.downinter = 0; defproxy->defsrv.agent.inter = DEF_CHKINTR; defproxy->defsrv.agent.fastinter = 0; defproxy->defsrv.agent.downinter = 0; defproxy->defsrv.check.rise = DEF_RISETIME; defproxy->defsrv.check.fall = DEF_FALLTIME; defproxy->defsrv.agent.rise = DEF_AGENT_RISETIME; defproxy->defsrv.agent.fall = DEF_AGENT_FALLTIME; defproxy->defsrv.check.port = 0; defproxy->defsrv.agent.port = 0; defproxy->defsrv.maxqueue = 0; defproxy->defsrv.minconn = 0; defproxy->defsrv.maxconn = 0; defproxy->defsrv.max_reuse = -1; defproxy->defsrv.max_idle_conns = -1; defproxy->defsrv.pool_purge_delay = 5000; defproxy->defsrv.slowstart = 0; defproxy->defsrv.onerror = DEF_HANA_ONERR; defproxy->defsrv.consecutive_errors_limit = DEF_HANA_ERRLIMIT; defproxy->defsrv.uweight = defproxy->defsrv.iweight = 1; LIST_INIT(&defproxy->defsrv.pp_tlvs); defproxy->email_alert.level = LOG_ALERT; defproxy->load_server_state_from_file = PR_SRV_STATE_FILE_UNSPEC; if (defproxy->cap & PR_CAP_INT) defproxy->timeout.connect = 5000; } /* Frees all dynamic settings allocated on a default proxy that's about to be * destroyed. This is a subset of the complete proxy deinit code, but these * should probably be merged ultimately. Note that most of the fields are not * even reset, so extreme care is required here, and calling * proxy_preset_defaults() afterwards would be safer. */ void proxy_free_defaults(struct proxy *defproxy) { struct acl *acl, *aclb; struct logger *log, *logb; struct cap_hdr *h,*h_next; ha_free(&defproxy->id); ha_free(&defproxy->conf.file); ha_free((char **)&defproxy->defsrv.conf.file); ha_free(&defproxy->check_command); ha_free(&defproxy->check_path); ha_free(&defproxy->cookie_name); ha_free(&defproxy->rdp_cookie_name); ha_free(&defproxy->dyncookie_key); ha_free(&defproxy->cookie_domain); ha_free(&defproxy->cookie_attrs); ha_free(&defproxy->lbprm.arg_str); ha_free(&defproxy->capture_name); istfree(&defproxy->monitor_uri); ha_free(&defproxy->defbe.name); ha_free(&defproxy->conn_src.iface_name); istfree(&defproxy->server_id_hdr_name); http_ext_clean(defproxy); list_for_each_entry_safe(acl, aclb, &defproxy->acl, list) { LIST_DELETE(&acl->list); prune_acl(acl); free(acl); } free_act_rules(&defproxy->tcp_req.inspect_rules); free_act_rules(&defproxy->tcp_rep.inspect_rules); free_act_rules(&defproxy->tcp_req.l4_rules); free_act_rules(&defproxy->tcp_req.l5_rules); free_act_rules(&defproxy->http_req_rules); free_act_rules(&defproxy->http_res_rules); free_act_rules(&defproxy->http_after_res_rules); h = defproxy->req_cap; while (h) { h_next = h->next; free(h->name); pool_destroy(h->pool); free(h); h = h_next; } h = defproxy->rsp_cap; while (h) { h_next = h->next; free(h->name); pool_destroy(h->pool); free(h); h = h_next; } if (defproxy->conf.logformat_string != default_http_log_format && defproxy->conf.logformat_string != default_tcp_log_format && defproxy->conf.logformat_string != clf_http_log_format && defproxy->conf.logformat_string != default_https_log_format) { ha_free(&defproxy->conf.logformat_string); } if (defproxy->conf.logformat_sd_string != default_rfc5424_sd_log_format) ha_free(&defproxy->conf.logformat_sd_string); list_for_each_entry_safe(log, logb, &defproxy->loggers, list) { LIST_DEL_INIT(&log->list); free_logger(log); } ha_free(&defproxy->conf.uniqueid_format_string); ha_free(&defproxy->conf.error_logformat_string); ha_free(&defproxy->conf.lfs_file); ha_free(&defproxy->conf.lfsd_file); ha_free(&defproxy->conf.uif_file); ha_free(&defproxy->conf.elfs_file); chunk_destroy(&defproxy->log_tag); free_email_alert(defproxy); proxy_release_conf_errors(defproxy); deinit_proxy_tcpcheck(defproxy); /* FIXME: we cannot free uri_auth because it might already be used by * another proxy (legacy code for stats URI ...). Refcount anyone ? */ } /* delete a defproxy from the tree if still in it, frees its content and its * storage. Nothing is done if is NULL or if it doesn't have PR_CAP_DEF * set, allowing to pass it the direct result of a lookup function. */ void proxy_destroy_defaults(struct proxy *px) { if (!px) return; if (!(px->cap & PR_CAP_DEF)) return; BUG_ON(px->conf.refcount != 0); ebpt_delete(&px->conf.by_name); proxy_free_defaults(px); free(px); } /* delete all unreferenced default proxies. A default proxy is unreferenced if * its refcount is equal to zero. */ void proxy_destroy_all_unref_defaults() { struct ebpt_node *n; n = ebpt_first(&defproxy_by_name); while (n) { struct proxy *px = container_of(n, struct proxy, conf.by_name); BUG_ON(!(px->cap & PR_CAP_DEF)); n = ebpt_next(n); if (!px->conf.refcount) proxy_destroy_defaults(px); } } /* 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. */ void proxy_ref_defaults(struct proxy *px, struct proxy *defpx) { if (px->defpx == defpx) return; BUG_ON(px->defpx != NULL); px->defpx = defpx; defpx->conf.refcount++; } /* 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 * thread safe and is performed during deinit staged only. */ void proxy_unref_defaults(struct proxy *px) { if (px->defpx == NULL) return; if (!--px->defpx->conf.refcount) proxy_destroy_defaults(px->defpx); px->defpx = NULL; } /* Allocates a new proxy of type . * Returns the proxy instance on success. On error, NULL is returned. */ struct proxy *alloc_new_proxy(const char *name, unsigned int cap, char **errmsg) { struct proxy *curproxy; if ((curproxy = calloc(1, sizeof(*curproxy))) == NULL) { memprintf(errmsg, "proxy '%s': out of memory", name); goto fail; } init_new_proxy(curproxy); curproxy->last_change = ns_to_sec(now_ns); curproxy->id = strdup(name); curproxy->cap = cap; if (!(cap & PR_CAP_INT)) proxy_store_name(curproxy); done: return curproxy; fail: /* Note: in case of fatal error here, we WILL make valgrind unhappy, * but its not worth trying to unroll everything here just before * quitting. */ free(curproxy); return NULL; } /* Copy the proxy settings from to . * Returns 0 on success. * Returns 1 on error. will be allocated with an error description. */ static int proxy_defproxy_cpy(struct proxy *curproxy, const struct proxy *defproxy, char **errmsg) { struct logger *tmplogger; char *tmpmsg = NULL; /* set default values from the specified default proxy */ srv_settings_cpy(&curproxy->defsrv, &defproxy->defsrv, 0); curproxy->flags = (defproxy->flags & PR_FL_DISABLED); /* Only inherit from disabled flag */ curproxy->options = defproxy->options; curproxy->options2 = defproxy->options2; curproxy->no_options = defproxy->no_options; curproxy->no_options2 = defproxy->no_options2; curproxy->retry_type = defproxy->retry_type; curproxy->tcp_req.inspect_delay = defproxy->tcp_req.inspect_delay; curproxy->tcp_rep.inspect_delay = defproxy->tcp_rep.inspect_delay; http_ext_clean(curproxy); http_ext_dup(defproxy, curproxy); if (isttest(defproxy->server_id_hdr_name)) curproxy->server_id_hdr_name = istdup(defproxy->server_id_hdr_name); /* initialize error relocations */ if (!proxy_dup_default_conf_errors(curproxy, defproxy, &tmpmsg)) { memprintf(errmsg, "proxy '%s' : %s", curproxy->id, tmpmsg); free(tmpmsg); return 1; } if (curproxy->cap & PR_CAP_FE) { curproxy->maxconn = defproxy->maxconn; curproxy->backlog = defproxy->backlog; curproxy->fe_sps_lim = defproxy->fe_sps_lim; curproxy->to_log = defproxy->to_log & ~LW_COOKIE & ~LW_REQHDR & ~ LW_RSPHDR; curproxy->max_out_conns = defproxy->max_out_conns; curproxy->clitcpka_cnt = defproxy->clitcpka_cnt; curproxy->clitcpka_idle = defproxy->clitcpka_idle; curproxy->clitcpka_intvl = defproxy->clitcpka_intvl; } if (curproxy->cap & PR_CAP_BE) { curproxy->lbprm.algo = defproxy->lbprm.algo; curproxy->lbprm.hash_balance_factor = defproxy->lbprm.hash_balance_factor; curproxy->fullconn = defproxy->fullconn; curproxy->conn_retries = defproxy->conn_retries; curproxy->redispatch_after = defproxy->redispatch_after; curproxy->max_ka_queue = defproxy->max_ka_queue; curproxy->tcpcheck_rules.flags = (defproxy->tcpcheck_rules.flags & ~TCPCHK_RULES_UNUSED_RS); curproxy->tcpcheck_rules.list = defproxy->tcpcheck_rules.list; if (!LIST_ISEMPTY(&defproxy->tcpcheck_rules.preset_vars)) { if (!dup_tcpcheck_vars(&curproxy->tcpcheck_rules.preset_vars, &defproxy->tcpcheck_rules.preset_vars)) { memprintf(errmsg, "proxy '%s': failed to duplicate tcpcheck preset-vars", curproxy->id); return 1; } } curproxy->ck_opts = defproxy->ck_opts; if (defproxy->cookie_name) curproxy->cookie_name = strdup(defproxy->cookie_name); curproxy->cookie_len = defproxy->cookie_len; if (defproxy->dyncookie_key) curproxy->dyncookie_key = strdup(defproxy->dyncookie_key); if (defproxy->cookie_domain) curproxy->cookie_domain = strdup(defproxy->cookie_domain); if (defproxy->cookie_maxidle) curproxy->cookie_maxidle = defproxy->cookie_maxidle; if (defproxy->cookie_maxlife) curproxy->cookie_maxlife = defproxy->cookie_maxlife; if (defproxy->rdp_cookie_name) curproxy->rdp_cookie_name = strdup(defproxy->rdp_cookie_name); curproxy->rdp_cookie_len = defproxy->rdp_cookie_len; if (defproxy->cookie_attrs) curproxy->cookie_attrs = strdup(defproxy->cookie_attrs); if (defproxy->lbprm.arg_str) curproxy->lbprm.arg_str = strdup(defproxy->lbprm.arg_str); curproxy->lbprm.arg_len = defproxy->lbprm.arg_len; curproxy->lbprm.arg_opt1 = defproxy->lbprm.arg_opt1; curproxy->lbprm.arg_opt2 = defproxy->lbprm.arg_opt2; curproxy->lbprm.arg_opt3 = defproxy->lbprm.arg_opt3; if (defproxy->conn_src.iface_name) curproxy->conn_src.iface_name = strdup(defproxy->conn_src.iface_name); curproxy->conn_src.iface_len = defproxy->conn_src.iface_len; curproxy->conn_src.opts = defproxy->conn_src.opts; #if defined(CONFIG_HAP_TRANSPARENT) curproxy->conn_src.tproxy_addr = defproxy->conn_src.tproxy_addr; #endif curproxy->load_server_state_from_file = defproxy->load_server_state_from_file; curproxy->srvtcpka_cnt = defproxy->srvtcpka_cnt; curproxy->srvtcpka_idle = defproxy->srvtcpka_idle; curproxy->srvtcpka_intvl = defproxy->srvtcpka_intvl; } if (curproxy->cap & PR_CAP_FE) { if (defproxy->capture_name) curproxy->capture_name = strdup(defproxy->capture_name); curproxy->capture_namelen = defproxy->capture_namelen; curproxy->capture_len = defproxy->capture_len; curproxy->nb_req_cap = defproxy->nb_req_cap; curproxy->req_cap = defproxy->req_cap; curproxy->nb_rsp_cap = defproxy->nb_rsp_cap; curproxy->rsp_cap = defproxy->rsp_cap; } if (curproxy->cap & PR_CAP_FE) { curproxy->timeout.client = defproxy->timeout.client; curproxy->timeout.client_hs = defproxy->timeout.client_hs; curproxy->timeout.clientfin = defproxy->timeout.clientfin; curproxy->timeout.tarpit = defproxy->timeout.tarpit; curproxy->timeout.httpreq = defproxy->timeout.httpreq; curproxy->timeout.httpka = defproxy->timeout.httpka; if (isttest(defproxy->monitor_uri)) curproxy->monitor_uri = istdup(defproxy->monitor_uri); if (defproxy->defbe.name) curproxy->defbe.name = strdup(defproxy->defbe.name); /* get either a pointer to the logformat string or a copy of it */ curproxy->conf.logformat_string = defproxy->conf.logformat_string; if (curproxy->conf.logformat_string && curproxy->conf.logformat_string != default_http_log_format && curproxy->conf.logformat_string != default_tcp_log_format && curproxy->conf.logformat_string != clf_http_log_format && curproxy->conf.logformat_string != default_https_log_format) curproxy->conf.logformat_string = strdup(curproxy->conf.logformat_string); if (defproxy->conf.lfs_file) { curproxy->conf.lfs_file = strdup(defproxy->conf.lfs_file); curproxy->conf.lfs_line = defproxy->conf.lfs_line; } /* get either a pointer to the logformat string for RFC5424 structured-data or a copy of it */ curproxy->conf.logformat_sd_string = defproxy->conf.logformat_sd_string; if (curproxy->conf.logformat_sd_string && curproxy->conf.logformat_sd_string != default_rfc5424_sd_log_format) curproxy->conf.logformat_sd_string = strdup(curproxy->conf.logformat_sd_string); if (defproxy->conf.lfsd_file) { curproxy->conf.lfsd_file = strdup(defproxy->conf.lfsd_file); curproxy->conf.lfsd_line = defproxy->conf.lfsd_line; } curproxy->conf.error_logformat_string = defproxy->conf.error_logformat_string; if (curproxy->conf.error_logformat_string) curproxy->conf.error_logformat_string = strdup(curproxy->conf.error_logformat_string); if (defproxy->conf.elfs_file) { curproxy->conf.elfs_file = strdup(defproxy->conf.elfs_file); curproxy->conf.elfs_line = defproxy->conf.elfs_line; } } if (curproxy->cap & PR_CAP_BE) { curproxy->timeout.connect = defproxy->timeout.connect; curproxy->timeout.server = defproxy->timeout.server; curproxy->timeout.serverfin = defproxy->timeout.serverfin; curproxy->timeout.check = defproxy->timeout.check; curproxy->timeout.queue = defproxy->timeout.queue; curproxy->timeout.tarpit = defproxy->timeout.tarpit; curproxy->timeout.httpreq = defproxy->timeout.httpreq; curproxy->timeout.httpka = defproxy->timeout.httpka; curproxy->timeout.tunnel = defproxy->timeout.tunnel; curproxy->conn_src.source_addr = defproxy->conn_src.source_addr; } curproxy->mode = defproxy->mode; curproxy->uri_auth = defproxy->uri_auth; /* for stats */ /* copy default loggers to curproxy */ list_for_each_entry(tmplogger, &defproxy->loggers, list) { struct logger *node = dup_logger(tmplogger); if (!node) { memprintf(errmsg, "proxy '%s': out of memory", curproxy->id); return 1; } LIST_APPEND(&curproxy->loggers, &node->list); } curproxy->conf.uniqueid_format_string = defproxy->conf.uniqueid_format_string; if (curproxy->conf.uniqueid_format_string) curproxy->conf.uniqueid_format_string = strdup(curproxy->conf.uniqueid_format_string); chunk_dup(&curproxy->log_tag, &defproxy->log_tag); if (defproxy->conf.uif_file) { curproxy->conf.uif_file = strdup(defproxy->conf.uif_file); curproxy->conf.uif_line = defproxy->conf.uif_line; } /* copy default header unique id */ if (isttest(defproxy->header_unique_id)) { const struct ist copy = istdup(defproxy->header_unique_id); if (!isttest(copy)) { memprintf(errmsg, "proxy '%s': out of memory for unique-id-header", curproxy->id); return 1; } curproxy->header_unique_id = copy; } /* default compression options */ if (defproxy->comp != NULL) { curproxy->comp = calloc(1, sizeof(*curproxy->comp)); if (!curproxy->comp) { memprintf(errmsg, "proxy '%s': out of memory for default compression options", curproxy->id); return 1; } curproxy->comp->algos_res = defproxy->comp->algos_res; curproxy->comp->algo_req = defproxy->comp->algo_req; curproxy->comp->types_res = defproxy->comp->types_res; curproxy->comp->types_req = defproxy->comp->types_req; curproxy->comp->flags = defproxy->comp->flags; } if (defproxy->check_path) curproxy->check_path = strdup(defproxy->check_path); if (defproxy->check_command) curproxy->check_command = strdup(defproxy->check_command); if (defproxy->email_alert.mailers.name) curproxy->email_alert.mailers.name = strdup(defproxy->email_alert.mailers.name); if (defproxy->email_alert.from) curproxy->email_alert.from = strdup(defproxy->email_alert.from); if (defproxy->email_alert.to) curproxy->email_alert.to = strdup(defproxy->email_alert.to); if (defproxy->email_alert.myhostname) curproxy->email_alert.myhostname = strdup(defproxy->email_alert.myhostname); curproxy->email_alert.level = defproxy->email_alert.level; curproxy->email_alert.set = defproxy->email_alert.set; return 0; } /* Allocates a new proxy of type found at position , * preset it from the defaults of and returns it. In case of error, * an alert is printed and NULL is returned. */ struct proxy *parse_new_proxy(const char *name, unsigned int cap, const char *file, int linenum, const struct proxy *defproxy) { struct proxy *curproxy = NULL; char *errmsg = NULL; if (!(curproxy = alloc_new_proxy(name, cap, &errmsg))) { ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg); free(errmsg); return NULL; } if (defproxy) { if (proxy_defproxy_cpy(curproxy, defproxy, &errmsg)) { ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg); free(errmsg); ha_free(&curproxy); return NULL; } } curproxy->conf.args.file = curproxy->conf.file = strdup(file); curproxy->conf.args.line = curproxy->conf.line = linenum; return curproxy; } /* to be called under the proxy lock after pausing some listeners. This will * automatically update the p->flags flag */ void proxy_cond_pause(struct proxy *p) { if (p->li_ready) return; p->flags |= PR_FL_PAUSED; } /* to be called under the proxy lock after resuming some listeners. This will * automatically update the p->flags flag */ void proxy_cond_resume(struct proxy *p) { if (!p->li_ready) return; p->flags &= ~PR_FL_PAUSED; } /* to be called under the proxy lock after stopping some listeners. This will * automatically update the p->flags flag after stopping the last one, and * will emit a log indicating the proxy's condition. The function is idempotent * so that it will not emit multiple logs; a proxy will be disabled only once. */ void proxy_cond_disable(struct proxy *p) { if (p->flags & (PR_FL_DISABLED|PR_FL_STOPPED)) return; if (p->li_ready + p->li_paused > 0) return; p->flags |= PR_FL_STOPPED; /* Note: syslog proxies use their own loggers so while it's somewhat OK * to report them being stopped as a warning, we must not spam their log * servers which are in fact production servers. For other types (CLI, * peers, etc) we must not report them at all as they're not really on * the data plane but on the control plane. */ if ((p->mode == PR_MODE_TCP || p->mode == PR_MODE_HTTP || p->mode == PR_MODE_SYSLOG) && !(p->cap & PR_CAP_INT)) ha_warning("Proxy %s stopped (cumulated conns: FE: %lld, BE: %lld).\n", p->id, p->fe_counters.cum_conn, p->be_counters.cum_conn); if ((p->mode == PR_MODE_TCP || p->mode == PR_MODE_HTTP) && !(p->cap & PR_CAP_INT)) send_log(p, LOG_WARNING, "Proxy %s stopped (cumulated conns: FE: %lld, BE: %lld).\n", p->id, p->fe_counters.cum_conn, p->be_counters.cum_conn); if (p->table && p->table->size && p->table->sync_task) task_wakeup(p->table->sync_task, TASK_WOKEN_MSG); if (p->task) task_wakeup(p->task, TASK_WOKEN_MSG); } /* * This is the proxy management task. It enables proxies when there are enough * free streams, or stops them when the table is full. It is designed to be * called as a task which is woken up upon stopping or when rate limiting must * be enforced. */ struct task *manage_proxy(struct task *t, void *context, unsigned int state) { struct proxy *p = context; int next = TICK_ETERNITY; unsigned int wait; /* We should periodically try to enable listeners waiting for a * global resource here. */ /* If the proxy holds a stick table, we need to purge all unused * entries. These are all the ones in the table with ref_cnt == 0 * and all the ones in the pool used to allocate new entries. Any * entry attached to an existing stream waiting for a store will * be in neither list. Any entry being dumped will have ref_cnt > 0. * However we protect tables that are being synced to peers. */ if (unlikely(stopping && (p->flags & (PR_FL_DISABLED|PR_FL_STOPPED)) && p->table && p->table->current)) { if (!p->table->refcnt) { /* !table->refcnt means there * is no more pending full resync * to push to a new process and * we are free to flush the table. */ int budget; int cleaned_up; /* We purposely enforce a budget limitation since we don't want * to spend too much time purging old entries * * This is known to cause the watchdog to occasionnaly trigger if * the table is huge and all entries become available for purge * at the same time * * Moreover, we must also anticipate the pool_gc() call which * will also be much slower if there is too much work at once */ budget = MIN(p->table->current, (1 << 15)); /* max: 32K */ cleaned_up = stktable_trash_oldest(p->table, budget); if (cleaned_up) { /* immediately release freed memory since we are stopping */ pool_gc(NULL); if (cleaned_up > (budget / 2)) { /* most of the budget was used to purge entries, * it is very likely that there are still trashable * entries in the table, reschedule a new cleanup * attempt ASAP */ t->expire = TICK_ETERNITY; task_wakeup(t, TASK_WOKEN_RES); return t; } } } if (p->table->current) { /* some entries still remain but are not yet available * for cleanup, let's recheck in one second */ next = tick_first(next, tick_add(now_ms, 1000)); } } /* the rest below is just for frontends */ if (!(p->cap & PR_CAP_FE)) goto out; /* check the various reasons we may find to block the frontend */ if (unlikely(p->feconn >= p->maxconn)) goto out; if (p->fe_sps_lim && (wait = next_event_delay(&p->fe_sess_per_sec, p->fe_sps_lim, 0))) { /* we're blocking because a limit was reached on the number of * requests/s on the frontend. We want to re-check ASAP, which * means in 1 ms before estimated expiration date, because the * timer will have settled down. */ next = tick_first(next, tick_add(now_ms, wait)); goto out; } /* The proxy is not limited so we can re-enable any waiting listener */ dequeue_proxy_listeners(p); out: t->expire = next; task_queue(t); return t; } static int proxy_parse_grace(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) { const char *res; if (!*args[1]) { memprintf(err, "'%s' expects