From e4e794d8b8965c902489f8784a6a02859aa3658d Mon Sep 17 00:00:00 2001 From: Fabian Keil Date: Sun, 21 Oct 2012 12:53:33 +0000 Subject: [PATCH] Add optional client-side pipelining support If the new config directive tolerate-pipelining is enabled, Privoxy will keep pipelined client requests around to deal with them once the current request has been served. At the moment this is only done if the current request is known to have no body. --- jcc.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++------ loadcfg.c | 18 ++++++++- loaders.c | 3 +- project.h | 11 +++++- 4 files changed, 131 insertions(+), 14 deletions(-) diff --git a/jcc.c b/jcc.c index 059e9e60..ac6f282d 100644 --- a/jcc.c +++ b/jcc.c @@ -1,4 +1,4 @@ -const char jcc_rcs[] = "$Id: jcc.c,v 1.397 2012/10/21 12:51:07 fabiankeil Exp $"; +const char jcc_rcs[] = "$Id: jcc.c,v 1.398 2012/10/21 12:51:57 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jcc.c,v $ @@ -1111,7 +1111,6 @@ static void verify_request_length(struct client_state *csp) if (!(csp->flags & CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ) && ((csp->client_iob->cur[0] != '\0') || (csp->expected_client_content_length != 0))) { - csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED; if (strcmpic(csp->http->gpc, "GET") && strcmpic(csp->http->gpc, "HEAD") && strcmpic(csp->http->gpc, "TRACE") @@ -1120,19 +1119,36 @@ static void verify_request_length(struct client_state *csp) { /* XXX: this is an incomplete hack */ csp->flags &= ~CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ; + csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED; log_error(LOG_LEVEL_CONNECT, "There might be a request body. The connection will not be kept alive."); } else { - /* XXX: and so is this */ csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ; - log_error(LOG_LEVEL_CONNECT, - "Possible pipeline attempt detected. The connection will not " - "be kept alive and we will only serve the first request."); - /* Nuke the pipelined requests from orbit, just to be sure. */ - csp->client_iob->buf[0] = '\0'; - csp->client_iob->eod = csp->client_iob->cur = csp->client_iob->buf; + + if ((csp->config->feature_flags & RUNTIME_FEATURE_TOLERATE_PIPELINING) == 0) + { + csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED; + log_error(LOG_LEVEL_CONNECT, + "Possible pipeline attempt detected. The connection will not " + "be kept alive and we will only serve the first request."); + /* Nuke the pipelined requests from orbit, just to be sure. */ + csp->client_iob->buf[0] = '\0'; + csp->client_iob->eod = csp->client_iob->cur = csp->client_iob->buf; + } + else + { + /* + * Keep the pipelined data around for now, we'll deal with + * it once we're done serving the current request. + */ + csp->flags |= CSP_FLAG_PIPELINED_REQUEST_WAITING; + assert(csp->client_iob->eod > csp->client_iob->cur); + log_error(LOG_LEVEL_CONNECT, "Complete client request followed by " + "%d bytes of pipelined data received.", + (int)(csp->client_iob->eod - csp->client_iob->cur)); + } } } else @@ -1196,6 +1212,27 @@ static char *get_request_line(struct client_state *csp) memset(buf, 0, sizeof(buf)); + if ((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) != 0) + { + /* + * If there are multiple pipelined requests waiting, + * the flag will be set again once the next request + * has been parsed. + */ + csp->flags &= ~CSP_FLAG_PIPELINED_REQUEST_WAITING; + + request_line = get_header(csp->client_iob); + if ((NULL != request_line) && ('\0' != *request_line)) + { + return request_line; + } + else + { + log_error(LOG_LEVEL_CONNECT, "No complete request line " + "received yet. Continuing reading from %d.", csp->cfd); + } + } + do { if (!data_is_available(csp->cfd, csp->config->socket_timeout)) @@ -1539,7 +1576,7 @@ static void chat(struct client_state *csp) struct http_response *rsp; struct timeval timeout; #ifdef FEATURE_CONNECTION_KEEP_ALIVE - int watch_client_socket = 1; + int watch_client_socket; #endif memset(buf, 0, sizeof(buf)); @@ -1723,7 +1760,8 @@ static void chat(struct client_state *csp) log_error(LOG_LEVEL_CONNECT, "Failed sending request headers to: %s: %E", http->hostport); } - else if (flush_socket(csp->server_connection.sfd, csp->client_iob) < 0)) + else if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0) + && (flush_socket(csp->server_connection.sfd, csp->client_iob) < 0)) { write_failure = 1; log_error(LOG_LEVEL_CONNECT, @@ -1768,6 +1806,8 @@ static void chat(struct client_state *csp) server_body = 0; + watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING); + for (;;) { #ifdef __OS2__ @@ -2484,6 +2524,37 @@ static void prepare_csp_for_next_request(struct client_state *csp) { csp->flags |= CSP_FLAG_TOGGLED_ON; } + + if (csp->client_iob->eod > csp->client_iob->cur) + { + long bytes_to_shift = csp->client_iob->cur - csp->client_iob->buf; + size_t data_length = (size_t)(csp->client_iob->eod - csp->client_iob->cur); + + assert(bytes_to_shift > 0); + assert(data_length > 0); + + log_error(LOG_LEVEL_CONNECT, "Shifting %d pipelined bytes by %d bytes", + data_length, bytes_to_shift); + memmove(csp->client_iob->buf, csp->client_iob->cur, data_length); + csp->client_iob->cur = csp->client_iob->buf; + assert(csp->client_iob->eod == csp->client_iob->buf + bytes_to_shift + data_length); + csp->client_iob->eod = csp->client_iob->buf + data_length; + memset(csp->client_iob->eod, '\0', (size_t)bytes_to_shift); + + csp->flags |= CSP_FLAG_PIPELINED_REQUEST_WAITING; + } + else + { + /* + * We mainly care about resetting client_iob->cur so we don't + * waste buffer space at the beginning and don't mess up the + * request restoration done by cgi_show_request(). + * + * Freeing the buffer itself isn't technically necessary, + * but makes debugging more convenient. + */ + IOB_RESET(csp->client_iob); + } } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ @@ -2531,6 +2602,15 @@ static void serve(struct client_state *csp) latency = (unsigned)(csp->server_connection.response_received - csp->server_connection.request_sent) / 2; + if ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) + && (csp->flags & CSP_FLAG_CRUNCHED) + && (csp->expected_client_content_length != 0)) + { + csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED; + log_error(LOG_LEVEL_CONNECT, + "Tainting client socket %d due to unread data.", csp->cfd); + } + continue_chatting = (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE) && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED) @@ -2579,6 +2659,16 @@ static void serve(struct client_state *csp) if (continue_chatting) { + if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) != 0) + && socket_is_still_alive(csp->cfd)) + { + log_error(LOG_LEVEL_CONNECT, "A client request has been " + "pipelined on socket %d and the socket is still alive.", + csp->cfd); + prepare_csp_for_next_request(csp); + continue; + } + if (0 != (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE)) { if (csp->server_connection.sfd != JB_INVALID_SOCKET) @@ -2595,6 +2685,7 @@ static void serve(struct client_state *csp) "No server socket to keep open.", csp->cfd); } } + if ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) && data_is_available(csp->cfd, (int)csp->config->keep_alive_timeout) && socket_is_still_alive(csp->cfd)) diff --git a/loadcfg.c b/loadcfg.c index a742fe8c..f3bec214 100644 --- a/loadcfg.c +++ b/loadcfg.c @@ -1,4 +1,4 @@ -const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.132 2012/10/17 18:02:10 fabiankeil Exp $"; +const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.133 2012/10/21 12:32:21 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/loadcfg.c,v $ @@ -155,6 +155,7 @@ static struct file_list *current_configfile = NULL; #define hash_split_large_cgi_forms 671658948U /* "split-large-cgi-forms" */ #define hash_suppress_blocklists 1948693308U /* "suppress-blocklists" */ #define hash_templdir 11067889U /* "templdir" */ +#define hash_tolerate_pipelining 1360286620U /* "tolerate-pipelining" */ #define hash_toggle 447966U /* "toggle" */ #define hash_trust_info_url 430331967U /* "trust-info-url" */ #define hash_trustfile 56494766U /* "trustfile" */ @@ -484,6 +485,7 @@ struct configuration_spec * load_config(void) */ config->compression_level = 1; #endif + config->feature_flags &= ~RUNTIME_FEATURE_TOLERATE_PIPELINING; configfp = fopen(configfile, "r"); if (NULL == configfp) @@ -1335,6 +1337,20 @@ struct configuration_spec * load_config(void) config->templdir = make_path(NULL, arg); break; +/* ************************************************************************* + * tolerate-pipelining (0|1) + * *************************************************************************/ + case hash_tolerate_pipelining : + if (parse_toggle_state(cmd, arg) == 1) + { + config->feature_flags |= RUNTIME_FEATURE_TOLERATE_PIPELINING; + } + else + { + config->feature_flags &= ~RUNTIME_FEATURE_TOLERATE_PIPELINING; + } + break; + /* ************************************************************************* * toggle (0|1) * *************************************************************************/ diff --git a/loaders.c b/loaders.c index 69febbcf..0e67da24 100644 --- a/loaders.c +++ b/loaders.c @@ -1,4 +1,4 @@ -const char loaders_rcs[] = "$Id: loaders.c,v 1.91 2012/06/19 12:50:22 fabiankeil Exp $"; +const char loaders_rcs[] = "$Id: loaders.c,v 1.92 2012/07/23 12:43:56 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/loaders.c,v $ @@ -182,6 +182,7 @@ unsigned int sweep(void) last_active->next = client_list->next; freez(csp->ip_addr_str); + freez(csp->client_iob->buf); freez(csp->iob->buf); freez(csp->error_message); diff --git a/project.h b/project.h index 36913f68..2b1ca3d3 100644 --- a/project.h +++ b/project.h @@ -1,7 +1,7 @@ #ifndef PROJECT_H_INCLUDED #define PROJECT_H_INCLUDED /** Version string. */ -#define PROJECT_H_VERSION "$Id: project.h,v 1.185 2012/10/21 12:39:27 fabiankeil Exp $" +#define PROJECT_H_VERSION "$Id: project.h,v 1.186 2012/10/21 12:52:35 fabiankeil Exp $" /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/project.h,v $ @@ -829,6 +829,12 @@ struct reusable_connection */ #define CSP_FLAG_BUFFERED_CONTENT_DEFLATED 0x00400000U +/** + * Flag for csp->flags: Set if we already read (parts of) + * a pipelined request in which case the client obviously + * isn't done talking. + */ +#define CSP_FLAG_PIPELINED_REQUEST_WAITING 0x00800000U /* * Flags for use in return codes of child processes @@ -1202,6 +1208,9 @@ struct access_control_list /** configuration_spec::feature_flags: Buffered content is sent compressed if the client supports it. */ #define RUNTIME_FEATURE_COMPRESSION 1024U +/** configuration_spec::feature_flags: Pipelined requests are served instead of being discarded. */ +#define RUNTIME_FEATURE_TOLERATE_PIPELINING 2048U + /** * Data loaded from the configuration file. * -- 2.39.2