/* * 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 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 */ unsigned int error_snapshot_id = 0; /* global ID assigned to each error then incremented */ /* 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_proxy", PR_O_HTTP_PROXY, PR_CAP_FE | PR_CAP_BE, 0, PR_MODE_HTTP }, { "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 }, { "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 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 CONFIG_HAP_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 }, { "independant-streams", PR_O2_INDEPSTR, PR_CAP_FE|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 }, { "http-use-htx", PR_O2_USE_HTX, PR_CAP_FE|PR_CAP_BE, 0, PR_MODE_HTTP }, { NULL, 0, 0, 0 } }; /* * 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, or "proxy" for all other * cases including the proxies declared in "listen" mode. */ const char *proxy_cap_str(int cap) { 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_HEALTH) return "health"; else if (mode == PR_MODE_CLI) return "cli"; else return "unknown"; } /* * This function scans the list of backends and servers to retrieve the first * backend and the first server with the given names, and sets them in both * parameters. It returns zero if either is not found, or non-zero and sets * the ones it did not found to NULL. If a NULL pointer is passed for the * backend, only the pointer to the server will be updated. */ int get_backend_server(const char *bk_name, const char *sv_name, struct proxy **bk, struct server **sv) { struct proxy *p; struct server *s; int sid; *sv = NULL; sid = -1; if (*sv_name == '#') sid = atoi(sv_name + 1); p = proxy_be_by_name(bk_name); if (bk) *bk = p; if (!p) return 0; for (s = p->srv; s; s = s->next) if ((sid >= 0 && s->puid == sid) || (sid < 0 && strcmp(s->id, sv_name) == 0)) break; *sv = s; if (!s) return 0; return 1; } /* 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, struct proxy *defpx, const char *file, int line, char **err) { unsigned timeout; int retval, cap; const char *res, *name; int *tv = NULL; int *td = NULL; int warn = 0; 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") || (!strcmp(args[0], "clitimeout") && (warn = WARN_CLITO_DEPRECATED))) { name = "client"; tv = &proxy->timeout.client; td = &defpx->timeout.client; cap = PR_CAP_FE; } else if (!strcmp(args[0], "tarpit")) { tv = &proxy->timeout.tarpit; td = &defpx->timeout.tarpit; cap = PR_CAP_FE | PR_CAP_BE; } else if (!strcmp(args[0], "http-keep-alive")) { tv = &proxy->timeout.httpka; td = &defpx->timeout.httpka; cap = PR_CAP_FE | PR_CAP_BE; } else if (!strcmp(args[0], "http-request")) { tv = &proxy->timeout.httpreq; td = &defpx->timeout.httpreq; cap = PR_CAP_FE | PR_CAP_BE; } else if (!strcmp(args[0], "server") || (!strcmp(args[0], "srvtimeout") && (warn = WARN_SRVTO_DEPRECATED))) { name = "server"; tv = &proxy->timeout.server; td = &defpx->timeout.server; cap = PR_CAP_BE; } else if (!strcmp(args[0], "connect") || (!strcmp(args[0], "contimeout") && (warn = WARN_CONTO_DEPRECATED))) { name = "connect"; tv = &proxy->timeout.connect; td = &defpx->timeout.connect; cap = PR_CAP_BE; } else if (!strcmp(args[0], "check")) { tv = &proxy->timeout.check; td = &defpx->timeout.check; cap = PR_CAP_BE; } else if (!strcmp(args[0], "queue")) { tv = &proxy->timeout.queue; td = &defpx->timeout.queue; cap = PR_CAP_BE; } else if (!strcmp(args[0], "tunnel")) { tv = &proxy->timeout.tunnel; td = &defpx->timeout.tunnel; cap = PR_CAP_BE; } else if (!strcmp(args[0], "client-fin")) { tv = &proxy->timeout.clientfin; td = &defpx->timeout.clientfin; cap = PR_CAP_FE; } else if (!strcmp(args[0], "server-fin")) { tv = &proxy->timeout.serverfin; td = &defpx->timeout.serverfin; cap = PR_CAP_BE; } else { memprintf(err, "'timeout' supports 'client', 'server', 'connect', 'check', " "'queue', '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) { 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; } else if (warn) { if (!already_warned(warn)) { memprintf(err, "the '%s' directive is now deprecated in favor of 'timeout %s', and will not be supported in future versions.", args[0], 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, struct proxy *defpx, const char *file, int line, char **err) { int retval, cap; char *res; unsigned int *tv = NULL; 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; cap = PR_CAP_FE; } 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 & cap)) { memprintf(err, "%s %s will be ignored because %s '%s' has no %s capability", args[0], args[1], proxy_type_str(proxy), proxy->id, (cap & PR_CAP_BE) ? "backend" : "frontend"); 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, 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, 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 keywork 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)); 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 inserts proxy into the tree of known proxies. The proxy's * name is used as the storing key so it must already have been initialized. */ void proxy_store_name(struct proxy *px) { px->conf.by_name.key = px->id; ebis_insert(&proxy_by_name, &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.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. */ struct proxy *proxy_find_by_name(const char *name, int cap, int table) { struct proxy *curproxy; if (*name == '#') { curproxy = proxy_find_by_id(atoi(name + 1), cap, table); if (curproxy) return curproxy; } else { struct ebpt_node *node; for (node = ebis_lookup(&proxy_by_name, 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.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)) 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 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("config : cookie will be ignored for %s '%s' (needs 'mode http').\n", proxy_type_str(curproxy), curproxy->id); } if (curproxy->rsp_exp != NULL) { ha_warning("config : server regular expressions will be ignored for %s '%s' (needs 'mode http').\n", proxy_type_str(curproxy), curproxy->id); } if (curproxy->req_exp != NULL) { ha_warning("config : client regular expressions will be ignored for %s '%s' (needs 'mode http').\n", proxy_type_str(curproxy), curproxy->id); } if (curproxy->monitor_uri != NULL) { ha_warning("config : 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("config : 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); } 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; p->pendconns = EB_ROOT; LIST_INIT(&p->acl); LIST_INIT(&p->http_req_rules); LIST_INIT(&p->http_res_rules); LIST_INIT(&p->block_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); LIST_INIT(&p->req_add); LIST_INIT(&p->rsp_add); LIST_INIT(&p->listener_queue); LIST_INIT(&p->logsrvs); LIST_INIT(&p->logformat); LIST_INIT(&p->logformat_sd); LIST_INIT(&p->format_unique_id); LIST_INIT(&p->conf.bind); LIST_INIT(&p->conf.listeners); LIST_INIT(&p->conf.args.list); LIST_INIT(&p->tcpcheck_rules); LIST_INIT(&p->filter_configs); /* Timeouts are defined as -1 */ proxy_reset_timeouts(p); p->tcp_rep.inspect_delay = TICK_ETERNITY; /* initial uuid is unassigned (-1) */ p->uuid = -1; HA_SPIN_INIT(&p->lock); } /* * This function creates all proxy sockets. It should be done very early, * typically before privileges are dropped. The sockets will be registered * but not added to any fd_set, in order not to loose them across the fork(). * The proxies also start in READY state because they all have their listeners * bound. * * Its return value is composed from ERR_NONE, ERR_RETRYABLE and ERR_FATAL. * Retryable errors will only be printed if is not zero. */ int start_proxies(int verbose) { struct proxy *curproxy; struct listener *listener; int lerr, err = ERR_NONE; int pxerr; char msg[100]; for (curproxy = proxies_list; curproxy != NULL; curproxy = curproxy->next) { if (curproxy->state != PR_STNEW) continue; /* already initialized */ pxerr = 0; list_for_each_entry(listener, &curproxy->conf.listeners, by_fe) { if (listener->state != LI_ASSIGNED) continue; /* already started */ lerr = listener->proto->bind(listener, msg, sizeof(msg)); /* errors are reported if is set or if they are fatal */ if (verbose || (lerr & (ERR_FATAL | ERR_ABORT))) { if (lerr & ERR_ALERT) ha_alert("Starting %s %s: %s\n", proxy_type_str(curproxy), curproxy->id, msg); else if (lerr & ERR_WARN) ha_warning("Starting %s %s: %s\n", proxy_type_str(curproxy), curproxy->id, msg); } err |= lerr; if (lerr & (ERR_ABORT | ERR_FATAL)) { pxerr |= 1; break; } else if (lerr & ERR_CODE) { pxerr |= 1; continue; } } if (!pxerr) { curproxy->state = PR_STREADY; send_log(curproxy, LOG_NOTICE, "Proxy %s started.\n", curproxy->id); } if (err & ERR_ABORT) break; } return err; } /* * 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 short 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. */ /* first, let's check if we need to stop the proxy */ if (unlikely(stopping && p->state != PR_STSTOPPED)) { int t; t = tick_remain(now_ms, p->stop_time); if (t == 0) { ha_warning("Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n", p->id, p->fe_counters.cum_conn, p->be_counters.cum_conn); send_log(p, LOG_WARNING, "Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n", p->id, p->fe_counters.cum_conn, p->be_counters.cum_conn); stop_proxy(p); /* try to free more memory */ pool_gc(NULL); } else { next = tick_first(next, p->stop_time); } } /* 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->state == PR_STSTOPPED && p->table.current)) { if (!p->table.syncing) { stktable_trash_oldest(&p->table, p->table.current); pool_gc(NULL); } if (p->table.current) { /* some entries still remain, 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)) { if (p->state == PR_STREADY) p->state = PR_STFULL; goto out; } /* OK we have no reason to block, so let's unblock if we were blocking */ if (p->state == PR_STFULL) p->state = PR_STREADY; 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 */ if (!LIST_ISEMPTY(&p->listener_queue)) dequeue_all_listeners(&p->listener_queue); out: t->expire = next; task_queue(t); return t; } static int proxy_parse_hard_stop_after(char **args, int section_type, struct proxy *curpx, struct proxy *defpx, const char *file, int line, char **err) { const char *res; if (!*args[1]) { memprintf(err, "'%s' expects