Welcome! Log In Create A New Profile

Advanced

[PATCH] MEDIUM: stats: Add `show events` command.

Posted by Aman Gupta 
Aman Gupta
[PATCH] MEDIUM: stats: Add `show events` command.
March 23, 2012 09:40PM
Tracing TCP connections that make hops through haproxy is currently very
challenging. To get a list of proxied connection pairs inside haproxy,
one must use `show sess` to dump the session table, grab the fd pairs
for each session, resolve those to inodes via the process's file
descriptor table, and then resolve those inodes to addresses via the TCP
connection table. This is quite cumbersome and slow (especially when the
TCP connection table is huge), and does not scale when there are
hundreds or thousands of connections happening per second.

This patch adds a new `show events` command to the stats socket, which
streams events when sessions are established or destroyed. This allows
any interested party to subscribe to these events and maintain their
own session table about the state inside haproxy. This data can then be
used to augment data collected from other sources (like pcap), to follow
a connection through all the hops it makes.
---
doc/configuration.txt | 11 ++++
include/proto/dumpstats.h | 4 ++
include/types/session.h | 3 +
src/dumpstats.c | 119 +++++++++++++++++++++++++++++++++++++++++++-
src/session.c | 4 ++
5 files changed, 138 insertions(+), 3 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 36f68a5..b586251 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -8526,6 +8526,17 @@ show errors [<iid>]
is the slash ('/') in header name "header/bizarre", which is not a valid
HTTP character for a header name.

+show events
+ Dump a stream of events about sessions as they are created and destroyed.
+ Streaming will continue until a new command is sent over the stats socket.
+
+ Example:
+ >>> $ echo "show show events" | socat stdio,ignoreeof /tmp/sock1
+ + 1 127.0.0.1:50869 127.0.0.1:9418 127.0.0.1:50870 127.0.0.1:6000
+ - 1
+ + 2 127.0.0.1:50874 127.0.0.1:9418 127.0.0.1:50875 127.0.0.1:6000
+ - 2
+
show info
Dump info about haproxy status on current process.

diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index 4728121..658fab7 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -53,6 +53,8 @@
#define STAT_CLI_O_SESS 6 /* dump sessions */
#define STAT_CLI_O_ERR 7 /* dump errors */

+#define STAT_CLI_EVENTS 8 /* event stream */
+
/* status codes (strictly 4 chars) used in the URL to display a message */
#define STAT_STATUS_UNKN "UNKN" /* an unknown error occured, shouldn't happen */
#define STAT_STATUS_DONE "DONE" /* the action is successful */
@@ -63,6 +65,8 @@

int stats_sock_parse_request(struct stream_interface *si, char *line);
void stats_io_handler(struct stream_interface *si);
+void stats_event_new_session(struct session *s);
+void stats_event_end_session(struct session *s);
int stats_dump_raw_to_buffer(struct session *s, struct buffer *rep);
int stats_dump_http(struct session *s, struct buffer *rep, struct uri_auth *uri);
int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri);
diff --git a/include/types/session.h b/include/types/session.h
index 20d2a2c..a296dcd 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -231,6 +231,9 @@ struct session {
int bol; /* pointer to beginning of current line */
} errors;
struct {
+ struct list list;
+ } events;
+ struct {
const char *msg; /* pointer to a persistent message to be returned in PRINT state */
} cli;
} data_ctx; /* used by stats I/O handlers to dump the stats */
diff --git a/src/dumpstats.c b/src/dumpstats.c
index f680134..959b114 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -52,6 +52,23 @@
#include <proto/stream_interface.h>
#include <proto/task.h>

+static struct list stats_event_listeners = LIST_HEAD_INIT(stats_event_listeners);
+
+static inline void stats_event_listener_add(struct session *s)
+{
+ LIST_ADDQ(&stats_event_listeners, &s->data_ctx.events.list);
+}
+
+static inline void stats_event_listener_remove(struct session *s)
+{
+ if (!LIST_ISEMPTY(&stats_event_listeners)) {
+ LIST_DEL(&s->data_ctx.events.list);
+ }
+
+ /* Re-initialize stats output */
+ memset(&s->data_ctx.stats, 0, sizeof(s->data_ctx.stats));
+}
+
const char stats_sock_usage_msg[] =
"Unknown command. Please enter one of the following commands only :\n"
" clear counters : clear max statistics counters (add 'all' for all counters)\n"
@@ -62,6 +79,7 @@ const char stats_sock_usage_msg[] =
" show stat : report counters for each proxy and server\n"
" show errors : report last request and response errors for each proxy\n"
" show sess [id] : report the list of current sessions or dump this session\n"
+ " show events : stream events about proxied sessions\n"
" get weight : report a server's current weight\n"
" set weight : change a server's weight\n"
" set timeout : change a timeout setting\n"
@@ -358,7 +376,11 @@ int stats_sock_parse_request(struct stream_interface *si, char *line)
s->data_state = DATA_ST_INIT;
si->st0 = STAT_CLI_O_ERR; // stats_dump_errors_to_buffer
}
- else { /* neither "stat" nor "info" nor "sess" nor "errors"*/
+ else if (strcmp(args[1], "events") == 0) {
+ si->st0 = STAT_CLI_EVENTS;
+ stats_event_listener_add(s);
+ }
+ else { /* neither "stat" nor "info" nor "sess" nor "errors" nor "events" */
return 0;
}
}
@@ -681,6 +703,88 @@ int stats_sock_parse_request(struct stream_interface *si, char *line)
return 1;
}

+void stats_event_new_session(struct session *s)
+{
+ struct session *curr;
+ struct stream_interface *si;
+ int ret;
+
+ char addrs[4][INET6_ADDRSTRLEN + strlen(":65535") + 1] = {"","","",""};
+ int i, fd, port;
+ const void *addr;
+ struct sockaddr_storage sock;
+ socklen_t addr_size;
+
+ for(i = 0; i < 4; i++) {
+ fd = s->si[ i/2 ].fd;
+
+ addr_size = sizeof(sock);
+ if (!(i%2==0 ? getpeername : getsockname)(fd, (struct sockaddr *)&sock, &addr_size)) {
+ switch (sock.ss_family) {
+ case AF_INET:
+ addr = (const void *)&((struct sockaddr_in *)&sock)->sin_addr;
+ port = ntohs(((struct sockaddr_in *)&sock)->sin_port);
+ inet_ntop(sock.ss_family, addr, addrs, sizeof(addrs));
+ snprintf(addrs, sizeof(addrs), "%s:%d", addrs, port);
+ break;
+
+ case AF_INET6:
+ addr = (const void *)&((struct sockaddr_in6 *)&sock)->sin6_addr;
+ port = ntohs(((struct sockaddr_in6 *)&sock)->sin6_port);
+ inet_ntop(sock.ss_family, addr, addrs, sizeof(addrs));
+ snprintf(addrs, sizeof(addrs), "%s:%d", addrs, port);
+ break;
+
+ case AF_UNIX:
+ default:
+ sprintf(addrs, "%s", "unknown");
+ }
+ }
+ }
+
+ if (LIST_ISEMPTY(&stats_event_listeners))
+ return;
+
+ snprintf(trash, sizeof(trash), "+ %u %s %s %s %s\n",
+ s->uniq_id,
+ addrs[0], // inbound peer
+ addrs[1], // inbound sock
+ addrs[3], // outbound sock
+ addrs[2] // outbound peer
+ );
+
+ list_for_each_entry(curr, &stats_event_listeners, data_ctx.events.list) {
+ si = &curr->si[1];
+
+ ret = buffer_feed(si->ib, trash);
+ if (ret == -1 && si->owner) {
+ si->ib->flags |= BF_SEND_DONTWAIT;
+ task_wakeup(si->owner, TASK_WOKEN_MSG);
+ }
+ }
+}
+
+void stats_event_end_session(struct session *s)
+{
+ struct session *curr;
+ struct stream_interface *si;
+ int ret;
+
+ if (LIST_ISEMPTY(&stats_event_listeners))
+ return;
+
+ snprintf(trash, sizeof(trash), "- %u\n", s->uniq_id);
+ list_for_each_entry(curr, &stats_event_listeners, data_ctx.events.list) {
+ si = &curr->si[1];
+
+ ret = buffer_feed(si->ib, trash);
+ if (ret == -1 && si->owner) {
+ si->ib->flags |= BF_SEND_DONTWAIT;
+ task_wakeup(si->owner, TASK_WOKEN_MSG);
+ }
+ }
+}
+
/* This I/O handler runs as an applet embedded in a stream interface. It is
* used to processes I/O from/to the stats unix socket. The system relies on a
* state machine handling requests and various responses. We read a request,
@@ -714,7 +818,7 @@ void stats_io_handler(struct stream_interface *si)
si->shutw(si);
break;
}
- else if (si->st0 == STAT_CLI_GETREQ) {
+ else if (si->st0 == STAT_CLI_GETREQ || si->st0 == STAT_CLI_EVENTS) {
/* ensure we have some output room left in the event we
* would want to return some info right after parsing.
*/
@@ -754,7 +858,11 @@ void stats_io_handler(struct stream_interface *si)

trash[len] = '\0';

+ if (si->st0 == STAT_CLI_EVENTS)
+ stats_event_listener_remove(s);
+
si->st0 = STAT_CLI_PROMPT;
+
if (len) {
if (strcmp(trash, "quit") == 0) {
si->st0 = STAT_CLI_END;
@@ -840,7 +948,7 @@ void stats_io_handler(struct stream_interface *si)
if ((res->flags & BF_SHUTR) && (si->state == SI_ST_EST) && (si->st0 != STAT_CLI_GETREQ)) {
DPRINTF(stderr, "%s@%d: si to buf closed. req=%08x, res=%08x, st=%d\n",
__FUNCTION__, __LINE__, req->flags, res->flags, si->state);
- /* Other size has closed, let's abort if we have no more processing to do
+ /* Other side has closed, let's abort if we have no more processing to do
* and nothing more to consume. This is comparable to a broken pipe, so
* we forward the close to the request side so that it flows upstream to
* the client.
@@ -866,12 +974,17 @@ void stats_io_handler(struct stream_interface *si)
si->ib->rex = TICK_ETERNITY;
si->ob->wex = TICK_ETERNITY;

+ /* no timeouts when streaming events */
+ if (si->st0 == STAT_CLI_EVENTS)
+ s->req->rex = TICK_ETERNITY;
+
out:
DPRINTF(stderr, "%s@%d: st=%d, rqf=%x, rpf=%x, rql=%d, rqs=%d, rl=%d, rs=%d\n",
__FUNCTION__, __LINE__,
si->state, req->flags, res->flags, req->l, req->send_max, res->l, res->send_max);

if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) {
+ stats_event_listener_remove(s);
/* check that we have released everything then unregister */
stream_int_unregister_handler(si);
}
diff --git a/src/session.c b/src/session.c
index 6906656..ae100db 100644
--- a/src/session.c
+++ b/src/session.c
@@ -350,6 +350,8 @@ void sess_establish(struct session *s, struct stream_interface *si)
rep->analysers |= s->fe->fe_rsp_ana | s->be->be_rsp_ana;
rep->flags |= BF_READ_ATTACHED; /* producer is now attached */
req->wex = TICK_ETERNITY;
+
+ stats_event_new_session(s);
}

/* Update stream interface status for input states SI_ST_ASS, SI_ST_QUE, SI_ST_TAR.
@@ -1655,6 +1657,8 @@ resync_stream_interface:
s->do_log(s);
}

+ stats_event_end_session(s);
+
/* the task MUST not be in the run queue anymore */
session_free(s);
task_delete(t);
--
1.7.5.3
Willy Tarreau
Re: [PATCH] MEDIUM: stats: Add `show events` command.
March 24, 2012 09:50PM
Hi Aman,

On Fri, Mar 23, 2012 at 01:33:30PM -0700, Aman Gupta wrote:
> Tracing TCP connections that make hops through haproxy is currently very
> challenging. To get a list of proxied connection pairs inside haproxy,
> one must use `show sess` to dump the session table, grab the fd pairs
> for each session, resolve those to inodes via the process's file
> descriptor table, and then resolve those inodes to addresses via the TCP
> connection table. This is quite cumbersome and slow (especially when the
> TCP connection table is huge), and does not scale when there are
> hundreds or thousands of connections happening per second.

Agreed. For this reason we've recently extended the log format to optionally
include new fields such as the outgoing source IP:port. I would not have
expected another request to come that soon :-)

> This patch adds a new `show events` command to the stats socket, which
> streams events when sessions are established or destroyed. This allows
> any interested party to subscribe to these events and maintain their
> own session table about the state inside haproxy. This data can then be
> used to augment data collected from other sources (like pcap), to follow
> a connection through all the hops it makes.

This is really nice that you did that, as it's been on my todo list for
a long time. But I have comments on this before I can merge it :

1) I'd rather use "debug sessions" than "show events" because till now
"show anything" only lists a finite list, and "events" can be a lot
of things.

2) it looks to me like the stats socket is still usable in parallel to the
command. If so, we could as well as an "undebug all" command to stop
the debugging. But I'm not requesting that for now, it's more out of
curiosity.

3) I'd very slightly change the output format to include delimiters. Maybe
something like this :

+ 1 127.0.0.1:50869 - 127.0.0.1:9418 | 127.0.0.1:50870 - 127.0.0.1:6000

instead of :

+ 1 127.0.0.1:50869 127.0.0.1:9418 127.0.0.1:50870 127.0.0.1:6000

4) Don't you think it would be useful to see a bit more states ? I would
personally like to see 3 steps instead of 2 per session :
- incoming session accepted
- forward to server
- closed session to client

We could then have a single letter char instead of +/- which would report
the state (A/F/C) :

A 1 127.0.0.1:50869 - 127.0.0.1:9418
F 1 127.0.0.1:50869 - 127.0.0.1:9418 | 127.0.0.1:50870 - 127.0.0.1:6000
C 1 127.0.0.1:50869 - 127.0.0.1:9418

> diff --git a/src/dumpstats.c b/src/dumpstats.c
> index f680134..959b114 100644
> --- a/src/dumpstats.c
> +++ b/src/dumpstats.c
(...)
> +void stats_event_new_session(struct session *s)
> +{
> + struct session *curr;
> + struct stream_interface *si;
> + int ret;
> +
> + char addrs[4][INET6_ADDRSTRLEN + strlen(":65535") + 1] = {"","","",""};
> + int i, fd, port;
> + const void *addr;
> + struct sockaddr_storage sock;
> + socklen_t addr_size;
> +
> + for(i = 0; i < 4; i++) {
> + fd = s->si[ i/2 ].fd;
> +
> + addr_size = sizeof(sock);
> + if (!(i%2==0 ? getpeername : getsockname)(fd, (struct sockaddr *)&sock, &addr_size)) {

Here it's a waste of CPU cycles to call getpeername/getsockname 4 times
for addresses we generally already have. You have everything in the
stream_interface's addr (.from, .to). The local address is not always
filled, you have to check the session's flags for SN_FRT_ADDR_SET and
SN_BCK_ADDR_SET and call the associated functions accordingly prior
to use the addresses. The advantage is that the getpeername/getsockname
are already performed once in all of a session's life.

> + switch (sock.ss_family) {
> + case AF_INET:
> + addr = (const void *)&((struct sockaddr_in *)&sock)->sin_addr;
> + port = ntohs(((struct sockaddr_in *)&sock)->sin_port);
> + inet_ntop(sock.ss_family, addr, addrs, sizeof(addrs));
> + snprintf(addrs, sizeof(addrs), "%s:%d", addrs, port);
> + break;
> +
> + case AF_INET6:
> + addr = (const void *)&((struct sockaddr_in6 *)&sock)->sin6_addr;
> + port = ntohs(((struct sockaddr_in6 *)&sock)->sin6_port);
> + inet_ntop(sock.ss_family, addr, addrs, sizeof(addrs));
> + snprintf(addrs, sizeof(addrs), "%s:%d", addrs, port);
> + break;
> +
> + case AF_UNIX:
> + default:
> + sprintf(addrs, "%s", "unknown");

In case of UNIX sockets, check how it's done in the logs, in short we log
"unix:$listener_id" on the frontend and nothing right now on the backend,
but if one day we support it it will probably be the destination socket
path.

> + }
> + }
> + }
> +
> + if (LIST_ISEMPTY(&stats_event_listeners))
> + return;

Hmmm.. this should have been put at the top of the function in order
to save this expensive work when nobody is listening.

(...)
> @@ -866,12 +974,17 @@ void stats_io_handler(struct stream_interface *si)
> si->ib->rex = TICK_ETERNITY;
> si->ob->wex = TICK_ETERNITY;
>
> + /* no timeouts when streaming events */
> + if (si->st0 == STAT_CLI_EVENTS)
> + s->req->rex = TICK_ETERNITY;
> +

This is not appropriate in my opinion. 1) it's a specific use, and 2)
it's already possible to set the timeout on the stats command line,
so better increase the CLI timeout before starting the dump.

> out:
> DPRINTF(stderr, "%s@%d: st=%d, rqf=%x, rpf=%x, rql=%d, rqs=%d, rl=%d, rs=%d\n",
> __FUNCTION__, __LINE__,
> si->state, req->flags, res->flags, req->l, req->send_max, res->l, res->send_max);
>
> if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) {
> + stats_event_listener_remove(s);
> /* check that we have released everything then unregister */
> stream_int_unregister_handler(si);

Normally you don't need this, you just need to add a .release callback
in cli_applet (dumpstats.c) which does the work.

> diff --git a/src/session.c b/src/session.c
> index 6906656..ae100db 100644
> --- a/src/session.c
> +++ b/src/session.c
> @@ -350,6 +350,8 @@ void sess_establish(struct session *s, struct stream_interface *si)
> rep->analysers |= s->fe->fe_rsp_ana | s->be->be_rsp_ana;
> rep->flags |= BF_READ_ATTACHED; /* producer is now attached */
> req->wex = TICK_ETERNITY;
> +
> + stats_event_new_session(s);

This call is expensive on every session establishment. I'd really like
to see it guarded by "if (!LIST_ISEMPTY(&stats_event_listeners))".
Alternatively you can rename your existing function __stats_event_new_session()
and have stats_event_new_session() be inline and add perform the test if you
prefer, I don't care. What I don't want is to call a function most of the
time for nothing.

> }
>
> /* Update stream interface status for input states SI_ST_ASS, SI_ST_QUE, SI_ST_TAR.
> @@ -1655,6 +1657,8 @@ resync_stream_interface:
> s->do_log(s);
> }
>
> + stats_event_end_session(s);
> +

Same here, obviously.

Thanks for doing this, it looks really appealing ! I'm impatient to
see what realtime info everyone will think about ! I suspect that
filtering on a frontend will be among the first requests in the
wish list, quickly followed by source-mask filtering !

Willy
Aman Gupta
Re: [PATCH] MEDIUM: stats: Add `show events` command.
March 25, 2012 08:00AM
> Agreed. For this reason we've recently extended the log format to optionally
> include new fields such as the outgoing source IP:port. I would not have
> expected another request to come that soon :-)

Quite the coincidence. I was working in a haproxy-1.4 checkout, so I
missed the new logging commits.

> This is really nice that you did that, as it's been on my todo list for
> a long time. But I have comments on this before I can merge it :

Awesome, thanks. This is my first time working with the haproxy
codebase and I wasn't sure if I was on the right track. Definitely
appreciate the feedback.

>
> 1) I'd rather use "debug sessions" than "show events" because till now
>   "show anything" only lists a finite list, and "events" can be a lot
>   of things.

I was initially using "stream", then "events". I considered "show sess
events" but "show events" was simpler to implement.

I agree re-using the "show" prefix is confusing, so I'll switch to
"debug sessions" (or "stream sessions"?)

>
> 2) it looks to me like the stats socket is still usable in parallel to the
>   command. If so, we could as well as an "undebug all" command to stop
>   the debugging. But I'm not requesting that for now, it's more out of
>   curiosity.

I wasn't quite sure what to do here either. Initially, once streaming
was enabled you would have to reconnect to do anything else. In the
current implementation, if a new command is received (or invalid input
with a newline), streaming is turned off and we go back to processing
commands.

Right now it doesn't make sense to use any other commands while
streaming, because it would be possible to separate the output. But if
we plan on adding more streaming/debug subcommands, then an "undebug
all" makes a lot of sense.

While streaming, it probably makes sense to disable commands like
"show sess" so they do not interfere with the streaming output.

> I would personally like to see 3 steps instead of 2 per session :
>     - incoming session accepted
>     - forward to server
>     - closed session to client
>
>   We could then have a single letter char instead of +/- which would report
>   the state (A/F/C) :
>
>    A 1 127.0.0.1:50869 - 127.0.0.1:9418
>    F 1 127.0.0.1:50869 - 127.0.0.1:9418 | 127.0.0.1:50870 - 127.0.0.1:6000
>    C 1 127.0.0.1:50869 - 127.0.0.1:9418

I like this.

I'm not sure how far to go in this direction, though. It would be
useful to include a lot of the details from the "show sess" command-
at the very least frontend/backend/srv names. It probably also makes
sense to include the id that "show sess <id>" uses, so you can lookup
detailed session information from an incoming event.

For my use case the bare minimum I needed was the proxied and
disconnected events, so that's where I started.

>> diff --git a/src/dumpstats.c b/src/dumpstats.c
>> index f680134..959b114 100644
>> --- a/src/dumpstats.c
>> +++ b/src/dumpstats.c
> (...)
>> +void stats_event_new_session(struct session *s)
>> +{
>> +     struct session *curr;
>> +     struct stream_interface *si;
>> +     int ret;
>> +
>> +     char addrs[4][INET6_ADDRSTRLEN + strlen(":65535") + 1] = {"","","",""};
>> +     int i, fd, port;
>> +     const void *addr;
>> +     struct sockaddr_storage sock;
>> +     socklen_t addr_size;
>> +
>> +     for(i = 0; i < 4; i++) {
>> +             fd = s->si[ i/2 ].fd;
>> +
>> +             addr_size = sizeof(sock);
>> +             if (!(i%2==0 ? getpeername : getsockname)(fd, (struct sockaddr *)&sock, &addr_size)) {
>
> Here it's a waste of CPU cycles to call getpeername/getsockname 4 times
> for addresses we generally already have. You have everything in the
> stream_interface's addr (.from, .to). The local address is not always
> filled, you have to check the session's flags for SN_FRT_ADDR_SET and
> SN_BCK_ADDR_SET and call the associated functions accordingly prior
> to use the addresses. The advantage is that the getpeername/getsockname
> are already performed once in all of a session's life.

I saw cli_addr was available in the session, but wasn't sure about the
others. I'll fix this up now that I know where the addresses are
stored, and call getpeername only once when necessary.

>
>> +                     switch (sock.ss_family) {
>> +                     case AF_INET:
>> +                             addr = (const void *)&((struct sockaddr_in *)&sock)->sin_addr;
>> +                             port = ntohs(((struct sockaddr_in *)&sock)->sin_port);
>> +                             inet_ntop(sock..ss_family, addr, addrs, sizeof(addrs));
>> +                             snprintf(addrs, sizeof(addrs), "%s:%d", addrs, port);
>> +                             break;
>> +
>> +                     case AF_INET6:
>> +                             addr = (const void *)&((struct sockaddr_in6 *)&sock)->sin6_addr;
>> +                             port = ntohs(((struct sockaddr_in6 *)&sock)->sin6_port);
>> +                             inet_ntop(sock..ss_family, addr, addrs, sizeof(addrs));
>> +                             snprintf(addrs, sizeof(addrs), "%s:%d", addrs, port);
>> +                             break;
>> +
>> +                     case AF_UNIX:
>> +                     default:
>> +                             sprintf(addrs, "%s", "unknown");
>
> In case of UNIX sockets, check how it's done in the logs, in short we log
> "unix:$listener_id" on the frontend and nothing right now on the backend,
> but if one day we support it it will probably be the destination socket
> path.

Cool. I'll copy the format from the logging code.

>
>> +                     }
>> +             }
>> +     }
>> +
>> +     if (LIST_ISEMPTY(&stats_event_listeners))
>> +             return;
>
> Hmmm.. this should have been put at the top of the function in order
> to save this expensive work when nobody is listening.

Yep, I caught this yesterday in some refactoring and it's already fixed.

>> +     /* no timeouts when streaming events */
>> +     if (si->st0 == STAT_CLI_EVENTS)
>> +             s->req->rex = TICK_ETERNITY;
>> +
>
> This is not appropriate in my opinion. 1) it's a specific use, and 2)
> it's already possible to set the timeout on the stats command line,
> so better increase the CLI timeout before starting the dump.

Is it possible to use "set timeout cli" to set an infinite timeout?

I'm not convinced it makes sense to have a timeout when streaming, but
if its possible to disable it beforehand I guess that's ok.

>
>>   out:
>>       DPRINTF(stderr, "%s@%d: st=%d, rqf=%x, rpf=%x, rql=%d, rqs=%d, rl=%d, rs=%d\n",
>>               __FUNCTION__, __LINE__,
>>               si->state, req->flags, res->flags, req->l, req->send_max, res->l, res->send_max);
>>
>>       if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) {
>> +             stats_event_listener_remove(s);
>>               /* check that we have released everything then unregister */
>>               stream_int_unregister_handler(si);
>
> Normally you don't need this, you just need to add a .release callback
> in cli_applet (dumpstats.c) which does the work.

I can't seem to find the release callback you're referring to.

>
>> diff --git a/src/session.c b/src/session.c
>> index 6906656..ae100db 100644
>> --- a/src/session.c
>> +++ b/src/session.c
>> @@ -350,6 +350,8 @@ void sess_establish(struct session *s, struct stream_interface *si)
>>       rep->analysers |= s->fe->fe_rsp_ana | s->be->be_rsp_ana;
>>       rep->flags |= BF_READ_ATTACHED; /* producer is now attached */
>>       req->wex = TICK_ETERNITY;
>> +
>> +     stats_event_new_session(s);
>
> This call is expensive on every session establishment. I'd really like
> to see it guarded by "if (!LIST_ISEMPTY(&stats_event_listeners))".
> Alternatively you can rename your existing function __stats_event_new_session()
> and have stats_event_new_session() be inline and add perform the test if you
> prefer, I don't care. What I don't want is to call a function most of the
> time for nothing.

I fixed this yesterday by adding an enabled flag and checking if
(unlikely(stats_event_enabled)) before the function call.

I like your idea of an inline wrapper, though.

> Thanks for doing this, it looks really appealing ! I'm impatient to
> see what realtime info everyone will think about ! I suspect that
> filtering on a frontend will be among the first requests in the
> wish list, quickly followed by source-mask filtering !

Thanks for taking the time to code review. I'm glad this is something
you think is useful and worth merging upstream.

I did a lot of cleanup yesterday, to refactor and bring the code in
line with the style guide. My branch is at
https://github.com/tmm1/haproxy/compare/session-events (or
https://github.com/tmm1/haproxy/compare/session-events.diff for just
the patch).

I'll incorporate your fixes above and email another patch for review.

As far as filtering, I already started on a fe/be filter with the
"show events [<name>]" syntax. This uses strcmp though, since I wasn't
sure how best to store each user's filters.
(https://github.com/tmm1/haproxy/compare/session-events...session-events-filter).

Filtering by event type would also be interesting, especially as the
number of events increases.

Finally, one thing I struggled with was the linked list of event
listeners. It went through a couple iterations and I think I have it
implemented sanely now, but if you could take a look and make sure I'm
not doing anything stupid that would be great.

Aman

>
> Willy
>
Willy Tarreau
Re: [PATCH] MEDIUM: stats: Add `show events` command.
March 25, 2012 09:20AM
Hi Aman,

On Sat, Mar 24, 2012 at 10:55:50PM -0700, Aman Gupta wrote:
> > Agreed. For this reason we've recently extended the log format to optionally
> > include new fields such as the outgoing source IP:port. I would not have
> > expected another request to come that soon :-)
>
> Quite the coincidence. I was working in a haproxy-1.4 checkout, so I
> missed the new logging commits.

OK just to be sure, you should *really* make your changes on 1.5-dev, not
1.4. 1.4 is in deep maintenance mode and I don't intend to merge such
changes there, as every time I did I caused some regressions.

> > This is really nice that you did that, as it's been on my todo list for
> > a long time. But I have comments on this before I can merge it :
>
> Awesome, thanks. This is my first time working with the haproxy
> codebase and I wasn't sure if I was on the right track. Definitely
> appreciate the feedback.

For someone who works for the first time on the code, you appear to
understand pretty well how it works, so feel free to continue :-)

> > 1) I'd rather use "debug sessions" than "show events" because till now
> >   "show anything" only lists a finite list, and "events" can be a lot
> >   of things.
>
> I was initially using "stream", then "events". I considered "show sess
> events" but "show events" was simpler to implement.
>
> I agree re-using the "show" prefix is confusing, so I'll switch to
> "debug sessions" (or "stream sessions"?)

The advantage with "debug" is that everyone is used to be very careful
when enabling debug, and to get a lot of information. And in fact, with
your patch we get some information similar to what we get when starting
haproxy with -d.

> > 2) it looks to me like the stats socket is still usable in parallel to the
> >   command. If so, we could as well as an "undebug all" command to stop
> >   the debugging. But I'm not requesting that for now, it's more out of
> >   curiosity.
>
> I wasn't quite sure what to do here either. Initially, once streaming
> was enabled you would have to reconnect to do anything else. In the
> current implementation, if a new command is received (or invalid input
> with a newline), streaming is turned off and we go back to processing
> commands.

OK good to know. So basically we just have to press enter to stop this.

> Right now it doesn't make sense to use any other commands while
> streaming, because it would be possible to separate the output. But if
> we plan on adding more streaming/debug subcommands, then an "undebug
> all" makes a lot of sense.

The advantage with "undebug all" is that many network admins are used to
do that when they don't want to debug anymore :-)
But since it's not needed at the moment, let's keep that for later.

> While streaming, it probably makes sense to disable commands like
> "show sess" so they do not interfere with the streaming output.

Not really. In fact, if the command is run in interactive mode, the
user knows what he's doing. So he gets mangled output but understands
the event he was waiting for finally happened. For instance if you put
a filter on a specific source address, you might still want to use the
CLI while waiting for it.

> > I would personally like to see 3 steps instead of 2 per session :
> >     - incoming session accepted
> >     - forward to server
> >     - closed session to client
> >
> >   We could then have a single letter char instead of +/- which would report
> >   the state (A/F/C) :
> >
> >    A 1 127.0.0.1:50869 - 127.0.0.1:9418
> >    F 1 127.0.0.1:50869 - 127.0.0.1:9418 | 127.0.0.1:50870 - 127.0.0.1:6000
> >    C 1 127.0.0.1:50869 - 127.0.0.1:9418
>
> I like this.
>
> I'm not sure how far to go in this direction, though. It would be
> useful to include a lot of the details from the "show sess" command-
> at the very least frontend/backend/srv names. It probably also makes
> sense to include the id that "show sess <id>" uses, so you can lookup
> detailed session information from an incoming event.
>
> For my use case the bare minimum I needed was the proxied and
> disconnected events, so that's where I started.

And you did right. We just need to be careful about the syntax when
adding new information so that it does not break existing scripts.

> > Here it's a waste of CPU cycles to call getpeername/getsockname 4 times
> > for addresses we generally already have. You have everything in the
> > stream_interface's addr (.from, .to). The local address is not always
> > filled, you have to check the session's flags for SN_FRT_ADDR_SET and
> > SN_BCK_ADDR_SET and call the associated functions accordingly prior
> > to use the addresses. The advantage is that the getpeername/getsockname
> > are already performed once in all of a session's life.
>
> I saw cli_addr was available in the session, but wasn't sure about the
> others. I'll fix this up now that I know where the addresses are
> stored, and call getpeername only once when necessary.

It's in 1.5. You'll also notice that the flag is only set at one place
in tcp_connect_server(). You should just move that to a function
"get_bck_addr()" similar to what is done in get_frt_addr().

> > This is not appropriate in my opinion. 1) it's a specific use, and 2)
> > it's already possible to set the timeout on the stats command line,
> > so better increase the CLI timeout before starting the dump.
>
> Is it possible to use "set timeout cli" to set an infinite timeout?

No, the largest you can use is 24 days. But quite frankly, do you really
think it makes sense to wait for an even for that long ?

> I'm not convinced it makes sense to have a timeout when streaming, but
> if its possible to disable it beforehand I guess that's ok.

The problem is not to have a timeout when streaming, but to have a timeout
for any connection to an external process. When we later support CLI over
TCP/SSL for instance, it will be mandatory to support a timeout.

> >>   out:
> >>       DPRINTF(stderr, "%s@%d: st=%d, rqf=%x, rpf=%x, rql=%d, rqs=%d, rl=%d, rs=%d\n",
> >>               __FUNCTION__, __LINE__,
> >>               si->state, req->flags, res->flags, req->l, req->send_max, res->l, res->send_max);
> >>
> >>       if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO)) {
> >> +             stats_event_listener_remove(s);
> >>               /* check that we have released everything then unregister */
> >>               stream_int_unregister_handler(si);
> >
> > Normally you don't need this, you just need to add a .release callback
> > in cli_applet (dumpstats.c) which does the work.
>
> I can't seem to find the release callback you're referring to.

You're right, the "si_applet" type does not have a release callback, it's
only in the stream interface. You can proceed as is done in peer_accept(),
by setting ->release after stream_int_register_handler() or you can
improve that by adding a release callback in the si_applet and make
stream_int_register_handler() automatically assign it to the stream
interface (preferred solution).

> > Thanks for doing this, it looks really appealing ! I'm impatient to
> > see what realtime info everyone will think about ! I suspect that
> > filtering on a frontend will be among the first requests in the
> > wish list, quickly followed by source-mask filtering !
>
> Thanks for taking the time to code review. I'm glad this is something
> you think is useful and worth merging upstream.

You're welcome.

> I did a lot of cleanup yesterday, to refactor and bring the code in
> line with the style guide. My branch is at
> https://github.com/tmm1/haproxy/compare/session-events (or
> https://github.com/tmm1/haproxy/compare/session-events.diff for just
> the patch).
>
> I'll incorporate your fixes above and email another patch for review.

That's perfect. Sending emails to the list is preferred over sending
links to external trees, as there are several persons able to review
code on the list and to suggest good ideas.

> As far as filtering, I already started on a fe/be filter with the
> "show events [<name>]" syntax.

Don't forget to prefix your <name> field by a special keyword (eg:
"frontend" or "proxy" or whatever you want), so that it leaves the
possibility to add new keywords later.

> This uses strcmp though, since I wasn't
> sure how best to store each user's filters.
> (https://github.com/tmm1/haproxy/compare/session-events...session-events-filter).

That's easy, just resolve the frontend name to a pointer using
findproxy(), and store the pointer in the stats context, otherwise
leave it NULL. In your checks, you'll just have to compare the
pointer with session->fe or session->be.

> Filtering by event type would also be interesting, especially as the
> number of events increases.

Yes of course.

> Finally, one thing I struggled with was the linked list of event
> listeners. It went through a couple iterations and I think I have it
> implemented sanely now, but if you could take a look and make sure I'm
> not doing anything stupid that would be great.

OK thanks for letting me know. At first glance it seemms OK, but I'll
review it more deeply with your next patch.

Regards,
Willy
"Brane F. Gračnar"
Re: [PATCH] MEDIUM: stats: Add `show events` command.
March 26, 2012 12:00PM
On 03/25/2012 07:55 AM, Aman Gupta wrote:
>> A 1 127.0.0.1:50869 - 127.0.0.1:9418
>> F 1 127.0.0.1:50869 - 127.0.0.1:9418 | 127.0.0.1:50870 - 127.0.0.1:6000
>> C 1 127.0.0.1:50869 - 127.0.0.1:9418

Is it possible to use [IP]:port format? We're already living in IPv6
world...

Best regards, Brane
Willy Tarreau
Re: [PATCH] MEDIUM: stats: Add `show events` command.
March 26, 2012 12:50PM
On Mon, Mar 26, 2012 at 11:57:01AM +0200, "Brane F. Gra??nar" wrote:
> On 03/25/2012 07:55 AM, Aman Gupta wrote:
> >> A 1 127.0.0.1:50869 - 127.0.0.1:9418
> >> F 1 127.0.0.1:50869 - 127.0.0.1:9418 | 127.0.0.1:50870 - 127.0.0.1:6000
> >> C 1 127.0.0.1:50869 - 127.0.0.1:9418
>
> Is it possible to use [IP]:port format? We're already living in IPv6
> world...

It's just a matter of taste. Everywhere you have a mandatory port, nothing
makes the brackets mandatory.

Willy
Aman Gupta
Re: [PATCH] MEDIUM: stats: Add `show events` command.
April 03, 2012 12:40AM
> OK just to be sure, you should *really* make your changes on 1.5-dev, not
> 1.4. 1.4 is in deep maintenance mode and I don't intend to merge such
> changes there, as every time I did I caused some regressions.

I'm in process of porting my patch to 1.5. I don't particularly want
to run 1.5 in production though, and it occurs to me that my patch is
quite involved for what I'm trying to accomplish.

My goal is essentially to have the remote ip of the upstream client
available to my application (sitting behind haproxy). For http
backends, I can use forwardfor and it works great. However, this
specific service is raw tcp. I know I can use tproxy, but would prefer
to avoid upgrading my kernel and setting up additional iptables rules.

The idea with this patch was that the application could connect to
haproxy to get events and use that data to figure out the upstream
client's ip. This adds a lot of complexity though, so I'm trying to
come up with alternatives. The only other obvious solution is to have
haproxy prepend something to the tcp stream. In my case this will work
fine, since I can modify my application to extract this from the
stream before consuming the client data.

Is this generic enough to add as a feature to haproxy? How would it
look ideally.. something like `option prepend "%inbound_peer\000"`?

Aman
Aman Gupta
[PATCH 1/4] MINOR: Add release callback to si_applet
April 03, 2012 04:00AM
---
include/types/stream_interface.h | 1 +
src/dumpstats.c | 2 ++
src/peers.c | 3 +--
src/stream_interface.c | 2 +-
4 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h
index 5acbd57..d59e3fb 100644
--- a/include/types/stream_interface.h
+++ b/include/types/stream_interface.h
@@ -189,6 +189,7 @@ struct stream_interface {
struct si_applet {
char *name; /* applet's name to report in logs */
void (*fct)(struct stream_interface *); /* internal I/O handler, may never be NULL */
+ void (*release)(struct stream_interface *); /* callback to release resources, may be NULL */
};

#endif /* _TYPES_STREAM_INTERFACE_H */
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 142429b..44117d9 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -3933,11 +3933,13 @@ static int stats_dump_errors_to_buffer(struct stream_interface *si)
struct si_applet http_stats_applet = {
.name = "<STATS>", /* used for logging */
.fct = http_stats_io_handler,
+ .release = NULL,
};

static struct si_applet cli_applet = {
.name = "<CLI>", /* used for logging */
.fct = cli_io_handler,
+ .release = NULL,
};

static struct cfg_kw_list cfg_kws = {{ },{
diff --git a/src/peers.c b/src/peers.c
index c6810e7..fe4ff9b 100644
--- a/src/peers.c
+++ b/src/peers.c
@@ -1044,6 +1044,7 @@ quit:
static struct si_applet peer_applet = {
.name = "<PEER>", /* used for logging */
.fct = peer_io_handler,
+ .release = peer_session_release,
};

/*
@@ -1079,7 +1080,6 @@ int peer_accept(struct session *s)
/* we have a dedicated I/O handler for the stats */
stream_int_register_handler(&s->si[1], &peer_applet);
copy_target(&s->target, &s->si[1].target); // for logging only
- s->si[1].release = peer_session_release;
s->si[1].applet.private = s;
s->si[1].applet.st0 = PEER_SESSION_ACCEPT;

@@ -1165,7 +1165,6 @@ static struct session *peer_session_create(struct peer *peer, struct peer_sessio
s->si[0].applet.st0 = PEER_SESSION_CONNECT;

stream_int_register_handler(&s->si[0], &peer_applet);
- s->si[0].release = peer_session_release;

s->si[1].fd = -1; /* just to help with debugging */
s->si[1].owner = t;
diff --git a/src/stream_interface.c b/src/stream_interface.c
index 350a47b..b8a6d58 100644
--- a/src/stream_interface.c
+++ b/src/stream_interface.c
@@ -316,7 +316,7 @@ struct task *stream_int_register_handler(struct stream_interface *si, struct si_
si->connect = NULL;
set_target_applet(&si->target, app);
si->applet.state = 0;
- si->release = NULL;
+ si->release = app->release;
si->flags |= SI_FL_WAIT_DATA;
return si->owner;
}
--
1.7.5.3
Aman Gupta
[PATCH 2/4] CLEANUP: Fix some minor typos
April 03, 2012 04:00AM
---
src/dumpstats.c | 4 ++--
src/proto_tcp.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/dumpstats.c b/src/dumpstats.c
index 44117d9..74ad966 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -824,7 +824,7 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line)
else if (strcmp(args[1], "table") == 0) {
stats_sock_table_request(si, args, true);
}
- else { /* neither "stat" nor "info" nor "sess" nor "errors" no "table" */
+ else { /* neither "stat" nor "info" nor "sess" nor "errors" nor "table" */
return 0;
}
}
@@ -1511,7 +1511,7 @@ static void cli_io_handler(struct stream_interface *si)
if ((res->flags & BF_SHUTR) && (si->state == SI_ST_EST) && (si->applet.st0 != STAT_CLI_GETREQ)) {
DPRINTF(stderr, "%s@%d: si to buf closed. req=%08x, res=%08x, st=%d\n",
__FUNCTION__, __LINE__, req->flags, res->flags, si->state);
- /* Other size has closed, let's abort if we have no more processing to do
+ /* Other side has closed, let's abort if we have no more processing to do
* and nothing more to consume. This is comparable to a broken pipe, so
* we forward the close to the request side so that it flows upstream to
* the client.
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 80f1e36..6324196 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -439,7 +439,7 @@ int tcp_connect_server(struct stream_interface *si)

/* needs src ip/port for logging */
if (si->flags & SI_FL_SRC_ADDR) {
- socklen_t addrlen = sizeof(si->addr.to);
+ socklen_t addrlen = sizeof(si->addr.from);
if (getsockname(fd, (struct sockaddr *)&si->addr.from, &addrlen) == -1) {
Warning("Cannot get source address for logging.\n");
}
--
1.7.5.3
---
include/types/stream_interface.h | 2 ++
src/frontend.c | 1 +
src/proto_tcp.c | 1 +
3 files changed, 4 insertions(+), 0 deletions(-)

diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h
index d59e3fb..16af806 100644
--- a/include/types/stream_interface.h
+++ b/include/types/stream_interface.h
@@ -73,6 +73,8 @@ enum {
SI_FL_INDEP_STR = 0x0040, /* independant streams = don't update rex on write */
SI_FL_NOLINGER = 0x0080, /* may close without lingering. One-shot. */
SI_FL_SRC_ADDR = 0x1000, /* get the source ip/port with getsockname */
+ SI_FL_TO_SET = 0x2000, /* addr.to is set */
+ SI_FL_FROM_SET = 0x4000, /* addr.from is set */
};

/* target types */
diff --git a/src/frontend.c b/src/frontend.c
index 12dc2c8..35c3ef3 100644
--- a/src/frontend.c
+++ b/src/frontend.c
@@ -54,6 +54,7 @@ void get_frt_addr(struct session *s)

if (get_original_dst(s->si[0].fd, (struct sockaddr_in *)&s->si[0].addr.to, &namelen) == -1)
getsockname(s->si[0].fd, (struct sockaddr *)&s->si[0].addr.to, &namelen);
+ s->si[0].flags |= SI_FL_TO_SET;
s->flags |= SN_FRT_ADDR_SET;
}

diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 6324196..39ef26b 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -443,6 +443,7 @@ int tcp_connect_server(struct stream_interface *si)
if (getsockname(fd, (struct sockaddr *)&si->addr.from, &addrlen) == -1) {
Warning("Cannot get source address for logging.\n");
}
+ si->flags |= SI_FL_FROM_SET;
}

fdtab[fd].owner = si;
--
1.7.5.3
---
doc/configuration.txt | 22 ++++
include/proto/dumpstats.h | 5 +
include/types/stream_interface.h | 5 +
src/dumpstats.c | 242 +++++++++++++++++++++++++++++++++++++-
src/session.c | 9 ++
5 files changed, 280 insertions(+), 3 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 2ede208..5b1ca85 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -10031,6 +10031,28 @@ shutdown sessions <backend>/<server>
maintenance mode, for instance. Such terminated sessions are reported with a
'K' flag in the logs.

+debug sess [proxy:<proxy_name>[:<server_name>]]
+
+ Dump a stream of events about sessions as they are added and removed.
+ The possible event formats are "Forward" and "Close":
+
+ "F <session_id> <in_peer> - <in_sock> | <out_sock> - <out_peer>\n"
+ "C <session_id>\n"
+
+ Streaming will continue until a new command is received or the
+ connection is closed. If <proxy_name> or <server_name> is specified, limit to
+ events concerning only the proxy and server specified.
+
+ This command is restricted and can only be issued on sockets configured
+ for levels "operator" or "admin".
+
+ Example:
+ >>> $ echo "set timeout cli 3600; debug sess" | socat stdio,ignoreeof /tmp/sock1
+ F 1 127.0.0.1:50869 - 127.0.0.1:9418 | 127.0.0.1:50870 - 127.0.0.1:6000
+ C 1
+ F 2 127.0.0.1:50874 - 127.0.0.1:9418 | 127.0.0.1:50875 - 127.0.0.1:6000
+ C 2
+
/*
* Local variables:
* fill-column: 79
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index eb44a36..d328881 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -55,6 +55,8 @@
#define STAT_CLI_O_TAB 8 /* dump tables */
#define STAT_CLI_O_CLR 9 /* clear tables */

+#define STAT_CLI_EVENTS 8 /* event stream */
+
/* status codes (strictly 4 chars) used in the URL to display a message */
#define STAT_STATUS_UNKN "UNKN" /* an unknown error occured, shouldn't happen */
#define STAT_STATUS_DONE "DONE" /* the action is successful */
@@ -63,8 +65,11 @@
#define STAT_STATUS_DENY "DENY" /* action denied */

extern struct si_applet http_stats_applet;
+extern int stats_event_enabled;

void stats_io_handler(struct stream_interface *si);
+void stats_event_new_session(struct session *s);
+void stats_event_end_session(struct session *s);


#endif /* _PROTO_DUMPSTATS_H */
diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h
index 16af806..d730559 100644
--- a/include/types/stream_interface.h
+++ b/include/types/stream_interface.h
@@ -169,6 +169,11 @@ struct stream_interface {
int bol; /* pointer to beginning of current line */
} errors;
struct {
+ struct list list; /* list of stats streams in the STAT_CLI_EVENTS state */
+ struct proxy *px; /* if not NULL, only send events associated with this proxy */
+ struct server *srv; /* if not NULL, only send events associated with this server */
+ } events;
+ struct {
void *target; /* table we want to dump, or NULL for all */
struct proxy *proxy; /* table being currently dumped (first if NULL) */
struct stksess *entry; /* last entry we were trying to dump (or first if NULL) */
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 74ad966..b3f4423 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -86,6 +86,7 @@ static const char stats_sock_usage_msg[] =
" disable : put a server or frontend in maintenance mode\n"
" enable : re-enable a server or frontend which is in maintenance mode\n"
" shutdown : kill a session or a frontend (eg:to release listening ports)\n"
+ " debug sess : stream events about proxied sessions\n"
"";

static const char stats_permission_denied_msg[] =
@@ -114,6 +115,77 @@ enum {
STAT_PX_ST_FIN,
};

+/* Keep track of sessions that want streaming events (STAT_CLI_EVENT).
+ */
+int stats_event_enabled = 0;
+static struct list stats_event_listeners = LIST_HEAD_INIT(stats_event_listeners);
+
+/* Add a session to the list of event listeners.
+ */
+static inline void stats_event_listener_add(struct stream_interface *si)
+{
+ LIST_ADDQ(&stats_event_listeners, &si->applet.ctx.events.list);
+ stats_event_enabled = 1;
+}
+
+/* Remove a session from the list of listeners, but only if it is a
+ * registered listener. This enables us to invoke the method on all
+ * disconnecting stats sockets to ensure they are cleaned up, regardless
+ * of how many times they switch between streaming and other commands.
+ */
+static inline void stats_event_listener_remove(struct stream_interface *si)
+{
+ int found = 0;
+ struct stream_interface *curr;
+ list_for_each_entry(curr, &stats_event_listeners, applet.ctx.events.list) {
+ if (curr == si) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (found) {
+ si->applet.ctx.events.px = NULL;
+ si->applet.ctx.events.srv = NULL;
+ LIST_DEL(&si->applet.ctx.events.list);
+ }
+
+ if (LIST_ISEMPTY(&stats_event_listeners))
+ stats_event_enabled = 0;
+
+ /* Re-initialize stats output */
+ memset(&si->applet.ctx.stats, 0, sizeof(si->applet.ctx.stats));
+}
+
+/* Send a message to all registered event listeners.
+ */
+static inline void stats_event_listener_message_all(char *msg, struct session *s)
+{
+ struct stream_interface *curr;
+
+ list_for_each_entry(curr, &stats_event_listeners, applet.ctx.events.list) {
+ struct proxy *px;
+ struct server *srv;
+
+ if (!(curr->flags & SI_FL_DONT_WAKE) && curr->owner) {
+ /* filter by proxy and server if required */
+ if ((px = curr->applet.ctx.events.px)) {
+ if (s->be != px && s->fe != px)
+ continue; /* ignore */
+ if ((srv = curr->applet.ctx.events.srv)) {
+ if (target_srv(&s->target) != srv)
+ continue; /* ignore */
+ }
+ }
+
+ if (buffer_feed(curr->ib, msg) == -1) {
+ curr->ib->flags |= BF_SEND_DONTWAIT;
+ task_wakeup(curr->owner, TASK_WOKEN_MSG);
+ }
+ }
+ }
+}
+
/* This function is called from the session-level accept() in order to instanciate
* a new stats socket. It returns a positive value upon success, 0 if the connection
* needs to be closed and ignored, or a negative value upon critical failure.
@@ -772,7 +844,54 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line)
args[arg] = line;

si->applet.ctx.stats.flags = 0;
- if (strcmp(args[0], "show") == 0) {
+ if (strcmp(args[0], "debug") == 0) {
+ if (strcmp(args[1], "sess") == 0) {
+ if (s->listener->perm.ux.level < ACCESS_LVL_OPER) {
+ si->applet.ctx.cli.msg = stats_permission_denied_msg;
+ si->applet.st0 = STAT_CLI_PRINT;
+ return 1;
+ }
+ if (*args[2] && !strncmp(args[2], "proxy", 5)) {
+ struct proxy *px = NULL;
+ struct server *srv = NULL;
+ char *px_name = args[2] + 6, *srv_name;
+
+ if ((srv_name = strchr(px_name, ':'))) {
+ *srv_name = 0;
+ srv_name += 1;
+ }
+
+ px = findproxy(px_name, PR_CAP_FE|PR_CAP_BE);
+ if (!px) {
+ si->applet.ctx.cli.msg = "Invalid proxy filter for event stream.";
+ si->applet.st0 = STAT_CLI_PRINT;
+ return 1;
+ }
+
+ if (srv_name && *srv_name) {
+ srv = findserver(px, srv_name);
+ if (!srv) {
+ si->applet.ctx.cli.msg = "Invalid server filter for event stream.";
+ si->applet.st0 = STAT_CLI_PRINT;
+ return 1;
+ }
+ }
+
+ si->applet.ctx.events.srv = srv;
+ si->applet.ctx.events.px = px;
+ } else {
+ si->applet.ctx.events.srv = NULL;
+ si->applet.ctx.events.px = NULL;
+ }
+
+ stats_event_listener_add(si);
+ si->applet.st0 = STAT_CLI_EVENTS;
+ }
+ else { /* not "sess" */
+ return 0;
+ }
+ }
+ else if (strcmp(args[0], "show") == 0) {
if (strcmp(args[1], "stat") == 0) {
if (*args[2] && *args[3] && *args[4]) {
si->applet.ctx.stats.flags |= STAT_BOUND;
@@ -1346,6 +1465,14 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line)
return 1;
}

+/* Callback to release a cli session.
+ */
+static void cli_session_release(struct stream_interface *si)
+{
+ /* remove if registered as event listener */
+ stats_event_listener_remove(si);
+}
+
/* This I/O handler runs as an applet embedded in a stream interface. It is
* used to processes I/O from/to the stats unix socket. The system relies on a
* state machine handling requests and various responses. We read a request,
@@ -1377,7 +1504,7 @@ static void cli_io_handler(struct stream_interface *si)
si->shutw(si);
break;
}
- else if (si->applet.st0 == STAT_CLI_GETREQ) {
+ else if (si->applet.st0 == STAT_CLI_GETREQ || si->applet.st0 == STAT_CLI_EVENTS) {
/* ensure we have some output room left in the event we
* would want to return some info right after parsing.
*/
@@ -1417,7 +1544,10 @@ static void cli_io_handler(struct stream_interface *si)

trash[len] = '\0';

+ if (si->applet.st0 == STAT_CLI_EVENTS)
+ stats_event_listener_remove(si);
si->applet.st0 = STAT_CLI_PROMPT;
+
if (len) {
if (strcmp(trash, "quit") == 0) {
si->applet.st0 = STAT_CLI_END;
@@ -3725,6 +3855,112 @@ static int stats_table_request(struct stream_interface *si, bool show)
return 1;
}

+/* Called whenever a new session is successfully established (reaches
+ * SI_ST_EST). If there are any stats sockets listening in the
+ * STAT_CLI_EVENTS state, they will be notified of this session's unique
+ * id, along with the sockname and peername of both sides of the session.
+ */
+void stats_event_new_session(struct session *s)
+{
+ if (LIST_ISEMPTY(&stats_event_listeners))
+ return;
+
+ char addrs[4][INET6_ADDRSTRLEN + strlen(":65535") + 1] = {"","","",""};
+ int i;
+
+ struct stream_interface *si0 = &s->si[0], *si1 = &s->si[1];
+ socklen_t namelen;
+
+ /* si0 from/to = peer/sock */
+ if (!(si0->flags & SI_FL_FROM_SET)) {
+ namelen = sizeof(si0->addr.from);
+ getpeername(si0->fd, (struct sockaddr *)&si0->addr.from, &namelen);
+ si0->flags |= SI_FL_FROM_SET;
+ }
+ if (!(si0->flags & SI_FL_TO_SET)) {
+ namelen = sizeof(si0->addr.to);
+ getsockname(si0->fd, (struct sockaddr *)&si0->addr.to, &namelen);
+ si0->flags |= SI_FL_TO_SET;
+ }
+
+ /* si1 from/to = sock/peer (reversed) */
+ if (!(si1->flags & SI_FL_FROM_SET)) {
+ namelen = sizeof(si1->addr.from);
+ getsockname(si1->fd, (struct sockaddr *)&si1->addr.from, &namelen);
+ si1->flags |= SI_FL_FROM_SET;
+ }
+ if (!(si1->flags & SI_FL_TO_SET)) {
+ namelen = sizeof(si1->addr.to);
+ getpeername(si1->fd, (struct sockaddr *)&si1->addr.to, &namelen);
+ si1->flags |= SI_FL_TO_SET;
+ }
+
+ for(i = 0; i < 4; i++) {
+ struct sockaddr_storage *sock;
+ const void *sin_addr = NULL;
+ int port = 0;
+
+ switch (i) {
+ case 0: // inbound peer
+ sock = &si0->addr.from;
+ break;
+ case 1: // inbound sock
+ sock = &si0->addr.to;
+ break;
+ case 2: // outbound sock
+ sock = &si1->addr.from;
+ break;
+ case 3: // outbound peer
+ sock = &si1->addr.to;
+ break;
+ }
+
+ switch (sock->ss_family) {
+ case AF_INET:
+ sin_addr = (const void *)&((struct sockaddr_in *)sock)->sin_addr;
+ port = ntohs(((struct sockaddr_in *)sock)->sin_port);
+ break;
+ case AF_INET6:
+ sin_addr = (const void *)&((struct sockaddr_in6 *)sock)->sin6_addr;
+ port = ntohs(((struct sockaddr_in6 *)sock)->sin6_port);
+ break;
+ }
+
+ switch (sock->ss_family) {
+ case AF_INET:
+ case AF_INET6:
+ inet_ntop(sock->ss_family, sin_addr, addrs, sizeof(addrs));
+ snprintf(addrs+strlen(addrs), sizeof(addrs)-strlen(addrs)-1, ":%d", port);
+ break;
+ case AF_UNIX:
+ sprintf(addrs, "unix:%d", s->listener->luid);
+ break;
+ default:
+ sprintf(addrs, "%s", "unknown");
+ }
+ }
+
+ snprintf(trash, sizeof(trash), "F %u %s - %s | %s - %s\n", s->uniq_id,
+ addrs[0], // inbound peer
+ addrs[1], // inbound sock
+ addrs[2], // outbound sock
+ addrs[3] // outbound peer
+ );
+ stats_event_listener_message_all(trash, s);
+}
+
+/* Called when the session argument's s->si[1]->state goes from SI_ST_EST
+ * to SI_ST_CLO. All stats listeners are notified of this destroy event.
+ */
+void stats_event_end_session(struct session *s)
+{
+ if (LIST_ISEMPTY(&stats_event_listeners))
+ return;
+
+ snprintf(trash, sizeof(trash), "C %u\n", s->uniq_id);
+ stats_event_listener_message_all(trash, s);
+}
+
/* print a line of text buffer (limited to 70 bytes) to <out>. The format is :
* <2 spaces> <offset=5 digits> <space or plus> <space> <70 chars max> <\n>
* which is 60 chars per line. Non-printable chars \t, \n, \r and \e are
@@ -3939,7 +4175,7 @@ struct si_applet http_stats_applet = {
static struct si_applet cli_applet = {
.name = "<CLI>", /* used for logging */
.fct = cli_io_handler,
- .release = NULL,
+ .release = cli_session_release,
};

static struct cfg_kw_list cfg_kws = {{ },{
diff --git a/src/session.c b/src/session.c
index e5b76eb..48c5435 100644
--- a/src/session.c
+++ b/src/session.c
@@ -708,6 +708,9 @@ static void sess_establish(struct session *s, struct stream_interface *si)
rep->rto = s->be->timeout.server;
}
req->wex = TICK_ETERNITY;
+
+ if (unlikely(stats_event_enabled))
+ stats_event_new_session(s);
}

/* Update stream interface status for input states SI_ST_ASS, SI_ST_QUE, SI_ST_TAR.
@@ -2148,6 +2151,12 @@ struct task *process_session(struct task *t)
s->do_log(s);
}

+ if (unlikely(stats_event_enabled)) {
+ if (s->si[1].state == SI_ST_CLO &&
+ s->si[1].prev_state == SI_ST_EST)
+ stats_event_end_session(s);
+ }
+
/* the task MUST not be in the run queue anymore */
session_free(s);
task_delete(t);
--
1.7.5.3
Willy Tarreau
Re: [PATCH] MEDIUM: stats: Add `show events` command.
April 03, 2012 08:30AM
Hi Aman,

On Mon, Apr 02, 2012 at 03:34:08PM -0700, Aman Gupta wrote:
> > OK just to be sure, you should *really* make your changes on 1.5-dev, not
> > 1.4. 1.4 is in deep maintenance mode and I don't intend to merge such
> > changes there, as every time I did I caused some regressions.
>
> I'm in process of porting my patch to 1.5. I don't particularly want
> to run 1.5 in production though, and it occurs to me that my patch is
> quite involved for what I'm trying to accomplish.
>
> My goal is essentially to have the remote ip of the upstream client
> available to my application (sitting behind haproxy). For http
> backends, I can use forwardfor and it works great. However, this
> specific service is raw tcp. I know I can use tproxy, but would prefer
> to avoid upgrading my kernel and setting up additional iptables rules.
>
> The idea with this patch was that the application could connect to
> haproxy to get events and use that data to figure out the upstream
> client's ip. This adds a lot of complexity though, so I'm trying to
> come up with alternatives. The only other obvious solution is to have
> haproxy prepend something to the tcp stream. In my case this will work
> fine, since I can modify my application to extract this from the
> stream before consuming the client data.

This is the principle of the PROXY protocol. The "send-proxy" server option
was added to 1.5-dev, but it's not in 1.4 though I have the patch to make
this possible. It requires very minor changes to the application and I know
that some people running FTP servers and SSL servers have modified them to
parse this line. The protocol was also already adopted by Stud and Stunnel.

The protocol is described here :

http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt

Please let me know whether you're interested, then I'll check where I left
the patch :-)

Willy
Aman Gupta
Re: [PATCH] MEDIUM: stats: Add `show events` command.
April 03, 2012 08:40AM
On Mon, Apr 2, 2012 at 11:21 PM, Willy Tarreau <[email protected]> wrote:
> Hi Aman,
>
> On Mon, Apr 02, 2012 at 03:34:08PM -0700, Aman Gupta wrote:
>> > OK just to be sure, you should *really* make your changes on 1.5-dev, not
>> > 1.4. 1.4 is in deep maintenance mode and I don't intend to merge such
>> > changes there, as every time I did I caused some regressions.
>>
>> I'm in process of porting my patch to 1.5. I don't particularly want
>> to run 1.5 in production though, and it occurs to me that my patch is
>> quite involved for what I'm trying to accomplish.
>>
>> My goal is essentially to have the remote ip of the upstream client
>> available to my application (sitting behind haproxy). For http
>> backends, I can use forwardfor and it works great. However, this
>> specific service is raw tcp. I know I can use tproxy, but would prefer
>> to avoid upgrading my kernel and setting up additional iptables rules.
>>
>> The idea with this patch was that the application could connect to
>> haproxy to get events and use that data to figure out the upstream
>> client's ip. This adds a lot of complexity though, so I'm trying to
>> come up with alternatives. The only other obvious solution is to have
>> haproxy prepend something to the tcp stream. In my case this will work
>> fine, since I can modify my application to extract this from the
>> stream before consuming the client data.
>
> This is the principle of the PROXY protocol. The "send-proxy" server option
> was added to 1.5-dev, but it's not in 1.4 though I have the patch to make
> this possible. It requires very minor changes to the application and I know
> that some people running FTP servers and SSL servers have modified them to
> parse this line. The protocol was also already adopted by Stud and Stunnel.
>
> The protocol is described here :
>
>      http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
>
> Please let me know whether you're interested, then I'll check where I left
> the patch :-)

This is exactly what I am looking for.

I am very much interested in a 1.4 backport, and happy to help with
the porting and testing process.

Aman

>
> Willy
>
"Brane F. Gračnar"
Re: [PATCH] MEDIUM: stats: Add `show events` command.
April 03, 2012 09:20AM
On 04/03/2012 08:21 AM, Willy Tarreau wrote:
> The protocol is described here :
>
> http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
>
> Please let me know whether you're interested, then I'll check where I left
> the patch :-)

Here ya go :)

Best regards, Brane
Aman Gupta
Re: [PATCH] MEDIUM: stats: Add `show events` command.
April 03, 2012 09:20AM
On Tue, Apr 3, 2012 at 12:10 AM, "Brane F. Gračnar"
<[email protected]> wrote:
> On 04/03/2012 08:21 AM, Willy Tarreau wrote:
>> The protocol is described here :
>>
>>       http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
>>
>> Please let me know whether you're interested, then I'll check where I left
>> the patch :-)
>
> Here ya go :)

Thanks, but this appears to be the accept-proxy side of the
implementation. I don't see any send-proxy code.

Aman

>
> Best regards, Brane
Willy Tarreau
Re: [PATCH] MEDIUM: stats: Add `show events` command.
April 03, 2012 09:40AM
On Tue, Apr 03, 2012 at 12:17:45AM -0700, Aman Gupta wrote:
> On Tue, Apr 3, 2012 at 12:10 AM, "Brane F. Gra??nar"
> <[email protected]> wrote:
> > On 04/03/2012 08:21 AM, Willy Tarreau wrote:
> >> The protocol is described here :
> >>
> >>       http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
> >>
> >> Please let me know whether you're interested, then I'll check where I left
> >> the patch :-)
> >
> > Here ya go :)
>
> Thanks, but this appears to be the accept-proxy side of the
> implementation. I don't see any send-proxy code.

Indeed, just checked and we only had the accept-proxy backport.

I've just done the send-proxy backport. Adapted the code to make
it build. I think it should be correct but I have not tested it
at all. Here are the patches. Please report if something appears
broken so that we can maintain the backport for users who need
this.

Willy

From 43c33e22c6e1b1f3a40fd66f96b5b6fab0512be8 Mon Sep 17 00:00:00 2001
From: Willy Tarreau <[email protected]>
Date: Tue, 3 Apr 2012 09:29:38 +0200
Subject: [PATCH 1/3] [MINOR] frontend: add a make_proxy_line function

This function will build a PROXY protocol line header from two addresses
(IPv4 or IPv6). AF_UNIX family will be reported as UNKNOWN.
(cherry picked from commit a73fcaf424ee8985abbec1d57b88c3a74f8198c5)
---
include/proto/client.h | 1 +
src/client.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 80 insertions(+), 0 deletions(-)

diff --git a/include/proto/client.h b/include/proto/client.h
index 1d368a4..b52001e 100644
--- a/include/proto/client.h
+++ b/include/proto/client.h
@@ -27,6 +27,7 @@

void get_frt_addr(struct session *s);
int event_accept(int fd);
+int make_proxy_line(char *buf, int buf_len, struct sockaddr_storage *src, struct sockaddr_storage *dst);


#endif /* _PROTO_CLIENT_H */
diff --git a/src/client.c b/src/client.c
index cac6cd7..76e79b4 100644
--- a/src/client.c
+++ b/src/client.c
@@ -520,6 +520,85 @@ int event_accept(int fd) {
}


+/* Makes a PROXY protocol line from the two addresses. The output is sent to
+ * buffer <buf> for a maximum size of <buf_len> (including the trailing zero).
+ * It returns the number of bytes composing this line (including the trailing
+ * LF), or zero in case of failure (eg: not enough space). It supports TCP4,
+ * TCP6 and "UNKNOWN" formats.
+ */
+int make_proxy_line(char *buf, int buf_len, struct sockaddr_storage *src, struct sockaddr_storage *dst)
+{
+ int ret = 0;
+
+ if (src->ss_family == dst->ss_family && src->ss_family == AF_INET) {
+ ret = snprintf(buf + ret, buf_len - ret, "PROXY TCP4 ");
+ if (ret >= buf_len)
+ return 0;
+
+ /* IPv4 src */
+ if (!inet_ntop(src->ss_family, &((struct sockaddr_in *)src)->sin_addr, buf + ret, buf_len - ret))
+ return 0;
+
+ ret += strlen(buf + ret);
+ if (ret >= buf_len)
+ return 0;
+
+ buf[ret++] = ' ';
+
+ /* IPv4 dst */
+ if (!inet_ntop(dst->ss_family, &((struct sockaddr_in *)dst)->sin_addr, buf + ret, buf_len - ret))
+ return 0;
+
+ ret += strlen(buf + ret);
+ if (ret >= buf_len)
+ return 0;
+
+ /* source and destination ports */
+ ret += snprintf(buf + ret, buf_len - ret, " %u %u\r\n",
+ ntohs(((struct sockaddr_in *)src)->sin_port),
+ ntohs(((struct sockaddr_in *)dst)->sin_port));
+ if (ret >= buf_len)
+ return 0;
+ }
+ else if (src->ss_family == dst->ss_family && src->ss_family == AF_INET6) {
+ ret = snprintf(buf + ret, buf_len - ret, "PROXY TCP6 ");
+ if (ret >= buf_len)
+ return 0;
+
+ /* IPv6 src */
+ if (!inet_ntop(src->ss_family, &((struct sockaddr_in6 *)src)->sin6_addr, buf + ret, buf_len - ret))
+ return 0;
+
+ ret += strlen(buf + ret);
+ if (ret >= buf_len)
+ return 0;
+
+ buf[ret++] = ' ';
+
+ /* IPv6 dst */
+ if (!inet_ntop(dst->ss_family, &((struct sockaddr_in6 *)dst)->sin6_addr, buf + ret, buf_len - ret))
+ return 0;
+
+ ret += strlen(buf + ret);
+ if (ret >= buf_len)
+ return 0;
+
+ /* source and destination ports */
+ ret += snprintf(buf + ret, buf_len - ret, " %u %u\r\n",
+ ntohs(((struct sockaddr_in6 *)src)->sin6_port),
+ ntohs(((struct sockaddr_in6 *)dst)->sin6_port));
+ if (ret >= buf_len)
+ return 0;
+ }
+ else {
+ /* unknown family combination */
+ ret = snprintf(buf, buf_len, "PROXY UNKNOWN\r\n");
+ if (ret >= buf_len)
+ return 0;
+ }
+ return ret;
+}
+

/************************************************************************/
/* All supported keywords must be declared here. */
--
1.7.2.1.45.g54fbc

From b4f72ddad84242d8596c2ac50a2191ad27ead411 Mon Sep 17 00:00:00 2001
From: Willy Tarreau <[email protected]>
Date: Tue, 3 Apr 2012 09:35:15 +0200
Subject: [PATCH 2/3] [MEDIUM] stream_sock: add support for sending the proxy protocol header line

Upon connection establishment, stream_sock is now able to send a PROXY
line before sending any data. Since it's possible that the buffer is
already full, and we don't want to allocate a block for that line, we
compute it on-the-fly when we need it. We just store the offset from
which to (re-)send from the end of the line, since it's assumed that
multiple outputs of the same proxy line will be strictly equivalent. In
practice, one call is enough. We just make sure to handle the case where
the first send() would indicate an incomplete output, eventhough it's
very unlikely to ever happen.
(cherry picked from commit b22e55bc8f06b6679aa5e71c3a73c0f61256f960)
---
include/types/stream_interface.h | 1 +
src/stream_sock.c | 41 ++++++++++++++++++++++++++++++++++++-
2 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h
index 6ad6684..e813e26 100644
--- a/include/types/stream_interface.h
+++ b/include/types/stream_interface.h
@@ -103,6 +103,7 @@ struct stream_interface {
void *err_loc; /* commonly the server, NULL when SI_ET_NONE */
void *private; /* may be used by any function above */
unsigned int st0, st1; /* may be used by any function above */
+ int send_proxy_ofs; /* <0 = offset to (re)send from the end, >0 = send all */
};


diff --git a/src/stream_sock.c b/src/stream_sock.c
index ffbd652..1b74f76 100644
--- a/src/stream_sock.c
+++ b/src/stream_sock.c
@@ -544,6 +544,43 @@ static int stream_sock_write_loop(struct stream_interface *si, struct buffer *b)
int retval = 1;
int ret, max;

+ if (unlikely(si->send_proxy_ofs)) {
+ struct session *s = ((struct task *)si->owner)->context;
+
+ /* The target server expects a PROXY line to be sent first.
+ * If the send_proxy_ofs is negative, it corresponds to the
+ * offset to start sending from then end of the proxy string
+ * (which is recomputed every time since it's constant). If
+ * it is positive, it means we have to send from the start.
+ */
+ ret = make_proxy_line(trash, sizeof(trash),
+ &s->cli_addr, &s->frt_addr);
+ if (!ret)
+ return -1;
+
+ if (si->send_proxy_ofs > 0)
+ si->send_proxy_ofs = -ret; /* first call */
+
+ /* we have to send trash from (ret+sp for -sp bytes) */
+ ret = send(si->fd, trash + ret + si->send_proxy_ofs, -si->send_proxy_ofs,
+ (b->flags & BF_OUT_EMPTY) ? 0 : MSG_MORE);
+ if (ret > 0) {
+ if (fdtab[si->fd].state == FD_STCONN)
+ fdtab[si->fd].state = FD_STREADY;
+
+ si->send_proxy_ofs += ret; /* becomes zero once complete */
+ b->flags |= BF_WRITE_NULL; /* connect() succeeded */
+ }
+ else if (ret == 0 || errno == EAGAIN) {
+ /* nothing written, we need to poll for write first */
+ return 0;
+ }
+ else {
+ /* bad, we got an error */
+ return -1;
+ }
+ }
+
#if defined(CONFIG_HAP_LINUX_SPLICE)
while (b->pipe) {
ret = splice(b->pipe->cons, NULL, si->fd, NULL, b->pipe->data,
@@ -704,7 +741,7 @@ int stream_sock_write(int fd)
if (b->flags & BF_SHUTW)
goto out_wakeup;

- if (likely(!(b->flags & BF_OUT_EMPTY))) {
+ if (likely(!(b->flags & BF_OUT_EMPTY) || si->send_proxy_ofs)) {
/* OK there are data waiting to be sent */
retval = stream_sock_write_loop(si, b);
if (retval < 0)
@@ -1041,7 +1078,7 @@ void stream_sock_chk_snd(struct stream_interface *si)

if (!(si->flags & SI_FL_WAIT_DATA) || /* not waiting for data */
(fdtab[si->fd].ev & FD_POLL_OUT) || /* we'll be called anyway */
- (ob->flags & BF_OUT_EMPTY)) /* called with nothing to send ! */
+ ((ob->flags & BF_OUT_EMPTY) && !(si->send_proxy_ofs))) /* called with nothing to send ! */
return;

retval = stream_sock_write_loop(si, ob);
--
1.7.2.1.45.g54fbc

From b122bdfb69665aa35aacd69681ac810ad3ce6db8 Mon Sep 17 00:00:00 2001
From: Willy Tarreau <[email protected]>
Date: Sun, 20 Mar 2011 10:32:26 +0100
Subject: [PATCH 3/3] [MEDIUM] server: add support for the "send-proxy" option

This option enables use of the PROXY protocol with the server, which
allows haproxy to transport original client's address across multiple
architecture layers.
(cherry picked from commit 5ab04ec47c9946a2bbc535687c023215ca813da0)
---
doc/configuration.txt | 15 +++++++++++++++
include/types/server.h | 39 ++++++++++++++++++++-------------------
src/backend.c | 8 ++++++++
src/cfgparse.c | 6 +++++-
4 files changed, 48 insertions(+), 20 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 36f68a5..0e6a842 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -6116,6 +6116,21 @@ rise <count>

Supported in default-server: Yes

+send-proxy
+ The "send-proxy" parameter enforces use of the PROXY protocol over any
+ connection established to this server. The PROXY protocol informs the other
+ end about the layer 3/4 addresses of the incoming connection, so that it can
+ know the client's address or the public address it accessed to, whatever the
+ upper layer protocol. For connections accepted by an "accept-proxy" listener,
+ the advertised address will be used. Only TCPv4 and TCPv6 address families
+ are supported. Other families such as Unix sockets, will report an UNKNOWN
+ family. Servers using this option can fully be chained to another instance of
+ haproxy listening with an "accept-proxy" setting. This setting must not be
+ used if the server isn't aware of the protocol. See also the "accept-proxy"
+ option of the "bind" keyword.
+
+ Supported in default-server: No
+
slowstart <start_time_in_ms>
The "slowstart" parameter for a server accepts a value in milliseconds which
indicates after how long a server which has just come back up will run at
diff --git a/include/types/server.h b/include/types/server.h
index 14e4d1f..4c500be 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -1,23 +1,23 @@
/*
- include/types/server.h
- This file defines everything related to servers.
-
- Copyright (C) 2000-2009 Willy Tarreau - [email protected]
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation, version 2.1
- exclusively.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-*/
+ * include/types/server.h
+ * This file defines everything related to servers.
+ *
+ * Copyright (C) 2000-2011 Willy Tarreau - [email protected]
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */

#ifndef _TYPES_SERVER_H
#define _TYPES_SERVER_H
@@ -53,6 +53,7 @@
#define SRV_TPROXY_CLI 0x0300 /* bind to the client's IP+port to reach this server */
#define SRV_TPROXY_DYN 0x0400 /* bind to a dynamically computed non-local address */
#define SRV_TPROXY_MASK 0x0700 /* bind to a non-local address to reach this server */
+#define SRV_SEND_PROXY 0x0800 /* this server talks the PROXY protocol */

/* function which act on servers need to return various errors */
#define SRV_STATUS_OK 0 /* everything is OK. */
diff --git a/src/backend.c b/src/backend.c
index a7a7867..0104bb8 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -916,6 +916,14 @@ int connect_server(struct session *s)
if (!s->req->cons->connect)
return SN_ERR_INTERNAL;

+ /* process the case where the server requires the PROXY protocol to be sent */
+ s->req->cons->send_proxy_ofs = 0;
+ if (s->srv->state & SRV_SEND_PROXY) {
+ s->req->cons->send_proxy_ofs = 1; /* must compute size */
+ if (!(s->flags & SN_FRT_ADDR_SET))
+ get_frt_addr(s);
+ }
+
assign_tproxy_address(s);

err = s->req->cons->connect(s->req->cons, s->be, s->srv,
diff --git a/src/cfgparse.c b/src/cfgparse.c
index e7e8602..ec89983 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -3628,6 +3628,10 @@ stats_error_parsing:
newsrv->state |= SRV_BACKUP;
cur_arg ++;
}
+ else if (!defsrv && !strcmp(args[cur_arg], "send-proxy")) {
+ newsrv->state |= SRV_SEND_PROXY;
+ cur_arg ++;
+ }
else if (!strcmp(args[cur_arg], "weight")) {
int w;
w = atol(args[cur_arg + 1]);
@@ -3915,7 +3919,7 @@ stats_error_parsing:
}
else {
if (!defsrv)
- Alert("parsing [%s:%d] : server %s only supports options 'backup', 'cookie', 'redir', 'observer', 'on-error', 'error-limit', 'check', 'disabled', 'track', 'id', 'inter', 'fastinter', 'downinter', 'rise', 'fall', 'addr', 'port', 'source', 'minconn', 'maxconn', 'maxqueue', 'slowstart' and 'weight'.\n",
+ Alert("parsing [%s:%d] : server %s only supports options 'backup', 'cookie', 'redir', 'observer', 'on-error', 'error-limit', 'check', 'disabled', 'track', 'id', 'inter', 'fastinter', 'downinter', 'rise', 'fall', 'addr', 'port', 'source', 'send-proxy', 'minconn', 'maxconn', 'maxqueue', 'slowstart' and 'weight'.\n",
file, linenum, newsrv->id);
else
Alert("parsing [%s:%d]: default-server only supports options 'on-error', 'error-limit', 'inter', 'fastinter', 'downinter', 'rise', 'fall', 'port', 'minconn', 'maxconn', 'maxqueue', 'slowstart' and 'weight'.\n",
--
1.7.2.1.45.g54fbc
Willy Tarreau
Re: [PATCH 1/4] MINOR: Add release callback to si_applet
April 05, 2012 10:50AM
Patch applied, thanks Aman.

Willy
On Mon, Apr 02, 2012 at 06:57:56PM -0700, Aman Gupta wrote:
> diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
> index eb44a36..d328881 100644
> --- a/include/proto/dumpstats.h
> +++ b/include/proto/dumpstats.h
> @@ -55,6 +55,8 @@
> #define STAT_CLI_O_TAB 8 /* dump tables */
> #define STAT_CLI_O_CLR 9 /* clear tables */
>
> +#define STAT_CLI_EVENTS 8 /* event stream */

This one should apparently be 10 otherwise it conflicts with dump tables above.
Probably that replacing these defines with an enum would fix the issue once for
all BTW.

> +/* Callback to release a cli session.
> + */
> +static void cli_session_release(struct stream_interface *si)
> +{
> + /* remove if registered as event listener */
> + stats_event_listener_remove(si);

Are you sure you don't need to check if (si->applet.st0 == STAT_CLI_EVENTS) here ?

The rest looks good. If you want I can apply the changes above with the patch.

Thanks,
Willy
On Thu, Apr 5, 2012 at 1:51 AM, Willy Tarreau <[email protected]> wrote:
> On Mon, Apr 02, 2012 at 06:57:56PM -0700, Aman Gupta wrote:
>> diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
>> index eb44a36..d328881 100644
>> --- a/include/proto/dumpstats.h
>> +++ b/include/proto/dumpstats.h
>> @@ -55,6 +55,8 @@
>>  #define STAT_CLI_O_TAB  8   /* dump tables */
>>  #define STAT_CLI_O_CLR  9   /* clear tables */
>>

>> +#define STAT_CLI_EVENTS 8   /* event stream */
>
> This one should apparently be 10 otherwise it conflicts with dump tables above.
> Probably that replacing these defines with an enum would fix the issue once for
> all BTW.

Oops, missed this in the forward port.

>
>> +/* Callback to release a cli session.
>> + */
>> +static void cli_session_release(struct stream_interface *si)
>> +{
>> +     /* remove if registered as event listener */
>> +     stats_event_listener_remove(si);
>
> Are you sure you don't need to check if (si->applet.st0 == STAT_CLI_EVENTS) here ?

Yep, you're right.

>
> The rest looks good. If you want I can apply the changes above with the patch.

Sounds good, thanks!

Aman

>
> Thanks,
> Willy
>
Hi Aman,

On Fri, Apr 06, 2012 at 02:24:16AM -0700, Aman Gupta wrote:
> On Thu, Apr 5, 2012 at 1:51 AM, Willy Tarreau <[email protected]> wrote:
> > On Mon, Apr 02, 2012 at 06:57:56PM -0700, Aman Gupta wrote:
> >> diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
> >> index eb44a36..d328881 100644
> >> --- a/include/proto/dumpstats.h
> >> +++ b/include/proto/dumpstats.h
> >> @@ -55,6 +55,8 @@
> >>  #define STAT_CLI_O_TAB  8   /* dump tables */
> >>  #define STAT_CLI_O_CLR  9   /* clear tables */
> >>
>
> >> +#define STAT_CLI_EVENTS 8   /* event stream */
> >
> > This one should apparently be 10 otherwise it conflicts with dump tables above.
> > Probably that replacing these defines with an enum would fix the issue once for
> > all BTW.
>
> Oops, missed this in the forward port.

OK fixed.

> >
> >> +/* Callback to release a cli session.
> >> + */
> >> +static void cli_session_release(struct stream_interface *si)
> >> +{
> >> +     /* remove if registered as event listener */
> >> +     stats_event_listener_remove(si);
> >
> > Are you sure you don't need to check if (si->applet.st0 == STAT_CLI_EVENTS) here ?
>
> Yep, you're right.

Fixed too.

I have noted two other issues :
- the Close event is not logged in HTTP mode, I didn't yet find why

- I managed to get a segfault when killing the socat attached to the
stats socket. I found that it was due to some missing calls to
stats_event_listener_remove() before switch the st0 state to CLI_END.
After adding them, the issue is gone.

For now I'm holding on the patch in another branch, hoping to find the
time to fix remaining issues soon enough to get it merged quickly.

I'm attaching the updated patch for reference.

Cheers,
Willy

From 552228f8d6ca3a85b70048a765e274ee9c1d1f44 Mon Sep 17 00:00:00 2001
From: Aman Gupta <[email protected]>
Date: Mon, 2 Apr 2012 18:57:56 -0700
Subject: [PATCH] MAJOR: Add `debug sess` command to unix socket stats interface

This is used to display accepted, connected and closed sessions
in real time on the stats socket.
---
doc/configuration.txt | 22 ++++
include/proto/dumpstats.h | 5 +
include/types/stream_interface.h | 5 +
src/dumpstats.c | 247 +++++++++++++++++++++++++++++++++++++-
src/session.c | 9 ++
5 files changed, 285 insertions(+), 3 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 782bda9..35e52ae 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -10158,6 +10158,28 @@ shutdown sessions <backend>/<server>
maintenance mode, for instance. Such terminated sessions are reported with a
'K' flag in the logs.

+debug sess [proxy:<proxy_name>[:<server_name>]]
+
+ Dump a stream of events about sessions as they are added and removed.
+ The possible event formats are "Forward" and "Close":
+
+ "F <session_id> <in_peer> - <in_sock> | <out_sock> - <out_peer>\n"
+ "C <session_id>\n"
+
+ Streaming will continue until a new command is received or the
+ connection is closed. If <proxy_name> or <server_name> is specified, limit to
+ events concerning only the proxy and server specified.
+
+ This command is restricted and can only be issued on sockets configured
+ for levels "operator" or "admin".
+
+ Example:
+ >>> $ echo "set timeout cli 3600; debug sess" | socat stdio,ignoreeof /tmp/sock1
+ F 1 127.0.0.1:50869 - 127.0.0.1:9418 | 127.0.0.1:50870 - 127.0.0.1:6000
+ C 1
+ F 2 127.0.0.1:50874 - 127.0.0.1:9418 | 127.0.0.1:50875 - 127.0.0.1:6000
+ C 2
+
/*
* Local variables:
* fill-column: 79
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index 319ab48..bf091ed 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -55,9 +55,14 @@
#define STAT_CLI_O_TAB 8 /* dump tables */
#define STAT_CLI_O_CLR 9 /* clear tables */

+#define STAT_CLI_EVENTS 10 /* events stream (debug sess) */
+
extern struct si_applet http_stats_applet;
+extern int stats_event_enabled;

void stats_io_handler(struct stream_interface *si);
+void stats_event_new_session(struct session *s);
+void stats_event_end_session(struct session *s);


#endif /* _PROTO_DUMPSTATS_H */
diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h
index dae36ef..403e264 100644
--- a/include/types/stream_interface.h
+++ b/include/types/stream_interface.h
@@ -169,6 +169,11 @@ struct stream_interface {
int bol; /* pointer to beginning of current line */
} errors;
struct {
+ struct list list; /* list of stats streams in the STAT_CLI_EVENTS state */
+ struct proxy *px; /* if not NULL, only send events associated with this proxy */
+ struct server *srv; /* if not NULL, only send events associated with this server */
+ } events;
+ struct {
void *target; /* table we want to dump, or NULL for all */
struct proxy *proxy; /* table being currently dumped (first if NULL) */
struct stksess *entry; /* last entry we were trying to dump (or first if NULL) */
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 88a5285..8ec0e71 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -86,6 +86,7 @@ static const char stats_sock_usage_msg[] =
" disable : put a server or frontend in maintenance mode\n"
" enable : re-enable a server or frontend which is in maintenance mode\n"
" shutdown : kill a session or a frontend (eg:to release listening ports)\n"
+ " debug sess : stream events about proxied sessions\n"
"";

static const char stats_permission_denied_msg[] =
@@ -116,6 +117,77 @@ enum {

extern const char *stat_status_codes[];

+/* Keep track of sessions that want streaming events (STAT_CLI_EVENT).
+ */
+int stats_event_enabled = 0;
+static struct list stats_event_listeners = LIST_HEAD_INIT(stats_event_listeners);
+
+/* Add a session to the list of event listeners.
+ */
+static inline void stats_event_listener_add(struct stream_interface *si)
+{
+ LIST_ADDQ(&stats_event_listeners, &si->applet.ctx.events.list);
+ stats_event_enabled = 1;
+}
+
+/* Remove a session from the list of listeners, but only if it is a
+ * registered listener. This enables us to invoke the method on all
+ * disconnecting stats sockets to ensure they are cleaned up, regardless
+ * of how many times they switch between streaming and other commands.
+ */
+static inline void stats_event_listener_remove(struct stream_interface *si)
+{
+ int found = 0;
+ struct stream_interface *curr;
+ list_for_each_entry(curr, &stats_event_listeners, applet.ctx.events.list) {
+ if (curr == si) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (found) {
+ si->applet.ctx.events.px = NULL;
+ si->applet.ctx.events.srv = NULL;
+ LIST_DEL(&si->applet.ctx.events.list);
+ }
+
+ if (LIST_ISEMPTY(&stats_event_listeners))
+ stats_event_enabled = 0;
+
+ /* Re-initialize stats output */
+ memset(&si->applet.ctx.stats, 0, sizeof(si->applet.ctx.stats));
+}
+
+/* Send a message to all registered event listeners.
+ */
+static inline void stats_event_listener_message_all(char *msg, struct session *s)
+{
+ struct stream_interface *curr;
+
+ list_for_each_entry(curr, &stats_event_listeners, applet.ctx.events.list) {
+ struct proxy *px;
+ struct server *srv;
+
+ if (!(curr->flags & SI_FL_DONT_WAKE) && curr->owner) {
+ /* filter by proxy and server if required */
+ if ((px = curr->applet.ctx.events.px)) {
+ if (s->be != px && s->fe != px)
+ continue; /* ignore */
+ if ((srv = curr->applet.ctx.events.srv)) {
+ if (target_srv(&s->target) != srv)
+ continue; /* ignore */
+ }
+ }
+
+ if (buffer_feed(curr->ib, msg) == -1) {
+ curr->ib->flags |= BF_SEND_DONTWAIT;
+ task_wakeup(curr->owner, TASK_WOKEN_MSG);
+ }
+ }
+ }
+}
+
/* This function is called from the session-level accept() in order to instanciate
* a new stats socket. It returns a positive value upon success, 0 if the connection
* needs to be closed and ignored, or a negative value upon critical failure.
@@ -774,7 +846,54 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line)
args[arg] = line;

si->applet.ctx.stats.flags = 0;
- if (strcmp(args[0], "show") == 0) {
+ if (strcmp(args[0], "debug") == 0) {
+ if (strcmp(args[1], "sess") == 0) {
+ if (s->listener->perm.ux.level < ACCESS_LVL_OPER) {
+ si->applet.ctx.cli.msg = stats_permission_denied_msg;
+ si->applet.st0 = STAT_CLI_PRINT;
+ return 1;
+ }
+ if (*args[2] && !strncmp(args[2], "proxy", 5)) {
+ struct proxy *px = NULL;
+ struct server *srv = NULL;
+ char *px_name = args[2] + 6, *srv_name;
+
+ if ((srv_name = strchr(px_name, ':'))) {
+ *srv_name = 0;
+ srv_name += 1;
+ }
+
+ px = findproxy(px_name, PR_CAP_FE|PR_CAP_BE);
+ if (!px) {
+ si->applet.ctx.cli.msg = "Invalid proxy filter for event stream.";
+ si->applet.st0 = STAT_CLI_PRINT;
+ return 1;
+ }
+
+ if (srv_name && *srv_name) {
+ srv = findserver(px, srv_name);
+ if (!srv) {
+ si->applet.ctx.cli.msg = "Invalid server filter for event stream.";
+ si->applet.st0 = STAT_CLI_PRINT;
+ return 1;
+ }
+ }
+
+ si->applet.ctx.events.srv = srv;
+ si->applet.ctx.events.px = px;
+ } else {
+ si->applet.ctx.events.srv = NULL;
+ si->applet.ctx.events.px = NULL;
+ }
+
+ stats_event_listener_add(si);
+ si->applet.st0 = STAT_CLI_EVENTS;
+ }
+ else { /* not "sess" */
+ return 0;
+ }
+ }
+ else if (strcmp(args[0], "show") == 0) {
if (strcmp(args[1], "stat") == 0) {
if (*args[2] && *args[3] && *args[4]) {
si->applet.ctx.stats.flags |= STAT_BOUND;
@@ -1348,6 +1467,15 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line)
return 1;
}

+/* Callback to release a cli session.
+ */
+static void cli_session_release(struct stream_interface *si)
+{
+ /* remove if registered as event listener */
+ if (si->applet.st0 == STAT_CLI_EVENTS)
+ stats_event_listener_remove(si);
+}
+
/* This I/O handler runs as an applet embedded in a stream interface. It is
* used to processes I/O from/to the stats unix socket. The system relies on a
* state machine handling requests and various responses. We read a request,
@@ -1379,7 +1507,7 @@ static void cli_io_handler(struct stream_interface *si)
si->shutw(si);
break;
}
- else if (si->applet.st0 == STAT_CLI_GETREQ) {
+ else if (si->applet.st0 == STAT_CLI_GETREQ || si->applet.st0 == STAT_CLI_EVENTS) {
/* ensure we have some output room left in the event we
* would want to return some info right after parsing.
*/
@@ -1390,6 +1518,8 @@ static void cli_io_handler(struct stream_interface *si)
if (reql <= 0) { /* closed or EOL not found */
if (reql == 0)
break;
+ if (si->applet.st0 == STAT_CLI_EVENTS)
+ stats_event_listener_remove(si);
si->applet.st0 = STAT_CLI_END;
continue;
}
@@ -1410,6 +1540,8 @@ static void cli_io_handler(struct stream_interface *si)
*/
len = reql - 1;
if (trash[len] != '\n') {
+ if (si->applet.st0 == STAT_CLI_EVENTS)
+ stats_event_listener_remove(si);
si->applet.st0 = STAT_CLI_END;
continue;
}
@@ -1419,7 +1551,10 @@ static void cli_io_handler(struct stream_interface *si)

trash[len] = '\0';

+ if (si->applet.st0 == STAT_CLI_EVENTS)
+ stats_event_listener_remove(si);
si->applet.st0 = STAT_CLI_PROMPT;
+
if (len) {
if (strcmp(trash, "quit") == 0) {
si->applet.st0 = STAT_CLI_END;
@@ -3753,6 +3888,112 @@ static int stats_table_request(struct stream_interface *si, bool show)
return 1;
}

+/* Called whenever a new session is successfully established (reaches
+ * SI_ST_EST). If there are any stats sockets listening in the
+ * STAT_CLI_EVENTS state, they will be notified of this session's unique
+ * id, along with the sockname and peername of both sides of the session.
+ */
+void stats_event_new_session(struct session *s)
+{
+ if (LIST_ISEMPTY(&stats_event_listeners))
+ return;
+
+ char addrs[4][INET6_ADDRSTRLEN + strlen(":65535") + 1] = {"","","",""};
+ int i;
+
+ struct stream_interface *si0 = &s->si[0], *si1 = &s->si[1];
+ socklen_t namelen;
+
+ /* si0 from/to = peer/sock */
+ if (!(si0->flags & SI_FL_FROM_SET)) {
+ namelen = sizeof(si0->addr.from);
+ getpeername(si0->fd, (struct sockaddr *)&si0->addr.from, &namelen);
+ si0->flags |= SI_FL_FROM_SET;
+ }
+ if (!(si0->flags & SI_FL_TO_SET)) {
+ namelen = sizeof(si0->addr.to);
+ getsockname(si0->fd, (struct sockaddr *)&si0->addr.to, &namelen);
+ si0->flags |= SI_FL_TO_SET;
+ }
+
+ /* si1 from/to = sock/peer (reversed) */
+ if (!(si1->flags & SI_FL_FROM_SET)) {
+ namelen = sizeof(si1->addr.from);
+ getsockname(si1->fd, (struct sockaddr *)&si1->addr.from, &namelen);
+ si1->flags |= SI_FL_FROM_SET;
+ }
+ if (!(si1->flags & SI_FL_TO_SET)) {
+ namelen = sizeof(si1->addr.to);
+ getpeername(si1->fd, (struct sockaddr *)&si1->addr.to, &namelen);
+ si1->flags |= SI_FL_TO_SET;
+ }
+
+ for(i = 0; i < 4; i++) {
+ struct sockaddr_storage *sock;
+ const void *sin_addr = NULL;
+ int port = 0;
+
+ switch (i) {
+ case 0: // inbound peer
+ sock = &si0->addr.from;
+ break;
+ case 1: // inbound sock
+ sock = &si0->addr.to;
+ break;
+ case 2: // outbound sock
+ sock = &si1->addr.from;
+ break;
+ case 3: // outbound peer
+ sock = &si1->addr.to;
+ break;
+ }
+
+ switch (sock->ss_family) {
+ case AF_INET:
+ sin_addr = (const void *)&((struct sockaddr_in *)sock)->sin_addr;
+ port = ntohs(((struct sockaddr_in *)sock)->sin_port);
+ break;
+ case AF_INET6:
+ sin_addr = (const void *)&((struct sockaddr_in6 *)sock)->sin6_addr;
+ port = ntohs(((struct sockaddr_in6 *)sock)->sin6_port);
+ break;
+ }
+
+ switch (sock->ss_family) {
+ case AF_INET:
+ case AF_INET6:
+ inet_ntop(sock->ss_family, sin_addr, addrs, sizeof(addrs));
+ snprintf(addrs+strlen(addrs), sizeof(addrs)-strlen(addrs)-1, ":%d", port);
+ break;
+ case AF_UNIX:
+ sprintf(addrs, "unix:%d", s->listener->luid);
+ break;
+ default:
+ sprintf(addrs, "%s", "unknown");
+ }
+ }
+
+ snprintf(trash, sizeof(trash), "F %u %s - %s | %s - %s\n", s->uniq_id,
+ addrs[0], // inbound peer
+ addrs[1], // inbound sock
+ addrs[2], // outbound sock
+ addrs[3] // outbound peer
+ );
+ stats_event_listener_message_all(trash, s);
+}
+
+/* Called when the session argument's s->si[1]->state goes from SI_ST_EST
+ * to SI_ST_CLO. All stats listeners are notified of this destroy event.
+ */
+void stats_event_end_session(struct session *s)
+{
+ if (LIST_ISEMPTY(&stats_event_listeners))
+ return;
+
+ snprintf(trash, sizeof(trash), "C %u\n", s->uniq_id);
+ stats_event_listener_message_all(trash, s);
+}
+
/* print a line of text buffer (limited to 70 bytes) to <out>. The format is :
* <2 spaces> <offset=5 digits> <space or plus> <space> <70 chars max> <\n>
* which is 60 chars per line. Non-printable chars \t, \n, \r and \e are
@@ -3967,7 +4208,7 @@ struct si_applet http_stats_applet = {
static struct si_applet cli_applet = {
.name = "<CLI>", /* used for logging */
.fct = cli_io_handler,
- .release = NULL,
+ .release = cli_session_release,
};

static struct cfg_kw_list cfg_kws = {{ },{
diff --git a/src/session.c b/src/session.c
index b90b254..adbd11e 100644
--- a/src/session.c
+++ b/src/session.c
@@ -708,6 +708,9 @@ static void sess_establish(struct session *s, struct stream_interface *si)
rep->rto = s->be->timeout.server;
}
req->wex = TICK_ETERNITY;
+
+ if (unlikely(stats_event_enabled))
+ stats_event_new_session(s);
}

/* Update stream interface status for input states SI_ST_ASS, SI_ST_QUE, SI_ST_TAR.
@@ -2203,6 +2206,12 @@ struct task *process_session(struct task *t)
s->do_log(s);
}

+ if (unlikely(stats_event_enabled)) {
+ if (s->si[1].state == SI_ST_CLO &&
+ s->si[1].prev_state == SI_ST_EST)
+ stats_event_end_session(s);
+ }
+
/* the task MUST not be in the run queue anymore */
session_free(s);
task_delete(t);
--
1.7.2.1.45.g54fbc
Sorry, only registered users may post in this forum.

Click here to login