Hi List, our support department has developed  a patch for supporting HTTP
header "Expect: 100-continue" and the proper behaviour, we have been
testing in our production environment for a week and Pound is managing more
than 17 millions of daily connections without any penalty.

I wanted to mention that the management of this Header isn't supported in
another Privative Load Balancer solutions as Barracuda, the offered
solution is disabling the header "Expect: 100-continue", but some privative
web applications require this header in a mandatory way, so with this patch
the sysadmin can take the decission for enabling / disabling the management
of this header with a new directive called "Ignore100continue".

*root@localhost # man ./pound.8*

*       Ignore100continue 0|1*
*              Ignore Header Expect: 100-continue (default: 1, Ignored).
If 0 pound manages Expect: 100-continue headers.*


BTW, the patch has been applied in the following joe's source code:

https://github.com/goochjj/pound/tree/stage_for_upstream/v2.8a

It would be great if this patch could be added in the master branch for
future use.

Any feedback will be welcomed!

Regards!


2016-06-22 21:22 GMT+02:00 Emilio Campos <[email protected]>:

> Dear Joe thanks for your comments, I reply you in line.
>
>
> 2016-06-22 14:42 GMT+02:00 Joe Gooch <[email protected]>:
>
>> I don't know enough about expect continue to know what we should do.
>>
>> Based on this:
>>
>> https://support.urbanairship.com/entries/59909909--Expect-100-Continue-Issues-and-Risks
>>
>> It seems like something I wouldn't ever use.  Anytime I've run into it in
>> the past it has been to disable it.
>>
>
> Joe I totally agree with you, but there are customer's application that
> use it, propietary apps.
>
>
>>
>> Based on the above, if pound isn't going to support it, returning code
>> 417 Expectation failed is probably the correct thing to do, instead of
>> silently dropping the header. (Which would only be a valid course of action
>> for GET requests).
>>
>
> It would be great.. right now pound is responding a 500 Internal Server
> Error and client thinks the reason is an issue in the load balancer. I
> think 417 is a more descriptive error.
>
>
>
>> Is that what's happening, or do you have an incoming post request that
>> never completes?
>>
>
> No, the connection is closed, in my case the Real Server/Backend is
> waiting for a header "Expectation: 100-continue" that never receives and
>  in the middle of the content transfer from LB to backend and after a few
> seconds the Backend closes the active transfer with pound, automatically
> the load balancer logs an copy chunk error in syslog and forward to the
> client "500 internal server error".
>
> We are making some tests in Lab, I will update you with results.
>
> Regards!
>
>
>>
>>
>> Joe
>>
>> On Jun 22, 2016, at 4:23 AM, Emilio Campos <
>> [email protected]> wrote:
>>
>> Some advise?
>>
>> Any information will be appreciated
>>
>> 2016-06-19 19:54 GMT+02:00 Emilio Campos <[email protected]>
>> :
>>
>>> We are currrently working with pound 2.8a, Joe's version from github and
>>> everything works like a charm, but after a few days ago a customer
>>> requested us a issue with some transfers.
>>>
>>> After a review we detected that they use HTTP 1/1 and some large request
>>> are sent from the client to server chunked and with header Expect:
>>> 100-continue, pound deletes this header and it looks like after a few
>>> chunked sent to the backend, the backend sends a unexpected "FIN,ACK" to
>>> pound and it closes the communication unexpectedly.
>>>
>>> I have been reading the code and pound delete this header also I found
>>> this in http.c
>>>
>>> *            * we do NOT support the "Expect: 100-continue" headers*
>>> *           * support may involve severe performance penalties
>>> (non-responding back-end, etc)*
>>> *           * as a stop-gap measure we just skip these headers*
>>>
>>> Tinkering in google I found a patch for 2.6:
>>>
>>>
>>> *https://bitbucket.org/amplidata/pound/src/f2affa5f867a9718c9dc864e5f1c2e0eddd91b9c/debian/patches/expect_100_continued.patch?at=default&fileviewer=file-view-default
>>> <https://bitbucket.org/amplidata/pound/src/f2affa5f867a9718c9dc864e5f1c2e0eddd91b9c/debian/patches/expect_100_continued.patch?at=default&fileviewer=file-view-default>
>>> *
>>>
>>> I would like to know if this could be added in the master branch, we
>>> could make tests to support it.
>>>
>>> --
>>> Load balancer distribution - Open Source Project
>>> http://www.zenloadbalancer.com
>>> Distribution list (subscribe):
>>> [email protected]
>>>
>>
>>
>>
>> --
>> Load balancer distribution - Open Source Project
>> http://www.zenloadbalancer.com
>> Distribution list (subscribe):
>> [email protected]
>>
>>
>
>
> --
> Load balancer distribution - Open Source Project
> http://www.zenloadbalancer.com
> Distribution list (subscribe):
> [email protected]
>



-- 
Load balancer distribution - Open Source Project
http://www.zenloadbalancer.com
Distribution list (subscribe): [email protected]
--- /opt/pound28a-github/config.c	2015-07-10 22:31:15.000000000 +0200
+++ /opt/pound-2.8a-100cont-zenloadbalancer/config.c	2016-06-27 21:39:51.127468467 +0200
@@ -79,7 +79,7 @@
 static regex_t  Service, ServiceName, URL, OrURLs, HeadRequire, HeadDeny, BackEnd, Emergency, Priority, HAport, HAportAddr;
 static regex_t  Redirect, TimeOut, Session, Type, TTL, ID, DynScale;
 static regex_t  ClientCert, AddHeader, DisableProto, SSLAllowClientRenegotiation, SSLHonorCipherOrder, Ciphers;
-static regex_t  CAlist, VerifyList, CRLlist, NoHTTPS11, Grace, Include, ConnTO, IgnoreCase, HTTPS;
+static regex_t  CAlist, VerifyList, CRLlist, NoHTTPS11, Grace, Include, ConnTO, IgnoreCase, Ignore100continue, HTTPS;
 static regex_t  Disabled, Threads, CNName, Anonymise, DHParams, ECDHCurve;
 
 static regex_t  ControlGroup, ControlUser, ControlMode;
@@ -864,8 +864,8 @@
             parse_sess(res);
         } else if(!regexec(&DynScale, lin, 4, matches, 0)) {
             res->dynscale = atoi(lin + matches[1].rm_so);
-        } else if(!regexec(&IgnoreCase, lin, 4, matches, 0)) {
-            ign_case = atoi(lin + matches[1].rm_so);
+	} else if(!regexec(&IgnoreCase, lin, 4, matches, 0)) {
+	    ign_case = atoi(lin + matches[1].rm_so);
         } else if(!regexec(&Disabled, lin, 4, matches, 0)) {
             res->disabled = atoi(lin + matches[1].rm_so);
         } else if(!regexec(&End, lin, 4, matches, 0)) {
@@ -1037,9 +1037,9 @@
             if(regcomp(&m->pat, lin + matches[1].rm_so, REG_ICASE | REG_NEWLINE | REG_EXTENDED))
                 conf_err("ForceHTTP10 bad pattern");
         } else if(!regexec(&Service, lin, 4, matches, 0)) {
-            if(res->services == NULL)
+            if(res->services == NULL) {
                 res->services = parse_service(NULL);
-            else {
+            } else {
                 for(svc = res->services; svc->next; svc = svc->next)
                     ;
                 svc->next = parse_service(NULL);
@@ -1644,6 +1644,8 @@
             be_to = atoi(lin + matches[1].rm_so);
         } else if(!regexec(&ConnTO, lin, 4, matches, 0)) {
             be_connto = atoi(lin + matches[1].rm_so);
+	} else if(!regexec(&Ignore100continue, lin, 4, matches, 0)) {
+	    ignore_100 = atoi(lin + matches[1].rm_so);
         } else if(!regexec(&IgnoreCase, lin, 4, matches, 0)) {
             ignore_case = atoi(lin + matches[1].rm_so);
 #if OPENSSL_VERSION_NUMBER >= 0x0090800fL
@@ -1822,6 +1824,7 @@
     || regcomp(&IncludeDir, "^[ \t]*IncludeDir[ \t]+\"(.+)\"[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&ConnTO, "^[ \t]*ConnTO[ \t]+([1-9][0-9]*)[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&IgnoreCase, "^[ \t]*IgnoreCase[ \t]+([01])[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
+    || regcomp(&Ignore100continue, "^[ \t]*Ignore100continue[ \t]+([01])[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&HTTPS, "^[ \t]*HTTPS[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&Disabled, "^[ \t]*Disabled[ \t]+([01])[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&DHParams, "^[ \t]*DHParams[ \t]+\"(.+)\"[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
@@ -1921,6 +1924,7 @@
     alive_to = 30;
     daemonize = 1;
     grace = 30;
+    ignore_100 = 1;
 
     services = NULL;
     listeners = NULL;
@@ -2010,6 +2014,7 @@
     regfree(&IncludeDir);
     regfree(&ConnTO);
     regfree(&IgnoreCase);
+    regfree(&Ignore100continue);
     regfree(&HTTPS);
     regfree(&Disabled);
     regfree(&CNName);
--- /opt/pound28a-github/http.c	2015-07-10 22:31:15.000000000 +0200
+++ /opt/pound-2.8a-100cont-zenloadbalancer/http.c	2016-06-27 21:43:30.431463667 +0200
@@ -520,7 +520,7 @@
 void
 do_http(thr_arg *arg)
 {
-    int                 cl_11, be_11, res, chunked, n, sock, no_cont, skip, conn_closed, force_10, sock_proto, is_rpc;
+    int                 cl_11, be_11, res, chunked, n, sock, no_cont, skip, conn_closed, force_10, sock_proto, is_rpc, exp_cont, read_cl_body;
     LISTENER            *lstn;
     SERVICE             *svc;
     BACKEND             *backend, *cur_backend, *old_backend;
@@ -706,7 +706,7 @@
         }
 
         /* check other headers */
-        for(chunked = 0, cont = L_1, n = 1; n < MAXHEADERS && headers[n]; n++) {
+        for(chunked = 0, cont = L_1, n = 1, exp_cont = 0; n < MAXHEADERS && headers[n]; n++) {
             /* no overflow - see check_header for details */
             switch(check_header(headers[n], buf)) {
             case HEADER_HOST:
@@ -742,13 +742,14 @@
                 }
                 break;
             case HEADER_EXPECT:
-                /*
-                 * we do NOT support the "Expect: 100-continue" headers
-                 * support may involve severe performance penalties (non-responding back-end, etc)
-                 * as a stop-gap measure we just skip these headers
-                 */
-                if(!strcasecmp("100-continue", buf))
-                    headers_ok[n] = 0;
+                 // By Zen Load Balancer: Supported "Expect: 100-continue" headers
+                if(!strcasecmp("100-continue", buf)) {
+		    if (ignore_100 == 1) {
+			headers_ok[n] = 0;
+		    } else {
+			exp_cont = 1;
+		    }
+		}
                 break;
             case HEADER_ILLEGAL:
                 if(lstn->log_level > 0) {
@@ -1171,6 +1172,78 @@
             BIO_puts(be, "\r\n");
         }
 
+        /* headers from client were sent to backend; if there was an 'Expect: 100-continue' we wait for the backend
+         * to respond and forward this to the client before handling the body of the request. 
+         * If server does not reply with 'Continue' we'll close connections and bail out without reading the request.
+         * Note that since we have no timeouts, we'll keep waiting for the server reply.
+         */
+
+        //headers = NULL;            /* headers will be read if exp_cont seen */
+        read_cl_body = 1;          /* will be reset to 0 if exp_cont and '100 Continue' not rx'd, to skip */
+
+        if (exp_cont) {              /* 'Expect: 100-continue' header seen! */
+		logmsg(LOG_INFO, "Managing connection Expect 100-continue");
+
+		/* flush to the back-end */
+        	if(cur_backend->be_type == 0 && BIO_flush(be) != 1) {
+            		str_be(buf, MAXBUF - 1, cur_backend);
+            		end_req = cur_time();
+            		addr2str(caddr, MAXBUF - 1, &from_host, 1);
+            		logmsg(LOG_NOTICE, "(%lx) e500 for %s error flush to %s/%s: %s (%.3f sec)",
+                		pthread_self(), caddr, buf, request, strerror(errno), (end_req - start_req) / 1000000.0);
+            		err_reply(cl, h500, lstn->err500);
+            		clean_all();
+            		return;
+        	 }
+
+            if((headers = get_headers(be, cl, lstn)) == NULL) {
+                str_be(buf, MAXBUF - 1, cur_backend);
+                end_req = cur_time();
+                addr2str(caddr, MAXBUF - 1, &from_host, 1);
+                logmsg(LOG_NOTICE, "(%lx) e500 for %s response error read headers from %s/%s: %s (%.3f secs)",
+                    pthread_self(), caddr, buf, request, strerror(errno), (end_req - start_req) / 1000000.0);
+                err_reply(cl, h500, lstn->err500);
+                clean_all();
+                return;
+            }
+            strncpy(response, headers[0], MAXBUF);  /* get_headers made sure of termination & length */
+            be_11 = (response[7] == '1');           /* just in case this is needed somewhere somehow */
+            if (regexec(&RESP_SKIP, response, 0, NULL, 0)) {      /* other reply than '100 Continue' ? */
+                logmsg(LOG_NOTICE, "(%lx) didn't get '100 Continue' but '%s' response from backend", pthread_self(), response);
+                conn_closed = 1;        /* force be connection to close; probably overkill but safe */
+                read_cl_body = 0;       /* skip reading client body */
+            } else {
+                /* send the response to client */
+                for(n = 0; n < MAXHEADERS && headers[n]; n++) {
+                    /* logmsg(LOG_NOTICE, "(%lx) sending '%s' to client", pthread_self(), headers[n]); */
+                    if(BIO_printf(cl, "%s\r\n", headers[n]) <= 0) {
+                        if(errno) {
+                            addr2str(caddr, MAXBUF - 1, &from_host, 1);
+                            logmsg(LOG_NOTICE, "(%lx) error write headers to %s: %s", pthread_self(), caddr, strerror(errno));
+                        }
+                        free_headers(headers);
+                        clean_all();
+                        return;
+                    }
+                }
+                free_headers(headers);
+                headers = NULL;
+
+                /* final CRLF */
+                BIO_puts(cl, "\r\n");
+                if(BIO_flush(cl) != 1) {
+                    if(errno) {
+                        addr2str(caddr, MAXBUF - 1, &from_host, 1);
+                        logmsg(LOG_NOTICE, "(%lx) error flush headers to %s: %s", pthread_self(), caddr, strerror(errno));
+                    }
+                    clean_all();
+                    return;
+                }
+             }
+        }
+
+        if (read_cl_body) {   /* read client body if '100 Continue' rx'd or no 'Expect: 100-continue' seen */
+
         if(cl_11 && chunked) {
             /* had Transfer-encoding: chunked so read/write all the chunks (HTTP/1.1 only) */
             if(copy_chunks(cl, be, NULL, cur_backend->be_type, lstn->max_req)) {
@@ -1259,7 +1332,8 @@
                     BIO_flush(be);
                 }
             }
-        }
+	 } 
+	}
 
         /* flush to the back-end */
         if(cur_backend->be_type == 0 && BIO_flush(be) != 1) {
@@ -1413,7 +1487,8 @@
             break;
         }
 
-        /* get the response */
+	/* we'll arrive directly here if '100 Continue' was expected but not received; headers will be valid then */
+	/* get the response */
         for(skip = 1; skip;) {
             if((headers = get_headers(be, cl, lstn)) == NULL) {
                 str_be(buf, MAXBUF - 1, cur_backend);
@@ -1429,11 +1504,17 @@
             strncpy(response, headers[0], MAXBUF);
             be_11 = (response[7] == '1');
             /* responses with code 100 are never passed back to the client */
-            skip = !regexec(&RESP_SKIP, response, 0, NULL, 0);
+	    if (exp_cont == 1){
+		skip = 0;  /* was: !regexec(&RESP_SKIP, response, 0, NULL, 0); to ignore 100_continue */
             /* some response codes (1xx, 204, 304) have no content */
+	    } else {
+		skip = !regexec(&RESP_SKIP, response, 0, NULL, 0);
+	    }
             if(!no_cont && !regexec(&RESP_IGN, response, 0, NULL, 0))
                 no_cont = 1;
 
+            for(n = 0; n < MAXHEADERS; n++)
+            	headers_ok[n] = 1;
             for(chunked = 0, cont = -1L, n = 1; n < MAXHEADERS && headers[n]; n++) {
                 switch(check_header(headers[n], buf)) {
                 case HEADER_CONNECTION:
@@ -1493,6 +1574,8 @@
             /* send the response */
             if(!skip)
                 for(n = 0; n < MAXHEADERS && headers[n]; n++) {
+                	if(!headers_ok[n])
+                		continue;
                     if(BIO_printf(cl, "%s\r\n", headers[n]) <= 0) {
                         if(errno) {
                             addr2str(caddr, MAXBUF - 1, &from_host, 1);
@@ -1504,6 +1587,9 @@
                     }
                 }
             free_headers(headers);
+            if(!skip && ssl && svc->sts >= 0)
+            	BIO_printf(cl, "Strict-Transport-Security: max-age=%d\r\n", svc->sts);
+
             if(!skip && cur_backend->be_type == 0 && svc->becookie && cur_backend->bekey) {
                 char *cp = buf;
                 char *ep = buf + sizeof(buf) - 1;
@@ -1553,6 +1639,7 @@
             if(!no_cont) {
                 /* ignore this if request was HEAD or similar */
                 if(be_11 && chunked) {
+		    logmsg(LOG_INFO, "Executing copy_chunks");
                     /* had Transfer-encoding: chunked so read/write all the chunks (HTTP/1.1 only) */
                     if(copy_chunks(be, cl, &res_bytes, skip, L0)) {
                         /* copy_chunks() has its own error messages */
--- /opt/pound28a-github/pound.h	2015-07-10 22:31:15.000000000 +0200
+++ /opt/pound-2.8a-100cont-zenloadbalancer/pound.h	2016-06-27 21:25:48.907486905 +0200
@@ -280,7 +280,10 @@
             log_facility,       /* log facility to use */
             print_log,          /* print log messages to stdout/stderr */
             grace,              /* grace period before shutdown */
-            control_sock;       /* control socket */
+            control_sock,       /* control socket */
+            ignore_100;		/* ignore header "Expect: 100-continue"*/
+				/* 1 Ignore header (Default)*/
+				/* 0 Manages header */
 
 extern regex_t  HEADER,     /* Allowed header */
                 CHUNK_HEAD, /* chunk header line */
--- /opt/pound28a-github/pound.8	2015-07-10 22:31:15.000000000 +0200
+++ /opt/pound-2.8a-100cont-zenloadbalancer/pound.8	2016-06-27 21:09:45.000000000 +0200
@@ -275,6 +275,10 @@
 Ignore case when matching URLs (default: 0). This value can be
 overridden for specific services.
 .TP
+\fBIgnore100continue\fR 0|1
+Ignore Header Expect: 100-continue (default: 1, Ignored). 
+If 0 pound manages Expect: 100-continue headers.
+.TP
 \fBDynScale\fR 0|1
 Enable or disable the dynamic rescaling code (default: 0). If enabled
 .B Pound

Reply via email to