diff options
Diffstat (limited to 'src/server.c')
-rw-r--r-- | src/server.c | 1295 |
1 files changed, 854 insertions, 441 deletions
diff --git a/src/server.c b/src/server.c index 9196fac..caf2f40 100644 --- a/src/server.c +++ b/src/server.c @@ -28,6 +28,7 @@ #include <haproxy/dict-t.h> #include <haproxy/errors.h> #include <haproxy/global.h> +#include <haproxy/guid.h> #include <haproxy/log.h> #include <haproxy/mailers.h> #include <haproxy/namespace.h> @@ -140,18 +141,10 @@ const char *srv_op_st_chg_cause(enum srv_op_st_chg_cause cause) int srv_downtime(const struct server *s) { - if ((s->cur_state != SRV_ST_STOPPED) || s->last_change >= ns_to_sec(now_ns)) // ignore negative time + if ((s->cur_state != SRV_ST_STOPPED) || s->counters.last_change >= ns_to_sec(now_ns)) // ignore negative time return s->down_time; - return ns_to_sec(now_ns) - s->last_change + s->down_time; -} - -int srv_lastsession(const struct server *s) -{ - if (s->counters.last_sess) - return ns_to_sec(now_ns) - s->counters.last_sess; - - return -1; + return ns_to_sec(now_ns) - s->counters.last_change + s->down_time; } int srv_getinter(const struct check *check) @@ -170,7 +163,7 @@ int srv_getinter(const struct check *check) /* Update server's addr:svc_port tuple in INET context * - * Must be called under thread isolation to ensure consistent readings accross + * Must be called under thread isolation to ensure consistent readings across * all threads (addr:svc_port might be read without srv lock being held). */ static void _srv_set_inetaddr_port(struct server *srv, @@ -184,6 +177,11 @@ static void _srv_set_inetaddr_port(struct server *srv, else srv->flags &= ~SRV_F_MAPPORTS; + if (srv->proxy->lbprm.update_server_eweight) { + /* some balancers (chash in particular) may use the addr in their routing decisions */ + srv->proxy->lbprm.update_server_eweight(srv); + } + if (srv->log_target && srv->log_target->type == LOG_TARGET_DGRAM) { /* server is used as a log target, manually update log target addr for DGRAM */ ipcpy(addr, srv->log_target->addr); @@ -268,7 +266,7 @@ static struct task *server_atomic_sync(struct task *task, void *context, unsigne px = proxy_find_by_id(data->server.safe.proxy_uuid, PR_CAP_BE, 0); if (!px) continue; - srv = findserver_unique_id(px, data->server.safe.puid, data->server.safe.rid); + srv = server_find_by_id_unique(px, data->server.safe.puid, data->server.safe.rid); if (!srv) continue; @@ -295,7 +293,7 @@ static struct task *server_atomic_sync(struct task *task, void *context, unsigne /* * this requires thread isolation, which is safe since we're the only * task working for the current subscription and we don't hold locks - * or ressources that other threads may depend on to complete a running + * or resources that other threads may depend on to complete a running * cycle. Note that we do this way because we assume that this event is * rather rare. */ @@ -306,9 +304,24 @@ static struct task *server_atomic_sync(struct task *task, void *context, unsigne _srv_set_inetaddr_port(srv, &new_addr, data->safe.next.port.svc, data->safe.next.port.map); - /* propagate the changes */ - if (data->safe.purge_conn) /* force connection cleanup on the given server? */ - srv_cleanup_connections(srv); + /* propagate the changes, force connection cleanup */ + if (new_addr.ss_family != AF_UNSPEC && + (srv->next_admin & SRV_ADMF_RMAINT)) { + /* server was previously put under DNS maintenance due + * to DNS error, but addr resolves again, so we must + * put it out of maintenance + */ + srv_clr_admin_flag(srv, SRV_ADMF_RMAINT); + + /* thanks to valid DNS resolution? */ + if (data->safe.updater.dns) { + chunk_reset(&trash); + chunk_printf(&trash, "Server %s/%s administratively READY thanks to valid DNS answer", srv->proxy->id, srv->id); + ha_warning("%s.\n", trash.area); + send_log(srv->proxy, LOG_NOTICE, "%s.\n", trash.area); + } + } + srv_cleanup_connections(srv); srv_set_dyncookie(srv); srv_set_addr_desc(srv, 1); } @@ -437,47 +450,25 @@ void _srv_event_hdl_prepare_state(struct event_hdl_cb_data_server_state *cb_data */ static void _srv_event_hdl_prepare_inetaddr(struct event_hdl_cb_data_server_inetaddr *cb_data, struct server *srv, - const struct sockaddr_storage *next_addr, - unsigned int next_port, uint8_t next_mapports, - uint8_t purge_conn) + const struct server_inetaddr *next_inetaddr, + struct server_inetaddr_updater updater) { - struct sockaddr_storage *prev_addr = &srv->addr; - unsigned int prev_port = srv->svc_port; - uint8_t prev_mapports = !!(srv->flags & SRV_F_MAPPORTS); + struct server_inetaddr prev_inetaddr; + + server_get_inetaddr(srv, &prev_inetaddr); /* only INET families are supported */ - BUG_ON((prev_addr->ss_family != AF_UNSPEC && - prev_addr->ss_family != AF_INET && prev_addr->ss_family != AF_INET6) || - (next_addr->ss_family != AF_UNSPEC && - next_addr->ss_family != AF_INET && next_addr->ss_family != AF_INET6)); + BUG_ON((next_inetaddr->family != AF_UNSPEC && + next_inetaddr->family != AF_INET && next_inetaddr->family != AF_INET6)); /* prev */ - cb_data->safe.prev.family = prev_addr->ss_family; - memset(&cb_data->safe.prev.addr, 0, sizeof(cb_data->safe.prev.addr)); - if (prev_addr->ss_family == AF_INET) - cb_data->safe.prev.addr.v4.s_addr = - ((struct sockaddr_in *)prev_addr)->sin_addr.s_addr; - else if (prev_addr->ss_family == AF_INET6) - memcpy(&cb_data->safe.prev.addr.v6, - &((struct sockaddr_in6 *)prev_addr)->sin6_addr, - sizeof(struct in6_addr)); - cb_data->safe.prev.port.svc = prev_port; - cb_data->safe.prev.port.map = prev_mapports; + cb_data->safe.prev = prev_inetaddr; /* next */ - cb_data->safe.next.family = next_addr->ss_family; - memset(&cb_data->safe.next.addr, 0, sizeof(cb_data->safe.next.addr)); - if (next_addr->ss_family == AF_INET) - cb_data->safe.next.addr.v4.s_addr = - ((struct sockaddr_in *)next_addr)->sin_addr.s_addr; - else if (next_addr->ss_family == AF_INET6) - memcpy(&cb_data->safe.next.addr.v6, - &((struct sockaddr_in6 *)next_addr)->sin6_addr, - sizeof(struct in6_addr)); - cb_data->safe.next.port.svc = next_port; - cb_data->safe.next.port.map = next_mapports; + cb_data->safe.next = *next_inetaddr; - cb_data->safe.purge_conn = purge_conn; + /* updater */ + cb_data->safe.updater = updater; } /* server event publishing helper: publish in both global and @@ -900,11 +891,6 @@ static int srv_parse_disabled(char **args, int *cur_arg, static int srv_parse_enabled(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err) { - if (newsrv->flags & SRV_F_DYNAMIC) { - ha_warning("Keyword 'enabled' is ignored for dynamic servers. It will be rejected from 3.0 onward."); - return 0; - } - newsrv->next_admin &= ~SRV_ADMF_CMAINT & ~SRV_ADMF_FMAINT; newsrv->next_state = SRV_ST_RUNNING; newsrv->check.state &= ~CHK_ST_PAUSED; @@ -933,6 +919,28 @@ static int srv_parse_error_limit(char **args, int *cur_arg, return 0; } +/* Parse the "guid" keyword */ +static int srv_parse_guid(char **args, int *cur_arg, + struct proxy *curproxy, struct server *newsrv, char **err) +{ + const char *guid; + char *guid_err = NULL; + + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : expects an argument", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + guid = args[*cur_arg + 1]; + if (guid_insert(&newsrv->obj_type, guid, &guid_err)) { + memprintf(err, "'%s': %s", args[*cur_arg], guid_err); + ha_free(&guid_err); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + /* Parse the "ws" keyword */ static int srv_parse_ws(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err) @@ -960,6 +968,32 @@ static int srv_parse_ws(char **args, int *cur_arg, return 0; } +/* Parse the "hash-key" server keyword */ +static int srv_parse_hash_key(char **args, int *cur_arg, + struct proxy *curproxy, struct server *newsrv, char **err) +{ + if (!args[*cur_arg + 1]) { + memprintf(err, "'%s expects 'id', 'addr', or 'addr-port' value", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if (strcmp(args[*cur_arg + 1], "id") == 0) { + newsrv->hash_key = SRV_HASH_KEY_ID; + } + else if (strcmp(args[*cur_arg + 1], "addr") == 0) { + newsrv->hash_key = SRV_HASH_KEY_ADDR; + } + else if (strcmp(args[*cur_arg + 1], "addr-port") == 0) { + newsrv->hash_key = SRV_HASH_KEY_ADDR_PORT; + } + else { + memprintf(err, "'%s' has to be 'id', 'addr', or 'addr-port'", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + /* Parse the "init-addr" server keyword */ static int srv_parse_init_addr(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err) @@ -1119,6 +1153,26 @@ static int srv_parse_pool_purge_delay(char **args, int *cur_arg, struct proxy *c return 0; } +static int srv_parse_pool_conn_name(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err) +{ + char *arg; + + arg = args[*cur_arg + 1]; + if (!*arg) { + memprintf(err, "'%s' expects <value> as argument", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + ha_free(&newsrv->pool_conn_name); + newsrv->pool_conn_name = strdup(arg); + if (!newsrv->pool_conn_name) { + memprintf(err, "'%s' : out of memory", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + static int srv_parse_pool_low_conn(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err) { char *arg; @@ -1199,6 +1253,7 @@ static int srv_parse_namespace(char **args, int *cur_arg, if (strcmp(arg, "*") == 0) { /* Use the namespace associated with the connection (if present). */ newsrv->flags |= SRV_F_USE_NS_FROM_PP; + global.last_checks |= LSTCHK_SYSADM; return 0; } @@ -1217,6 +1272,7 @@ static int srv_parse_namespace(char **args, int *cur_arg, memprintf(err, "Cannot open namespace '%s'", arg); return ERR_ALERT | ERR_FATAL; } + global.last_checks |= LSTCHK_SYSADM; return 0; #else @@ -2230,9 +2286,11 @@ static struct srv_kw_list srv_kws = { "ALL", { }, { { "backup", srv_parse_backup, 0, 1, 1 }, /* Flag as backup server */ { "cookie", srv_parse_cookie, 1, 1, 1 }, /* Assign a cookie to the server */ { "disabled", srv_parse_disabled, 0, 1, 1 }, /* Start the server in 'disabled' state */ - { "enabled", srv_parse_enabled, 0, 1, 1 }, /* Start the server in 'enabled' state */ + { "enabled", srv_parse_enabled, 0, 1, 0 }, /* Start the server in 'enabled' state */ { "error-limit", srv_parse_error_limit, 1, 1, 1 }, /* Configure the consecutive count of check failures to consider a server on error */ + { "guid", srv_parse_guid, 1, 0, 1 }, /* Set global unique ID of the server */ { "ws", srv_parse_ws, 1, 1, 1 }, /* websocket protocol */ + { "hash-key", srv_parse_hash_key, 1, 1, 1 }, /* Configure how chash keys are computed */ { "id", srv_parse_id, 1, 0, 1 }, /* set id# of server */ { "init-addr", srv_parse_init_addr, 1, 1, 0 }, /* */ { "log-bufsize", srv_parse_log_bufsize, 1, 1, 0 }, /* Set the ring bufsize for log server (only for log backends) */ @@ -2251,6 +2309,7 @@ static struct srv_kw_list srv_kws = { "ALL", { }, { { "on-error", srv_parse_on_error, 1, 1, 1 }, /* Configure the action on check failure */ { "on-marked-down", srv_parse_on_marked_down, 1, 1, 1 }, /* Configure the action when a server is marked down */ { "on-marked-up", srv_parse_on_marked_up, 1, 1, 1 }, /* Configure the action when a server is marked up */ + { "pool-conn-name", srv_parse_pool_conn_name, 1, 1, 1 }, /* Define expression to identify connections in idle pool */ { "pool-low-conn", srv_parse_pool_low_conn, 1, 1, 1 }, /* Set the min number of orphan idle connecbefore being allowed to pick from other threads */ { "pool-max-conn", srv_parse_pool_max_conn, 1, 1, 1 }, /* Set the max number of orphan idle connections, -1 means unlimited */ { "pool-purge-delay", srv_parse_pool_purge_delay, 1, 1, 1 }, /* Set the time before we destroy orphan idle connections, defaults to 1s */ @@ -2290,17 +2349,19 @@ void server_recalc_eweight(struct server *sv, int must_update) struct proxy *px = sv->proxy; unsigned w; - if (ns_to_sec(now_ns) < sv->last_change || ns_to_sec(now_ns) >= sv->last_change + sv->slowstart) { - /* go to full throttle if the slowstart interval is reached */ - if (sv->next_state == SRV_ST_STARTING) + if (ns_to_sec(now_ns) < sv->counters.last_change || ns_to_sec(now_ns) >= sv->counters.last_change + sv->slowstart) { + /* go to full throttle if the slowstart interval is reached unless server is currently down */ + if ((sv->cur_state != SRV_ST_STOPPED) && (sv->next_state == SRV_ST_STARTING)) sv->next_state = SRV_ST_RUNNING; } /* We must take care of not pushing the server to full throttle during slow starts. * It must also start immediately, at least at the minimal step when leaving maintenance. */ - if ((sv->next_state == SRV_ST_STARTING) && (px->lbprm.algo & BE_LB_PROP_DYN)) - w = (px->lbprm.wdiv * (ns_to_sec(now_ns) - sv->last_change) + sv->slowstart) / sv->slowstart; + if ((sv->cur_state == SRV_ST_STOPPED) && (sv->next_state == SRV_ST_STARTING) && (px->lbprm.algo & BE_LB_PROP_DYN)) + w = 1; + else if ((sv->next_state == SRV_ST_STARTING) && (px->lbprm.algo & BE_LB_PROP_DYN)) + w = (px->lbprm.wdiv * (ns_to_sec(now_ns) - sv->counters.last_change) + sv->slowstart) / sv->slowstart; else w = px->lbprm.wdiv; @@ -2334,7 +2395,7 @@ const char *server_parse_weight_change_request(struct server *sv, w = strtol(weight_str, &end, 10); if (end == weight_str) - return "Empty weight string empty or preceded by garbage"; + return "Empty weight string empty or preceded by garbage\n"; else if (end[0] == '%' && end[1] == '\0') { if (w < 0) return "Relative weight must be positive.\n"; @@ -2348,7 +2409,7 @@ const char *server_parse_weight_change_request(struct server *sv, else if (w < 0 || w > 256) return "Absolute weight can only be between 0 and 256 inclusive.\n"; else if (end[0] != '\0') - return "Trailing garbage in weight string"; + return "Trailing garbage in weight string\n"; if (w && w != sv->iweight && !(px->lbprm.algo & BE_LB_PROP_DYN)) return "Backend is using a static LB algorithm and only accepts weights '0%' and '100%'.\n"; @@ -2360,32 +2421,6 @@ const char *server_parse_weight_change_request(struct server *sv, } /* - * Parses <addr_str> and configures <sv> accordingly. <from> precise - * the source of the change in the associated message log. - * Returns: - * - error string on error - * - NULL on success - * - * Must be called with the server lock held. - */ -const char *server_parse_addr_change_request(struct server *sv, - const char *addr_str, const char *updater) -{ - unsigned char ip[INET6_ADDRSTRLEN]; - - if (inet_pton(AF_INET6, addr_str, ip)) { - srv_update_addr(sv, ip, AF_INET6, updater); - return NULL; - } - if (inet_pton(AF_INET, addr_str, ip)) { - srv_update_addr(sv, ip, AF_INET, updater); - return NULL; - } - - return "Could not understand IP address format.\n"; -} - -/* * Must be called with the server lock held. */ const char *server_parse_maxconn_change_request(struct server *sv, @@ -2399,9 +2434,9 @@ const char *server_parse_maxconn_change_request(struct server *sv, v = strtol(maxconn_str, &end, 10); if (end == maxconn_str) - return "maxconn string empty or preceded by garbage"; + return "maxconn string empty or preceded by garbage\n"; else if (end[0] != '\0') - return "Trailing garbage in maxconn string"; + return "Trailing garbage in maxconn string\n"; if (sv->maxconn == sv->minconn) { // static maxconn sv->maxconn = sv->minconn = v; @@ -2415,42 +2450,56 @@ const char *server_parse_maxconn_change_request(struct server *sv, return NULL; } -static struct sample_expr *srv_sni_sample_parse_expr(struct server *srv, struct proxy *px, - const char *file, int linenum, char **err) +/* Interpret <expr> as sample expression. This function is reserved for + * internal server allocation. On parsing use parse_srv_expr() for extra sample + * check validity. + * + * Returns the allocated sample on success or NULL on error. + */ +struct sample_expr *_parse_srv_expr(char *expr, struct arg_list *args_px, + const char *file, int linenum, char **err) { int idx; const char *args[] = { - srv->sni_expr, + expr, NULL, }; idx = 0; - px->conf.args.ctx = ARGC_SRV; + args_px->ctx = ARGC_SRV; - return sample_parse_expr((char **)args, &idx, file, linenum, err, &px->conf.args, NULL); + return sample_parse_expr((char **)args, &idx, file, linenum, err, args_px, NULL); } -int server_parse_sni_expr(struct server *newsrv, struct proxy *px, char **err) +/* Interpret <str> if not empty as a sample expression and store it into <out>. + * Contrary to _parse_srv_expr(), fetch scope validity is checked to ensure it + * is valid on a server line context. It also updates <px> HTTP mode + * requirement depending on fetch method used. + * + * Returns 0 on success else non zero. + */ +static int parse_srv_expr(char *str, struct sample_expr **out, struct proxy *px, + char **err) { struct sample_expr *expr; - expr = srv_sni_sample_parse_expr(newsrv, px, px->conf.file, px->conf.line, err); - if (!expr) { - memprintf(err, "error detected while parsing sni expression : %s", *err); + if (!str) + return 0; + + expr = _parse_srv_expr(str, &px->conf.args, px->conf.file, px->conf.line, err); + if (!expr) return ERR_ALERT | ERR_FATAL; - } if (!(expr->fetch->val & SMP_VAL_BE_SRV_CON)) { - memprintf(err, "error detected while parsing sni expression : " - " fetch method '%s' extracts information from '%s', " + memprintf(err, "fetch method '%s' extracts information from '%s', " "none of which is available here.", - newsrv->sni_expr, sample_src_names(expr->fetch->use)); + str, sample_src_names(expr->fetch->use)); return ERR_ALERT | ERR_FATAL; } px->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY); - release_sample_expr(newsrv->ssl_ctx.sni); - newsrv->ssl_ctx.sni = expr; + release_sample_expr(*out); + *out = expr; return 0; } @@ -2634,6 +2683,45 @@ int srv_prepare_for_resolution(struct server *srv, const char *hostname) return -1; } +/* Initialize default values for <srv>. Used both for dynamic servers and + * default servers. The latter are not initialized via new_server(), hence this + * function purpose. For static servers, srv_settings_cpy() is used instead + * reusing their default server instance. + */ +void srv_settings_init(struct server *srv) +{ + srv->check.inter = DEF_CHKINTR; + srv->check.fastinter = 0; + srv->check.downinter = 0; + srv->check.rise = DEF_RISETIME; + srv->check.fall = DEF_FALLTIME; + srv->check.port = 0; + + srv->agent.inter = DEF_CHKINTR; + srv->agent.fastinter = 0; + srv->agent.downinter = 0; + srv->agent.rise = DEF_AGENT_RISETIME; + srv->agent.fall = DEF_AGENT_FALLTIME; + srv->agent.port = 0; + + srv->maxqueue = 0; + srv->minconn = 0; + srv->maxconn = 0; + + srv->max_reuse = -1; + srv->max_idle_conns = -1; + srv->pool_purge_delay = 5000; + + srv->slowstart = 0; + + srv->onerror = DEF_HANA_ONERR; + srv->consecutive_errors_limit = DEF_HANA_ERRLIMIT; + + srv->uweight = srv->iweight = 1; + + LIST_INIT(&srv->pp_tlvs); +} + /* * Copy <src> server settings to <srv> server allocating * everything needed. @@ -2704,6 +2792,7 @@ void srv_settings_cpy(struct server *srv, const struct server *src, int srv_tmpl srv->minconn = src->minconn; srv->maxconn = src->maxconn; srv->slowstart = src->slowstart; + srv->hash_key = src->hash_key; srv->observe = src->observe; srv->onerror = src->onerror; srv->onmarkeddown = src->onmarkeddown; @@ -2751,6 +2840,8 @@ void srv_settings_cpy(struct server *srv, const struct server *src, int srv_tmpl srv->tcp_ut = src->tcp_ut; #endif srv->mux_proto = src->mux_proto; + if (srv->pool_conn_name) + srv->pool_conn_name = strdup(srv->pool_conn_name); srv->pool_purge_delay = src->pool_purge_delay; srv->low_idle_conns = src->low_idle_conns; srv->max_idle_conns = src->max_idle_conns; @@ -2806,7 +2897,7 @@ struct server *new_server(struct proxy *proxy) srv->rid = 0; /* rid defaults to 0 */ srv->next_state = SRV_ST_RUNNING; /* early server setup */ - srv->last_change = ns_to_sec(now_ns); + srv->counters.last_change = ns_to_sec(now_ns); srv->check.obj_type = OBJ_TYPE_CHECK; srv->check.status = HCHK_STATUS_INI; @@ -2820,6 +2911,10 @@ struct server *new_server(struct proxy *proxy) srv->agent.proxy = proxy; srv->xprt = srv->check.xprt = srv->agent.xprt = xprt_get(XPRT_RAW); + MT_LIST_INIT(&srv->sess_conns); + + guid_init(&srv->guid); + srv->extra_counters = NULL; #ifdef USE_OPENSSL HA_RWLOCK_INIT(&srv->ssl_ctx.lock); @@ -2840,6 +2935,8 @@ void srv_take(struct server *srv) /* deallocate common server parameters (may be used by default-servers) */ void srv_free_params(struct server *srv) { + struct srv_pp_tlv_list *srv_tlv = NULL; + free(srv->cookie); free(srv->rdr_pfx); free(srv->hostname); @@ -2848,6 +2945,8 @@ void srv_free_params(struct server *srv) free(srv->per_thr); free(srv->per_tgrp); free(srv->curr_idle_thr); + free(srv->pool_conn_name); + release_sample_expr(srv->pool_conn_name_expr); free(srv->resolvers_id); free(srv->addr_node.key); free(srv->lb_nodes); @@ -2858,6 +2957,14 @@ void srv_free_params(struct server *srv) if (xprt_get(XPRT_SSL) && xprt_get(XPRT_SSL)->destroy_srv) xprt_get(XPRT_SSL)->destroy_srv(srv); + + while (!LIST_ISEMPTY(&srv->pp_tlvs)) { + srv_tlv = LIST_ELEM(srv->pp_tlvs.n, struct srv_pp_tlv_list *, list); + LIST_DEL_INIT(&srv_tlv->list); + lf_expr_deinit(&srv_tlv->fmt); + ha_free(&srv_tlv->fmt_string); + ha_free(&srv_tlv); + } } /* Deallocate a server <srv> and its member. <srv> must be allocated. For @@ -2882,6 +2989,8 @@ struct server *srv_drop(struct server *srv) if (HA_ATOMIC_SUB_FETCH(&srv->refcount, 1)) goto end; + guid_remove(&srv->guid); + /* make sure we are removed from our 'next->prev_deleted' list * This doesn't require full thread isolation as we're using mt lists * However this could easily be turned into regular list if required @@ -3018,6 +3127,12 @@ static int _srv_parse_tmpl_init(struct server *srv, struct proxy *px) int i; struct server *newsrv; + /* Set the first server's ID. */ + _srv_parse_set_id_from_prefix(srv, srv->tmpl_info.prefix, srv->tmpl_info.nb_low); + srv->conf.name.key = srv->id; + ebis_insert(&curproxy->conf.used_server_name, &srv->conf.name); + + /* then create other servers from this one */ for (i = srv->tmpl_info.nb_low + 1; i <= srv->tmpl_info.nb_high; i++) { newsrv = new_server(px); if (!newsrv) @@ -3029,8 +3144,21 @@ static int _srv_parse_tmpl_init(struct server *srv, struct proxy *px) srv_settings_cpy(newsrv, srv, 1); srv_prepare_for_resolution(newsrv, srv->hostname); + /* Use sni as fallback if pool_conn_name isn't set */ + if (!newsrv->pool_conn_name && newsrv->sni_expr) { + newsrv->pool_conn_name = strdup(newsrv->sni_expr); + if (!newsrv->pool_conn_name) + goto err; + } + + if (newsrv->pool_conn_name) { + newsrv->pool_conn_name_expr = _parse_srv_expr(srv->pool_conn_name, &px->conf.args, NULL, 0, NULL); + if (!newsrv->pool_conn_name_expr) + goto err; + } + if (newsrv->sni_expr) { - newsrv->ssl_ctx.sni = srv_sni_sample_parse_expr(newsrv, px, NULL, 0, NULL); + newsrv->ssl_ctx.sni = _parse_srv_expr(srv->sni_expr, &px->conf.args, NULL, 0, NULL); if (!newsrv->ssl_ctx.sni) goto err; } @@ -3045,6 +3173,9 @@ static int _srv_parse_tmpl_init(struct server *srv, struct proxy *px) /* Linked backwards first. This will be restablished after parsing. */ newsrv->next = px->srv; px->srv = newsrv; + + newsrv->conf.name.key = newsrv->id; + ebis_insert(&curproxy->conf.used_server_name, &newsrv->conf.name); } _srv_parse_set_id_from_prefix(srv, srv->tmpl_info.prefix, srv->tmpl_info.nb_low); @@ -3316,30 +3447,18 @@ static int _srv_parse_init(struct server **srv, char **args, int *cur_arg, /* Copy default server settings to new server */ srv_settings_cpy(newsrv, &curproxy->defsrv, 0); } else { - /* Initialize dynamic server weight to 1 */ - newsrv->uweight = newsrv->iweight = 1; + srv_settings_init(newsrv); /* A dynamic server is disabled on startup */ newsrv->next_admin = SRV_ADMF_FMAINT; newsrv->next_state = SRV_ST_STOPPED; server_recalc_eweight(newsrv, 0); - - /* Set default values for checks */ - newsrv->check.inter = DEF_CHKINTR; - newsrv->check.rise = DEF_RISETIME; - newsrv->check.fall = DEF_FALLTIME; - - newsrv->agent.inter = DEF_CHKINTR; - newsrv->agent.rise = DEF_AGENT_RISETIME; - newsrv->agent.fall = DEF_AGENT_FALLTIME; } HA_SPIN_INIT(&newsrv->lock); } else { *srv = newsrv = &curproxy->defsrv; *cur_arg = 1; - newsrv->resolv_opts.family_prio = AF_INET6; - newsrv->resolv_opts.accept_duplicate_ip = 0; } free(fqdn); @@ -3426,25 +3545,6 @@ out: return err_code; } -/* This function is first intended to be used through parse_server to - * initialize a new server on startup. - */ -static int _srv_parse_sni_expr_init(char **args, int cur_arg, - struct server *srv, struct proxy *proxy, - char **errmsg) -{ - int ret; - - if (!srv->sni_expr) - return 0; - - ret = server_parse_sni_expr(srv, proxy, errmsg); - if (!ret) - return 0; - - return ret; -} - /* Server initializations finalization. * Initialize health check, agent check, SNI expression and outgoing TLVs if enabled. * Must not be called for a default server instance. @@ -3471,9 +3571,27 @@ static int _srv_parse_finalize(char **args, int cur_arg, return ERR_ALERT | ERR_FATAL; } - if ((ret = _srv_parse_sni_expr_init(args, cur_arg, srv, px, &errmsg)) != 0) { + if ((ret = parse_srv_expr(srv->sni_expr, &srv->ssl_ctx.sni, px, &errmsg))) { if (errmsg) { - ha_alert("%s\n", errmsg); + ha_alert("error detected while parsing sni expression : %s.\n", errmsg); + free(errmsg); + } + return ret; + } + + /* Use sni as fallback if pool_conn_name isn't set */ + if (!srv->pool_conn_name && srv->sni_expr) { + srv->pool_conn_name = strdup(srv->sni_expr); + if (!srv->pool_conn_name) { + ha_alert("out of memory\n"); + return ERR_ALERT | ERR_FATAL; + } + } + + if ((ret = parse_srv_expr(srv->pool_conn_name, &srv->pool_conn_name_expr, + px, &errmsg))) { + if (errmsg) { + ha_alert("error detected while parsing pool-conn-name expression : %s.\n", errmsg); free(errmsg); } return ret; @@ -3490,7 +3608,7 @@ static int _srv_parse_finalize(char **args, int cur_arg, } list_for_each_entry(srv_tlv, &srv->pp_tlvs, list) { - LIST_INIT(&srv_tlv->fmt); + lf_expr_init(&srv_tlv->fmt); if (srv_tlv->fmt_string && unlikely(!parse_logformat_string(srv_tlv->fmt_string, srv->proxy, &srv_tlv->fmt, 0, SMP_VAL_BE_SRV_CON, &errmsg))) { if (errmsg) { @@ -3562,8 +3680,13 @@ int parse_server(const char *file, int linenum, char **args, goto out; } - if (parse_flags & SRV_PARSE_TEMPLATE) + if (parse_flags & SRV_PARSE_TEMPLATE) { _srv_parse_tmpl_init(newsrv, curproxy); + } + else if (!(parse_flags & SRV_PARSE_DEFAULT_SERVER)) { + newsrv->conf.name.key = newsrv->id; + ebis_insert(&curproxy->conf.used_server_name, &newsrv->conf.name); + } /* If the server id is fixed, insert it in the proxy used_id tree. * This is needed to detect a later duplicate id via srv_parse_id. @@ -3610,6 +3733,25 @@ struct server *server_find_by_id(struct proxy *bk, int id) return curserver; } +/* + * This function finds a server with matching "<puid> x <rid>" within + * selected backend <bk>. + * 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 (<id> is unique within the list, but it is not true over the + * process lifetime as new servers may reuse the id of a previously deleted + * server). + */ +struct server *server_find_by_id_unique(struct proxy *bk, int id, uint32_t rid) +{ + struct server *curserver; + + curserver = server_find_by_id(bk, id); + if (!curserver || curserver->rid != rid) + return NULL; + return curserver; +} + /* Returns a pointer to the first server matching either name <name>, or id * if <name> starts with a '#'. NULL is returned if no match is found. * the lookup is performed in the backend <bk> @@ -3628,20 +3770,43 @@ struct server *server_find_by_name(struct proxy *bk, const char *name) curserver = NULL; if (*name == '#') { curserver = server_find_by_id(bk, atoi(name + 1)); - if (curserver) - return curserver; } else { - curserver = bk->srv; - - while (curserver && (strcmp(curserver->id, name) != 0)) - curserver = curserver->next; + struct ebpt_node *node; - if (curserver) - return curserver; + node = ebis_lookup(&bk->conf.used_server_name, name); + if (node) + curserver = container_of(node, struct server, conf.name); } - return NULL; + return curserver; +} + +/* + * This function finds a server with matching "<name> x <rid>" within + * selected backend <bk>. + * 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. For this we assume that <name> is unique within the list, + * which is the case in most setups, but in rare cases the user may have + * enforced duplicate server names in the initial config (ie: if he intends to + * use numerical IDs for identification instead). In this particular case, the + * function will not work as expected so server_find_by_id_unique() should be + * used to match a unique server instead. + * + * Just like server_find_by_id_unique(), if a server is deleted and a new server + * reuses the same name, the rid check will prevent the function from returning + * a different server from the one we were expecting to match against at a given + * time. + */ +struct server *server_find_by_name_unique(struct proxy *bk, const char *name, uint32_t rid) +{ + struct server *curserver; + + curserver = server_find_by_name(bk, name); + if (!curserver || curserver->rid != rid) + return NULL; + return curserver; } struct server *server_find_best_match(struct proxy *bk, char *name, int id, int *diff) @@ -3705,101 +3870,332 @@ struct server *server_find_best_match(struct proxy *bk, char *name, int id, int return NULL; } -/* - * update a server's current IP address. - * ip is a pointer to the new IP address, whose address family is ip_sin_family. - * ip is in network format. - * updater is a string which contains an information about the requester of the update. - * updater is used if not NULL. +/* This functions retrieves server's addr and port to fill + * <inetaddr> struct passed as argument. * - * A log line and a stderr warning message is generated based on server's backend options. - * - * Must be called with the server lock held. + * This may only be used under inet context. */ -int srv_update_addr(struct server *s, void *ip, int ip_sin_family, const char *updater) +void server_get_inetaddr(struct server *s, struct server_inetaddr *inetaddr) { - union { - struct event_hdl_cb_data_server_inetaddr addr; - struct event_hdl_cb_data_server common; - } cb_data; - struct sockaddr_storage new_addr = { }; // shut up gcc warning + struct sockaddr_storage *addr = &s->addr; + unsigned int port = s->svc_port; + uint8_t mapports = !!(s->flags & SRV_F_MAPPORTS); - /* save the new IP family & address if necessary */ - switch (ip_sin_family) { - case AF_INET: - if (s->addr.ss_family == ip_sin_family && - !memcmp(ip, &((struct sockaddr_in *)&s->addr)->sin_addr.s_addr, 4)) - return 0; - break; - case AF_INET6: - if (s->addr.ss_family == ip_sin_family && - !memcmp(ip, &((struct sockaddr_in6 *)&s->addr)->sin6_addr.s6_addr, 16)) - return 0; - break; - }; + /* only INET families are supported */ + BUG_ON((addr->ss_family != AF_UNSPEC && + addr->ss_family != AF_INET && addr->ss_family != AF_INET6)); - /* generates a log line and a warning on stderr */ - if (1) { - /* book enough space for both IPv4 and IPv6 */ - char oldip[INET6_ADDRSTRLEN]; - char newip[INET6_ADDRSTRLEN]; + inetaddr->family = addr->ss_family; + memset(&inetaddr->addr, 0, sizeof(inetaddr->addr)); - memset(oldip, '\0', INET6_ADDRSTRLEN); - memset(newip, '\0', INET6_ADDRSTRLEN); + if (addr->ss_family == AF_INET) + inetaddr->addr.v4 = + ((struct sockaddr_in *)addr)->sin_addr; + else if (addr->ss_family == AF_INET6) + inetaddr->addr.v6 = + ((struct sockaddr_in6 *)addr)->sin6_addr; - /* copy old IP address in a string */ - switch (s->addr.ss_family) { - case AF_INET: - inet_ntop(s->addr.ss_family, &((struct sockaddr_in *)&s->addr)->sin_addr, oldip, INET_ADDRSTRLEN); + inetaddr->port.svc = port; + inetaddr->port.map = mapports; +} + +/* get human readable name for server_inetaddr_updater .by struct member + */ +const char *server_inetaddr_updater_by_to_str(enum server_inetaddr_updater_by by) +{ + switch (by) { + case SERVER_INETADDR_UPDATER_BY_CLI: + return "stats socket command"; + case SERVER_INETADDR_UPDATER_BY_LUA: + return "Lua script"; + case SERVER_INETADDR_UPDATER_BY_DNS_AR: + return "DNS additional record"; + case SERVER_INETADDR_UPDATER_BY_DNS_CACHE: + return "DNS cache"; + case SERVER_INETADDR_UPDATER_BY_DNS_RESOLVER: + return "DNS resolver"; + default: + /* unknown, don't mention updater */ break; - case AF_INET6: - inet_ntop(s->addr.ss_family, &((struct sockaddr_in6 *)&s->addr)->sin6_addr, oldip, INET6_ADDRSTRLEN); + } + return NULL; +} + +/* append inetaddr updater info to chunk <out> + */ +static void _srv_append_inetaddr_updater_info(struct buffer *out, + struct server *s, + struct server_inetaddr_updater updater) +{ + switch (updater.by) { + case SERVER_INETADDR_UPDATER_BY_DNS_RESOLVER: + /* we need to report the resolver/nameserver id which is + * responsible for the update + */ + { + struct resolvers *r = s->resolvers; + struct dns_nameserver *ns; + + /* we already know that the update comes from the + * resolver section linked to the server, but we + * need to find out which nameserver handled the dns + * query + */ + BUG_ON(!r); + ns = find_nameserver_by_resolvers_and_id(r, updater.u.dns_resolver.ns_id); + BUG_ON(!ns); + chunk_appendf(out, " by '%s/%s'", r->id, ns->id); + } break; default: - strlcpy2(oldip, "(none)", sizeof(oldip)); + { + const char *by_name; + + by_name = server_inetaddr_updater_by_to_str(updater.by); + if (by_name) + chunk_appendf(out, " by '%s'", by_name); + } break; - }; + } +} - /* copy new IP address in a string */ - switch (ip_sin_family) { +/* server_set_inetaddr() helper */ +static void _addr_to_str(int family, const void *addr, char *addr_str, size_t len) +{ + memset(addr_str, 0, len); + switch (family) { case AF_INET: - inet_ntop(ip_sin_family, ip, newip, INET_ADDRSTRLEN); - break; case AF_INET6: - inet_ntop(ip_sin_family, ip, newip, INET6_ADDRSTRLEN); + inet_ntop(family, addr, addr_str, len); break; - }; + default: + strlcpy2(addr_str, "(none)", len); + break; + } +} +/* server_set_inetaddr() helper */ +static int _inetaddr_addr_cmp(const struct server_inetaddr *inetaddr, const struct sockaddr_storage *addr) +{ + struct in_addr *v4; + struct in6_addr *v6; + + if (inetaddr->family != addr->ss_family) + return 1; + + if (inetaddr->family == AF_INET) { + v4 = &((struct sockaddr_in *)addr)->sin_addr; + if (memcmp(&inetaddr->addr.v4, v4, sizeof(struct in_addr))) + return 1; + } + else if (inetaddr->family == AF_INET6) { + v6 = &((struct sockaddr_in6 *)addr)->sin6_addr; + if (memcmp(&inetaddr->addr.v6, v6, sizeof(struct in6_addr))) + return 1; + } + + return 0; // both inetaddr storage are equivalent +} + +/* This function sets a server's addr and port in inet context based on new + * inetaddr input + * + * The function first does the following, in that order: + * - checks if an update is required (new IP or port is different than current + * one) + * - check the update is allowed: + * - allow all changes if no CHECKS are configured + * - if CHECK is configured: + * - if switch to port map (SRV_F_MAPPORTS), ensure health check have their + * own ports + * - applies required changes to both ADDR and PORT if both 'required' and + * 'allowed' conditions are met. + * + * Caller can pass <msg> buffer so that it gets some information about the + * operation. It may as well provide <updater> so that messages mention that + * the update was performed on the behalf of it. + * + * <inetaddr> family may be set to UNSPEC to reset server's addr + * + * Caller must set <inetaddr>->port.map to 1 if <inetaddr>->port.svc must be + * handled as an offset + * + * The function returns 1 if an update was performed and 0 if nothing was + * changed. + */ +int server_set_inetaddr(struct server *s, + const struct server_inetaddr *inetaddr, + struct server_inetaddr_updater updater, struct buffer *msg) +{ + union { + struct event_hdl_cb_data_server_inetaddr addr; + struct event_hdl_cb_data_server common; + } cb_data; + char addr_str[INET6_ADDRSTRLEN]; + uint16_t current_port; + uint8_t ip_change = 0; + uint8_t port_change = 0; + int ret = 0; + + /* only INET families are supported */ + BUG_ON((inetaddr->family != AF_UNSPEC && + inetaddr->family != AF_INET && inetaddr->family != AF_INET6) || + (s->addr.ss_family != AF_UNSPEC && + s->addr.ss_family != AF_INET && s->addr.ss_family != AF_INET6)); + + /* ignore if no change */ + if (!_inetaddr_addr_cmp(inetaddr, &s->addr)) + goto port; + + ip_change = 1; + + /* update report for caller */ + if (msg) { + void *from_ptr = NULL; + + if (s->addr.ss_family == AF_INET) + from_ptr = &((struct sockaddr_in *)&s->addr)->sin_addr; + else if (s->addr.ss_family == AF_INET6) + from_ptr = &((struct sockaddr_in6 *)&s->addr)->sin6_addr; - /* save log line into a buffer */ - chunk_printf(&trash, "%s/%s changed its IP from %s to %s by %s", - s->proxy->id, s->id, oldip, newip, updater); + _addr_to_str(s->addr.ss_family, from_ptr, addr_str, sizeof(addr_str)); + chunk_printf(msg, "IP changed from '%s'", addr_str); + _addr_to_str(inetaddr->family, &inetaddr->addr, addr_str, sizeof(addr_str)); + chunk_appendf(msg, " to '%s'", addr_str); + } + + if (inetaddr->family == AF_UNSPEC) + goto out; // ignore port information when unsetting addr + + port: + /* collection data currently setup */ + current_port = s->svc_port; + + /* check if caller triggers a port mapped or offset */ + if (inetaddr->port.map) { + /* check if server currently uses port map */ + if (!(s->flags & SRV_F_MAPPORTS)) { + /* we're switching from a fixed port to a SRV_F_MAPPORTS + * (mapped) port, prevent PORT change if check is enabled + * and it doesn't have it's dedicated port while switching + * to port mapping + */ + if ((s->check.state & CHK_ST_ENABLED) && !s->check.port) { + if (msg) { + if (ip_change) + chunk_appendf(msg, ", "); + chunk_appendf(msg, "can't change <port> to port map because it is incompatible with current health check port configuration (use 'port' statement from the 'server' directive)."); + } + goto out; + } + /* switch from fixed port to port map mandatorily triggers + * a port change + */ + port_change = 1; + } + /* else we're already using port maps */ + else { + port_change = current_port != inetaddr->port.svc; + } + } + /* fixed port */ + else { + if ((s->flags & SRV_F_MAPPORTS)) + port_change = 1; // changing from mapped to fixed + else + port_change = current_port != inetaddr->port.svc; + } + + /* update response message about PORT change */ + if (port_change && msg) { + if (ip_change) + chunk_appendf(msg, ", "); + + chunk_appendf(msg, "port changed from '"); + if (s->flags & SRV_F_MAPPORTS) + chunk_appendf(msg, "+"); + + chunk_appendf(msg, "%d' to '", s->svc_port); + if (inetaddr->port.map) + chunk_appendf(msg, "+"); + chunk_appendf(msg, "%d'", inetaddr->port.svc); + } + + out: + if (ip_change || port_change) { + _srv_event_hdl_prepare(&cb_data.common, s, 0); + _srv_event_hdl_prepare_inetaddr(&cb_data.addr, s, + inetaddr, + updater); + + /* server_atomic_sync_task will apply the changes for us */ + _srv_event_hdl_publish(EVENT_HDL_SUB_SERVER_INETADDR, cb_data, s); + + ret = 1; + } + + if (ret && msg && updater.by != SERVER_INETADDR_UPDATER_BY_NONE) + _srv_append_inetaddr_updater_info(msg, s, updater); + return ret; +} + +/* Sets new server's addr and/or svc_port, then send a log and report a + * warning on stderr if something has changed. + * + * Returns 1 if something has changed, 0 otherwise. + * see server_set_inetaddr() for more information. + */ +int server_set_inetaddr_warn(struct server *s, + const struct server_inetaddr *inetaddr, + struct server_inetaddr_updater updater) +{ + struct buffer *msg = get_trash_chunk(); + int ret; + + chunk_reset(msg); + + ret = server_set_inetaddr(s, inetaddr, updater, msg); + if (msg->data) { /* write the buffer on stderr */ - ha_warning("%s.\n", trash.area); + ha_warning("%s/%s: %s.\n", s->proxy->id, s->id, msg->area); /* send a log */ - send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area); + send_log(s->proxy, LOG_NOTICE, "%s/%s: %s.\n", s->proxy->id, s->id, msg->area); } + return ret; +} + +/* + * update a server's current IP address. + * ip is a pointer to the new IP address, whose address family is ip_sin_family. + * ip is in network format. + * updater is a string which contains an information about the requester of the update. + * updater is used if not NULL. + * + * A log line and a stderr warning message is generated based on server's backend options. + * + * Must be called with the server lock held. + */ +int srv_update_addr(struct server *s, void *ip, int ip_sin_family, struct server_inetaddr_updater updater) +{ + struct server_inetaddr inetaddr; + + server_get_inetaddr(s, &inetaddr); + BUG_ON(ip_sin_family != AF_INET && ip_sin_family != AF_INET6); /* save the new IP family */ - new_addr.ss_family = ip_sin_family; + inetaddr.family = ip_sin_family; /* save the new IP address */ switch (ip_sin_family) { case AF_INET: - memcpy(&((struct sockaddr_in *)&new_addr)->sin_addr.s_addr, ip, 4); + memcpy(&inetaddr.addr.v4, ip, 4); break; case AF_INET6: - memcpy(((struct sockaddr_in6 *)&new_addr)->sin6_addr.s6_addr, ip, 16); + memcpy(&inetaddr.addr.v6, ip, 16); break; }; - _srv_event_hdl_prepare(&cb_data.common, s, 0); - _srv_event_hdl_prepare_inetaddr(&cb_data.addr, s, - &new_addr, s->svc_port, !!(s->flags & SRV_F_MAPPORTS), - 0); - - /* server_atomic_sync_task will apply the changes for us */ - _srv_event_hdl_publish(EVENT_HDL_SUB_SERVER_INETADDR, cb_data, s); + server_set_inetaddr_warn(s, &inetaddr, updater); return 0; } @@ -3906,40 +4302,37 @@ out: /* * This function update a server's addr and port only for AF_INET and AF_INET6 families. * - * Caller can pass its name through <updater> to get it integrated in the response message - * returned by the function. + * Caller can pass its info through <updater> to get it integrated in the response + * message returned by the function. * * The function first does the following, in that order: + * - checks that don't switch from/to a family other than AF_INET and AF_INET6 * - validates the new addr and/or port - * - checks if an update is required (new IP or port is different than current ones) - * - checks the update is allowed: - * - don't switch from/to a family other than AF_INET4 and AF_INET6 - * - allow all changes if no CHECKS are configured - * - if CHECK is configured: - * - if switch to port map (SRV_F_MAPPORTS), ensure health check have their own ports - * - applies required changes to both ADDR and PORT if both 'required' and 'allowed' - * conditions are met + * - calls server_set_inetaddr() to check and apply the change * * Must be called with the server lock held. */ -const char *srv_update_addr_port(struct server *s, const char *addr, const char *port, char *updater) +const char *srv_update_addr_port(struct server *s, const char *addr, const char *port, + struct server_inetaddr_updater updater) { - union { - struct event_hdl_cb_data_server_inetaddr addr; - struct event_hdl_cb_data_server common; - } cb_data; struct sockaddr_storage sa; - int ret; - char current_addr[INET6_ADDRSTRLEN]; - uint16_t current_port, new_port = 0; + struct server_inetaddr inetaddr; struct buffer *msg; - int ip_change = 0; - int port_change = 0; - uint8_t mapports = !!(s->flags & SRV_F_MAPPORTS); + int ret; msg = get_trash_chunk(); chunk_reset(msg); + /* even a simple port change is not supported outside of inet context, because + * s->svc_port is only relevant under inet context + */ + if ((s->addr.ss_family != AF_INET) && (s->addr.ss_family != AF_INET6)) { + chunk_printf(msg, "Update for the current server address family is only supported through configuration file."); + goto out; + } + + server_get_inetaddr(s, &inetaddr); + if (addr) { memset(&sa, 0, sizeof(struct sockaddr_storage)); if (str2ip2(addr, &sa, 0) == NULL) { @@ -3953,40 +4346,24 @@ const char *srv_update_addr_port(struct server *s, const char *addr, const char goto out; } - /* collecting data currently setup */ - memset(current_addr, '\0', sizeof(current_addr)); - ret = addr_to_str(&s->addr, current_addr, sizeof(current_addr)); - /* changes are allowed on AF_INET* families only */ - if ((ret != AF_INET) && (ret != AF_INET6)) { - chunk_printf(msg, "Update for the current server address family is only supported through configuration file"); - goto out; - } - - /* applying ADDR changes if required and allowed - * ipcmp returns 0 when both ADDR are the same - */ - if (ipcmp(&s->addr, &sa, 0) == 0) { - chunk_appendf(msg, "no need to change the addr"); - goto port; + inetaddr.family = sa.ss_family; + switch (inetaddr.family) { + case AF_INET: + inetaddr.addr.v4 = ((struct sockaddr_in *)&sa)->sin_addr; + break; + case AF_INET6: + inetaddr.addr.v6 = ((struct sockaddr_in6 *)&sa)->sin6_addr; + break; } - ip_change = 1; - - /* update report for caller */ - chunk_printf(msg, "IP changed from '%s' to '%s'", current_addr, addr); } - port: if (port) { + uint16_t new_port; char sign = '\0'; char *endptr; - if (addr) - chunk_appendf(msg, ", "); - - /* collecting data currently setup */ - current_port = s->svc_port; - sign = *port; + errno = 0; new_port = strtol(port, &endptr, 10); if ((errno != 0) || (port == endptr)) { @@ -3995,98 +4372,46 @@ const char *srv_update_addr_port(struct server *s, const char *addr, const char } /* check if caller triggers a port mapped or offset */ - if (sign == '-' || (sign == '+')) { - /* check if server currently uses port map */ - if (!(s->flags & SRV_F_MAPPORTS)) { - /* check is configured - * we're switching from a fixed port to a SRV_F_MAPPORTS (mapped) port - * prevent PORT change if check doesn't have it's dedicated port while switching - * to port mapping */ - if (!s->check.port) { - chunk_appendf(msg, "can't change <port> to port map because it is incompatible with current health check port configuration (use 'port' statement from the 'server' directive."); - goto out; - } - /* switch from fixed port to port map mandatorily triggers - * a port change */ - port_change = 1; - } - /* we're already using port maps */ - else { - port_change = current_port != new_port; - } - } - /* fixed port */ - else { - port_change = current_port != new_port; - } - - /* applying PORT changes if required and update response message */ - if (port_change) { - uint16_t new_port_print = new_port; - - /* prepare message */ - chunk_appendf(msg, "port changed from '"); - if (s->flags & SRV_F_MAPPORTS) - chunk_appendf(msg, "+"); - chunk_appendf(msg, "%d' to '", current_port); - - if (sign == '-') { - mapports = 1; - chunk_appendf(msg, "%c", sign); - /* just use for result output */ - new_port_print = -new_port_print; - } - else if (sign == '+') { - mapports = 1; - chunk_appendf(msg, "%c", sign); - } - else { - mapports = 0; - } - - chunk_appendf(msg, "%d'", new_port_print); - } - else { - chunk_appendf(msg, "no need to change the port"); - } + if (sign == '-' || sign == '+') + inetaddr.port.map = 1; + else + inetaddr.port.map = 0; + + inetaddr.port.svc = new_port; + + /* note: negative offset was converted to positive offset + * (new_port is unsigned) to prevent later conversions errors + * since svc_port is handled as an unsigned int all along the + * chain. Unfortunately this is a one-way operation so the user + * could be surprised to see a negative offset reported using + * its equivalent positive offset in the generated message + * (-X = +(65535 - (X-1))), but thanks to proper wraparound it + * will be interpreted as a negative offset during port + * remapping so it will work as expected. + */ } -out: - if (ip_change || port_change) { - _srv_event_hdl_prepare(&cb_data.common, s, 0); - _srv_event_hdl_prepare_inetaddr(&cb_data.addr, s, - ((ip_change) ? &sa : &s->addr), - ((port_change) ? new_port : s->svc_port), mapports, - 1); + ret = server_set_inetaddr(s, &inetaddr, updater, msg); + if (!ret) + chunk_printf(msg, "nothing changed"); - /* server_atomic_sync_task will apply the changes for us */ - _srv_event_hdl_publish(EVENT_HDL_SUB_SERVER_INETADDR, cb_data, s); - } - if (updater) - chunk_appendf(msg, " by '%s'", updater); - chunk_appendf(msg, "\n"); + out: return msg->area; } /* - * update server status based on result of SRV resolution + * put the server in maintenance because of failing SRV resolution * returns: - * 0 if server status is updated + * 0 if server was put under maintenance * 1 if server status has not changed * * Must be called with the server lock held. */ -int srvrq_update_srv_status(struct server *s, int has_no_ip) +int srvrq_set_srv_down(struct server *s) { if (!s->srvrq) return 1; - /* since this server has an IP, it can go back in production */ - if (has_no_ip == 0) { - srv_clr_admin_flag(s, SRV_ADMF_RMAINT); - return 1; - } - if (s->next_admin & SRV_ADMF_RMAINT) return 1; @@ -4095,59 +4420,46 @@ int srvrq_update_srv_status(struct server *s, int has_no_ip) } /* - * update server status based on result of name resolution + * put server under maintenance as a result of name resolution * returns: - * 0 if server status is updated + * 0 if server was put under maintenance * 1 if server status has not changed * * Must be called with the server lock held. */ -int snr_update_srv_status(struct server *s, int has_no_ip) +int snr_set_srv_down(struct server *s) { struct resolvers *resolvers = s->resolvers; struct resolv_resolution *resolution = (s->resolv_requester ? s->resolv_requester->resolution : NULL); int exp; + /* server already under maintenance */ + if (s->next_admin & SRV_ADMF_RMAINT) + goto out; + /* If resolution is NULL we're dealing with SRV records Additional records */ if (resolution == NULL) - return srvrq_update_srv_status(s, has_no_ip); + return srvrq_set_srv_down(s); switch (resolution->status) { case RSLV_STATUS_NONE: /* status when HAProxy has just (re)started. * Nothing to do, since the task is already automatically started */ - break; + goto out; case RSLV_STATUS_VALID: /* - * resume health checks - * server will be turned back on if health check is safe + * valid resolution but no usable server address */ - if (has_no_ip) { - if (s->next_admin & SRV_ADMF_RMAINT) - return 1; - srv_set_admin_flag(s, SRV_ADMF_RMAINT, SRV_ADM_STCHGC_DNS_NOIP); - return 0; - } - - if (!(s->next_admin & SRV_ADMF_RMAINT)) - return 1; - srv_clr_admin_flag(s, SRV_ADMF_RMAINT); - chunk_printf(&trash, "Server %s/%s administratively READY thanks to valid DNS answer", - s->proxy->id, s->id); - - ha_warning("%s.\n", trash.area); - send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area); + srv_set_admin_flag(s, SRV_ADMF_RMAINT, SRV_ADM_STCHGC_DNS_NOIP); return 0; case RSLV_STATUS_NX: /* stop server if resolution is NX for a long enough period */ exp = tick_add(resolution->last_valid, resolvers->hold.nx); if (!tick_is_expired(exp, now_ms)) - break; + goto out; // not yet expired - if (s->next_admin & SRV_ADMF_RMAINT) - return 1; srv_set_admin_flag(s, SRV_ADMF_RMAINT, SRV_ADM_STCHGC_DNS_NX); return 0; @@ -4155,10 +4467,8 @@ int snr_update_srv_status(struct server *s, int has_no_ip) /* stop server if resolution is TIMEOUT for a long enough period */ exp = tick_add(resolution->last_valid, resolvers->hold.timeout); if (!tick_is_expired(exp, now_ms)) - break; + goto out; // not yet expired - if (s->next_admin & SRV_ADMF_RMAINT) - return 1; srv_set_admin_flag(s, SRV_ADMF_RMAINT, SRV_ADM_STCHGC_DNS_TIMEOUT); return 0; @@ -4166,10 +4476,8 @@ int snr_update_srv_status(struct server *s, int has_no_ip) /* stop server if resolution is REFUSED for a long enough period */ exp = tick_add(resolution->last_valid, resolvers->hold.refused); if (!tick_is_expired(exp, now_ms)) - break; + goto out; // not yet expired - if (s->next_admin & SRV_ADMF_RMAINT) - return 1; srv_set_admin_flag(s, SRV_ADMF_RMAINT, SRV_ADM_STCHGC_DNS_REFUSED); return 0; @@ -4177,14 +4485,13 @@ int snr_update_srv_status(struct server *s, int has_no_ip) /* stop server if resolution failed for a long enough period */ exp = tick_add(resolution->last_valid, resolvers->hold.other); if (!tick_is_expired(exp, now_ms)) - break; + goto out; // not yet expired - if (s->next_admin & SRV_ADMF_RMAINT) - return 1; srv_set_admin_flag(s, SRV_ADMF_RMAINT, SRV_ADM_STCHGC_DNS_UNSPEC); return 0; } + out: return 1; } @@ -4210,7 +4517,6 @@ int snr_resolution_cb(struct resolv_requester *requester, struct dns_counters *c void *serverip, *firstip; short server_sin_family, firstip_sin_family; int ret; - struct buffer *chk = get_trash_chunk(); int has_no_ip = 0; s = objt_server(requester->owner); @@ -4269,12 +4575,6 @@ int snr_resolution_cb(struct resolv_requester *requester, struct dns_counters *c has_no_ip = 1; goto update_status; - case RSLV_UPD_NAME_ERROR: - /* update resolution status to OTHER error type */ - resolution->status = RSLV_STATUS_OTHER; - has_no_ip = 1; - goto update_status; - default: has_no_ip = 1; goto invalid; @@ -4285,15 +4585,21 @@ int snr_resolution_cb(struct resolv_requester *requester, struct dns_counters *c if (counters) { counters->app.resolver.update++; /* save the first ip we found */ - chunk_printf(chk, "%s/%s", counters->pid, counters->id); + srv_update_addr(s, firstip, firstip_sin_family, + SERVER_INETADDR_UPDATER_DNS_RESOLVER(counters->ns_puid)); } else - chunk_printf(chk, "DNS cache"); - srv_update_addr(s, firstip, firstip_sin_family, (char *) chk->area); + srv_update_addr(s, firstip, firstip_sin_family, SERVER_INETADDR_UPDATER_DNS_CACHE); update_status: - if (!snr_update_srv_status(s, has_no_ip) && has_no_ip) - memset(&s->addr, 0, sizeof(s->addr)); + if (has_no_ip && !snr_set_srv_down(s)) { + struct server_inetaddr srv_addr; + + /* unset server's addr, keep port */ + server_get_inetaddr(s, &srv_addr); + memset(&srv_addr.addr, 0, sizeof(srv_addr.addr)); + server_set_inetaddr(s, &srv_addr, SERVER_INETADDR_UPDATER_NONE, NULL); + } return 1; invalid: @@ -4301,8 +4607,14 @@ int snr_resolution_cb(struct resolv_requester *requester, struct dns_counters *c counters->app.resolver.invalid++; goto update_status; } - if (!snr_update_srv_status(s, has_no_ip) && has_no_ip) - memset(&s->addr, 0, sizeof(s->addr)); + if (has_no_ip && !snr_set_srv_down(s)) { + struct server_inetaddr srv_addr; + + /* unset server's addr, keep port */ + server_get_inetaddr(s, &srv_addr); + memset(&srv_addr.addr, 0, sizeof(srv_addr.addr)); + server_set_inetaddr(s, &srv_addr, SERVER_INETADDR_UPDATER_NONE, NULL); + } return 0; } @@ -4382,8 +4694,13 @@ int snr_resolution_error_cb(struct resolv_requester *requester, int error_code) return 0; HA_SPIN_LOCK(SERVER_LOCK, &s->lock); - if (!snr_update_srv_status(s, 1)) { - memset(&s->addr, 0, sizeof(s->addr)); + if (!snr_set_srv_down(s)) { + struct server_inetaddr srv_addr; + + /* unset server's addr, keep port */ + server_get_inetaddr(s, &srv_addr); + memset(&srv_addr.addr, 0, sizeof(srv_addr.addr)); + server_set_inetaddr(s, &srv_addr, SERVER_INETADDR_UPDATER_NONE, NULL); HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock); resolv_detach_from_resolution_answer_items(requester->resolution, requester); return 0; @@ -4739,16 +5056,16 @@ struct server *cli_find_server(struct appctx *appctx, char *arg) be_name = istsplit(&sv_name, '/'); if (!istlen(sv_name)) { - cli_err(appctx, "Require 'backend/server'."); + cli_err(appctx, "Require 'backend/server'.\n"); return NULL; } if (!(px = proxy_be_by_name(ist0(be_name)))) { - cli_err(appctx, "No such backend."); + cli_err(appctx, "No such backend.\n"); return NULL; } if (!(sv = server_find_by_name(px, ist0(sv_name)))) { - cli_err(appctx, "No such server."); + cli_err(appctx, "No such server.\n"); return NULL; } @@ -4915,10 +5232,9 @@ static int cli_parse_set_server(char **args, char *payload, struct appctx *appct port = args[6]; } HA_SPIN_LOCK(SERVER_LOCK, &sv->lock); - warning = srv_update_addr_port(sv, addr, port, "stats socket command"); + warning = srv_update_addr_port(sv, addr, port, SERVER_INETADDR_UPDATER_CLI); if (warning) cli_msg(appctx, LOG_WARNING, warning); - srv_clr_admin_flag(sv, SRV_ADMF_RMAINT); HA_SPIN_UNLOCK(SERVER_LOCK, &sv->lock); } else if (strcmp(args[3], "fqdn") == 0) { @@ -4994,12 +5310,12 @@ static int cli_parse_get_weight(char **args, char *payload, struct appctx *appct be_name = istsplit(&sv_name, '/'); if (!istlen(sv_name)) - return cli_err(appctx, "Require 'backend/server'."); + return cli_err(appctx, "Require 'backend/server'.\n"); if (!(be = proxy_be_by_name(ist0(be_name)))) - return cli_err(appctx, "No such backend."); + return cli_err(appctx, "No such backend.\n"); if (!(sv = server_find_by_name(be, ist0(sv_name)))) - return cli_err(appctx, "No such server."); + return cli_err(appctx, "No such server.\n"); /* return server's effective weight at the moment */ snprintf(trash.area, trash.size, "%d (initial %d)\n", sv->uweight, @@ -5234,7 +5550,7 @@ static int srv_alloc_lb(struct server *sv, struct proxy *be) /* updates the server's weight during a warmup stage. Once the final weight is * reached, the task automatically stops. Note that any server status change - * must have updated s->last_change accordingly. + * must have updated s->counters.last_change accordingly. */ static struct task *server_warmup(struct task *t, void *context, unsigned int state) { @@ -5290,7 +5606,7 @@ static int init_srv_slowstart(struct server *srv) if (srv->next_state == SRV_ST_STARTING) { task_schedule(srv->warmup, tick_add(now_ms, - MS_TO_TICKS(MAX(1000, (ns_to_sec(now_ns) - srv->last_change)) / 20))); + MS_TO_TICKS(MAX(1000, (ns_to_sec(now_ns) - srv->counters.last_change)) / 20))); } } @@ -5352,19 +5668,19 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct } if (!*sv_name) - return cli_err(appctx, "Require 'backend/server'."); + return cli_err(appctx, "Require 'backend/server'.\n"); be = proxy_be_by_name(be_name); if (!be) - return cli_err(appctx, "No such backend."); + 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."); + 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."); + cli_err(appctx," Dynamic servers cannot be used with log backends.\n"); return 1; } @@ -5554,11 +5870,11 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct */ if (srv->check.state & CHK_ST_CONFIGURED) { if (!start_check_task(&srv->check, 0, 1, 1)) - ha_alert("System might be unstable, consider to execute a reload"); + ha_alert("System might be unstable, consider to execute a reload\n"); } if (srv->agent.state & CHK_ST_CONFIGURED) { if (!start_check_task(&srv->agent, 0, 1, 1)) - ha_alert("System might be unstable, consider to execute a reload"); + ha_alert("System might be unstable, consider to execute a reload\n"); } if (srv->cklen && be->mode != PR_MODE_HTTP) @@ -5594,6 +5910,72 @@ out: return 1; } +/* Check if the server <bename>/<svname> exists and is ready for being deleted. + * Both <bename> and <svname> must be valid strings. This must be called under + * thread isolation. If pb/ps are not null, upon success, the pointer to + * the backend and server respectively will be put there. If pm is not null, + * a pointer to an error/success message is returned there (possibly NULL if + * nothing to say). Returned values: + * >0 if OK + * 0 if not yet (should wait if it can) + * <0 if not possible + */ +int srv_check_for_deletion(const char *bename, const char *svname, struct proxy **pb, struct server **ps, const char **pm) +{ + struct server *srv = NULL; + struct proxy *be = NULL; + const char *msg = NULL; + int ret; + + /* First, unrecoverable errors */ + ret = -1; + + if (!(be = proxy_be_by_name(bename))) { + msg = "No such backend."; + goto leave; + } + + if (!(srv = server_find_by_name(be, svname))) { + msg = "No such server."; + goto leave; + } + + if (srv->flags & SRV_F_NON_PURGEABLE) { + msg = "This server cannot be removed at runtime due to other configuration elements pointing to it."; + goto leave; + } + + /* Only servers in maintenance can be deleted. This ensures that the + * server is not present anymore in the lb structures (through + * lbprm.set_server_status_down). + */ + if (!(srv->cur_admin & SRV_ADMF_MAINT)) { + msg = "Only servers in maintenance mode can be deleted."; + goto leave; + } + + /* Second, conditions that may change over time */ + ret = 0; + + /* Ensure that there is no active/pending connection on the server. */ + if (srv->curr_used_conns || + !eb_is_empty(&srv->queue.head) || srv_has_streams(srv)) { + msg = "Server still has connections attached to it, cannot remove it."; + goto leave; + } + + /* OK, let's go */ + ret = 1; +leave: + if (pb) + *pb = be; + if (ps) + *ps = srv; + if (pm) + *pm = msg; + return ret; +} + /* Parse a "del server" command * Returns 0 if the server has been successfully initialized, 1 on failure. */ @@ -5603,6 +5985,10 @@ static int cli_parse_delete_server(char **args, char *payload, struct appctx *ap struct server *srv; struct server *prev_del; struct ist be_name, sv_name; + struct mt_list *elt1, elt2; + struct sess_priv_conns *sess_conns = NULL; + const char *msg; + int ret, i; if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) return 1; @@ -5620,42 +6006,71 @@ static int cli_parse_delete_server(char **args, char *payload, struct appctx *ap sv_name = ist(args[1]); be_name = istsplit(&sv_name, '/'); if (!istlen(sv_name)) { - cli_err(appctx, "Require 'backend/server'."); + cli_err(appctx, "Require 'backend/server'.\n"); goto out; } - if (!(be = proxy_be_by_name(ist0(be_name)))) { - cli_err(appctx, "No such backend."); - goto out; - } - if (!(srv = server_find_by_name(be, ist0(sv_name)))) { - cli_err(appctx, "No such server."); + ret = srv_check_for_deletion(ist0(be_name), ist0(sv_name), &be, &srv, &msg); + if (ret <= 0) { + /* failure (recoverable or not) */ + cli_err(appctx, msg); goto out; } - if (srv->flags & SRV_F_NON_PURGEABLE) { - cli_err(appctx, "This server cannot be removed at runtime due to other configuration elements pointing to it."); - goto out; - } + /* Close idle connections attached to this server. */ + for (i = tid;;) { + struct list *list = &srv->per_thr[i].idle_conn_list; + struct connection *conn; + + while (!LIST_ISEMPTY(list)) { + conn = LIST_ELEM(list->n, struct connection *, idle_list); + if (i != tid) { + if (conn->mux && conn->mux->takeover) + conn->mux->takeover(conn, i, 1); + else if (conn->xprt && conn->xprt->takeover) + conn->xprt->takeover(conn, conn->ctx, i, 1); + } + conn_release(conn); + } - /* Only servers in maintenance can be deleted. This ensures that the - * server is not present anymore in the lb structures (through - * lbprm.set_server_status_down). - */ - if (!(srv->cur_admin & SRV_ADMF_MAINT)) { - cli_err(appctx, "Only servers in maintenance mode can be deleted."); - goto out; + /* Also remove all purgeable conns as some of them may still + * reference the currently deleted server. + */ + while ((conn = MT_LIST_POP(&idle_conns[i].toremove_conns, + struct connection *, toremove_list))) { + conn_release(conn); + } + + if ((i = ((i + 1 == global.nbthread) ? 0 : i + 1)) == tid) + break; } - /* Ensure that there is no active/idle/pending connection on the server. - * - * TODO idle connections should not prevent server deletion. A proper - * cleanup function should be implemented to be used here. - */ - if (srv->curr_used_conns || srv->curr_idle_conns || - !eb_is_empty(&srv->queue.head) || srv_has_streams(srv)) { - cli_err(appctx, "Server still has connections attached to it, cannot remove it."); - goto out; + /* All idle connections should be removed now. */ + BUG_ON(srv->curr_idle_conns); + + /* Close idle private connections attached to this server. */ + mt_list_for_each_entry_safe(sess_conns, &srv->sess_conns, srv_el, elt1, elt2) { + struct connection *conn, *conn_back; + list_for_each_entry_safe(conn, conn_back, &sess_conns->conn_list, sess_el) { + + /* Only idle connections should be present if srv_check_for_deletion() is true. */ + BUG_ON(!(conn->flags & CO_FL_SESS_IDLE)); + + LIST_DEL_INIT(&conn->sess_el); + conn->owner = NULL; + conn->flags &= ~CO_FL_SESS_IDLE; + if (sess_conns->tid != tid) { + if (conn->mux && conn->mux->takeover) + conn->mux->takeover(conn, sess_conns->tid, 1); + else if (conn->xprt && conn->xprt->takeover) + conn->xprt->takeover(conn, conn->ctx, sess_conns->tid, 1); + } + conn_release(conn); + } + + LIST_DELETE(&sess_conns->sess_el); + MT_LIST_DELETE_SAFE(elt1); + pool_free(pool_head_sess_priv_conns, sess_conns); } /* removing cannot fail anymore when we reach this: @@ -5724,13 +6139,11 @@ static int cli_parse_delete_server(char **args, char *payload, struct appctx *ap ha_notice("Server deleted.\n"); srv_drop(srv); - cli_msg(appctx, LOG_INFO, "Server deleted."); - + cli_msg(appctx, LOG_INFO, "Server deleted.\n"); return 0; out: thread_release(); - return 1; } @@ -6334,8 +6747,8 @@ static void srv_update_status(struct server *s, int type, int cause) if (srv_prev_state != s->cur_state) { if (srv_prev_state == SRV_ST_STOPPED) { /* server was down and no longer is */ - if (s->last_change < ns_to_sec(now_ns)) // ignore negative times - s->down_time += ns_to_sec(now_ns) - s->last_change; + if (s->counters.last_change < ns_to_sec(now_ns)) // ignore negative times + s->down_time += ns_to_sec(now_ns) - s->counters.last_change; _srv_event_hdl_publish(EVENT_HDL_SUB_SERVER_UP, cb_data.common, s); } else if (s->cur_state == SRV_ST_STOPPED) { @@ -6343,7 +6756,7 @@ static void srv_update_status(struct server *s, int type, int cause) s->counters.down_trans++; _srv_event_hdl_publish(EVENT_HDL_SUB_SERVER_DOWN, cb_data.common, s); } - s->last_change = ns_to_sec(now_ns); + s->counters.last_change = ns_to_sec(now_ns); /* publish the state change */ _srv_event_hdl_prepare_state(&cb_data.state, @@ -6358,9 +6771,9 @@ static void srv_update_status(struct server *s, int type, int cause) /* backend was down and is back up again: * no helper function, updating last_change and backend downtime stats */ - if (s->proxy->last_change < ns_to_sec(now_ns)) // ignore negative times - s->proxy->down_time += ns_to_sec(now_ns) - s->proxy->last_change; - s->proxy->last_change = ns_to_sec(now_ns); + if (s->proxy->be_counters.last_change < ns_to_sec(now_ns)) // ignore negative times + s->proxy->down_time += ns_to_sec(now_ns) - s->proxy->be_counters.last_change; + s->proxy->be_counters.last_change = ns_to_sec(now_ns); } } |