]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] extract TCP request processing from HTTP
authorWilly Tarreau <w@1wt.eu>
Sun, 30 Nov 2008 22:15:34 +0000 (23:15 +0100)
committerWilly Tarreau <w@1wt.eu>
Sun, 30 Nov 2008 22:15:34 +0000 (23:15 +0100)
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().

include/proto/proto_tcp.h
src/proto_http.c
src/proto_tcp.c
src/session.c

index ee7586c61fea1653108639486e60214bc843a02f..109bc09acb699dd894a13ca35c86cf2be8ba3384 100644 (file)
@@ -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 */
 
index b52607675e3f89944462d1f3deaf38bc256bc8e9..a3750bd18d2105f22046b6b5c8776ac0868ffdf6 100644 (file)
@@ -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;
 }
 
index adc32d1e0430ee844fbc404a05c2143411d121fc..ce0c43ac666ea42508be2c443d216bf9b22cafed 100644 (file)
@@ -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.
  */
index 54b312b515a655dae9db4eb34841411e53c186c7..e13bca648f3b91f9fbece33ad73999ff8e6823f0 100644 (file)
@@ -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;