diff --git a/include/types/cli.h b/include/types/cli.h index 883c72d1e..ebe4adb62 100644 --- a/include/types/cli.h +++ b/include/types/cli.h @@ -48,9 +48,6 @@ enum { STAT_CLI_PRINT, /* display message in cli->msg */ STAT_CLI_PRINT_FREE, /* display message in cli->msg. After the display, free the pointer */ STAT_CLI_O_ERR, /* dump errors */ - STAT_CLI_O_TAB, /* dump tables */ - STAT_CLI_O_CLR, /* clear tables */ - STAT_CLI_O_SET, /* set entries in tables */ STAT_CLI_O_ENV, /* dump environment */ STAT_CLI_O_CUSTOM, /* custom callback pointer */ }; diff --git a/src/cli.c b/src/cli.c index 4db420c61..39fe01fde 100644 --- a/src/cli.c +++ b/src/cli.c @@ -69,22 +69,17 @@ static int stats_dump_env_to_buffer(struct stream_interface *si); static int stats_dump_errors_to_buffer(struct stream_interface *si); -static int stats_table_request(struct stream_interface *si, int show); - static struct applet cli_applet; static const char stats_sock_usage_msg[] = "Unknown command. Please enter one of the following commands only :\n" " clear counters : clear max statistics counters (add 'all' for all counters)\n" - " clear table : remove an entry from a table\n" " help : this message\n" " prompt : toggle interactive mode with prompt\n" " quit : disconnect\n" " show env [var] : dump environment variables known to the process\n" " show errors : report last request and response errors for each proxy\n" - " show table [id]: report table usage stats or dump this table's contents\n" - " set table [id] : update or create a table entry's data\n" " set timeout : change a timeout setting\n" " set maxconn : change a maxconn setting\n" " set rate-limit : change a rate limiting value\n" @@ -409,365 +404,6 @@ int cli_has_level(struct appctx *appctx, int level) } -/* Dump the status of a table to a stream interface's - * read buffer. It returns 0 if the output buffer is full - * and needs to be called again, otherwise non-zero. - */ -static int stats_dump_table_head_to_buffer(struct chunk *msg, struct stream_interface *si, - struct proxy *proxy, struct proxy *target) -{ - struct stream *s = si_strm(si); - - chunk_appendf(msg, "# table: %s, type: %s, size:%d, used:%d\n", - proxy->id, stktable_types[proxy->table.type].kw, proxy->table.size, proxy->table.current); - - /* any other information should be dumped here */ - - if (target && strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) - chunk_appendf(msg, "# contents not dumped due to insufficient privileges\n"); - - if (bi_putchk(si_ic(si), msg) == -1) { - si_applet_cant_put(si); - return 0; - } - - return 1; -} - -/* Dump the a table entry to a stream interface's - * read buffer. It returns 0 if the output buffer is full - * and needs to be called again, otherwise non-zero. - */ -static int stats_dump_table_entry_to_buffer(struct chunk *msg, struct stream_interface *si, - struct proxy *proxy, struct stksess *entry) -{ - int dt; - - chunk_appendf(msg, "%p:", entry); - - if (proxy->table.type == SMP_T_IPV4) { - char addr[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, (const void *)&entry->key.key, addr, sizeof(addr)); - chunk_appendf(msg, " key=%s", addr); - } - else if (proxy->table.type == SMP_T_IPV6) { - char addr[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, (const void *)&entry->key.key, addr, sizeof(addr)); - chunk_appendf(msg, " key=%s", addr); - } - else if (proxy->table.type == SMP_T_SINT) { - chunk_appendf(msg, " key=%u", *(unsigned int *)entry->key.key); - } - else if (proxy->table.type == SMP_T_STR) { - chunk_appendf(msg, " key="); - dump_text(msg, (const char *)entry->key.key, proxy->table.key_size); - } - else { - chunk_appendf(msg, " key="); - dump_binary(msg, (const char *)entry->key.key, proxy->table.key_size); - } - - chunk_appendf(msg, " use=%d exp=%d", entry->ref_cnt - 1, tick_remain(now_ms, entry->expire)); - - for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) { - void *ptr; - - if (proxy->table.data_ofs[dt] == 0) - continue; - if (stktable_data_types[dt].arg_type == ARG_T_DELAY) - chunk_appendf(msg, " %s(%d)=", stktable_data_types[dt].name, proxy->table.data_arg[dt].u); - else - chunk_appendf(msg, " %s=", stktable_data_types[dt].name); - - ptr = stktable_data_ptr(&proxy->table, entry, dt); - switch (stktable_data_types[dt].std_type) { - case STD_T_SINT: - chunk_appendf(msg, "%d", stktable_data_cast(ptr, std_t_sint)); - break; - case STD_T_UINT: - chunk_appendf(msg, "%u", stktable_data_cast(ptr, std_t_uint)); - break; - case STD_T_ULL: - chunk_appendf(msg, "%lld", stktable_data_cast(ptr, std_t_ull)); - break; - case STD_T_FRQP: - chunk_appendf(msg, "%d", - read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp), - proxy->table.data_arg[dt].u)); - break; - } - } - chunk_appendf(msg, "\n"); - - if (bi_putchk(si_ic(si), msg) == -1) { - si_applet_cant_put(si); - return 0; - } - - return 1; -} - -static void stats_sock_table_key_request(struct stream_interface *si, char **args, int action) -{ - struct stream *s = si_strm(si); - struct appctx *appctx = __objt_appctx(si->end); - struct proxy *px = appctx->ctx.table.target; - struct stksess *ts; - uint32_t uint32_key; - unsigned char ip6_key[sizeof(struct in6_addr)]; - long long value; - int data_type; - int cur_arg; - void *ptr; - struct freq_ctr_period *frqp; - - appctx->st0 = STAT_CLI_OUTPUT; - - if (!*args[4]) { - appctx->ctx.cli.msg = "Key value expected\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - - switch (px->table.type) { - case SMP_T_IPV4: - uint32_key = htonl(inetaddr_host(args[4])); - static_table_key->key = &uint32_key; - break; - case SMP_T_IPV6: - inet_pton(AF_INET6, args[4], ip6_key); - static_table_key->key = &ip6_key; - break; - case SMP_T_SINT: - { - char *endptr; - unsigned long val; - errno = 0; - val = strtoul(args[4], &endptr, 10); - if ((errno == ERANGE && val == ULONG_MAX) || - (errno != 0 && val == 0) || endptr == args[4] || - val > 0xffffffff) { - appctx->ctx.cli.msg = "Invalid key\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - uint32_key = (uint32_t) val; - static_table_key->key = &uint32_key; - break; - } - break; - case SMP_T_STR: - static_table_key->key = args[4]; - static_table_key->key_len = strlen(args[4]); - break; - default: - switch (action) { - case STAT_CLI_O_TAB: - appctx->ctx.cli.msg = "Showing keys from tables of type other than ip, ipv6, string and integer is not supported\n"; - break; - case STAT_CLI_O_CLR: - appctx->ctx.cli.msg = "Removing keys from ip tables of type other than ip, ipv6, string and integer is not supported\n"; - break; - default: - appctx->ctx.cli.msg = "Unknown action\n"; - break; - } - appctx->st0 = STAT_CLI_PRINT; - return; - } - - /* check permissions */ - if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) { - appctx->ctx.cli.msg = stats_permission_denied_msg; - appctx->st0 = STAT_CLI_PRINT; - return; - } - - ts = stktable_lookup_key(&px->table, static_table_key); - - switch (action) { - case STAT_CLI_O_TAB: - if (!ts) - return; - chunk_reset(&trash); - if (!stats_dump_table_head_to_buffer(&trash, si, px, px)) - return; - stats_dump_table_entry_to_buffer(&trash, si, px, ts); - return; - - case STAT_CLI_O_CLR: - if (!ts) - return; - if (ts->ref_cnt) { - /* don't delete an entry which is currently referenced */ - appctx->ctx.cli.msg = "Entry currently in use, cannot remove\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - stksess_kill(&px->table, ts); - break; - - case STAT_CLI_O_SET: - if (ts) - stktable_touch(&px->table, ts, 1); - else { - ts = stksess_new(&px->table, static_table_key); - if (!ts) { - /* don't delete an entry which is currently referenced */ - appctx->ctx.cli.msg = "Unable to allocate a new entry\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - stktable_store(&px->table, ts, 1); - } - - for (cur_arg = 5; *args[cur_arg]; cur_arg += 2) { - if (strncmp(args[cur_arg], "data.", 5) != 0) { - appctx->ctx.cli.msg = "\"data.<type>\" followed by a value expected\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - - data_type = stktable_get_data_type(args[cur_arg] + 5); - if (data_type < 0) { - appctx->ctx.cli.msg = "Unknown data type\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - - if (!px->table.data_ofs[data_type]) { - appctx->ctx.cli.msg = "Data type not stored in this table\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - - if (!*args[cur_arg+1] || strl2llrc(args[cur_arg+1], strlen(args[cur_arg+1]), &value) != 0) { - appctx->ctx.cli.msg = "Require a valid integer value to store\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - - ptr = stktable_data_ptr(&px->table, ts, data_type); - - switch (stktable_data_types[data_type].std_type) { - case STD_T_SINT: - stktable_data_cast(ptr, std_t_sint) = value; - break; - case STD_T_UINT: - stktable_data_cast(ptr, std_t_uint) = value; - break; - case STD_T_ULL: - stktable_data_cast(ptr, std_t_ull) = value; - break; - case STD_T_FRQP: - /* We set both the current and previous values. That way - * the reported frequency is stable during all the period - * then slowly fades out. This allows external tools to - * push measures without having to update them too often. - */ - frqp = &stktable_data_cast(ptr, std_t_frqp); - frqp->curr_tick = now_ms; - frqp->prev_ctr = 0; - frqp->curr_ctr = value; - break; - } - } - break; - - default: - appctx->ctx.cli.msg = "Unknown action\n"; - appctx->st0 = STAT_CLI_PRINT; - break; - } -} - -static void stats_sock_table_data_request(struct stream_interface *si, char **args, int action) -{ - struct appctx *appctx = __objt_appctx(si->end); - - if (action != STAT_CLI_O_TAB && action != STAT_CLI_O_CLR) { - appctx->ctx.cli.msg = "content-based lookup is only supported with the \"show\" and \"clear\" actions"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - - /* condition on stored data value */ - appctx->ctx.table.data_type = stktable_get_data_type(args[3] + 5); - if (appctx->ctx.table.data_type < 0) { - appctx->ctx.cli.msg = "Unknown data type\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - - if (!((struct proxy *)appctx->ctx.table.target)->table.data_ofs[appctx->ctx.table.data_type]) { - appctx->ctx.cli.msg = "Data type not stored in this table\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - - appctx->ctx.table.data_op = get_std_op(args[4]); - if (appctx->ctx.table.data_op < 0) { - appctx->ctx.cli.msg = "Require and operator among \"eq\", \"ne\", \"le\", \"ge\", \"lt\", \"gt\"\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - - if (!*args[5] || strl2llrc(args[5], strlen(args[5]), &appctx->ctx.table.value) != 0) { - appctx->ctx.cli.msg = "Require a valid integer value to compare against\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } -} - -static void stats_sock_table_request(struct stream_interface *si, char **args, int action) -{ - struct appctx *appctx = __objt_appctx(si->end); - - appctx->ctx.table.data_type = -1; - appctx->st2 = STAT_ST_INIT; - appctx->ctx.table.target = NULL; - appctx->ctx.table.proxy = NULL; - appctx->ctx.table.entry = NULL; - appctx->st0 = action; - - if (*args[2]) { - appctx->ctx.table.target = proxy_tbl_by_name(args[2]); - if (!appctx->ctx.table.target) { - appctx->ctx.cli.msg = "No such table\n"; - appctx->st0 = STAT_CLI_PRINT; - return; - } - } - else { - if (action != STAT_CLI_O_TAB) - goto err_args; - return; - } - - if (strcmp(args[3], "key") == 0) - stats_sock_table_key_request(si, args, action); - else if (strncmp(args[3], "data.", 5) == 0) - stats_sock_table_data_request(si, args, action); - else if (*args[3]) - goto err_args; - - return; - -err_args: - switch (action) { - case STAT_CLI_O_TAB: - appctx->ctx.cli.msg = "Optional argument only supports \"data.<store_data_type>\" <operator> <value> and key <key>\n"; - break; - case STAT_CLI_O_CLR: - appctx->ctx.cli.msg = "Required arguments: <table> \"data.<store_data_type>\" <operator> <value> or <table> key <key>\n"; - break; - default: - appctx->ctx.cli.msg = "Unknown action\n"; - break; - } - appctx->st0 = STAT_CLI_PRINT; -} - /* Expects to find a frontend named <arg> and returns it, otherwise displays various * adequate error messages and returns NULL. This function also expects the stream * level to be admin. @@ -955,9 +591,6 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) appctx->st2 = STAT_ST_INIT; appctx->st0 = STAT_CLI_O_ERR; // stats_dump_errors_to_buffer } - else if (strcmp(args[1], "table") == 0) { - stats_sock_table_request(si, args, STAT_CLI_O_TAB); - } else { /* neither "stat" nor "info" nor "sess" nor "errors" nor "table" */ return 0; } @@ -1021,11 +654,6 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) global.sps_max = 0; return 1; } - else if (strcmp(args[1], "table") == 0) { - stats_sock_table_request(si, args, STAT_CLI_O_CLR); - /* end of processing */ - return 1; - } else { /* unknown "clear" argument */ return 0; @@ -1298,9 +926,6 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) appctx->st0 = STAT_CLI_PRINT; return 1; } - } - else if (strcmp(args[1], "table") == 0) { - stats_sock_table_request(si, args, STAT_CLI_O_SET); } else { /* unknown "set" parameter */ return 0; } @@ -1670,11 +1295,6 @@ static void cli_io_handler(struct appctx *appctx) if (stats_dump_errors_to_buffer(si)) appctx->st0 = STAT_CLI_PROMPT; break; - case STAT_CLI_O_TAB: - case STAT_CLI_O_CLR: - if (stats_table_request(si, appctx->st0)) - appctx->st0 = STAT_CLI_PROMPT; - break; case STAT_CLI_O_ENV: /* environment dump */ if (stats_dump_env_to_buffer(si)) appctx->st0 = STAT_CLI_PROMPT; @@ -1759,176 +1379,12 @@ static void cli_release_handler(struct appctx *appctx) appctx->io_release(appctx); appctx->io_release = NULL; } - else if ((appctx->st0 == STAT_CLI_O_TAB || appctx->st0 == STAT_CLI_O_CLR) && - appctx->st2 == STAT_ST_LIST) { - appctx->ctx.table.entry->ref_cnt--; - stksess_kill_if_expired(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry); - } else if (appctx->st0 == STAT_CLI_PRINT_FREE) { free(appctx->ctx.cli.err); appctx->ctx.cli.err = NULL; } } -/* This function is used to either dump tables states (when action is set - * to STAT_CLI_O_TAB) or clear tables (when action is STAT_CLI_O_CLR). - * It returns 0 if the output buffer is full and it needs to be called - * again, otherwise non-zero. - */ -static int stats_table_request(struct stream_interface *si, int action) -{ - struct appctx *appctx = __objt_appctx(si->end); - struct stream *s = si_strm(si); - struct ebmb_node *eb; - int dt; - int skip_entry; - int show = action == STAT_CLI_O_TAB; - - /* - * We have 3 possible states in appctx->st2 : - * - STAT_ST_INIT : the first call - * - STAT_ST_INFO : the proxy pointer points to the next table to - * dump, the entry pointer is NULL ; - * - STAT_ST_LIST : the proxy pointer points to the current table - * and the entry pointer points to the next entry to be dumped, - * and the refcount on the next entry is held ; - * - STAT_ST_END : nothing left to dump, the buffer may contain some - * data though. - */ - - if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW))) { - /* in case of abort, remove any refcount we might have set on an entry */ - if (appctx->st2 == STAT_ST_LIST) { - appctx->ctx.table.entry->ref_cnt--; - stksess_kill_if_expired(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry); - } - return 1; - } - - chunk_reset(&trash); - - while (appctx->st2 != STAT_ST_FIN) { - switch (appctx->st2) { - case STAT_ST_INIT: - appctx->ctx.table.proxy = appctx->ctx.table.target; - if (!appctx->ctx.table.proxy) - appctx->ctx.table.proxy = proxy; - - appctx->ctx.table.entry = NULL; - appctx->st2 = STAT_ST_INFO; - break; - - case STAT_ST_INFO: - if (!appctx->ctx.table.proxy || - (appctx->ctx.table.target && - appctx->ctx.table.proxy != appctx->ctx.table.target)) { - appctx->st2 = STAT_ST_END; - break; - } - - if (appctx->ctx.table.proxy->table.size) { - if (show && !stats_dump_table_head_to_buffer(&trash, si, appctx->ctx.table.proxy, - appctx->ctx.table.target)) - return 0; - - if (appctx->ctx.table.target && - strm_li(s)->bind_conf->level >= ACCESS_LVL_OPER) { - /* dump entries only if table explicitly requested */ - eb = ebmb_first(&appctx->ctx.table.proxy->table.keys); - if (eb) { - appctx->ctx.table.entry = ebmb_entry(eb, struct stksess, key); - appctx->ctx.table.entry->ref_cnt++; - appctx->st2 = STAT_ST_LIST; - break; - } - } - } - appctx->ctx.table.proxy = appctx->ctx.table.proxy->next; - break; - - case STAT_ST_LIST: - skip_entry = 0; - - if (appctx->ctx.table.data_type >= 0) { - /* we're filtering on some data contents */ - void *ptr; - long long data; - - dt = appctx->ctx.table.data_type; - ptr = stktable_data_ptr(&appctx->ctx.table.proxy->table, - appctx->ctx.table.entry, - dt); - - data = 0; - switch (stktable_data_types[dt].std_type) { - case STD_T_SINT: - data = stktable_data_cast(ptr, std_t_sint); - break; - case STD_T_UINT: - data = stktable_data_cast(ptr, std_t_uint); - break; - case STD_T_ULL: - data = stktable_data_cast(ptr, std_t_ull); - break; - case STD_T_FRQP: - data = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp), - appctx->ctx.table.proxy->table.data_arg[dt].u); - break; - } - - /* skip the entry if the data does not match the test and the value */ - if ((data < appctx->ctx.table.value && - (appctx->ctx.table.data_op == STD_OP_EQ || - appctx->ctx.table.data_op == STD_OP_GT || - appctx->ctx.table.data_op == STD_OP_GE)) || - (data == appctx->ctx.table.value && - (appctx->ctx.table.data_op == STD_OP_NE || - appctx->ctx.table.data_op == STD_OP_GT || - appctx->ctx.table.data_op == STD_OP_LT)) || - (data > appctx->ctx.table.value && - (appctx->ctx.table.data_op == STD_OP_EQ || - appctx->ctx.table.data_op == STD_OP_LT || - appctx->ctx.table.data_op == STD_OP_LE))) - skip_entry = 1; - } - - if (show && !skip_entry && - !stats_dump_table_entry_to_buffer(&trash, si, appctx->ctx.table.proxy, - appctx->ctx.table.entry)) - return 0; - - appctx->ctx.table.entry->ref_cnt--; - - eb = ebmb_next(&appctx->ctx.table.entry->key); - if (eb) { - struct stksess *old = appctx->ctx.table.entry; - appctx->ctx.table.entry = ebmb_entry(eb, struct stksess, key); - if (show) - stksess_kill_if_expired(&appctx->ctx.table.proxy->table, old); - else if (!skip_entry && !appctx->ctx.table.entry->ref_cnt) - stksess_kill(&appctx->ctx.table.proxy->table, old); - appctx->ctx.table.entry->ref_cnt++; - break; - } - - - if (show) - stksess_kill_if_expired(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry); - else if (!skip_entry && !appctx->ctx.table.entry->ref_cnt) - stksess_kill(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry); - - appctx->ctx.table.proxy = appctx->ctx.table.proxy->next; - appctx->st2 = STAT_ST_INFO; - break; - - case STAT_ST_END: - appctx->st2 = STAT_ST_FIN; - break; - } - } - return 1; -} - /* This function dumps all captured errors onto the stream interface's * read buffer. It returns 0 if the output buffer is full and it needs * to be called again, otherwise non-zero. diff --git a/src/stick_table.c b/src/stick_table.c index 8f0392c41..dc772ae22 100644 --- a/src/stick_table.c +++ b/src/stick_table.c @@ -12,6 +12,7 @@ */ #include <string.h> +#include <errno.h> #include <common/config.h> #include <common/memory.h> @@ -22,12 +23,17 @@ #include <ebmbtree.h> #include <ebsttree.h> +#include <types/cli.h> +#include <types/stats.h> + #include <proto/arg.h> +#include <proto/cli.h> #include <proto/proto_http.h> #include <proto/proto_tcp.h> #include <proto/proxy.h> #include <proto/sample.h> #include <proto/stream.h> +#include <proto/stream_interface.h> #include <proto/stick_table.h> #include <proto/task.h> #include <proto/peers.h> @@ -1514,6 +1520,568 @@ static enum act_parse_ret parse_set_gpt0(const char **args, int *arg, struct pro return ACT_RET_PRS_OK; } + +/* The functions below are used to manipulate table contents from the CLI. + * There are 3 main actions, "clear", "set" and "show". The code is shared + * between all actions, and the action is encoded in the void *private in + * the appctx as well as in the keyword registration, among one of the + * following values. + */ + +enum { + STK_CLI_ACT_CLR, + STK_CLI_ACT_SET, + STK_CLI_ACT_SHOW, +}; + +/* Dump the status of a table to a stream interface's + * read buffer. It returns 0 if the output buffer is full + * and needs to be called again, otherwise non-zero. + */ +static int table_dump_head_to_buffer(struct chunk *msg, struct stream_interface *si, + struct proxy *proxy, struct proxy *target) +{ + struct stream *s = si_strm(si); + + chunk_appendf(msg, "# table: %s, type: %s, size:%d, used:%d\n", + proxy->id, stktable_types[proxy->table.type].kw, proxy->table.size, proxy->table.current); + + /* any other information should be dumped here */ + + if (target && strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) + chunk_appendf(msg, "# contents not dumped due to insufficient privileges\n"); + + if (bi_putchk(si_ic(si), msg) == -1) { + si_applet_cant_put(si); + return 0; + } + + return 1; +} + +/* Dump a table entry to a stream interface's + * read buffer. It returns 0 if the output buffer is full + * and needs to be called again, otherwise non-zero. + */ +static int table_dump_entry_to_buffer(struct chunk *msg, struct stream_interface *si, + struct proxy *proxy, struct stksess *entry) +{ + int dt; + + chunk_appendf(msg, "%p:", entry); + + if (proxy->table.type == SMP_T_IPV4) { + char addr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, (const void *)&entry->key.key, addr, sizeof(addr)); + chunk_appendf(msg, " key=%s", addr); + } + else if (proxy->table.type == SMP_T_IPV6) { + char addr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, (const void *)&entry->key.key, addr, sizeof(addr)); + chunk_appendf(msg, " key=%s", addr); + } + else if (proxy->table.type == SMP_T_SINT) { + chunk_appendf(msg, " key=%u", *(unsigned int *)entry->key.key); + } + else if (proxy->table.type == SMP_T_STR) { + chunk_appendf(msg, " key="); + dump_text(msg, (const char *)entry->key.key, proxy->table.key_size); + } + else { + chunk_appendf(msg, " key="); + dump_binary(msg, (const char *)entry->key.key, proxy->table.key_size); + } + + chunk_appendf(msg, " use=%d exp=%d", entry->ref_cnt - 1, tick_remain(now_ms, entry->expire)); + + for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) { + void *ptr; + + if (proxy->table.data_ofs[dt] == 0) + continue; + if (stktable_data_types[dt].arg_type == ARG_T_DELAY) + chunk_appendf(msg, " %s(%d)=", stktable_data_types[dt].name, proxy->table.data_arg[dt].u); + else + chunk_appendf(msg, " %s=", stktable_data_types[dt].name); + + ptr = stktable_data_ptr(&proxy->table, entry, dt); + switch (stktable_data_types[dt].std_type) { + case STD_T_SINT: + chunk_appendf(msg, "%d", stktable_data_cast(ptr, std_t_sint)); + break; + case STD_T_UINT: + chunk_appendf(msg, "%u", stktable_data_cast(ptr, std_t_uint)); + break; + case STD_T_ULL: + chunk_appendf(msg, "%lld", stktable_data_cast(ptr, std_t_ull)); + break; + case STD_T_FRQP: + chunk_appendf(msg, "%d", + read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp), + proxy->table.data_arg[dt].u)); + break; + } + } + chunk_appendf(msg, "\n"); + + if (bi_putchk(si_ic(si), msg) == -1) { + si_applet_cant_put(si); + return 0; + } + + return 1; +} + + +/* Processes a single table entry matching a specific key passed in argument. + * returns 0 if wants to be called again, 1 if has ended processing. + */ +static int table_process_entry_per_key(struct appctx *appctx, char **args) +{ + struct stream_interface *si = appctx->owner; + int action = (long)appctx->private; + struct proxy *px = appctx->ctx.table.target; + struct stksess *ts; + uint32_t uint32_key; + unsigned char ip6_key[sizeof(struct in6_addr)]; + long long value; + int data_type; + int cur_arg; + void *ptr; + struct freq_ctr_period *frqp; + + if (!*args[4]) { + appctx->ctx.cli.msg = "Key value expected\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + switch (px->table.type) { + case SMP_T_IPV4: + uint32_key = htonl(inetaddr_host(args[4])); + static_table_key->key = &uint32_key; + break; + case SMP_T_IPV6: + inet_pton(AF_INET6, args[4], ip6_key); + static_table_key->key = &ip6_key; + break; + case SMP_T_SINT: + { + char *endptr; + unsigned long val; + errno = 0; + val = strtoul(args[4], &endptr, 10); + if ((errno == ERANGE && val == ULONG_MAX) || + (errno != 0 && val == 0) || endptr == args[4] || + val > 0xffffffff) { + appctx->ctx.cli.msg = "Invalid key\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + uint32_key = (uint32_t) val; + static_table_key->key = &uint32_key; + break; + } + break; + case SMP_T_STR: + static_table_key->key = args[4]; + static_table_key->key_len = strlen(args[4]); + break; + default: + switch (action) { + case STK_CLI_ACT_SHOW: + appctx->ctx.cli.msg = "Showing keys from tables of type other than ip, ipv6, string and integer is not supported\n"; + break; + case STK_CLI_ACT_CLR: + appctx->ctx.cli.msg = "Removing keys from tables of type other than ip, ipv6, string and integer is not supported\n"; + break; + case STK_CLI_ACT_SET: + appctx->ctx.cli.msg = "Inserting keys into tables of type other than ip, ipv6, string and integer is not supported\n"; + break; + default: + appctx->ctx.cli.msg = "Unknown action\n"; + break; + } + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* check permissions */ + if (!cli_has_level(appctx, ACCESS_LVL_OPER)) + return 1; + + ts = stktable_lookup_key(&px->table, static_table_key); + + switch (action) { + case STK_CLI_ACT_SHOW: + if (!ts) + return 1; + chunk_reset(&trash); + if (!table_dump_head_to_buffer(&trash, si, px, px)) + return 0; + if (!table_dump_entry_to_buffer(&trash, si, px, ts)) + return 0; + break; + + case STK_CLI_ACT_CLR: + if (!ts) + return 1; + if (ts->ref_cnt) { + /* don't delete an entry which is currently referenced */ + appctx->ctx.cli.msg = "Entry currently in use, cannot remove\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + stksess_kill(&px->table, ts); + break; + + case STK_CLI_ACT_SET: + if (ts) + stktable_touch(&px->table, ts, 1); + else { + ts = stksess_new(&px->table, static_table_key); + if (!ts) { + /* don't delete an entry which is currently referenced */ + appctx->ctx.cli.msg = "Unable to allocate a new entry\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + stktable_store(&px->table, ts, 1); + } + + for (cur_arg = 5; *args[cur_arg]; cur_arg += 2) { + if (strncmp(args[cur_arg], "data.", 5) != 0) { + appctx->ctx.cli.msg = "\"data.<type>\" followed by a value expected\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + data_type = stktable_get_data_type(args[cur_arg] + 5); + if (data_type < 0) { + appctx->ctx.cli.msg = "Unknown data type\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + if (!px->table.data_ofs[data_type]) { + appctx->ctx.cli.msg = "Data type not stored in this table\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + if (!*args[cur_arg+1] || strl2llrc(args[cur_arg+1], strlen(args[cur_arg+1]), &value) != 0) { + appctx->ctx.cli.msg = "Require a valid integer value to store\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + ptr = stktable_data_ptr(&px->table, ts, data_type); + + switch (stktable_data_types[data_type].std_type) { + case STD_T_SINT: + stktable_data_cast(ptr, std_t_sint) = value; + break; + case STD_T_UINT: + stktable_data_cast(ptr, std_t_uint) = value; + break; + case STD_T_ULL: + stktable_data_cast(ptr, std_t_ull) = value; + break; + case STD_T_FRQP: + /* We set both the current and previous values. That way + * the reported frequency is stable during all the period + * then slowly fades out. This allows external tools to + * push measures without having to update them too often. + */ + frqp = &stktable_data_cast(ptr, std_t_frqp); + frqp->curr_tick = now_ms; + frqp->prev_ctr = 0; + frqp->curr_ctr = value; + break; + } + } + break; + + default: + appctx->ctx.cli.msg = "Unknown action\n"; + appctx->st0 = STAT_CLI_PRINT; + break; + } + return 1; +} + +/* Prepares the appctx fields with the data-based filters from the command line. + * Returns 0 if the dump can proceed, 1 if has ended processing. + */ +static int table_prepare_data_request(struct appctx *appctx, char **args) +{ + int action = (long)appctx->private; + + if (action != STK_CLI_ACT_SHOW && action != STK_CLI_ACT_CLR) { + appctx->ctx.cli.msg = "content-based lookup is only supported with the \"show\" and \"clear\" actions"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* condition on stored data value */ + appctx->ctx.table.data_type = stktable_get_data_type(args[3] + 5); + if (appctx->ctx.table.data_type < 0) { + appctx->ctx.cli.msg = "Unknown data type\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + if (!((struct proxy *)appctx->ctx.table.target)->table.data_ofs[appctx->ctx.table.data_type]) { + appctx->ctx.cli.msg = "Data type not stored in this table\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + appctx->ctx.table.data_op = get_std_op(args[4]); + if (appctx->ctx.table.data_op < 0) { + appctx->ctx.cli.msg = "Require and operator among \"eq\", \"ne\", \"le\", \"ge\", \"lt\", \"gt\"\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + if (!*args[5] || strl2llrc(args[5], strlen(args[5]), &appctx->ctx.table.value) != 0) { + appctx->ctx.cli.msg = "Require a valid integer value to compare against\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* OK we're done, all the fields are set */ + return 0; +} + +/* returns 0 if wants to be called, 1 if has ended processing */ +static int cli_parse_table_req(char **args, struct appctx *appctx, void *private) +{ + int action = (long)private; + + appctx->private = private; + appctx->ctx.table.data_type = -1; + appctx->st2 = STAT_ST_INIT; + appctx->ctx.table.target = NULL; + appctx->ctx.table.proxy = NULL; + appctx->ctx.table.entry = NULL; + + if (*args[2]) { + appctx->ctx.table.target = proxy_tbl_by_name(args[2]); + if (!appctx->ctx.table.target) { + appctx->ctx.cli.msg = "No such table\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + } + else { + if (action != STK_CLI_ACT_SHOW) + goto err_args; + return 0; + } + + if (strcmp(args[3], "key") == 0) + return table_process_entry_per_key(appctx, args); + else if (strncmp(args[3], "data.", 5) == 0) + return table_prepare_data_request(appctx, args); + else if (*args[3]) + goto err_args; + + return 0; + +err_args: + switch (action) { + case STK_CLI_ACT_SHOW: + appctx->ctx.cli.msg = "Optional argument only supports \"data.<store_data_type>\" <operator> <value> and key <key>\n"; + break; + case STK_CLI_ACT_CLR: + appctx->ctx.cli.msg = "Required arguments: <table> \"data.<store_data_type>\" <operator> <value> or <table> key <key>\n"; + break; + case STK_CLI_ACT_SET: + appctx->ctx.cli.msg = "Required arguments: <table> key <key> [data.<store_data_type> <value>]*\n"; + break; + default: + appctx->ctx.cli.msg = "Unknown action\n"; + break; + } + appctx->st0 = STAT_CLI_PRINT; + return 1; +} + +/* This function is used to deal with table operations (dump or clear depending + * on the action stored in appctx->private). It returns 0 if the output buffer is + * full and it needs to be called again, otherwise non-zero. + */ +static int cli_io_handler_table(struct appctx *appctx) +{ + struct stream_interface *si = appctx->owner; + int action = (long)appctx->private; + struct stream *s = si_strm(si); + struct ebmb_node *eb; + int dt; + int skip_entry; + int show = action == STK_CLI_ACT_SHOW; + + /* + * We have 3 possible states in appctx->st2 : + * - STAT_ST_INIT : the first call + * - STAT_ST_INFO : the proxy pointer points to the next table to + * dump, the entry pointer is NULL ; + * - STAT_ST_LIST : the proxy pointer points to the current table + * and the entry pointer points to the next entry to be dumped, + * and the refcount on the next entry is held ; + * - STAT_ST_END : nothing left to dump, the buffer may contain some + * data though. + */ + + if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW))) { + /* in case of abort, remove any refcount we might have set on an entry */ + if (appctx->st2 == STAT_ST_LIST) { + appctx->ctx.table.entry->ref_cnt--; + stksess_kill_if_expired(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry); + } + return 1; + } + + chunk_reset(&trash); + + while (appctx->st2 != STAT_ST_FIN) { + switch (appctx->st2) { + case STAT_ST_INIT: + appctx->ctx.table.proxy = appctx->ctx.table.target; + if (!appctx->ctx.table.proxy) + appctx->ctx.table.proxy = proxy; + + appctx->ctx.table.entry = NULL; + appctx->st2 = STAT_ST_INFO; + break; + + case STAT_ST_INFO: + if (!appctx->ctx.table.proxy || + (appctx->ctx.table.target && + appctx->ctx.table.proxy != appctx->ctx.table.target)) { + appctx->st2 = STAT_ST_END; + break; + } + + if (appctx->ctx.table.proxy->table.size) { + if (show && !table_dump_head_to_buffer(&trash, si, appctx->ctx.table.proxy, appctx->ctx.table.target)) + return 0; + + if (appctx->ctx.table.target && + strm_li(s)->bind_conf->level >= ACCESS_LVL_OPER) { + /* dump entries only if table explicitly requested */ + eb = ebmb_first(&appctx->ctx.table.proxy->table.keys); + if (eb) { + appctx->ctx.table.entry = ebmb_entry(eb, struct stksess, key); + appctx->ctx.table.entry->ref_cnt++; + appctx->st2 = STAT_ST_LIST; + break; + } + } + } + appctx->ctx.table.proxy = appctx->ctx.table.proxy->next; + break; + + case STAT_ST_LIST: + skip_entry = 0; + + if (appctx->ctx.table.data_type >= 0) { + /* we're filtering on some data contents */ + void *ptr; + long long data; + + dt = appctx->ctx.table.data_type; + ptr = stktable_data_ptr(&appctx->ctx.table.proxy->table, + appctx->ctx.table.entry, + dt); + + data = 0; + switch (stktable_data_types[dt].std_type) { + case STD_T_SINT: + data = stktable_data_cast(ptr, std_t_sint); + break; + case STD_T_UINT: + data = stktable_data_cast(ptr, std_t_uint); + break; + case STD_T_ULL: + data = stktable_data_cast(ptr, std_t_ull); + break; + case STD_T_FRQP: + data = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp), + appctx->ctx.table.proxy->table.data_arg[dt].u); + break; + } + + /* skip the entry if the data does not match the test and the value */ + if ((data < appctx->ctx.table.value && + (appctx->ctx.table.data_op == STD_OP_EQ || + appctx->ctx.table.data_op == STD_OP_GT || + appctx->ctx.table.data_op == STD_OP_GE)) || + (data == appctx->ctx.table.value && + (appctx->ctx.table.data_op == STD_OP_NE || + appctx->ctx.table.data_op == STD_OP_GT || + appctx->ctx.table.data_op == STD_OP_LT)) || + (data > appctx->ctx.table.value && + (appctx->ctx.table.data_op == STD_OP_EQ || + appctx->ctx.table.data_op == STD_OP_LT || + appctx->ctx.table.data_op == STD_OP_LE))) + skip_entry = 1; + } + + if (show && !skip_entry && + !table_dump_entry_to_buffer(&trash, si, appctx->ctx.table.proxy, appctx->ctx.table.entry)) + return 0; + + appctx->ctx.table.entry->ref_cnt--; + + eb = ebmb_next(&appctx->ctx.table.entry->key); + if (eb) { + struct stksess *old = appctx->ctx.table.entry; + appctx->ctx.table.entry = ebmb_entry(eb, struct stksess, key); + if (show) + stksess_kill_if_expired(&appctx->ctx.table.proxy->table, old); + else if (!skip_entry && !appctx->ctx.table.entry->ref_cnt) + stksess_kill(&appctx->ctx.table.proxy->table, old); + appctx->ctx.table.entry->ref_cnt++; + break; + } + + + if (show) + stksess_kill_if_expired(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry); + else if (!skip_entry && !appctx->ctx.table.entry->ref_cnt) + stksess_kill(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry); + + appctx->ctx.table.proxy = appctx->ctx.table.proxy->next; + appctx->st2 = STAT_ST_INFO; + break; + + case STAT_ST_END: + appctx->st2 = STAT_ST_FIN; + break; + } + } + return 1; +} + +static void cli_release_show_table(struct appctx *appctx) +{ + if (appctx->st2 == STAT_ST_LIST) { + appctx->ctx.table.entry->ref_cnt--; + stksess_kill_if_expired(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry); + } +} + +/* register cli keywords */ +static struct cli_kw_list cli_kws = {{ },{ + { { "clear", "table", NULL }, "clear table : remove an entry from a table", cli_parse_table_req, cli_io_handler_table, cli_release_show_table, (void *)STK_CLI_ACT_CLR }, + { { "set", "table", NULL }, "set table [id] : update or create a table entry's data", cli_parse_table_req, cli_io_handler_table, NULL, (void *)STK_CLI_ACT_SET }, + { { "show", "table", NULL }, "show table [id]: report table usage stats or dump this table's contents", cli_parse_table_req, cli_io_handler_table, cli_release_show_table, (void *)STK_CLI_ACT_SHOW }, + {{},} +}}; + + static struct action_kw_list tcp_conn_kws = { { }, { { "sc-inc-gpc0", parse_inc_gpc0, 1 }, { "sc-set-gpt0", parse_set_gpt0, 1 }, @@ -1587,4 +2155,5 @@ static void __stick_table_init(void) /* register sample fetch and format conversion keywords */ sample_register_convs(&sample_conv_kws); + cli_register_kw(&cli_kws); }