blob: 39010510466722f2bfcc8ee492a930b326d49af6 [file] [log] [blame]
William Lallemand74c24fb2016-11-21 17:18:36 +01001/*
2 * Functions dedicated to statistics output and the stats socket
3 *
4 * Copyright 2000-2012 Willy Tarreau <w@1wt.eu>
5 * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
14#include <ctype.h>
15#include <errno.h>
16#include <fcntl.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <pwd.h>
21#include <grp.h>
22
23#include <sys/socket.h>
24#include <sys/stat.h>
25#include <sys/types.h>
26
27#include <common/cfgparse.h>
28#include <common/compat.h>
29#include <common/config.h>
30#include <common/debug.h>
31#include <common/memory.h>
32#include <common/mini-clist.h>
33#include <common/standard.h>
34#include <common/ticks.h>
35#include <common/time.h>
36#include <common/uri_auth.h>
37#include <common/version.h>
38#include <common/base64.h>
39
40#include <types/applet.h>
William Lallemand9ed62032016-11-21 17:49:11 +010041#include <types/cli.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010042#include <types/global.h>
43#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010044#include <types/stats.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010045
46#include <proto/backend.h>
47#include <proto/channel.h>
48#include <proto/checks.h>
49#include <proto/compression.h>
William Lallemand9ed62032016-11-21 17:49:11 +010050#include <proto/stats.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010051#include <proto/fd.h>
52#include <proto/freq_ctr.h>
53#include <proto/frontend.h>
54#include <proto/log.h>
55#include <proto/pattern.h>
56#include <proto/pipe.h>
57#include <proto/listener.h>
58#include <proto/map.h>
59#include <proto/proto_http.h>
60#include <proto/proto_uxst.h>
61#include <proto/proxy.h>
62#include <proto/sample.h>
63#include <proto/session.h>
64#include <proto/stream.h>
65#include <proto/server.h>
66#include <proto/raw_sock.h>
67#include <proto/stream_interface.h>
68#include <proto/task.h>
69
70#ifdef USE_OPENSSL
71#include <proto/ssl_sock.h>
72#include <types/ssl_sock.h>
73#endif
74
75/* These are the field names for each INF_* field position. Please pay attention
76 * to always use the exact same name except that the strings for new names must
77 * be lower case or CamelCase while the enum entries must be upper case.
78 */
79const char *info_field_names[INF_TOTAL_FIELDS] = {
80 [INF_NAME] = "Name",
81 [INF_VERSION] = "Version",
82 [INF_RELEASE_DATE] = "Release_date",
83 [INF_NBPROC] = "Nbproc",
84 [INF_PROCESS_NUM] = "Process_num",
85 [INF_PID] = "Pid",
86 [INF_UPTIME] = "Uptime",
87 [INF_UPTIME_SEC] = "Uptime_sec",
88 [INF_MEMMAX_MB] = "Memmax_MB",
89 [INF_POOL_ALLOC_MB] = "PoolAlloc_MB",
90 [INF_POOL_USED_MB] = "PoolUsed_MB",
91 [INF_POOL_FAILED] = "PoolFailed",
92 [INF_ULIMIT_N] = "Ulimit-n",
93 [INF_MAXSOCK] = "Maxsock",
94 [INF_MAXCONN] = "Maxconn",
95 [INF_HARD_MAXCONN] = "Hard_maxconn",
96 [INF_CURR_CONN] = "CurrConns",
97 [INF_CUM_CONN] = "CumConns",
98 [INF_CUM_REQ] = "CumReq",
99 [INF_MAX_SSL_CONNS] = "MaxSslConns",
100 [INF_CURR_SSL_CONNS] = "CurrSslConns",
101 [INF_CUM_SSL_CONNS] = "CumSslConns",
102 [INF_MAXPIPES] = "Maxpipes",
103 [INF_PIPES_USED] = "PipesUsed",
104 [INF_PIPES_FREE] = "PipesFree",
105 [INF_CONN_RATE] = "ConnRate",
106 [INF_CONN_RATE_LIMIT] = "ConnRateLimit",
107 [INF_MAX_CONN_RATE] = "MaxConnRate",
108 [INF_SESS_RATE] = "SessRate",
109 [INF_SESS_RATE_LIMIT] = "SessRateLimit",
110 [INF_MAX_SESS_RATE] = "MaxSessRate",
111 [INF_SSL_RATE] = "SslRate",
112 [INF_SSL_RATE_LIMIT] = "SslRateLimit",
113 [INF_MAX_SSL_RATE] = "MaxSslRate",
114 [INF_SSL_FRONTEND_KEY_RATE] = "SslFrontendKeyRate",
115 [INF_SSL_FRONTEND_MAX_KEY_RATE] = "SslFrontendMaxKeyRate",
116 [INF_SSL_FRONTEND_SESSION_REUSE_PCT] = "SslFrontendSessionReuse_pct",
117 [INF_SSL_BACKEND_KEY_RATE] = "SslBackendKeyRate",
118 [INF_SSL_BACKEND_MAX_KEY_RATE] = "SslBackendMaxKeyRate",
119 [INF_SSL_CACHE_LOOKUPS] = "SslCacheLookups",
120 [INF_SSL_CACHE_MISSES] = "SslCacheMisses",
121 [INF_COMPRESS_BPS_IN] = "CompressBpsIn",
122 [INF_COMPRESS_BPS_OUT] = "CompressBpsOut",
123 [INF_COMPRESS_BPS_RATE_LIM] = "CompressBpsRateLim",
124 [INF_ZLIB_MEM_USAGE] = "ZlibMemUsage",
125 [INF_MAX_ZLIB_MEM_USAGE] = "MaxZlibMemUsage",
126 [INF_TASKS] = "Tasks",
127 [INF_RUN_QUEUE] = "Run_queue",
128 [INF_IDLE_PCT] = "Idle_pct",
129 [INF_NODE] = "node",
130 [INF_DESCRIPTION] = "description",
131};
132
133/* one line of stats */
134static struct field info[INF_TOTAL_FIELDS];
135
136static int stats_dump_backend_to_buffer(struct stream_interface *si);
137static int stats_dump_env_to_buffer(struct stream_interface *si);
138static int stats_dump_info_to_buffer(struct stream_interface *si);
139static int stats_dump_servers_state_to_buffer(struct stream_interface *si);
140static int stats_dump_pools_to_buffer(struct stream_interface *si);
141static int stats_dump_full_sess_to_buffer(struct stream_interface *si, struct stream *sess);
142static int stats_dump_sess_to_buffer(struct stream_interface *si);
143static int stats_dump_errors_to_buffer(struct stream_interface *si);
144static int stats_table_request(struct stream_interface *si, int show);
145static int stats_dump_resolvers_to_buffer(struct stream_interface *si);
William Lallemand74c24fb2016-11-21 17:18:36 +0100146
147static int dump_servers_state(struct stream_interface *si, struct chunk *buf);
148
149static struct applet cli_applet;
150
151static const char stats_sock_usage_msg[] =
152 "Unknown command. Please enter one of the following commands only :\n"
153 " clear counters : clear max statistics counters (add 'all' for all counters)\n"
154 " clear table : remove an entry from a table\n"
155 " help : this message\n"
156 " prompt : toggle interactive mode with prompt\n"
157 " quit : disconnect\n"
158 " show backend : list backends in the current running config\n"
159 " show env [var] : dump environment variables known to the process\n"
160 " show info : report information about the running process\n"
161 " show pools : report information about the memory pools usage\n"
162 " show stat : report counters for each proxy and server\n"
163 " show stat resolvers [id]: dumps counters from all resolvers section and\n"
164 " associated name servers\n"
165 " show errors : report last request and response errors for each proxy\n"
166 " show sess [id] : report the list of current sessions or dump this session\n"
167 " show table [id]: report table usage stats or dump this table's contents\n"
168 " show servers state [id]: dump volatile server information (for backend <id>)\n"
169 " get weight : report a server's current weight\n"
170 " set weight : change a server's weight\n"
171 " set server : change a server's state, weight or address\n"
172 " set table [id] : update or create a table entry's data\n"
173 " set timeout : change a timeout setting\n"
174 " set maxconn : change a maxconn setting\n"
175 " set rate-limit : change a rate limiting value\n"
176 " disable : put a server or frontend in maintenance mode\n"
177 " enable : re-enable a server or frontend which is in maintenance mode\n"
178 " shutdown : kill a session or a frontend (eg:to release listening ports)\n"
William Lallemand74c24fb2016-11-21 17:18:36 +0100179 "";
180
181static const char stats_permission_denied_msg[] =
182 "Permission denied\n"
183 "";
184
185
186static char *dynamic_usage_msg = NULL;
187
188/* List head of cli keywords */
189static struct cli_kw_list cli_keywords = {
190 .list = LIST_HEAD_INIT(cli_keywords.list)
191};
192
193extern const char *stat_status_codes[];
194
195char *cli_gen_usage_msg()
196{
197 struct cli_kw_list *kw_list;
198 struct cli_kw *kw;
199 struct chunk *tmp = get_trash_chunk();
200 struct chunk out;
201
202 free(dynamic_usage_msg);
203 dynamic_usage_msg = NULL;
204
205 if (LIST_ISEMPTY(&cli_keywords.list))
206 return NULL;
207
208 chunk_reset(tmp);
209 chunk_strcat(tmp, stats_sock_usage_msg);
210 list_for_each_entry(kw_list, &cli_keywords.list, list) {
211 kw = &kw_list->kw[0];
212 while (kw->usage) {
213 chunk_appendf(tmp, " %s\n", kw->usage);
214 kw++;
215 }
216 }
217 chunk_init(&out, NULL, 0);
218 chunk_dup(&out, tmp);
219 dynamic_usage_msg = out.str;
220 return dynamic_usage_msg;
221}
222
223struct cli_kw* cli_find_kw(char **args)
224{
225 struct cli_kw_list *kw_list;
226 struct cli_kw *kw;/* current cli_kw */
227 char **tmp_args;
228 const char **tmp_str_kw;
229 int found = 0;
230
231 if (LIST_ISEMPTY(&cli_keywords.list))
232 return NULL;
233
234 list_for_each_entry(kw_list, &cli_keywords.list, list) {
235 kw = &kw_list->kw[0];
236 while (*kw->str_kw) {
237 tmp_args = args;
238 tmp_str_kw = kw->str_kw;
239 while (*tmp_str_kw) {
240 if (strcmp(*tmp_str_kw, *tmp_args) == 0) {
241 found = 1;
242 } else {
243 found = 0;
244 break;
245 }
246 tmp_args++;
247 tmp_str_kw++;
248 }
249 if (found)
250 return (kw);
251 kw++;
252 }
253 }
254 return NULL;
255}
256
257void cli_register_kw(struct cli_kw_list *kw_list)
258{
259 LIST_ADDQ(&cli_keywords.list, &kw_list->list);
260}
261
262
263/* allocate a new stats frontend named <name>, and return it
264 * (or NULL in case of lack of memory).
265 */
266static struct proxy *alloc_stats_fe(const char *name, const char *file, int line)
267{
268 struct proxy *fe;
269
270 fe = calloc(1, sizeof(*fe));
271 if (!fe)
272 return NULL;
273
274 init_new_proxy(fe);
275 fe->next = proxy;
276 proxy = fe;
277 fe->last_change = now.tv_sec;
278 fe->id = strdup("GLOBAL");
279 fe->cap = PR_CAP_FE;
280 fe->maxconn = 10; /* default to 10 concurrent connections */
281 fe->timeout.client = MS_TO_TICKS(10000); /* default timeout of 10 seconds */
282 fe->conf.file = strdup(file);
283 fe->conf.line = line;
284 fe->accept = frontend_accept;
285 fe->default_target = &cli_applet.obj_type;
286
287 /* the stats frontend is the only one able to assign ID #0 */
288 fe->conf.id.key = fe->uuid = 0;
289 eb32_insert(&used_proxy_id, &fe->conf.id);
290 return fe;
291}
292
293/* This function parses a "stats" statement in the "global" section. It returns
294 * -1 if there is any error, otherwise zero. If it returns -1, it will write an
295 * error message into the <err> buffer which will be preallocated. The trailing
296 * '\n' must not be written. The function must be called with <args> pointing to
297 * the first word after "stats".
298 */
299static int stats_parse_global(char **args, int section_type, struct proxy *curpx,
300 struct proxy *defpx, const char *file, int line,
301 char **err)
302{
303 struct bind_conf *bind_conf;
304 struct listener *l;
305
306 if (!strcmp(args[1], "socket")) {
307 int cur_arg;
308
309 if (*args[2] == 0) {
310 memprintf(err, "'%s %s' in global section expects an address or a path to a UNIX socket", args[0], args[1]);
311 return -1;
312 }
313
314 if (!global.stats_fe) {
315 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
316 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
317 return -1;
318 }
319 }
320
321 bind_conf = bind_conf_alloc(&global.stats_fe->conf.bind, file, line, args[2]);
322 bind_conf->level = ACCESS_LVL_OPER; /* default access level */
323
324 if (!str2listener(args[2], global.stats_fe, bind_conf, file, line, err)) {
325 memprintf(err, "parsing [%s:%d] : '%s %s' : %s\n",
326 file, line, args[0], args[1], err && *err ? *err : "error");
327 return -1;
328 }
329
330 cur_arg = 3;
331 while (*args[cur_arg]) {
332 static int bind_dumped;
333 struct bind_kw *kw;
334
335 kw = bind_find_kw(args[cur_arg]);
336 if (kw) {
337 if (!kw->parse) {
338 memprintf(err, "'%s %s' : '%s' option is not implemented in this version (check build options).",
339 args[0], args[1], args[cur_arg]);
340 return -1;
341 }
342
343 if (kw->parse(args, cur_arg, global.stats_fe, bind_conf, err) != 0) {
344 if (err && *err)
345 memprintf(err, "'%s %s' : '%s'", args[0], args[1], *err);
346 else
347 memprintf(err, "'%s %s' : error encountered while processing '%s'",
348 args[0], args[1], args[cur_arg]);
349 return -1;
350 }
351
352 cur_arg += 1 + kw->skip;
353 continue;
354 }
355
356 if (!bind_dumped) {
357 bind_dump_kws(err);
358 indent_msg(err, 4);
359 bind_dumped = 1;
360 }
361
362 memprintf(err, "'%s %s' : unknown keyword '%s'.%s%s",
363 args[0], args[1], args[cur_arg],
364 err && *err ? " Registered keywords :" : "", err && *err ? *err : "");
365 return -1;
366 }
367
368 list_for_each_entry(l, &bind_conf->listeners, by_bind) {
369 l->maxconn = global.stats_fe->maxconn;
370 l->backlog = global.stats_fe->backlog;
371 l->accept = session_accept_fd;
372 l->handler = process_stream;
373 l->default_target = global.stats_fe->default_target;
374 l->options |= LI_O_UNLIMITED; /* don't make the peers subject to global limits */
375 l->nice = -64; /* we want to boost priority for local stats */
376 global.maxsock += l->maxconn;
377 }
378 }
379 else if (!strcmp(args[1], "timeout")) {
380 unsigned timeout;
381 const char *res = parse_time_err(args[2], &timeout, TIME_UNIT_MS);
382
383 if (res) {
384 memprintf(err, "'%s %s' : unexpected character '%c'", args[0], args[1], *res);
385 return -1;
386 }
387
388 if (!timeout) {
389 memprintf(err, "'%s %s' expects a positive value", args[0], args[1]);
390 return -1;
391 }
392 if (!global.stats_fe) {
393 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
394 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
395 return -1;
396 }
397 }
398 global.stats_fe->timeout.client = MS_TO_TICKS(timeout);
399 }
400 else if (!strcmp(args[1], "maxconn")) {
401 int maxconn = atol(args[2]);
402
403 if (maxconn <= 0) {
404 memprintf(err, "'%s %s' expects a positive value", args[0], args[1]);
405 return -1;
406 }
407
408 if (!global.stats_fe) {
409 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
410 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
411 return -1;
412 }
413 }
414 global.stats_fe->maxconn = maxconn;
415 }
416 else if (!strcmp(args[1], "bind-process")) { /* enable the socket only on some processes */
417 int cur_arg = 2;
418 unsigned long set = 0;
419
420 if (!global.stats_fe) {
421 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
422 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
423 return -1;
424 }
425 }
426
427 while (*args[cur_arg]) {
428 unsigned int low, high;
429
430 if (strcmp(args[cur_arg], "all") == 0) {
431 set = 0;
432 break;
433 }
434 else if (strcmp(args[cur_arg], "odd") == 0) {
435 set |= ~0UL/3UL; /* 0x555....555 */
436 }
437 else if (strcmp(args[cur_arg], "even") == 0) {
438 set |= (~0UL/3UL) << 1; /* 0xAAA...AAA */
439 }
440 else if (isdigit((int)*args[cur_arg])) {
441 char *dash = strchr(args[cur_arg], '-');
442
443 low = high = str2uic(args[cur_arg]);
444 if (dash)
445 high = str2uic(dash + 1);
446
447 if (high < low) {
448 unsigned int swap = low;
449 low = high;
450 high = swap;
451 }
452
453 if (low < 1 || high > LONGBITS) {
454 memprintf(err, "'%s %s' supports process numbers from 1 to %d.\n",
455 args[0], args[1], LONGBITS);
456 return -1;
457 }
458 while (low <= high)
459 set |= 1UL << (low++ - 1);
460 }
461 else {
462 memprintf(err,
463 "'%s %s' expects 'all', 'odd', 'even', or a list of process ranges with numbers from 1 to %d.\n",
464 args[0], args[1], LONGBITS);
465 return -1;
466 }
467 cur_arg++;
468 }
469 global.stats_fe->bind_proc = set;
470 }
471 else {
472 memprintf(err, "'%s' only supports 'socket', 'maxconn', 'bind-process' and 'timeout' (got '%s')", args[0], args[1]);
473 return -1;
474 }
475 return 0;
476}
477
478
479/* print a string of text buffer to <out>. The format is :
480 * Non-printable chars \t, \n, \r and \e are * encoded in C format.
481 * Other non-printable chars are encoded "\xHH". Space, '\', and '=' are also escaped.
482 * Print stopped if null char or <bsize> is reached, or if no more place in the chunk.
483 */
484static int dump_text(struct chunk *out, const char *buf, int bsize)
485{
486 unsigned char c;
487 int ptr = 0;
488
489 while (buf[ptr] && ptr < bsize) {
490 c = buf[ptr];
491 if (isprint(c) && isascii(c) && c != '\\' && c != ' ' && c != '=') {
492 if (out->len > out->size - 1)
493 break;
494 out->str[out->len++] = c;
495 }
496 else if (c == '\t' || c == '\n' || c == '\r' || c == '\e' || c == '\\' || c == ' ' || c == '=') {
497 if (out->len > out->size - 2)
498 break;
499 out->str[out->len++] = '\\';
500 switch (c) {
501 case ' ': c = ' '; break;
502 case '\t': c = 't'; break;
503 case '\n': c = 'n'; break;
504 case '\r': c = 'r'; break;
505 case '\e': c = 'e'; break;
506 case '\\': c = '\\'; break;
507 case '=': c = '='; break;
508 }
509 out->str[out->len++] = c;
510 }
511 else {
512 if (out->len > out->size - 4)
513 break;
514 out->str[out->len++] = '\\';
515 out->str[out->len++] = 'x';
516 out->str[out->len++] = hextab[(c >> 4) & 0xF];
517 out->str[out->len++] = hextab[c & 0xF];
518 }
519 ptr++;
520 }
521
522 return ptr;
523}
524
525/* print a buffer in hexa.
526 * Print stopped if <bsize> is reached, or if no more place in the chunk.
527 */
528static int dump_binary(struct chunk *out, const char *buf, int bsize)
529{
530 unsigned char c;
531 int ptr = 0;
532
533 while (ptr < bsize) {
534 c = buf[ptr];
535
536 if (out->len > out->size - 2)
537 break;
538 out->str[out->len++] = hextab[(c >> 4) & 0xF];
539 out->str[out->len++] = hextab[c & 0xF];
540
541 ptr++;
542 }
543 return ptr;
544}
545
546/* Dump the status of a table to a stream interface's
547 * read buffer. It returns 0 if the output buffer is full
548 * and needs to be called again, otherwise non-zero.
549 */
550static int stats_dump_table_head_to_buffer(struct chunk *msg, struct stream_interface *si,
551 struct proxy *proxy, struct proxy *target)
552{
553 struct stream *s = si_strm(si);
554
555 chunk_appendf(msg, "# table: %s, type: %s, size:%d, used:%d\n",
556 proxy->id, stktable_types[proxy->table.type].kw, proxy->table.size, proxy->table.current);
557
558 /* any other information should be dumped here */
559
560 if (target && strm_li(s)->bind_conf->level < ACCESS_LVL_OPER)
561 chunk_appendf(msg, "# contents not dumped due to insufficient privileges\n");
562
563 if (bi_putchk(si_ic(si), msg) == -1) {
564 si_applet_cant_put(si);
565 return 0;
566 }
567
568 return 1;
569}
570
571/* Dump the a table entry to a stream interface's
572 * read buffer. It returns 0 if the output buffer is full
573 * and needs to be called again, otherwise non-zero.
574 */
575static int stats_dump_table_entry_to_buffer(struct chunk *msg, struct stream_interface *si,
576 struct proxy *proxy, struct stksess *entry)
577{
578 int dt;
579
580 chunk_appendf(msg, "%p:", entry);
581
582 if (proxy->table.type == SMP_T_IPV4) {
583 char addr[INET_ADDRSTRLEN];
584 inet_ntop(AF_INET, (const void *)&entry->key.key, addr, sizeof(addr));
585 chunk_appendf(msg, " key=%s", addr);
586 }
587 else if (proxy->table.type == SMP_T_IPV6) {
588 char addr[INET6_ADDRSTRLEN];
589 inet_ntop(AF_INET6, (const void *)&entry->key.key, addr, sizeof(addr));
590 chunk_appendf(msg, " key=%s", addr);
591 }
592 else if (proxy->table.type == SMP_T_SINT) {
593 chunk_appendf(msg, " key=%u", *(unsigned int *)entry->key.key);
594 }
595 else if (proxy->table.type == SMP_T_STR) {
596 chunk_appendf(msg, " key=");
597 dump_text(msg, (const char *)entry->key.key, proxy->table.key_size);
598 }
599 else {
600 chunk_appendf(msg, " key=");
601 dump_binary(msg, (const char *)entry->key.key, proxy->table.key_size);
602 }
603
604 chunk_appendf(msg, " use=%d exp=%d", entry->ref_cnt - 1, tick_remain(now_ms, entry->expire));
605
606 for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
607 void *ptr;
608
609 if (proxy->table.data_ofs[dt] == 0)
610 continue;
611 if (stktable_data_types[dt].arg_type == ARG_T_DELAY)
612 chunk_appendf(msg, " %s(%d)=", stktable_data_types[dt].name, proxy->table.data_arg[dt].u);
613 else
614 chunk_appendf(msg, " %s=", stktable_data_types[dt].name);
615
616 ptr = stktable_data_ptr(&proxy->table, entry, dt);
617 switch (stktable_data_types[dt].std_type) {
618 case STD_T_SINT:
619 chunk_appendf(msg, "%d", stktable_data_cast(ptr, std_t_sint));
620 break;
621 case STD_T_UINT:
622 chunk_appendf(msg, "%u", stktable_data_cast(ptr, std_t_uint));
623 break;
624 case STD_T_ULL:
625 chunk_appendf(msg, "%lld", stktable_data_cast(ptr, std_t_ull));
626 break;
627 case STD_T_FRQP:
628 chunk_appendf(msg, "%d",
629 read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
630 proxy->table.data_arg[dt].u));
631 break;
632 }
633 }
634 chunk_appendf(msg, "\n");
635
636 if (bi_putchk(si_ic(si), msg) == -1) {
637 si_applet_cant_put(si);
638 return 0;
639 }
640
641 return 1;
642}
643
644static void stats_sock_table_key_request(struct stream_interface *si, char **args, int action)
645{
646 struct stream *s = si_strm(si);
647 struct appctx *appctx = __objt_appctx(si->end);
648 struct proxy *px = appctx->ctx.table.target;
649 struct stksess *ts;
650 uint32_t uint32_key;
651 unsigned char ip6_key[sizeof(struct in6_addr)];
652 long long value;
653 int data_type;
654 int cur_arg;
655 void *ptr;
656 struct freq_ctr_period *frqp;
657
658 appctx->st0 = STAT_CLI_OUTPUT;
659
660 if (!*args[4]) {
661 appctx->ctx.cli.msg = "Key value expected\n";
662 appctx->st0 = STAT_CLI_PRINT;
663 return;
664 }
665
666 switch (px->table.type) {
667 case SMP_T_IPV4:
668 uint32_key = htonl(inetaddr_host(args[4]));
669 static_table_key->key = &uint32_key;
670 break;
671 case SMP_T_IPV6:
672 inet_pton(AF_INET6, args[4], ip6_key);
673 static_table_key->key = &ip6_key;
674 break;
675 case SMP_T_SINT:
676 {
677 char *endptr;
678 unsigned long val;
679 errno = 0;
680 val = strtoul(args[4], &endptr, 10);
681 if ((errno == ERANGE && val == ULONG_MAX) ||
682 (errno != 0 && val == 0) || endptr == args[4] ||
683 val > 0xffffffff) {
684 appctx->ctx.cli.msg = "Invalid key\n";
685 appctx->st0 = STAT_CLI_PRINT;
686 return;
687 }
688 uint32_key = (uint32_t) val;
689 static_table_key->key = &uint32_key;
690 break;
691 }
692 break;
693 case SMP_T_STR:
694 static_table_key->key = args[4];
695 static_table_key->key_len = strlen(args[4]);
696 break;
697 default:
698 switch (action) {
699 case STAT_CLI_O_TAB:
700 appctx->ctx.cli.msg = "Showing keys from tables of type other than ip, ipv6, string and integer is not supported\n";
701 break;
702 case STAT_CLI_O_CLR:
703 appctx->ctx.cli.msg = "Removing keys from ip tables of type other than ip, ipv6, string and integer is not supported\n";
704 break;
705 default:
706 appctx->ctx.cli.msg = "Unknown action\n";
707 break;
708 }
709 appctx->st0 = STAT_CLI_PRINT;
710 return;
711 }
712
713 /* check permissions */
714 if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) {
715 appctx->ctx.cli.msg = stats_permission_denied_msg;
716 appctx->st0 = STAT_CLI_PRINT;
717 return;
718 }
719
720 ts = stktable_lookup_key(&px->table, static_table_key);
721
722 switch (action) {
723 case STAT_CLI_O_TAB:
724 if (!ts)
725 return;
726 chunk_reset(&trash);
727 if (!stats_dump_table_head_to_buffer(&trash, si, px, px))
728 return;
729 stats_dump_table_entry_to_buffer(&trash, si, px, ts);
730 return;
731
732 case STAT_CLI_O_CLR:
733 if (!ts)
734 return;
735 if (ts->ref_cnt) {
736 /* don't delete an entry which is currently referenced */
737 appctx->ctx.cli.msg = "Entry currently in use, cannot remove\n";
738 appctx->st0 = STAT_CLI_PRINT;
739 return;
740 }
741 stksess_kill(&px->table, ts);
742 break;
743
744 case STAT_CLI_O_SET:
745 if (ts)
746 stktable_touch(&px->table, ts, 1);
747 else {
748 ts = stksess_new(&px->table, static_table_key);
749 if (!ts) {
750 /* don't delete an entry which is currently referenced */
751 appctx->ctx.cli.msg = "Unable to allocate a new entry\n";
752 appctx->st0 = STAT_CLI_PRINT;
753 return;
754 }
755 stktable_store(&px->table, ts, 1);
756 }
757
758 for (cur_arg = 5; *args[cur_arg]; cur_arg += 2) {
759 if (strncmp(args[cur_arg], "data.", 5) != 0) {
760 appctx->ctx.cli.msg = "\"data.<type>\" followed by a value expected\n";
761 appctx->st0 = STAT_CLI_PRINT;
762 return;
763 }
764
765 data_type = stktable_get_data_type(args[cur_arg] + 5);
766 if (data_type < 0) {
767 appctx->ctx.cli.msg = "Unknown data type\n";
768 appctx->st0 = STAT_CLI_PRINT;
769 return;
770 }
771
772 if (!px->table.data_ofs[data_type]) {
773 appctx->ctx.cli.msg = "Data type not stored in this table\n";
774 appctx->st0 = STAT_CLI_PRINT;
775 return;
776 }
777
778 if (!*args[cur_arg+1] || strl2llrc(args[cur_arg+1], strlen(args[cur_arg+1]), &value) != 0) {
779 appctx->ctx.cli.msg = "Require a valid integer value to store\n";
780 appctx->st0 = STAT_CLI_PRINT;
781 return;
782 }
783
784 ptr = stktable_data_ptr(&px->table, ts, data_type);
785
786 switch (stktable_data_types[data_type].std_type) {
787 case STD_T_SINT:
788 stktable_data_cast(ptr, std_t_sint) = value;
789 break;
790 case STD_T_UINT:
791 stktable_data_cast(ptr, std_t_uint) = value;
792 break;
793 case STD_T_ULL:
794 stktable_data_cast(ptr, std_t_ull) = value;
795 break;
796 case STD_T_FRQP:
797 /* We set both the current and previous values. That way
798 * the reported frequency is stable during all the period
799 * then slowly fades out. This allows external tools to
800 * push measures without having to update them too often.
801 */
802 frqp = &stktable_data_cast(ptr, std_t_frqp);
803 frqp->curr_tick = now_ms;
804 frqp->prev_ctr = 0;
805 frqp->curr_ctr = value;
806 break;
807 }
808 }
809 break;
810
811 default:
812 appctx->ctx.cli.msg = "Unknown action\n";
813 appctx->st0 = STAT_CLI_PRINT;
814 break;
815 }
816}
817
818static void stats_sock_table_data_request(struct stream_interface *si, char **args, int action)
819{
820 struct appctx *appctx = __objt_appctx(si->end);
821
822 if (action != STAT_CLI_O_TAB && action != STAT_CLI_O_CLR) {
823 appctx->ctx.cli.msg = "content-based lookup is only supported with the \"show\" and \"clear\" actions";
824 appctx->st0 = STAT_CLI_PRINT;
825 return;
826 }
827
828 /* condition on stored data value */
829 appctx->ctx.table.data_type = stktable_get_data_type(args[3] + 5);
830 if (appctx->ctx.table.data_type < 0) {
831 appctx->ctx.cli.msg = "Unknown data type\n";
832 appctx->st0 = STAT_CLI_PRINT;
833 return;
834 }
835
836 if (!((struct proxy *)appctx->ctx.table.target)->table.data_ofs[appctx->ctx.table.data_type]) {
837 appctx->ctx.cli.msg = "Data type not stored in this table\n";
838 appctx->st0 = STAT_CLI_PRINT;
839 return;
840 }
841
842 appctx->ctx.table.data_op = get_std_op(args[4]);
843 if (appctx->ctx.table.data_op < 0) {
844 appctx->ctx.cli.msg = "Require and operator among \"eq\", \"ne\", \"le\", \"ge\", \"lt\", \"gt\"\n";
845 appctx->st0 = STAT_CLI_PRINT;
846 return;
847 }
848
849 if (!*args[5] || strl2llrc(args[5], strlen(args[5]), &appctx->ctx.table.value) != 0) {
850 appctx->ctx.cli.msg = "Require a valid integer value to compare against\n";
851 appctx->st0 = STAT_CLI_PRINT;
852 return;
853 }
854}
855
856static void stats_sock_table_request(struct stream_interface *si, char **args, int action)
857{
858 struct appctx *appctx = __objt_appctx(si->end);
859
860 appctx->ctx.table.data_type = -1;
861 appctx->st2 = STAT_ST_INIT;
862 appctx->ctx.table.target = NULL;
863 appctx->ctx.table.proxy = NULL;
864 appctx->ctx.table.entry = NULL;
865 appctx->st0 = action;
866
867 if (*args[2]) {
868 appctx->ctx.table.target = proxy_tbl_by_name(args[2]);
869 if (!appctx->ctx.table.target) {
870 appctx->ctx.cli.msg = "No such table\n";
871 appctx->st0 = STAT_CLI_PRINT;
872 return;
873 }
874 }
875 else {
876 if (action != STAT_CLI_O_TAB)
877 goto err_args;
878 return;
879 }
880
881 if (strcmp(args[3], "key") == 0)
882 stats_sock_table_key_request(si, args, action);
883 else if (strncmp(args[3], "data.", 5) == 0)
884 stats_sock_table_data_request(si, args, action);
885 else if (*args[3])
886 goto err_args;
887
888 return;
889
890err_args:
891 switch (action) {
892 case STAT_CLI_O_TAB:
893 appctx->ctx.cli.msg = "Optional argument only supports \"data.<store_data_type>\" <operator> <value> and key <key>\n";
894 break;
895 case STAT_CLI_O_CLR:
896 appctx->ctx.cli.msg = "Required arguments: <table> \"data.<store_data_type>\" <operator> <value> or <table> key <key>\n";
897 break;
898 default:
899 appctx->ctx.cli.msg = "Unknown action\n";
900 break;
901 }
902 appctx->st0 = STAT_CLI_PRINT;
903}
904
905/* Expects to find a frontend named <arg> and returns it, otherwise displays various
906 * adequate error messages and returns NULL. This function also expects the stream
907 * level to be admin.
908 */
909static struct proxy *expect_frontend_admin(struct stream *s, struct stream_interface *si, const char *arg)
910{
911 struct appctx *appctx = __objt_appctx(si->end);
912 struct proxy *px;
913
914 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
915 appctx->ctx.cli.msg = stats_permission_denied_msg;
916 appctx->st0 = STAT_CLI_PRINT;
917 return NULL;
918 }
919
920 if (!*arg) {
921 appctx->ctx.cli.msg = "A frontend name is expected.\n";
922 appctx->st0 = STAT_CLI_PRINT;
923 return NULL;
924 }
925
926 px = proxy_fe_by_name(arg);
927 if (!px) {
928 appctx->ctx.cli.msg = "No such frontend.\n";
929 appctx->st0 = STAT_CLI_PRINT;
930 return NULL;
931 }
932 return px;
933}
934
935/* Expects to find a backend and a server in <arg> under the form <backend>/<server>,
936 * and returns the pointer to the server. Otherwise, display adequate error messages
937 * and returns NULL. This function also expects the stream level to be admin. Note:
938 * the <arg> is modified to remove the '/'.
939 */
940static struct server *expect_server_admin(struct stream *s, struct stream_interface *si, char *arg)
941{
942 struct appctx *appctx = __objt_appctx(si->end);
943 struct proxy *px;
944 struct server *sv;
945 char *line;
946
947 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
948 appctx->ctx.cli.msg = stats_permission_denied_msg;
949 appctx->st0 = STAT_CLI_PRINT;
950 return NULL;
951 }
952
953 /* split "backend/server" and make <line> point to server */
954 for (line = arg; *line; line++)
955 if (*line == '/') {
956 *line++ = '\0';
957 break;
958 }
959
960 if (!*line || !*arg) {
961 appctx->ctx.cli.msg = "Require 'backend/server'.\n";
962 appctx->st0 = STAT_CLI_PRINT;
963 return NULL;
964 }
965
966 if (!get_backend_server(arg, line, &px, &sv)) {
967 appctx->ctx.cli.msg = px ? "No such server.\n" : "No such backend.\n";
968 appctx->st0 = STAT_CLI_PRINT;
969 return NULL;
970 }
971
972 if (px->state == PR_STSTOPPED) {
973 appctx->ctx.cli.msg = "Proxy is disabled.\n";
974 appctx->st0 = STAT_CLI_PRINT;
975 return NULL;
976 }
977
978 return sv;
979}
980
William Lallemand74c24fb2016-11-21 17:18:36 +0100981/* Processes the stats interpreter on the statistics socket. This function is
982 * called from an applet running in a stream interface. The function returns 1
983 * if the request was understood, otherwise zero. It sets appctx->st0 to a value
984 * designating the function which will have to process the request, which can
985 * also be the print function to display the return message set into cli.msg.
986 */
987static int stats_sock_parse_request(struct stream_interface *si, char *line)
988{
989 struct stream *s = si_strm(si);
990 struct appctx *appctx = __objt_appctx(si->end);
991 char *args[MAX_STATS_ARGS + 1];
992 struct cli_kw *kw;
993 int arg;
994 int i, j;
995
996 while (isspace((unsigned char)*line))
997 line++;
998
999 arg = 0;
1000 args[arg] = line;
1001
1002 while (*line && arg < MAX_STATS_ARGS) {
1003 if (*line == '\\') {
1004 line++;
1005 if (*line == '\0')
1006 break;
1007 }
1008 else if (isspace((unsigned char)*line)) {
1009 *line++ = '\0';
1010
1011 while (isspace((unsigned char)*line))
1012 line++;
1013
1014 args[++arg] = line;
1015 continue;
1016 }
1017
1018 line++;
1019 }
1020
1021 while (++arg <= MAX_STATS_ARGS)
1022 args[arg] = line;
1023
1024 /* remove \ */
1025 arg = 0;
1026 while (*args[arg] != '\0') {
1027 j = 0;
1028 for (i=0; args[arg][i] != '\0'; i++) {
1029 if (args[arg][i] == '\\')
1030 continue;
1031 args[arg][j] = args[arg][i];
1032 j++;
1033 }
1034 args[arg][j] = '\0';
1035 arg++;
1036 }
1037
1038 appctx->ctx.stats.scope_str = 0;
1039 appctx->ctx.stats.scope_len = 0;
1040 appctx->ctx.stats.flags = 0;
1041 if ((kw = cli_find_kw(args))) {
1042 if (kw->parse) {
1043 if (kw->parse(args, appctx, kw->private) == 0 && kw->io_handler) {
1044 appctx->st0 = STAT_CLI_O_CUSTOM;
1045 appctx->io_handler = kw->io_handler;
1046 appctx->io_release = kw->io_release;
1047 }
1048 }
1049 } else if (strcmp(args[0], "show") == 0) {
1050 if (strcmp(args[1], "backend") == 0) {
1051 appctx->ctx.be.px = NULL;
1052 appctx->st2 = STAT_ST_INIT;
1053 appctx->st0 = STAT_CLI_O_BACKEND;
1054 }
1055 else if (strcmp(args[1], "env") == 0) {
1056 extern char **environ;
1057
1058 if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) {
1059 appctx->ctx.cli.msg = stats_permission_denied_msg;
1060 appctx->st0 = STAT_CLI_PRINT;
1061 return 1;
1062 }
1063 appctx->ctx.env.var = environ;
1064 appctx->st2 = STAT_ST_INIT;
1065 appctx->st0 = STAT_CLI_O_ENV; // stats_dump_env_to_buffer
1066
1067 if (*args[2]) {
1068 int len = strlen(args[2]);
1069
1070 for (; *appctx->ctx.env.var; appctx->ctx.env.var++) {
1071 if (strncmp(*appctx->ctx.env.var, args[2], len) == 0 &&
1072 (*appctx->ctx.env.var)[len] == '=')
1073 break;
1074 }
1075 if (!*appctx->ctx.env.var) {
1076 appctx->ctx.cli.msg = "Variable not found\n";
1077 appctx->st0 = STAT_CLI_PRINT;
1078 return 1;
1079 }
1080 appctx->st2 = STAT_ST_END;
1081 }
1082 }
1083 else if (strcmp(args[1], "stat") == 0) {
1084 if (strcmp(args[2], "resolvers") == 0) {
1085 struct dns_resolvers *presolvers;
1086
1087 if (*args[3]) {
1088 appctx->ctx.resolvers.ptr = NULL;
1089 list_for_each_entry(presolvers, &dns_resolvers, list) {
1090 if (strcmp(presolvers->id, args[3]) == 0) {
1091 appctx->ctx.resolvers.ptr = presolvers;
1092 break;
1093 }
1094 }
1095 if (appctx->ctx.resolvers.ptr == NULL) {
1096 appctx->ctx.cli.msg = "Can't find that resolvers section\n";
1097 appctx->st0 = STAT_CLI_PRINT;
1098 return 1;
1099 }
1100 }
1101
1102 appctx->st2 = STAT_ST_INIT;
1103 appctx->st0 = STAT_CLI_O_RESOLVERS;
1104 return 1;
1105 }
1106 else if (*args[2] && *args[3] && *args[4]) {
1107 appctx->ctx.stats.flags |= STAT_BOUND;
1108 appctx->ctx.stats.iid = atoi(args[2]);
1109 appctx->ctx.stats.type = atoi(args[3]);
1110 appctx->ctx.stats.sid = atoi(args[4]);
1111 if (strcmp(args[5], "typed") == 0)
1112 appctx->ctx.stats.flags |= STAT_FMT_TYPED;
1113 }
1114 else if (strcmp(args[2], "typed") == 0)
1115 appctx->ctx.stats.flags |= STAT_FMT_TYPED;
1116
1117 appctx->st2 = STAT_ST_INIT;
1118 appctx->st0 = STAT_CLI_O_STAT; // stats_dump_stat_to_buffer
1119 }
1120 else if (strcmp(args[1], "info") == 0) {
1121 if (strcmp(args[2], "typed") == 0)
1122 appctx->ctx.stats.flags |= STAT_FMT_TYPED;
1123 appctx->st2 = STAT_ST_INIT;
1124 appctx->st0 = STAT_CLI_O_INFO; // stats_dump_info_to_buffer
1125 }
1126 else if (strcmp(args[1], "servers") == 0 && strcmp(args[2], "state") == 0) {
1127 appctx->ctx.server_state.iid = 0;
1128 appctx->ctx.server_state.px = NULL;
1129 appctx->ctx.server_state.sv = NULL;
1130
1131 /* check if a backend name has been provided */
1132 if (*args[3]) {
1133 /* read server state from local file */
1134 appctx->ctx.server_state.px = proxy_be_by_name(args[3]);
1135
1136 if (!appctx->ctx.server_state.px) {
1137 appctx->ctx.cli.msg = "Can't find backend.\n";
1138 appctx->st0 = STAT_CLI_PRINT;
1139 return 1;
1140 }
1141 appctx->ctx.server_state.iid = appctx->ctx.server_state.px->uuid;
1142 }
1143 appctx->st2 = STAT_ST_INIT;
1144 appctx->st0 = STAT_CLI_O_SERVERS_STATE; // stats_dump_servers_state_to_buffer
1145 return 1;
1146 }
1147 else if (strcmp(args[1], "pools") == 0) {
1148 appctx->st2 = STAT_ST_INIT;
1149 appctx->st0 = STAT_CLI_O_POOLS; // stats_dump_pools_to_buffer
1150 }
1151 else if (strcmp(args[1], "sess") == 0) {
1152 appctx->st2 = STAT_ST_INIT;
1153 if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) {
1154 appctx->ctx.cli.msg = stats_permission_denied_msg;
1155 appctx->st0 = STAT_CLI_PRINT;
1156 return 1;
1157 }
1158 if (*args[2] && strcmp(args[2], "all") == 0)
1159 appctx->ctx.sess.target = (void *)-1;
1160 else if (*args[2])
1161 appctx->ctx.sess.target = (void *)strtoul(args[2], NULL, 0);
1162 else
1163 appctx->ctx.sess.target = NULL;
1164 appctx->ctx.sess.section = 0; /* start with stream status */
1165 appctx->ctx.sess.pos = 0;
1166 appctx->st0 = STAT_CLI_O_SESS; // stats_dump_sess_to_buffer
1167 }
1168 else if (strcmp(args[1], "errors") == 0) {
1169 if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) {
1170 appctx->ctx.cli.msg = stats_permission_denied_msg;
1171 appctx->st0 = STAT_CLI_PRINT;
1172 return 1;
1173 }
1174 if (*args[2])
1175 appctx->ctx.errors.iid = atoi(args[2]);
1176 else
1177 appctx->ctx.errors.iid = -1;
1178 appctx->ctx.errors.px = NULL;
1179 appctx->st2 = STAT_ST_INIT;
1180 appctx->st0 = STAT_CLI_O_ERR; // stats_dump_errors_to_buffer
1181 }
1182 else if (strcmp(args[1], "table") == 0) {
1183 stats_sock_table_request(si, args, STAT_CLI_O_TAB);
1184 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001185 else { /* neither "stat" nor "info" nor "sess" nor "errors" nor "table" */
1186 return 0;
1187 }
1188 }
1189 else if (strcmp(args[0], "clear") == 0) {
1190 if (strcmp(args[1], "counters") == 0) {
1191 struct proxy *px;
1192 struct server *sv;
1193 struct listener *li;
1194 int clrall = 0;
1195
1196 if (strcmp(args[2], "all") == 0)
1197 clrall = 1;
1198
1199 /* check permissions */
1200 if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER ||
1201 (clrall && strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN)) {
1202 appctx->ctx.cli.msg = stats_permission_denied_msg;
1203 appctx->st0 = STAT_CLI_PRINT;
1204 return 1;
1205 }
1206
1207 for (px = proxy; px; px = px->next) {
1208 if (clrall) {
1209 memset(&px->be_counters, 0, sizeof(px->be_counters));
1210 memset(&px->fe_counters, 0, sizeof(px->fe_counters));
1211 }
1212 else {
1213 px->be_counters.conn_max = 0;
1214 px->be_counters.p.http.rps_max = 0;
1215 px->be_counters.sps_max = 0;
1216 px->be_counters.cps_max = 0;
1217 px->be_counters.nbpend_max = 0;
1218
1219 px->fe_counters.conn_max = 0;
1220 px->fe_counters.p.http.rps_max = 0;
1221 px->fe_counters.sps_max = 0;
1222 px->fe_counters.cps_max = 0;
1223 px->fe_counters.nbpend_max = 0;
1224 }
1225
1226 for (sv = px->srv; sv; sv = sv->next)
1227 if (clrall)
1228 memset(&sv->counters, 0, sizeof(sv->counters));
1229 else {
1230 sv->counters.cur_sess_max = 0;
1231 sv->counters.nbpend_max = 0;
1232 sv->counters.sps_max = 0;
1233 }
1234
1235 list_for_each_entry(li, &px->conf.listeners, by_fe)
1236 if (li->counters) {
1237 if (clrall)
1238 memset(li->counters, 0, sizeof(*li->counters));
1239 else
1240 li->counters->conn_max = 0;
1241 }
1242 }
1243
1244 global.cps_max = 0;
1245 global.sps_max = 0;
1246 return 1;
1247 }
1248 else if (strcmp(args[1], "table") == 0) {
1249 stats_sock_table_request(si, args, STAT_CLI_O_CLR);
1250 /* end of processing */
1251 return 1;
1252 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001253 else {
1254 /* unknown "clear" argument */
1255 return 0;
1256 }
1257 }
1258 else if (strcmp(args[0], "get") == 0) {
1259 if (strcmp(args[1], "weight") == 0) {
1260 struct proxy *px;
1261 struct server *sv;
1262
1263 /* split "backend/server" and make <line> point to server */
1264 for (line = args[2]; *line; line++)
1265 if (*line == '/') {
1266 *line++ = '\0';
1267 break;
1268 }
1269
1270 if (!*line) {
1271 appctx->ctx.cli.msg = "Require 'backend/server'.\n";
1272 appctx->st0 = STAT_CLI_PRINT;
1273 return 1;
1274 }
1275
1276 if (!get_backend_server(args[2], line, &px, &sv)) {
1277 appctx->ctx.cli.msg = px ? "No such server.\n" : "No such backend.\n";
1278 appctx->st0 = STAT_CLI_PRINT;
1279 return 1;
1280 }
1281
1282 /* return server's effective weight at the moment */
1283 snprintf(trash.str, trash.size, "%d (initial %d)\n", sv->uweight, sv->iweight);
1284 if (bi_putstr(si_ic(si), trash.str) == -1)
1285 si_applet_cant_put(si);
1286
1287 return 1;
1288 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001289 else { /* not "get weight" */
1290 return 0;
1291 }
1292 }
1293 else if (strcmp(args[0], "set") == 0) {
1294 if (strcmp(args[1], "weight") == 0) {
1295 struct server *sv;
1296 const char *warning;
1297
1298 sv = expect_server_admin(s, si, args[2]);
1299 if (!sv)
1300 return 1;
1301
1302 warning = server_parse_weight_change_request(sv, args[3]);
1303 if (warning) {
1304 appctx->ctx.cli.msg = warning;
1305 appctx->st0 = STAT_CLI_PRINT;
1306 }
1307 return 1;
1308 }
1309 else if (strcmp(args[1], "server") == 0) {
1310 struct server *sv;
1311 const char *warning;
1312
1313 sv = expect_server_admin(s, si, args[2]);
1314 if (!sv)
1315 return 1;
1316
1317 if (strcmp(args[3], "weight") == 0) {
1318 warning = server_parse_weight_change_request(sv, args[4]);
1319 if (warning) {
1320 appctx->ctx.cli.msg = warning;
1321 appctx->st0 = STAT_CLI_PRINT;
1322 }
1323 }
1324 else if (strcmp(args[3], "state") == 0) {
1325 if (strcmp(args[4], "ready") == 0)
1326 srv_adm_set_ready(sv);
1327 else if (strcmp(args[4], "drain") == 0)
1328 srv_adm_set_drain(sv);
1329 else if (strcmp(args[4], "maint") == 0)
1330 srv_adm_set_maint(sv);
1331 else {
1332 appctx->ctx.cli.msg = "'set server <srv> state' expects 'ready', 'drain' and 'maint'.\n";
1333 appctx->st0 = STAT_CLI_PRINT;
1334 }
1335 }
1336 else if (strcmp(args[3], "health") == 0) {
1337 if (sv->track) {
1338 appctx->ctx.cli.msg = "cannot change health on a tracking server.\n";
1339 appctx->st0 = STAT_CLI_PRINT;
1340 }
1341 else if (strcmp(args[4], "up") == 0) {
1342 sv->check.health = sv->check.rise + sv->check.fall - 1;
1343 srv_set_running(sv, "changed from CLI");
1344 }
1345 else if (strcmp(args[4], "stopping") == 0) {
1346 sv->check.health = sv->check.rise + sv->check.fall - 1;
1347 srv_set_stopping(sv, "changed from CLI");
1348 }
1349 else if (strcmp(args[4], "down") == 0) {
1350 sv->check.health = 0;
1351 srv_set_stopped(sv, "changed from CLI");
1352 }
1353 else {
1354 appctx->ctx.cli.msg = "'set server <srv> health' expects 'up', 'stopping', or 'down'.\n";
1355 appctx->st0 = STAT_CLI_PRINT;
1356 }
1357 }
1358 else if (strcmp(args[3], "agent") == 0) {
1359 if (!(sv->agent.state & CHK_ST_ENABLED)) {
1360 appctx->ctx.cli.msg = "agent checks are not enabled on this server.\n";
1361 appctx->st0 = STAT_CLI_PRINT;
1362 }
1363 else if (strcmp(args[4], "up") == 0) {
1364 sv->agent.health = sv->agent.rise + sv->agent.fall - 1;
1365 srv_set_running(sv, "changed from CLI");
1366 }
1367 else if (strcmp(args[4], "down") == 0) {
1368 sv->agent.health = 0;
1369 srv_set_stopped(sv, "changed from CLI");
1370 }
1371 else {
1372 appctx->ctx.cli.msg = "'set server <srv> agent' expects 'up' or 'down'.\n";
1373 appctx->st0 = STAT_CLI_PRINT;
1374 }
1375 }
1376 else if (strcmp(args[3], "check-port") == 0) {
1377 int i = 0;
1378 if (strl2irc(args[4], strlen(args[4]), &i) != 0) {
1379 appctx->ctx.cli.msg = "'set server <srv> check-port' expects an integer as argument.\n";
1380 appctx->st0 = STAT_CLI_PRINT;
1381 }
1382 if ((i < 0) || (i > 65535)) {
1383 appctx->ctx.cli.msg = "provided port is not valid.\n";
1384 appctx->st0 = STAT_CLI_PRINT;
1385 }
1386 /* prevent the update of port to 0 if MAPPORTS are in use */
1387 if ((sv->flags & SRV_F_MAPPORTS) && (i == 0)) {
1388 appctx->ctx.cli.msg = "can't unset 'port' since MAPPORTS is in use.\n";
1389 appctx->st0 = STAT_CLI_PRINT;
1390 return 1;
1391 }
1392 sv->check.port = i;
1393 appctx->ctx.cli.msg = "health check port updated.\n";
1394 appctx->st0 = STAT_CLI_PRINT;
1395 }
1396 else if (strcmp(args[3], "addr") == 0) {
1397 char *addr = NULL;
1398 char *port = NULL;
1399 if (strlen(args[4]) == 0) {
1400 appctx->ctx.cli.msg = "set server <b>/<s> addr requires an address and optionally a port.\n";
1401 appctx->st0 = STAT_CLI_PRINT;
1402 return 1;
1403 }
1404 else {
1405 addr = args[4];
1406 }
1407 if (strcmp(args[5], "port") == 0) {
1408 port = args[6];
1409 }
1410 warning = update_server_addr_port(sv, addr, port, "stats socket command");
1411 if (warning) {
1412 appctx->ctx.cli.msg = warning;
1413 appctx->st0 = STAT_CLI_PRINT;
1414 }
1415 srv_clr_admin_flag(sv, SRV_ADMF_RMAINT);
1416 }
1417 else {
1418 appctx->ctx.cli.msg = "'set server <srv>' only supports 'agent', 'health', 'state', 'weight', 'addr' and 'check-port'.\n";
1419 appctx->st0 = STAT_CLI_PRINT;
1420 }
1421 return 1;
1422 }
1423 else if (strcmp(args[1], "timeout") == 0) {
1424 if (strcmp(args[2], "cli") == 0) {
1425 unsigned timeout;
1426 const char *res;
1427
1428 if (!*args[3]) {
1429 appctx->ctx.cli.msg = "Expects an integer value.\n";
1430 appctx->st0 = STAT_CLI_PRINT;
1431 return 1;
1432 }
1433
1434 res = parse_time_err(args[3], &timeout, TIME_UNIT_S);
1435 if (res || timeout < 1) {
1436 appctx->ctx.cli.msg = "Invalid timeout value.\n";
1437 appctx->st0 = STAT_CLI_PRINT;
1438 return 1;
1439 }
1440
1441 s->req.rto = s->res.wto = 1 + MS_TO_TICKS(timeout*1000);
1442 task_wakeup(s->task, TASK_WOKEN_MSG); // recompute timeouts
1443 return 1;
1444 }
1445 else {
1446 appctx->ctx.cli.msg = "'set timeout' only supports 'cli'.\n";
1447 appctx->st0 = STAT_CLI_PRINT;
1448 return 1;
1449 }
1450 }
1451 else if (strcmp(args[1], "maxconn") == 0) {
1452 if (strcmp(args[2], "frontend") == 0) {
1453 struct proxy *px;
1454 struct listener *l;
1455 int v;
1456
1457 px = expect_frontend_admin(s, si, args[3]);
1458 if (!px)
1459 return 1;
1460
1461 if (!*args[4]) {
1462 appctx->ctx.cli.msg = "Integer value expected.\n";
1463 appctx->st0 = STAT_CLI_PRINT;
1464 return 1;
1465 }
1466
1467 v = atoi(args[4]);
1468 if (v < 0) {
1469 appctx->ctx.cli.msg = "Value out of range.\n";
1470 appctx->st0 = STAT_CLI_PRINT;
1471 return 1;
1472 }
1473
1474 /* OK, the value is fine, so we assign it to the proxy and to all of
1475 * its listeners. The blocked ones will be dequeued.
1476 */
1477 px->maxconn = v;
1478 list_for_each_entry(l, &px->conf.listeners, by_fe) {
1479 l->maxconn = v;
1480 if (l->state == LI_FULL)
1481 resume_listener(l);
1482 }
1483
1484 if (px->maxconn > px->feconn && !LIST_ISEMPTY(&px->listener_queue))
1485 dequeue_all_listeners(&px->listener_queue);
1486
1487 return 1;
1488 }
1489 else if (strcmp(args[2], "server") == 0) {
1490 struct server *sv;
1491 const char *warning;
1492
1493 sv = expect_server_admin(s, si, args[3]);
1494 if (!sv)
1495 return 1;
1496
1497 warning = server_parse_maxconn_change_request(sv, args[4]);
1498 if (warning) {
1499 appctx->ctx.cli.msg = warning;
1500 appctx->st0 = STAT_CLI_PRINT;
1501 }
1502
1503 return 1;
1504 }
1505 else if (strcmp(args[2], "global") == 0) {
1506 int v;
1507
1508 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
1509 appctx->ctx.cli.msg = stats_permission_denied_msg;
1510 appctx->st0 = STAT_CLI_PRINT;
1511 return 1;
1512 }
1513
1514 if (!*args[3]) {
1515 appctx->ctx.cli.msg = "Expects an integer value.\n";
1516 appctx->st0 = STAT_CLI_PRINT;
1517 return 1;
1518 }
1519
1520 v = atoi(args[3]);
1521 if (v > global.hardmaxconn) {
1522 appctx->ctx.cli.msg = "Value out of range.\n";
1523 appctx->st0 = STAT_CLI_PRINT;
1524 return 1;
1525 }
1526
1527 /* check for unlimited values */
1528 if (v <= 0)
1529 v = global.hardmaxconn;
1530
1531 global.maxconn = v;
1532
1533 /* Dequeues all of the listeners waiting for a resource */
1534 if (!LIST_ISEMPTY(&global_listener_queue))
1535 dequeue_all_listeners(&global_listener_queue);
1536
1537 return 1;
1538 }
1539 else {
1540 appctx->ctx.cli.msg = "'set maxconn' only supports 'frontend', 'server', and 'global'.\n";
1541 appctx->st0 = STAT_CLI_PRINT;
1542 return 1;
1543 }
1544 }
1545 else if (strcmp(args[1], "rate-limit") == 0) {
1546 if (strcmp(args[2], "connections") == 0) {
1547 if (strcmp(args[3], "global") == 0) {
1548 int v;
1549
1550 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
1551 appctx->ctx.cli.msg = stats_permission_denied_msg;
1552 appctx->st0 = STAT_CLI_PRINT;
1553 return 1;
1554 }
1555
1556 if (!*args[4]) {
1557 appctx->ctx.cli.msg = "Expects an integer value.\n";
1558 appctx->st0 = STAT_CLI_PRINT;
1559 return 1;
1560 }
1561
1562 v = atoi(args[4]);
1563 if (v < 0) {
1564 appctx->ctx.cli.msg = "Value out of range.\n";
1565 appctx->st0 = STAT_CLI_PRINT;
1566 return 1;
1567 }
1568
1569 global.cps_lim = v;
1570
1571 /* Dequeues all of the listeners waiting for a resource */
1572 if (!LIST_ISEMPTY(&global_listener_queue))
1573 dequeue_all_listeners(&global_listener_queue);
1574
1575 return 1;
1576 }
1577 else {
1578 appctx->ctx.cli.msg = "'set rate-limit connections' only supports 'global'.\n";
1579 appctx->st0 = STAT_CLI_PRINT;
1580 return 1;
1581 }
1582 }
1583 else if (strcmp(args[2], "sessions") == 0) {
1584 if (strcmp(args[3], "global") == 0) {
1585 int v;
1586
1587 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
1588 appctx->ctx.cli.msg = stats_permission_denied_msg;
1589 appctx->st0 = STAT_CLI_PRINT;
1590 return 1;
1591 }
1592
1593 if (!*args[4]) {
1594 appctx->ctx.cli.msg = "Expects an integer value.\n";
1595 appctx->st0 = STAT_CLI_PRINT;
1596 return 1;
1597 }
1598
1599 v = atoi(args[4]);
1600 if (v < 0) {
1601 appctx->ctx.cli.msg = "Value out of range.\n";
1602 appctx->st0 = STAT_CLI_PRINT;
1603 return 1;
1604 }
1605
1606 global.sps_lim = v;
1607
1608 /* Dequeues all of the listeners waiting for a resource */
1609 if (!LIST_ISEMPTY(&global_listener_queue))
1610 dequeue_all_listeners(&global_listener_queue);
1611
1612 return 1;
1613 }
1614 else {
1615 appctx->ctx.cli.msg = "'set rate-limit sessions' only supports 'global'.\n";
1616 appctx->st0 = STAT_CLI_PRINT;
1617 return 1;
1618 }
1619 }
1620#ifdef USE_OPENSSL
1621 else if (strcmp(args[2], "ssl-sessions") == 0) {
1622 if (strcmp(args[3], "global") == 0) {
1623 int v;
1624
1625 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
1626 appctx->ctx.cli.msg = stats_permission_denied_msg;
1627 appctx->st0 = STAT_CLI_PRINT;
1628 return 1;
1629 }
1630
1631 if (!*args[4]) {
1632 appctx->ctx.cli.msg = "Expects an integer value.\n";
1633 appctx->st0 = STAT_CLI_PRINT;
1634 return 1;
1635 }
1636
1637 v = atoi(args[4]);
1638 if (v < 0) {
1639 appctx->ctx.cli.msg = "Value out of range.\n";
1640 appctx->st0 = STAT_CLI_PRINT;
1641 return 1;
1642 }
1643
1644 global.ssl_lim = v;
1645
1646 /* Dequeues all of the listeners waiting for a resource */
1647 if (!LIST_ISEMPTY(&global_listener_queue))
1648 dequeue_all_listeners(&global_listener_queue);
1649
1650 return 1;
1651 }
1652 else {
1653 appctx->ctx.cli.msg = "'set rate-limit ssl-sessions' only supports 'global'.\n";
1654 appctx->st0 = STAT_CLI_PRINT;
1655 return 1;
1656 }
1657 }
1658#endif
1659 else if (strcmp(args[2], "http-compression") == 0) {
1660 if (strcmp(args[3], "global") == 0) {
1661 int v;
1662
1663 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
1664 appctx->ctx.cli.msg = stats_permission_denied_msg;
1665 appctx->st0 = STAT_CLI_PRINT;
1666 return 1;
1667 }
1668
1669 if (!*args[4]) {
1670 appctx->ctx.cli.msg = "Expects a maximum input byte rate in kB/s.\n";
1671 appctx->st0 = STAT_CLI_PRINT;
1672 return 1;
1673 }
1674
1675 v = atoi(args[4]);
1676 global.comp_rate_lim = v * 1024; /* Kilo to bytes. */
1677 }
1678 else {
1679 appctx->ctx.cli.msg = "'set rate-limit http-compression' only supports 'global'.\n";
1680 appctx->st0 = STAT_CLI_PRINT;
1681 return 1;
1682 }
1683 }
1684 else {
1685 appctx->ctx.cli.msg = "'set rate-limit' supports 'connections', 'sessions', 'ssl-sessions', and 'http-compression'.\n";
1686 appctx->st0 = STAT_CLI_PRINT;
1687 return 1;
1688 }
1689 }
1690 else if (strcmp(args[1], "table") == 0) {
1691 stats_sock_table_request(si, args, STAT_CLI_O_SET);
William Lallemand32af2032016-10-29 18:09:35 +02001692 } else { /* unknown "set" parameter */
William Lallemand74c24fb2016-11-21 17:18:36 +01001693 return 0;
1694 }
1695 }
1696 else if (strcmp(args[0], "enable") == 0) {
1697 if (strcmp(args[1], "agent") == 0) {
1698 struct server *sv;
1699
1700 sv = expect_server_admin(s, si, args[2]);
1701 if (!sv)
1702 return 1;
1703
1704 if (!(sv->agent.state & CHK_ST_CONFIGURED)) {
1705 appctx->ctx.cli.msg = "Agent was not configured on this server, cannot enable.\n";
1706 appctx->st0 = STAT_CLI_PRINT;
1707 return 1;
1708 }
1709
1710 sv->agent.state |= CHK_ST_ENABLED;
1711 return 1;
1712 }
1713 else if (strcmp(args[1], "health") == 0) {
1714 struct server *sv;
1715
1716 sv = expect_server_admin(s, si, args[2]);
1717 if (!sv)
1718 return 1;
1719
1720 if (!(sv->check.state & CHK_ST_CONFIGURED)) {
1721 appctx->ctx.cli.msg = "Health checks are not configured on this server, cannot enable.\n";
1722 appctx->st0 = STAT_CLI_PRINT;
1723 return 1;
1724 }
1725
1726 sv->check.state |= CHK_ST_ENABLED;
1727 return 1;
1728 }
1729 else if (strcmp(args[1], "server") == 0) {
1730 struct server *sv;
1731
1732 sv = expect_server_admin(s, si, args[2]);
1733 if (!sv)
1734 return 1;
1735
1736 srv_adm_set_ready(sv);
1737 return 1;
1738 }
1739 else if (strcmp(args[1], "frontend") == 0) {
1740 struct proxy *px;
1741
1742 px = expect_frontend_admin(s, si, args[2]);
1743 if (!px)
1744 return 1;
1745
1746 if (px->state == PR_STSTOPPED) {
1747 appctx->ctx.cli.msg = "Frontend was previously shut down, cannot enable.\n";
1748 appctx->st0 = STAT_CLI_PRINT;
1749 return 1;
1750 }
1751
1752 if (px->state != PR_STPAUSED) {
1753 appctx->ctx.cli.msg = "Frontend is already enabled.\n";
1754 appctx->st0 = STAT_CLI_PRINT;
1755 return 1;
1756 }
1757
1758 if (!resume_proxy(px)) {
1759 appctx->ctx.cli.msg = "Failed to resume frontend, check logs for precise cause (port conflict?).\n";
1760 appctx->st0 = STAT_CLI_PRINT;
1761 return 1;
1762 }
1763 return 1;
1764 }
1765 else { /* unknown "enable" parameter */
1766 appctx->ctx.cli.msg = "'enable' only supports 'agent', 'frontend', 'health', and 'server'.\n";
1767 appctx->st0 = STAT_CLI_PRINT;
1768 return 1;
1769 }
1770 }
1771 else if (strcmp(args[0], "disable") == 0) {
1772 if (strcmp(args[1], "agent") == 0) {
1773 struct server *sv;
1774
1775 sv = expect_server_admin(s, si, args[2]);
1776 if (!sv)
1777 return 1;
1778
1779 sv->agent.state &= ~CHK_ST_ENABLED;
1780 return 1;
1781 }
1782 else if (strcmp(args[1], "health") == 0) {
1783 struct server *sv;
1784
1785 sv = expect_server_admin(s, si, args[2]);
1786 if (!sv)
1787 return 1;
1788
1789 sv->check.state &= ~CHK_ST_ENABLED;
1790 return 1;
1791 }
1792 else if (strcmp(args[1], "server") == 0) {
1793 struct server *sv;
1794
1795 sv = expect_server_admin(s, si, args[2]);
1796 if (!sv)
1797 return 1;
1798
1799 srv_adm_set_maint(sv);
1800 return 1;
1801 }
1802 else if (strcmp(args[1], "frontend") == 0) {
1803 struct proxy *px;
1804
1805 px = expect_frontend_admin(s, si, args[2]);
1806 if (!px)
1807 return 1;
1808
1809 if (px->state == PR_STSTOPPED) {
1810 appctx->ctx.cli.msg = "Frontend was previously shut down, cannot disable.\n";
1811 appctx->st0 = STAT_CLI_PRINT;
1812 return 1;
1813 }
1814
1815 if (px->state == PR_STPAUSED) {
1816 appctx->ctx.cli.msg = "Frontend is already disabled.\n";
1817 appctx->st0 = STAT_CLI_PRINT;
1818 return 1;
1819 }
1820
1821 if (!pause_proxy(px)) {
1822 appctx->ctx.cli.msg = "Failed to pause frontend, check logs for precise cause.\n";
1823 appctx->st0 = STAT_CLI_PRINT;
1824 return 1;
1825 }
1826 return 1;
1827 }
1828 else { /* unknown "disable" parameter */
1829 appctx->ctx.cli.msg = "'disable' only supports 'agent', 'frontend', 'health', and 'server'.\n";
1830 appctx->st0 = STAT_CLI_PRINT;
1831 return 1;
1832 }
1833 }
1834 else if (strcmp(args[0], "shutdown") == 0) {
1835 if (strcmp(args[1], "frontend") == 0) {
1836 struct proxy *px;
1837
1838 px = expect_frontend_admin(s, si, args[2]);
1839 if (!px)
1840 return 1;
1841
1842 if (px->state == PR_STSTOPPED) {
1843 appctx->ctx.cli.msg = "Frontend was already shut down.\n";
1844 appctx->st0 = STAT_CLI_PRINT;
1845 return 1;
1846 }
1847
1848 Warning("Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n",
1849 px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn);
1850 send_log(px, LOG_WARNING, "Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n",
1851 px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn);
1852 stop_proxy(px);
1853 return 1;
1854 }
1855 else if (strcmp(args[1], "session") == 0) {
1856 struct stream *sess, *ptr;
1857
1858 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
1859 appctx->ctx.cli.msg = stats_permission_denied_msg;
1860 appctx->st0 = STAT_CLI_PRINT;
1861 return 1;
1862 }
1863
1864 if (!*args[2]) {
1865 appctx->ctx.cli.msg = "Session pointer expected (use 'show sess').\n";
1866 appctx->st0 = STAT_CLI_PRINT;
1867 return 1;
1868 }
1869
1870 ptr = (void *)strtoul(args[2], NULL, 0);
1871
1872 /* first, look for the requested stream in the stream table */
1873 list_for_each_entry(sess, &streams, list) {
1874 if (sess == ptr)
1875 break;
1876 }
1877
1878 /* do we have the stream ? */
1879 if (sess != ptr) {
1880 appctx->ctx.cli.msg = "No such session (use 'show sess').\n";
1881 appctx->st0 = STAT_CLI_PRINT;
1882 return 1;
1883 }
1884
1885 stream_shutdown(sess, SF_ERR_KILLED);
1886 return 1;
1887 }
1888 else if (strcmp(args[1], "sessions") == 0) {
1889 if (strcmp(args[2], "server") == 0) {
1890 struct server *sv;
1891 struct stream *sess, *sess_bck;
1892
1893 sv = expect_server_admin(s, si, args[3]);
1894 if (!sv)
1895 return 1;
1896
1897 /* kill all the stream that are on this server */
1898 list_for_each_entry_safe(sess, sess_bck, &sv->actconns, by_srv)
1899 if (sess->srv_conn == sv)
1900 stream_shutdown(sess, SF_ERR_KILLED);
1901
1902 return 1;
1903 }
1904 else {
1905 appctx->ctx.cli.msg = "'shutdown sessions' only supports 'server'.\n";
1906 appctx->st0 = STAT_CLI_PRINT;
1907 return 1;
1908 }
1909 }
1910 else { /* unknown "disable" parameter */
1911 appctx->ctx.cli.msg = "'shutdown' only supports 'frontend', 'session' and 'sessions'.\n";
1912 appctx->st0 = STAT_CLI_PRINT;
1913 return 1;
1914 }
1915 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001916 else { /* not "show" nor "clear" nor "get" nor "set" nor "enable" nor "disable" */
1917 return 0;
1918 }
1919 return 1;
1920}
1921
1922/* This I/O handler runs as an applet embedded in a stream interface. It is
1923 * used to processes I/O from/to the stats unix socket. The system relies on a
1924 * state machine handling requests and various responses. We read a request,
1925 * then we process it and send the response, and we possibly display a prompt.
1926 * Then we can read again. The state is stored in appctx->st0 and is one of the
1927 * STAT_CLI_* constants. appctx->st1 is used to indicate whether prompt is enabled
1928 * or not.
1929 */
1930static void cli_io_handler(struct appctx *appctx)
1931{
1932 struct stream_interface *si = appctx->owner;
1933 struct channel *req = si_oc(si);
1934 struct channel *res = si_ic(si);
1935 int reql;
1936 int len;
1937
1938 if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
1939 goto out;
1940
1941 while (1) {
1942 if (appctx->st0 == STAT_CLI_INIT) {
1943 /* Stats output not initialized yet */
1944 memset(&appctx->ctx.stats, 0, sizeof(appctx->ctx.stats));
1945 appctx->st0 = STAT_CLI_GETREQ;
1946 }
1947 else if (appctx->st0 == STAT_CLI_END) {
1948 /* Let's close for real now. We just close the request
1949 * side, the conditions below will complete if needed.
1950 */
1951 si_shutw(si);
1952 break;
1953 }
1954 else if (appctx->st0 == STAT_CLI_GETREQ) {
1955 /* ensure we have some output room left in the event we
1956 * would want to return some info right after parsing.
1957 */
1958 if (buffer_almost_full(si_ib(si))) {
1959 si_applet_cant_put(si);
1960 break;
1961 }
1962
1963 reql = bo_getline(si_oc(si), trash.str, trash.size);
1964 if (reql <= 0) { /* closed or EOL not found */
1965 if (reql == 0)
1966 break;
1967 appctx->st0 = STAT_CLI_END;
1968 continue;
1969 }
1970
1971 /* seek for a possible unescaped semi-colon. If we find
1972 * one, we replace it with an LF and skip only this part.
1973 */
1974 for (len = 0; len < reql; len++) {
1975 if (trash.str[len] == '\\') {
1976 len++;
1977 continue;
1978 }
1979 if (trash.str[len] == ';') {
1980 trash.str[len] = '\n';
1981 reql = len + 1;
1982 break;
1983 }
1984 }
1985
1986 /* now it is time to check that we have a full line,
1987 * remove the trailing \n and possibly \r, then cut the
1988 * line.
1989 */
1990 len = reql - 1;
1991 if (trash.str[len] != '\n') {
1992 appctx->st0 = STAT_CLI_END;
1993 continue;
1994 }
1995
1996 if (len && trash.str[len-1] == '\r')
1997 len--;
1998
1999 trash.str[len] = '\0';
2000
2001 appctx->st0 = STAT_CLI_PROMPT;
2002 if (len) {
2003 if (strcmp(trash.str, "quit") == 0) {
2004 appctx->st0 = STAT_CLI_END;
2005 continue;
2006 }
2007 else if (strcmp(trash.str, "prompt") == 0)
2008 appctx->st1 = !appctx->st1;
2009 else if (strcmp(trash.str, "help") == 0 ||
2010 !stats_sock_parse_request(si, trash.str)) {
2011 cli_gen_usage_msg();
2012 if (dynamic_usage_msg)
2013 appctx->ctx.cli.msg = dynamic_usage_msg;
2014 else
2015 appctx->ctx.cli.msg = stats_sock_usage_msg;
2016 appctx->st0 = STAT_CLI_PRINT;
2017 }
2018 /* NB: stats_sock_parse_request() may have put
2019 * another STAT_CLI_O_* into appctx->st0.
2020 */
2021 }
2022 else if (!appctx->st1) {
2023 /* if prompt is disabled, print help on empty lines,
2024 * so that the user at least knows how to enable
2025 * prompt and find help.
2026 */
2027 cli_gen_usage_msg();
2028 if (dynamic_usage_msg)
2029 appctx->ctx.cli.msg = dynamic_usage_msg;
2030 else
2031 appctx->ctx.cli.msg = stats_sock_usage_msg;
2032 appctx->st0 = STAT_CLI_PRINT;
2033 }
2034
2035 /* re-adjust req buffer */
2036 bo_skip(si_oc(si), reql);
2037 req->flags |= CF_READ_DONTWAIT; /* we plan to read small requests */
2038 }
2039 else { /* output functions */
2040 switch (appctx->st0) {
2041 case STAT_CLI_PROMPT:
2042 break;
2043 case STAT_CLI_PRINT:
2044 if (bi_putstr(si_ic(si), appctx->ctx.cli.msg) != -1)
2045 appctx->st0 = STAT_CLI_PROMPT;
2046 else
2047 si_applet_cant_put(si);
2048 break;
2049 case STAT_CLI_PRINT_FREE:
2050 if (bi_putstr(si_ic(si), appctx->ctx.cli.err) != -1) {
2051 free(appctx->ctx.cli.err);
2052 appctx->st0 = STAT_CLI_PROMPT;
2053 }
2054 else
2055 si_applet_cant_put(si);
2056 break;
2057 case STAT_CLI_O_BACKEND:
2058 if (stats_dump_backend_to_buffer(si))
2059 appctx->st0 = STAT_CLI_PROMPT;
2060 break;
2061 case STAT_CLI_O_INFO:
2062 if (stats_dump_info_to_buffer(si))
2063 appctx->st0 = STAT_CLI_PROMPT;
2064 break;
2065 case STAT_CLI_O_SERVERS_STATE:
2066 if (stats_dump_servers_state_to_buffer(si))
2067 appctx->st0 = STAT_CLI_PROMPT;
2068 break;
2069 case STAT_CLI_O_STAT:
2070 if (stats_dump_stat_to_buffer(si, NULL))
2071 appctx->st0 = STAT_CLI_PROMPT;
2072 break;
2073 case STAT_CLI_O_RESOLVERS:
2074 if (stats_dump_resolvers_to_buffer(si))
2075 appctx->st0 = STAT_CLI_PROMPT;
2076 break;
2077 case STAT_CLI_O_SESS:
2078 if (stats_dump_sess_to_buffer(si))
2079 appctx->st0 = STAT_CLI_PROMPT;
2080 break;
2081 case STAT_CLI_O_ERR: /* errors dump */
2082 if (stats_dump_errors_to_buffer(si))
2083 appctx->st0 = STAT_CLI_PROMPT;
2084 break;
2085 case STAT_CLI_O_TAB:
2086 case STAT_CLI_O_CLR:
2087 if (stats_table_request(si, appctx->st0))
2088 appctx->st0 = STAT_CLI_PROMPT;
2089 break;
William Lallemand74c24fb2016-11-21 17:18:36 +01002090 case STAT_CLI_O_POOLS:
2091 if (stats_dump_pools_to_buffer(si))
2092 appctx->st0 = STAT_CLI_PROMPT;
2093 break;
William Lallemand74c24fb2016-11-21 17:18:36 +01002094 case STAT_CLI_O_ENV: /* environment dump */
2095 if (stats_dump_env_to_buffer(si))
2096 appctx->st0 = STAT_CLI_PROMPT;
2097 break;
2098 case STAT_CLI_O_CUSTOM: /* use custom pointer */
2099 if (appctx->io_handler)
2100 if (appctx->io_handler(appctx)) {
2101 appctx->st0 = STAT_CLI_PROMPT;
2102 if (appctx->io_release) {
2103 appctx->io_release(appctx);
2104 appctx->io_release = NULL;
2105 }
2106 }
2107 break;
2108 default: /* abnormal state */
2109 si->flags |= SI_FL_ERR;
2110 break;
2111 }
2112
2113 /* The post-command prompt is either LF alone or LF + '> ' in interactive mode */
2114 if (appctx->st0 == STAT_CLI_PROMPT) {
2115 if (bi_putstr(si_ic(si), appctx->st1 ? "\n> " : "\n") != -1)
2116 appctx->st0 = STAT_CLI_GETREQ;
2117 else
2118 si_applet_cant_put(si);
2119 }
2120
2121 /* If the output functions are still there, it means they require more room. */
2122 if (appctx->st0 >= STAT_CLI_OUTPUT)
2123 break;
2124
2125 /* Now we close the output if one of the writers did so,
2126 * or if we're not in interactive mode and the request
2127 * buffer is empty. This still allows pipelined requests
2128 * to be sent in non-interactive mode.
2129 */
2130 if ((res->flags & (CF_SHUTW|CF_SHUTW_NOW)) || (!appctx->st1 && !req->buf->o)) {
2131 appctx->st0 = STAT_CLI_END;
2132 continue;
2133 }
2134
2135 /* switch state back to GETREQ to read next requests */
2136 appctx->st0 = STAT_CLI_GETREQ;
2137 }
2138 }
2139
2140 if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST)) {
2141 DPRINTF(stderr, "%s@%d: si to buf closed. req=%08x, res=%08x, st=%d\n",
2142 __FUNCTION__, __LINE__, req->flags, res->flags, si->state);
2143 /* Other side has closed, let's abort if we have no more processing to do
2144 * and nothing more to consume. This is comparable to a broken pipe, so
2145 * we forward the close to the request side so that it flows upstream to
2146 * the client.
2147 */
2148 si_shutw(si);
2149 }
2150
2151 if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST) && (appctx->st0 < STAT_CLI_OUTPUT)) {
2152 DPRINTF(stderr, "%s@%d: buf to si closed. req=%08x, res=%08x, st=%d\n",
2153 __FUNCTION__, __LINE__, req->flags, res->flags, si->state);
2154 /* We have no more processing to do, and nothing more to send, and
2155 * the client side has closed. So we'll forward this state downstream
2156 * on the response buffer.
2157 */
2158 si_shutr(si);
2159 res->flags |= CF_READ_NULL;
2160 }
2161
2162 out:
2163 DPRINTF(stderr, "%s@%d: st=%d, rqf=%x, rpf=%x, rqh=%d, rqs=%d, rh=%d, rs=%d\n",
2164 __FUNCTION__, __LINE__,
2165 si->state, req->flags, res->flags, req->buf->i, req->buf->o, res->buf->i, res->buf->o);
2166}
2167
2168/* Dump all fields from <info> into <out> using the "show info" format (name: value) */
2169static int stats_dump_info_fields(struct chunk *out, const struct field *info)
2170{
2171 int field;
2172
2173 for (field = 0; field < INF_TOTAL_FIELDS; field++) {
2174 if (!field_format(info, field))
2175 continue;
2176
2177 if (!chunk_appendf(out, "%s: ", info_field_names[field]))
2178 return 0;
2179 if (!stats_emit_raw_data_field(out, &info[field]))
2180 return 0;
2181 if (!chunk_strcat(out, "\n"))
2182 return 0;
2183 }
2184 return 1;
2185}
2186
2187/* Dump all fields from <info> into <out> using the "show info typed" format */
2188static int stats_dump_typed_info_fields(struct chunk *out, const struct field *info)
2189{
2190 int field;
2191
2192 for (field = 0; field < INF_TOTAL_FIELDS; field++) {
2193 if (!field_format(info, field))
2194 continue;
2195
2196 if (!chunk_appendf(out, "%d.%s.%u:", field, info_field_names[field], info[INF_PROCESS_NUM].u.u32))
2197 return 0;
2198 if (!stats_emit_field_tags(out, &info[field], ':'))
2199 return 0;
2200 if (!stats_emit_typed_data_field(out, &info[field]))
2201 return 0;
2202 if (!chunk_strcat(out, "\n"))
2203 return 0;
2204 }
2205 return 1;
2206}
2207
2208/* Fill <info> with HAProxy global info. <info> is preallocated
2209 * array of length <len>. The length of the aray must be
2210 * INF_TOTAL_FIELDS. If this length is less then this value, the
2211 * function returns 0, otherwise, it returns 1.
2212 */
2213int stats_fill_info(struct field *info, int len)
2214{
2215 unsigned int up = (now.tv_sec - start_date.tv_sec);
2216 struct chunk *out = get_trash_chunk();
2217
2218#ifdef USE_OPENSSL
2219 int ssl_sess_rate = read_freq_ctr(&global.ssl_per_sec);
2220 int ssl_key_rate = read_freq_ctr(&global.ssl_fe_keys_per_sec);
2221 int ssl_reuse = 0;
2222
2223 if (ssl_key_rate < ssl_sess_rate) {
2224 /* count the ssl reuse ratio and avoid overflows in both directions */
2225 ssl_reuse = 100 - (100 * ssl_key_rate + (ssl_sess_rate - 1) / 2) / ssl_sess_rate;
2226 }
2227#endif
2228
2229 if (len < INF_TOTAL_FIELDS)
2230 return 0;
2231
2232 chunk_reset(out);
2233 memset(info, 0, sizeof(*info) * len);
2234
2235 info[INF_NAME] = mkf_str(FO_PRODUCT|FN_OUTPUT|FS_SERVICE, PRODUCT_NAME);
2236 info[INF_VERSION] = mkf_str(FO_PRODUCT|FN_OUTPUT|FS_SERVICE, HAPROXY_VERSION);
2237 info[INF_RELEASE_DATE] = mkf_str(FO_PRODUCT|FN_OUTPUT|FS_SERVICE, HAPROXY_DATE);
2238
2239 info[INF_NBPROC] = mkf_u32(FO_CONFIG|FS_SERVICE, global.nbproc);
2240 info[INF_PROCESS_NUM] = mkf_u32(FO_KEY, relative_pid);
2241 info[INF_PID] = mkf_u32(FO_STATUS, pid);
2242
2243 info[INF_UPTIME] = mkf_str(FN_DURATION, chunk_newstr(out));
2244 chunk_appendf(out, "%ud %uh%02um%02us", up / 86400, (up % 86400) / 3600, (up % 3600) / 60, (up % 60));
2245
2246 info[INF_UPTIME_SEC] = mkf_u32(FN_DURATION, up);
2247 info[INF_MEMMAX_MB] = mkf_u32(FO_CONFIG|FN_LIMIT, global.rlimit_memmax);
2248 info[INF_POOL_ALLOC_MB] = mkf_u32(0, (unsigned)(pool_total_allocated() / 1048576L));
2249 info[INF_POOL_USED_MB] = mkf_u32(0, (unsigned)(pool_total_used() / 1048576L));
2250 info[INF_POOL_FAILED] = mkf_u32(FN_COUNTER, pool_total_failures());
2251 info[INF_ULIMIT_N] = mkf_u32(FO_CONFIG|FN_LIMIT, global.rlimit_nofile);
2252 info[INF_MAXSOCK] = mkf_u32(FO_CONFIG|FN_LIMIT, global.maxsock);
2253 info[INF_MAXCONN] = mkf_u32(FO_CONFIG|FN_LIMIT, global.maxconn);
2254 info[INF_HARD_MAXCONN] = mkf_u32(FO_CONFIG|FN_LIMIT, global.hardmaxconn);
2255 info[INF_CURR_CONN] = mkf_u32(0, actconn);
2256 info[INF_CUM_CONN] = mkf_u32(FN_COUNTER, totalconn);
2257 info[INF_CUM_REQ] = mkf_u32(FN_COUNTER, global.req_count);
2258#ifdef USE_OPENSSL
2259 info[INF_MAX_SSL_CONNS] = mkf_u32(FN_MAX, global.maxsslconn);
2260 info[INF_CURR_SSL_CONNS] = mkf_u32(0, sslconns);
2261 info[INF_CUM_SSL_CONNS] = mkf_u32(FN_COUNTER, totalsslconns);
2262#endif
2263 info[INF_MAXPIPES] = mkf_u32(FO_CONFIG|FN_LIMIT, global.maxpipes);
2264 info[INF_PIPES_USED] = mkf_u32(0, pipes_used);
2265 info[INF_PIPES_FREE] = mkf_u32(0, pipes_free);
2266 info[INF_CONN_RATE] = mkf_u32(FN_RATE, read_freq_ctr(&global.conn_per_sec));
2267 info[INF_CONN_RATE_LIMIT] = mkf_u32(FO_CONFIG|FN_LIMIT, global.cps_lim);
2268 info[INF_MAX_CONN_RATE] = mkf_u32(FN_MAX, global.cps_max);
2269 info[INF_SESS_RATE] = mkf_u32(FN_RATE, read_freq_ctr(&global.sess_per_sec));
2270 info[INF_SESS_RATE_LIMIT] = mkf_u32(FO_CONFIG|FN_LIMIT, global.sps_lim);
2271 info[INF_MAX_SESS_RATE] = mkf_u32(FN_RATE, global.sps_max);
2272
2273#ifdef USE_OPENSSL
2274 info[INF_SSL_RATE] = mkf_u32(FN_RATE, ssl_sess_rate);
2275 info[INF_SSL_RATE_LIMIT] = mkf_u32(FO_CONFIG|FN_LIMIT, global.ssl_lim);
2276 info[INF_MAX_SSL_RATE] = mkf_u32(FN_MAX, global.ssl_max);
2277 info[INF_SSL_FRONTEND_KEY_RATE] = mkf_u32(0, ssl_key_rate);
2278 info[INF_SSL_FRONTEND_MAX_KEY_RATE] = mkf_u32(FN_MAX, global.ssl_fe_keys_max);
2279 info[INF_SSL_FRONTEND_SESSION_REUSE_PCT] = mkf_u32(0, ssl_reuse);
2280 info[INF_SSL_BACKEND_KEY_RATE] = mkf_u32(FN_RATE, read_freq_ctr(&global.ssl_be_keys_per_sec));
2281 info[INF_SSL_BACKEND_MAX_KEY_RATE] = mkf_u32(FN_MAX, global.ssl_be_keys_max);
2282 info[INF_SSL_CACHE_LOOKUPS] = mkf_u32(FN_COUNTER, global.shctx_lookups);
2283 info[INF_SSL_CACHE_MISSES] = mkf_u32(FN_COUNTER, global.shctx_misses);
2284#endif
2285 info[INF_COMPRESS_BPS_IN] = mkf_u32(FN_RATE, read_freq_ctr(&global.comp_bps_in));
2286 info[INF_COMPRESS_BPS_OUT] = mkf_u32(FN_RATE, read_freq_ctr(&global.comp_bps_out));
2287 info[INF_COMPRESS_BPS_RATE_LIM] = mkf_u32(FO_CONFIG|FN_LIMIT, global.comp_rate_lim);
2288#ifdef USE_ZLIB
2289 info[INF_ZLIB_MEM_USAGE] = mkf_u32(0, zlib_used_memory);
2290 info[INF_MAX_ZLIB_MEM_USAGE] = mkf_u32(FO_CONFIG|FN_LIMIT, global.maxzlibmem);
2291#endif
2292 info[INF_TASKS] = mkf_u32(0, nb_tasks_cur);
2293 info[INF_RUN_QUEUE] = mkf_u32(0, run_queue_cur);
2294 info[INF_IDLE_PCT] = mkf_u32(FN_AVG, idle_pct);
2295 info[INF_NODE] = mkf_str(FO_CONFIG|FN_OUTPUT|FS_SERVICE, global.node);
2296 if (global.desc)
2297 info[INF_DESCRIPTION] = mkf_str(FO_CONFIG|FN_OUTPUT|FS_SERVICE, global.desc);
2298
2299 return 1;
2300}
2301
2302/* This function dumps information onto the stream interface's read buffer.
2303 * It returns 0 as long as it does not complete, non-zero upon completion.
2304 * No state is used.
2305 */
2306static int stats_dump_info_to_buffer(struct stream_interface *si)
2307{
2308 struct appctx *appctx = __objt_appctx(si->end);
2309
2310 if (!stats_fill_info(info, INF_TOTAL_FIELDS))
2311 return 0;
2312
2313 chunk_reset(&trash);
2314
2315 if (appctx->ctx.stats.flags & STAT_FMT_TYPED)
2316 stats_dump_typed_info_fields(&trash, info);
2317 else
2318 stats_dump_info_fields(&trash, info);
2319
2320 if (bi_putchk(si_ic(si), &trash) == -1) {
2321 si_applet_cant_put(si);
2322 return 0;
2323 }
2324
2325 return 1;
2326}
2327
2328/* dumps server state information into <buf> for all the servers found in <backend>
2329 * These information are all the parameters which may change during HAProxy runtime.
2330 * By default, we only export to the last known server state file format.
2331 * These information can be used at next startup to recover same level of server state.
2332 */
2333static int dump_servers_state(struct stream_interface *si, struct chunk *buf)
2334{
2335 struct appctx *appctx = __objt_appctx(si->end);
2336 struct server *srv;
2337 char srv_addr[INET6_ADDRSTRLEN + 1];
2338 time_t srv_time_since_last_change;
2339 int bk_f_forced_id, srv_f_forced_id;
2340
2341
2342 /* we don't want to report any state if the backend is not enabled on this process */
2343 if (appctx->ctx.server_state.px->bind_proc && !(appctx->ctx.server_state.px->bind_proc & (1UL << (relative_pid - 1))))
2344 return 1;
2345
2346 if (!appctx->ctx.server_state.sv)
2347 appctx->ctx.server_state.sv = appctx->ctx.server_state.px->srv;
2348
2349 for (; appctx->ctx.server_state.sv != NULL; appctx->ctx.server_state.sv = srv->next) {
2350 srv = appctx->ctx.server_state.sv;
2351 srv_addr[0] = '\0';
2352
2353 switch (srv->addr.ss_family) {
2354 case AF_INET:
2355 inet_ntop(srv->addr.ss_family, &((struct sockaddr_in *)&srv->addr)->sin_addr,
2356 srv_addr, INET_ADDRSTRLEN + 1);
2357 break;
2358 case AF_INET6:
2359 inet_ntop(srv->addr.ss_family, &((struct sockaddr_in6 *)&srv->addr)->sin6_addr,
2360 srv_addr, INET6_ADDRSTRLEN + 1);
2361 break;
2362 }
2363 srv_time_since_last_change = now.tv_sec - srv->last_change;
2364 bk_f_forced_id = appctx->ctx.server_state.px->options & PR_O_FORCED_ID ? 1 : 0;
2365 srv_f_forced_id = srv->flags & SRV_F_FORCED_ID ? 1 : 0;
2366
2367 chunk_appendf(buf,
2368 "%d %s "
2369 "%d %s %s "
2370 "%d %d %d %d %ld "
2371 "%d %d %d %d %d "
2372 "%d %d"
2373 "\n",
2374 appctx->ctx.server_state.px->uuid, appctx->ctx.server_state.px->id,
2375 srv->puid, srv->id, srv_addr,
2376 srv->state, srv->admin, srv->uweight, srv->iweight, (long int)srv_time_since_last_change,
2377 srv->check.status, srv->check.result, srv->check.health, srv->check.state, srv->agent.state,
2378 bk_f_forced_id, srv_f_forced_id);
2379 if (bi_putchk(si_ic(si), &trash) == -1) {
2380 si_applet_cant_put(si);
2381 return 0;
2382 }
2383 }
2384 return 1;
2385}
2386
2387/* Parses backend list and simply report backend names */
2388static int stats_dump_backend_to_buffer(struct stream_interface *si)
2389{
2390 struct appctx *appctx = __objt_appctx(si->end);
2391 extern struct proxy *proxy;
2392 struct proxy *curproxy;
2393
2394 chunk_reset(&trash);
2395
2396 if (!appctx->ctx.be.px) {
2397 chunk_printf(&trash, "# name\n");
2398 if (bi_putchk(si_ic(si), &trash) == -1) {
2399 si_applet_cant_put(si);
2400 return 0;
2401 }
2402 appctx->ctx.be.px = proxy;
2403 }
2404
2405 for (; appctx->ctx.be.px != NULL; appctx->ctx.be.px = curproxy->next) {
2406 curproxy = appctx->ctx.be.px;
2407
2408 /* looking for backends only */
2409 if (!(curproxy->cap & PR_CAP_BE))
2410 continue;
2411
2412 /* we don't want to list a backend which is bound to this process */
2413 if (curproxy->bind_proc && !(curproxy->bind_proc & (1UL << (relative_pid - 1))))
2414 continue;
2415
2416 chunk_appendf(&trash, "%s\n", curproxy->id);
2417 if (bi_putchk(si_ic(si), &trash) == -1) {
2418 si_applet_cant_put(si);
2419 return 0;
2420 }
2421 }
2422
2423 return 1;
2424}
2425
2426/* Parses backend list or simply use backend name provided by the user to return
2427 * states of servers to stdout.
2428 */
2429static int stats_dump_servers_state_to_buffer(struct stream_interface *si)
2430{
2431 struct appctx *appctx = __objt_appctx(si->end);
2432 extern struct proxy *proxy;
2433 struct proxy *curproxy;
2434
2435 chunk_reset(&trash);
2436
2437 if (appctx->st2 == STAT_ST_INIT) {
2438 if (!appctx->ctx.server_state.px)
2439 appctx->ctx.server_state.px = proxy;
2440 appctx->st2 = STAT_ST_HEAD;
2441 }
2442
2443 if (appctx->st2 == STAT_ST_HEAD) {
2444 chunk_printf(&trash, "%d\n# %s\n", SRV_STATE_FILE_VERSION, SRV_STATE_FILE_FIELD_NAMES);
2445 if (bi_putchk(si_ic(si), &trash) == -1) {
2446 si_applet_cant_put(si);
2447 return 0;
2448 }
2449 appctx->st2 = STAT_ST_INFO;
2450 }
2451
2452 /* STAT_ST_INFO */
2453 for (; appctx->ctx.server_state.px != NULL; appctx->ctx.server_state.px = curproxy->next) {
2454 curproxy = appctx->ctx.server_state.px;
2455 /* servers are only in backends */
2456 if (curproxy->cap & PR_CAP_BE) {
2457 if (!dump_servers_state(si, &trash))
2458 return 0;
2459
2460 if (bi_putchk(si_ic(si), &trash) == -1) {
2461 si_applet_cant_put(si);
2462 return 0;
2463 }
2464 }
2465 /* only the selected proxy is dumped */
2466 if (appctx->ctx.server_state.iid)
2467 break;
2468 }
2469
2470 return 1;
2471}
2472
2473/* This function dumps memory usage information onto the stream interface's
2474 * read buffer. It returns 0 as long as it does not complete, non-zero upon
2475 * completion. No state is used.
2476 */
2477static int stats_dump_pools_to_buffer(struct stream_interface *si)
2478{
2479 dump_pools_to_trash();
2480 if (bi_putchk(si_ic(si), &trash) == -1) {
2481 si_applet_cant_put(si);
2482 return 0;
2483 }
2484 return 1;
2485}
2486
2487static inline const char *get_conn_ctrl_name(const struct connection *conn)
2488{
2489 if (!conn_ctrl_ready(conn))
2490 return "NONE";
2491 return conn->ctrl->name;
2492}
2493
2494static inline const char *get_conn_xprt_name(const struct connection *conn)
2495{
2496 static char ptr[19];
2497
2498 if (!conn_xprt_ready(conn))
2499 return "NONE";
2500
2501 if (conn->xprt == &raw_sock)
2502 return "RAW";
2503
2504#ifdef USE_OPENSSL
2505 if (conn->xprt == &ssl_sock)
2506 return "SSL";
2507#endif
2508 snprintf(ptr, sizeof(ptr), "%p", conn->xprt);
2509 return ptr;
2510}
2511
2512static inline const char *get_conn_data_name(const struct connection *conn)
2513{
2514 static char ptr[19];
2515
2516 if (!conn->data)
2517 return "NONE";
2518
2519 if (conn->data == &sess_conn_cb)
2520 return "SESS";
2521
2522 if (conn->data == &si_conn_cb)
2523 return "STRM";
2524
2525 if (conn->data == &check_conn_cb)
2526 return "CHCK";
2527
2528 snprintf(ptr, sizeof(ptr), "%p", conn->data);
2529 return ptr;
2530}
2531
2532/* This function dumps a complete stream state onto the stream interface's
2533 * read buffer. The stream has to be set in sess->target. It returns
2534 * 0 if the output buffer is full and it needs to be called again, otherwise
2535 * non-zero. It is designed to be called from stats_dump_sess_to_buffer() below.
2536 */
2537static int stats_dump_full_sess_to_buffer(struct stream_interface *si, struct stream *sess)
2538{
2539 struct appctx *appctx = __objt_appctx(si->end);
2540 struct tm tm;
2541 extern const char *monthname[12];
2542 char pn[INET6_ADDRSTRLEN];
2543 struct connection *conn;
2544 struct appctx *tmpctx;
2545
2546 chunk_reset(&trash);
2547
2548 if (appctx->ctx.sess.section > 0 && appctx->ctx.sess.uid != sess->uniq_id) {
2549 /* stream changed, no need to go any further */
2550 chunk_appendf(&trash, " *** session terminated while we were watching it ***\n");
2551 if (bi_putchk(si_ic(si), &trash) == -1) {
2552 si_applet_cant_put(si);
2553 return 0;
2554 }
2555 appctx->ctx.sess.uid = 0;
2556 appctx->ctx.sess.section = 0;
2557 return 1;
2558 }
2559
2560 switch (appctx->ctx.sess.section) {
2561 case 0: /* main status of the stream */
2562 appctx->ctx.sess.uid = sess->uniq_id;
2563 appctx->ctx.sess.section = 1;
2564 /* fall through */
2565
2566 case 1:
2567 get_localtime(sess->logs.accept_date.tv_sec, &tm);
2568 chunk_appendf(&trash,
2569 "%p: [%02d/%s/%04d:%02d:%02d:%02d.%06d] id=%u proto=%s",
2570 sess,
2571 tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900,
2572 tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(sess->logs.accept_date.tv_usec),
2573 sess->uniq_id,
2574 strm_li(sess) ? strm_li(sess)->proto->name : "?");
2575
2576 conn = objt_conn(strm_orig(sess));
2577 switch (conn ? addr_to_str(&conn->addr.from, pn, sizeof(pn)) : AF_UNSPEC) {
2578 case AF_INET:
2579 case AF_INET6:
2580 chunk_appendf(&trash, " source=%s:%d\n",
2581 pn, get_host_port(&conn->addr.from));
2582 break;
2583 case AF_UNIX:
2584 chunk_appendf(&trash, " source=unix:%d\n", strm_li(sess)->luid);
2585 break;
2586 default:
2587 /* no more information to print right now */
2588 chunk_appendf(&trash, "\n");
2589 break;
2590 }
2591
2592 chunk_appendf(&trash,
2593 " flags=0x%x, conn_retries=%d, srv_conn=%p, pend_pos=%p\n",
2594 sess->flags, sess->si[1].conn_retries, sess->srv_conn, sess->pend_pos);
2595
2596 chunk_appendf(&trash,
2597 " frontend=%s (id=%u mode=%s), listener=%s (id=%u)",
2598 strm_fe(sess)->id, strm_fe(sess)->uuid, strm_fe(sess)->mode ? "http" : "tcp",
2599 strm_li(sess) ? strm_li(sess)->name ? strm_li(sess)->name : "?" : "?",
2600 strm_li(sess) ? strm_li(sess)->luid : 0);
2601
2602 if (conn)
2603 conn_get_to_addr(conn);
2604
2605 switch (conn ? addr_to_str(&conn->addr.to, pn, sizeof(pn)) : AF_UNSPEC) {
2606 case AF_INET:
2607 case AF_INET6:
2608 chunk_appendf(&trash, " addr=%s:%d\n",
2609 pn, get_host_port(&conn->addr.to));
2610 break;
2611 case AF_UNIX:
2612 chunk_appendf(&trash, " addr=unix:%d\n", strm_li(sess)->luid);
2613 break;
2614 default:
2615 /* no more information to print right now */
2616 chunk_appendf(&trash, "\n");
2617 break;
2618 }
2619
2620 if (sess->be->cap & PR_CAP_BE)
2621 chunk_appendf(&trash,
2622 " backend=%s (id=%u mode=%s)",
2623 sess->be->id,
2624 sess->be->uuid, sess->be->mode ? "http" : "tcp");
2625 else
2626 chunk_appendf(&trash, " backend=<NONE> (id=-1 mode=-)");
2627
2628 conn = objt_conn(sess->si[1].end);
2629 if (conn)
2630 conn_get_from_addr(conn);
2631
2632 switch (conn ? addr_to_str(&conn->addr.from, pn, sizeof(pn)) : AF_UNSPEC) {
2633 case AF_INET:
2634 case AF_INET6:
2635 chunk_appendf(&trash, " addr=%s:%d\n",
2636 pn, get_host_port(&conn->addr.from));
2637 break;
2638 case AF_UNIX:
2639 chunk_appendf(&trash, " addr=unix\n");
2640 break;
2641 default:
2642 /* no more information to print right now */
2643 chunk_appendf(&trash, "\n");
2644 break;
2645 }
2646
2647 if (sess->be->cap & PR_CAP_BE)
2648 chunk_appendf(&trash,
2649 " server=%s (id=%u)",
2650 objt_server(sess->target) ? objt_server(sess->target)->id : "<none>",
2651 objt_server(sess->target) ? objt_server(sess->target)->puid : 0);
2652 else
2653 chunk_appendf(&trash, " server=<NONE> (id=-1)");
2654
2655 if (conn)
2656 conn_get_to_addr(conn);
2657
2658 switch (conn ? addr_to_str(&conn->addr.to, pn, sizeof(pn)) : AF_UNSPEC) {
2659 case AF_INET:
2660 case AF_INET6:
2661 chunk_appendf(&trash, " addr=%s:%d\n",
2662 pn, get_host_port(&conn->addr.to));
2663 break;
2664 case AF_UNIX:
2665 chunk_appendf(&trash, " addr=unix\n");
2666 break;
2667 default:
2668 /* no more information to print right now */
2669 chunk_appendf(&trash, "\n");
2670 break;
2671 }
2672
2673 chunk_appendf(&trash,
2674 " task=%p (state=0x%02x nice=%d calls=%d exp=%s%s",
2675 sess->task,
2676 sess->task->state,
2677 sess->task->nice, sess->task->calls,
2678 sess->task->expire ?
2679 tick_is_expired(sess->task->expire, now_ms) ? "<PAST>" :
2680 human_time(TICKS_TO_MS(sess->task->expire - now_ms),
2681 TICKS_TO_MS(1000)) : "<NEVER>",
2682 task_in_rq(sess->task) ? ", running" : "");
2683
2684 chunk_appendf(&trash,
2685 " age=%s)\n",
2686 human_time(now.tv_sec - sess->logs.accept_date.tv_sec, 1));
2687
2688 if (sess->txn)
2689 chunk_appendf(&trash,
2690 " txn=%p flags=0x%x meth=%d status=%d req.st=%s rsp.st=%s waiting=%d\n",
2691 sess->txn, sess->txn->flags, sess->txn->meth, sess->txn->status,
2692 http_msg_state_str(sess->txn->req.msg_state), http_msg_state_str(sess->txn->rsp.msg_state), !LIST_ISEMPTY(&sess->buffer_wait));
2693
2694 chunk_appendf(&trash,
2695 " si[0]=%p (state=%s flags=0x%02x endp0=%s:%p exp=%s, et=0x%03x)\n",
2696 &sess->si[0],
2697 si_state_str(sess->si[0].state),
2698 sess->si[0].flags,
2699 obj_type_name(sess->si[0].end),
2700 obj_base_ptr(sess->si[0].end),
2701 sess->si[0].exp ?
2702 tick_is_expired(sess->si[0].exp, now_ms) ? "<PAST>" :
2703 human_time(TICKS_TO_MS(sess->si[0].exp - now_ms),
2704 TICKS_TO_MS(1000)) : "<NEVER>",
2705 sess->si[0].err_type);
2706
2707 chunk_appendf(&trash,
2708 " si[1]=%p (state=%s flags=0x%02x endp1=%s:%p exp=%s, et=0x%03x)\n",
2709 &sess->si[1],
2710 si_state_str(sess->si[1].state),
2711 sess->si[1].flags,
2712 obj_type_name(sess->si[1].end),
2713 obj_base_ptr(sess->si[1].end),
2714 sess->si[1].exp ?
2715 tick_is_expired(sess->si[1].exp, now_ms) ? "<PAST>" :
2716 human_time(TICKS_TO_MS(sess->si[1].exp - now_ms),
2717 TICKS_TO_MS(1000)) : "<NEVER>",
2718 sess->si[1].err_type);
2719
2720 if ((conn = objt_conn(sess->si[0].end)) != NULL) {
2721 chunk_appendf(&trash,
2722 " co0=%p ctrl=%s xprt=%s data=%s target=%s:%p\n",
2723 conn,
2724 get_conn_ctrl_name(conn),
2725 get_conn_xprt_name(conn),
2726 get_conn_data_name(conn),
2727 obj_type_name(conn->target),
2728 obj_base_ptr(conn->target));
2729
2730 chunk_appendf(&trash, " flags=0x%08x", conn->flags);
2731
2732 if (conn->t.sock.fd >= 0) {
2733 chunk_appendf(&trash, " fd=%d fd.state=%02x fd.cache=%d updt=%d\n",
2734 conn->t.sock.fd, fdtab[conn->t.sock.fd].state,
2735 fdtab[conn->t.sock.fd].cache, fdtab[conn->t.sock.fd].updated);
2736 }
2737 else
2738 chunk_appendf(&trash, " fd=<dead>\n");
2739 }
2740 else if ((tmpctx = objt_appctx(sess->si[0].end)) != NULL) {
2741 chunk_appendf(&trash,
2742 " app0=%p st0=%d st1=%d st2=%d applet=%s\n",
2743 tmpctx,
2744 tmpctx->st0,
2745 tmpctx->st1,
2746 tmpctx->st2,
2747 tmpctx->applet->name);
2748 }
2749
2750 if ((conn = objt_conn(sess->si[1].end)) != NULL) {
2751 chunk_appendf(&trash,
2752 " co1=%p ctrl=%s xprt=%s data=%s target=%s:%p\n",
2753 conn,
2754 get_conn_ctrl_name(conn),
2755 get_conn_xprt_name(conn),
2756 get_conn_data_name(conn),
2757 obj_type_name(conn->target),
2758 obj_base_ptr(conn->target));
2759
2760 chunk_appendf(&trash, " flags=0x%08x", conn->flags);
2761
2762 if (conn->t.sock.fd >= 0) {
2763 chunk_appendf(&trash, " fd=%d fd.state=%02x fd.cache=%d updt=%d\n",
2764 conn->t.sock.fd, fdtab[conn->t.sock.fd].state,
2765 fdtab[conn->t.sock.fd].cache, fdtab[conn->t.sock.fd].updated);
2766 }
2767 else
2768 chunk_appendf(&trash, " fd=<dead>\n");
2769 }
2770 else if ((tmpctx = objt_appctx(sess->si[1].end)) != NULL) {
2771 chunk_appendf(&trash,
2772 " app1=%p st0=%d st1=%d st2=%d applet=%s\n",
2773 tmpctx,
2774 tmpctx->st0,
2775 tmpctx->st1,
2776 tmpctx->st2,
2777 tmpctx->applet->name);
2778 }
2779
2780 chunk_appendf(&trash,
2781 " req=%p (f=0x%06x an=0x%x pipe=%d tofwd=%d total=%lld)\n"
2782 " an_exp=%s",
2783 &sess->req,
2784 sess->req.flags, sess->req.analysers,
2785 sess->req.pipe ? sess->req.pipe->data : 0,
2786 sess->req.to_forward, sess->req.total,
2787 sess->req.analyse_exp ?
2788 human_time(TICKS_TO_MS(sess->req.analyse_exp - now_ms),
2789 TICKS_TO_MS(1000)) : "<NEVER>");
2790
2791 chunk_appendf(&trash,
2792 " rex=%s",
2793 sess->req.rex ?
2794 human_time(TICKS_TO_MS(sess->req.rex - now_ms),
2795 TICKS_TO_MS(1000)) : "<NEVER>");
2796
2797 chunk_appendf(&trash,
2798 " wex=%s\n"
2799 " buf=%p data=%p o=%d p=%d req.next=%d i=%d size=%d\n",
2800 sess->req.wex ?
2801 human_time(TICKS_TO_MS(sess->req.wex - now_ms),
2802 TICKS_TO_MS(1000)) : "<NEVER>",
2803 sess->req.buf,
2804 sess->req.buf->data, sess->req.buf->o,
2805 (int)(sess->req.buf->p - sess->req.buf->data),
2806 sess->txn ? sess->txn->req.next : 0, sess->req.buf->i,
2807 sess->req.buf->size);
2808
2809 chunk_appendf(&trash,
2810 " res=%p (f=0x%06x an=0x%x pipe=%d tofwd=%d total=%lld)\n"
2811 " an_exp=%s",
2812 &sess->res,
2813 sess->res.flags, sess->res.analysers,
2814 sess->res.pipe ? sess->res.pipe->data : 0,
2815 sess->res.to_forward, sess->res.total,
2816 sess->res.analyse_exp ?
2817 human_time(TICKS_TO_MS(sess->res.analyse_exp - now_ms),
2818 TICKS_TO_MS(1000)) : "<NEVER>");
2819
2820 chunk_appendf(&trash,
2821 " rex=%s",
2822 sess->res.rex ?
2823 human_time(TICKS_TO_MS(sess->res.rex - now_ms),
2824 TICKS_TO_MS(1000)) : "<NEVER>");
2825
2826 chunk_appendf(&trash,
2827 " wex=%s\n"
2828 " buf=%p data=%p o=%d p=%d rsp.next=%d i=%d size=%d\n",
2829 sess->res.wex ?
2830 human_time(TICKS_TO_MS(sess->res.wex - now_ms),
2831 TICKS_TO_MS(1000)) : "<NEVER>",
2832 sess->res.buf,
2833 sess->res.buf->data, sess->res.buf->o,
2834 (int)(sess->res.buf->p - sess->res.buf->data),
2835 sess->txn ? sess->txn->rsp.next : 0, sess->res.buf->i,
2836 sess->res.buf->size);
2837
2838 if (bi_putchk(si_ic(si), &trash) == -1) {
2839 si_applet_cant_put(si);
2840 return 0;
2841 }
2842
2843 /* use other states to dump the contents */
2844 }
2845 /* end of dump */
2846 appctx->ctx.sess.uid = 0;
2847 appctx->ctx.sess.section = 0;
2848 return 1;
2849}
2850
William Lallemand74c24fb2016-11-21 17:18:36 +01002851/* This function dumps all streams' states onto the stream interface's
2852 * read buffer. It returns 0 if the output buffer is full and it needs
2853 * to be called again, otherwise non-zero. It is designed to be called
2854 * from stats_dump_sess_to_buffer() below.
2855 */
2856static int stats_dump_sess_to_buffer(struct stream_interface *si)
2857{
2858 struct appctx *appctx = __objt_appctx(si->end);
2859 struct connection *conn;
2860
2861 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW))) {
2862 /* If we're forced to shut down, we might have to remove our
2863 * reference to the last stream being dumped.
2864 */
2865 if (appctx->st2 == STAT_ST_LIST) {
2866 if (!LIST_ISEMPTY(&appctx->ctx.sess.bref.users)) {
2867 LIST_DEL(&appctx->ctx.sess.bref.users);
2868 LIST_INIT(&appctx->ctx.sess.bref.users);
2869 }
2870 }
2871 return 1;
2872 }
2873
2874 chunk_reset(&trash);
2875
2876 switch (appctx->st2) {
2877 case STAT_ST_INIT:
2878 /* the function had not been called yet, let's prepare the
2879 * buffer for a response. We initialize the current stream
2880 * pointer to the first in the global list. When a target
2881 * stream is being destroyed, it is responsible for updating
2882 * this pointer. We know we have reached the end when this
2883 * pointer points back to the head of the streams list.
2884 */
2885 LIST_INIT(&appctx->ctx.sess.bref.users);
2886 appctx->ctx.sess.bref.ref = streams.n;
2887 appctx->st2 = STAT_ST_LIST;
2888 /* fall through */
2889
2890 case STAT_ST_LIST:
2891 /* first, let's detach the back-ref from a possible previous stream */
2892 if (!LIST_ISEMPTY(&appctx->ctx.sess.bref.users)) {
2893 LIST_DEL(&appctx->ctx.sess.bref.users);
2894 LIST_INIT(&appctx->ctx.sess.bref.users);
2895 }
2896
2897 /* and start from where we stopped */
2898 while (appctx->ctx.sess.bref.ref != &streams) {
2899 char pn[INET6_ADDRSTRLEN];
2900 struct stream *curr_sess;
2901
2902 curr_sess = LIST_ELEM(appctx->ctx.sess.bref.ref, struct stream *, list);
2903
2904 if (appctx->ctx.sess.target) {
2905 if (appctx->ctx.sess.target != (void *)-1 && appctx->ctx.sess.target != curr_sess)
2906 goto next_sess;
2907
2908 LIST_ADDQ(&curr_sess->back_refs, &appctx->ctx.sess.bref.users);
2909 /* call the proper dump() function and return if we're missing space */
2910 if (!stats_dump_full_sess_to_buffer(si, curr_sess))
2911 return 0;
2912
2913 /* stream dump complete */
2914 LIST_DEL(&appctx->ctx.sess.bref.users);
2915 LIST_INIT(&appctx->ctx.sess.bref.users);
2916 if (appctx->ctx.sess.target != (void *)-1) {
2917 appctx->ctx.sess.target = NULL;
2918 break;
2919 }
2920 else
2921 goto next_sess;
2922 }
2923
2924 chunk_appendf(&trash,
2925 "%p: proto=%s",
2926 curr_sess,
2927 strm_li(curr_sess) ? strm_li(curr_sess)->proto->name : "?");
2928
2929 conn = objt_conn(strm_orig(curr_sess));
2930 switch (conn ? addr_to_str(&conn->addr.from, pn, sizeof(pn)) : AF_UNSPEC) {
2931 case AF_INET:
2932 case AF_INET6:
2933 chunk_appendf(&trash,
2934 " src=%s:%d fe=%s be=%s srv=%s",
2935 pn,
2936 get_host_port(&conn->addr.from),
2937 strm_fe(curr_sess)->id,
2938 (curr_sess->be->cap & PR_CAP_BE) ? curr_sess->be->id : "<NONE>",
2939 objt_server(curr_sess->target) ? objt_server(curr_sess->target)->id : "<none>"
2940 );
2941 break;
2942 case AF_UNIX:
2943 chunk_appendf(&trash,
2944 " src=unix:%d fe=%s be=%s srv=%s",
2945 strm_li(curr_sess)->luid,
2946 strm_fe(curr_sess)->id,
2947 (curr_sess->be->cap & PR_CAP_BE) ? curr_sess->be->id : "<NONE>",
2948 objt_server(curr_sess->target) ? objt_server(curr_sess->target)->id : "<none>"
2949 );
2950 break;
2951 }
2952
2953 chunk_appendf(&trash,
2954 " ts=%02x age=%s calls=%d",
2955 curr_sess->task->state,
2956 human_time(now.tv_sec - curr_sess->logs.tv_accept.tv_sec, 1),
2957 curr_sess->task->calls);
2958
2959 chunk_appendf(&trash,
2960 " rq[f=%06xh,i=%d,an=%02xh,rx=%s",
2961 curr_sess->req.flags,
2962 curr_sess->req.buf->i,
2963 curr_sess->req.analysers,
2964 curr_sess->req.rex ?
2965 human_time(TICKS_TO_MS(curr_sess->req.rex - now_ms),
2966 TICKS_TO_MS(1000)) : "");
2967
2968 chunk_appendf(&trash,
2969 ",wx=%s",
2970 curr_sess->req.wex ?
2971 human_time(TICKS_TO_MS(curr_sess->req.wex - now_ms),
2972 TICKS_TO_MS(1000)) : "");
2973
2974 chunk_appendf(&trash,
2975 ",ax=%s]",
2976 curr_sess->req.analyse_exp ?
2977 human_time(TICKS_TO_MS(curr_sess->req.analyse_exp - now_ms),
2978 TICKS_TO_MS(1000)) : "");
2979
2980 chunk_appendf(&trash,
2981 " rp[f=%06xh,i=%d,an=%02xh,rx=%s",
2982 curr_sess->res.flags,
2983 curr_sess->res.buf->i,
2984 curr_sess->res.analysers,
2985 curr_sess->res.rex ?
2986 human_time(TICKS_TO_MS(curr_sess->res.rex - now_ms),
2987 TICKS_TO_MS(1000)) : "");
2988
2989 chunk_appendf(&trash,
2990 ",wx=%s",
2991 curr_sess->res.wex ?
2992 human_time(TICKS_TO_MS(curr_sess->res.wex - now_ms),
2993 TICKS_TO_MS(1000)) : "");
2994
2995 chunk_appendf(&trash,
2996 ",ax=%s]",
2997 curr_sess->res.analyse_exp ?
2998 human_time(TICKS_TO_MS(curr_sess->res.analyse_exp - now_ms),
2999 TICKS_TO_MS(1000)) : "");
3000
3001 conn = objt_conn(curr_sess->si[0].end);
3002 chunk_appendf(&trash,
3003 " s0=[%d,%1xh,fd=%d,ex=%s]",
3004 curr_sess->si[0].state,
3005 curr_sess->si[0].flags,
3006 (conn && conn->t.sock.fd >= 0) ? conn->t.sock.fd : -1,
3007 curr_sess->si[0].exp ?
3008 human_time(TICKS_TO_MS(curr_sess->si[0].exp - now_ms),
3009 TICKS_TO_MS(1000)) : "");
3010
3011 conn = objt_conn(curr_sess->si[1].end);
3012 chunk_appendf(&trash,
3013 " s1=[%d,%1xh,fd=%d,ex=%s]",
3014 curr_sess->si[1].state,
3015 curr_sess->si[1].flags,
3016 (conn && conn->t.sock.fd >= 0) ? conn->t.sock.fd : -1,
3017 curr_sess->si[1].exp ?
3018 human_time(TICKS_TO_MS(curr_sess->si[1].exp - now_ms),
3019 TICKS_TO_MS(1000)) : "");
3020
3021 chunk_appendf(&trash,
3022 " exp=%s",
3023 curr_sess->task->expire ?
3024 human_time(TICKS_TO_MS(curr_sess->task->expire - now_ms),
3025 TICKS_TO_MS(1000)) : "");
3026 if (task_in_rq(curr_sess->task))
3027 chunk_appendf(&trash, " run(nice=%d)", curr_sess->task->nice);
3028
3029 chunk_appendf(&trash, "\n");
3030
3031 if (bi_putchk(si_ic(si), &trash) == -1) {
3032 /* let's try again later from this stream. We add ourselves into
3033 * this stream's users so that it can remove us upon termination.
3034 */
3035 si_applet_cant_put(si);
3036 LIST_ADDQ(&curr_sess->back_refs, &appctx->ctx.sess.bref.users);
3037 return 0;
3038 }
3039
3040 next_sess:
3041 appctx->ctx.sess.bref.ref = curr_sess->list.n;
3042 }
3043
3044 if (appctx->ctx.sess.target && appctx->ctx.sess.target != (void *)-1) {
3045 /* specified stream not found */
3046 if (appctx->ctx.sess.section > 0)
3047 chunk_appendf(&trash, " *** session terminated while we were watching it ***\n");
3048 else
3049 chunk_appendf(&trash, "Session not found.\n");
3050
3051 if (bi_putchk(si_ic(si), &trash) == -1) {
3052 si_applet_cant_put(si);
3053 return 0;
3054 }
3055
3056 appctx->ctx.sess.target = NULL;
3057 appctx->ctx.sess.uid = 0;
3058 return 1;
3059 }
3060
3061 appctx->st2 = STAT_ST_FIN;
3062 /* fall through */
3063
3064 default:
3065 appctx->st2 = STAT_ST_FIN;
3066 return 1;
3067 }
3068}
3069
3070/* This is called when the stream interface is closed. For instance, upon an
3071 * external abort, we won't call the i/o handler anymore so we may need to
3072 * remove back references to the stream currently being dumped.
3073 */
3074static void cli_release_handler(struct appctx *appctx)
3075{
3076 if (appctx->io_release) {
3077 appctx->io_release(appctx);
3078 appctx->io_release = NULL;
3079 }
3080 if (appctx->st0 == STAT_CLI_O_SESS && appctx->st2 == STAT_ST_LIST) {
3081 if (!LIST_ISEMPTY(&appctx->ctx.sess.bref.users))
3082 LIST_DEL(&appctx->ctx.sess.bref.users);
3083 }
3084 else if ((appctx->st0 == STAT_CLI_O_TAB || appctx->st0 == STAT_CLI_O_CLR) &&
3085 appctx->st2 == STAT_ST_LIST) {
3086 appctx->ctx.table.entry->ref_cnt--;
3087 stksess_kill_if_expired(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry);
3088 }
3089 else if (appctx->st0 == STAT_CLI_PRINT_FREE) {
3090 free(appctx->ctx.cli.err);
3091 appctx->ctx.cli.err = NULL;
3092 }
William Lallemand74c24fb2016-11-21 17:18:36 +01003093}
3094
3095/* This function is used to either dump tables states (when action is set
3096 * to STAT_CLI_O_TAB) or clear tables (when action is STAT_CLI_O_CLR).
3097 * It returns 0 if the output buffer is full and it needs to be called
3098 * again, otherwise non-zero.
3099 */
3100static int stats_table_request(struct stream_interface *si, int action)
3101{
3102 struct appctx *appctx = __objt_appctx(si->end);
3103 struct stream *s = si_strm(si);
3104 struct ebmb_node *eb;
3105 int dt;
3106 int skip_entry;
3107 int show = action == STAT_CLI_O_TAB;
3108
3109 /*
3110 * We have 3 possible states in appctx->st2 :
3111 * - STAT_ST_INIT : the first call
3112 * - STAT_ST_INFO : the proxy pointer points to the next table to
3113 * dump, the entry pointer is NULL ;
3114 * - STAT_ST_LIST : the proxy pointer points to the current table
3115 * and the entry pointer points to the next entry to be dumped,
3116 * and the refcount on the next entry is held ;
3117 * - STAT_ST_END : nothing left to dump, the buffer may contain some
3118 * data though.
3119 */
3120
3121 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW))) {
3122 /* in case of abort, remove any refcount we might have set on an entry */
3123 if (appctx->st2 == STAT_ST_LIST) {
3124 appctx->ctx.table.entry->ref_cnt--;
3125 stksess_kill_if_expired(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry);
3126 }
3127 return 1;
3128 }
3129
3130 chunk_reset(&trash);
3131
3132 while (appctx->st2 != STAT_ST_FIN) {
3133 switch (appctx->st2) {
3134 case STAT_ST_INIT:
3135 appctx->ctx.table.proxy = appctx->ctx.table.target;
3136 if (!appctx->ctx.table.proxy)
3137 appctx->ctx.table.proxy = proxy;
3138
3139 appctx->ctx.table.entry = NULL;
3140 appctx->st2 = STAT_ST_INFO;
3141 break;
3142
3143 case STAT_ST_INFO:
3144 if (!appctx->ctx.table.proxy ||
3145 (appctx->ctx.table.target &&
3146 appctx->ctx.table.proxy != appctx->ctx.table.target)) {
3147 appctx->st2 = STAT_ST_END;
3148 break;
3149 }
3150
3151 if (appctx->ctx.table.proxy->table.size) {
3152 if (show && !stats_dump_table_head_to_buffer(&trash, si, appctx->ctx.table.proxy,
3153 appctx->ctx.table.target))
3154 return 0;
3155
3156 if (appctx->ctx.table.target &&
3157 strm_li(s)->bind_conf->level >= ACCESS_LVL_OPER) {
3158 /* dump entries only if table explicitly requested */
3159 eb = ebmb_first(&appctx->ctx.table.proxy->table.keys);
3160 if (eb) {
3161 appctx->ctx.table.entry = ebmb_entry(eb, struct stksess, key);
3162 appctx->ctx.table.entry->ref_cnt++;
3163 appctx->st2 = STAT_ST_LIST;
3164 break;
3165 }
3166 }
3167 }
3168 appctx->ctx.table.proxy = appctx->ctx.table.proxy->next;
3169 break;
3170
3171 case STAT_ST_LIST:
3172 skip_entry = 0;
3173
3174 if (appctx->ctx.table.data_type >= 0) {
3175 /* we're filtering on some data contents */
3176 void *ptr;
3177 long long data;
3178
3179 dt = appctx->ctx.table.data_type;
3180 ptr = stktable_data_ptr(&appctx->ctx.table.proxy->table,
3181 appctx->ctx.table.entry,
3182 dt);
3183
3184 data = 0;
3185 switch (stktable_data_types[dt].std_type) {
3186 case STD_T_SINT:
3187 data = stktable_data_cast(ptr, std_t_sint);
3188 break;
3189 case STD_T_UINT:
3190 data = stktable_data_cast(ptr, std_t_uint);
3191 break;
3192 case STD_T_ULL:
3193 data = stktable_data_cast(ptr, std_t_ull);
3194 break;
3195 case STD_T_FRQP:
3196 data = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
3197 appctx->ctx.table.proxy->table.data_arg[dt].u);
3198 break;
3199 }
3200
3201 /* skip the entry if the data does not match the test and the value */
3202 if ((data < appctx->ctx.table.value &&
3203 (appctx->ctx.table.data_op == STD_OP_EQ ||
3204 appctx->ctx.table.data_op == STD_OP_GT ||
3205 appctx->ctx.table.data_op == STD_OP_GE)) ||
3206 (data == appctx->ctx.table.value &&
3207 (appctx->ctx.table.data_op == STD_OP_NE ||
3208 appctx->ctx.table.data_op == STD_OP_GT ||
3209 appctx->ctx.table.data_op == STD_OP_LT)) ||
3210 (data > appctx->ctx.table.value &&
3211 (appctx->ctx.table.data_op == STD_OP_EQ ||
3212 appctx->ctx.table.data_op == STD_OP_LT ||
3213 appctx->ctx.table.data_op == STD_OP_LE)))
3214 skip_entry = 1;
3215 }
3216
3217 if (show && !skip_entry &&
3218 !stats_dump_table_entry_to_buffer(&trash, si, appctx->ctx.table.proxy,
3219 appctx->ctx.table.entry))
3220 return 0;
3221
3222 appctx->ctx.table.entry->ref_cnt--;
3223
3224 eb = ebmb_next(&appctx->ctx.table.entry->key);
3225 if (eb) {
3226 struct stksess *old = appctx->ctx.table.entry;
3227 appctx->ctx.table.entry = ebmb_entry(eb, struct stksess, key);
3228 if (show)
3229 stksess_kill_if_expired(&appctx->ctx.table.proxy->table, old);
3230 else if (!skip_entry && !appctx->ctx.table.entry->ref_cnt)
3231 stksess_kill(&appctx->ctx.table.proxy->table, old);
3232 appctx->ctx.table.entry->ref_cnt++;
3233 break;
3234 }
3235
3236
3237 if (show)
3238 stksess_kill_if_expired(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry);
3239 else if (!skip_entry && !appctx->ctx.table.entry->ref_cnt)
3240 stksess_kill(&appctx->ctx.table.proxy->table, appctx->ctx.table.entry);
3241
3242 appctx->ctx.table.proxy = appctx->ctx.table.proxy->next;
3243 appctx->st2 = STAT_ST_INFO;
3244 break;
3245
3246 case STAT_ST_END:
3247 appctx->st2 = STAT_ST_FIN;
3248 break;
3249 }
3250 }
3251 return 1;
3252}
3253
3254/* print a line of text buffer (limited to 70 bytes) to <out>. The format is :
3255 * <2 spaces> <offset=5 digits> <space or plus> <space> <70 chars max> <\n>
3256 * which is 60 chars per line. Non-printable chars \t, \n, \r and \e are
3257 * encoded in C format. Other non-printable chars are encoded "\xHH". Original
3258 * lines are respected within the limit of 70 output chars. Lines that are
3259 * continuation of a previous truncated line begin with "+" instead of " "
3260 * after the offset. The new pointer is returned.
3261 */
3262static int dump_text_line(struct chunk *out, const char *buf, int bsize, int len,
3263 int *line, int ptr)
3264{
3265 int end;
3266 unsigned char c;
3267
3268 end = out->len + 80;
3269 if (end > out->size)
3270 return ptr;
3271
3272 chunk_appendf(out, " %05d%c ", ptr, (ptr == *line) ? ' ' : '+');
3273
3274 while (ptr < len && ptr < bsize) {
3275 c = buf[ptr];
3276 if (isprint(c) && isascii(c) && c != '\\') {
3277 if (out->len > end - 2)
3278 break;
3279 out->str[out->len++] = c;
3280 } else if (c == '\t' || c == '\n' || c == '\r' || c == '\e' || c == '\\') {
3281 if (out->len > end - 3)
3282 break;
3283 out->str[out->len++] = '\\';
3284 switch (c) {
3285 case '\t': c = 't'; break;
3286 case '\n': c = 'n'; break;
3287 case '\r': c = 'r'; break;
3288 case '\e': c = 'e'; break;
3289 case '\\': c = '\\'; break;
3290 }
3291 out->str[out->len++] = c;
3292 } else {
3293 if (out->len > end - 5)
3294 break;
3295 out->str[out->len++] = '\\';
3296 out->str[out->len++] = 'x';
3297 out->str[out->len++] = hextab[(c >> 4) & 0xF];
3298 out->str[out->len++] = hextab[c & 0xF];
3299 }
3300 if (buf[ptr++] == '\n') {
3301 /* we had a line break, let's return now */
3302 out->str[out->len++] = '\n';
3303 *line = ptr;
3304 return ptr;
3305 }
3306 }
3307 /* we have an incomplete line, we return it as-is */
3308 out->str[out->len++] = '\n';
3309 return ptr;
3310}
3311
3312/* This function dumps counters from all resolvers section and associated name servers.
3313 * It returns 0 if the output buffer is full and it needs
3314 * to be called again, otherwise non-zero.
3315 */
3316static int stats_dump_resolvers_to_buffer(struct stream_interface *si)
3317{
3318 struct appctx *appctx = __objt_appctx(si->end);
3319 struct dns_resolvers *presolvers;
3320 struct dns_nameserver *pnameserver;
3321
3322 chunk_reset(&trash);
3323
3324 switch (appctx->st2) {
3325 case STAT_ST_INIT:
3326 appctx->st2 = STAT_ST_LIST; /* let's start producing data */
3327 /* fall through */
3328
3329 case STAT_ST_LIST:
3330 if (LIST_ISEMPTY(&dns_resolvers)) {
3331 chunk_appendf(&trash, "No resolvers found\n");
3332 }
3333 else {
3334 list_for_each_entry(presolvers, &dns_resolvers, list) {
3335 if (appctx->ctx.resolvers.ptr != NULL && appctx->ctx.resolvers.ptr != presolvers)
3336 continue;
3337
3338 chunk_appendf(&trash, "Resolvers section %s\n", presolvers->id);
3339 list_for_each_entry(pnameserver, &presolvers->nameserver_list, list) {
3340 chunk_appendf(&trash, " nameserver %s:\n", pnameserver->id);
3341 chunk_appendf(&trash, " sent: %ld\n", pnameserver->counters.sent);
3342 chunk_appendf(&trash, " valid: %ld\n", pnameserver->counters.valid);
3343 chunk_appendf(&trash, " update: %ld\n", pnameserver->counters.update);
3344 chunk_appendf(&trash, " cname: %ld\n", pnameserver->counters.cname);
3345 chunk_appendf(&trash, " cname_error: %ld\n", pnameserver->counters.cname_error);
3346 chunk_appendf(&trash, " any_err: %ld\n", pnameserver->counters.any_err);
3347 chunk_appendf(&trash, " nx: %ld\n", pnameserver->counters.nx);
3348 chunk_appendf(&trash, " timeout: %ld\n", pnameserver->counters.timeout);
3349 chunk_appendf(&trash, " refused: %ld\n", pnameserver->counters.refused);
3350 chunk_appendf(&trash, " other: %ld\n", pnameserver->counters.other);
3351 chunk_appendf(&trash, " invalid: %ld\n", pnameserver->counters.invalid);
3352 chunk_appendf(&trash, " too_big: %ld\n", pnameserver->counters.too_big);
3353 chunk_appendf(&trash, " truncated: %ld\n", pnameserver->counters.truncated);
3354 chunk_appendf(&trash, " outdated: %ld\n", pnameserver->counters.outdated);
3355 }
3356 }
3357 }
3358
3359 /* display response */
3360 if (bi_putchk(si_ic(si), &trash) == -1) {
3361 /* let's try again later from this session. We add ourselves into
3362 * this session's users so that it can remove us upon termination.
3363 */
3364 si->flags |= SI_FL_WAIT_ROOM;
3365 return 0;
3366 }
3367
3368 appctx->st2 = STAT_ST_FIN;
3369 /* fall through */
3370
3371 default:
3372 appctx->st2 = STAT_ST_FIN;
3373 return 1;
3374 }
3375}
3376
3377/* This function dumps all captured errors onto the stream interface's
3378 * read buffer. It returns 0 if the output buffer is full and it needs
3379 * to be called again, otherwise non-zero.
3380 */
3381static int stats_dump_errors_to_buffer(struct stream_interface *si)
3382{
3383 struct appctx *appctx = __objt_appctx(si->end);
3384 extern const char *monthname[12];
3385
3386 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
3387 return 1;
3388
3389 chunk_reset(&trash);
3390
3391 if (!appctx->ctx.errors.px) {
3392 /* the function had not been called yet, let's prepare the
3393 * buffer for a response.
3394 */
3395 struct tm tm;
3396
3397 get_localtime(date.tv_sec, &tm);
3398 chunk_appendf(&trash, "Total events captured on [%02d/%s/%04d:%02d:%02d:%02d.%03d] : %u\n",
3399 tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900,
3400 tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(date.tv_usec/1000),
3401 error_snapshot_id);
3402
3403 if (bi_putchk(si_ic(si), &trash) == -1) {
3404 /* Socket buffer full. Let's try again later from the same point */
3405 si_applet_cant_put(si);
3406 return 0;
3407 }
3408
3409 appctx->ctx.errors.px = proxy;
3410 appctx->ctx.errors.buf = 0;
3411 appctx->ctx.errors.bol = 0;
3412 appctx->ctx.errors.ptr = -1;
3413 }
3414
3415 /* we have two inner loops here, one for the proxy, the other one for
3416 * the buffer.
3417 */
3418 while (appctx->ctx.errors.px) {
3419 struct error_snapshot *es;
3420
3421 if (appctx->ctx.errors.buf == 0)
3422 es = &appctx->ctx.errors.px->invalid_req;
3423 else
3424 es = &appctx->ctx.errors.px->invalid_rep;
3425
3426 if (!es->when.tv_sec)
3427 goto next;
3428
3429 if (appctx->ctx.errors.iid >= 0 &&
3430 appctx->ctx.errors.px->uuid != appctx->ctx.errors.iid &&
3431 es->oe->uuid != appctx->ctx.errors.iid)
3432 goto next;
3433
3434 if (appctx->ctx.errors.ptr < 0) {
3435 /* just print headers now */
3436
3437 char pn[INET6_ADDRSTRLEN];
3438 struct tm tm;
3439 int port;
3440
3441 get_localtime(es->when.tv_sec, &tm);
3442 chunk_appendf(&trash, " \n[%02d/%s/%04d:%02d:%02d:%02d.%03d]",
3443 tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900,
3444 tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(es->when.tv_usec/1000));
3445
3446 switch (addr_to_str(&es->src, pn, sizeof(pn))) {
3447 case AF_INET:
3448 case AF_INET6:
3449 port = get_host_port(&es->src);
3450 break;
3451 default:
3452 port = 0;
3453 }
3454
3455 switch (appctx->ctx.errors.buf) {
3456 case 0:
3457 chunk_appendf(&trash,
3458 " frontend %s (#%d): invalid request\n"
3459 " backend %s (#%d)",
3460 appctx->ctx.errors.px->id, appctx->ctx.errors.px->uuid,
3461 (es->oe->cap & PR_CAP_BE) ? es->oe->id : "<NONE>",
3462 (es->oe->cap & PR_CAP_BE) ? es->oe->uuid : -1);
3463 break;
3464 case 1:
3465 chunk_appendf(&trash,
3466 " backend %s (#%d): invalid response\n"
3467 " frontend %s (#%d)",
3468 appctx->ctx.errors.px->id, appctx->ctx.errors.px->uuid,
3469 es->oe->id, es->oe->uuid);
3470 break;
3471 }
3472
3473 chunk_appendf(&trash,
3474 ", server %s (#%d), event #%u\n"
3475 " src %s:%d, session #%d, session flags 0x%08x\n"
3476 " HTTP msg state %d, msg flags 0x%08x, tx flags 0x%08x\n"
3477 " HTTP chunk len %lld bytes, HTTP body len %lld bytes\n"
3478 " buffer flags 0x%08x, out %d bytes, total %lld bytes\n"
3479 " pending %d bytes, wrapping at %d, error at position %d:\n \n",
3480 es->srv ? es->srv->id : "<NONE>", es->srv ? es->srv->puid : -1,
3481 es->ev_id,
3482 pn, port, es->sid, es->s_flags,
3483 es->state, es->m_flags, es->t_flags,
3484 es->m_clen, es->m_blen,
3485 es->b_flags, es->b_out, es->b_tot,
3486 es->len, es->b_wrap, es->pos);
3487
3488 if (bi_putchk(si_ic(si), &trash) == -1) {
3489 /* Socket buffer full. Let's try again later from the same point */
3490 si_applet_cant_put(si);
3491 return 0;
3492 }
3493 appctx->ctx.errors.ptr = 0;
3494 appctx->ctx.errors.sid = es->sid;
3495 }
3496
3497 if (appctx->ctx.errors.sid != es->sid) {
3498 /* the snapshot changed while we were dumping it */
3499 chunk_appendf(&trash,
3500 " WARNING! update detected on this snapshot, dump interrupted. Please re-check!\n");
3501 if (bi_putchk(si_ic(si), &trash) == -1) {
3502 si_applet_cant_put(si);
3503 return 0;
3504 }
3505 goto next;
3506 }
3507
3508 /* OK, ptr >= 0, so we have to dump the current line */
3509 while (es->buf && appctx->ctx.errors.ptr < es->len && appctx->ctx.errors.ptr < global.tune.bufsize) {
3510 int newptr;
3511 int newline;
3512
3513 newline = appctx->ctx.errors.bol;
3514 newptr = dump_text_line(&trash, es->buf, global.tune.bufsize, es->len, &newline, appctx->ctx.errors.ptr);
3515 if (newptr == appctx->ctx.errors.ptr)
3516 return 0;
3517
3518 if (bi_putchk(si_ic(si), &trash) == -1) {
3519 /* Socket buffer full. Let's try again later from the same point */
3520 si_applet_cant_put(si);
3521 return 0;
3522 }
3523 appctx->ctx.errors.ptr = newptr;
3524 appctx->ctx.errors.bol = newline;
3525 };
3526 next:
3527 appctx->ctx.errors.bol = 0;
3528 appctx->ctx.errors.ptr = -1;
3529 appctx->ctx.errors.buf++;
3530 if (appctx->ctx.errors.buf > 1) {
3531 appctx->ctx.errors.buf = 0;
3532 appctx->ctx.errors.px = appctx->ctx.errors.px->next;
3533 }
3534 }
3535
3536 /* dump complete */
3537 return 1;
3538}
3539
3540/* This function dumps all environmnent variables to the buffer. It returns 0
3541 * if the output buffer is full and it needs to be called again, otherwise
3542 * non-zero. Dumps only one entry if st2 == STAT_ST_END.
3543 */
3544static int stats_dump_env_to_buffer(struct stream_interface *si)
3545{
3546 struct appctx *appctx = __objt_appctx(si->end);
3547
3548 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
3549 return 1;
3550
3551 chunk_reset(&trash);
3552
3553 /* we have two inner loops here, one for the proxy, the other one for
3554 * the buffer.
3555 */
3556 while (*appctx->ctx.env.var) {
3557 chunk_printf(&trash, "%s\n", *appctx->ctx.env.var);
3558
3559 if (bi_putchk(si_ic(si), &trash) == -1) {
3560 si_applet_cant_put(si);
3561 return 0;
3562 }
3563 if (appctx->st2 == STAT_ST_END)
3564 break;
3565 appctx->ctx.env.var++;
3566 }
3567
3568 /* dump complete */
3569 return 1;
3570}
3571
3572/* parse the "level" argument on the bind lines */
3573static int bind_parse_level(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
3574{
3575 if (!*args[cur_arg + 1]) {
3576 memprintf(err, "'%s' : missing level", args[cur_arg]);
3577 return ERR_ALERT | ERR_FATAL;
3578 }
3579
3580 if (!strcmp(args[cur_arg+1], "user"))
3581 conf->level = ACCESS_LVL_USER;
3582 else if (!strcmp(args[cur_arg+1], "operator"))
3583 conf->level = ACCESS_LVL_OPER;
3584 else if (!strcmp(args[cur_arg+1], "admin"))
3585 conf->level = ACCESS_LVL_ADMIN;
3586 else {
3587 memprintf(err, "'%s' only supports 'user', 'operator', and 'admin' (got '%s')",
3588 args[cur_arg], args[cur_arg+1]);
3589 return ERR_ALERT | ERR_FATAL;
3590 }
3591
3592 return 0;
3593}
3594
3595static struct applet cli_applet = {
3596 .obj_type = OBJ_TYPE_APPLET,
3597 .name = "<CLI>", /* used for logging */
3598 .fct = cli_io_handler,
3599 .release = cli_release_handler,
3600};
3601
3602static struct cfg_kw_list cfg_kws = {ILH, {
3603 { CFG_GLOBAL, "stats", stats_parse_global },
3604 { 0, NULL, NULL },
3605}};
3606
3607static struct bind_kw_list bind_kws = { "STAT", { }, {
3608 { "level", bind_parse_level, 1 }, /* set the unix socket admin level */
3609 { NULL, NULL, 0 },
3610}};
3611
3612__attribute__((constructor))
3613static void __dumpstats_module_init(void)
3614{
3615 cfg_register_keywords(&cfg_kws);
3616 bind_register_keywords(&bind_kws);
3617}
3618
3619/*
3620 * Local variables:
3621 * c-indent-level: 8
3622 * c-basic-offset: 8
3623 * End:
3624 */