From fa7e10251def8336519e6de619bdd3efc3a6b3fc Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sun, 19 Oct 2008 07:30:41 +0200 Subject: [PATCH] [MAJOR] rework of the server FSM srv_state has been removed from HTTP state machines, and states have been split in either TCP states or analyzers. For instance, the TARPIT state has just become a simple analyzer. New flags have been added to the struct buffer to compensate this. The high-level stream processors sometimes need to force a disconnection without touching a file-descriptor (eg: report an error). But if they touched BF_SHUTW or BF_SHUTR, the file descriptor would not be closed. Thus, the two SHUT?_NOW flags have been added so that an application can request a forced close which the stream interface will be forced to obey. During this change, a new BF_HIJACK flag was added. It will be used for data generation, eg during a stats dump. It prevents the producer on a buffer from sending data into it. BF_SHUTR_NOW /* the producer must shut down for reads ASAP */ BF_SHUTW_NOW /* the consumer must shut down for writes ASAP */ BF_HIJACK /* the producer is temporarily replaced */ BF_SHUTW_NOW has precedence over BF_HIJACK. BF_HIJACK has precedence over BF_MAY_FORWARD (so that it does not need it). New functions buffer_shutr_now(), buffer_shutw_now(), buffer_abort() are provided to manipulate BF_SHUT* flags. A new type "stream_interface" has been added to describe both sides of a buffer. A stream interface has states and error reporting. The session now has two stream interfaces (one per side). Each buffer has stream_interface pointers to both consumer and producer sides. The server-side file descriptor has moved to its stream interface, so that even the buffer has access to it. process_srv() has been split into three parts : - tcp_get_connection() obtains a connection to the server - tcp_connection_failed() tests if a previously attempted connection has succeeded or not. - process_srv_data() only manages the data phase, and in this sense should be roughly equivalent to process_cli. Little code has been removed, and a lot of old code has been left in comments for now. --- include/proto/buffers.h | 19 + include/proto/proto_http.h | 3 +- include/proto/stream_sock.h | 3 +- include/types/buffers.h | 14 +- include/types/session.h | 3 +- include/types/stream_interface.h | 73 ++ src/backend.c | 76 +- src/client.c | 17 +- src/proto_http.c | 2096 ++++++++++++++++++++++-------- src/proto_uxst.c | 3 +- src/senddata.c | 15 +- src/stream_sock.c | 19 +- 12 files changed, 1751 insertions(+), 590 deletions(-) create mode 100644 include/types/stream_interface.h diff --git a/include/proto/buffers.h b/include/proto/buffers.h index 4ceedc077e..3b5e2bdc70 100644 --- a/include/proto/buffers.h +++ b/include/proto/buffers.h @@ -47,6 +47,7 @@ static inline void buffer_init(struct buffer *buf) { buf->l = buf->total = 0; buf->analysers = 0; + buf->cons = NULL; buf->flags = BF_EMPTY; buf->r = buf->lr = buf->w = buf->data; buf->rlim = buf->data + BUFSIZE; @@ -89,6 +90,24 @@ static inline void buffer_shutw(struct buffer *buf) buf->flags |= BF_SHUTW; } +/* marks the buffer as "shutdown" ASAP for reads */ +static inline void buffer_shutr_now(struct buffer *buf) +{ + buf->flags |= BF_SHUTR_NOW; +} + +/* marks the buffer as "shutdown" ASAP for writes */ +static inline void buffer_shutw_now(struct buffer *buf) +{ + buf->flags |= BF_SHUTW_NOW; +} + +/* marks the buffer as "shutdown" ASAP in both directions */ +static inline void buffer_abort(struct buffer *buf) +{ + buf->flags |= BF_SHUTR_NOW | BF_SHUTW_NOW; +} + /* returns the maximum number of bytes writable at once in this buffer */ static inline int buffer_max(const struct buffer *buf) { diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index fc1e01d38c..17f302dc5c 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -60,7 +60,8 @@ extern const char http_is_ver_token[256]; int event_accept(int fd); void process_session(struct task *t, int *next); int process_cli(struct session *t); -int process_srv(struct session *t); +int process_srv_data(struct session *t); +int process_srv_conn(struct session *t); int process_request(struct session *t); int process_response(struct session *t); diff --git a/include/proto/stream_sock.h b/include/proto/stream_sock.h index 5619e60dca..d57ddf5b64 100644 --- a/include/proto/stream_sock.h +++ b/include/proto/stream_sock.h @@ -2,7 +2,7 @@ include/proto/stream_sock.h This file contains client-side definitions. - Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -27,6 +27,7 @@ #include #include +#include /* main event functions used to move data between sockets and buffers */ diff --git a/include/types/buffers.h b/include/types/buffers.h index f516ffe14e..637427125d 100644 --- a/include/types/buffers.h +++ b/include/types/buffers.h @@ -57,6 +57,15 @@ #define BF_READ_TIMEOUT 32768 /* timeout while waiting for producer */ #define BF_WRITE_TIMEOUT 65536 /* timeout while waiting for consumer */ +/* When either BF_SHUTR_NOW or BF_HIJACK is set, it is strictly forbidden for + * the stream interface to alter the buffer contents. When BF_SHUTW_NOW is set, + * it is strictly forbidden for the stream interface to send anything from the + * buffer. + */ +#define BF_SHUTR_NOW 131072 /* the producer must shut down for reads ASAP */ +#define BF_SHUTW_NOW 262144 /* the consumer must shut down for writes ASAP */ +#define BF_HIJACK 524288 /* the producer is temporarily replaced */ + /* Analysers (buffer->analysers). * Those bits indicate that there are some processing to do on the buffer @@ -68,7 +77,8 @@ #define AN_REQ_INSPECT 0x00000001 /* inspect request contents */ #define AN_REQ_HTTP_HDR 0x00000002 /* inspect HTTP request headers */ #define AN_REQ_HTTP_BODY 0x00000004 /* inspect HTTP request body */ -#define AN_RTR_HTTP_HDR 0x00000008 /* inspect HTTP response headers */ +#define AN_REQ_HTTP_TARPIT 0x00000008 /* wait for end of HTTP tarpit */ +#define AN_RTR_HTTP_HDR 0x00000010 /* inspect HTTP response headers */ /* describes a chunk of string */ struct chunk { @@ -91,6 +101,8 @@ struct buffer { unsigned char xfer_large; /* number of consecutive large xfers */ unsigned char xfer_small; /* number of consecutive small xfers */ unsigned long long total; /* total data read */ + struct stream_interface *prod; /* producer attached to this buffer */ + struct stream_interface *cons; /* consumer attached to this buffer */ char data[BUFSIZE]; }; diff --git a/include/types/session.h b/include/types/session.h index 3035956072..2d0439b7d6 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -36,6 +36,7 @@ #include #include #include +#include #include @@ -156,7 +157,6 @@ struct session { struct proxy *fe; /* the proxy this session depends on for the client side */ struct proxy *be; /* the proxy this session depends on for the server side */ int cli_fd; /* the client side fd */ - int srv_fd; /* the server side fd */ int cli_state; /* state of the client side */ int srv_state; /* state of the server side */ int conn_retries; /* number of connect retries left */ @@ -164,6 +164,7 @@ struct session { unsigned term_trace; /* term trace: 4*8 bits indicating which part of the code closed */ struct buffer *req; /* request buffer */ struct buffer *rep; /* response buffer */ + struct stream_interface si[2]; /* client and server stream interfaces */ struct sockaddr_storage cli_addr; /* the client address */ struct sockaddr_storage frt_addr; /* the frontend address reached by the client if SN_FRT_ADDR_SET is set */ struct sockaddr_in srv_addr; /* the address to connect to */ diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h new file mode 100644 index 0000000000..edfb7583b2 --- /dev/null +++ b/include/types/stream_interface.h @@ -0,0 +1,73 @@ +/* + include/types/stream_interface.h + This file describes the stream_interface struct and associated constants. + + Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu + + 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_STREAM_INTERFACE_H +#define _TYPES_STREAM_INTERFACE_H + +#include + +#include + +/* A stream interface must have its own errors independantly of the buffer's, + * so that applications can rely on what the buffer reports while the stream + * interface is performing some retries (eg: connection error). + */ +enum { + SI_ST_INI = 0, /* interface not initialized yet and might not exist */ + SI_ST_QUE, /* interface waiting in queue */ + SI_ST_TAR, /* interface in turn-around state after failed connect attempt */ + SI_ST_ASS, /* server just assigned to this interface */ + SI_ST_CON, /* initiated connection request (resource exists) */ + SI_ST_EST, /* connection established (resource exists) */ + SI_ST_CLO, /* stream interface closed, might not existing anymore */ +}; + +/* error types reported on the streams interface for more accurate reporting */ +enum { + SI_ET_NONE = 0, /* no error yet, leave it to zero */ + SI_ET_QUEUE_TO, /* queue timeout */ + SI_ET_QUEUE_ERR, /* queue error (eg: full) */ + SI_ET_QUEUE_ABRT, /* aborted in queue by external cause */ + SI_ET_CONN_TO, /* connection timeout */ + SI_ET_CONN_ERR, /* connection error (eg: no server available) */ + SI_ET_CONN_ABRT, /* connection aborted by external cause (eg: abort) */ + SI_ET_CONN_OTHER, /* connection aborted for other reason (eg: 500) */ + SI_ET_DATA_TO, /* timeout during data phase */ + SI_ET_DATA_ERR, /* error during data phase */ + SI_ET_DATA_ABRT, /* data phase aborted by external cause */ +}; + +struct stream_interface { + unsigned int state; /* SI_ST* */ + int err_type; /* first error detected, one of SI_ET_* */ + void *err_loc; /* commonly the server, NULL when SI_ET_NONE */ + int fd; /* file descriptor for a stream driver when known */ +}; + + +#endif /* _TYPES_STREAM_INTERFACE_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/backend.c b/src/backend.c index 2a39d77310..f51ac8fc38 100644 --- a/src/backend.c +++ b/src/backend.c @@ -1646,7 +1646,7 @@ int connect_server(struct session *s) return SN_ERR_INTERNAL; } - if ((fd = s->srv_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + if ((fd = s->req->cons->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { qfprintf(stderr, "Cannot get a server socket.\n"); if (errno == ENFILE) @@ -1818,6 +1818,7 @@ int connect_server(struct session *s) fd_insert(fd); EV_FD_SET(fd, DIR_WR); /* for connect status */ + s->req->cons->state = SI_ST_CON; if (s->srv) { s->srv->cur_sess++; if (s->srv->cur_sess > s->srv->cur_sess_max) @@ -1833,8 +1834,8 @@ int connect_server(struct session *s) /* * This function checks the retry count during the connect() job. - * It updates the session's srv_state and retries, so that the caller knows - * what it has to do. It uses the last connection error to set the log when + * It updates the session's retries, so that the caller knows what it + * has to do. It uses the last connection error to set the log when * it expires. It returns 1 when it has expired, and 0 otherwise. */ int srv_count_retry_down(struct session *t, int conn_err) @@ -1844,9 +1845,15 @@ int srv_count_retry_down(struct session *t, int conn_err) if (t->conn_retries < 0) { /* if not retryable anymore, let's abort */ - t->req->wex = TICK_ETERNITY; - srv_close_with_err(t, conn_err, SN_FINST_C, - 503, error_message(t, HTTP_ERR_503)); + //t->req->wex = TICK_ETERNITY; + //srv_close_with_err(t, conn_err, SN_FINST_C, + // 503, error_message(t, HTTP_ERR_503)); + + if (!t->req->cons->err_type) { + t->req->cons->err_type = SI_ET_CONN_ERR; + t->req->cons->err_loc = t->srv; + } + if (t->srv) t->srv->failed_conns++; t->be->failed_conns++; @@ -1864,9 +1871,9 @@ int srv_count_retry_down(struct session *t, int conn_err) /* * This function performs the retryable part of the connect() job. - * It updates the session's srv_state and retries, so that the caller knows - * what it has to do. It returns 1 when it breaks out of the loop, or 0 if - * it needs to redispatch. + * It updates the session's and retries, so that the caller knows + * what it has to do. It returns 1 when it breaks out of the loop, + * or 0 if it needs to redispatch. */ int srv_retryable_connect(struct session *t) { @@ -1882,15 +1889,19 @@ int srv_retryable_connect(struct session *t) case SN_ERR_NONE: //fprintf(stderr,"0: c=%d, s=%d\n", c, s); - t->srv_state = SV_STCONN; if (t->srv) t->srv->cum_sess++; return 1; case SN_ERR_INTERNAL: - t->req->wex = TICK_ETERNITY; - srv_close_with_err(t, SN_ERR_INTERNAL, SN_FINST_C, - 500, error_message(t, HTTP_ERR_500)); + if (!t->req->cons->err_type) { + t->req->cons->err_type = SI_ET_CONN_OTHER; + t->req->cons->err_loc = t->srv; + } + + //t->req->wex = TICK_ETERNITY; + //srv_close_with_err(t, SN_ERR_INTERNAL, SN_FINST_C, + // 500, error_message(t, HTTP_ERR_500)); if (t->srv) t->srv->cum_sess++; if (t->srv) @@ -1902,9 +1913,8 @@ int srv_retryable_connect(struct session *t) return 1; } /* ensure that we have enough retries left */ - if (srv_count_retry_down(t, conn_err)) { + if (srv_count_retry_down(t, conn_err)) return 1; - } } while (t->srv == NULL || t->conn_retries > 0 || !(t->be->options & PR_O_REDISP)); /* We're on our last chance, and the REDISP option was specified. @@ -1959,9 +1969,14 @@ int srv_redispatch_connect(struct session *t) goto redispatch; } - t->req->wex = TICK_ETERNITY; - srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_Q, - 503, error_message(t, HTTP_ERR_503)); + //t->req->wex = TICK_ETERNITY; + //srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_Q, + // 503, error_message(t, HTTP_ERR_503)); + + if (!t->req->cons->err_type) { + t->req->cons->err_type = SI_ET_QUEUE_ERR; + t->req->cons->err_loc = t->srv; + } t->srv->failed_conns++; t->be->failed_conns++; @@ -1969,24 +1984,35 @@ int srv_redispatch_connect(struct session *t) case SRV_STATUS_NOSRV: /* note: it is guaranteed that t->srv == NULL here */ - t->req->wex = TICK_ETERNITY; - srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_C, - 503, error_message(t, HTTP_ERR_503)); + //t->req->wex = TICK_ETERNITY; + //srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_C, + // 503, error_message(t, HTTP_ERR_503)); + + if (!t->req->cons->err_type) { + t->req->cons->err_type = SI_ET_CONN_ERR; + t->req->cons->err_loc = NULL; + } t->be->failed_conns++; return 1; case SRV_STATUS_QUEUED: t->req->wex = tick_add_ifset(now_ms, t->be->timeout.queue); - t->srv_state = SV_STIDLE; + t->req->cons->state = SI_ST_QUE; /* do nothing else and do not wake any other session up */ return 1; case SRV_STATUS_INTERNAL: default: - t->req->wex = TICK_ETERNITY; - srv_close_with_err(t, SN_ERR_INTERNAL, SN_FINST_C, - 500, error_message(t, HTTP_ERR_500)); + //t->req->wex = TICK_ETERNITY; + //srv_close_with_err(t, SN_ERR_INTERNAL, SN_FINST_C, + // 500, error_message(t, HTTP_ERR_500)); + + if (!t->req->cons->err_type) { + t->req->cons->err_type = SI_ET_CONN_OTHER; + t->req->cons->err_loc = t->srv; + } + if (t->srv) t->srv->cum_sess++; if (t->srv) diff --git a/src/client.c b/src/client.c index a6c8ebfb81..5df5c0dff5 100644 --- a/src/client.c +++ b/src/client.c @@ -168,11 +168,19 @@ int event_accept(int fd) { } s->cli_state = CL_STDATA; - s->srv_state = SV_STIDLE; s->req = s->rep = NULL; /* will be allocated later */ + s->si[0].state = SI_ST_EST; + s->si[0].err_type = SI_ET_NONE; + s->si[0].err_loc = NULL; + s->si[0].fd = cfd; s->cli_fd = cfd; - s->srv_fd = -1; + + s->si[1].state = SI_ST_INI; + s->si[1].err_type = SI_ET_NONE; + s->si[1].err_loc = NULL; + s->si[1].fd = -1; /* just to help with debugging */ + s->srv = s->prev_srv = s->srv_conn = NULL; s->pend_pos = NULL; s->conn_retries = s->be->conn_retries; @@ -326,6 +334,9 @@ int event_accept(int fd) { goto out_fail_req; /* no memory */ buffer_init(s->req); + s->req->prod = &s->si[0]; + s->req->cons = &s->si[1]; + if (p->mode == PR_MODE_HTTP) /* reserve some space for header rewriting */ s->req->rlim -= MAXREWRITE; @@ -346,6 +357,8 @@ int event_accept(int fd) { goto out_fail_rep; /* no memory */ buffer_init(s->rep); + s->rep->prod = &s->si[1]; + s->rep->cons = &s->si[0]; s->rep->rto = s->be->timeout.server; s->rep->wto = s->fe->timeout.client; diff --git a/src/proto_http.c b/src/proto_http.c index 700a56e1c0..c065fda21b 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -366,7 +366,6 @@ const char http_is_ver_token[256] = { #ifdef DEBUG_FULL static char *cli_stnames[4] = { "DAT", "SHR", "SHW", "CLS" }; -static char *srv_stnames[6] = { "IDL", "CON", "DAT", "SHR", "SHW", "CLS" }; #endif static void http_sess_log(struct session *s); @@ -537,14 +536,13 @@ int http_find_header(const char *name, return http_find_header2(name, strlen(name), sol, idx, ctx); } -/* This function turns the server state into the SV_STCLOSE, and sets - * indicators accordingly. Note that if is 0, or if the message - * pointer is NULL, then no message is returned. +/* This function shuts down the buffers on the server side, and sets indicators + * accordingly. The server's fd is supposed to already be closed. Note that if + * is 0, or if the message pointer is NULL, then no message is returned. */ void srv_close_with_err(struct session *t, int err, int finst, int status, const struct chunk *msg) { - t->srv_state = SV_STCLOSE; t->rep->flags |= BF_MAY_FORWARD; buffer_shutw(t->req); buffer_shutr(t->rep); @@ -675,16 +673,22 @@ void process_session(struct task *t, int *next) if (tick_is_expired(s->req->rex, now_ms)) s->req->flags |= BF_READ_TIMEOUT; - if (tick_is_expired(s->req->wex, now_ms)) - s->req->flags |= BF_WRITE_TIMEOUT; - - if (tick_is_expired(s->rep->rex, now_ms)) - s->rep->flags |= BF_READ_TIMEOUT; + //if (tick_is_expired(s->req->wex, now_ms)) + // s->req->flags |= BF_WRITE_TIMEOUT; + // + //if (tick_is_expired(s->rep->rex, now_ms)) + // s->rep->flags |= BF_READ_TIMEOUT; if (tick_is_expired(s->rep->wex, now_ms)) s->rep->flags |= BF_WRITE_TIMEOUT; } + //if (fdtab[s->cli_fd].state == FD_STERROR) { + // fprintf(stderr, "s=%p fd=%d req=%p rep=%p cs=%d ss=%d, term=%08x\n", + // s, s->cli_fd, s->req, s->rep, s->cli_state, + // s->si[1].state, s->term_trace); + // sleep(1); + //} do { if (resync & PROCESS_REQ) { resync &= ~PROCESS_REQ; @@ -737,15 +741,31 @@ void process_session(struct task *t, int *next) rpf = s->rep->flags; resync &= ~PROCESS_SRV; - if (process_srv(s)) - resync |= PROCESS_SRV; + if (s->req->cons->state != SI_ST_CLO) { + if (s->req->cons->state < SI_ST_EST && s->req->flags & BF_MAY_FORWARD) + process_srv_conn(s); + + if (s->req->cons->state == SI_ST_EST) { + if (process_srv_data(s)) + resync |= PROCESS_SRV; + } + if (unlikely((s->req->cons->state == SI_ST_CLO) && + (global.mode & MODE_DEBUG) && + (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)))) { + int len; + len = sprintf(trash, "%08x:%s.srvcls[%04x:%04x]\n", + s->uniq_id, s->be->id, (unsigned short)s->cli_fd, (unsigned short)s->req->cons->fd); + write(1, trash, len); + } + } if (rqf != s->req->flags || rpf != s->rep->flags) resync |= PROCESS_ALL & ~PROCESS_SRV; } } while (resync); - if (likely(s->cli_state != CL_STCLOSE || s->srv_state != SV_STCLOSE)) { + if (likely(s->cli_state != CL_STCLOSE || + (s->req->cons->state != SI_ST_CLO && s->req->cons->state != SI_ST_INI))) { if ((s->fe->options & PR_O_CONTSTATS) && (s->flags & SN_BE_ASSIGNED)) session_process_counters(s); @@ -792,7 +812,7 @@ void process_session(struct task *t, int *next) int len; len = sprintf(trash, "%08x:%s.closed[%04x:%04x] (term_trace=0x%08x)\n", s->uniq_id, s->be->id, - (unsigned short)s->cli_fd, (unsigned short)s->srv_fd, + (unsigned short)s->cli_fd, (unsigned short)s->req->cons->fd, s->term_trace); write(1, trash, len); } @@ -1655,9 +1675,9 @@ int process_request(struct session *t) struct buffer *req = t->req; struct buffer *rep = t->rep; - DPRINTF(stderr,"[%u] process_req: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x analysers=%02x\n", - now_ms, - cli_stnames[t->cli_state], srv_stnames[t->srv_state], + DPRINTF(stderr,"[%u] %s: c=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x analysers=%02x\n", + now_ms, __FUNCTION__, + cli_stnames[t->cli_state], t->cli_fd >= 0 && fdtab[t->cli_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->cli_fd, DIR_RD) : 0, t->cli_fd >= 0 && fdtab[t->cli_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->cli_fd, DIR_WR) : 0, req->rex, rep->wex, req->flags, rep->flags, req->analysers); @@ -1729,10 +1749,11 @@ int process_request(struct session *t) if (ret) { /* we have a matching rule. */ if (rule->action == TCP_ACT_REJECT) { - buffer_shutr(req); - buffer_shutw(rep); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; + buffer_abort(req); + buffer_abort(rep); + //FIXME: this delete this + //fd_delete(t->cli_fd); + //t->cli_state = CL_STCLOSE; req->analysers = 0; t->fe->failed_req++; if (!(t->flags & SN_ERR_MASK)) @@ -1829,7 +1850,7 @@ int process_request(struct session *t) } /* 2: have we encountered a close ? */ - else if (req->flags & BF_READ_NULL) { + else if (req->flags & (BF_READ_NULL | BF_SHUTR)) { txn->status = 400; client_retnclose(t, error_message(t, HTTP_ERR_400)); msg->msg_state = HTTP_MSG_ERROR; @@ -1858,8 +1879,8 @@ int process_request(struct session *t) return 0; } - /* 4: have we encountered a read error or did we have to shutdown ? */ - else if (req->flags & (BF_READ_ERROR | BF_SHUTR)) { + /* 4: have we encountered a read error ? */ + else if (req->flags & BF_READ_ERROR) { /* we cannot return any message on error */ msg->msg_state = HTTP_MSG_ERROR; req->analysers = 0; @@ -1898,6 +1919,10 @@ int process_request(struct session *t) */ txn->meth = find_http_meth(&req->data[msg->som], msg->sl.rq.m_l); + /* we can make use of server redirect on GET and HEAD */ + if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD) + t->flags |= SN_REDIRECTABLE; + /* * 2: check if the URI matches the monitor_uri. * We have to do this for every request which gets in, because @@ -2498,17 +2523,18 @@ int process_request(struct session *t) /* When a connection is tarpitted, we use the tarpit timeout, * which may be the same as the connect timeout if unspecified. * If unset, then set it to zero because we really want it to - * eventually expire. - * FIXME: this part should be moved elsewhere (eg: on the server side) + * eventually expire. We build the tarpit as an analyser. */ if (txn->flags & TX_CLTARPIT) { buffer_flush(t->req); /* flush the request so that we can drop the connection early * if the client closes first. */ - req->wex = tick_add_ifset(now_ms, t->be->timeout.tarpit); - if (!req->wex) - req->wex = now_ms; + req->flags &= ~BF_MAY_FORWARD; + req->analysers |= AN_REQ_HTTP_TARPIT; + req->analyse_exp = tick_add_ifset(now_ms, t->be->timeout.tarpit); + if (!req->analyse_exp) + req->analyse_exp = now_ms; } /* OK let's go on with the BODY now */ @@ -2530,6 +2556,42 @@ int process_request(struct session *t) ; // to keep gcc happy } + if (req->analysers & AN_REQ_HTTP_TARPIT) { + struct http_txn *txn = &t->txn; + + /* This connection is being tarpitted. The CLIENT side has + * already set the connect expiration date to the right + * timeout. We just have to check that the client is still + * there and that the timeout has not expired. + */ + if ((req->flags & (BF_READ_NULL|BF_READ_ERROR)) == 0 && + !tick_is_expired(req->analyse_exp, now_ms)) + return 0; + + /* We will set the queue timer to the time spent, just for + * logging purposes. We fake a 500 server error, so that the + * attacker will not suspect his connection has been tarpitted. + * It will not cause trouble to the logs because we can exclude + * the tarpitted connections by filtering on the 'PT' status flags. + */ + trace_term(t, TT_HTTP_SRV_2); + t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); + + txn->status = 500; + if (req->flags != BF_READ_ERROR) + client_retnclose(t, error_message(t, HTTP_ERR_500)); + + req->analysers = 0; + req->analyse_exp = TICK_ETERNITY; + + t->fe->failed_req++; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_T; + return 0; + } + if (req->analysers & AN_REQ_HTTP_BODY) { /* We have to parse the HTTP request body to find any required data. * "balance url_param check_post" should have been the only way to get @@ -2585,7 +2647,7 @@ int process_request(struct session *t) * buffer closed). */ if (req->l - body >= limit || /* enough bytes! */ - req->flags & (BF_FULL | BF_READ_ERROR | BF_READ_NULL | BF_READ_TIMEOUT) || + req->flags & (BF_FULL | BF_READ_ERROR | BF_SHUTR | BF_READ_NULL | BF_READ_TIMEOUT) || tick_is_expired(req->analyse_exp, now_ms)) { /* The situation will not evolve, so let's give up on the analysis. */ t->logs.tv_request = now; /* update the request timer to reflect full request */ @@ -2610,13 +2672,13 @@ int process_request(struct session *t) * probably reduce one day's debugging session. */ #ifdef DEBUG_DEV - if (req->analysers & ~(AN_REQ_INSPECT | AN_REQ_HTTP_HDR | AN_REQ_HTTP_BODY)) { + if (req->analysers & ~(AN_REQ_INSPECT | AN_REQ_HTTP_HDR | AN_REQ_HTTP_TARPIT | AN_REQ_HTTP_BODY)) { fprintf(stderr, "FIXME !!!! unknown analysers flags %s:%d = 0x%08X\n", __FILE__, __LINE__, req->analysers); ABORT_NOW(); } #endif - req->analysers &= AN_REQ_INSPECT | AN_REQ_HTTP_HDR | AN_REQ_HTTP_BODY; + req->analysers &= AN_REQ_INSPECT | AN_REQ_HTTP_HDR | AN_REQ_HTTP_TARPIT | AN_REQ_HTTP_BODY; return 0; } @@ -2632,11 +2694,9 @@ int process_response(struct session *t) struct buffer *req = t->req; struct buffer *rep = t->rep; - DPRINTF(stderr,"[%u] process_rep: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x analysers=%02x\n", - now_ms, - cli_stnames[t->cli_state], srv_stnames[t->srv_state], - t->srv_fd >= 0 && fdtab[t->srv_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->srv_fd, DIR_RD) : 0, - t->srv_fd >= 0 && fdtab[t->srv_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->srv_fd, DIR_WR) : 0, + DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x analysers=%02x\n", + now_ms, __FUNCTION__, + cli_stnames[t->cli_state], req->rex, rep->wex, req->flags, rep->flags, rep->analysers); if (rep->analysers & AN_RTR_HTTP_HDR) { /* receiving server headers */ @@ -2701,16 +2761,18 @@ int process_response(struct session *t) /* Invalid response */ if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) { hdr_response_bad: - buffer_shutr(rep); - buffer_shutw(req); - fd_delete(t->srv_fd); + //buffer_shutr(rep); + //buffer_shutw(req); + //fd_delete(req->cons->fd); + //req->cons->state = SI_ST_CLO; + buffer_shutr_now(rep); + buffer_shutw_now(req); if (t->srv) { - t->srv->cur_sess--; + //t->srv->cur_sess--; t->srv->failed_resp++; - sess_change_server(t, NULL); + //sess_change_server(t, NULL); } t->be->failed_resp++; - t->srv_state = SV_STCLOSE; rep->analysers = 0; txn->status = 502; client_return(t, error_message(t, HTTP_ERR_502)); @@ -2719,24 +2781,23 @@ int process_response(struct session *t) if (!(t->flags & SN_FINST_MASK)) t->flags |= SN_FINST_H; - if (t->srv && may_dequeue_tasks(t->srv, t->be)) - process_srv_queue(t->srv); + //if (t->srv && may_dequeue_tasks(t->srv, t->be)) + // process_srv_queue(t->srv); return 0; } /* write error to client, read error or close from server */ - if (req->flags & BF_WRITE_ERROR || - rep->flags & (BF_READ_ERROR | BF_READ_NULL | BF_SHUTW)) { - buffer_shutr(rep); - buffer_shutw(req); - fd_delete(t->srv_fd); + if (rep->flags & (BF_WRITE_ERROR|BF_SHUTW|BF_READ_ERROR|BF_SHUTR|BF_READ_NULL)) { + buffer_shutr_now(rep); + buffer_shutw_now(req); + //fd_delete(req->cons->fd); + //req->cons->state = SI_ST_CLO; if (t->srv) { - t->srv->cur_sess--; + //t->srv->cur_sess--; t->srv->failed_resp++; - sess_change_server(t, NULL); + //sess_change_server(t, NULL); } t->be->failed_resp++; - t->srv_state = SV_STCLOSE; rep->analysers = 0; txn->status = 502; client_return(t, error_message(t, HTTP_ERR_502)); @@ -2745,8 +2806,8 @@ int process_response(struct session *t) if (!(t->flags & SN_FINST_MASK)) t->flags |= SN_FINST_H; - if (t->srv && may_dequeue_tasks(t->srv, t->be)) - process_srv_queue(t->srv); + //if (t->srv && may_dequeue_tasks(t->srv, t->be)) + // process_srv_queue(t->srv); return 0; } @@ -2756,16 +2817,16 @@ int process_response(struct session *t) } /* read timeout : return a 504 to the client. */ else if (rep->flags & BF_READ_TIMEOUT) { - buffer_shutr(rep); - buffer_shutw(req); - fd_delete(t->srv_fd); + buffer_shutr_now(rep); + buffer_shutw_now(req); + //fd_delete(req->cons->fd); + //req->cons->state = SI_ST_CLO; if (t->srv) { - t->srv->cur_sess--; + //t->srv->cur_sess--; t->srv->failed_resp++; - sess_change_server(t, NULL); + //sess_change_server(t, NULL); } t->be->failed_resp++; - t->srv_state = SV_STCLOSE; rep->analysers = 0; txn->status = 504; client_return(t, error_message(t, HTTP_ERR_504)); @@ -2774,8 +2835,8 @@ int process_response(struct session *t) if (!(t->flags & SN_FINST_MASK)) t->flags |= SN_FINST_H; - if (t->srv && may_dequeue_tasks(t->srv, t->be)) - process_srv_queue(t->srv); + //if (t->srv && may_dequeue_tasks(t->srv, t->be)) + // process_srv_queue(t->srv); return 0; } @@ -2856,16 +2917,16 @@ int process_response(struct session *t) if (apply_filters_to_response(t, rep, rule_set->rsp_exp) < 0) { return_bad_resp: if (t->srv) { - t->srv->cur_sess--; + //t->srv->cur_sess--; t->srv->failed_resp++; - sess_change_server(t, NULL); + //sess_change_server(t, NULL); } cur_proxy->failed_resp++; return_srv_prx_502: - buffer_shutr(rep); - buffer_shutw(req); - fd_delete(t->srv_fd); - t->srv_state = SV_STCLOSE; + buffer_shutr_now(rep); + buffer_shutw_now(req); + //fd_delete(req->cons->fd); + //req->cons->state = SI_ST_CLO; rep->analysers = 0; txn->status = 502; client_return(t, error_message(t, HTTP_ERR_502)); @@ -2876,8 +2937,8 @@ int process_response(struct session *t) /* We used to have a free connection slot. Since we'll never use it, * we have to inform the server that it may be used by another session. */ - if (t->srv && may_dequeue_tasks(t->srv, cur_proxy)) - process_srv_queue(t->srv); + //if (t->srv && may_dequeue_tasks(t->srv, cur_proxy)) + // process_srv_queue(t->srv); return 0; } } @@ -2885,9 +2946,9 @@ int process_response(struct session *t) /* has the response been denied ? */ if (txn->flags & TX_SVDENY) { if (t->srv) { - t->srv->cur_sess--; + //t->srv->cur_sess--; t->srv->failed_secu++; - sess_change_server(t, NULL); + //sess_change_server(t, NULL); } cur_proxy->denied_resp++; goto return_srv_prx_502; @@ -3024,9 +3085,9 @@ int process_response(struct session *t) * the 'checkcache' option, and send an alert. */ if (t->srv) { - t->srv->cur_sess--; + //t->srv->cur_sess--; t->srv->failed_secu++; - sess_change_server(t, NULL); + //sess_change_server(t, NULL); } t->be->denied_resp++; @@ -3063,7 +3124,7 @@ int process_response(struct session *t) #ifdef CONFIG_HAP_TCPSPLICE if ((t->fe->options & t->be->options) & PR_O_TCPSPLICE) { /* TCP splicing supported by both FE and BE */ - tcp_splice_splicefd(t->cli_fd, t->srv_fd, 0); + tcp_splice_splicefd(t->cli_fd, req->cons->fd, 0); } #endif /* if the user wants to log as soon as possible, without counting @@ -3113,9 +3174,10 @@ int process_cli(struct session *t) struct buffer *req = t->req; struct buffer *rep = t->rep; - DPRINTF(stderr,"[%u] process_cli: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", - now_ms, - cli_stnames[t->cli_state], srv_stnames[t->srv_state], + DPRINTF(stderr,"[%u] %s: fd=%d[%d] c=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", + now_ms, __FUNCTION__, + t->cli_fd, t->cli_fd >= 0 ? fdtab[t->cli_fd].state : 0, /* fd,state*/ + cli_stnames[t->cli_state], t->cli_fd >= 0 && fdtab[t->cli_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->cli_fd, DIR_RD) : 0, t->cli_fd >= 0 && fdtab[t->cli_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->cli_fd, DIR_WR) : 0, req->rex, rep->wex, @@ -3128,17 +3190,20 @@ int process_cli(struct session *t) */ if (t->cli_state == CL_STDATA || t->cli_state == CL_STSHUTR) { /* we can skip most of the tests at once if some conditions are not met */ - if (!((req->flags & (BF_READ_TIMEOUT|BF_READ_ERROR)) || - (rep->flags & (BF_WRITE_TIMEOUT|BF_WRITE_ERROR)) || + if (!((fdtab[t->cli_fd].state == FD_STERROR) || + (req->flags & (BF_READ_TIMEOUT|BF_READ_ERROR|BF_SHUTR_NOW)) || + (rep->flags & (BF_WRITE_TIMEOUT|BF_WRITE_ERROR|BF_SHUTW_NOW)) || (!(req->flags & BF_SHUTR) && req->flags & (BF_READ_NULL|BF_SHUTW)) || (!(rep->flags & BF_SHUTW) && (rep->flags & (BF_EMPTY|BF_MAY_FORWARD|BF_SHUTR)) == (BF_EMPTY|BF_MAY_FORWARD|BF_SHUTR)))) goto update_timeouts; /* read or write error */ - if (rep->flags & BF_WRITE_ERROR || req->flags & BF_READ_ERROR) { + if (fdtab[t->cli_fd].state == FD_STERROR) { buffer_shutr(req); + req->flags |= BF_READ_ERROR; buffer_shutw(rep); + rep->flags |= BF_WRITE_ERROR; fd_delete(t->cli_fd); t->cli_state = CL_STCLOSE; trace_term(t, TT_HTTP_CLI_1); @@ -3146,9 +3211,9 @@ int process_cli(struct session *t) if (!(t->flags & SN_ERR_MASK)) t->flags |= SN_ERR_CLICL; if (!(t->flags & SN_FINST_MASK)) { - if (t->pend_pos) + if (req->cons->err_type <= SI_ET_QUEUE_ABRT) t->flags |= SN_FINST_Q; - else if (t->srv_state == SV_STCONN) + else if (req->cons->err_type <= SI_ET_CONN_OTHER) t->flags |= SN_FINST_C; else t->flags |= SN_FINST_D; @@ -3158,7 +3223,7 @@ int process_cli(struct session *t) } /* last read, or end of server write */ else if (!(req->flags & BF_SHUTR) && /* not already done */ - req->flags & (BF_READ_NULL | BF_SHUTW)) { + req->flags & (BF_READ_NULL|BF_SHUTR_NOW|BF_SHUTW)) { buffer_shutr(req); if (!(rep->flags & BF_SHUTW)) { EV_FD_CLR(t->cli_fd, DIR_RD); @@ -3175,8 +3240,9 @@ int process_cli(struct session *t) * allowed to forward the data. */ else if (!(rep->flags & BF_SHUTW) && /* not already done */ - rep->flags & BF_EMPTY && rep->flags & BF_MAY_FORWARD && - rep->flags & BF_SHUTR && !(t->flags & SN_SELF_GEN)) { + ((rep->flags & BF_SHUTW_NOW) || + (rep->flags & BF_EMPTY && rep->flags & BF_MAY_FORWARD && + rep->flags & BF_SHUTR && !(t->flags & SN_SELF_GEN)))) { buffer_shutw(rep); if (!(req->flags & BF_SHUTR)) { EV_FD_CLR(t->cli_fd, DIR_WR); @@ -3209,9 +3275,9 @@ int process_cli(struct session *t) if (!(t->flags & SN_ERR_MASK)) t->flags |= SN_ERR_CLITO; if (!(t->flags & SN_FINST_MASK)) { - if (t->pend_pos) + if (req->cons->err_type <= SI_ET_QUEUE_ABRT) t->flags |= SN_FINST_Q; - else if (t->srv_state == SV_STCONN) + else if (req->cons->err_type <= SI_ET_CONN_OTHER) t->flags |= SN_FINST_C; else t->flags |= SN_FINST_D; @@ -3239,9 +3305,9 @@ int process_cli(struct session *t) if (!(t->flags & SN_ERR_MASK)) t->flags |= SN_ERR_CLITO; if (!(t->flags & SN_FINST_MASK)) { - if (t->pend_pos) + if (req->cons->err_type <= SI_ET_QUEUE_ABRT) t->flags |= SN_FINST_Q; - else if (t->srv_state == SV_STCONN) + else if (req->cons->err_type <= SI_ET_CONN_OTHER) t->flags |= SN_FINST_C; else t->flags |= SN_FINST_D; @@ -3282,7 +3348,7 @@ int process_cli(struct session *t) } /* we don't enable client write if the buffer is empty, nor if the server has to analyze it */ - if ((rep->flags & BF_EMPTY) || !(rep->flags & BF_MAY_FORWARD)) { + if ((rep->flags & (BF_EMPTY|BF_MAY_FORWARD)) != BF_MAY_FORWARD) { if (EV_FD_COND_C(t->cli_fd, DIR_WR)) { /* stop writing */ rep->wex = TICK_ETERNITY; @@ -3306,7 +3372,7 @@ int process_cli(struct session *t) else if (t->cli_state == CL_STCLOSE) { /* CL_STCLOSE: nothing to do */ if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { int len; - len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", t->uniq_id, t->be->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); + len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", t->uniq_id, t->be->id, (unsigned short)t->cli_fd, (unsigned short)req->cons->fd); write(1, trash, len); } return 0; @@ -3319,529 +3385,1471 @@ int process_cli(struct session *t) } -/* - * Manages the server FSM and its socket. It normally returns zero, but may - * return 1 if it absolutely wants to be called again. - * - * Note: process_srv is the ONLY function allowed to set srv_state to anything - * but SV_STCLOSE. The only exception is for functions called from this - * one (eg: those in backend.c). +/* Return 1 if the pending connection has failed and should be retried, + * otherwise zero. We may only come here in SI_ST_CON state, which means that + * the socket's file descriptor is known. */ -int process_srv(struct session *t) +int tcp_connection_status(struct session *t) { - struct http_txn *txn = &t->txn; struct buffer *req = t->req; struct buffer *rep = t->rep; - int conn_err; + int conn_err = 0; - DPRINTF(stderr,"[%u] process_srv: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", - now_ms, - cli_stnames[t->cli_state], srv_stnames[t->srv_state], - t->srv_fd >= 0 && fdtab[t->srv_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->srv_fd, DIR_RD) : 0, - t->srv_fd >= 0 && fdtab[t->srv_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->srv_fd, DIR_WR) : 0, + DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", + now_ms, __FUNCTION__, + cli_stnames[t->cli_state], rep->rex, req->wex, req->flags, rep->flags, req->l, rep->l); - update_state: - if (t->srv_state == SV_STIDLE) { - if ((rep->flags & BF_SHUTW) || - ((req->flags & BF_SHUTR) && - (req->flags & BF_EMPTY || t->be->options & PR_O_ABRT_CLOSE))) { /* give up */ - req->wex = TICK_ETERNITY; - if (t->pend_pos) + if ((req->flags & BF_SHUTW_NOW) || + (rep->flags & BF_SHUTW) || + ((req->flags & BF_SHUTR) && /* FIXME: this should not prevent a connection from establishing */ + ((req->flags & BF_EMPTY && !(req->flags & BF_WRITE_STATUS)) || + t->be->options & PR_O_ABRT_CLOSE))) { /* give up */ + + trace_term(t, TT_HTTP_SRV_5); + req->wex = TICK_ETERNITY; + fd_delete(req->cons->fd); + if (t->srv) { + t->srv->cur_sess--; + sess_change_server(t, NULL); + } + /* note that this must not return any error because it would be able to + * overwrite the client_retnclose() output. + */ + //srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_C, 0, NULL); + + // FIXME: should we set rep->MAY_FORWARD ? + buffer_shutw(req); + buffer_shutr(rep); + req->cons->state = SI_ST_CLO; + if (!req->cons->err_type) + req->cons->err_type = SI_ET_CONN_ABRT; + req->cons->err_loc = t->srv; + return 0; + } + + /* check for timeouts and asynchronous connect errors */ + if (fdtab[req->cons->fd].state == FD_STERROR) { + conn_err = SI_ET_CONN_ERR; + if (!req->cons->err_type) + req->cons->err_type = SI_ET_CONN_ERR; + } + else if (!(req->flags & BF_WRITE_STATUS)) { + /* nothing happened, maybe we timed out */ + if (tick_is_expired(req->wex, now_ms)) { + conn_err = SI_ET_CONN_TO; + if (!req->cons->err_type) + req->cons->err_type = SI_ET_CONN_TO; + } + else + return 0; /* let's wait a bit more */ + } + + if (conn_err) { + fd_delete(req->cons->fd); + req->cons->state = SI_ST_CLO; + + if (t->srv) { + t->srv->cur_sess--; + sess_change_server(t, NULL); + req->cons->err_loc = t->srv; + } + + /* ensure that we have enough retries left */ + if (srv_count_retry_down(t, conn_err)) + return 0; + + if (conn_err == SI_ET_CONN_ERR) { + /* we encountered an immediate connection error, and we + * will have to retry connecting to the same server, most + * likely leading to the same result. To avoid this, we + * fake a connection timeout to retry after a turn-around + * time of 1 second. We will wait in the previous if block. + */ + req->cons->state = SI_ST_TAR; + req->wex = tick_add(now_ms, MS_TO_TICKS(1000)); + return 0; + } + + if (t->srv && t->conn_retries == 0 && t->be->options & PR_O_REDISP) { + /* We're on our last chance, and the REDISP option was specified. + * We will ignore cookie and force to balance or use the dispatcher. + */ + /* let's try to offer this slot to anybody */ + if (may_dequeue_tasks(t->srv, t->be)) + process_srv_queue(t->srv); + + /* it's left to the dispatcher to choose a server */ + t->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET); + t->prev_srv = t->srv; + } else { + /* we just want to retry */ + if (t->srv) + t->srv->retries++; + t->be->retries++; + + /* Now we will try to either reconnect to the same server or + * connect to another server. If the connection gets queued + * because all servers are saturated, then we will go back to + * the idle state where the buffer's consumer is marked as + * unknown. + */ + if (srv_retryable_connect(t)) { + /* success or unrecoverable error */ t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); - /* note that this must not return any error because it would be able to - * overwrite the client_retnclose() output. + return 0; + } + } + + /* We'll rely on the caller to try to get a connection again */ + return 1; + } + else { + /* no error and write OK : connection succeeded */ + t->logs.t_connect = tv_ms_elapsed(&t->logs.tv_accept, &now); + req->cons->state = SI_ST_EST; + req->cons->err_type = SI_ET_NONE; + req->cons->err_loc = NULL; + + if (req->flags & BF_EMPTY) { + EV_FD_CLR(req->cons->fd, DIR_WR); + req->wex = TICK_ETERNITY; + } else { + EV_FD_SET(req->cons->fd, DIR_WR); + req->wex = tick_add_ifset(now_ms, t->be->timeout.server); + if (tick_isset(req->wex)) { + /* FIXME: to prevent the server from expiring read timeouts during writes, + * we refresh it. */ + rep->rex = req->wex; + } + } + + if (t->be->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */ + if (!(rep->flags & BF_HIJACK)) { + EV_FD_SET(req->cons->fd, DIR_RD); + rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); + } + buffer_set_rlim(rep, BUFSIZE); /* no rewrite needed */ + + /* if the user wants to log as soon as possible, without counting + bytes from the server, then this is the right moment. */ + if (t->fe->to_log && !(t->logs.logwait & LW_BYTES)) { + t->logs.t_close = t->logs.t_connect; /* to get a valid end date */ + tcp_sess_log(t); + } +#ifdef CONFIG_HAP_TCPSPLICE + if ((t->fe->options & t->be->options) & PR_O_TCPSPLICE) { + /* TCP splicing supported by both FE and BE */ + tcp_splice_splicefd(t->cli_fd, req->cons->fd, 0); + } +#endif + } + else { + rep->analysers |= AN_RTR_HTTP_HDR; + buffer_set_rlim(rep, BUFSIZE - MAXREWRITE); /* rewrite needed */ + t->txn.rsp.msg_state = HTTP_MSG_RPBEFORE; + /* reset hdr_idx which was already initialized by the request. + * right now, the http parser does it. + * hdr_idx_init(&t->txn.hdr_idx); */ - if (txn->flags & TX_CLTARPIT) - srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_T, 0, NULL); - else - srv_close_with_err(t, SN_ERR_CLICL, t->pend_pos ? SN_FINST_Q : SN_FINST_C, 0, NULL); + } + + if (!rep->analysers) + t->rep->flags |= BF_MAY_FORWARD; + req->wex = TICK_ETERNITY; + return 0; + } +} + + +/* + * This function tries to assign a server to a stream_sock interface. + * It may be called only for t->req->cons->state = one of { SI_ST_INI, + * SI_ST_TAR, SI_ST_QUE }. It returns one of those states, SI_ST_ASS + * in case of success, or SI_ST_CLO in case of failure. It returns 1 if + * it returns SI_ST_ASS, otherwise zero. + */ +int stream_sock_assign_server(struct session *t) +{ + DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", + now_ms, __FUNCTION__, + cli_stnames[t->cli_state], + t->rep->rex, t->req->wex, + t->req->flags, t->rep->flags, + t->req->l, t->rep->l); + + if (t->req->cons->state == SI_ST_TAR) { + /* connection might be aborted */ + if ((t->req->flags & BF_SHUTW_NOW) || + (t->rep->flags & BF_SHUTW) || + ((t->req->flags & BF_SHUTR) && /* FIXME: this should not prevent a connection from establishing */ + (t->req->flags & BF_EMPTY || t->be->options & PR_O_ABRT_CLOSE))) { /* give up */ trace_term(t, TT_HTTP_SRV_1); - goto update_state; + t->req->wex = TICK_ETERNITY; + + // FIXME: should we set rep->MAY_FORWARD ? + buffer_shutr(t->rep); + buffer_shutw(t->req); + if (!t->req->cons->err_type) + t->req->cons->err_type = SI_ET_CONN_ABRT; + t->req->cons->state = SI_ST_CLO; + return 0; } - else if (req->flags & BF_MAY_FORWARD) { - /* the client allows the server to connect */ - if (txn->flags & TX_CLTARPIT) { - /* This connection is being tarpitted. The CLIENT side has - * already set the connect expiration date to the right - * timeout. We just have to check that it has not expired. - */ - if (!(req->flags & BF_WRITE_TIMEOUT)) - return 0; - /* We will set the queue timer to the time spent, just for - * logging purposes. We fake a 500 server error, so that the - * attacker will not suspect his connection has been tarpitted. - * It will not cause trouble to the logs because we can exclude - * the tarpitted connections by filtering on the 'PT' status flags. - */ - req->wex = TICK_ETERNITY; + if (!tick_is_expired(t->req->wex, now_ms)) + return 0; /* still in turn-around */ + + t->req->cons->state = SI_ST_INI; + } + else if (t->req->cons->state == SI_ST_QUE) { + if (t->pend_pos) { + /* request still in queue... */ + if (tick_is_expired(t->req->wex, now_ms)) { + /* ... and timeout expired */ + trace_term(t, TT_HTTP_SRV_3); + t->req->wex = TICK_ETERNITY; t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); - srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_T, - 500, error_message(t, HTTP_ERR_500)); - trace_term(t, TT_HTTP_SRV_2); - goto update_state; + if (t->srv) + t->srv->failed_conns++; + t->be->failed_conns++; + + // FIXME: should we set rep->MAY_FORWARD ? + buffer_shutr(t->rep); + buffer_shutw(t->req); + t->req->flags |= BF_WRITE_TIMEOUT; + if (!t->req->cons->err_type) + t->req->cons->err_type = SI_ET_QUEUE_TO; + t->req->cons->state = SI_ST_CLO; + return 0; } + /* connection remains in queue, check if we have to abort it */ + if ((t->req->flags & BF_SHUTW_NOW) || + (t->rep->flags & BF_SHUTW) || + ((t->req->flags & BF_SHUTR) && /* FIXME: this should not prevent a connection from establishing */ + (t->req->flags & BF_EMPTY || t->be->options & PR_O_ABRT_CLOSE))) { + /* give up */ + trace_term(t, TT_HTTP_SRV_1); + t->req->wex = TICK_ETERNITY; + t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); - /* Right now, we will need to create a connection to the server. - * We might already have tried, and got a connection pending, in - * which case we will not do anything till it's pending. It's up - * to any other session to release it and wake us up again. - */ - if (t->pend_pos) { - if (!(req->flags & BF_WRITE_TIMEOUT)) { - return 0; - } else { - /* we've been waiting too long here */ - req->wex = TICK_ETERNITY; - t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); - srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_Q, - 503, error_message(t, HTTP_ERR_503)); - trace_term(t, TT_HTTP_SRV_3); - if (t->srv) - t->srv->failed_conns++; - t->be->failed_conns++; - goto update_state; - } + // FIXME: should we set rep->MAY_FORWARD ? + buffer_shutr(t->rep); + buffer_shutw(t->req); + if (!t->req->cons->err_type) + t->req->cons->err_type = SI_ET_QUEUE_ABRT; + t->req->cons->state = SI_ST_CLO; } + return 0; + } + /* The connection is not in the queue anymore */ + t->req->cons->state = SI_ST_INI; + } - do { - /* first, get a connection */ - if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD) - t->flags |= SN_REDIRECTABLE; + /* we may get here from above */ + if (t->req->cons->state == SI_ST_INI) { + /* no connection in progress, we have to get a new one */ - if (srv_redispatch_connect(t)) { - if (t->srv_state == SV_STIDLE) - return 0; - goto update_state; - } + /* first, check if the connection has been aborted */ + if ((t->req->flags & BF_SHUTW_NOW) || + (t->rep->flags & BF_SHUTW) || + ((t->req->flags & BF_SHUTR) && + (t->req->flags & BF_EMPTY || t->be->options & PR_O_ABRT_CLOSE))) { /* give up */ - if ((t->flags & SN_REDIRECTABLE) && t->srv && t->srv->rdr_len) { - /* Server supporting redirection and it is possible. - * Invalid requests are reported as such. It concerns all - * the largest ones. - */ - struct chunk rdr; - char *path; - int len; + trace_term(t, TT_HTTP_SRV_1); + t->req->wex = TICK_ETERNITY; + + // FIXME: should we set rep->MAY_FORWARD ? + buffer_shutr(t->rep); + buffer_shutw(t->req); + if (!t->req->cons->err_type) + t->req->cons->err_type = SI_ET_CONN_ABRT; + t->req->cons->state = SI_ST_CLO; + return 0; + } - /* 1: create the response header */ - rdr.len = strlen(HTTP_302); - rdr.str = trash; - memcpy(rdr.str, HTTP_302, rdr.len); + /* try to get a server assigned */ + if (srv_redispatch_connect(t) != 0) { + /* we did not get any server, let's check the cause */ + if (t->req->cons->state == SI_ST_QUE) { + /* the connection was queued, that's OK */ + return 0; + } - /* 2: add the server's prefix */ - if (rdr.len + t->srv->rdr_len > sizeof(trash)) - goto cancel_redir; + trace_term(t, TT_HTTP_SRV_2); + t->req->wex = TICK_ETERNITY; - memcpy(rdr.str + rdr.len, t->srv->rdr_pfx, t->srv->rdr_len); - rdr.len += t->srv->rdr_len; + // FIXME: should we set rep->MAY_FORWARD ? + buffer_shutr(t->rep); + buffer_shutw(t->req); + t->req->flags |= BF_WRITE_ERROR; + if (!t->req->cons->err_type) + t->req->cons->err_type = SI_ET_CONN_OTHER; + t->req->cons->state = SI_ST_CLO; + return 0; + } - /* 3: add the request URI */ - path = http_get_path(txn); - if (!path) - goto cancel_redir; - len = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path; - if (rdr.len + len > sizeof(trash) - 4) /* 4 for CRLF-CRLF */ - goto cancel_redir; + t->req->cons->state = SI_ST_ASS; + /* Once the server is assigned, we have to return because + * the caller might be interested in checking several + * things before connecting. + */ + return 1; + } + return 0; +} - memcpy(rdr.str + rdr.len, path, len); - rdr.len += len; - memcpy(rdr.str + rdr.len, "\r\n\r\n", 4); - rdr.len += 4; - srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, 302, &rdr); - trace_term(t, TT_HTTP_SRV_3); +/* + * This function tries to establish a connection to an assigned server. It also + * performs connection retries. It may only be called with t->req->cons->state + * in { SI_ST_ASS, SI_ST_CON }. It may also set the state to SI_ST_INI, + * SI_ST_EST, or SI_ST_CLO. + */ +int stream_sock_connect_server(struct session *t) +{ + if (t->req->cons->state == SI_ST_ASS) { + /* server assigned to request, we have to try to connect now */ - /* FIXME: we should increase a counter of redirects per server and per backend. */ - if (t->srv) - t->srv->cum_sess++; - goto update_state; - cancel_redir: - txn->status = 400; - t->fe->failed_req++; - srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, - 400, error_message(t, HTTP_ERR_400)); - trace_term(t, TT_HTTP_SRV_4); - goto update_state; - } + if (!srv_retryable_connect(t)) { + /* we need to redispatch */ + t->req->cons->state = SI_ST_INI; + return 0; + } - /* try to (re-)connect to the server, and fail if we expire the - * number of retries. - */ - if (srv_retryable_connect(t)) { - t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); - if (t->srv_state == SV_STIDLE) - return 0; - goto update_state; - } - } while (1); - } /* end if may_forward */ - return 0; + t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); + if (t->req->cons->state != SI_ST_CON) { + /* it was an error */ + trace_term(t, TT_HTTP_SRV_4); + t->req->wex = TICK_ETERNITY; + + // FIXME: should we set rep->MAY_FORWARD ? + buffer_shutr(t->rep); + buffer_shutw(t->req); + t->req->flags |= BF_WRITE_ERROR; + if (!t->req->cons->err_type) + t->req->cons->err_type = SI_ET_CONN_OTHER; + t->req->cons->state = SI_ST_CLO; + return 0; + } + /* We have a socket and switched to SI_ST_CON */ } - else if (t->srv_state == SV_STCONN) { /* connection in progress */ - if ((rep->flags & BF_SHUTW) || - ((req->flags & BF_SHUTR) && - ((req->flags & BF_EMPTY && !(req->flags & BF_WRITE_STATUS)) || - t->be->options & PR_O_ABRT_CLOSE))) { /* give up */ - req->wex = TICK_ETERNITY; - if (!(t->flags & SN_CONN_TAR)) { - /* if we are in turn-around, we have already closed the FD */ - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - sess_change_server(t, NULL); - } - } - /* note that this must not return any error because it would be able to - * overwrite the client_retnclose() output. - */ - srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_C, 0, NULL); - trace_term(t, TT_HTTP_SRV_5); - goto update_state; - } - if (!(req->flags & (BF_WRITE_STATUS | BF_WRITE_TIMEOUT))) { - return 0; /* nothing changed */ + /* we may also get here from above */ + if (t->req->cons->state == SI_ST_CON) { + /* connection in progress or just completed */ + if (!tcp_connection_status(t)) + return 0; + } + return 0; +} + + +/* + * Tries to establish a connection to the server and associate it to the + * request buffer's consumer side. It is assumed that this function will not be + * be called with SI_ST_EST nor with BF_MAY_FORWARD cleared. It normally + * returns zero, but may return 1 if it absolutely wants to be called again. + */ +int process_srv_conn(struct session *t) +{ + DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", + now_ms, __FUNCTION__, + cli_stnames[t->cli_state], + t->rep->rex, t->req->wex, + t->req->flags, t->rep->flags, + t->req->l, t->rep->l); + + do { + if (t->req->cons->state == SI_ST_INI || + t->req->cons->state == SI_ST_TAR || + t->req->cons->state == SI_ST_QUE) { + /* try to assign a server */ + if (!stream_sock_assign_server(t)) + return 0; } - else if (!(req->flags & BF_WRITE_STATUS) || (req->flags & BF_WRITE_ERROR)) { - /* timeout, asynchronous connect error or first write error */ - if (t->flags & SN_CONN_TAR) { - /* We are doing a turn-around waiting for a new connection attempt. */ - if (!(req->flags & BF_WRITE_TIMEOUT)) - return 0; - t->flags &= ~SN_CONN_TAR; - } - else { - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - sess_change_server(t, NULL); - } - if (!(req->flags & BF_WRITE_STATUS)) - conn_err = SN_ERR_SRVTO; // it was a connect timeout. - else - conn_err = SN_ERR_SRVCL; // it was an asynchronous connect error. + if (t->req->cons->state == SI_ST_ASS && + t->srv && t->srv->rdr_len && t->flags & SN_REDIRECTABLE) { + /* Server supporting redirection and it is possible. + * Invalid requests are reported as such. It concerns all + * the largest ones. + */ + struct http_txn *txn = &t->txn; + struct chunk rdr; + char *path; + int len; + + /* 1: create the response header */ + rdr.len = strlen(HTTP_302); + rdr.str = trash; + memcpy(rdr.str, HTTP_302, rdr.len); - /* ensure that we have enough retries left */ - if (srv_count_retry_down(t, conn_err)) { - goto update_state; - } + /* 2: add the server's prefix */ + if (rdr.len + t->srv->rdr_len > sizeof(trash)) + goto cancel_redir; - if (req->flags & BF_WRITE_ERROR) { - /* we encountered an immediate connection error, and we - * will have to retry connecting to the same server, most - * likely leading to the same result. To avoid this, we - * fake a connection timeout to retry after a turn-around - * time of 1 second. We will wait in the previous if block. - */ - t->flags |= SN_CONN_TAR; - req->wex = tick_add(now_ms, MS_TO_TICKS(1000)); - return 0; - } - } + memcpy(rdr.str + rdr.len, t->srv->rdr_pfx, t->srv->rdr_len); + rdr.len += t->srv->rdr_len; - if (t->srv && t->conn_retries == 0 && t->be->options & PR_O_REDISP) { - /* We're on our last chance, and the REDISP option was specified. - * We will ignore cookie and force to balance or use the dispatcher. - */ - /* let's try to offer this slot to anybody */ - if (may_dequeue_tasks(t->srv, t->be)) - process_srv_queue(t->srv); - - /* it's left to the dispatcher to choose a server */ - t->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET); - t->prev_srv = t->srv; - - /* first, get a connection */ - if (srv_redispatch_connect(t)) { - if (t->srv_state == SV_STCONN) - return 0; - goto update_state; - } - } else { - if (t->srv) - t->srv->retries++; - t->be->retries++; - } + /* 3: add the request URI */ + path = http_get_path(txn); + if (!path) + goto cancel_redir; + len = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path; + if (rdr.len + len > sizeof(trash) - 4) /* 4 for CRLF-CRLF */ + goto cancel_redir; - do { - /* Now we will try to either reconnect to the same server or - * connect to another server. If the connection gets queued - * because all servers are saturated, then we will go back to - * the SV_STIDLE state. - */ - if (srv_retryable_connect(t)) { - t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); - if (t->srv_state == SV_STCONN) - return 0; - goto update_state; - } + memcpy(rdr.str + rdr.len, path, len); + rdr.len += len; + memcpy(rdr.str + rdr.len, "\r\n\r\n", 4); + rdr.len += 4; - /* we need to redispatch the connection to another server */ - if (srv_redispatch_connect(t)) { - if (t->srv_state == SV_STCONN) - return 0; - goto update_state; - } - } while (1); + srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, 302, &rdr); + trace_term(t, TT_HTTP_SRV_3); + + /* FIXME: we should increase a counter of redirects per server and per backend. */ + if (t->srv) + t->srv->cum_sess++; + + t->req->cons->state = SI_ST_CLO; + return 0; + cancel_redir: + //txn->status = 400; + //t->fe->failed_req++; + //srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, + // 400, error_message(t, HTTP_ERR_400)); + trace_term(t, TT_HTTP_SRV_4); + + // FIXME: should we set rep->MAY_FORWARD ? + buffer_shutw(t->req); + buffer_shutr(t->rep); + if (!t->req->cons->err_type) + t->req->cons->err_type = SI_ET_CONN_OTHER; + t->req->cons->state = SI_ST_CLO; + return 0; } - else { /* no error or write 0 */ - t->logs.t_connect = tv_ms_elapsed(&t->logs.tv_accept, &now); - if (req->flags & BF_EMPTY) { - EV_FD_CLR(t->srv_fd, DIR_WR); - req->wex = TICK_ETERNITY; - } else { - EV_FD_SET(t->srv_fd, DIR_WR); - req->wex = tick_add_ifset(now_ms, t->be->timeout.server); - if (tick_isset(req->wex)) { - /* FIXME: to prevent the server from expiring read timeouts during writes, - * we refresh it. */ - rep->rex = req->wex; - } - } + if (t->req->cons->state == SI_ST_CON || + t->req->cons->state == SI_ST_ASS) { + stream_sock_connect_server(t); + } + } while (t->req->cons->state != SI_ST_CLO && + t->req->cons->state != SI_ST_CON && + t->req->cons->state != SI_ST_EST); + return 0; +} - if (t->be->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */ - EV_FD_SET(t->srv_fd, DIR_RD); - rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); - buffer_set_rlim(rep, BUFSIZE); /* no rewrite needed */ - /* if the user wants to log as soon as possible, without counting - bytes from the server, then this is the right moment. */ - if (t->fe->to_log && !(t->logs.logwait & LW_BYTES)) { - t->logs.t_close = t->logs.t_connect; /* to get a valid end date */ - tcp_sess_log(t); - } -#ifdef CONFIG_HAP_TCPSPLICE - if ((t->fe->options & t->be->options) & PR_O_TCPSPLICE) { - /* TCP splicing supported by both FE and BE */ - tcp_splice_splicefd(t->cli_fd, t->srv_fd, 0); - } -#endif - } - else { - rep->analysers |= AN_RTR_HTTP_HDR; - buffer_set_rlim(rep, BUFSIZE - MAXREWRITE); /* rewrite needed */ - t->txn.rsp.msg_state = HTTP_MSG_RPBEFORE; - /* reset hdr_idx which was already initialized by the request. - * right now, the http parser does it. - * hdr_idx_init(&t->txn.hdr_idx); - */ - } +/* + * Manages the server FSM and its socket during the DATA phase. It must not be + * called when a file descriptor is not attached to the buffer. It must only be + * called during SI_ST_EST. It normally returns zero, but may return 1 if it + * absolutely wants to be called again. + */ +int process_srv_data(struct session *t) +{ + struct buffer *req = t->req; + struct buffer *rep = t->rep; + int fd = req->cons->fd; - t->srv_state = SV_STDATA; - /* FIXME: this should be turned into t->rep->flags |= BF_PROD_READY */ - if (!rep->analysers) - t->rep->flags |= BF_MAY_FORWARD; - req->wex = TICK_ETERNITY; - goto update_state; - } /* else no error or write 0 */ + DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", + now_ms, __FUNCTION__, + cli_stnames[t->cli_state], + rep->rex, req->wex, + req->flags, rep->flags, + req->l, rep->l); + + if (req->flags & (BF_WRITE_ERROR | BF_WRITE_TIMEOUT) || + rep->flags & (BF_READ_ERROR | BF_READ_TIMEOUT)) { + /* nothing more to be done here */ + fprintf(stderr, "Hey what are you doing there? t=%p fd=%d state=%d\n", + t, t->req->cons->fd, t->req->cons->state); + return 0; } - else if (t->srv_state == SV_STDATA) { - /* we can skip most of the tests at once if some conditions are not met */ - if (!((req->flags & (BF_WRITE_TIMEOUT|BF_WRITE_ERROR)) || - (!(req->flags & BF_SHUTW) && - (req->flags & (BF_EMPTY|BF_MAY_FORWARD)) == (BF_EMPTY|BF_MAY_FORWARD)) || - (rep->flags & (BF_READ_TIMEOUT|BF_READ_ERROR)) || - (!(rep->flags & BF_SHUTR) && rep->flags & (BF_READ_NULL|BF_SHUTW)))) - goto update_timeouts; - /* read or write error */ - /* FIXME: what happens when we have to deal with HTTP ??? */ - if (req->flags & BF_WRITE_ERROR || rep->flags & BF_READ_ERROR) { - buffer_shutr(rep); - buffer_shutw(req); - fd_delete(t->srv_fd); + /* we can skip most of the tests at once if some conditions are not met */ + /* FIXME: place req->BF_SHUTW_NOW here */ + //if (!((fdtab[fd].state == FD_STERROR) || + // (!(req->flags & BF_SHUTW) && + // (req->flags & (BF_EMPTY|BF_MAY_FORWARD)) == (BF_EMPTY|BF_MAY_FORWARD)) || + // (rep->flags & (BF_READ_TIMEOUT|BF_READ_ERROR)) || + // (!(rep->flags & BF_SHUTR) && rep->flags & (BF_READ_NULL|BF_SHUTR_NOW|BF_SHUTW)))) + // goto update_timeouts; + + /* read or write error */ + /* FIXME: what happens when we have to deal with HTTP ??? */ + if (fdtab[fd].state == FD_STERROR) { + trace_term(t, TT_HTTP_SRV_6); + buffer_shutw(req); + req->flags |= BF_WRITE_ERROR; + buffer_shutr(rep); + rep->flags |= BF_READ_ERROR; + fd_delete(fd); + req->cons->state = SI_ST_CLO; + if (t->srv) { + t->srv->cur_sess--; + //t->srv->failed_resp++; + //FIXME: si on ne traite pas l'erreur ici, le serveur est perdu et on ne la comptabilisera plus ensuite. + //il va donc falloir stocker l'info du dernier serveur en erreur pour que les couches du dessus traitent. + sess_change_server(t, NULL); + } + //t->be->failed_resp++; + //if (!rep->analysers) { + // if (!(t->flags & SN_ERR_MASK)) + // t->flags |= SN_ERR_SRVCL; + // if (!(t->flags & SN_FINST_MASK)) + // t->flags |= SN_FINST_D; + //} + if (may_dequeue_tasks(t->srv, t->be)) + process_srv_queue(t->srv); + + return 0; + } + + /* last read, or end of client write */ + if (!(rep->flags & BF_SHUTR) && /* not already done */ + rep->flags & (BF_READ_NULL|BF_SHUTR_NOW|BF_SHUTW)) { + buffer_shutr(rep); + if (!(req->flags & BF_SHUTW)) { + EV_FD_CLR(fd, DIR_RD); + trace_term(t, TT_HTTP_SRV_7); + } else { + /* output was already closed */ + trace_term(t, TT_HTTP_SRV_8); + fd_delete(fd); + req->cons->state = SI_ST_CLO; if (t->srv) { t->srv->cur_sess--; - t->srv->failed_resp++; sess_change_server(t, NULL); } - t->be->failed_resp++; - t->srv_state = SV_STCLOSE; - trace_term(t, TT_HTTP_SRV_6); - if (!rep->analysers) { - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVCL; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_D; - } + if (may_dequeue_tasks(t->srv, t->be)) process_srv_queue(t->srv); - - goto update_state; + return 0; } - /* last read, or end of client write */ - else if (!(rep->flags & BF_SHUTR) && /* not already done */ - rep->flags & (BF_READ_NULL | BF_SHUTW)) { - buffer_shutr(rep); - if (!(req->flags & BF_SHUTW)) { - EV_FD_CLR(t->srv_fd, DIR_RD); - trace_term(t, TT_HTTP_SRV_7); - } else { - /* output was already closed */ - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - sess_change_server(t, NULL); - } - t->srv_state = SV_STCLOSE; - trace_term(t, TT_HTTP_SRV_8); + } + /* end of client read and no more data to send. We can forward + * the close when we're allowed to forward data (anytime right + * now). If we're using option forceclose, then we may also + * shutdown the outgoing write channel once the response starts + * coming from the server. + */ - if (may_dequeue_tasks(t->srv, t->be)) - process_srv_queue(t->srv); + // FIXME: option FORCE_CLOSE should move to upper layer. + if (!(req->flags & BF_SHUTW) && /* not already done */ + (req->flags & BF_SHUTW_NOW || + (req->flags & BF_EMPTY && req->flags & BF_MAY_FORWARD && + (req->flags & BF_SHUTR || + (t->be->options & PR_O_FORCE_CLO && rep->flags & BF_READ_STATUS))))) { + buffer_shutw(req); + if (!(rep->flags & BF_SHUTR)) { + trace_term(t, TT_HTTP_SRV_9); + EV_FD_CLR(fd, DIR_WR); + shutdown(fd, SHUT_WR); + /* We must ensure that the read part is still alive when switching to shutw */ + /* FIXME: is this still true ? */ + EV_FD_SET(fd, DIR_RD); + rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); + } else { + trace_term(t, TT_HTTP_SRV_10); + fd_delete(fd); + req->cons->state = SI_ST_CLO; + if (t->srv) { + t->srv->cur_sess--; + sess_change_server(t, NULL); } - goto update_state; - } - /* end of client read and no more data to send. We can forward - * the close when we're allowed to forward data (anytime right - * now). If we're using option forceclose, then we may also - * shutdown the outgoing write channel once the response starts - * coming from the server. - */ - else if (!(req->flags & BF_SHUTW) && /* not already done */ - req->flags & BF_EMPTY && req->flags & BF_MAY_FORWARD && - (req->flags & BF_SHUTR || - (t->be->options & PR_O_FORCE_CLO && rep->flags & BF_READ_STATUS))) { - buffer_shutw(req); - if (!(rep->flags & BF_SHUTR)) { - EV_FD_CLR(t->srv_fd, DIR_WR); - shutdown(t->srv_fd, SHUT_WR); - trace_term(t, TT_HTTP_SRV_9); - /* We must ensure that the read part is still alive when switching to shutw */ - /* FIXME: is this still true ? */ - EV_FD_SET(t->srv_fd, DIR_RD); - rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); - } else { - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - sess_change_server(t, NULL); - } - t->srv_state = SV_STCLOSE; - trace_term(t, TT_HTTP_SRV_10); - if (may_dequeue_tasks(t->srv, t->be)) - process_srv_queue(t->srv); - } - goto update_state; + if (may_dequeue_tasks(t->srv, t->be)) + process_srv_queue(t->srv); + return 0; } - /* read timeout */ - else if ((rep->flags & (BF_SHUTR|BF_READ_TIMEOUT)) == BF_READ_TIMEOUT) { - buffer_shutr(rep); - if (!(req->flags & BF_SHUTW)) { - EV_FD_CLR(t->srv_fd, DIR_RD); - trace_term(t, TT_HTTP_SRV_11); - } else { - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - sess_change_server(t, NULL); - } - t->srv_state = SV_STCLOSE; - trace_term(t, TT_HTTP_SRV_12); + } - if (may_dequeue_tasks(t->srv, t->be)) - process_srv_queue(t->srv); - } - if (!rep->analysers) { - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVTO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_D; + /* read timeout */ + if ((rep->flags & (BF_SHUTR|BF_READ_TIMEOUT)) == 0 && + tick_is_expired(rep->rex, now_ms)) { + rep->flags |= BF_READ_TIMEOUT; + //if (!rep->analysers) { + // if (!(t->flags & SN_ERR_MASK)) + // t->flags |= SN_ERR_SRVTO; + // if (!(t->flags & SN_FINST_MASK)) + // t->flags |= SN_FINST_D; + //} + buffer_shutr(rep); + if (!(req->flags & BF_SHUTW)) { + trace_term(t, TT_HTTP_SRV_11); + EV_FD_CLR(fd, DIR_RD); + } else { + trace_term(t, TT_HTTP_SRV_12); + fd_delete(fd); + req->cons->state = SI_ST_CLO; + if (t->srv) { + t->srv->cur_sess--; + sess_change_server(t, NULL); } - goto update_state; - } - /* write timeout */ - else if ((req->flags & (BF_SHUTW|BF_WRITE_TIMEOUT)) == BF_WRITE_TIMEOUT) { - buffer_shutw(req); - if (!(rep->flags & BF_SHUTR)) { - EV_FD_CLR(t->srv_fd, DIR_WR); - shutdown(t->srv_fd, SHUT_WR); - /* We must ensure that the read part is still alive when switching to shutw */ - /* FIXME: is this still needed ? */ - EV_FD_SET(t->srv_fd, DIR_RD); - rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); - trace_term(t, TT_HTTP_SRV_13); - } else { - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - sess_change_server(t, NULL); - } - t->srv_state = SV_STCLOSE; - trace_term(t, TT_HTTP_SRV_14); - - if (may_dequeue_tasks(t->srv, t->be)) - process_srv_queue(t->srv); - } - if (!rep->analysers) { - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVTO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_D; - } - goto update_state; + if (may_dequeue_tasks(t->srv, t->be)) + process_srv_queue(t->srv); + return 0; } + } - update_timeouts: - /* manage read timeout */ + /* write timeout */ + if ((req->flags & (BF_SHUTW|BF_WRITE_TIMEOUT)) == 0 && + tick_is_expired(req->wex, now_ms)) { + req->flags |= BF_WRITE_TIMEOUT; + //if (!rep->analysers) { + // if (!(t->flags & SN_ERR_MASK)) + // t->flags |= SN_ERR_SRVTO; + // if (!(t->flags & SN_FINST_MASK)) + // t->flags |= SN_FINST_D; + //} + buffer_shutw(req); if (!(rep->flags & BF_SHUTR)) { - if (rep->flags & BF_FULL) { - if (EV_FD_COND_C(t->srv_fd, DIR_RD)) - rep->rex = TICK_ETERNITY; - } else { - EV_FD_COND_S(t->srv_fd, DIR_RD); - rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); + trace_term(t, TT_HTTP_SRV_13); + EV_FD_CLR(fd, DIR_WR); + shutdown(fd, SHUT_WR); + /* We must ensure that the read part is still alive when switching to shutw */ + /* FIXME: is this still needed ? */ + EV_FD_SET(fd, DIR_RD); + rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); + } else { + trace_term(t, TT_HTTP_SRV_14); + fd_delete(fd); + req->cons->state = SI_ST_CLO; + if (t->srv) { + t->srv->cur_sess--; + sess_change_server(t, NULL); } + + if (may_dequeue_tasks(t->srv, t->be)) + process_srv_queue(t->srv); + return 0; } + } - /* manage write timeout */ - if (!(req->flags & BF_SHUTW)) { - if (req->flags & BF_EMPTY || !(req->flags & BF_MAY_FORWARD)) { - /* stop writing */ - if (EV_FD_COND_C(t->srv_fd, DIR_WR)) - req->wex = TICK_ETERNITY; - } else { - /* buffer not empty, there are still data to be transferred */ - EV_FD_COND_S(t->srv_fd, DIR_WR); - if (!tick_isset(req->wex)) { - /* restart writing */ - req->wex = tick_add_ifset(now_ms, t->be->timeout.server); - if (!(rep->flags & BF_SHUTR) && tick_isset(req->wex) && tick_isset(rep->rex)) { - /* FIXME: to prevent the server from expiring read timeouts during writes, - * we refresh it, except if it was already infinite. - */ - rep->rex = req->wex; - } - } - } + update_timeouts: + /* manage read timeout */ + if (!(rep->flags & BF_SHUTR)) { + if (rep->flags & (BF_FULL|BF_HIJACK)) { + if (EV_FD_COND_C(fd, DIR_RD)) + rep->rex = TICK_ETERNITY; + } else { + EV_FD_COND_S(fd, DIR_RD); + rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); } - return 0; /* other cases change nothing */ } - else if (t->srv_state == SV_STCLOSE) { /* SV_STCLOSE : nothing to do */ - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { - int len; - len = sprintf(trash, "%08x:%s.srvcls[%04x:%04x]\n", - t->uniq_id, t->be->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); - write(1, trash, len); + + /* manage write timeout */ + if (!(req->flags & BF_SHUTW)) { + if ((req->flags & (BF_EMPTY|BF_MAY_FORWARD)) != BF_MAY_FORWARD) { + /* stop writing */ + if (EV_FD_COND_C(fd, DIR_WR)) + req->wex = TICK_ETERNITY; + } else { + /* buffer not empty, there are still data to be transferred */ + EV_FD_COND_S(fd, DIR_WR); + if (!tick_isset(req->wex)) { + /* restart writing */ + req->wex = tick_add_ifset(now_ms, t->be->timeout.server); + if (!(rep->flags & BF_SHUTR) && tick_isset(req->wex) && tick_isset(rep->rex)) { + /* FIXME: to prevent the server from expiring read timeouts during writes, + * we refresh it, except if it was already infinite. + */ + rep->rex = req->wex; + } + } } - return 0; } -#ifdef DEBUG_DEV - fprintf(stderr, "FIXME !!!! impossible state at %s:%d = %d\n", __FILE__, __LINE__, t->srv_state); - ABORT_NOW(); -#endif - return 0; + return 0; /* other cases change nothing */ } +///* +// * Manages the client FSM and its socket. It normally returns zero, but may +// * return 1 if it absolutely wants to be called again. +// * +// * Note: process_cli is the ONLY function allowed to set cli_state to anything +// * but CL_STCLOSE. +// */ +//int process_cli(struct session *t) +//{ +// struct buffer *req = t->req; +// struct buffer *rep = t->rep; +// +// DPRINTF(stderr,"[%u] %s: c=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", +// now_ms, __FUNCTION__, +// cli_stnames[t->cli_state], +// t->cli_fd >= 0 && fdtab[t->cli_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->cli_fd, DIR_RD) : 0, +// t->cli_fd >= 0 && fdtab[t->cli_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->cli_fd, DIR_WR) : 0, +// req->rex, rep->wex, +// req->flags, rep->flags, +// req->l, rep->l); +// +// update_state: +// /* FIXME: we still have to check for CL_STSHUTR because client_retnclose +// * still set this state (and will do until unix sockets are converted). +// */ +// if (t->cli_state == CL_STDATA || t->cli_state == CL_STSHUTR) { +// /* we can skip most of the tests at once if some conditions are not met */ +// if (!((req->flags & (BF_READ_TIMEOUT|BF_READ_ERROR)) || +// (rep->flags & (BF_WRITE_TIMEOUT|BF_WRITE_ERROR)) || +// (!(req->flags & BF_SHUTR) && req->flags & (BF_READ_NULL|BF_SHUTW)) || +// (!(rep->flags & BF_SHUTW) && +// (rep->flags & (BF_EMPTY|BF_MAY_FORWARD|BF_SHUTR)) == (BF_EMPTY|BF_MAY_FORWARD|BF_SHUTR)))) +// goto update_timeouts; +// +// /* read or write error */ +// if (rep->flags & BF_WRITE_ERROR || req->flags & BF_READ_ERROR) { +// buffer_shutr(req); +// buffer_shutw(rep); +// fd_delete(t->cli_fd); +// t->cli_state = CL_STCLOSE; +// trace_term(t, TT_HTTP_CLI_1); +// if (!req->analysers) { +// if (!(t->flags & SN_ERR_MASK)) +// t->flags |= SN_ERR_CLICL; +// if (!(t->flags & SN_FINST_MASK)) { +// if (t->pend_pos) +// t->flags |= SN_FINST_Q; +// else if (!(req->flags & BF_CONNECTED)) +// t->flags |= SN_FINST_C; +// else +// t->flags |= SN_FINST_D; +// } +// } +// goto update_state; +// } +// /* last read, or end of server write */ +// else if (!(req->flags & BF_SHUTR) && /* not already done */ +// req->flags & (BF_READ_NULL | BF_SHUTW)) { +// buffer_shutr(req); +// if (!(rep->flags & BF_SHUTW)) { +// EV_FD_CLR(t->cli_fd, DIR_RD); +// trace_term(t, TT_HTTP_CLI_2); +// } else { +// /* output was already closed */ +// fd_delete(t->cli_fd); +// t->cli_state = CL_STCLOSE; +// trace_term(t, TT_HTTP_CLI_3); +// } +// goto update_state; +// } +// /* last server read and buffer empty : we only check them when we're +// * allowed to forward the data. +// */ +// else if (!(rep->flags & BF_SHUTW) && /* not already done */ +// rep->flags & BF_EMPTY && rep->flags & BF_MAY_FORWARD && +// rep->flags & BF_SHUTR && !(t->flags & SN_SELF_GEN)) { +// buffer_shutw(rep); +// if (!(req->flags & BF_SHUTR)) { +// EV_FD_CLR(t->cli_fd, DIR_WR); +// shutdown(t->cli_fd, SHUT_WR); +// /* We must ensure that the read part is still alive when switching to shutw */ +// /* FIXME: is this still true ? */ +// EV_FD_SET(t->cli_fd, DIR_RD); +// req->rex = tick_add_ifset(now_ms, t->fe->timeout.client); +// trace_term(t, TT_HTTP_CLI_4); +// } else { +// fd_delete(t->cli_fd); +// t->cli_state = CL_STCLOSE; +// trace_term(t, TT_HTTP_CLI_5); +// } +// goto update_state; +// } +// /* read timeout */ +// else if ((req->flags & (BF_SHUTR|BF_READ_TIMEOUT)) == BF_READ_TIMEOUT) { +// buffer_shutr(req); +// if (!(rep->flags & BF_SHUTW)) { +// EV_FD_CLR(t->cli_fd, DIR_RD); +// trace_term(t, TT_HTTP_CLI_6); +// } else { +// /* output was already closed */ +// fd_delete(t->cli_fd); +// t->cli_state = CL_STCLOSE; +// trace_term(t, TT_HTTP_CLI_7); +// } +// if (!req->analysers) { +// if (!(t->flags & SN_ERR_MASK)) +// t->flags |= SN_ERR_CLITO; +// if (!(t->flags & SN_FINST_MASK)) { +// if (t->pend_pos) +// t->flags |= SN_FINST_Q; +// else if (!(req->flags & BF_CONNECTED)) +// t->flags |= SN_FINST_C; +// else +// t->flags |= SN_FINST_D; +// } +// } +// goto update_state; +// } +// /* write timeout */ +// else if ((rep->flags & (BF_SHUTW|BF_WRITE_TIMEOUT)) == BF_WRITE_TIMEOUT) { +// buffer_shutw(rep); +// if (!(req->flags & BF_SHUTR)) { +// EV_FD_CLR(t->cli_fd, DIR_WR); +// shutdown(t->cli_fd, SHUT_WR); +// /* We must ensure that the read part is still alive when switching to shutw */ +// /* FIXME: is this still true ? */ +// EV_FD_SET(t->cli_fd, DIR_RD); +// req->rex = tick_add_ifset(now_ms, t->fe->timeout.client); +// trace_term(t, TT_HTTP_CLI_8); +// } else { +// fd_delete(t->cli_fd); +// t->cli_state = CL_STCLOSE; +// trace_term(t, TT_HTTP_CLI_9); +// } +// if (!req->analysers) { +// if (!(t->flags & SN_ERR_MASK)) +// t->flags |= SN_ERR_CLITO; +// if (!(t->flags & SN_FINST_MASK)) { +// if (t->pend_pos) +// t->flags |= SN_FINST_Q; +// else if (!(req->flags & BF_CONNECTED)) +// t->flags |= SN_FINST_C; +// else +// t->flags |= SN_FINST_D; +// } +// } +// goto update_state; +// } +// +// update_timeouts: +// /* manage read timeout */ +// if (!(req->flags & BF_SHUTR)) { +// if (req->flags & BF_FULL) { +// /* no room to read more data */ +// if (EV_FD_COND_C(t->cli_fd, DIR_RD)) { +// /* stop reading until we get some space */ +// req->rex = TICK_ETERNITY; +// } +// } else { +// EV_FD_COND_S(t->cli_fd, DIR_RD); +// req->rex = tick_add_ifset(now_ms, t->fe->timeout.client); +// } +// } +// +// /* manage write timeout */ +// if (!(rep->flags & BF_SHUTW)) { +// /* first, we may have to produce data (eg: stats). +// * right now, this is limited to the SHUTR state. +// */ +// if (req->flags & BF_SHUTR && t->flags & SN_SELF_GEN) { +// produce_content(t); +// if (rep->flags & BF_EMPTY) { +// buffer_shutw(rep); +// fd_delete(t->cli_fd); +// t->cli_state = CL_STCLOSE; +// trace_term(t, TT_HTTP_CLI_10); +// goto update_state; +// } +// } +// +// /* we don't enable client write if the buffer is empty, nor if the server has to analyze it */ +// if ((rep->flags & BF_EMPTY) || !(rep->flags & BF_MAY_FORWARD)) { +// if (EV_FD_COND_C(t->cli_fd, DIR_WR)) { +// /* stop writing */ +// rep->wex = TICK_ETERNITY; +// } +// } else { +// /* buffer not empty */ +// EV_FD_COND_S(t->cli_fd, DIR_WR); +// if (!tick_isset(rep->wex)) { +// /* restart writing */ +// rep->wex = tick_add_ifset(now_ms, t->fe->timeout.client); +// if (!(req->flags & BF_SHUTR) && tick_isset(rep->wex) && tick_isset(req->rex)) { +// /* FIXME: to prevent the client from expiring read timeouts during writes, +// * we refresh it, except if it was already infinite. */ +// req->rex = rep->wex; +// } +// } +// } +// } +// return 0; /* other cases change nothing */ +// } +// else if (t->cli_state == CL_STCLOSE) { /* CL_STCLOSE: nothing to do */ +// if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { +// int len; +// len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", t->uniq_id, t->be->id, (unsigned short)t->cli_fd, (unsigned short)req->cons->fd); +// write(1, trash, len); +// } +// return 0; +// } +//#ifdef DEBUG_DEV +// fprintf(stderr, "FIXME !!!! impossible state at %s:%d = %d\n", __FILE__, __LINE__, t->cli_state); +// ABORT_NOW(); +//#endif +// return 0; +//} +// +// +///* Return 1 if we could get a new connection for session t, otherwise zero */ +//int tcp_get_connection(struct session *t) +//{ +// struct http_txn *txn = &t->txn; +// struct buffer *req = t->req; +// struct buffer *rep = t->rep; +// +// DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", +// now_ms, __FUNCTION__, +// cli_stnames[t->cli_state], +// rep->rex, req->wex, +// req->flags, rep->flags, +// req->l, rep->l); +// +// +// if ((rep->flags & BF_SHUTW) || +// ((req->flags & BF_SHUTR) && +// (req->flags & BF_EMPTY || t->be->options & PR_O_ABRT_CLOSE))) { /* give up */ +// req->wex = TICK_ETERNITY; +// if (t->pend_pos) +// t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); +// /* note that this must not return any error because it would be able to +// * overwrite the client_retnclose() output. +// */ +// if (txn->flags & TX_CLTARPIT) +// srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_T, 0, NULL); +// else +// srv_close_with_err(t, SN_ERR_CLICL, t->pend_pos ? SN_FINST_Q : SN_FINST_C, 0, NULL); +// +// trace_term(t, TT_HTTP_SRV_1); +// return 0; +// } +// +// /* stop here if we're not allowed to connect */ +// if (!(req->flags & BF_MAY_FORWARD)) +// return 0; +// +// /* the client allows the server to connect */ +// if (txn->flags & TX_CLTARPIT) { +// /* This connection is being tarpitted. The CLIENT side has +// * already set the connect expiration date to the right +// * timeout. We just have to check that it has not expired. +// */ +// if (!(req->flags & BF_WRITE_TIMEOUT)) +// return 0; +// +// /* We will set the queue timer to the time spent, just for +// * logging purposes. We fake a 500 server error, so that the +// * attacker will not suspect his connection has been tarpitted. +// * It will not cause trouble to the logs because we can exclude +// * the tarpitted connections by filtering on the 'PT' status flags. +// */ +// req->wex = TICK_ETERNITY; +// t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); +// srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_T, +// 500, error_message(t, HTTP_ERR_500)); +// trace_term(t, TT_HTTP_SRV_2); +// return 0; +// } +// +// /* Right now, we will need to create a connection to the server. +// * We might already have tried, and got a connection pending, in +// * which case we will not do anything till it's pending. It's up +// * to any other session to release it and wake us up again. +// */ +// if (t->pend_pos) { +// if (!(req->flags & BF_WRITE_TIMEOUT)) { +// return 0; +// } else { +// /* we've been waiting too long here */ +// req->wex = TICK_ETERNITY; +// t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); +// srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_Q, +// 503, error_message(t, HTTP_ERR_503)); +// trace_term(t, TT_HTTP_SRV_3); +// if (t->srv) +// t->srv->failed_conns++; +// t->be->failed_conns++; +// return 0; +// } +// } +// +// do { +// if (srv_redispatch_connect(t) != 0) +// return 0; +// +// if (t->srv && t->srv->rdr_len && t->flags & SN_REDIRECTABLE) { +// /* Server supporting redirection and it is possible. +// * Invalid requests are reported as such. It concerns all +// * the largest ones. +// */ +// struct chunk rdr; +// char *path; +// int len; +// +// /* 1: create the response header */ +// rdr.len = strlen(HTTP_302); +// rdr.str = trash; +// memcpy(rdr.str, HTTP_302, rdr.len); +// +// /* 2: add the server's prefix */ +// if (rdr.len + t->srv->rdr_len > sizeof(trash)) +// goto cancel_redir; +// +// memcpy(rdr.str + rdr.len, t->srv->rdr_pfx, t->srv->rdr_len); +// rdr.len += t->srv->rdr_len; +// +// /* 3: add the request URI */ +// path = http_get_path(txn); +// if (!path) +// goto cancel_redir; +// len = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path; +// if (rdr.len + len > sizeof(trash) - 4) /* 4 for CRLF-CRLF */ +// goto cancel_redir; +// +// memcpy(rdr.str + rdr.len, path, len); +// rdr.len += len; +// memcpy(rdr.str + rdr.len, "\r\n\r\n", 4); +// rdr.len += 4; +// +// srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, 302, &rdr); +// trace_term(t, TT_HTTP_SRV_3); +// +// /* FIXME: we should increase a counter of redirects per server and per backend. */ +// if (t->srv) +// t->srv->cum_sess++; +// return 0; +// cancel_redir: +// txn->status = 400; +// t->fe->failed_req++; +// srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, +// 400, error_message(t, HTTP_ERR_400)); +// trace_term(t, TT_HTTP_SRV_4); +// return 0; +// } +// +// /* try to (re-)connect to the server, and fail if we expire the +// * number of retries. +// */ +// if (srv_retryable_connect(t)) { +// t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); +// if (!(req->cons.flags & BC_KNOWN)) +// return 0; +// /* We got an FD */ +// return 1; +// } +// } while (1); +//} +// +// +///* Return 1 if the pending connection has failed and should be retried, +// * otherwise zero. +// */ +//int tcp_connection_failed(struct session *t) +//{ +// struct buffer *req = t->req; +// struct buffer *rep = t->rep; +// int conn_err; +// +// DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", +// now_ms, __FUNCTION__, +// cli_stnames[t->cli_state], +// rep->rex, req->wex, +// req->flags, rep->flags, +// req->l, rep->l); +// +// if ((rep->flags & BF_SHUTW) || +// ((req->flags & BF_SHUTR) && +// ((req->flags & BF_EMPTY && !(req->flags & BF_WRITE_STATUS)) || +// t->be->options & PR_O_ABRT_CLOSE))) { /* give up */ +// req->wex = TICK_ETERNITY; +// if (!(t->flags & SN_CONN_TAR)) { +// /* if we are in turn-around, we have already closed the FD */ +// fd_delete(req->cons->fd); +// req->cons->state = SI_ST_CLO; +// if (t->srv) { +// t->srv->cur_sess--; +// sess_change_server(t, NULL); +// } +// } +// +// /* note that this must not return any error because it would be able to +// * overwrite the client_retnclose() output. +// */ +// srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_C, 0, NULL); +// trace_term(t, TT_HTTP_SRV_5); +// return 0; +// } +// +// if (!(req->flags & (BF_WRITE_STATUS | BF_WRITE_TIMEOUT))) +// return 0; /* nothing changed */ +// +// if (!(req->flags & BF_WRITE_STATUS) || (req->flags & BF_WRITE_ERROR)) { +// /* timeout, asynchronous connect error or first write error */ +// if (t->flags & SN_CONN_TAR) { +// /* We are doing a turn-around waiting for a new connection attempt. */ +// if (!(req->flags & BF_WRITE_TIMEOUT)) +// return 0; +// t->flags &= ~SN_CONN_TAR; +// } +// else { +// fd_delete(req->cons->fd); +// req->cons->state = SI_ST_CLO; +// if (t->srv) { +// t->srv->cur_sess--; +// sess_change_server(t, NULL); +// } +// +// if (!(req->flags & BF_WRITE_STATUS)) +// conn_err = SN_ERR_SRVTO; // it was a connect timeout. +// else +// conn_err = SN_ERR_SRVCL; // it was an asynchronous connect error. +// +// /* ensure that we have enough retries left */ +// if (srv_count_retry_down(t, conn_err)) +// return 0; +// +// if (req->flags & BF_WRITE_ERROR) { +// /* we encountered an immediate connection error, and we +// * will have to retry connecting to the same server, most +// * likely leading to the same result. To avoid this, we +// * fake a connection timeout to retry after a turn-around +// * time of 1 second. We will wait in the previous if block. +// */ +// t->flags |= SN_CONN_TAR; +// req->wex = tick_add(now_ms, MS_TO_TICKS(1000)); +// return 0; +// } +// } +// +// if (t->srv && t->conn_retries == 0 && t->be->options & PR_O_REDISP) { +// /* We're on our last chance, and the REDISP option was specified. +// * We will ignore cookie and force to balance or use the dispatcher. +// */ +// /* let's try to offer this slot to anybody */ +// if (may_dequeue_tasks(t->srv, t->be)) +// process_srv_queue(t->srv); +// +// /* it's left to the dispatcher to choose a server */ +// t->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET); +// t->prev_srv = t->srv; +// +// /* first, get a connection */ +// if (srv_redispatch_connect(t)) { +// if (req->cons.flags & BC_KNOWN) +// return 0; +// /* we need to get a connection */ +// return 1; +// } +// } else { +// if (t->srv) +// t->srv->retries++; +// t->be->retries++; +// } +// +// do { +// /* Now we will try to either reconnect to the same server or +// * connect to another server. If the connection gets queued +// * because all servers are saturated, then we will go back to +// * the idle state where the buffer's consumer is marked as +// * unknown. +// */ +// if (srv_retryable_connect(t)) { +// t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now); +// if (req->cons.flags & BC_KNOWN) +// return 0; +// /* we did not get a connection */ +// return 1; +// } +// +// /* we need to redispatch the connection to another server */ +// if (srv_redispatch_connect(t)) { +// if (req->cons.flags & BC_KNOWN) +// return 0; +// /* we need to get a connection */ +// return 1; +// } +// } while (1); +// } +// else { /* no error and write OK */ +// t->logs.t_connect = tv_ms_elapsed(&t->logs.tv_accept, &now); +// +// if (req->flags & BF_EMPTY) { +// EV_FD_CLR(req->cons->fd, DIR_WR); +// req->wex = TICK_ETERNITY; +// } else { +// EV_FD_SET(req->cons->fd, DIR_WR); +// req->wex = tick_add_ifset(now_ms, t->be->timeout.server); +// if (tick_isset(req->wex)) { +// /* FIXME: to prevent the server from expiring read timeouts during writes, +// * we refresh it. */ +// rep->rex = req->wex; +// } +// } +// +// if (t->be->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */ +// EV_FD_SET(req->cons->fd, DIR_RD); +// rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); +// buffer_set_rlim(rep, BUFSIZE); /* no rewrite needed */ +// +// /* if the user wants to log as soon as possible, without counting +// bytes from the server, then this is the right moment. */ +// if (t->fe->to_log && !(t->logs.logwait & LW_BYTES)) { +// t->logs.t_close = t->logs.t_connect; /* to get a valid end date */ +// tcp_sess_log(t); +// } +//#ifdef CONFIG_HAP_TCPSPLICE +// if ((t->fe->options & t->be->options) & PR_O_TCPSPLICE) { +// /* TCP splicing supported by both FE and BE */ +// tcp_splice_splicefd(t->cli_fd, req->cons->fd, 0); +// } +//#endif +// } +// else { +// rep->analysers |= AN_RTR_HTTP_HDR; +// buffer_set_rlim(rep, BUFSIZE - MAXREWRITE); /* rewrite needed */ +// t->txn.rsp.msg_state = HTTP_MSG_RPBEFORE; +// /* reset hdr_idx which was already initialized by the request. +// * right now, the http parser does it. +// * hdr_idx_init(&t->txn.hdr_idx); +// */ +// } +// +// req->flags |= BF_CONNECTED; +// if (!rep->analysers) +// t->rep->flags |= BF_MAY_FORWARD; +// req->wex = TICK_ETERNITY; +// return 0; +// } +//} +// +// +///* +// * Tries to establish a connection to the server and associate it to the +// * request buffer's consumer side. It normally returns zero, but may return 1 +// * if it absolutely wants to be called again. +// */ +//int process_srv_conn(struct session *t) +//{ +// DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", +// now_ms, __FUNCTION__, +// cli_stnames[t->cli_state], +// t->rep->rex, t->req->wex, +// t->req->flags, t->rep->flags, +// t->req->l, t->rep->l); +// +// while (!(t->req->flags & BF_CONNECTED)) { +// if (!(t->req->cons.flags & BC_KNOWN)) { +// /* no connection in progress, get a new one */ +// if (!tcp_get_connection(t)) +// break; +// } else { +// /* connection in progress or just completed */ +// if (!tcp_connection_failed(t)) +// break; +// } +// } +// return 0; +//} +// +// +///* +// * Manages the server FSM and its socket during the DATA phase. It must not +// * be called when a file descriptor is not attached to the buffer. It normally +// * returns zero, but may return 1 if it absolutely wants to be called again. +// */ +//int process_srv_data(struct session *t) +//{ +// struct buffer *req = t->req; +// struct buffer *rep = t->rep; +// +// DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d\n", +// now_ms, __FUNCTION__, +// cli_stnames[t->cli_state], +// rep->rex, req->wex, +// req->flags, rep->flags, +// req->l, rep->l); +// +// /* we can skip most of the tests at once if some conditions are not met */ +// if (!((req->flags & (BF_WRITE_TIMEOUT|BF_WRITE_ERROR)) || +// (!(req->flags & BF_SHUTW) && +// (req->flags & (BF_EMPTY|BF_MAY_FORWARD)) == (BF_EMPTY|BF_MAY_FORWARD)) || +// (rep->flags & (BF_READ_TIMEOUT|BF_READ_ERROR)) || +// (!(rep->flags & BF_SHUTR) && rep->flags & (BF_READ_NULL|BF_SHUTW)))) +// goto update_timeouts; +// +// /* read or write error */ +// /* FIXME: what happens when we have to deal with HTTP ??? */ +// if (req->flags & BF_WRITE_ERROR || rep->flags & BF_READ_ERROR) { +// buffer_shutr(rep); +// buffer_shutw(req); +// fd_delete(req->cons->fd); +// req->cons->state = SI_ST_CLO; +// if (t->srv) { +// t->srv->cur_sess--; +// t->srv->failed_resp++; +// sess_change_server(t, NULL); +// } +// t->be->failed_resp++; +// trace_term(t, TT_HTTP_SRV_6); +// if (!rep->analysers) { +// if (!(t->flags & SN_ERR_MASK)) +// t->flags |= SN_ERR_SRVCL; +// if (!(t->flags & SN_FINST_MASK)) +// t->flags |= SN_FINST_D; +// } +// if (may_dequeue_tasks(t->srv, t->be)) +// process_srv_queue(t->srv); +// +// return 0; +// } +// +// /* last read, or end of client write */ +// if (!(rep->flags & BF_SHUTR) && /* not already done */ +// rep->flags & (BF_READ_NULL | BF_SHUTW)) { +// buffer_shutr(rep); +// if (!(req->flags & BF_SHUTW)) { +// EV_FD_CLR(req->cons->fd, DIR_RD); +// trace_term(t, TT_HTTP_SRV_7); +// } else { +// /* output was already closed */ +// fd_delete(req->cons->fd); +// req->cons->state = SI_ST_CLO; +// if (t->srv) { +// t->srv->cur_sess--; +// sess_change_server(t, NULL); +// } +// trace_term(t, TT_HTTP_SRV_8); +// +// if (may_dequeue_tasks(t->srv, t->be)) +// process_srv_queue(t->srv); +// return 0; +// } +// } +// /* end of client read and no more data to send. We can forward +// * the close when we're allowed to forward data (anytime right +// * now). If we're using option forceclose, then we may also +// * shutdown the outgoing write channel once the response starts +// * coming from the server. +// */ +// if (!(req->flags & BF_SHUTW) && /* not already done */ +// req->flags & BF_EMPTY && req->flags & BF_MAY_FORWARD && +// (req->flags & BF_SHUTR || +// (t->be->options & PR_O_FORCE_CLO && rep->flags & BF_READ_STATUS))) { +// buffer_shutw(req); +// if (!(rep->flags & BF_SHUTR)) { +// EV_FD_CLR(req->cons->fd, DIR_WR); +// shutdown(req->cons->fd, SHUT_WR); +// trace_term(t, TT_HTTP_SRV_9); +// /* We must ensure that the read part is still alive when switching to shutw */ +// /* FIXME: is this still true ? */ +// EV_FD_SET(req->cons->fd, DIR_RD); +// rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); +// } else { +// fd_delete(req->cons->fd); +// req->cons->state = SI_ST_CLO; +// if (t->srv) { +// t->srv->cur_sess--; +// sess_change_server(t, NULL); +// } +// trace_term(t, TT_HTTP_SRV_10); +// +// if (may_dequeue_tasks(t->srv, t->be)) +// process_srv_queue(t->srv); +// return 0; +// } +// } +// +// /* read timeout */ +// if ((rep->flags & (BF_SHUTR|BF_READ_TIMEOUT)) == BF_READ_TIMEOUT) { +// if (!rep->analysers) { +// if (!(t->flags & SN_ERR_MASK)) +// t->flags |= SN_ERR_SRVTO; +// if (!(t->flags & SN_FINST_MASK)) +// t->flags |= SN_FINST_D; +// } +// buffer_shutr(rep); +// if (!(req->flags & BF_SHUTW)) { +// EV_FD_CLR(req->cons->fd, DIR_RD); +// trace_term(t, TT_HTTP_SRV_11); +// } else { +// fd_delete(req->cons->fd); +// req->cons->state = SI_ST_CLO; +// if (t->srv) { +// t->srv->cur_sess--; +// sess_change_server(t, NULL); +// } +// trace_term(t, TT_HTTP_SRV_12); +// +// if (may_dequeue_tasks(t->srv, t->be)) +// process_srv_queue(t->srv); +// return 0; +// } +// } +// +// /* write timeout */ +// if ((req->flags & (BF_SHUTW|BF_WRITE_TIMEOUT)) == BF_WRITE_TIMEOUT) { +// if (!rep->analysers) { +// if (!(t->flags & SN_ERR_MASK)) +// t->flags |= SN_ERR_SRVTO; +// if (!(t->flags & SN_FINST_MASK)) +// t->flags |= SN_FINST_D; +// } +// buffer_shutw(req); +// if (!(rep->flags & BF_SHUTR)) { +// EV_FD_CLR(req->cons->fd, DIR_WR); +// shutdown(req->cons->fd, SHUT_WR); +// /* We must ensure that the read part is still alive when switching to shutw */ +// /* FIXME: is this still needed ? */ +// EV_FD_SET(req->cons->fd, DIR_RD); +// rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); +// trace_term(t, TT_HTTP_SRV_13); +// } else { +// fd_delete(req->cons->fd); +// req->cons->state = SI_ST_CLO; +// if (t->srv) { +// t->srv->cur_sess--; +// sess_change_server(t, NULL); +// } +// trace_term(t, TT_HTTP_SRV_14); +// +// if (may_dequeue_tasks(t->srv, t->be)) +// process_srv_queue(t->srv); +// return 0; +// } +// } +// +// update_timeouts: +// /* manage read timeout */ +// if (!(rep->flags & BF_SHUTR)) { +// if (rep->flags & BF_FULL) { +// if (EV_FD_COND_C(req->cons->fd, DIR_RD)) +// rep->rex = TICK_ETERNITY; +// } else { +// EV_FD_COND_S(req->cons->fd, DIR_RD); +// rep->rex = tick_add_ifset(now_ms, t->be->timeout.server); +// } +// } +// +// /* manage write timeout */ +// if (!(req->flags & BF_SHUTW)) { +// if (req->flags & BF_EMPTY || !(req->flags & BF_MAY_FORWARD)) { +// /* stop writing */ +// if (EV_FD_COND_C(req->cons->fd, DIR_WR)) +// req->wex = TICK_ETERNITY; +// } else { +// /* buffer not empty, there are still data to be transferred */ +// EV_FD_COND_S(req->cons->fd, DIR_WR); +// if (!tick_isset(req->wex)) { +// /* restart writing */ +// req->wex = tick_add_ifset(now_ms, t->be->timeout.server); +// if (!(rep->flags & BF_SHUTR) && tick_isset(req->wex) && tick_isset(rep->rex)) { +// /* FIXME: to prevent the server from expiring read timeouts during writes, +// * we refresh it, except if it was already infinite. +// */ +// rep->rex = req->wex; +// } +// } +// } +// } +// return 0; /* other cases change nothing */ +//} +// + /* * Produces data for the session depending on its source. Expects to be * called with client socket shut down on input. Right now, only statistics can @@ -5267,7 +6275,7 @@ void debug_hdr(const char *dir, struct session *t, const char *start, const char { int len, max; len = sprintf(trash, "%08x:%s.%s[%04x:%04x]: ", t->uniq_id, t->be->id, - dir, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); + dir, (unsigned short)t->cli_fd, (unsigned short)t->req->cons->fd); max = end - start; UBOUND(max, sizeof(trash) - len - 1); len += strlcpy2(trash + len, start, max + 1); diff --git a/src/proto_uxst.c b/src/proto_uxst.c index a56ab5d275..050c38a533 100644 --- a/src/proto_uxst.c +++ b/src/proto_uxst.c @@ -454,7 +454,6 @@ int uxst_event_accept(int fd) { s->req = s->rep = NULL; /* will be allocated later */ s->cli_fd = cfd; - s->srv_fd = -1; s->srv = NULL; s->pend_pos = NULL; @@ -791,7 +790,7 @@ static int process_uxst_cli(struct session *t) if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { int len; len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", t->uniq_id, t->be?t->be->id:"", - (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); + (unsigned short)t->cli_fd, (unsigned short)t->req->cons->fd); write(1, trash, len); } return 0; diff --git a/src/senddata.c b/src/senddata.c index 6bc5915b49..dea2c9674d 100644 --- a/src/senddata.c +++ b/src/senddata.c @@ -48,16 +48,19 @@ */ void client_retnclose(struct session *s, const struct chunk *msg) { - EV_FD_CLR(s->cli_fd, DIR_RD); - EV_FD_SET(s->cli_fd, DIR_WR); - buffer_shutr(s->req); - buffer_flush(s->req); - s->rep->wex = tick_add_ifset(now_ms, s->rep->wto); - s->rep->flags |= BF_MAY_FORWARD; + //FIXME: must move to lower level + //EV_FD_CLR(s->cli_fd, DIR_RD); + //EV_FD_SET(s->cli_fd, DIR_WR); + buffer_abort(s->req); + s->cli_state = CL_STSHUTR; // FIXME: still used by unix sockets buffer_flush(s->rep); + buffer_shutr_now(s->rep); if (msg && msg->len) buffer_write(s->rep, msg->str, msg->len); + + s->rep->wex = tick_add_ifset(now_ms, s->rep->wto); + s->rep->flags |= BF_MAY_FORWARD; } diff --git a/src/stream_sock.c b/src/stream_sock.c index b35b9fe767..cc33b815d8 100644 --- a/src/stream_sock.c +++ b/src/stream_sock.c @@ -223,9 +223,12 @@ int stream_sock_read(int fd) { if (tick_isset(b->rex) && b->flags & BF_PARTIAL_READ) b->rex = tick_add_ifset(now_ms, b->rto); + if (!(b->flags & BF_READ_STATUS)) + goto out_skip_wakeup; out_wakeup: - if (b->flags & BF_READ_STATUS) - task_wakeup(fdtab[fd].owner); + task_wakeup(fdtab[fd].owner); + + out_skip_wakeup: fdtab[fd].ev &= ~FD_POLL_IN; return retval; @@ -241,7 +244,6 @@ int stream_sock_read(int fd) { */ fdtab[fd].state = FD_STERROR; fdtab[fd].ev &= ~FD_POLL_STICKY; - b->flags |= BF_READ_ERROR; b->rex = TICK_ETERNITY; goto out_wakeup; } @@ -296,7 +298,7 @@ int stream_sock_write(int fd) { if (errno == EALREADY || errno == EINPROGRESS) { retval = 0; - goto out_wakeup; + goto out_may_wakeup; } if (errno && errno != EISCONN) @@ -392,9 +394,13 @@ int stream_sock_write(int fd) { } } + out_may_wakeup: + if (!(b->flags & BF_WRITE_STATUS)) + goto out_skip_wakeup; out_wakeup: - if (b->flags & BF_WRITE_STATUS) - task_wakeup(fdtab[fd].owner); + task_wakeup(fdtab[fd].owner); + + out_skip_wakeup: fdtab[fd].ev &= ~FD_POLL_OUT; return retval; @@ -404,7 +410,6 @@ int stream_sock_write(int fd) { */ fdtab[fd].state = FD_STERROR; fdtab[fd].ev &= ~FD_POLL_STICKY; - b->flags |= BF_WRITE_ERROR; b->wex = TICK_ETERNITY; goto out_wakeup; } -- 2.39.5