From 0ae3d1dbdfedd83b62b8899d7eeb94a90ef26eaa Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Mon, 6 Apr 2020 17:54:24 +0200 Subject: [PATCH] MEDIUM: checks: Implement agent check using tcp-check rules A shared tcp-check ruleset is now created to support agent checks. The following sequence is used : tcp-check send "%[var(check.agent_string)] log-format tcp-check expect custom The custom function to evaluate the expect rule does the same that it was done to handle agent response when a custom check was used. --- include/proto/checks.h | 2 + include/types/checks.h | 3 +- include/types/proxy.h | 3 +- src/checks.c | 567 +++++++++++++++++++++++++---------------- src/server.c | 23 +- 5 files changed, 361 insertions(+), 237 deletions(-) diff --git a/include/proto/checks.h b/include/proto/checks.h index 669c62080..4dba34a67 100644 --- a/include/proto/checks.h +++ b/include/proto/checks.h @@ -84,6 +84,8 @@ int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, st int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx, const char *file, int line); +int set_srv_agent_send(struct server *srv, const char *send); + #endif /* _PROTO_CHECKS_H */ /* diff --git a/include/types/checks.h b/include/types/checks.h index d3da5f29a..d828331fe 100644 --- a/include/types/checks.h +++ b/include/types/checks.h @@ -180,8 +180,6 @@ struct check { * rise to rise+fall-1 = good */ int rise, fall; /* time in iterations */ int type; /* Check type, one of PR_O2_*_CHK */ - int send_string_len; /* length of agent command string */ - char *send_string; /* optionally send a string when connecting to the agent */ struct server *server; /* back-pointer to server */ struct proxy *proxy; /* proxy to be used */ char **argv; /* the arguments to use if running a process-based check */ @@ -318,6 +316,7 @@ struct tcpcheck_rule { #define TCPCHK_RULES_MYSQL_CHK 0x00000050 #define TCPCHK_RULES_LDAP_CHK 0x00000060 #define TCPCHK_RULES_SSL3_CHK 0x00000070 +#define TCPCHK_RULES_AGENT_CHK 0x00000080 #define TCPCHK_RULES_SPOP_CHK 0x00000090 /* A list of tcp-check vars, to be registered before executing a ruleset */ diff --git a/include/types/proxy.h b/include/types/proxy.h index e13c63c05..f9fe7c594 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -171,8 +171,7 @@ enum PR_SRV_STATE_FILE { #define PR_O2_CHK_NONE 0x00000000 /* no L7 health checks configured (TCP by default) */ /* unused: 0x10000000..0x30000000 */ #define PR_O2_HTTP_CHK 0x40000000 /* use HTTP 'OPTIONS' method to check server health */ -/* unused 0x50000000..0x70000000 */ -#define PR_O2_LB_AGENT_CHK 0x80000000 /* use a TCP connection to obtain a metric of server health */ +/* unused 0x50000000..0x80000000 */ #define PR_O2_TCPCHK_CHK 0x90000000 /* use TCPCHK check for server health */ #define PR_O2_EXT_CHK 0xA0000000 /* use external command for server health */ /* unused: 0xB0000000 to 0xF000000, reserved for health checks */ diff --git a/src/checks.c b/src/checks.c index 94454bb0b..2ffa1947b 100644 --- a/src/checks.c +++ b/src/checks.c @@ -948,213 +948,6 @@ static void __event_srv_chk_r(struct conn_stream *cs) } break; - case PR_O2_LB_AGENT_CHK: { - int status = HCHK_STATUS_CHECKED; - const char *hs = NULL; /* health status */ - const char *as = NULL; /* admin status */ - const char *ps = NULL; /* performance status */ - const char *cs = NULL; /* maxconn */ - const char *err = NULL; /* first error to report */ - const char *wrn = NULL; /* first warning to report */ - char *cmd, *p; - - /* We're getting an agent check response. The agent could - * have been disabled in the mean time with a long check - * still pending. It is important that we ignore the whole - * response. - */ - if (!(check->server->agent.state & CHK_ST_ENABLED)) - break; - - /* The agent supports strings made of a single line ended by the - * first CR ('\r') or LF ('\n'). This line is composed of words - * delimited by spaces (' '), tabs ('\t'), or commas (','). The - * line may optionally contained a description of a state change - * after a sharp ('#'), which is only considered if a health state - * is announced. - * - * Words may be composed of : - * - a numeric weight suffixed by the percent character ('%'). - * - a health status among "up", "down", "stopped", and "fail". - * - an admin status among "ready", "drain", "maint". - * - * These words may appear in any order. If multiple words of the - * same category appear, the last one wins. - */ - - p = b_head(&check->bi); - while (*p && *p != '\n' && *p != '\r') - p++; - - if (!*p) { - if (!done) - goto wait_more_data; - - /* at least inform the admin that the agent is mis-behaving */ - set_server_check_status(check, check->status, "Ignoring incomplete line from agent"); - break; - } - - *p = 0; - cmd = b_head(&check->bi); - - while (*cmd) { - /* look for next word */ - if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') { - cmd++; - continue; - } - - if (*cmd == '#') { - /* this is the beginning of a health status description, - * skip the sharp and blanks. - */ - cmd++; - while (*cmd == '\t' || *cmd == ' ') - cmd++; - break; - } - - /* find the end of the word so that we have a null-terminated - * word between and

. - */ - p = cmd + 1; - while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',') - p++; - if (*p) - *p++ = 0; - - /* first, health statuses */ - if (strcasecmp(cmd, "up") == 0) { - check->health = check->rise + check->fall - 1; - status = HCHK_STATUS_L7OKD; - hs = cmd; - } - else if (strcasecmp(cmd, "down") == 0) { - check->health = 0; - status = HCHK_STATUS_L7STS; - hs = cmd; - } - else if (strcasecmp(cmd, "stopped") == 0) { - check->health = 0; - status = HCHK_STATUS_L7STS; - hs = cmd; - } - else if (strcasecmp(cmd, "fail") == 0) { - check->health = 0; - status = HCHK_STATUS_L7STS; - hs = cmd; - } - /* admin statuses */ - else if (strcasecmp(cmd, "ready") == 0) { - as = cmd; - } - else if (strcasecmp(cmd, "drain") == 0) { - as = cmd; - } - else if (strcasecmp(cmd, "maint") == 0) { - as = cmd; - } - /* try to parse a weight here and keep the last one */ - else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) { - ps = cmd; - } - /* try to parse a maxconn here */ - else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) { - cs = cmd; - } - else { - /* keep a copy of the first error */ - if (!err) - err = cmd; - } - /* skip to next word */ - cmd = p; - } - /* here, cmd points either to \0 or to the beginning of a - * description. Skip possible leading spaces. - */ - while (*cmd == ' ' || *cmd == '\n') - cmd++; - - /* First, update the admin status so that we avoid sending other - * possibly useless warnings and can also update the health if - * present after going back up. - */ - if (as) { - if (strcasecmp(as, "drain") == 0) - srv_adm_set_drain(check->server); - else if (strcasecmp(as, "maint") == 0) - srv_adm_set_maint(check->server); - else - srv_adm_set_ready(check->server); - } - - /* now change weights */ - if (ps) { - const char *msg; - - msg = server_parse_weight_change_request(s, ps); - if (!wrn || !*wrn) - wrn = msg; - } - - if (cs) { - const char *msg; - - cs += strlen("maxconn:"); - - msg = server_parse_maxconn_change_request(s, cs); - if (!wrn || !*wrn) - wrn = msg; - } - - /* and finally health status */ - if (hs) { - /* We'll report some of the warnings and errors we have - * here. Down reports are critical, we leave them untouched. - * Lack of report, or report of 'UP' leaves the room for - * ERR first, then WARN. - */ - const char *msg = cmd; - struct buffer *t; - - if (!*msg || status == HCHK_STATUS_L7OKD) { - if (err && *err) - msg = err; - else if (wrn && *wrn) - msg = wrn; - } - - t = get_trash_chunk(); - chunk_printf(t, "via agent : %s%s%s%s", - hs, *msg ? " (" : "", - msg, *msg ? ")" : ""); - - set_server_check_status(check, status, t->area); - } - else if (err && *err) { - /* No status change but we'd like to report something odd. - * Just report the current state and copy the message. - */ - chunk_printf(&trash, "agent reports an error : %s", err); - set_server_check_status(check, status/*check->status*/, - trash.area); - - } - else if (wrn && *wrn) { - /* No status change but we'd like to report something odd. - * Just report the current state and copy the message. - */ - chunk_printf(&trash, "agent warns : %s", wrn); - set_server_check_status(check, status/*check->status*/, - trash.area); - } - else - set_server_check_status(check, status, NULL); - break; - } - default: /* good connection is enough for pure TCP check */ if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) { @@ -1421,10 +1214,6 @@ static int connect_conn_chk(struct task *t) } } - if ((check->type & PR_O2_LB_AGENT_CHK) && check->send_string_len) { - b_putblk(&check->bo, check->send_string, check->send_string_len); - } - /* for tcp-checks, the initial connection setup is handled separately as * it may be sent to a specific port and not to the server's. */ @@ -2804,6 +2593,217 @@ static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *chec goto out; } +static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read) +{ + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP; + enum healthcheck_status status = HCHK_STATUS_CHECKED; + const char *hs = NULL; /* health status */ + const char *as = NULL; /* admin status */ + const char *ps = NULL; /* performance status */ + const char *cs = NULL; /* maxconn */ + const char *err = NULL; /* first error to report */ + const char *wrn = NULL; /* first warning to report */ + char *cmd, *p; + + /* We're getting an agent check response. The agent could + * have been disabled in the mean time with a long check + * still pending. It is important that we ignore the whole + * response. + */ + if (!(check->state & CHK_ST_ENABLED)) + goto out; + + /* The agent supports strings made of a single line ended by the + * first CR ('\r') or LF ('\n'). This line is composed of words + * delimited by spaces (' '), tabs ('\t'), or commas (','). The + * line may optionally contained a description of a state change + * after a sharp ('#'), which is only considered if a health state + * is announced. + * + * Words may be composed of : + * - a numeric weight suffixed by the percent character ('%'). + * - a health status among "up", "down", "stopped", and "fail". + * - an admin status among "ready", "drain", "maint". + * + * These words may appear in any order. If multiple words of the + * same category appear, the last one wins. + */ + + p = b_head(&check->bi); + while (*p && *p != '\n' && *p != '\r') + p++; + + if (!*p) { + if (!last_read) + goto wait_more_data; + + /* at least inform the admin that the agent is mis-behaving */ + set_server_check_status(check, check->status, "Ignoring incomplete line from agent"); + goto out; + } + + *p = 0; + cmd = b_head(&check->bi); + + while (*cmd) { + /* look for next word */ + if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') { + cmd++; + continue; + } + + if (*cmd == '#') { + /* this is the beginning of a health status description, + * skip the sharp and blanks. + */ + cmd++; + while (*cmd == '\t' || *cmd == ' ') + cmd++; + break; + } + + /* find the end of the word so that we have a null-terminated + * word between and

. + */ + p = cmd + 1; + while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',') + p++; + if (*p) + *p++ = 0; + + /* first, health statuses */ + if (strcasecmp(cmd, "up") == 0) { + check->server->check.health = check->server->check.rise + check->server->check.fall - 1; + status = HCHK_STATUS_L7OKD; + hs = cmd; + } + else if (strcasecmp(cmd, "down") == 0) { + check->server->check.health = 0; + status = HCHK_STATUS_L7STS; + hs = cmd; + } + else if (strcasecmp(cmd, "stopped") == 0) { + check->server->check.health = 0; + status = HCHK_STATUS_L7STS; + hs = cmd; + } + else if (strcasecmp(cmd, "fail") == 0) { + check->server->check.health = 0; + status = HCHK_STATUS_L7STS; + hs = cmd; + } + /* admin statuses */ + else if (strcasecmp(cmd, "ready") == 0) { + as = cmd; + } + else if (strcasecmp(cmd, "drain") == 0) { + as = cmd; + } + else if (strcasecmp(cmd, "maint") == 0) { + as = cmd; + } + /* try to parse a weight here and keep the last one */ + else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) { + ps = cmd; + } + /* try to parse a maxconn here */ + else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) { + cs = cmd; + } + else { + /* keep a copy of the first error */ + if (!err) + err = cmd; + } + /* skip to next word */ + cmd = p; + } + /* here, cmd points either to \0 or to the beginning of a + * description. Skip possible leading spaces. + */ + while (*cmd == ' ' || *cmd == '\n') + cmd++; + + /* First, update the admin status so that we avoid sending other + * possibly useless warnings and can also update the health if + * present after going back up. + */ + if (as) { + if (strcasecmp(as, "drain") == 0) + srv_adm_set_drain(check->server); + else if (strcasecmp(as, "maint") == 0) + srv_adm_set_maint(check->server); + else + srv_adm_set_ready(check->server); + } + + /* now change weights */ + if (ps) { + const char *msg; + + msg = server_parse_weight_change_request(check->server, ps); + if (!wrn || !*wrn) + wrn = msg; + } + + if (cs) { + const char *msg; + + cs += strlen("maxconn:"); + + msg = server_parse_maxconn_change_request(check->server, cs); + if (!wrn || !*wrn) + wrn = msg; + } + + /* and finally health status */ + if (hs) { + /* We'll report some of the warnings and errors we have + * here. Down reports are critical, we leave them untouched. + * Lack of report, or report of 'UP' leaves the room for + * ERR first, then WARN. + */ + const char *msg = cmd; + struct buffer *t; + + if (!*msg || status == HCHK_STATUS_L7OKD) { + if (err && *err) + msg = err; + else if (wrn && *wrn) + msg = wrn; + } + + t = get_trash_chunk(); + chunk_printf(t, "via agent : %s%s%s%s", + hs, *msg ? " (" : "", + msg, *msg ? ")" : ""); + set_server_check_status(check, status, t->area); + } + else if (err && *err) { + /* No status change but we'd like to report something odd. + * Just report the current state and copy the message. + */ + chunk_printf(&trash, "agent reports an error : %s", err); + set_server_check_status(check, status/*check->status*/, trash.area); + } + else if (wrn && *wrn) { + /* No status change but we'd like to report something odd. + * Just report the current state and copy the message. + */ + chunk_printf(&trash, "agent warns : %s", wrn); + set_server_check_status(check, status/*check->status*/, trash.area); + } + else + set_server_check_status(check, status, NULL); + + out: + return ret; + + wait_more_data: + ret = TCPCHK_EVAL_WAIT; + goto out; +} + /* Evaluate a TCPCHK_ACT_CONNECT rule. It returns 1 to evaluate the next rule, 0 * to wait and -1 to stop the check. */ static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule) @@ -4143,13 +4143,33 @@ static int init_srv_check(struct server *srv) static int init_srv_agent_check(struct server *srv) { + struct tcpcheck_rule *chk; const char *err; int ret = 0; if (!srv->do_agent) goto out; - err = init_check(&srv->agent, PR_O2_LB_AGENT_CHK); + /* If there is no connect rule preceeding all send / expect rules, an + * implicit one is inserted before all others. + */ + chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules); + if (!chk || chk->action != TCPCHK_ACT_CONNECT) { + chk = calloc(1, sizeof(*chk)); + if (!chk) { + ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule" + " to agent-check for server '%s' (out of memory).\n", + proxy_type_str(srv->proxy), srv->proxy->id, srv->id); + ret |= ERR_ALERT | ERR_FATAL; + goto out; + } + chk->action = TCPCHK_ACT_CONNECT; + chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT); + LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list); + } + + + err = init_check(&srv->agent, PR_O2_TCPCHK_CHK); if (err) { ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n", proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err); @@ -4197,9 +4217,17 @@ static void deinit_srv_check(struct server *srv) static void deinit_srv_agent_check(struct server *srv) { - if (srv->do_agent) + if (srv->agent.tcpcheck_rules) { + free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars); + free(srv->agent.tcpcheck_rules); + srv->agent.tcpcheck_rules = NULL; + } + + if (srv->agent.state & CHK_ST_CONFIGURED) free_check(&srv->agent); - free(srv->agent.send_string); + + srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT; + srv->do_agent = 0; } static void deinit_tcpchecks() @@ -6045,8 +6073,70 @@ static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv, char **errmsg) { + struct tcpcheck_ruleset *rs = NULL; + struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules; + struct tcpcheck_rule *chk; + int err_code = 0; + + if (srv->do_agent) + goto out; + + if (!rules) { + rules = calloc(1, sizeof(*rules)); + if (!rules) { + memprintf(errmsg, "out of memory."); + goto error; + } + LIST_INIT(&rules->preset_vars); + srv->agent.tcpcheck_rules = rules; + } + rules->list = NULL; + rules->flags = 0; + + rs = tcpcheck_ruleset_lookup("*agent-check"); + if (rs) + goto ruleset_found; + + rs = tcpcheck_ruleset_create("*agent-check"); + if (rs == NULL) { + memprintf(errmsg, "out of memory."); + goto error; + } + + chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""}, + 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg); + if (!chk) { + memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg); + goto error; + } + chk->index = 0; + LIST_ADDQ(&rs->rules, &chk->list); + + chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""}, + 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg); + if (!chk) { + memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg); + goto error; + } + chk->expect.custom = tcpcheck_agent_expect_reply; + chk->index = 1; + LIST_ADDQ(&rs->rules, &chk->list); + + LIST_ADDQ(&tcpchecks_list, &rs->list); + + ruleset_found: + rules->list = &rs->rules; + rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_AGENT_CHK); srv->do_agent = 1; + + out: return 0; + + error: + deinit_srv_agent_check(srv); + tcpcheck_ruleset_release(rs); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; } /* Parse the "agent-inter" server keyword */ @@ -6115,11 +6205,38 @@ static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, goto out; } +int set_srv_agent_send(struct server *srv, const char *send) +{ + struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules; + struct tcpcheck_var *var = NULL; + char *str; + + str = strdup(send); + var = tcpcheck_var_create("check.agent_string"); + if (str == NULL || var == NULL) + goto error; + + free_tcpcheck_vars(&rules->preset_vars); + + var->data.type = SMP_T_STR; + var->data.u.str.area = str; + var->data.u.str.data = strlen(str); + LIST_INIT(&var->list); + LIST_ADDQ(&rules->preset_vars, &var->list); + + return 1; + + error: + free(str); + free(var); + return 0; +} /* Parse the "agent-send" server keyword */ static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv, char **errmsg) { + struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules; int err_code = 0; if (!*(args[*cur_arg+1])) { @@ -6127,10 +6244,17 @@ static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, goto error; } - free(srv->agent.send_string); - srv->agent.send_string_len = strlen(args[*cur_arg+1]); - srv->agent.send_string = strdup(args[*cur_arg+1]); - if (srv->agent.send_string == NULL) { + if (!rules) { + rules = calloc(1, sizeof(*rules)); + if (!rules) { + memprintf(errmsg, "out of memory."); + goto error; + } + LIST_INIT(&rules->preset_vars); + srv->agent.tcpcheck_rules = rules; + } + + if (!set_srv_agent_send(srv, args[*cur_arg+1])) { memprintf(errmsg, "out of memory."); goto error; } @@ -6139,6 +6263,7 @@ static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, return err_code; error: + deinit_srv_agent_check(srv); err_code |= ERR_ALERT | ERR_FATAL; goto out; } @@ -6147,11 +6272,7 @@ static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv, char **errmsg) { - free_check(&srv->agent); - srv->agent.inter = 0; - srv->agent.port = 0; - srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT; - srv->do_agent = 0; + deinit_srv_agent_check(srv); return 0; } diff --git a/src/server.c b/src/server.c index b369000b1..e3a74498d 100644 --- a/src/server.c +++ b/src/server.c @@ -1665,9 +1665,18 @@ static void srv_settings_cpy(struct server *srv, struct server *src, int srv_tmp srv->check.downinter = src->check.downinter; srv->agent.use_ssl = src->agent.use_ssl; srv->agent.port = src->agent.port; - if (src->agent.send_string != NULL) - srv->agent.send_string = strdup(src->agent.send_string); - srv->agent.send_string_len = src->agent.send_string_len; + + if (src->agent.tcpcheck_rules) { + srv->agent.tcpcheck_rules = calloc(1, sizeof(*srv->agent.tcpcheck_rules)); + if (srv->agent.tcpcheck_rules) { + srv->agent.tcpcheck_rules->flags = src->agent.tcpcheck_rules->flags; + srv->agent.tcpcheck_rules->list = src->agent.tcpcheck_rules->list; + LIST_INIT(&srv->agent.tcpcheck_rules->preset_vars); + dup_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars, + &src->agent.tcpcheck_rules->preset_vars); + } + } + srv->agent.inter = src->agent.inter; srv->agent.fastinter = src->agent.fastinter; srv->agent.downinter = src->agent.downinter; @@ -4300,14 +4309,8 @@ static int cli_parse_set_server(char **args, char *payload, struct appctx *appct if (!(sv->agent.state & CHK_ST_ENABLED)) cli_err(appctx, "agent checks are not enabled on this server.\n"); else { - char *nss = strdup(args[4]); - if (!nss) + if (!set_srv_agent_send(sv, args[4])) cli_err(appctx, "cannot allocate memory for new string.\n"); - else { - free(sv->agent.send_string); - sv->agent.send_string = nss; - sv->agent.send_string_len = strlen(args[4]); - } } } else if (strcmp(args[3], "check-port") == 0) {