diff --git a/Makefile b/Makefile index d42d5fb6e..e6c94c894 100644 --- a/Makefile +++ b/Makefile @@ -750,7 +750,7 @@ OBJS = src/haproxy.o src/base64.o src/protocol.o \ src/session.o src/stream.o src/hdr_idx.o src/ev_select.o src/signal.o \ src/acl.o src/sample.o src/memory.o src/freq_ctr.o src/auth.o src/proto_udp.o \ src/compression.o src/payload.o src/hash.o src/pattern.o src/map.o \ - src/namespace.o src/mailers.o src/dns.o src/vars.o + src/namespace.o src/mailers.o src/dns.o src/vars.o src/filters.o EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \ $(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \ diff --git a/include/proto/filters.h b/include/proto/filters.h new file mode 100644 index 000000000..d86042456 --- /dev/null +++ b/include/proto/filters.h @@ -0,0 +1,138 @@ +/* + * include/proto/filters.h + * This file defines function prototypes for stream filters management. + * + * Copyright (C) 2015 Qualys Inc., Christopher Faulet + * + * 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 _PROTO_FILTERS_H +#define _PROTO_FILTERS_H + +#include +#include +#include +#include +#include + +#include + +/* Useful macros to access per-channel values. It can be safely used inside + * filters. */ +#define CHN_IDX(chn) (((chn)->flags & CF_ISRESP) == CF_ISRESP) +#define FLT_NXT(flt, chn) ((flt)->next[CHN_IDX(chn)]) +#define FLT_FWD(flt, chn) ((flt)->fwd[CHN_IDX(chn)]) + +extern struct pool_head *pool2_filter; + +int flt_init(struct proxy *p); +void flt_deinit(struct proxy *p); +int flt_check(struct proxy *p); + +int flt_stream_start(struct stream *s); +void flt_stream_stop(struct stream *s); + +int flt_http_headers(struct stream *s, struct http_msg *msg); +int flt_http_start_chunk(struct stream *s, struct http_msg *msg); +int flt_http_data(struct stream *s, struct http_msg *msg); +int flt_http_last_chunk(struct stream *s, struct http_msg *msg); +int flt_http_end_chunk(struct stream *s, struct http_msg *msg); +int flt_http_chunk_trailers(struct stream *s, struct http_msg *msg); +int flt_http_end(struct stream *s, struct http_msg *msg); +void flt_http_reset(struct stream *s, struct http_msg *msg); + +void flt_http_reply(struct stream *s, short status, const struct chunk *msg); +int flt_http_forward_data(struct stream *s, struct http_msg *msg, unsigned int len); + +int flt_start_analyze(struct stream *s, struct channel *chn, unsigned int an_bit); +int flt_analyze(struct stream *s, struct channel *chn, unsigned int an_bit); +int flt_end_analyze(struct stream *s, struct channel *chn, unsigned int an_bit); + +int flt_xfer_data(struct stream *s, struct channel *chn, unsigned int an_bit); + +void flt_register_keywords(struct flt_kw_list *kwl); +struct flt_kw *flt_find_kw(const char *kw); +void flt_dump_kws(char **out); + +static inline void +flt_set_forward_data(struct filter *filter, struct channel *chn) +{ + filter->flags[CHN_IDX(chn)] |= FILTER_FL_FORWARD_DATA; +} + +static inline void +flt_reset_forward_data(struct filter *filter, struct channel *chn) +{ + filter->flags[CHN_IDX(chn)] &= ~FILTER_FL_FORWARD_DATA; +} + +static inline int +flt_want_forward_data(struct filter *filter, const struct channel *chn) +{ + return filter->flags[CHN_IDX(chn)] & FILTER_FL_FORWARD_DATA; +} + + +/* This function must be called when a filter alter incoming data. It updates + * next offset value of all filter's predecessors. Do not call this function + * when a filter change the size of incomding data leads to an undefined + * behavior. + * + * This is the filter's responsiblitiy to update data itself. For now, it is + * unclear to know how to handle data updates, so we do the minimum here. For + * example, if you filter an HTTP message, we must update msg->next and + * msg->chunk_len values. + */ +static inline void +flt_change_next_size(struct filter *filter, struct channel *chn, int len) +{ + struct stream *s = chn_strm(chn); + struct filter *f; + + list_for_each_entry(f, &s->strm_flt.filters, list) { + if (f == filter) + break; + FLT_NXT(f, chn) += len; + } +} + +/* This function must be called when a filter alter forwarded data. It updates + * offset values (next and forward) of all filters. Do not call this function + * when a filter change the size of forwarded data leads to an undefined + * behavior. + * + * This is the filter's responsiblitiy to update data itself. For now, it is + * unclear to know how to handle data updates, so we do the minimum here. For + * example, if you filter an HTTP message, we must update msg->next and + * msg->chunk_len values. + */ +static inline void +flt_change_forward_size(struct filter *filter, struct channel *chn, int len) +{ + struct stream *s = chn_strm(chn); + struct filter *f; + int before = 1; + + list_for_each_entry(f, &s->strm_flt.filters, list) { + if (f == filter) + before = 0; + if (before) + FLT_FWD(f, chn) += len; + FLT_NXT(f, chn) += len; + } +} + + +#endif /* _PROTO_FILTERS_H */ diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index ef564dc7f..9317a551e 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -126,6 +126,7 @@ struct http_txn *http_alloc_txn(struct stream *s); void http_init_txn(struct stream *s); void http_end_txn(struct stream *s); void http_reset_txn(struct stream *s); +void http_end_txn_clean_session(struct stream *s); void http_adjust_conn_mode(struct stream *s, struct http_txn *txn, struct http_msg *msg); struct act_rule *parse_http_req_cond(const char **args, const char *file, int linenum, struct proxy *proxy); @@ -284,6 +285,7 @@ static inline const char *http_msg_state_str(int msg_state) case HTTP_MSG_DATA: return "MSG_DATA"; case HTTP_MSG_CHUNK_CRLF: return "MSG_CHUNK_CRLF"; case HTTP_MSG_TRAILERS: return "MSG_TRAILERS"; + case HTTP_MSG_ENDING: return "MSG_ENDING"; case HTTP_MSG_DONE: return "MSG_DONE"; case HTTP_MSG_CLOSING: return "MSG_CLOSING"; case HTTP_MSG_CLOSED: return "MSG_CLOSED"; diff --git a/include/types/channel.h b/include/types/channel.h index e95c462ab..e43e8ebff 100644 --- a/include/types/channel.h +++ b/include/types/channel.h @@ -157,6 +157,13 @@ #define AN_RES_STORE_RULES 0x00080000 /* table persistence matching */ #define AN_RES_HTTP_XFER_BODY 0x00100000 /* forward response body */ +#define AN_FLT_START_FE 0x01000000 +#define AN_FLT_START_BE 0x02000000 +#define AN_FLT_END 0x04000000 +#define AN_FLT_XFER_DATA 0x08000000 + +#define AN_FLT_ALL_FE 0x0d000000 +#define AN_FLT_ALL_BE 0x0e000000 /* Magic value to forward infinite size (TCP, ...), used with ->to_forward */ #define CHN_INFINITE_FORWARD MAX_RANGE(unsigned int) diff --git a/include/types/filters.h b/include/types/filters.h new file mode 100644 index 000000000..635b2d191 --- /dev/null +++ b/include/types/filters.h @@ -0,0 +1,233 @@ +/* + * include/types/filteers.h + * This file defines everything related to stream filters. + * + * Copyright (C) 2015 Qualys Inc., Christopher Faulet + * + * 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_FILTERS_H +#define _TYPES_FILTERS_H + +#include +#include + +struct http_msg; +struct proxy; +struct stream; +struct channel; +struct filter; + +/* Descriptor for a "filter" keyword. The ->parse() function returns 0 in case + * of success, or a combination of ERR_* flags if an error is encountered. The + * function pointer can be NULL if not implemented. The function also has an + * access to the current "server" config line. The ->skip value tells the parser + * how many words have to be skipped after the keyword. If the function needs to + * parse more keywords, it needs to update cur_arg. + */ +struct flt_kw { + const char *kw; + int (*parse)(char **args, int *cur_arg, struct proxy *px, + struct filter *filter, char **err); +}; + +/* + * A keyword list. It is a NULL-terminated array of keywords. It embeds a struct + * list in order to be linked to other lists, allowing it to easily be declared + * where it is needed, and linked without duplicating data nor allocating + * memory. It is also possible to indicate a scope for the keywords. + */ +struct flt_kw_list { + const char *scope; + struct list list; + struct flt_kw kw[VAR_ARRAY]; +}; + +/* + * Filter flags set for a specific filter on channel + * + * - FILTER_FL_FORWARD_DATA : When this flag is set, the rest of the data is + * directly forwarded. For chunk-encoded HTTP + * messages, this flag is reseted between each + * chunks. + */ +#define FILTER_FL_FORWARD_DATA 0x00000001 + + +/* + * Callbacks available on a filter: + * + * - init : Initializes the filter for a proxy. Returns a + * negative value if an error occurs. + * - deinit : Cleans up what the init function has done. + * - check : Check the filter config for a proxy. Returns the + * number of errors encountered. + * + * + * - stream_start : Called when a stream is started. This callback will + * only be called for filters defined on a proxy with + * the frontend capability. + * Returns a negative value if an error occurs, any + * other value otherwise. + * - stream_stop : Called when a stream is stopped. This callback will + * only be called for filters defined on a proxy with + * the frontend capability. + * + * + * - channel_start_analyze: Called when a filter starts to analyze a channel. + * Returns a negative value if an error occurs, 0 if + * it needs to wait, any other value otherwise. + * - channel_analyze : Called before each analyzer attached to a channel, + * expects analyzers responsible for data sending. + * Returns a negative value if an error occurs, 0 if + * it needs to wait, any other value otherwise. + * - channel_end_analyze : Called when all other analyzers have finished their + * processing. + * Returns a negative value if an error occurs, 0 if + * it needs to wait, any other value otherwise. + * + * + * - http_headers : Called just before headers sending and parsing of + * the body. At this step, headers are fully parsed + * and the processing on it is finished. + * Returns a negative value if an error occurs, 0 if + * it needs to read more data (or to wait for some + * reason), any other value otherwise. + * - http_start_chunk : Called when we start to process a new chunk + * (for chunk-encoded request/response only). At this + * step, the chunk length is known and non-null. + * Returns a negative value if an error occurs, 0 if + * it needs to read more data (or to wait for some + * reason), any other value otherwise. + * - http_data : Called when unparsed body data are available. + * Returns a negative value if an error occurs, else + * the number of consumed bytes. + * - http_last_chunk : Called when the last chunk (with a zero length) is + * received. + * Returns a negative value if an error occurs, 0 if + * it needs to read more data (or to wait for some + * reason), any other value otherwise. + * - http_end_chunk : Called at the end of a chunk (expect for the last + * one). + * Returns a negative value if an error occurs, 0 if + * it needs to read more data (or to wait for some + * reason), any other value otherwise. + * - http_chunk_trailers : Called when part of trailer headers of a + * chunk-encoded request/response are ready to be + * processed. + * Returns a negative value if an error occurs, any + * other value otherwise. + * - http_end : Called when all the request/response has been + * processed and all body data has been forwarded. + * Returns a negative value if an error occurs, 0 if + * it needs to wait for some reason, any other value + * otherwise. + * - http_reset : Called when the HTTP message is reseted. It happens + * when a 100-continue response is received. + * Returns nothing. + * - http_reply : Called when, at any time, HA proxy decides to stop + * the HTTP message's processing and to send a message + * to the client (mainly, when an error or a redirect + * occur). + * Returns nothing. + * - http_forward_data : Called when some data can be consumed. + * Returns a negative value if an error occurs, else + * the number of forwarded bytes. + * - tcp_data : Called when unparsed data are available. + * Returns a negative value if an error occurs, else + * the number of consumed bytes. + * - tcp_forward_data : Called when some data can be consumed. + * Returns a negative value if an error occurs, else + * or the number of forwarded bytes. + */ +struct flt_ops { + /* + * Callbacks to manage the filter lifecycle + */ + int (*init) (struct proxy *p, struct filter *f); + void (*deinit)(struct proxy *p, struct filter *f); + int (*check) (struct proxy *p, struct filter *f); + + /* + * Stream callbacks + */ + int (*stream_start) (struct stream *s, struct filter *f); + void (*stream_stop) (struct stream *s, struct filter *f); + + /* + * Channel callbacks + */ + int (*channel_start_analyze)(struct stream *s, struct filter *f, struct channel *chn); + int (*channel_analyze) (struct stream *s, struct filter *f, struct channel *chn, unsigned int an_bit); + int (*channel_end_analyze) (struct stream *s, struct filter *f, struct channel *chn); + + /* + * HTTP callbacks + */ + int (*http_headers) (struct stream *s, struct filter *f, struct http_msg *msg); + int (*http_start_chunk) (struct stream *s, struct filter *f, struct http_msg *msg); + int (*http_data) (struct stream *s, struct filter *f, struct http_msg *msg); + int (*http_last_chunk) (struct stream *s, struct filter *f, struct http_msg *msg); + int (*http_end_chunk) (struct stream *s, struct filter *f, struct http_msg *msg); + int (*http_chunk_trailers)(struct stream *s, struct filter *f, struct http_msg *msg); + int (*http_end) (struct stream *s, struct filter *f, struct http_msg *msg); + void (*http_reset) (struct stream *s, struct filter *f, struct http_msg *msg); + + void (*http_reply) (struct stream *s, struct filter *f, short status, + const struct chunk *msg); + int (*http_forward_data) (struct stream *s, struct filter *f, struct http_msg *msg, + unsigned int len); + + /* + * TCP callbacks + */ + int (*tcp_data) (struct stream *s, struct filter *f, struct channel *chn); + int (*tcp_forward_data)(struct stream *s, struct filter *f, struct channel *chn, + unsigned int len); +}; + +/* + * Structure representing the state of a filter. When attached to a proxy, only + * and field (and optionnaly ) are set. All other fields are + * used when the filter is attached to a stream. + * + * 2D-Array fields are used to store info per channel. The first index stands + * for the request channel, and the second one for the response channel. + * Especially, and are offets representing amount of data that the + * filter are, respectively, parsed and forwarded on a channel. Filters can + * access these values using FLT_NXT and FLT_FWD macros. + */ +struct filter { + const char *id; /* The filter id */ + struct flt_ops *ops; /* The filter callbacks */ + void *conf; /* The filter configuration */ + void *ctx; /* The filter context (opaque) */ + int is_backend_filter; /* Flag to specify if the filter is a "backend" filter */ + unsigned int flags[2]; /* 0: request, 1: response */ + unsigned int next[2]; /* Offset, relative to buf->p, to the next byte to parse for a specific channel + * 0: request channel, 1: response channel */ + unsigned int fwd[2]; /* Offset, relative to buf->p, to the next byte to forward for a specific channel + * 0: request channel, 1: response channel */ + struct list list; /* Next filter for the same proxy/stream */ +}; + +#endif /* _TYPES_FILTERS_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/proto_http.h b/include/types/proto_http.h index e5e96679c..c6699bd1e 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -28,7 +28,7 @@ #include #include -#include +#include /* These are the flags that are found in txn->flags */ @@ -170,10 +170,11 @@ enum ht_state { HTTP_MSG_CHUNK_CRLF = 31, // skipping CRLF after data chunk HTTP_MSG_TRAILERS = 32, // trailers (post-data entity headers) /* we enter this state when we've received the end of the current message */ - HTTP_MSG_DONE = 33, // message end received, waiting for resync or close - HTTP_MSG_CLOSING = 34, // shutdown_w done, not all bytes sent yet - HTTP_MSG_CLOSED = 35, // shutdown_w done, all bytes sent - HTTP_MSG_TUNNEL = 36, // tunneled data after DONE + HTTP_MSG_ENDING = 33, // message end received, wait that the filters end too + HTTP_MSG_DONE = 34, // message end received, waiting for resync or close + HTTP_MSG_CLOSING = 35, // shutdown_w done, not all bytes sent yet + HTTP_MSG_CLOSED = 36, // shutdown_w done, all bytes sent + HTTP_MSG_TUNNEL = 37, // tunneled data after DONE } __attribute__((packed)); /* @@ -194,6 +195,7 @@ enum ht_state { * contents if something needs them during a redispatch. */ #define HTTP_MSGF_WAIT_CONN 0x00000010 /* Wait for connect() to be confirmed before processing body */ +#define HTTP_MSGF_COMPRESSING 0x00000020 /* data compression is in progress */ /* Redirect flags */ diff --git a/include/types/proxy.h b/include/types/proxy.h index e18ae72ee..71fd35dd6 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -431,6 +432,8 @@ struct proxy { * this backend. If not specified or void, then the backend * name is used */ + + struct list filters; }; struct switching_rule { diff --git a/include/types/stream.h b/include/types/stream.h index bba5f4333..292e36a4e 100644 --- a/include/types/stream.h +++ b/include/types/stream.h @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -45,7 +46,6 @@ #include #include - /* Various Stream Flags, bits values 0x01 to 0x100 (shift 0) */ #define SF_DIRECT 0x00000001 /* connection made on the server matching the client cookie */ #define SF_ASSIGNED 0x00000002 /* no need to assign a server to this stream */ @@ -127,6 +127,11 @@ struct stream { struct http_txn *txn; /* current HTTP transaction being processed. Should become a list. */ + struct { + struct list filters; + struct filter *current[2]; /* 0: request, 1: response */ + } strm_flt; + struct task *task; /* the task associated with this stream */ struct list list; /* position in global streams list */ struct list by_srv; /* position in server stream list */ diff --git a/src/cfgparse.c b/src/cfgparse.c index ec47542ca..22ff6af4a 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -45,6 +45,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -8477,6 +8479,9 @@ int check_config_validity() } } + /* Check filter configuration, if any */ + cfgerr += flt_check(curproxy); + if (curproxy->cap & PR_CAP_FE) { if (!curproxy->accept) curproxy->accept = frontend_accept; @@ -8492,6 +8497,12 @@ int check_config_validity() /* both TCP and HTTP must check switching rules */ curproxy->fe_req_ana |= AN_REQ_SWITCHING_RULES; + + /* Add filters analyzers if needed */ + if (!LIST_ISEMPTY(&curproxy->filters)) { + curproxy->fe_req_ana |= AN_FLT_ALL_FE; + curproxy->fe_rsp_ana |= AN_FLT_ALL_FE; + } } if (curproxy->cap & PR_CAP_BE) { @@ -8512,6 +8523,12 @@ int check_config_validity() */ if (curproxy->options2 & PR_O2_RDPC_PRST) curproxy->be_req_ana |= AN_REQ_PRST_RDP_COOKIE; + + /* Add filters analyzers if needed */ + if (!LIST_ISEMPTY(&curproxy->filters)) { + curproxy->be_req_ana |= AN_FLT_ALL_BE; + curproxy->be_rsp_ana |= AN_FLT_ALL_BE; + } } } diff --git a/src/compression.c b/src/compression.c index 97f1fd7fc..471c102b1 100644 --- a/src/compression.c +++ b/src/compression.c @@ -218,11 +218,6 @@ int http_compression_buffer_add_data(struct stream *s, struct buffer *in, struct /* restore original buffer pointer */ b_rew(in, msg->next); - - if (consumed_data > 0) { - msg->next += consumed_data; - msg->chunk_len -= consumed_data; - } return consumed_data; } @@ -307,7 +302,7 @@ int http_compression_buffer_end(struct stream *s, struct buffer **in, struct buf if (msg->msg_state >= HTTP_MSG_TRAILERS) { memcpy(tail, "0\r\n", 3); tail += 3; - if (msg->msg_state >= HTTP_MSG_DONE) { + if (msg->msg_state >= HTTP_MSG_ENDING) { memcpy(tail, "\r\n", 2); tail += 2; } diff --git a/src/filters.c b/src/filters.c new file mode 100644 index 000000000..ab88f23b9 --- /dev/null +++ b/src/filters.c @@ -0,0 +1,946 @@ +/* + * Stream filters related variables and functions. + * + * Copyright (C) 2015 Qualys Inc., Christopher Faulet + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +/* Pool used to allocate filters */ +struct pool_head *pool2_filter = NULL; + +static int handle_analyzer_result(struct stream *s, struct channel *chn, unsigned int an_bit, int ret); + +/* - RESUME_FILTER_LOOP and RESUME_FILTER_END must always be used together. + * The first one begins a loop and the seconds one ends it. + * + * - BREAK_EXECUTION must be used to break the loop and set the filter from + * which to resume the next time. + * + * Here is an exemple: + * + * RESUME_FILTER_LOOP(stream, channel) { + * ... + * if (cond) + * BREAK_EXECUTION(stream, channel, label); + * ... + * } RESUME_FILTER_END; + * ... + * label: + * ... + * + */ +#define RESUME_FILTER_LOOP(strm, chn) \ + do { \ + struct filter *filter; \ + \ + if ((strm)->strm_flt.current[CHN_IDX(chn)]) { \ + filter = (strm)->strm_flt.current[CHN_IDX(chn)]; \ + (strm)->strm_flt.current[CHN_IDX(chn)] = NULL; \ + goto resume_execution; \ + } \ + \ + list_for_each_entry(filter, &s->strm_flt.filters, list) { \ + resume_execution: + +#define RESUME_FILTER_END \ + } \ + } while(0) + +#define BREAK_EXECUTION(strm, chn, label) \ + do { \ + (strm)->strm_flt.current[CHN_IDX(chn)] = filter; \ + goto label; \ + } while (0) + + +/* List head of all known filter keywords */ +static struct flt_kw_list flt_keywords = { + .list = LIST_HEAD_INIT(flt_keywords.list) +}; + +/* + * Registers the filter keyword list as a list of valid keywords for next + * parsing sessions. + */ +void +flt_register_keywords(struct flt_kw_list *kwl) +{ + LIST_ADDQ(&flt_keywords.list, &kwl->list); +} + +/* + * Returns a pointer to the filter keyword , or NULL if not found. If the + * keyword is found with a NULL ->parse() function, then an attempt is made to + * find one with a valid ->parse() function. This way it is possible to declare + * platform-dependant, known keywords as NULL, then only declare them as valid + * if some options are met. Note that if the requested keyword contains an + * opening parenthesis, everything from this point is ignored. + */ +struct flt_kw * +flt_find_kw(const char *kw) +{ + int index; + const char *kwend; + struct flt_kw_list *kwl; + struct flt_kw *ret = NULL; + + kwend = strchr(kw, '('); + if (!kwend) + kwend = kw + strlen(kw); + + list_for_each_entry(kwl, &flt_keywords.list, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + if ((strncmp(kwl->kw[index].kw, kw, kwend - kw) == 0) && + kwl->kw[index].kw[kwend-kw] == 0) { + if (kwl->kw[index].parse) + return &kwl->kw[index]; /* found it !*/ + else + ret = &kwl->kw[index]; /* may be OK */ + } + } + } + return ret; +} + +/* + * Dumps all registered "filter" keywords to the string pointer. The + * unsupported keywords are only dumped if their supported form was not found. + */ +void +flt_dump_kws(char **out) +{ + struct flt_kw_list *kwl; + int index; + + *out = NULL; + list_for_each_entry(kwl, &flt_keywords.list, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + if (kwl->kw[index].parse || + flt_find_kw(kwl->kw[index].kw) == &kwl->kw[index]) { + memprintf(out, "%s[%4s] %s%s\n", *out ? *out : "", + kwl->scope, + kwl->kw[index].kw, + kwl->kw[index].parse ? "" : " (not supported)"); + } + } + } +} + +/* + * Parses the "filter" keyword. All keywords must be handled by filters + * themselves + */ +static int +parse_filter(char **args, int section_type, struct proxy *curpx, + struct proxy *defpx, const char *file, int line, char **err) +{ + struct filter *filter = NULL; + + /* Filter cannot be defined on a default proxy */ + if (curpx == defpx) { + memprintf(err, "parsing [%s:%d] : %s is only allowed in a 'default' section.", + file, line, args[0]); + return -1; + } + if (!strcmp(args[0], "filter")) { + struct flt_kw *kw; + int cur_arg; + + if (!*args[1]) { + memprintf(err, + "parsing [%s:%d] : missing argument for '%s' in %s '%s'.", + file, line, args[0], proxy_type_str(curpx), curpx->id); + goto error; + } + filter = pool_alloc2(pool2_filter); + if (!filter) { + memprintf(err, "'%s' : out of memory", args[0]); + goto error; + } + memset(filter, 0, sizeof(*filter)); + + cur_arg = 1; + kw = flt_find_kw(args[cur_arg]); + if (kw) { + if (!kw->parse) { + memprintf(err, "parsing [%s:%d] : '%s' : " + "'%s' option is not implemented in this version (check build options).", + file, line, args[0], args[cur_arg]); + goto error; + } + if (kw->parse(args, &cur_arg, curpx, filter, err) != 0) { + if (err && *err) + memprintf(err, "'%s' : '%s'", + args[0], *err); + else + memprintf(err, "'%s' : error encountered while processing '%s'", + args[0], args[cur_arg]); + goto error; + } + } + else { + flt_dump_kws(err); + indent_msg(err, 4); + memprintf(err, "'%s' : unknown keyword '%s'.%s%s", + args[0], args[cur_arg], + err && *err ? " Registered keywords :" : "", err && *err ? *err : ""); + goto error; + } + if (*args[cur_arg]) { + memprintf(err, "'%s %s' : unknown keyword '%s'.", + args[0], args[1], args[cur_arg]); + goto error; + } + + LIST_ADDQ(&curpx->filters, &filter->list); + } + return 0; + + error: + if (filter) + pool_free2(pool2_filter, filter); + return -1; + + +} + +/* + * Calls 'init' callback for all filters attached to a proxy. This happens after + * the configuration parsing. Filters can finish to fill their config. Returns + * (ERR_ALERT|ERR_FATAL) if an error occurs, 0 otherwise. + */ +int +flt_init(struct proxy *proxy) +{ + struct filter *filter; + + list_for_each_entry(filter, &proxy->filters, list) { + if (filter->ops->init && filter->ops->init(proxy, filter) < 0) + return ERR_ALERT|ERR_FATAL; + } + return 0; +} + +/* + * Calls 'check' callback for all filters attached to a proxy. This happens + * after the configuration parsing but before filters initialization. Returns + * the number of encountered errors. + */ +int +flt_check(struct proxy *proxy) +{ + struct filter *filter; + int err = 0; + + list_for_each_entry(filter, &proxy->filters, list) { + if (filter->ops->check) + err += filter->ops->check(proxy, filter); + } + return err; +} + +/* + * Calls 'denit' callback for all filters attached to a proxy. This happens when + * HAProxy is stopped. + */ +void +flt_deinit(struct proxy *proxy) +{ + struct filter *filter, *back; + + list_for_each_entry_safe(filter, back, &proxy->filters, list) { + if (filter->ops->deinit) + filter->ops->deinit(proxy, filter); + LIST_DEL(&filter->list); + pool_free2(pool2_filter, filter); + } +} + +/* + * Calls 'stream_start' for all filters attached to a stream. This happens when + * the stream is created, just after calling flt_stream_init + * function. Returns -1 if an error occurs, 0 otherwise. + */ +int +flt_stream_start(struct stream *s) +{ + struct filter *filter; + + list_for_each_entry(filter, &s->strm_flt.filters, list) { + if (filter->ops->stream_start && filter->ops->stream_start(s, filter) < 0) + return -1; + } + return 0; +} + +/* + * Calls 'stream_stop' for all filters attached to a stream. This happens when + * the stream is stopped, just before calling flt_stream_release function. + */ +void +flt_stream_stop(struct stream *s) +{ + struct filter *filter; + + list_for_each_entry(filter, &s->strm_flt.filters, list) { + if (filter->ops->stream_stop) + filter->ops->stream_stop(s, filter); + } +} + +int +flt_http_headers(struct stream *s, struct http_msg *msg) +{ + struct filter *filter; + int ret = 1; + + if (LIST_ISEMPTY(&s->strm_flt.filters)) + goto end; + + RESUME_FILTER_LOOP(s, msg->chn) { + if (filter->ops && filter->ops->http_headers) { + ret = filter->ops->http_headers(s, filter, msg); + if (ret <= 0) + BREAK_EXECUTION(s, msg->chn, end); + } + } RESUME_FILTER_END; + + /* We increase FLT_NXT offset after all processing on headers because + * any filter can alter them. So the definitive size of headers + * (msg->sov) is only known when all filters have been called. */ + list_for_each_entry(filter, &s->strm_flt.filters, list) { + FLT_NXT(filter, msg->chn) = msg->sov; + } + end: + return ret; +} + +int +flt_http_start_chunk(struct stream *s, struct http_msg *msg) +{ + int ret = 1; + + if (LIST_ISEMPTY(&s->strm_flt.filters)) + goto end; + + RESUME_FILTER_LOOP(s, msg->chn) { + if (filter->ops->http_start_chunk) { + ret = filter->ops->http_start_chunk(s, filter, msg); + if (ret <= 0) + BREAK_EXECUTION(s, msg->chn, end); + } + FLT_NXT(filter, msg->chn) += msg->sol; + } RESUME_FILTER_END; + end: + return ret; +} + +/* + * Calls 'http_data' callback for all "data" filters attached to a stream. This + * function is called when incoming data are available (excluding chunks + * envelope for chunked messages) in the AN_REQ_HTTP_XFER_BODY and + * AN_RES_HTTP_XFER_BODY analyzers. It takes care to update the next offset of + * filters and adjusts available data to be sure that a filter cannot parse more + * data than its predecessors. A filter can choose to not consume all available + * data. Returns -1 if an error occurs, the number of consumed bytes otherwise. + */ +int +flt_http_data(struct stream *s, struct http_msg *msg) +{ + struct filter *filter = NULL; + unsigned int buf_i; + int ret = 0; + + /* No filter, consume all available data */ + if (LIST_ISEMPTY(&s->strm_flt.filters)) { + ret = MIN(msg->chunk_len, msg->chn->buf->i - msg->next); + goto end; + } + + /* Save buffer state */ + buf_i = msg->chn->buf->i; + list_for_each_entry(filter, &s->strm_flt.filters, list) { + if (filter->ops->http_data && !flt_want_forward_data(filter, msg->chn)) { + ret = filter->ops->http_data(s, filter, msg); + if (ret < 0) + break; + } + else { + /* msg->chunk_len is the remaining size of data to parse + * in the body (or in the current chunk for + * chunk-encoded messages) from the HTTP parser point of + * view (relatively to msg->next). To have it from the + * filter point of view, we need to be add (msg->next + * -FLT_NEXT) to it. */ + ret = MIN(msg->chunk_len + msg->next, msg->chn->buf->i) - FLT_NXT(filter, msg->chn); + } + + /* Increase FLT_NXT offset of the current filter */ + FLT_NXT(filter, msg->chn) += ret; + + /* And set this value as the bound for the next filter. It will + * not able to parse more data than the current one. */ + msg->chn->buf->i = FLT_NXT(filter, msg->chn); + } + /* Restore the original buffer state */ + msg->chn->buf->i = buf_i; + end: + return ret; +} + +int +flt_http_end_chunk(struct stream *s, struct http_msg *msg) +{ + int ret = 1; + + if (LIST_ISEMPTY(&s->strm_flt.filters)) + goto end; + + RESUME_FILTER_LOOP(s, msg->chn) { + if (filter->ops->http_end_chunk) { + ret = filter->ops->http_end_chunk(s, filter, msg); + if (ret <= 0) + BREAK_EXECUTION(s, msg->chn, end); + } + flt_reset_forward_data(filter, msg->chn); + FLT_NXT(filter, msg->chn) += msg->sol; + } RESUME_FILTER_END; + end: + return ret; +} + +int +flt_http_last_chunk(struct stream *s, struct http_msg *msg) +{ + int ret = 1; + + if (LIST_ISEMPTY(&s->strm_flt.filters)) + goto end; + + RESUME_FILTER_LOOP(s, msg->chn) { + if (filter->ops->http_last_chunk) { + ret = filter->ops->http_last_chunk(s, filter, msg); + if (ret <= 0) + BREAK_EXECUTION(s, msg->chn, end); + } + flt_reset_forward_data(filter, msg->chn); + FLT_NXT(filter, msg->chn) += msg->sol; + } RESUME_FILTER_END; + end: + return ret; +} + + +/* + * Calls 'http_chunk_trailers' callback for all "data" filters attached to a + * stream. This function is called for chunked messages only when a part of the + * trailers was parsed in the AN_REQ_HTTP_XFER_BODY and AN_RES_HTTP_XFER_BODY + * analyzers. Filters can know how much data were parsed by the HTTP parsing + * until the last call with the msg->sol value. Returns a negative value if an + * error occurs, any other value otherwise. + */ +int +flt_http_chunk_trailers(struct stream *s, struct http_msg *msg) +{ + int ret = 1; + + if (LIST_ISEMPTY(&s->strm_flt.filters)) + goto end; + + RESUME_FILTER_LOOP(s, msg->chn) { + if (filter->ops->http_chunk_trailers) { + ret = filter->ops->http_chunk_trailers(s, filter, msg); + if (ret <= 0) + BREAK_EXECUTION(s, msg->chn, end); + } + FLT_NXT(filter, msg->chn) += msg->sol; + } RESUME_FILTER_END; +end: + return ret; +} + +/* + * Calls 'http_end' callback for all filters attached to a stream. All filters + * are called here, but only if there is at least one "data" filter. This + * functions is called when all data were parsed and forwarded. 'http_end' + * callback is resumable, so this function returns a negative value if an error + * occurs, 0 if it needs to wait for some reason, any other value otherwise. + */ +int +flt_http_end(struct stream *s, struct http_msg *msg) +{ + int ret = 1; + + if (LIST_ISEMPTY(&s->strm_flt.filters)) + goto end; + + RESUME_FILTER_LOOP(s, msg->chn) { + if (filter->ops->http_end) { + ret = filter->ops->http_end(s, filter, msg); + if (ret <= 0) + BREAK_EXECUTION(s, msg->chn, end); + } + flt_reset_forward_data(filter, msg->chn); + } RESUME_FILTER_END; +end: + return ret; +} + +/* + * Calls 'http_reset' callback for all filters attached to a stream. This + * happens when a 100-continue response is received. + */ +void +flt_http_reset(struct stream *s, struct http_msg *msg) +{ + struct filter *filter; + + if (LIST_ISEMPTY(&s->strm_flt.filters)) + return; + + list_for_each_entry(filter, &s->strm_flt.filters, list) { + if (filter->ops->http_reset) + filter->ops->http_reset(s, filter, msg); + } +} + +/* + * Calls 'http_reply' callback for all filters attached to a stream when HA + * decides to stop the HTTP message processing. + */ +void +flt_http_reply(struct stream *s, short status, const struct chunk *msg) +{ + struct filter *filter; + + if (LIST_ISEMPTY(&s->strm_flt.filters)) + return; + + list_for_each_entry(filter, &s->strm_flt.filters, list) { + if (filter->ops->http_reply) + filter->ops->http_reply(s, filter, status, msg); + } +} + +/* + * Calls 'http_forward_data' callback for all "data" filters attached to a + * stream. This function is called when some data can be forwarded in the + * AN_REQ_HTTP_XFER_BODY and AN_RES_HTTP_XFER_BODY analyzers. It takes care to + * update the forward offset of filters and adjusts "forwardable" data to be + * sure that a filter cannot forward more data than its predecessors. A filter + * can choose to not forward all parsed data. Returns a negative value if an + * error occurs, else the number of forwarded bytes. + */ +int +flt_http_forward_data(struct stream *s, struct http_msg *msg, unsigned int len) +{ + struct filter *filter = NULL; + int ret = len; + + /* No filter, forward all data */ + if (LIST_ISEMPTY(&s->strm_flt.filters)) + goto end; + + list_for_each_entry(filter, &s->strm_flt.filters, list) { + if (filter->ops->http_forward_data) { + /* Remove bytes that the current filter considered as + * forwarded */ + ret = filter->ops->http_forward_data(s, filter, msg, + ret - FLT_FWD(filter, msg->chn)); + if (ret < 0) + goto end; + } + + /* Adjust bytes that the current filter considers as + * forwarded */ + FLT_FWD(filter, msg->chn) += ret; + + /* And set this value as the bound for the next filter. It will + * not able to forward more data than the current one. */ + ret = FLT_FWD(filter, msg->chn); + } + + if (!ret) + goto end; + + /* Finally, adjust filters offsets by removing data that HAProxy will + * forward. */ + list_for_each_entry(filter, &s->strm_flt.filters, list) { + FLT_NXT(filter, msg->chn) -= ret; + FLT_FWD(filter, msg->chn) -= ret; + } + end: + return ret; +} + +/* + * Calls 'channel_start_analyze' callback for all filters attached to a + * stream. This function is called when we start to analyze a request or a + * response. For frontend filters, it is called before all other analyzers. For + * backend ones, it is called before all backend + * analyzers. 'channel_start_analyze' callback is resumable, so this function + * returns 0 if an error occurs or if it needs to wait, any other value + * otherwise. + */ +int +flt_start_analyze(struct stream *s, struct channel *chn, unsigned int an_bit) +{ + int ret = 1; + + /* If this function is called, this means there is at least one filter, + * so we do not need to check the filter list's emptiness. */ + + RESUME_FILTER_LOOP(s, chn) { + if (an_bit == AN_FLT_START_BE && !filter->is_backend_filter) + continue; + + filter->next[CHN_IDX(chn)] = 0; + filter->fwd[CHN_IDX(chn)] = 0; + + if (filter->ops->channel_start_analyze) { + ret = filter->ops->channel_start_analyze(s, filter, chn); + if (ret <= 0) + BREAK_EXECUTION(s, chn, end); + } + } RESUME_FILTER_END; + + end: + return handle_analyzer_result(s, chn, an_bit, ret); +} + +/* + * Calls 'channel_analyze' callback for all filters attached to a stream. This + * function is called before each analyzer attached to a channel, expects + * analyzers responsible for data sending. 'channel_analyze' callback is + * resumable, so this function returns 0 if an error occurs or if it needs to + * wait, any other value otherwise. + */ +int +flt_analyze(struct stream *s, struct channel *chn, unsigned int an_bit) +{ + int ret = 1; + + if (LIST_ISEMPTY(&s->strm_flt.filters)) + goto end; + + RESUME_FILTER_LOOP(s, chn) { + if (filter->ops->channel_analyze) { + ret = filter->ops->channel_analyze(s, filter, chn, an_bit); + if (ret <= 0) + BREAK_EXECUTION(s, chn, check_result); + } + } RESUME_FILTER_END; + + check_result: + ret = handle_analyzer_result(s, chn, 0, ret); + end: + return ret; +} + +/* + * Calls 'channel_end_analyze' callback for all filters attached to a + * stream. This function is called when we stop to analyze a request or a + * response. It is called after all other analyzers. 'channel_end_analyze' + * callback is resumable, so this function returns 0 if an error occurs or if it + * needs to wait, any other value otherwise. + */ +int +flt_end_analyze(struct stream *s, struct channel *chn, unsigned int an_bit) +{ + int ret = 1; + + /* If this function is called, this means there is at least one filter, + * so we do not need to check the filter list's emptiness. */ + + RESUME_FILTER_LOOP(s, chn) { + filter->next[CHN_IDX(chn)] = 0; + + if (filter->ops->channel_end_analyze) { + ret = filter->ops->channel_end_analyze(s, filter, chn); + if (ret <= 0) + BREAK_EXECUTION(s, chn, end); + } + } RESUME_FILTER_END; + +end: + ret = handle_analyzer_result(s, chn, an_bit, ret); + if (!(s->req.analysers & AN_FLT_END) && + !(s->res.analysers & AN_FLT_END) && + s->txn && (s->txn->flags & TX_WAIT_NEXT_RQ)) { + struct filter *filter, *back; + + s->req.analysers = strm_li(s) ? strm_li(s)->analysers : 0; + s->res.analysers = 0; + + list_for_each_entry_safe(filter, back, &s->strm_flt.filters, list) { + if (filter->is_backend_filter) { + LIST_DEL(&filter->list); + pool_free2(pool2_filter, filter); + } + } + } + else if (ret) { + /* Analyzer ends only for one channel. So wake up the stream to + * be sure to process it for the other side as soon as + * possible. */ + task_wakeup(s->task, TASK_WOKEN_MSG); + } + return ret; +} + + +/* + * Calls 'tcp_data' callback for all "data" filters attached to a stream. This + * function is called when incoming data are available. It takes care to update + * the next offset of filters and adjusts available data to be sure that a + * filter cannot parse more data than its predecessors. A filter can choose to + * not consume all available data. Returns -1 if an error occurs, the number of + * consumed bytes otherwise. + */ +static int +flt_data(struct stream *s, struct channel *chn) +{ + struct filter *filter = NULL; + unsigned int buf_i; + int ret = chn->buf->i; + + /* Save buffer state */ + buf_i = chn->buf->i; + list_for_each_entry(filter, &s->strm_flt.filters, list) { + if (filter->ops->tcp_data && !flt_want_forward_data(filter, chn)) { + ret = filter->ops->tcp_data(s, filter, chn); + if (ret < 0) + break; + } + else + ret = chn->buf->i - FLT_NXT(filter, chn); + + /* Increase next offset of the current filter */ + FLT_NXT(filter, chn) += ret; + + /* Update value to be sure to have the last one when we + * exit from the loop. */ + ret = FLT_NXT(filter, chn); + + /* And set this value as the bound for the next filter. It will + * not able to parse more data than the current one. */ + chn->buf->i = FLT_NXT(filter, chn); + } + // Restore the original buffer state + chn->buf->i = buf_i; + return ret; +} + +/* + * Calls 'tcp_forward_data' callback for all "data" filters attached to a + * stream. This function is called when some data can be forwarded. It takes + * care to update the forward offset of filters and adjusts "forwardable" data + * to be sure that a filter cannot forward more data than its predecessors. A + * filter can choose to not forward all parsed data. Returns a negative value if + * an error occurs, else the number of forwarded bytes. + */ +static int +flt_forward_data(struct stream *s, struct channel *chn, unsigned int len) +{ + struct filter *filter = NULL; + int ret = len; + + list_for_each_entry(filter, &s->strm_flt.filters, list) { + if (filter->ops->tcp_forward_data) { + /* Remove bytes that the current filter considered as + * forwarded */ + ret = filter->ops->tcp_forward_data(s, filter, chn, ret - FLT_FWD(filter, chn)); + if (ret < 0) + goto end; + } + + /* Adjust bytes taht the current filter considers as + * forwarded */ + FLT_FWD(filter, chn) += ret; + + /* And set this value as the bound for the next filter. It will + * not able to forward more data than the current one. */ + ret = FLT_FWD(filter, chn); + } + + if (!ret) + goto end; + + /* Adjust forward counter and next offset of filters by removing data + * that HAProxy will consider as forwarded. */ + list_for_each_entry(filter, &s->strm_flt.filters, list) { + FLT_NXT(filter, chn) -= ret; + FLT_FWD(filter, chn) -= ret; + } + + /* Consume data that all filters consider as forwarded. */ + b_adv(chn->buf, ret); + end: + return ret; +} + +/* + * Called when TCP data must be filtered on a channel. This function is the + * AN_FLT_XFER_DATA analyzer. When called, it is responsible to forward data + * when the proxy is not in http mode. Behind the scene, it calls consecutively + * 'tcp_data' and 'tcp_forward_data' callbacks for all "data" filters attached + * to a stream. Returns 0 if an error occurs or if it needs to wait, any other + * value otherwise. + */ +int +flt_xfer_data(struct stream *s, struct channel *chn, unsigned int an_bit) +{ + int ret = 1; + + /* If this function is called, this means there is at least one filter, + * so we do not need to check the filter list's emptiness. */ + + /* Be sure that the output is still opened. Else we stop the data + * filtering. */ + if ((chn->flags & (CF_READ_ERROR|CF_READ_TIMEOUT|CF_WRITE_ERROR|CF_WRITE_TIMEOUT)) || + ((chn->flags & CF_SHUTW) && (chn->to_forward || chn->buf->o))) + goto end; + + /* Let all "data" filters parsing incoming data */ + ret = flt_data(s, chn); + if (ret < 0) + goto end; + + /* And forward them */ + ret = flt_forward_data(s, chn, ret); + if (ret < 0) + goto end; + + /* Stop waiting data if the input in closed and no data is pending or if + * the output is closed. */ + if ((chn->flags & CF_SHUTW) || + ((chn->flags & CF_SHUTR) && !buffer_pending(chn->buf))) { + ret = 1; + goto end; + } + + /* Wait for data */ + return 0; + end: + /* Terminate the data filtering. If is negative, an error was + * encountered during the filtering. */ + return handle_analyzer_result(s, chn, an_bit, ret); +} + +/* + * Handles result of filter's analyzers. It returns 0 if an error occurs or if + * it needs to wait, any other value otherwise. + */ +static int +handle_analyzer_result(struct stream *s, struct channel *chn, + unsigned int an_bit, int ret) +{ + int finst; + + if (ret < 0) + goto return_bad_req; + else if (!ret) + goto wait; + + /* End of job, return OK */ + if (an_bit) { + chn->analysers &= ~an_bit; + chn->analyse_exp = TICK_ETERNITY; + } + return 1; + + return_bad_req: + /* An error occurs */ + channel_abort(&s->req); + channel_abort(&s->res); + + if (!(chn->flags & CF_ISRESP)) { + s->req.analysers &= AN_FLT_END; + finst = SF_FINST_R; + /* FIXME: incr counters */ + } + else { + s->res.analysers &= AN_FLT_END; + finst = SF_FINST_H; + /* FIXME: incr counters */ + } + + if (s->txn) { + /* Do not do that when we are waiting for the next request */ + if (s->txn->status) + http_reply_and_close(s, s->txn->status, NULL); + else { + s->txn->status = 400; + http_reply_and_close(s, 400, http_error_message(s, HTTP_ERR_400)); + } + } + + if (!(s->flags & SF_ERR_MASK)) + s->flags |= SF_ERR_PRXCOND; + if (!(s->flags & SF_FINST_MASK)) + s->flags |= finst; + return 0; + + wait: + if (!(chn->flags & CF_ISRESP)) + channel_dont_connect(chn); + return 0; +} + + +/* Note: must not be declared as its list will be overwritten. + * Please take care of keeping this list alphabetically sorted, doing so helps + * all code contributors. + * Optional keywords are also declared with a NULL ->parse() function so that + * the config parser can report an appropriate error when a known keyword was + * not enabled. */ +static struct cfg_kw_list cfg_kws = {ILH, { + { CFG_LISTEN, "filter", parse_filter }, + { 0, NULL, NULL }, + } +}; + +__attribute__((constructor)) +static void +__filters_init(void) +{ + pool2_filter = create_pool("filter", sizeof(struct filter), MEM_F_SHARED); + cfg_register_keywords(&cfg_kws); +} + +__attribute__((destructor)) +static void +__filters_deinit(void) +{ + pool_destroy2(pool2_filter); +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/haproxy.c b/src/haproxy.c index f7b4cedf6..8ffdb677b 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -76,6 +76,7 @@ #include #include +#include #include #include #include @@ -89,6 +90,7 @@ #include #include #include +#include #include #include #include @@ -560,6 +562,7 @@ void init(int argc, char **argv) char *progname; char *change_dir = NULL; struct tm curtime; + struct proxy *px; chunk_init(&trash, malloc(global.tune.bufsize), global.tune.bufsize); alloc_trash_buffers(global.tune.bufsize); @@ -861,6 +864,15 @@ void init(int argc, char **argv) init_51degrees(); #endif + for (px = proxy; px; px = px->next) { + err_code |= flt_init(px); + if (err_code & (ERR_ABORT|ERR_FATAL)) { + Alert("Failed to initialize filters for proxy '%s'.\n", + px->id); + exit(1); + } + } + if (start_checks() < 0) exit(1); @@ -1468,6 +1480,8 @@ void deinit(void) free(bind_conf); } + flt_deinit(p); + free(p->desc); free(p->fwdfor_hdr_name); @@ -1550,7 +1564,6 @@ void deinit(void) pool_destroy2(pool2_sig_handlers); pool_destroy2(pool2_hdr_idx); pool_destroy2(pool2_http_txn); - deinit_pollers(); } /* end deinit() */ diff --git a/src/proto_http.c b/src/proto_http.c index 247c3b612..79b21f902 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -39,6 +39,7 @@ #include #include +#include #include #include @@ -51,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -933,6 +935,7 @@ int http_remove_header2(struct http_msg *msg, struct hdr_idx *idx, struct hdr_ct static void http_server_error(struct stream *s, struct stream_interface *si, int err, int finst, int status, const struct chunk *msg) { + flt_http_reply(s, status, msg); channel_auto_read(si_oc(si)); channel_abort(si_oc(si)); channel_auto_close(si_oc(si)); @@ -966,6 +969,8 @@ struct chunk *http_error_message(struct stream *s, int msgnum) void http_reply_and_close(struct stream *s, short status, struct chunk *msg) { + s->txn->flags &= ~TX_WAIT_NEXT_RQ; + flt_http_reply(s, status, msg); stream_int_retnclose(&s->si[0], msg); } @@ -2180,10 +2185,8 @@ static inline int http_parse_chunk_size(struct http_msg *msg) msg->sol = ptr - ptr_old; if (unlikely(ptr < ptr_old)) msg->sol += buf->size; - msg->next = buffer_count(buf, buf->p, ptr); msg->chunk_len = chunk; msg->body_len += chunk; - msg->msg_state = chunk ? HTTP_MSG_DATA : HTTP_MSG_TRAILERS; return 1; error: msg->err_pos = buffer_count(buf, buf->p, ptr); @@ -2211,7 +2214,7 @@ static int http_forward_trailers(struct http_msg *msg) /* we have msg->next which points to next line. Look for CRLF. */ while (1) { const char *p1 = NULL, *p2 = NULL; - const char *ptr = b_ptr(buf, msg->next); + const char *ptr = b_ptr(buf, msg->next + msg->sol); const char *stop = bi_end(buf); int bytes; @@ -2245,21 +2248,20 @@ static int http_forward_trailers(struct http_msg *msg) if (p2 >= buf->data + buf->size) p2 = buf->data; - bytes = p2 - b_ptr(buf, msg->next); + bytes = p2 - b_ptr(buf, msg->next + msg->sol); if (bytes < 0) bytes += buf->size; - if (p1 == b_ptr(buf, msg->next)) { - /* LF/CRLF at beginning of line => end of trailers at p2. - * Everything was scheduled for forwarding, there's nothing - * left from this message. - */ - msg->next = buffer_count(buf, buf->p, p2); - msg->msg_state = HTTP_MSG_DONE; + /* LF/CRLF at beginning of line => end of trailers at p2. + * Everything was scheduled for forwarding, there's nothing left + * from this message. + */ + if (p1 == b_ptr(buf, msg->next + msg->sol)) { + msg->sol += bytes; return 1; } + msg->sol += bytes; /* OK, next line then */ - msg->next = buffer_count(buf, buf->p, p2); } } @@ -2299,13 +2301,7 @@ static inline int http_skip_chunk_crlf(struct http_msg *msg) msg->err_pos = buffer_count(buf, buf->p, ptr); return -1; } - - ptr++; - if (unlikely(ptr >= buf->data + buf->size)) - ptr = buf->data; - /* Advance ->next to allow the CRLF to be forwarded */ - msg->next += bytes; - msg->msg_state = HTTP_MSG_CHUNK_SIZE; + msg->sol = bytes; return 1; } @@ -2475,11 +2471,12 @@ int select_compression_response_header(struct stream *s, struct buffer *res) /* compress 200,201,202,203 responses only */ if ((txn->status != 200) && - (txn->status != 201) && - (txn->status != 202) && - (txn->status != 203)) + (txn->status != 201) && + (txn->status != 202) && + (txn->status != 203)) goto fail; + /* Content-Length is null */ if (!(msg->flags & HTTP_MSGF_TE_CHNK) && msg->body_len == 0) goto fail; @@ -2812,8 +2809,7 @@ int http_wait_for_request(struct stream *s, struct channel *req, int an_bit) txn->status = 400; msg->msg_state = HTTP_MSG_ERROR; http_reply_and_close(s, txn->status, NULL); - req->analysers = 0; - + req->analysers &= AN_FLT_END; stream_inc_http_req_ctr(s); proxy_inc_fe_req_ctr(sess->fe); sess->fe->fe_counters.failed_req++; @@ -2844,7 +2840,7 @@ int http_wait_for_request(struct stream *s, struct channel *req, int an_bit) txn->status = 408; msg->msg_state = HTTP_MSG_ERROR; http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_408)); - req->analysers = 0; + req->analysers &= AN_FLT_END; stream_inc_http_req_ctr(s); proxy_inc_fe_req_ctr(sess->fe); @@ -2873,8 +2869,7 @@ int http_wait_for_request(struct stream *s, struct channel *req, int an_bit) txn->status = 400; msg->msg_state = HTTP_MSG_ERROR; http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_400)); - req->analysers = 0; - + req->analysers &= AN_FLT_END; stream_inc_http_err_ctr(s); stream_inc_http_req_ctr(s); proxy_inc_fe_req_ctr(sess->fe); @@ -2930,7 +2925,7 @@ int http_wait_for_request(struct stream *s, struct channel *req, int an_bit) */ txn->status = 0; msg->msg_state = HTTP_MSG_RQBEFORE; - req->analysers = 0; + req->analysers &= AN_FLT_END; s->logs.logwait = 0; s->logs.level = 0; s->res.flags &= ~CF_EXPECT_MORE; /* speed up sending a previous response */ @@ -3257,7 +3252,7 @@ int http_wait_for_request(struct stream *s, struct channel *req, int an_bit) if (!(s->flags & SF_FINST_MASK)) s->flags |= SF_FINST_R; - req->analysers = 0; + req->analysers &= AN_FLT_END; req->analyse_exp = TICK_ETERNITY; return 0; } @@ -4260,13 +4255,14 @@ static int http_apply_redirect_rule(struct redirect_rule *rule, struct stream *s } memcpy(trash.str + trash.len, "\r\n\r\n", 4); trash.len += 4; + flt_http_reply(s, txn->status, &trash); bo_inject(res->chn, trash.str, trash.len); /* "eat" the request */ bi_fast_delete(req->chn->buf, req->sov); req->next -= req->sov; req->sov = 0; - s->req.analysers = AN_REQ_HTTP_XFER_BODY; - s->res.analysers = AN_RES_HTTP_XFER_BODY; + s->req.analysers = AN_REQ_HTTP_XFER_BODY | (s->req.analysers & AN_FLT_END); + s->res.analysers = AN_RES_HTTP_XFER_BODY | (s->req.analysers & AN_FLT_END); req->msg_state = HTTP_MSG_CLOSED; res->msg_state = HTTP_MSG_DONE; /* Trim any possible response */ @@ -4282,7 +4278,7 @@ static int http_apply_redirect_rule(struct redirect_rule *rule, struct stream *s trash.len += 23; } http_reply_and_close(s, txn->status, &trash); - req->chn->analysers = 0; + req->chn->analysers &= AN_FLT_END; } if (!(s->flags & SF_ERR_MASK)) @@ -4428,7 +4424,9 @@ int http_process_req_common(struct stream *s, struct channel *req, int an_bit, s select_compression_request_header(s, req->buf); /* enable the minimally required analyzers to handle keep-alive and compression on the HTTP response */ - req->analysers = (req->analysers & AN_REQ_HTTP_BODY) | AN_REQ_HTTP_XFER_BODY; + req->analysers &= (AN_REQ_HTTP_BODY | AN_FLT_END); + req->analysers &= ~AN_FLT_XFER_DATA; + req->analysers |= AN_REQ_HTTP_XFER_BODY; goto done; } @@ -4478,7 +4476,7 @@ int http_process_req_common(struct stream *s, struct channel *req, int an_bit, s * if the client closes first. */ channel_dont_connect(req); - req->analysers = 0; /* remove switching rules etc... */ + req->analysers &= AN_FLT_END; /* remove switching rules etc... */ req->analysers |= AN_REQ_HTTP_TARPIT; req->analyse_exp = tick_add_ifset(now_ms, s->be->timeout.tarpit); if (!req->analyse_exp) @@ -4527,7 +4525,7 @@ int http_process_req_common(struct stream *s, struct channel *req, int an_bit, s if (!(s->flags & SF_FINST_MASK)) s->flags |= SF_FINST_R; - req->analysers = 0; + req->analysers &= AN_FLT_END; req->analyse_exp = TICK_ETERNITY; return 0; @@ -4586,7 +4584,7 @@ int http_process_request(struct stream *s, struct channel *req, int an_bit) if (unlikely((conn = si_alloc_conn(&s->si[1])) == NULL)) { txn->req.msg_state = HTTP_MSG_ERROR; txn->status = 500; - req->analysers = 0; + req->analysers &= AN_FLT_END; http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_500)); if (!(s->flags & SF_ERR_MASK)) @@ -4819,6 +4817,7 @@ int http_process_request(struct stream *s, struct channel *req, int an_bit) } if (msg->flags & HTTP_MSGF_XFER_LEN) { + req->analysers &= ~AN_FLT_XFER_DATA; req->analysers |= AN_REQ_HTTP_XFER_BODY; #ifdef TCP_QUICKACK /* We expect some data from the client. Unless we know for sure @@ -4861,7 +4860,7 @@ int http_process_request(struct stream *s, struct channel *req, int an_bit) txn->req.msg_state = HTTP_MSG_ERROR; txn->status = 400; - req->analysers = 0; + req->analysers &= AN_FLT_END; http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_400)); sess->fe->fe_counters.failed_req++; @@ -4905,7 +4904,7 @@ int http_process_tarpit(struct stream *s, struct channel *req, int an_bit) if (!(req->flags & CF_READ_ERROR)) http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_500)); - req->analysers = 0; + req->analysers &= AN_FLT_END; req->analyse_exp = TICK_ETERNITY; if (!(s->flags & SF_ERR_MASK)) @@ -4994,6 +4993,9 @@ int http_wait_for_request_body(struct stream *s, struct channel *req, int an_bit stream_inc_http_err_ctr(s); goto return_bad_req; } + msg->next += msg->sol; + msg->sol = 0; + msg->msg_state = msg->chunk_len ? HTTP_MSG_DATA : HTTP_MSG_TRAILERS; } /* Now we're in HTTP_MSG_DATA or HTTP_MSG_TRAILERS state. @@ -5057,7 +5059,7 @@ int http_wait_for_request_body(struct stream *s, struct channel *req, int an_bit s->flags |= SF_FINST_R; return_err_msg: - req->analysers = 0; + req->analysers &= AN_FLT_END; sess->fe->fe_counters.failed_req++; if (sess->listener->counters) sess->listener->counters->failed_req++; @@ -5086,7 +5088,7 @@ int http_send_name_header(struct http_txn *txn, struct proxy* be, const char* sr /* The request was already skipped, let's restore it */ b_rew(chn->buf, old_o); txn->req.next += old_o; - txn->req.sov += old_o; + txn->req.sov += old_o; } old_i = chn->buf->i; @@ -5308,8 +5310,14 @@ void http_end_txn_clean_session(struct stream *s) si_idle_conn(&s->si[1], &srv->idle_conns); } - s->req.analysers = strm_li(s) ? strm_li(s)->analysers : 0; - s->res.analysers = 0; + if (LIST_ISEMPTY(&s->strm_flt.filters)) { + s->req.analysers = strm_li(s) ? strm_li(s)->analysers : 0; + s->res.analysers = 0; + } + else { + s->req.analysers &= AN_FLT_END; + s->res.analysers &= AN_FLT_END; + } } @@ -5622,10 +5630,10 @@ int http_resync_states(struct stream *s) txn->rsp.msg_state == HTTP_MSG_TUNNEL || (txn->req.msg_state == HTTP_MSG_CLOSED && txn->rsp.msg_state == HTTP_MSG_CLOSED)) { - s->req.analysers = 0; + s->req.analysers &= AN_FLT_END; channel_auto_close(&s->req); channel_auto_read(&s->req); - s->res.analysers = 0; + s->res.analysers &= AN_FLT_END; channel_auto_close(&s->res); channel_auto_read(&s->res); } @@ -5633,10 +5641,10 @@ int http_resync_states(struct stream *s) (txn->rsp.msg_state == HTTP_MSG_CLOSED || (s->res.flags & CF_SHUTW))) || txn->rsp.msg_state == HTTP_MSG_ERROR || txn->req.msg_state == HTTP_MSG_ERROR) { - s->res.analysers = 0; + s->res.analysers &= AN_FLT_END; channel_auto_close(&s->res); channel_auto_read(&s->res); - s->req.analysers = 0; + s->req.analysers &= AN_FLT_END; channel_abort(&s->req); channel_auto_close(&s->req); channel_auto_read(&s->req); @@ -5673,6 +5681,7 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) struct session *sess = s->sess; struct http_txn *txn = s->txn; struct http_msg *msg = &s->txn->req; + int ret, ret2; if (unlikely(msg->msg_state < HTTP_MSG_BODY)) return 0; @@ -5693,15 +5702,24 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) * an "Expect: 100-continue" header. */ - if (msg->sov > 0) { + if (msg->msg_state == HTTP_MSG_BODY) { /* we have msg->sov which points to the first byte of message * body, and req->buf.p still points to the beginning of the * message. We forward the headers now, as we don't need them * anymore, and we want to flush them. */ - b_adv(req->buf, msg->sov); - msg->next -= msg->sov; - msg->sov = 0; + ret = flt_http_headers(s, msg); + if (ret < 0) + goto return_bad_req; + if (!ret) + return 0; + + ret = flt_http_forward_data(s, msg, msg->sov); + if (ret < 0) + goto return_bad_req; + b_adv(req->buf, ret); + msg->next -= ret; + msg->sov -= ret; /* The previous analysers guarantee that the state is somewhere * between MSG_BODY and the first MSG_DATA. So msg->sol and @@ -5713,6 +5731,12 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) else msg->msg_state = HTTP_MSG_DATA; } + + /* TODO/filters: when http-buffer-request option is set or if a + * rule on url_param exists, the first chunk size could be + * already parsed. In that case, msg->next is after the chunk + * size (including the CRLF after the size). So this case should + * be handled to */ } /* Some post-connect processing might want us to refrain from starting to @@ -5741,81 +5765,134 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) if (msg->msg_state == HTTP_MSG_DATA) { /* must still forward */ /* we may have some pending data starting at req->buf->p */ - if (msg->chunk_len > req->buf->i - msg->next) { - req->flags |= CF_WAKE_WRITE; + ret = flt_http_data(s, msg); + if (ret < 0) + goto aborted_xfer; + msg->next += ret; + msg->chunk_len -= ret; + + if (msg->chunk_len) { + /* input empty or output full */ + if (req->buf->i > msg->next) + req->flags |= CF_WAKE_WRITE; goto missing_data; } - msg->next += msg->chunk_len; - msg->chunk_len = 0; /* nothing left to forward */ if (msg->flags & HTTP_MSGF_TE_CHNK) msg->msg_state = HTTP_MSG_CHUNK_CRLF; else - msg->msg_state = HTTP_MSG_DONE; + msg->msg_state = HTTP_MSG_ENDING; } else if (msg->msg_state == HTTP_MSG_CHUNK_SIZE) { /* read the chunk size and assign it to ->chunk_len, then * set ->next to point to the body and switch to DATA or * TRAILERS state. */ - int ret = http_parse_chunk_size(msg); - - if (ret == 0) - goto missing_data; - else if (ret < 0) { - stream_inc_http_err_ctr(s); - if (msg->err_pos >= 0) - http_capture_bad_message(&sess->fe->invalid_req, s, msg, HTTP_MSG_CHUNK_SIZE, s->be); - goto return_bad_req; + if (!msg->sol) { + ret = http_parse_chunk_size(msg); + if (ret == 0) + goto missing_data; + else if (ret < 0) { + stream_inc_http_err_ctr(s); + if (msg->err_pos >= 0) + http_capture_bad_message(&sess->fe->invalid_req, s, msg, HTTP_MSG_CHUNK_SIZE, s->be); + goto return_bad_req; + } } + ret = (msg->chunk_len + ? flt_http_start_chunk(s, msg) + : flt_http_last_chunk(s, msg)); + if (ret < 0) + goto return_bad_req; + if (!ret) + goto missing_data; + msg->next += msg->sol; + msg->sol = 0; + msg->msg_state = msg->chunk_len ? HTTP_MSG_DATA : HTTP_MSG_TRAILERS; /* otherwise we're in HTTP_MSG_DATA or HTTP_MSG_TRAILERS state */ } else if (msg->msg_state == HTTP_MSG_CHUNK_CRLF) { /* we want the CRLF after the data */ - int ret = http_skip_chunk_crlf(msg); - - if (ret == 0) - goto missing_data; - else if (ret < 0) { - stream_inc_http_err_ctr(s); - if (msg->err_pos >= 0) - http_capture_bad_message(&sess->fe->invalid_req, s, msg, HTTP_MSG_CHUNK_CRLF, s->be); - goto return_bad_req; + if (!msg->sol) { + ret = http_skip_chunk_crlf(msg); + if (ret == 0) + goto missing_data; + else if (ret < 0) { + stream_inc_http_err_ctr(s); + if (msg->err_pos >= 0) + http_capture_bad_message(&sess->fe->invalid_req, s, msg, HTTP_MSG_CHUNK_CRLF, s->be); + goto return_bad_req; + } } + ret = flt_http_end_chunk(s, msg); + if (ret < 0) + goto return_bad_req; + if (!ret) + goto missing_data; + msg->next += msg->sol; + msg->sol = 0; + msg->msg_state = HTTP_MSG_CHUNK_SIZE; /* we're in MSG_CHUNK_SIZE now */ } else if (msg->msg_state == HTTP_MSG_TRAILERS) { - int ret = http_forward_trailers(msg); - - if (ret == 0) - goto missing_data; - else if (ret < 0) { - stream_inc_http_err_ctr(s); - if (msg->err_pos >= 0) - http_capture_bad_message(&sess->fe->invalid_req, s, msg, HTTP_MSG_TRAILERS, s->be); - goto return_bad_req; + ret = 1; + if (!msg->sol) { + ret = http_forward_trailers(msg); + if (ret < 0) { + stream_inc_http_err_ctr(s); + if (msg->err_pos >= 0) + http_capture_bad_message(&sess->fe->invalid_req, s, msg, HTTP_MSG_TRAILERS, s->be); + goto return_bad_req; + } } - /* we're in HTTP_MSG_DONE now */ + ret2 = flt_http_chunk_trailers(s, msg); + if (ret2 < 0) + goto return_bad_req; + if (!ret2) + goto missing_data; + msg->next += msg->sol; + msg->sol = 0; + if (!ret) + goto missing_data; + msg->msg_state = HTTP_MSG_ENDING; } - else { - int old_state = msg->msg_state; - - /* other states, DONE...TUNNEL */ - - /* we may have some pending data starting at req->buf->p - * such as last chunk of data or trailers. - */ - b_adv(req->buf, msg->next); - if (unlikely(!(s->req.flags & CF_WROTE_DATA))) - msg->sov -= msg->next; - msg->next = 0; - + else if (msg->msg_state == HTTP_MSG_ENDING) { /* we don't want to forward closes on DONE except in * tunnel mode. */ if ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) channel_dont_close(req); + /* we may have some pending data starting at req->buf->p + * such as last chunk of data or trailers. + */ + ret = flt_http_forward_data(s, msg, msg->next); + if (ret < 0) + goto return_bad_req; + b_adv(req->buf, ret); + msg->next -= ret; + if (unlikely(!(s->req.flags & CF_WROTE_DATA) || msg->sov > 0)) + msg->sov -= ret; + + if (msg->next) + goto skip_resync_states; + + ret = flt_http_end(s, msg); + if (ret < 0) + goto return_bad_req; + if (!ret) + goto skip_resync_states; + msg->msg_state = HTTP_MSG_DONE; + } + else { + /* other states, DONE...TUNNEL */ + /* we don't want to forward closes on DONE except in + * tunnel mode. + */ + if ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) + channel_dont_close(req); + + ret = msg->msg_state; if (http_resync_states(s)) { /* some state changes occurred, maybe the analyser * was disabled too. @@ -5828,12 +5905,13 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) goto aborted_xfer; } if (msg->err_pos >= 0) - http_capture_bad_message(&sess->fe->invalid_req, s, msg, old_state, s->be); + http_capture_bad_message(&sess->fe->invalid_req, s, msg, ret, s->be); goto return_bad_req; } return 1; } + skip_resync_states: /* If "option abortonclose" is set on the backend, we * want to monitor the client's connection and forward * any shutdown notification to the server, which will @@ -5864,12 +5942,15 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) missing_data: /* we may have some pending data starting at req->buf->p */ - b_adv(req->buf, msg->next); - if (unlikely(!(s->req.flags & CF_WROTE_DATA))) - msg->sov -= msg->next + MIN(msg->chunk_len, req->buf->i); - - msg->next = 0; - msg->chunk_len -= channel_forward(req, msg->chunk_len); + ret = flt_http_forward_data(s, msg, msg->next); + if (ret < 0) + goto return_bad_req; + b_adv(req->buf, ret); + msg->next -= ret; + if (unlikely(!(s->req.flags & CF_WROTE_DATA) || msg->sov > 0)) + msg->sov -= ret; + if (LIST_ISEMPTY(&s->strm_flt.filters)) + msg->chunk_len -= channel_forward(req, msg->chunk_len); /* stop waiting for data if the input is closed before the end */ if (req->flags & CF_SHUTR) { @@ -5919,10 +6000,6 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) sess->listener->counters->failed_req++; return_bad_req_stats_ok: - /* we may have some pending data starting at req->buf->p */ - b_adv(req->buf, msg->next); - msg->next = 0; - txn->req.msg_state = HTTP_MSG_ERROR; if (txn->status) { /* Note: we don't send any error if some data were already sent */ @@ -5931,8 +6008,8 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) txn->status = 400; http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_400)); } - req->analysers = 0; - s->res.analysers = 0; /* we're in data phase, we want to abort both directions */ + req->analysers &= AN_FLT_END; + s->res.analysers &= AN_FLT_END; /* we're in data phase, we want to abort both directions */ if (!(s->flags & SF_ERR_MASK)) s->flags |= SF_ERR_PRXCOND; @@ -5953,8 +6030,8 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) txn->status = 502; http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_502)); } - req->analysers = 0; - s->res.analysers = 0; /* we're in data phase, we want to abort both directions */ + req->analysers &= AN_FLT_END; + s->res.analysers &= AN_FLT_END; /* we're in data phase, we want to abort both directions */ sess->fe->fe_counters.srv_aborts++; s->be->be_counters.srv_aborts++; @@ -6090,7 +6167,7 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit) } abort_response: channel_auto_close(rep); - rep->analysers = 0; + rep->analysers &= AN_FLT_END; txn->status = 502; s->si[1].flags |= SI_FL_NOLINGER; channel_truncate(rep); @@ -6125,7 +6202,7 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit) } channel_auto_close(rep); - rep->analysers = 0; + rep->analysers &= AN_FLT_END; txn->status = 502; s->si[1].flags |= SI_FL_NOLINGER; channel_truncate(rep); @@ -6150,7 +6227,7 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit) } channel_auto_close(rep); - rep->analysers = 0; + rep->analysers &= AN_FLT_END; txn->status = 504; s->si[1].flags |= SI_FL_NOLINGER; channel_truncate(rep); @@ -6170,7 +6247,7 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit) if (objt_server(s->target)) objt_server(s->target)->counters.cli_aborts++; - rep->analysers = 0; + rep->analysers &= AN_FLT_END; channel_auto_close(rep); txn->status = 400; @@ -6200,7 +6277,7 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit) } channel_auto_close(rep); - rep->analysers = 0; + rep->analysers &= AN_FLT_END; txn->status = 502; s->si[1].flags |= SI_FL_NOLINGER; channel_truncate(rep); @@ -6221,7 +6298,7 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit) goto abort_keep_alive; s->be->be_counters.failed_resp++; - rep->analysers = 0; + rep->analysers &= AN_FLT_END; channel_auto_close(rep); if (!(s->flags & SF_ERR_MASK)) @@ -6324,6 +6401,7 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit) msg->msg_state = HTTP_MSG_RPBEFORE; txn->status = 0; s->logs.t_data = -1; /* was not a response yet */ + flt_http_reset(s, msg); goto next_one; case 200: @@ -6579,8 +6657,8 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit) * any other information so that the client retries. */ txn->status = 0; - rep->analysers = 0; - s->req.analysers = 0; + rep->analysers &= AN_FLT_END; + s->req.analysers &= AN_FLT_END; channel_auto_close(rep); s->logs.logwait = 0; s->logs.level = 0; @@ -6680,7 +6758,7 @@ int http_process_res_common(struct stream *s, struct channel *rep, int an_bit, s } s->be->be_counters.failed_resp++; return_srv_prx_502: - rep->analysers = 0; + rep->analysers &= AN_FLT_END; txn->status = 502; s->logs.t_data = -1; /* was not a valid response */ s->si[1].flags |= SI_FL_NOLINGER; @@ -6899,8 +6977,11 @@ int http_process_res_common(struct stream *s, struct channel *rep, int an_bit, s skip_header_mangling: if ((msg->flags & HTTP_MSGF_XFER_LEN) || - (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_TUN) + !LIST_ISEMPTY(&s->strm_flt.filters) || + (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_TUN) { + rep->analysers &= ~AN_FLT_XFER_DATA; rep->analysers |= AN_RES_HTTP_XFER_BODY; + } /* if the user wants to log as soon as possible, without counting * bytes from the server, then this is the right moment. We have @@ -6949,16 +7030,14 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit struct session *sess = s->sess; struct http_txn *txn = s->txn; struct http_msg *msg = &s->txn->rsp; - static struct buffer *tmpbuf = &buf_empty; - int compressing = 0; - int ret; + int ret, ret2; if (unlikely(msg->msg_state < HTTP_MSG_BODY)) return 0; if ((res->flags & (CF_READ_ERROR|CF_READ_TIMEOUT|CF_WRITE_ERROR|CF_WRITE_TIMEOUT)) || ((res->flags & CF_SHUTW) && (res->to_forward || res->buf->o)) || - !s->req.analysers) { + !s->req.analysers) { /* Output closed while we were sending data. We must abort and * wake the other side up. */ @@ -6970,15 +7049,24 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit /* in most states, we should abort in case of early close */ channel_auto_close(res); - if (msg->sov > 0) { + if (msg->msg_state == HTTP_MSG_BODY) { /* we have msg->sov which points to the first byte of message * body, and res->buf.p still points to the beginning of the * message. We forward the headers now, as we don't need them * anymore, and we want to flush them. */ - b_adv(res->buf, msg->sov); - msg->next -= msg->sov; - msg->sov = 0; + ret = flt_http_headers(s, msg); + if (ret < 0) + goto return_bad_res; + if (!ret) + return 0; + + ret = flt_http_forward_data(s, msg, msg->sov); + if (ret < 0) + goto return_bad_res; + b_adv(res->buf, ret); + msg->next -= ret; + msg->sov -= ret; /* The previous analysers guarantee that the state is somewhere * between MSG_BODY and the first MSG_DATA. So msg->sol and @@ -6998,71 +7086,62 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit goto missing_data; } - if (unlikely(s->comp_algo != NULL) && msg->msg_state < HTTP_MSG_TRAILERS) { - /* We need a compression buffer in the DATA state to put the - * output of compressed data, and in CRLF state to let the - * TRAILERS state finish the job of removing the trailing CRLF. - */ - if (unlikely(!tmpbuf->size)) { - /* this is the first time we need the compression buffer */ - if (b_alloc(&tmpbuf) == NULL) - goto aborted_xfer; /* no memory */ - } - - ret = http_compression_buffer_init(s, res->buf, tmpbuf); - if (ret < 0) { - res->flags |= CF_WAKE_WRITE; - goto missing_data; /* not enough spaces in buffers */ - } - compressing = 1; - } - while (1) { switch (msg->msg_state - HTTP_MSG_DATA) { case HTTP_MSG_DATA - HTTP_MSG_DATA: /* must still forward */ /* we may have some pending data starting at res->buf->p */ - if (unlikely(s->comp_algo)) { - ret = http_compression_buffer_add_data(s, res->buf, tmpbuf); - if (ret < 0) - goto aborted_xfer; - if (msg->chunk_len) { - /* input empty or output full */ - if (res->buf->i > msg->next) - res->flags |= CF_WAKE_WRITE; - goto missing_data; - } - } - else { - if (msg->chunk_len > res->buf->i - msg->next) { - /* output full */ + /* Neither content-length, nor transfer-encoding was + * found, so we must read the body until the server + * connection is closed. In that case, we eat data as + * they come. */ + if (!(msg->flags & HTTP_MSGF_XFER_LEN)) + msg->chunk_len = (res->buf->i - msg->next); + ret = flt_http_data(s, msg); + if (ret < 0) + goto aborted_xfer; + msg->next += ret; + msg->chunk_len -= ret; + if (msg->chunk_len) { + /* input empty or output full */ + if (res->buf->i > msg->next) res->flags |= CF_WAKE_WRITE; - goto missing_data; - } - msg->next += msg->chunk_len; - msg->chunk_len = 0; + goto missing_data; } /* nothing left to forward */ if (msg->flags & HTTP_MSGF_TE_CHNK) { msg->msg_state = HTTP_MSG_CHUNK_CRLF; + } else if (!(msg->flags & HTTP_MSGF_XFER_LEN) && + !(res->flags & CF_SHUTR)) { + /* The server still sending data */ + goto missing_data; } else { - msg->msg_state = HTTP_MSG_DONE; + msg->msg_state = HTTP_MSG_ENDING; break; } /* fall through for HTTP_MSG_CHUNK_CRLF */ case HTTP_MSG_CHUNK_CRLF - HTTP_MSG_DATA: /* we want the CRLF after the data */ - - ret = http_skip_chunk_crlf(msg); - if (ret == 0) - goto missing_data; - else if (ret < 0) { - if (msg->err_pos >= 0) - http_capture_bad_message(&s->be->invalid_rep, s, msg, HTTP_MSG_CHUNK_CRLF, sess->fe); - goto return_bad_res; + if (!msg->sol) { + ret = http_skip_chunk_crlf(msg); + if (ret == 0) + goto missing_data; + else if (ret < 0) { + if (msg->err_pos >= 0) + http_capture_bad_message(&s->be->invalid_rep, s, msg, HTTP_MSG_CHUNK_CRLF, sess->fe); + goto return_bad_res; + } } + ret = flt_http_end_chunk(s, msg); + if (ret < 0) + goto return_bad_res; + if (!ret) + goto missing_data; + msg->next += msg->sol; + msg->sol = 0; + msg->msg_state = HTTP_MSG_CHUNK_SIZE; /* we're in MSG_CHUNK_SIZE now, fall through */ case HTTP_MSG_CHUNK_SIZE - HTTP_MSG_DATA: @@ -7070,55 +7149,88 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit * set ->next to point to the body and switch to DATA or * TRAILERS state. */ - - ret = http_parse_chunk_size(msg); - if (ret == 0) - goto missing_data; - else if (ret < 0) { - if (msg->err_pos >= 0) - http_capture_bad_message(&s->be->invalid_rep, s, msg, HTTP_MSG_CHUNK_SIZE, sess->fe); - goto return_bad_res; + if (!msg->sol) { + ret = http_parse_chunk_size(msg); + if (ret == 0) + goto missing_data; + else if (ret < 0) { + if (msg->err_pos >= 0) + http_capture_bad_message(&s->be->invalid_rep, s, msg, HTTP_MSG_CHUNK_SIZE, sess->fe); + goto return_bad_res; + } } + ret = (msg->chunk_len + ? flt_http_start_chunk(s, msg) + : flt_http_last_chunk(s, msg)); + if (ret < 0) + goto return_bad_res; + if (!ret) + goto missing_data; + msg->next += msg->sol; + msg->sol = 0; + msg->msg_state = msg->chunk_len ? HTTP_MSG_DATA : HTTP_MSG_TRAILERS; /* otherwise we're in HTTP_MSG_DATA or HTTP_MSG_TRAILERS state */ break; case HTTP_MSG_TRAILERS - HTTP_MSG_DATA: - if (unlikely(compressing)) { - /* we need to flush output contents before syncing FSMs */ - http_compression_buffer_end(s, &res->buf, &tmpbuf, 1); - compressing = 0; + ret = 1; + if (!msg->sol) { + ret = http_forward_trailers(msg); + if (ret < 0) { + if (msg->err_pos >= 0) + http_capture_bad_message(&s->be->invalid_rep, s, msg, HTTP_MSG_TRAILERS, sess->fe); + goto return_bad_res; + } } - - ret = http_forward_trailers(msg); - if (ret == 0) - goto missing_data; - else if (ret < 0) { - if (msg->err_pos >= 0) - http_capture_bad_message(&s->be->invalid_rep, s, msg, HTTP_MSG_TRAILERS, sess->fe); + ret2 = flt_http_chunk_trailers(s, msg); + if (ret2 < 0) goto return_bad_res; - } - /* we're in HTTP_MSG_DONE now, fall through */ + if (!ret2) + goto missing_data; + msg->next += msg->sol; + msg->sol = 0; + if (!ret) + goto missing_data; + msg->msg_state = HTTP_MSG_ENDING; + /* fall through */ - default: - /* other states, DONE...TUNNEL */ - if (unlikely(compressing)) { - /* we need to flush output contents before syncing FSMs */ - http_compression_buffer_end(s, &res->buf, &tmpbuf, 1); - compressing = 0; - } + case HTTP_MSG_ENDING - HTTP_MSG_DATA: + /* for keep-alive we don't want to forward closes on ENDING */ + if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL || + (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) + channel_dont_close(res); /* we may have some pending data starting at res->buf->p * such as a last chunk of data or trailers. */ - b_adv(res->buf, msg->next); - msg->next = 0; + ret = flt_http_forward_data(s, msg, msg->next); + if (ret < 0) + goto return_bad_res; + b_adv(res->buf, ret); + msg->next -= ret; + if (msg->sov > 0) + msg->sov -= ret; + + if (msg->next) + goto skip_resync_states; + + ret = flt_http_end(s, msg); + if (ret < 0) + goto return_bad_res; + if (!ret) + goto skip_resync_states; + msg->msg_state = HTTP_MSG_DONE; + /* fall through */ + + default: + /* other states, DONE...TUNNEL */ - ret = msg->msg_state; /* for keep-alive we don't want to forward closes on DONE */ if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL || (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) channel_dont_close(res); + ret = msg->msg_state; if (http_resync_states(s)) { /* some state changes occurred, maybe the analyser * was disabled too. @@ -7136,22 +7248,25 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit } return 1; } + + skip_resync_states: return 0; } } missing_data: /* we may have some pending data starting at res->buf->p */ - if (unlikely(compressing)) { - http_compression_buffer_end(s, &res->buf, &tmpbuf, msg->msg_state >= HTTP_MSG_TRAILERS); - compressing = 0; - } + ret = flt_http_forward_data(s, msg, msg->next); + if (ret < 0) + goto return_bad_res; + b_adv(res->buf, ret); + msg->next -= ret; + if (msg->sov > 0) + msg->sov -= ret; - if ((s->comp_algo == NULL || msg->msg_state >= HTTP_MSG_TRAILERS)) { - b_adv(res->buf, msg->next); - msg->next = 0; + if ((s->comp_algo == NULL || msg->msg_state >= HTTP_MSG_TRAILERS) && + LIST_ISEMPTY(&s->strm_flt.filters)) msg->chunk_len -= channel_forward(res, msg->chunk_len); - } if (res->flags & CF_SHUTW) goto aborted_xfer; @@ -7184,7 +7299,7 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit * Similarly, with keep-alive on the client side, we don't want to forward a * close. */ - if ((msg->flags & HTTP_MSGF_TE_CHNK) || s->comp_algo || + if ((msg->flags & HTTP_MSGF_TE_CHNK) || s->comp_algo || !msg->body_len || (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL || (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) channel_dont_close(res); @@ -7209,22 +7324,11 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit objt_server(s->target)->counters.failed_resp++; return_bad_res_stats_ok: - if (unlikely(compressing)) { - http_compression_buffer_end(s, &res->buf, &tmpbuf, msg->msg_state >= HTTP_MSG_TRAILERS); - compressing = 0; - } - - /* we may have some pending data starting at res->buf->p */ - if (s->comp_algo == NULL) { - b_adv(res->buf, msg->next); - msg->next = 0; - } - txn->rsp.msg_state = HTTP_MSG_ERROR; /* don't send any error message as we're in the body */ http_reply_and_close(s, txn->status, NULL); - res->analysers = 0; - s->req.analysers = 0; /* we're in data phase, we want to abort both directions */ + res->analysers &= AN_FLT_END; + s->req.analysers &= AN_FLT_END; /* we're in data phase, we want to abort both directions */ if (objt_server(s->target)) health_adjust(objt_server(s->target), HANA_STATUS_HTTP_HDRRSP); @@ -7235,16 +7339,11 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit return 0; aborted_xfer: - if (unlikely(compressing)) { - http_compression_buffer_end(s, &res->buf, &tmpbuf, msg->msg_state >= HTTP_MSG_TRAILERS); - compressing = 0; - } - txn->rsp.msg_state = HTTP_MSG_ERROR; /* don't send any error message as we're in the body */ http_reply_and_close(s, txn->status, NULL); - res->analysers = 0; - s->req.analysers = 0; /* we're in data phase, we want to abort both directions */ + res->analysers &= AN_FLT_END; + s->req.analysers &= AN_FLT_END; /* we're in data phase, we want to abort both directions */ sess->fe->fe_counters.cli_aborts++; s->be->be_counters.cli_aborts++; diff --git a/src/proxy.c b/src/proxy.c index 3939beefc..2014c739e 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -747,6 +748,7 @@ void init_new_proxy(struct proxy *p) LIST_INIT(&p->conf.listeners); LIST_INIT(&p->conf.args.list); LIST_INIT(&p->tcpcheck_rules); + LIST_INIT(&p->filters); /* Timeouts are defined as -1 */ proxy_reset_timeouts(p); @@ -1128,6 +1130,8 @@ void resume_proxies(void) */ int stream_set_backend(struct stream *s, struct proxy *be) { + struct filter *filter; + if (s->flags & SF_BE_ASSIGNED) return 1; s->be = be; @@ -1136,6 +1140,20 @@ int stream_set_backend(struct stream *s, struct proxy *be) be->be_counters.conn_max = be->beconn; proxy_inc_be_ctr(be); + if (strm_fe(s) != be) { + list_for_each_entry(filter, &be->filters, list) { + struct filter *f = pool_alloc2(pool2_filter); + if (!f) + return 0; /* not enough memory */ + memset(f, 0, sizeof(*f)); + f->id = filter->id; + f->ops = filter->ops; + f->conf = filter->conf; + f->is_backend_filter = 1; + LIST_ADDQ(&s->strm_flt.filters, &f->list); + } + } + /* assign new parameters to the stream from the new backend */ s->si[1].flags &= ~SI_FL_INDEP_STR; if (be->options2 & PR_O2_INDEPSTR) @@ -1146,9 +1164,7 @@ int stream_set_backend(struct stream *s, struct proxy *be) * be more reliable to store the list of analysers that have been run, * but what we do here is OK for now. */ - s->req.analysers |= be->be_req_ana; - if (strm_li(s)) - s->req.analysers &= ~strm_li(s)->analysers; + s->req.analysers |= be->be_req_ana & (strm_li(s) ? ~strm_li(s)->analysers : 0); /* If the target backend requires HTTP processing, we have to allocate * the HTTP transaction and hdr_idx if we did not have one. diff --git a/src/session.c b/src/session.c index 44fccbf1d..fdb24045b 100644 --- a/src/session.c +++ b/src/session.c @@ -266,8 +266,9 @@ int session_accept_fd(struct listener *l, int cfd, struct sockaddr_storage *addr if (!strm) goto out_free_task; - strm->target = sess->listener->default_target; - strm->req.analysers = sess->listener->analysers; + strm->target = sess->listener->default_target; + strm->req.analysers |= sess->listener->analysers; + return 1; out_free_task: @@ -431,8 +432,8 @@ static int conn_complete_session(struct connection *conn) if (!strm) goto fail; - strm->target = sess->listener->default_target; - strm->req.analysers = sess->listener->analysers; + strm->target = sess->listener->default_target; + strm->req.analysers |= sess->listener->analysers; conn->flags &= ~CO_FL_INIT_DATA; return 0; diff --git a/src/stream.c b/src/stream.c index fe48be428..a98ecb06d 100644 --- a/src/stream.c +++ b/src/stream.c @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +76,7 @@ static struct list service_keywords = LIST_HEAD_INIT(service_keywords); struct stream *stream_new(struct session *sess, struct task *t, enum obj_type *origin) { struct stream *s; + struct filter *filter, *back; struct connection *conn = objt_conn(origin); struct appctx *appctx = objt_appctx(origin); @@ -214,6 +217,21 @@ struct stream *stream_new(struct session *sess, struct task *t, enum obj_type *o HLUA_INIT(&s->hlua); + LIST_INIT(&s->strm_flt.filters); + memset(s->strm_flt.current, 0, sizeof(s->strm_flt.current)); + list_for_each_entry(filter, &sess->fe->filters, list) { + struct filter *f = pool_alloc2(pool2_filter); + if (!f) + goto out_fail_accept; + memset(f, 0, sizeof(*f)); + f->id = filter->id; + f->ops = filter->ops; + f->conf = filter->conf; + LIST_ADDQ(&s->strm_flt.filters, &f->list); + } + if (flt_stream_start(s) < 0) + goto out_fail_accept; + /* finish initialization of the accepted file descriptor */ if (conn) conn_data_want_recv(conn); @@ -232,6 +250,10 @@ struct stream *stream_new(struct session *sess, struct task *t, enum obj_type *o /* Error unrolling */ out_fail_accept: + list_for_each_entry_safe(filter, back, &s->strm_flt.filters, list) { + LIST_DEL(&filter->list); + pool_free2(pool2_filter, filter); + } LIST_DEL(&s->list); pool_free2(pool2_stream, s); return NULL; @@ -246,6 +268,7 @@ static void stream_free(struct stream *s) struct proxy *fe = sess->fe; struct bref *bref, *back; struct connection *cli_conn = objt_conn(sess->origin); + struct filter *filter, *fback; int i; if (s->pend_pos) @@ -306,6 +329,12 @@ static void stream_free(struct stream *s) s->txn = NULL; } + flt_stream_stop(s); + list_for_each_entry_safe(filter, fback, &s->strm_flt.filters, list) { + LIST_DEL(&filter->list); + pool_free2(pool2_filter, filter); + } + if (fe) { pool_free2(fe->rsp_cap_pool, s->res_cap); pool_free2(fe->req_cap_pool, s->req_cap); @@ -1166,6 +1195,7 @@ static int process_switching_rules(struct stream *s, struct channel *req, int an if (fe == s->be) { s->req.analysers &= ~AN_REQ_INSPECT_BE; s->req.analysers &= ~AN_REQ_HTTP_PROCESS_BE; + s->req.analysers &= ~AN_FLT_START_BE; } /* as soon as we know the backend, we must check if we have a matching forced or ignored @@ -1206,7 +1236,7 @@ static int process_switching_rules(struct stream *s, struct channel *req, int an if (s->txn) s->txn->status = 500; - s->req.analysers = 0; + s->req.analysers &= AN_FLT_END; s->req.analyse_exp = TICK_ETERNITY; return 0; } @@ -1749,84 +1779,131 @@ struct task *process_stream(struct task *t) ana_list = ana_back = req->analysers; while (ana_list && max_loops--) { /* Warning! ensure that analysers are always placed in ascending order! */ + if (ana_list & AN_FLT_START_FE) { + if (!flt_start_analyze(s, req, AN_FLT_START_FE)) + break; + UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_FLT_START_FE); + } if (ana_list & AN_REQ_INSPECT_FE) { + if (!flt_analyze(s, req, AN_REQ_INSPECT_FE)) + break; if (!tcp_inspect_request(s, req, AN_REQ_INSPECT_FE)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_INSPECT_FE); } if (ana_list & AN_REQ_WAIT_HTTP) { + if (!flt_analyze(s, req, AN_REQ_WAIT_HTTP)) + break; if (!http_wait_for_request(s, req, AN_REQ_WAIT_HTTP)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_WAIT_HTTP); } if (ana_list & AN_REQ_HTTP_BODY) { + if (!flt_analyze(s, req, AN_REQ_HTTP_BODY)) + break; if (!http_wait_for_request_body(s, req, AN_REQ_HTTP_BODY)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_HTTP_BODY); } if (ana_list & AN_REQ_HTTP_PROCESS_FE) { + if (!flt_analyze(s, req, AN_REQ_HTTP_PROCESS_FE)) + break; if (!http_process_req_common(s, req, AN_REQ_HTTP_PROCESS_FE, sess->fe)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_HTTP_PROCESS_FE); } if (ana_list & AN_REQ_SWITCHING_RULES) { + if (!flt_analyze(s, req, AN_REQ_SWITCHING_RULES)) + break; if (!process_switching_rules(s, req, AN_REQ_SWITCHING_RULES)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_SWITCHING_RULES); } if (ana_list & AN_REQ_INSPECT_BE) { + if (!flt_analyze(s, req, AN_REQ_INSPECT_BE)) + break; if (!tcp_inspect_request(s, req, AN_REQ_INSPECT_BE)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_INSPECT_BE); } if (ana_list & AN_REQ_HTTP_PROCESS_BE) { + if (!flt_analyze(s, req, AN_REQ_HTTP_PROCESS_BE)) + break; if (!http_process_req_common(s, req, AN_REQ_HTTP_PROCESS_BE, s->be)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_HTTP_PROCESS_BE); } if (ana_list & AN_REQ_HTTP_TARPIT) { + if (!flt_analyze(s, req, AN_REQ_HTTP_TARPIT)) + break; if (!http_process_tarpit(s, req, AN_REQ_HTTP_TARPIT)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_HTTP_TARPIT); } if (ana_list & AN_REQ_SRV_RULES) { + if (!flt_analyze(s, req, AN_REQ_SRV_RULES)) + break; if (!process_server_rules(s, req, AN_REQ_SRV_RULES)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_SRV_RULES); } if (ana_list & AN_REQ_HTTP_INNER) { + if (!flt_analyze(s, req, AN_REQ_HTTP_INNER)) + break; if (!http_process_request(s, req, AN_REQ_HTTP_INNER)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_HTTP_INNER); } if (ana_list & AN_REQ_PRST_RDP_COOKIE) { + if (!flt_analyze(s, req, AN_REQ_PRST_RDP_COOKIE)) + break; if (!tcp_persist_rdp_cookie(s, req, AN_REQ_PRST_RDP_COOKIE)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_PRST_RDP_COOKIE); } if (ana_list & AN_REQ_STICKING_RULES) { + if (!flt_analyze(s, req, AN_REQ_STICKING_RULES)) + break; if (!process_sticking_rules(s, req, AN_REQ_STICKING_RULES)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_STICKING_RULES); } + if (ana_list & AN_FLT_START_BE) { + if (!flt_start_analyze(s, req, AN_FLT_START_BE)) + break; + UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_FLT_START_BE); + } + + if (ana_list & AN_FLT_XFER_DATA) { + if (!flt_xfer_data(s, req, AN_FLT_XFER_DATA)) + break; + UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_FLT_XFER_DATA); + } + if (ana_list & AN_REQ_HTTP_XFER_BODY) { if (!http_request_forward_body(s, req, AN_REQ_HTTP_XFER_BODY)) break; UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_REQ_HTTP_XFER_BODY); } + + if (ana_list & AN_FLT_END) { + if (!flt_end_analyze(s, req, AN_FLT_END)) + break; + UPDATE_ANALYSERS(req->analysers, ana_list, ana_back, AN_FLT_END); + } break; } } @@ -1896,36 +1973,67 @@ struct task *process_stream(struct task *t) ana_list = ana_back = res->analysers; while (ana_list && max_loops--) { /* Warning! ensure that analysers are always placed in ascending order! */ + if (ana_list & AN_FLT_START_FE) { + if (!flt_start_analyze(s, res, AN_FLT_START_FE)) + break; + UPDATE_ANALYSERS(res->analysers, ana_list, ana_back, AN_FLT_START_FE); + } if (ana_list & AN_RES_INSPECT) { + if (!flt_analyze(s, res, AN_RES_INSPECT)) + break; if (!tcp_inspect_response(s, res, AN_RES_INSPECT)) break; UPDATE_ANALYSERS(res->analysers, ana_list, ana_back, AN_RES_INSPECT); } if (ana_list & AN_RES_WAIT_HTTP) { + if (!flt_analyze(s, res, AN_RES_WAIT_HTTP)) + break; if (!http_wait_for_response(s, res, AN_RES_WAIT_HTTP)) break; UPDATE_ANALYSERS(res->analysers, ana_list, ana_back, AN_RES_WAIT_HTTP); } if (ana_list & AN_RES_STORE_RULES) { + if (!flt_analyze(s, res, AN_RES_STORE_RULES)) + break; if (!process_store_rules(s, res, AN_RES_STORE_RULES)) break; UPDATE_ANALYSERS(res->analysers, ana_list, ana_back, AN_RES_STORE_RULES); } if (ana_list & AN_RES_HTTP_PROCESS_BE) { + if (!flt_analyze(s, res, AN_RES_HTTP_PROCESS_BE)) + break; if (!http_process_res_common(s, res, AN_RES_HTTP_PROCESS_BE, s->be)) break; UPDATE_ANALYSERS(res->analysers, ana_list, ana_back, AN_RES_HTTP_PROCESS_BE); } + if (ana_list & AN_FLT_START_BE) { + if (!flt_start_analyze(s, res, AN_FLT_START_BE)) + break; + UPDATE_ANALYSERS(res->analysers, ana_list, ana_back, AN_FLT_START_BE); + } + + if (ana_list & AN_FLT_XFER_DATA) { + if (!flt_xfer_data(s, res, AN_FLT_XFER_DATA)) + break; + UPDATE_ANALYSERS(res->analysers, ana_list, ana_back, AN_FLT_XFER_DATA); + } + if (ana_list & AN_RES_HTTP_XFER_BODY) { if (!http_response_forward_body(s, res, AN_RES_HTTP_XFER_BODY)) break; UPDATE_ANALYSERS(res->analysers, ana_list, ana_back, AN_RES_HTTP_XFER_BODY); } + + if (ana_list & AN_FLT_END) { + if (!flt_end_analyze(s, res, AN_FLT_END)) + break; + UPDATE_ANALYSERS(res->analysers, ana_list, ana_back, AN_FLT_END); + } break; } }