mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-05-16 22:58:02 +00:00
[MEDIUM] extract TCP request processing from HTTP
The TCP analyser has moved to proto_tcp.c. Breaking the function has required finer use of the return value and adding some tests to process_session().
This commit is contained in:
parent
b025325274
commit
edcf6687d6
@ -32,6 +32,7 @@ int tcpv4_bind_socket(int fd, int flags, struct sockaddr_in *local, struct socka
|
||||
void tcpv4_add_listener(struct listener *listener);
|
||||
void tcpv6_add_listener(struct listener *listener);
|
||||
int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen);
|
||||
int tcp_inspect_request(struct session *s, struct buffer *req);
|
||||
|
||||
#endif /* _PROTO_PROTO_TCP_H */
|
||||
|
||||
|
124
src/proto_http.c
124
src/proto_http.c
@ -1542,32 +1542,30 @@ void http_msg_analyzer(struct buffer *buf, struct http_msg *msg, struct hdr_idx
|
||||
}
|
||||
|
||||
/* This function performs all the processing enabled for the current request.
|
||||
* It normally returns zero, but may return 1 if it absolutely needs to be
|
||||
* called again after other functions. It relies on buffers flags, and updates
|
||||
* t->req->analysers. It might make sense to explode it into several other
|
||||
* functions. Its behaviour is rather simple :
|
||||
* It returns 1 if the processing can continue on next analysers, or zero if it
|
||||
* needs more data, encounters an error, or wants to immediately abort the
|
||||
* request. It relies on buffers flags, and updates s->req->analysers. Its
|
||||
* behaviour is rather simple:
|
||||
* - all enabled analysers are called in turn from the lower to the higher
|
||||
* bit.
|
||||
* - if an analyser does not have enough data, it must return without calling
|
||||
* other ones. It should also probably reset the BF_WRITE_ENA bit to ensure
|
||||
* - the analyser must check for errors and timeouts, and react as expected.
|
||||
* It does not have to close anything upon error, the caller will.
|
||||
* - if the analyser does not have enough data, it must return 0without calling
|
||||
* other ones. It should also probably do a buffer_write_dis() to ensure
|
||||
* that unprocessed data will not be forwarded. But that probably depends on
|
||||
* the protocol. Generally it is not reset in case of errors.
|
||||
* the protocol.
|
||||
* - if an analyser has enough data, it just has to pass on to the next
|
||||
* analyser without touching BF_WRITE_ENA (it is enabled prior to
|
||||
* analysis).
|
||||
* analyser without using buffer_write_dis() (enabled by default).
|
||||
* - if an analyser thinks it has no added value anymore staying here, it must
|
||||
* reset its bit from the analysers flags in order not to be called anymore.
|
||||
*
|
||||
* In the future, analysers should be able to indicate that they want to be
|
||||
* called after XXX bytes have been received (or transfered), and the min of
|
||||
* all's wishes will be used to ring back (unless a special condition occurs).
|
||||
*
|
||||
*
|
||||
*/
|
||||
int process_request(struct session *t)
|
||||
{
|
||||
struct buffer *req = t->req;
|
||||
struct buffer *rep = t->rep;
|
||||
|
||||
DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
|
||||
now_ms, __FUNCTION__,
|
||||
@ -1578,98 +1576,6 @@ int process_request(struct session *t)
|
||||
req->l,
|
||||
req->analysers);
|
||||
|
||||
/* The tcp-inspect analyser is always called alone */
|
||||
if (req->analysers & AN_REQ_INSPECT) {
|
||||
struct tcp_rule *rule;
|
||||
int partial;
|
||||
|
||||
/* We will abort if we encounter a read error. In theory, we
|
||||
* should not abort if we get a close, it might be valid,
|
||||
* although very unlikely. FIXME: we'll abort for now, this
|
||||
* will be easier to change later.
|
||||
*/
|
||||
if (req->flags & BF_READ_ERROR) {
|
||||
req->analysers = 0;
|
||||
//t->fe->failed_req++;
|
||||
if (!(t->flags & SN_ERR_MASK))
|
||||
t->flags |= SN_ERR_CLICL;
|
||||
if (!(t->flags & SN_FINST_MASK))
|
||||
t->flags |= SN_FINST_R;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Abort if client read timeout has expired */
|
||||
else if (req->flags & BF_READ_TIMEOUT) {
|
||||
req->analysers = 0;
|
||||
t->fe->failed_req++;
|
||||
if (!(t->flags & SN_ERR_MASK))
|
||||
t->flags |= SN_ERR_CLITO;
|
||||
if (!(t->flags & SN_FINST_MASK))
|
||||
t->flags |= SN_FINST_R;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We don't know whether we have enough data, so must proceed
|
||||
* this way :
|
||||
* - iterate through all rules in their declaration order
|
||||
* - if one rule returns MISS, it means the inspect delay is
|
||||
* not over yet, then return immediately, otherwise consider
|
||||
* it as a non-match.
|
||||
* - if one rule returns OK, then return OK
|
||||
* - if one rule returns KO, then return KO
|
||||
*/
|
||||
|
||||
if (req->flags & BF_SHUTR || tick_is_expired(req->analyse_exp, now_ms))
|
||||
partial = 0;
|
||||
else
|
||||
partial = ACL_PARTIAL;
|
||||
|
||||
list_for_each_entry(rule, &t->fe->tcp_req.inspect_rules, list) {
|
||||
int ret = ACL_PAT_PASS;
|
||||
|
||||
if (rule->cond) {
|
||||
ret = acl_exec_cond(rule->cond, t->fe, t, NULL, ACL_DIR_REQ | partial);
|
||||
if (ret == ACL_PAT_MISS) {
|
||||
buffer_write_dis(req);
|
||||
/* just set the request timeout once at the beginning of the request */
|
||||
if (!tick_isset(req->analyse_exp))
|
||||
req->analyse_exp = tick_add_ifset(now_ms, t->fe->tcp_req.inspect_delay);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = acl_pass(ret);
|
||||
if (rule->cond->pol == ACL_COND_UNLESS)
|
||||
ret = !ret;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
/* we have a matching rule. */
|
||||
if (rule->action == TCP_ACT_REJECT) {
|
||||
buffer_abort(req);
|
||||
buffer_abort(rep);
|
||||
//FIXME: this delete this
|
||||
//fd_delete(t->cli_fd);
|
||||
//t->cli_state = CL_STCLOSE;
|
||||
req->analysers = 0;
|
||||
t->fe->failed_req++;
|
||||
if (!(t->flags & SN_ERR_MASK))
|
||||
t->flags |= SN_ERR_PRXCOND;
|
||||
if (!(t->flags & SN_FINST_MASK))
|
||||
t->flags |= SN_FINST_R;
|
||||
return 0;
|
||||
}
|
||||
/* otherwise accept */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if we get there, it means we have no rule which matches, or
|
||||
* we have an explicit accept, so we apply the default accept.
|
||||
*/
|
||||
req->analysers &= ~AN_REQ_INSPECT;
|
||||
req->analyse_exp = TICK_ETERNITY;
|
||||
}
|
||||
|
||||
if (req->analysers & AN_REQ_HTTP_HDR) {
|
||||
/*
|
||||
* Now parse the partial (or complete) lines.
|
||||
@ -2141,8 +2047,10 @@ int process_request(struct session *t)
|
||||
* FIXME!!! that one is rather dangerous, we want to
|
||||
* make it follow standard rules (eg: clear req->analysers).
|
||||
*/
|
||||
if (stats_check_uri_auth(t, rule_set))
|
||||
return 1;
|
||||
if (stats_check_uri_auth(t, rule_set)) {
|
||||
req->analysers = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* now check whether we have some switching rules for this request */
|
||||
@ -2193,7 +2101,6 @@ int process_request(struct session *t)
|
||||
t->flags |= SN_BE_ASSIGNED;
|
||||
}
|
||||
} while (t->be != cur_proxy); /* we loop only if t->be has changed */
|
||||
|
||||
|
||||
if (!(t->flags & SN_BE_ASSIGNED)) {
|
||||
/* To ensure correct connection accounting on
|
||||
@ -2305,7 +2212,7 @@ int process_request(struct session *t)
|
||||
memcpy(trash, t->fe->fwdfor_hdr_name, len);
|
||||
}
|
||||
len += sprintf(trash + len, ": %s", pn);
|
||||
|
||||
|
||||
if (unlikely(http_header_add_tail2(req, &txn->req,
|
||||
&txn->hdr_idx, trash, len)) < 0)
|
||||
goto return_bad_req;
|
||||
@ -2574,7 +2481,6 @@ int process_request(struct session *t)
|
||||
ABORT_NOW();
|
||||
}
|
||||
#endif
|
||||
req->analysers &= AN_REQ_INSPECT | AN_REQ_HTTP_HDR | AN_REQ_HTTP_TARPIT | AN_REQ_HTTP_BODY;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
130
src/proto_tcp.c
130
src/proto_tcp.c
@ -203,7 +203,7 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
|
||||
msg = "cannot create listening socket";
|
||||
goto tcp_return;
|
||||
}
|
||||
|
||||
|
||||
if (fd >= global.maxsock) {
|
||||
err |= ERR_FATAL | ERR_ABORT | ERR_ALERT;
|
||||
msg = "not enough free sockets (raise '-n' parameter)";
|
||||
@ -226,7 +226,7 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
|
||||
|
||||
if (listener->options & LI_O_NOLINGER)
|
||||
setsockopt(fd, SOL_SOCKET, SO_LINGER, (struct linger *) &nolinger, sizeof(struct linger));
|
||||
|
||||
|
||||
#ifdef SO_REUSEPORT
|
||||
/* OpenBSD supports this. As it's present in old libc versions of Linux,
|
||||
* it might return an error that we will silently ignore.
|
||||
@ -234,7 +234,7 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
|
||||
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &one, sizeof(one));
|
||||
#endif
|
||||
#ifdef CONFIG_HAP_LINUX_TPROXY
|
||||
if ((listener->options & LI_O_FOREIGN)
|
||||
if ((listener->options & LI_O_FOREIGN)
|
||||
&& (setsockopt(fd, SOL_IP, IP_TRANSPARENT, (char *) &one, sizeof(one)) == -1)
|
||||
&& (setsockopt(fd, SOL_IP, IP_FREEBIND, (char *) &one, sizeof(one)) == -1)) {
|
||||
msg = "cannot make listening socket transparent";
|
||||
@ -246,13 +246,13 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
|
||||
msg = "cannot bind socket";
|
||||
goto tcp_close_return;
|
||||
}
|
||||
|
||||
|
||||
if (listen(fd, listener->backlog ? listener->backlog : listener->maxconn) == -1) {
|
||||
err |= ERR_RETRYABLE | ERR_ALERT;
|
||||
msg = "cannot listen to socket";
|
||||
goto tcp_close_return;
|
||||
}
|
||||
|
||||
|
||||
/* the socket is ready */
|
||||
listener->fd = fd;
|
||||
listener->state = LI_LISTEN;
|
||||
@ -325,6 +325,126 @@ void tcpv6_add_listener(struct listener *listener)
|
||||
proto_tcpv6.nb_listeners++;
|
||||
}
|
||||
|
||||
/* This function performs the TCP request analysis on the current request. It
|
||||
* returns 1 if the processing can continue on next analysers, or zero if it
|
||||
* needs more data, encounters an error, or wants to immediately abort the
|
||||
* request. It relies on buffers flags, and updates s->req->analysers. Its
|
||||
* behaviour is rather simple:
|
||||
* - the analyser must check for errors and timeouts, and react as expected.
|
||||
* It does not have to close anything upon error, the caller will.
|
||||
* - if the analyser does not have enough data, it must return 0without calling
|
||||
* other ones. It should also probably do a buffer_write_dis() to ensure
|
||||
* that unprocessed data will not be forwarded. But that probably depends on
|
||||
* the protocol.
|
||||
* - if an analyser has enough data, it just has to pass on to the next
|
||||
* analyser without using buffer_write_dis() (enabled by default).
|
||||
* - if an analyser thinks it has no added value anymore staying here, it must
|
||||
* reset its bit from the analysers flags in order not to be called anymore.
|
||||
*
|
||||
* In the future, analysers should be able to indicate that they want to be
|
||||
* called after XXX bytes have been received (or transfered), and the min of
|
||||
* all's wishes will be used to ring back (unless a special condition occurs).
|
||||
*/
|
||||
int tcp_inspect_request(struct session *s, struct buffer *req)
|
||||
{
|
||||
struct tcp_rule *rule;
|
||||
int partial;
|
||||
|
||||
DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
|
||||
now_ms, __FUNCTION__,
|
||||
s,
|
||||
req,
|
||||
req->rex, req->wex,
|
||||
req->flags,
|
||||
req->l,
|
||||
req->analysers);
|
||||
|
||||
/* We will abort if we encounter a read error. In theory, we
|
||||
* should not abort if we get a close, it might be valid,
|
||||
* although very unlikely. FIXME: we'll abort for now, this
|
||||
* will be easier to change later.
|
||||
*/
|
||||
if (req->flags & BF_READ_ERROR) {
|
||||
req->analysers = 0;
|
||||
s->fe->failed_req++;
|
||||
if (!(s->flags & SN_ERR_MASK))
|
||||
s->flags |= SN_ERR_CLICL;
|
||||
if (!(s->flags & SN_FINST_MASK))
|
||||
s->flags |= SN_FINST_R;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Abort if client read timeout has expired */
|
||||
else if (req->flags & BF_READ_TIMEOUT) {
|
||||
req->analysers = 0;
|
||||
s->fe->failed_req++;
|
||||
if (!(s->flags & SN_ERR_MASK))
|
||||
s->flags |= SN_ERR_CLITO;
|
||||
if (!(s->flags & SN_FINST_MASK))
|
||||
s->flags |= SN_FINST_R;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We don't know whether we have enough data, so must proceed
|
||||
* this way :
|
||||
* - iterate through all rules in their declaration order
|
||||
* - if one rule returns MISS, it means the inspect delay is
|
||||
* not over yet, then return immediately, otherwise consider
|
||||
* it as a non-match.
|
||||
* - if one rule returns OK, then return OK
|
||||
* - if one rule returns KO, then return KO
|
||||
*/
|
||||
|
||||
if (req->flags & BF_SHUTR || tick_is_expired(req->analyse_exp, now_ms))
|
||||
partial = 0;
|
||||
else
|
||||
partial = ACL_PARTIAL;
|
||||
|
||||
list_for_each_entry(rule, &s->fe->tcp_req.inspect_rules, list) {
|
||||
int ret = ACL_PAT_PASS;
|
||||
|
||||
if (rule->cond) {
|
||||
ret = acl_exec_cond(rule->cond, s->fe, s, NULL, ACL_DIR_REQ | partial);
|
||||
if (ret == ACL_PAT_MISS) {
|
||||
buffer_write_dis(req);
|
||||
/* just set the request timeout once at the beginning of the request */
|
||||
if (!tick_isset(req->analyse_exp))
|
||||
req->analyse_exp = tick_add_ifset(now_ms, s->fe->tcp_req.inspect_delay);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = acl_pass(ret);
|
||||
if (rule->cond->pol == ACL_COND_UNLESS)
|
||||
ret = !ret;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
/* we have a matching rule. */
|
||||
if (rule->action == TCP_ACT_REJECT) {
|
||||
buffer_abort(req);
|
||||
buffer_abort(s->rep);
|
||||
req->analysers = 0;
|
||||
s->fe->failed_req++;
|
||||
if (!(s->flags & SN_ERR_MASK))
|
||||
s->flags |= SN_ERR_PRXCOND;
|
||||
if (!(s->flags & SN_FINST_MASK))
|
||||
s->flags |= SN_FINST_R;
|
||||
return 0;
|
||||
}
|
||||
/* otherwise accept */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if we get there, it means we have no rule which matches, or
|
||||
* we have an explicit accept, so we apply the default accept.
|
||||
*/
|
||||
req->analysers &= ~AN_REQ_INSPECT;
|
||||
req->analyse_exp = TICK_ETERNITY;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* This function should be called to parse a line starting with the "tcp-request"
|
||||
* keyword.
|
||||
*/
|
||||
|
@ -693,8 +693,28 @@ resync_stream_interface:
|
||||
if (s->req->prod->state >= SI_ST_EST) {
|
||||
/* it's up to the analysers to reset write_ena */
|
||||
buffer_write_ena(s->req);
|
||||
if (s->req->analysers)
|
||||
process_request(s);
|
||||
|
||||
/* We will call all analysers for which a bit is set in
|
||||
* s->req->analysers, following the bit order from LSB
|
||||
* to MSB. The analysers must remove themselves from
|
||||
* the list when not needed. This while() loop is in
|
||||
* fact a cleaner if().
|
||||
*/
|
||||
while (s->req->analysers) {
|
||||
if (s->req->analysers & AN_REQ_INSPECT)
|
||||
if (!tcp_inspect_request(s, s->req))
|
||||
break;
|
||||
|
||||
if (s->req->analysers)
|
||||
if (!process_request(s))
|
||||
break;
|
||||
|
||||
/* Just make sure that nobody set a wrong flag causing an endless loop */
|
||||
s->req->analysers &= AN_REQ_INSPECT | AN_REQ_HTTP_HDR | AN_REQ_HTTP_TARPIT | AN_REQ_HTTP_BODY;
|
||||
|
||||
/* we don't want to loop anyway */
|
||||
break;
|
||||
}
|
||||
}
|
||||
s->req->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT;
|
||||
flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT;
|
||||
|
Loading…
Reference in New Issue
Block a user