Use case: Skype groups appear to use TLS-encrypted MSNP protocol instead of HTTPS. This change allows Squid admins using SslBump to tunnel Skype groups and similar non-HTTP traffic bytes via "on_unsupported_protocol tunnel all". Previously, the combination resulted in encrypted HTTP 400 (Bad Request) messages sent to the client (that does not speak HTTP).

Also this patch:
* fixes bug 4529: !EBIT_TEST(entry->flags, ENTRY_FWD_HDR_WAIT) assertion in FwdState.cc.

* when splicing transparent connections during SslBump step1, avoid access-logging an extra record and log %ssl::bump_mode as the expected "splice" not "none".

* handles an XXX comment inside clientTunnelOnError for possible memory leak of client streams related objects

 * fixes TunnelStateData logging in the case of splicing after peek.

This is a Measurement Factory project.
Support tunneling of bumped non-HTTP traffic. Other SslBump fixes.

Use case: Skype groups appear to use TLS-encrypted MSNP protocol instead
of HTTPS. This change allows Squid admins using SslBump to tunnel Skype
groups and similar non-HTTP traffic bytes via "on_unsupported_protocol
tunnel all". Previously, the combination resulted in encrypted HTTP 400
(Bad Request) messages sent to the client (that does not speak HTTP).

Also this patch:
 * fixes bug 4529: !EBIT_TEST(entry->flags, ENTRY_FWD_HDR_WAIT)
   assertion in FwdState.cc.

 * when splicing transparent connections during SslBump step1, avoid
   access-logging an extra record and log %ssl::bump_mode as the expected
   "splice" not "none".

 * handles an XXX comment inside clientTunnelOnError for possible memory
   leak of client streams related objects

 * fixes TunnelStateData logging in the case of splicing after peek.

This is a Measurement Factory project.

=== modified file 'src/FwdState.cc'
--- src/FwdState.cc	2016-08-10 00:56:30 +0000
+++ src/FwdState.cc	2016-10-06 08:27:50 +0000
@@ -830,62 +830,59 @@
 
     request->hier.startPeerClock();
 
     // Do not fowrward bumped connections to parent proxy unless it is an
     // origin server
     if (serverDestinations[0]->getPeer() && !serverDestinations[0]->getPeer()->options.originserver && request->flags.sslBumped) {
         debugs(50, 4, "fwdConnectStart: Ssl bumped connections through parent proxy are not allowed");
         ErrorState *anErr = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request);
         fail(anErr);
         self = NULL; // refcounted
         return;
     }
 
     request->flags.pinned = false; // XXX: what if the ConnStateData set this to flag existing credentials?
     // XXX: answer: the peer selection *should* catch it and give us only the pinned peer. so we reverse the =0 step below.
     // XXX: also, logs will now lie if pinning is broken and leads to an error message.
     if (serverDestinations[0]->peerType == PINNED) {
         ConnStateData *pinned_connection = request->pinnedConnection();
         debugs(17,7, "pinned peer connection: " << pinned_connection);
         // pinned_connection may become nil after a pconn race
-        if (pinned_connection) {
-            serverConn = pinned_connection->borrowPinnedConnection(request, serverDestinations[0]->getPeer());
-            if (Comm::IsConnOpen(serverConn)) {
-                pinned_connection->stopPinnedConnectionMonitoring();
-                flags.connected_okay = true;
-                ++n_tries;
-                request->flags.pinned = true;
-                if (pinned_connection->pinnedAuth())
-                    request->flags.auth = true;
-
-                closeHandler = comm_add_close_handler(serverConn->fd,  fwdServerClosedWrapper, this);
-
-                syncWithServerConn(pinned_connection->pinning.host);
-
-                // the server may close the pinned connection before this request
-                pconnRace = racePossible;
-                dispatch();
-                return;
-            }
+        serverConn = pinned_connection ? pinned_connection->borrowPinnedConnection(request, serverDestinations[0]->getPeer()) : nullptr;
+        if (Comm::IsConnOpen(serverConn)) {
+            flags.connected_okay = true;
+            ++n_tries;
+            request->flags.pinned = true;
+
+            if (pinned_connection->pinnedAuth())
+                request->flags.auth = true;
+
+            closeHandler = comm_add_close_handler(serverConn->fd,  fwdServerClosedWrapper, this);
+
+            syncWithServerConn(pinned_connection->pinning.host);
+
+            // the server may close the pinned connection before this request
+            pconnRace = racePossible;
+            dispatch();
+            return;
+        }
 
-        } else
-            serverConn = nullptr;
 
         // Pinned connection failure.
         debugs(17,2,HERE << "Pinned connection failed: " << pinned_connection);
         ErrorState *anErr = new ErrorState(ERR_ZERO_SIZE_OBJECT, Http::scServiceUnavailable, request);
         fail(anErr);
         self = NULL; // refcounted
         return;
     }
 
     // Use pconn to avoid opening a new connection.
     const char *host = NULL;
     if (!serverDestinations[0]->getPeer())
         host = request->url.host();
 
     Comm::ConnectionPointer temp;
     // Avoid pconns after races so that the same client does not suffer twice.
     // This does not increase the total number of connections because we just
     // closed the connection that failed the race. And re-pinning assumes this.
     if (pconnRace != raceHappened)
         temp = pconnPop(serverDestinations[0], host);

=== modified file 'src/RequestFlags.h'
--- src/RequestFlags.h	2016-01-01 00:12:18 +0000
+++ src/RequestFlags.h	2016-10-04 07:46:28 +0000
@@ -99,38 +99,41 @@
     bool chunkedReply :1;
     /** set if stream error has occured */
     bool streamError :1;
     /** internal ssl-bump request to get server cert */
     bool sslPeek :1;
     /** set if X-Forwarded-For checking is complete
      *
      * do not read directly; use doneFollowXff for reading
      */
     bool done_follow_x_forwarded_for :1;
     /** set for ssl-bumped requests */
     bool sslBumped :1;
     /// carries a representation of an FTP command [received on ftp_port]
     bool ftpNative :1;
     bool destinationIpLookedUp:1;
     /** request to reset the TCP stream */
     bool resetTcp:1;
     /** set if the request is ranged */
     bool isRanged :1;
 
+    /// whether to forward via TunnelStateData (instead of FwdState)
+    bool forceTunnel :1;
+
     /** clone the flags, resetting to default those which are not safe in
      *  a related (e.g. ICAP-adapted) request.
      */
     RequestFlags cloneAdaptationImmune() const;
 
     // if FOLLOW_X_FORWARDED_FOR is not set, we always return "done".
     bool doneFollowXff() const {
         return done_follow_x_forwarded_for || !FOLLOW_X_FORWARDED_FOR;
     }
 
     // if USE_HTTP_VIOLATIONS is not set, never allow this
     bool noCacheHack() const {
         return USE_HTTP_VIOLATIONS && nocacheHack;
     }
 };
 
 #endif /* SQUID_REQUESTFLAGS_H_ */
 

=== modified file 'src/client_side.cc'
--- src/client_side.cc	2016-09-22 14:21:12 +0000
+++ src/client_side.cc	2016-10-13 15:08:07 +0000
@@ -1281,50 +1281,53 @@
 /** Parse an HTTP request
  *
  *  \note Sets result->flags.parsed_ok to 0 if failed to parse the request,
  *          to 1 if the request was correctly parsed.
  *  \param[in] csd a ConnStateData. The caller must make sure it is not null
  *  \param[in] hp an Http1::RequestParser
  *  \param[out] mehtod_p will be set as a side-effect of the parsing.
  *          Pointed-to value will be set to Http::METHOD_NONE in case of
  *          parsing failure
  *  \param[out] http_ver will be set as a side-effect of the parsing
  *  \return NULL on incomplete requests,
  *          a Http::Stream on success or failure.
  */
 Http::Stream *
 parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
 {
     /* Attempt to parse the first line; this will define where the method, url, version and header begin */
     {
         const bool parsedOk = hp->parse(csd->inBuf);
 
-        if (csd->port->flags.isIntercepted() && Config.accessList.on_unsupported_protocol)
-            csd->preservedClientData = csd->inBuf;
         // sync the buffers after parsing.
         csd->inBuf = hp->remaining();
 
         if (hp->needsMoreData()) {
             debugs(33, 5, "Incomplete request, waiting for end of request line");
             return NULL;
         }
 
+        if (csd->mayTunnelUnsupportedProto()) {
+            csd->preservedClientData = hp->parsed();
+            csd->preservedClientData.append(csd->inBuf);
+        }
+
         if (!parsedOk) {
             const bool tooBig =
                 hp->parseStatusCode == Http::scRequestHeaderFieldsTooLarge ||
                 hp->parseStatusCode == Http::scUriTooLong;
             auto result = csd->abortRequestParsing(
                               tooBig ? "error:request-too-large" : "error:invalid-request");
             // assume that remaining leftovers belong to this bad request
             csd->consumeInput(csd->inBuf.length());
             return result;
         }
     }
 
     /* We know the whole request is in parser now */
     debugs(11, 2, "HTTP Client " << csd->clientConnection);
     debugs(11, 2, "HTTP Client REQUEST:\n---------\n" <<
            hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol() << "\n" <<
            hp->mimeHeader() <<
            "\n----------");
 
     /* deny CONNECT via accelerated ports */
@@ -1547,77 +1550,62 @@
                     context->http->al->request = request;
                     HTTPMSGLOCK(context->http->al->request);
                 }
                 repContext->setReplyToError(request->method, err);
                 assert(context->http->out.offset == 0);
                 context->pullData();
                 return true;
             }
         }
     }
 
     return false;
 }
 #endif // USE_OPENSSL
 
 /**
  * Check on_unsupported_protocol checklist and return true if tunnel mode selected
  * or false otherwise
  */
 bool
-clientTunnelOnError(ConnStateData *conn, Http::Stream *context, HttpRequest *request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes)
+clientTunnelOnError(ConnStateData *conn, Http::StreamPointer &context, HttpRequest::Pointer &request, const HttpRequestMethod& method, err_type requestError)
 {
-    if (conn->port->flags.isIntercepted() &&
-            Config.accessList.on_unsupported_protocol && conn->pipeline.nrequests <= 1) {
-        ACLFilledChecklist checklist(Config.accessList.on_unsupported_protocol, request, NULL);
+    if (conn->mayTunnelUnsupportedProto()) {
+        ACLFilledChecklist checklist(Config.accessList.on_unsupported_protocol, request.getRaw(), NULL);
         checklist.requestErrorType = requestError;
         checklist.src_addr = conn->clientConnection->remote;
         checklist.my_addr = conn->clientConnection->local;
         checklist.conn(conn);
         allow_t answer = checklist.fastCheck();
         if (answer == ACCESS_ALLOWED && answer.kind == 1) {
             debugs(33, 3, "Request will be tunneled to server");
             if (context) {
-                // XXX: Either the context is finished() or it should stay queued.
-                // The below may leak client streams BodyPipe objects. BUT, we need
-                // to check if client-streams detatch is safe to do here (finished() will detatch).
                 assert(conn->pipeline.front() == context); // XXX: still assumes HTTP/1 semantics
-                conn->pipeline.popMe(Http::StreamPointer(context));
+                context->finished(); // Will remove from conn->pipeline queue
             }
             Comm::SetSelect(conn->clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
-            return conn->fakeAConnectRequest("unknown-protocol", conn->preservedClientData);
+            return conn->initiateTunneledRequest(request, Http::METHOD_NONE, "unknown-protocol", conn->preservedClientData);
         } else {
             debugs(33, 3, "Continue with returning the error: " << requestError);
         }
     }
 
-    if (context) {
-        conn->quitAfterError(request);
-        clientStreamNode *node = context->getClientReplyContext();
-        clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
-        assert (repContext);
-
-        repContext->setReplyToError(requestError, errStatusCode, method, context->http->uri, conn->clientConnection->remote, NULL, requestErrorBytes, NULL);
-
-        assert(context->http->out.offset == 0);
-        context->pullData();
-    } // else Probably an ERR_REQUEST_START_TIMEOUT error so just return.
     return false;
 }
 
 void
 clientProcessRequestFinished(ConnStateData *conn, const HttpRequest::Pointer &request)
 {
     /*
      * DPW 2007-05-18
      * Moved the TCP_RESET feature from clientReplyContext::sendMoreData
      * to here because calling comm_reset_close() causes http to
      * be freed before accessing.
      */
     if (request != NULL && request->flags.resetTcp && Comm::IsConnOpen(conn->clientConnection)) {
         debugs(33, 3, HERE << "Sending TCP RST on " << conn->clientConnection);
         conn->flags.readMore = false;
         comm_reset_close(conn->clientConnection);
     }
 }
 
 void
@@ -2138,41 +2126,41 @@
  */
 bool
 ConnStateData::clientParseRequests()
 {
     bool parsed_req = false;
 
     debugs(33, 5, HERE << clientConnection << ": attempting to parse");
 
     // Loop while we have read bytes that are not needed for producing the body
     // On errors, bodyPipe may become nil, but readMore will be cleared
     while (!inBuf.isEmpty() && !bodyPipe && flags.readMore) {
 
         /* Limit the number of concurrent requests */
         if (concurrentRequestQueueFilled())
             break;
 
         // try to parse the PROXY protocol header magic bytes
         if (needProxyProtocolHeader_ && !parseProxyProtocolHeader())
             break;
 
-        if (Http::Stream *context = parseOneRequest()) {
+        if (Http::StreamPointer context = parseOneRequest()) {
             debugs(33, 5, clientConnection << ": done parsing a request");
 
             AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout",
                                              CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http));
             commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall);
 
             context->registerWithConn();
 
             processParsedRequest(context);
 
             parsed_req = true; // XXX: do we really need to parse everything right NOW ?
 
             if (context->mayUseConnection()) {
                 debugs(33, 3, HERE << "Not parsing new requests, as this request may need the connection");
                 break;
             }
         } else {
             debugs(33, 5, clientConnection << ": not enough request data: " <<
                    inBuf.length() << " < " << Config.maxRequestHeaderSize);
             Must(inBuf.length() < Config.maxRequestHeaderSize);
@@ -2358,57 +2346,46 @@
     flags.readMore = false;
 }
 
 void
 ConnStateData::noteBodyConsumerAborted(BodyPipe::Pointer )
 {
     // request reader may get stuck waiting for space if nobody consumes body
     if (bodyPipe != NULL)
         bodyPipe->enableAutoConsumption();
 
     // kids extend
 }
 
 /** general lifetime handler for HTTP requests */
 void
 ConnStateData::requestTimeout(const CommTimeoutCbParams &io)
 {
     if (!Comm::IsConnOpen(io.conn))
         return;
 
-    if (Config.accessList.on_unsupported_protocol && !receivedFirstByte_) {
-#if USE_OPENSSL
-        if (serverBump() && (serverBump()->act.step1 == Ssl::bumpPeek || serverBump()->act.step1 == Ssl::bumpStare)) {
-            if (spliceOnError(ERR_REQUEST_START_TIMEOUT)) {
-                receivedFirstByte();
-                return;
-            }
-        } else if (!fd_table[io.conn->fd].ssl)
-#endif
-        {
-            const HttpRequestMethod method;
-            if (clientTunnelOnError(this, NULL, NULL, method, ERR_REQUEST_START_TIMEOUT, Http::scNone, NULL)) {
-                // Tunnel established. Set receivedFirstByte to avoid loop.
-                receivedFirstByte();
-                return;
-            }
-        }
+    if (mayTunnelUnsupportedProto() && !receivedFirstByte_) {
+        Http::StreamPointer context = pipeline.front();
+        Must(context && context->http);
+        HttpRequest::Pointer request = context->http->request;
+        if (clientTunnelOnError(this, context, request, HttpRequestMethod(), ERR_REQUEST_START_TIMEOUT))
+            return;
     }
     /*
     * Just close the connection to not confuse browsers
     * using persistent connections. Some browsers open
     * a connection and then do not use it until much
     * later (presumeably because the request triggering
     * the open has already been completed on another
     * connection)
     */
     debugs(33, 3, "requestTimeout: FD " << io.fd << ": lifetime is expired.");
     io.conn->close();
 }
 
 static void
 clientLifetimeTimeout(const CommTimeoutCbParams &io)
 {
     ClientHttpRequest *http = static_cast<ClientHttpRequest *>(io.data);
     debugs(33, DBG_IMPORTANT, "WARNING: Closing client connection due to lifetime timeout");
     debugs(33, DBG_IMPORTANT, "\t" << http->uri);
     http->logType.err.timedout = true;
@@ -2418,40 +2395,41 @@
 
 ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
     AsyncJob("ConnStateData"), // kids overwrite
     Server(xact),
     bodyParser(nullptr),
 #if USE_OPENSSL
     sslBumpMode(Ssl::bumpEnd),
 #endif
     needProxyProtocolHeader_(false),
 #if USE_OPENSSL
     switchedToHttps_(false),
     parsingTlsHandshake(false),
     sslServerBump(NULL),
     signAlgorithm(Ssl::algSignTrusted),
 #endif
     stoppedSending_(NULL),
     stoppedReceiving_(NULL)
 {
     flags.readMore = true; // kids may overwrite
     flags.swanSang = false;
+    flags.tunneling = false;
 
     pinning.host = NULL;
     pinning.port = -1;
     pinning.pinned = false;
     pinning.auth = false;
     pinning.zeroReply = false;
     pinning.peer = NULL;
 
     // store the details required for creating more MasterXaction objects as new requests come in
     log_addr = xact->tcpClient->remote;
     log_addr.applyMask(Config.Addrs.client_netmask);
 
     // register to receive notice of Squid signal events
     // which may affect long persisting client connections
     registerRunner();
 }
 
 void
 ConnStateData::start()
 {
@@ -2750,41 +2728,41 @@
 
     Comm::SetSelect(details->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0);
 }
 
 /**
  * A callback function to use with the ACLFilledChecklist callback.
  * In the case of ACCESS_ALLOWED answer initializes a bumped SSL connection,
  * else reverts the connection to tunnel mode.
  */
 static void
 httpsSslBumpAccessCheckDone(allow_t answer, void *data)
 {
     ConnStateData *connState = (ConnStateData *) data;
 
     // if the connection is closed or closing, just return.
     if (!connState->isOpen())
         return;
 
     // Require both a match and a positive bump mode to work around exceptional
     // cases where ACL code may return ACCESS_ALLOWED with zero answer.kind.
-    if (answer == ACCESS_ALLOWED && (answer.kind != Ssl::bumpNone && answer.kind != Ssl::bumpSplice)) {
+    if (answer == ACCESS_ALLOWED && answer.kind != Ssl::bumpNone) {
         debugs(33, 2, "sslBump needed for " << connState->clientConnection << " method " << answer.kind);
         connState->sslBumpMode = static_cast<Ssl::BumpMode>(answer.kind);
     } else {
         debugs(33, 2, HERE << "sslBump not needed for " << connState->clientConnection);
         connState->sslBumpMode = Ssl::bumpNone;
     }
     if (!connState->fakeAConnectRequest("ssl-bump", connState->inBuf))
         connState->clientConnection->close();
 }
 
 /** handle a new HTTPS connection */
 static void
 httpsAccept(const CommAcceptCbParams &params)
 {
     MasterXaction::Pointer xact = params.xaction;
     const AnyP::PortCfgPointer s = xact->squidPort;
 
     // NP: it is possible the port was reconfigured when the call or accept() was queued.
 
     if (params.flag != Comm::OK) {
@@ -2882,56 +2860,43 @@
 
                     Security::ContextPointer ctx;
                     ctx.resetAndLock(SSL_get_SSL_CTX(ssl));
                     Ssl::configureUnconfiguredSslContext(ctx, signAlgorithm, *port);
                 } else {
                     Security::ContextPointer ctx(Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port));
                     getSslContextDone(ctx, true);
                 }
                 return;
             }
         }
     }
     Security::ContextPointer nil;
     getSslContextDone(nil);
 }
 
 void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties)
 {
     certProperties.commonName =  sslCommonName_.isEmpty() ? sslConnectHostOrIp.termedBuf() : sslCommonName_.c_str();
 
-    // fake certificate adaptation requires bump-server-first mode
-    if (!sslServerBump) {
-        assert(port->signingCert.get());
-        certProperties.signWithX509.resetAndLock(port->signingCert.get());
-        if (port->signPkey.get())
-            certProperties.signWithPkey.resetAndLock(port->signPkey.get());
-        certProperties.signAlgorithm = Ssl::algSignTrusted;
-        return;
-    }
-
-    // In case of an error while connecting to the secure server, use a fake
-    // trusted certificate, with no mimicked fields and no adaptation
-    // algorithms. There is nothing we can mimic so we want to minimize the
-    // number of warnings the user will have to see to get to the error page.
-    assert(sslServerBump->entry);
-    if (sslServerBump->entry->isEmpty()) {
+    const bool triedToConnect = sslServerBump && sslServerBump->entry;
+    const bool connectedOK = triedToConnect && sslServerBump->entry->isEmpty();
+    if (connectedOK) {
         if (X509 *mimicCert = sslServerBump->serverCert.get())
             certProperties.mimicCert.resetAndLock(mimicCert);
 
         ACLFilledChecklist checklist(NULL, sslServerBump->request.getRaw(),
                                      clientConnection != NULL ? clientConnection->rfc931 : dash_str);
         checklist.sslErrors = cbdataReference(sslServerBump->sslErrors());
 
         for (sslproxy_cert_adapt *ca = Config.ssl_client.cert_adapt; ca != NULL; ca = ca->next) {
             // If the algorithm already set, then ignore it.
             if ((ca->alg == Ssl::algSetCommonName && certProperties.setCommonName) ||
                     (ca->alg == Ssl::algSetValidAfter && certProperties.setValidAfter) ||
                     (ca->alg == Ssl::algSetValidBefore && certProperties.setValidBefore) )
                 continue;
 
             if (ca->aclList && checklist.fastCheck(ca->aclList) == ACCESS_ALLOWED) {
                 const char *alg = Ssl::CertAdaptAlgorithmStr[ca->alg];
                 const char *param = ca->param;
 
                 // For parameterless CN adaptation, use hostname from the
                 // CONNECT request.
@@ -2940,45 +2905,47 @@
                         param = sslConnectHostOrIp.termedBuf();
                     certProperties.commonName = param;
                     certProperties.setCommonName = true;
                 } else if (ca->alg == Ssl::algSetValidAfter)
                     certProperties.setValidAfter = true;
                 else if (ca->alg == Ssl::algSetValidBefore)
                     certProperties.setValidBefore = true;
 
                 debugs(33, 5, HERE << "Matches certificate adaptation aglorithm: " <<
                        alg << " param: " << (param ? param : "-"));
             }
         }
 
         certProperties.signAlgorithm = Ssl::algSignEnd;
         for (sslproxy_cert_sign *sg = Config.ssl_client.cert_sign; sg != NULL; sg = sg->next) {
             if (sg->aclList && checklist.fastCheck(sg->aclList) == ACCESS_ALLOWED) {
                 certProperties.signAlgorithm = (Ssl::CertSignAlgorithm)sg->alg;
                 break;
             }
         }
-    } else {// if (!sslServerBump->entry->isEmpty())
-        // Use trusted certificate for a Squid-generated error
-        // or the user would have to add a security exception
-        // just to see the error page. We will close the connection
-        // so that the trust is not extended to non-Squid content.
+    } else {// did not try to connect (e.g. client-first) or failed to connect
+        // In case of an error while connecting to the secure server, use a
+        // trusted certificate, with no mimicked fields and no adaptation
+        // algorithms. There is nothing we can mimic, so we want to minimize the
+        // number of warnings the user will have to see to get to the error page.
+        // We will close the connection, so that the trust is not extended to
+        // non-Squid content.
         certProperties.signAlgorithm = Ssl::algSignTrusted;
     }
 
     assert(certProperties.signAlgorithm != Ssl::algSignEnd);
 
     if (certProperties.signAlgorithm == Ssl::algSignUntrusted) {
         assert(port->untrustedSigningCert.get());
         certProperties.signWithX509.resetAndLock(port->untrustedSigningCert.get());
         certProperties.signWithPkey.resetAndLock(port->untrustedSignPkey.get());
     } else {
         assert(port->signingCert.get());
         certProperties.signWithX509.resetAndLock(port->signingCert.get());
 
         if (port->signPkey.get())
             certProperties.signWithPkey.resetAndLock(port->signPkey.get());
     }
     signAlgorithm = certProperties.signAlgorithm;
 
     certProperties.signHash = Ssl::DefaultSignHash;
 }
@@ -3167,85 +3134,77 @@
 
     assert(!inBuf.isEmpty());
     receivedFirstByte();
     fd_note(clientConnection->fd, "Parsing TLS handshake");
 
     bool unsupportedProtocol = false;
     try {
         if (!tlsParser.parseHello(inBuf)) {
             // need more data to finish parsing
             readSomeData();
             return;
         }
     }
     catch (const std::exception &ex) {
         debugs(83, 2, "error on FD " << clientConnection->fd << ": " << ex.what());
         unsupportedProtocol = true;
     }
 
     parsingTlsHandshake = false;
 
+    if (mayTunnelUnsupportedProto())
+        preservedClientData = inBuf;
+
     // Even if the parser failed, each TLS detail should either be set
     // correctly or still be "unknown"; copying unknown detail is a no-op.
     clientConnection->tlsNegotiations()->retrieveParsedInfo(tlsParser.details);
 
     // We should disable read/write handlers
     Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
     Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, NULL, NULL, 0);
 
-    if (!sslServerBump) { // BumpClientFirst mode does not use this member
+    if (unsupportedProtocol) {
+        Http::StreamPointer context = pipeline.front();
+        Must(context && context->http);
+        HttpRequest::Pointer request = context->http->request;
+        debugs(83, 5, "Got something other than TLS Client Hello. Cannot SslBump.");
+        sslBumpMode = Ssl::bumpNone;
+        if (!clientTunnelOnError(this, context, request, HttpRequestMethod(), ERR_PROTOCOL_UNKNOWN))
+            clientConnection->close();
+        return;
+    }
+
+    if (!sslServerBump || sslServerBump->act.step1 == Ssl::bumpClientFirst) { // Either means client-first.
         getSslContextStart();
         return;
     } else if (sslServerBump->act.step1 == Ssl::bumpServerFirst) {
         // will call httpsPeeked() with certificate and connection, eventually
         FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
     } else {
         Must(sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare);
-        startPeekAndSplice(unsupportedProtocol);
+        startPeekAndSplice();
     }
 }
 
-bool
-ConnStateData::spliceOnError(const err_type err)
-{
-    if (Config.accessList.on_unsupported_protocol) {
-        assert(serverBump());
-        ACLFilledChecklist checklist(Config.accessList.on_unsupported_protocol, serverBump()->request.getRaw(), NULL);
-        checklist.requestErrorType = err;
-        checklist.conn(this);
-        allow_t answer = checklist.fastCheck();
-        if (answer == ACCESS_ALLOWED && answer.kind == 1) {
-            return splice();
-        }
-    }
-    return false;
-}
-
 void
-ConnStateData::startPeekAndSplice(const bool unsupportedProtocol)
+ConnStateData::startPeekAndSplice()
 {
-    if (unsupportedProtocol) {
-        if (!spliceOnError(ERR_PROTOCOL_UNKNOWN))
-            clientConnection->close();
-        return;
-    }
-
     if (serverBump()) {
         Security::TlsDetails::Pointer const &details = tlsParser.details;
         if (details && !details->serverName.isEmpty()) {
             serverBump()->clientSni = details->serverName;
             resetSslCommonName(details->serverName.c_str());
         }
     }
 
     startPeekAndSpliceDone();
 }
 
 void httpsSslBumpStep2AccessCheckDone(allow_t answer, void *data)
 {
     ConnStateData *connState = (ConnStateData *) data;
 
     // if the connection is closed or closing, just return.
     if (!connState->isOpen())
         return;
 
     debugs(33, 5, "Answer: " << answer << " kind:" << answer.kind);
@@ -3261,55 +3220,48 @@
 
     if (bumpAction == Ssl::bumpTerminate) {
         connState->clientConnection->close();
     } else if (bumpAction != Ssl::bumpSplice) {
         connState->startPeekAndSpliceDone();
     } else if (!connState->splice())
         connState->clientConnection->close();
 }
 
 bool
 ConnStateData::splice()
 {
     // normally we can splice here, because we just got client hello message
 
     if (fd_table[clientConnection->fd].ssl.get()) {
         // Restore default read methods
         fd_table[clientConnection->fd].read_method = &default_read_method;
         fd_table[clientConnection->fd].write_method = &default_write_method;
     }
 
-    if (transparent()) {
-        // set the current protocol to something sensible (was "HTTPS" for the bumping process)
-        // we are sending a faked-up HTTP/1.1 message wrapper, so go with that.
-        transferProtocol = Http::ProtocolVersion();
-        return fakeAConnectRequest("intercepted TLS spliced", inBuf);
-    } else {
-        // XXX: assuming that there was an HTTP/1.1 CONNECT to begin with...
-
-        // reset the current protocol to HTTP/1.1 (was "HTTPS" for the bumping process)
-        transferProtocol = Http::ProtocolVersion();
-        Http::StreamPointer context = pipeline.front();
-        ClientHttpRequest *http = context->http;
-        tunnelStart(http);
-        return true;
-    }
+    // XXX: assuming that there was an HTTP/1.1 CONNECT to begin with...
+    // reset the current protocol to HTTP/1.1 (was "HTTPS" for the bumping process)
+    transferProtocol = Http::ProtocolVersion();
+    assert(!pipeline.empty());
+    Http::StreamPointer context = pipeline.front();
+    ClientHttpRequest *http = context->http;
+    tunnelStart(http);
+    return true;
 }
 
 void
 ConnStateData::startPeekAndSpliceDone()
 {
     // This is the Step2 of the SSL bumping
     assert(sslServerBump);
     Http::StreamPointer context = pipeline.front();
     ClientHttpRequest *http = context ? context->http : NULL;
 
     if (sslServerBump->step == Ssl::bumpStep1) {
         sslServerBump->step = Ssl::bumpStep2;
         // Run a accessList check to check if want to splice or continue bumping
 
         ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, sslServerBump->request.getRaw(), NULL);
         acl_checklist->al = http ? http->al : NULL;
         //acl_checklist->src_addr = params.conn->remote;
         //acl_checklist->my_addr = s->s;
         acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpNone));
         acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpClientFirst));
@@ -3323,42 +3275,42 @@
     fd_table[clientConnection->fd].dynamicTlsContext = unConfiguredCTX;
 
     if (!httpsCreate(clientConnection, unConfiguredCTX))
         return;
 
     switchedToHttps_ = true;
 
     auto ssl = fd_table[clientConnection->fd].ssl.get();
     BIO *b = SSL_get_rbio(ssl);
     Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
     bio->setReadBufData(inBuf);
     bio->hold(true);
 
     // Here squid should have all of the client hello message so the
     // Squid_SSL_accept should return 0;
     // This block exist only to force openSSL parse client hello and detect
     // ERR_SECURE_ACCEPT_FAIL error, which should be checked and splice if required.
     int ret = 0;
     if ((ret = Squid_SSL_accept(this, NULL)) < 0) {
         debugs(83, 2, "SSL_accept failed.");
-        const err_type err = ERR_SECURE_ACCEPT_FAIL;
-        if (!spliceOnError(err))
+        HttpRequest::Pointer request = http->request;
+        if (!clientTunnelOnError(this, context, request, HttpRequestMethod(), ERR_SECURE_ACCEPT_FAIL))
             clientConnection->close();
         return;
     }
 
     // We need to reset inBuf here, to be used by incoming requests in the case
     // of SSL bump
     inBuf.clear();
 
     debugs(83, 5, "Peek and splice at step2 done. Start forwarding the request!!! ");
     FwdState::Start(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw(), http ? http->al : NULL);
 }
 
 void
 ConnStateData::doPeekAndSpliceStep()
 {
     auto ssl = fd_table[clientConnection->fd].ssl.get();
     BIO *b = SSL_get_rbio(ssl);
     assert(b);
     Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
 
@@ -3375,75 +3327,159 @@
     Must(sslServerBump != NULL);
 
     if (Comm::IsConnOpen(serverConnection)) {
         pinConnection(serverConnection, NULL, NULL, false);
 
         debugs(33, 5, HERE << "bumped HTTPS server: " << sslConnectHostOrIp);
     } else {
         debugs(33, 5, HERE << "Error while bumping: " << sslConnectHostOrIp);
 
         //  copy error detail from bump-server-first request to CONNECT request
         if (!pipeline.empty() && pipeline.front()->http != nullptr && pipeline.front()->http->request)
             pipeline.front()->http->request->detailError(sslServerBump->request->errType, sslServerBump->request->errDetail);
     }
 
     getSslContextStart();
 }
 
 #endif /* USE_OPENSSL */
 
 bool
-ConnStateData::fakeAConnectRequest(const char *reason, const SBuf &payload)
+ConnStateData::initiateTunneledRequest(HttpRequest::Pointer const &cause, Http::MethodType const method, const char *reason, const SBuf &payload)
 {
     // fake a CONNECT request to force connState to tunnel
     SBuf connectHost;
+    unsigned short connectPort = 0;
+
+    if (pinning.serverConnection != nullptr) {
+        static char ip[MAX_IPSTRLEN];
+        connectHost.assign(pinning.serverConnection->remote.toStr(ip, sizeof(ip)));
+        connectPort = pinning.serverConnection->remote.port();
+    } else if (cause != NULL && cause->method == Http::METHOD_CONNECT) {
+        // We are inside a (not fully established) CONNECT request
+        connectHost = cause->url.host();
+        connectPort = cause->url.port();
+    } else {
+        debugs(33, 2, "Not able to compute URL, abort request tunneling for " << reason);
+        return false;
+    }
+
+    debugs(33, 2, "Request tunneling for " << reason);
+    ClientHttpRequest *http = buildFakeRequest(method, connectHost, connectPort, payload);
+    HttpRequest::Pointer request = http->request;
+    request->flags.forceTunnel = true;
+    http->calloutContext = new ClientRequestContext(http);
+    http->doCallouts();
+    clientProcessRequestFinished(this, request);
+    return true;
+}
+
+bool
+ConnStateData::fakeAConnectRequest(const char *reason, const SBuf &payload)
+{
+    debugs(33, 2, "fake a CONNECT request to force connState to tunnel for " << reason);
+
+    SBuf connectHost;
+    assert(transparent());
+    const unsigned short connectPort = clientConnection->local.port();
+
 #if USE_OPENSSL
-    if (serverBump() && !serverBump()->clientSni.isEmpty()) {
+    if (serverBump() && !serverBump()->clientSni.isEmpty())
         connectHost.assign(serverBump()->clientSni);
-        if (clientConnection->local.port() > 0)
-            connectHost.appendf(":%d",clientConnection->local.port());
-    } else
+    else
 #endif
     {
         static char ip[MAX_IPSTRLEN];
-        connectHost.assign(clientConnection->local.toUrl(ip, sizeof(ip)));
+        connectHost.assign(clientConnection->local.toStr(ip, sizeof(ip)));
     }
-    // Pre-pend this fake request to the TLS bits already in the buffer
-    SBuf retStr;
-    retStr.append("CONNECT ");
-    retStr.append(connectHost);
-    retStr.append(" HTTP/1.1\r\nHost: ");
-    retStr.append(connectHost);
-    retStr.append("\r\n\r\n");
-    retStr.append(payload);
-    inBuf = retStr;
-    bool ret = handleReadData();
-    if (ret)
-        ret = clientParseRequests();
 
-    if (!ret) {
-        debugs(33, 2, "Failed to start fake CONNECT request for " << reason << " connection: " << clientConnection);
-        return false;
-    }
+    ClientHttpRequest *http = buildFakeRequest(Http::METHOD_CONNECT, connectHost, connectPort, payload);
+
+    http->calloutContext = new ClientRequestContext(http);
+    HttpRequest::Pointer request = http->request;
+    http->doCallouts();
+    clientProcessRequestFinished(this, request);
     return true;
 }
 
+ClientHttpRequest *
+ConnStateData::buildFakeRequest(Http::MethodType const method, SBuf &useHost, unsigned short usePort, const SBuf &payload)
+{
+    ClientHttpRequest *http = new ClientHttpRequest(this);
+    Http::Stream *stream = new Http::Stream(clientConnection, http);
+
+    StoreIOBuffer tempBuffer;
+    tempBuffer.data = stream->reqbuf;
+    tempBuffer.length = HTTP_REQBUF_SZ;
+
+    ClientStreamData newServer = new clientReplyContext(http);
+    ClientStreamData newClient = stream;
+    clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach,
+                     clientReplyStatus, newServer, clientSocketRecipient,
+                     clientSocketDetach, newClient, tempBuffer);
+
+    http->uri = SBufToCstring(useHost);
+    stream->flags.parsed_ok = 1; // Do we need it?
+    stream->mayUseConnection(true);
+    
+    AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout",
+                                                CommTimeoutCbPtrFun(clientLifetimeTimeout, stream->http));
+    commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall);
+
+    stream->registerWithConn();
+
+    // Setup Http::Request object. Maybe should be replaced by a call to (modified)
+    // clientProcessRequest
+    HttpRequest::Pointer request = new HttpRequest();
+    AnyP::ProtocolType proto = (method == Http::METHOD_NONE) ? AnyP::PROTO_UNKNOWN : AnyP::PROTO_HTTP;
+    request->url.setScheme(proto, nullptr);
+    request->method = method;
+    request->url.host(useHost.c_str());
+    request->url.port(usePort);
+    http->request = request.getRaw();
+    HTTPMSGLOCK(http->request);
+
+    request->clientConnectionManager = this;
+
+    if (proto == AnyP::PROTO_HTTP)
+        request->header.putStr(Http::HOST, useHost.c_str());
+    request->flags.intercepted = ((clientConnection->flags & COMM_INTERCEPTION) != 0);
+    request->flags.interceptTproxy = ((clientConnection->flags & COMM_TRANSPARENT) != 0 );
+    request->sources |= ((switchedToHttps() || port->transport.protocol == AnyP::PROTO_HTTPS) ? HttpMsg::srcHttps : HttpMsg::srcHttp);
+#if USE_AUTH
+    if (getAuth() != NULL)
+        request->auth_user_request = getAuth();
+#endif
+    request->client_addr = clientConnection->remote;
+#if FOLLOW_X_FORWARDED_FOR
+    request->indirect_client_addr = clientConnection->remote;
+#endif /* FOLLOW_X_FORWARDED_FOR */
+    request->my_addr = clientConnection->local;
+    request->myportname = port->name;
+
+    inBuf = payload;
+    flags.readMore = false;
+
+    setLogUri(http, urlCanonicalClean(request.getRaw()));
+    return http;
+}
+
 /// check FD after clientHttp[s]ConnectionOpened, adjust HttpSockets as needed
 static bool
 OpenedHttpSocket(const Comm::ConnectionPointer &c, const Ipc::FdNoteId portType)
 {
     if (!Comm::IsConnOpen(c)) {
         Must(NHttpSockets > 0); // we tried to open some
         --NHttpSockets; // there will be fewer sockets than planned
         Must(HttpSockets[NHttpSockets] < 0); // no extra fds received
 
         if (!NHttpSockets) // we could not open any listen sockets at all
             fatalf("Unable to open %s",FdNote(portType));
 
         return false;
     }
     return true;
 }
 
 /// find any unused HttpSockets[] slot and store fd there or return false
 static bool
 AddOpenedHttpSocket(const Comm::ConnectionPointer &conn)
@@ -4051,28 +4087,43 @@
         return;
 
     // a request currently using this connection is responsible for logging
     if (!pipeline.empty() && pipeline.back()->mayUseConnection())
         return;
 
     /* Either we are waiting for the very first transaction, or
      * we are done with the Nth transaction and are waiting for N+1st.
      * XXX: We assume that if anything was added to inBuf, then it could
      * only be consumed by actions already covered by the above checks.
      */
 
     // do not log connections that closed after a transaction (it is normal)
     // TODO: access_log needs ACLs to match received-no-bytes connections
     // XXX: TLS may return here even though we got no transactions yet
     // XXX: PROXY protocol may return here even though we got no
     // transactions yet
     if (receivedFirstByte_ && inBuf.isEmpty())
         return;
 
+    // TunnelStateData::http is responsible for logging
+    if (flags.tunneling)
+        return;
+
     /* Create a temporary ClientHttpRequest object. Its destructor will log. */
     ClientHttpRequest http(this);
     http.req_sz = inBuf.length();
     char const *uri = "error:transaction-end-before-headers";
     http.uri = xstrdup(uri);
     setLogUri(&http, uri);
 }
 
+bool
+ConnStateData::mayTunnelUnsupportedProto()
+{
+    return Config.accessList.on_unsupported_protocol
+#if USE_OPENSSL
+        &&
+        ((port->flags.isIntercepted() && port->flags.tunnelSslBumping)
+        || (serverBump() && pinning.serverConnection != NULL))
+#endif
+        ;
+}

=== modified file 'src/client_side.h'
--- src/client_side.h	2016-09-21 17:56:27 +0000
+++ src/client_side.h	2016-10-13 15:11:40 +0000
@@ -103,40 +103,43 @@
      * NOTE: this is ONLY connection based because NTLM and Negotiate is against HTTP spec.
      */
     const Auth::UserRequest::Pointer &getAuth() const { return auth_; }
 
     /**
      * Set the user details for connection-based authentication to use from now until connection closure.
      *
      * Any change to existing credentials shows that something invalid has happened. Such as:
      * - NTLM/Negotiate auth was violated by the per-request headers missing a revalidation token
      * - NTLM/Negotiate auth was violated by the per-request headers being for another user
      * - SSL-Bump CONNECT tunnel with persistent credentials has ended
      */
     void setAuth(const Auth::UserRequest::Pointer &aur, const char *cause);
 #endif
 
     Ip::Address log_addr;
 
     struct {
         bool readMore; ///< needs comm_read (for this request or new requests)
         bool swanSang; // XXX: temporary flag to check proper cleanup
+
+        /// whether tunnel.cc is responsible for handling the current request
+        bool tunneling;
     } flags;
     struct {
         Comm::ConnectionPointer serverConnection; /* pinned server side connection */
         char *host;             /* host name of pinned connection */
         int port;               /* port of pinned connection */
         bool pinned;             /* this connection was pinned */
         bool auth;               /* pinned for www authentication */
         bool reading;   ///< we are monitoring for peer connection closure
         bool zeroReply; ///< server closed w/o response (ERR_ZERO_SIZE_OBJECT)
         CachePeer *peer;             /* CachePeer the connection goes via */
         AsyncCall::Pointer readHandler; ///< detects serverConnection closure
         AsyncCall::Pointer closeHandler; /*The close handler for pinned server side connection*/
     } pinning;
 
     bool transparent() const;
 
     /// true if we stopped receiving the request
     const char *stoppedReceiving() const { return stoppedReceiving_; }
     /// true if we stopped sending the response
     const char *stoppedSending() const { return stoppedSending_; }
@@ -186,58 +189,53 @@
     void connStateClosed(const CommCloseCbParams &io);
     void requestTimeout(const CommTimeoutCbParams &params);
 
     // AsyncJob API
     virtual void start();
     virtual bool doneAll() const { return BodyProducer::doneAll() && false;}
     virtual void swanSong();
 
     /// Changes state so that we close the connection and quit after serving
     /// the client-side-detected error response instead of getting stuck.
     void quitAfterError(HttpRequest *request); // meant to be private
 
     /// The caller assumes responsibility for connection closure detection.
     void stopPinnedConnectionMonitoring();
 
 #if USE_OPENSSL
     /// the second part of old httpsAccept, waiting for future HttpsServer home
     void postHttpsAccept();
 
     /// Initializes and starts a peek-and-splice negotiation with the SSL client
-    void startPeekAndSplice(const bool unknownProtocol);
+    void startPeekAndSplice();
     /// Called when the initialization of peek-and-splice negotiation finidhed
     void startPeekAndSpliceDone();
     /// Called when a peek-and-splice step finished. For example after
     /// server SSL certificates received and fake server SSL certificates
     /// generated
     void doPeekAndSpliceStep();
     /// called by FwdState when it is done bumping the server
     void httpsPeeked(Comm::ConnectionPointer serverConnection);
 
     /// Splice a bumped client connection on peek-and-splice mode
     bool splice();
 
-    /// Check on_unsupported_protocol access list and splice if required
-    /// \retval true on splice
-    /// \retval false otherwise
-    bool spliceOnError(const err_type err);
-
     /// Start to create dynamic Security::ContextPointer for host or uses static port SSL context.
     void getSslContextStart();
     /**
      * Done create dynamic ssl certificate.
      *
      * \param[in] isNew if generated certificate is new, so we need to add this certificate to storage.
      */
     void getSslContextDone(Security::ContextPointer &, bool isNew = false);
     /// Callback function. It is called when squid receive message from ssl_crtd.
     static void sslCrtdHandleReplyWrapper(void *data, const Helper::Reply &reply);
     /// Proccess response from ssl_crtd.
     void sslCrtdHandleReply(const Helper::Reply &reply);
 
     void switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode);
     void parseTlsHandshake();
     bool switchedToHttps() const { return switchedToHttps_; }
     Ssl::ServerBump *serverBump() {return sslServerBump;}
     inline void setServerBump(Ssl::ServerBump *srvBump) {
         if (!sslServerBump)
             sslServerBump = srvBump;
@@ -270,70 +268,79 @@
 
     /// handle a control message received by context from a peer and call back
     virtual void writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call) = 0;
 
     /// ClientStream calls this to supply response header (once) and data
     /// for the current Http::Stream.
     virtual void handleReply(HttpReply *header, StoreIOBuffer receivedData) = 0;
 
     /// remove no longer needed leading bytes from the input buffer
     void consumeInput(const size_t byteCount);
 
     /* TODO: Make the methods below (at least) non-public when possible. */
 
     /// stop parsing the request and create context for relaying error info
     Http::Stream *abortRequestParsing(const char *const errUri);
 
     /// generate a fake CONNECT request with the given payload
     /// at the beginning of the client I/O buffer
     bool fakeAConnectRequest(const char *reason, const SBuf &payload);
 
+    /// generates and sends to tunnel.cc a fake request with a given payload
+    bool initiateTunneledRequest(HttpRequest::Pointer const &cause, Http::MethodType const method, const char *reason, const SBuf &payload);
+
+    /// whether tunneling of unsupported protocol is allowed for this connection
+    bool mayTunnelUnsupportedProto();
+
+    /// build a fake http request
+    ClientHttpRequest *buildFakeRequest(Http::MethodType const method, SBuf &useHost, unsigned short usePort, const SBuf &payload);
+
     /// client data which may need to forward as-is to server after an
     /// on_unsupported_protocol tunnel decision.
     SBuf preservedClientData;
 
     /* Registered Runner API */
     virtual void startShutdown();
     virtual void endingShutdown();
 
 protected:
     void startDechunkingRequest();
     void finishDechunkingRequest(bool withSuccess);
     void abortChunkedRequestBody(const err_type error);
     err_type handleChunkedRequestBody();
 
     void startPinnedConnectionMonitoring();
     void clientPinnedConnectionRead(const CommIoCbParams &io);
 #if USE_OPENSSL
     /// Handles a ready-for-reading TLS squid-to-server connection that
     /// we thought was idle.
     /// \return false if and only if the connection should be closed.
     bool handleIdleClientPinnedTlsRead();
 #endif
 
     /// parse input buffer prefix into a single transfer protocol request
     /// return NULL to request more header bytes (after checking any limits)
     /// use abortRequestParsing() to handle parsing errors w/o creating request
     virtual Http::Stream *parseOneRequest() = 0;
 
     /// start processing a freshly parsed request
-    virtual void processParsedRequest(Http::Stream *) = 0;
+    virtual void processParsedRequest(Http::StreamPointer &) = 0;
 
     /// returning N allows a pipeline of 1+N requests (see pipeline_prefetch)
     virtual int pipelinePrefetchMax() const;
 
     /// timeout to use when waiting for the next request
     virtual time_t idleTimeout() const = 0;
 
     BodyPipe::Pointer bodyPipe; ///< set when we are reading request body
 
 private:
     /* ::Server API */
     virtual bool connFinishedWithConn(int size);
     virtual void checkLogging();
 
     void clientAfterReadingRequests();
     bool concurrentRequestQueueFilled() const;
 
     void pinNewConnection(const Comm::ConnectionPointer &pinServer, HttpRequest *request, CachePeer *aPeer, bool auth);
 
     /* PROXY protocol functionality */

=== modified file 'src/client_side_request.cc'
--- src/client_side_request.cc	2016-10-06 22:05:50 +0000
+++ src/client_side_request.cc	2016-10-13 15:08:07 +0000
@@ -1395,40 +1395,45 @@
     calloutContext->checkNoCacheDone(answer);
 }
 
 void
 ClientRequestContext::checkNoCacheDone(const allow_t &answer)
 {
     acl_checklist = NULL;
     http->request->flags.cachable = (answer == ACCESS_ALLOWED);
     http->doCallouts();
 }
 
 #if USE_OPENSSL
 bool
 ClientRequestContext::sslBumpAccessCheck()
 {
     if (!http->getConn()) {
         http->al->ssl.bumpMode = Ssl::bumpEnd; // SslBump does not apply; log -
         return false;
     }
 
+    if (http->request->flags.forceTunnel) {
+        debugs(85, 5, "not needed; already decided to tunnel " << http->getConn());
+        return false;
+    }
+
     // If SSL connection tunneling or bumping decision has been made, obey it.
     const Ssl::BumpMode bumpMode = http->getConn()->sslBumpMode;
     if (bumpMode != Ssl::bumpEnd) {
         debugs(85, 5, HERE << "SslBump already decided (" << bumpMode <<
                "), " << "ignoring ssl_bump for " << http->getConn());
         if (!http->getConn()->serverBump())
             http->sslBumpNeed(bumpMode); // for processRequest() to bump if needed and not already bumped
         http->al->ssl.bumpMode = bumpMode; // inherited from bumped connection
         return false;
     }
 
     // If we have not decided yet, decide whether to bump now.
 
     // Bumping here can only start with a CONNECT request on a bumping port
     // (bumping of intercepted SSL conns is decided before we get 1st request).
     // We also do not bump redirected CONNECT requests.
     if (http->request->method != Http::METHOD_CONNECT || http->redirect.status ||
             !Config.accessList.ssl_bump ||
             !http->getConn()->port->flags.tunnelSslBumping) {
         http->al->ssl.bumpMode = Ssl::bumpEnd; // SslBump does not apply; log -
@@ -1473,47 +1478,51 @@
 
     const Ssl::BumpMode bumpMode = answer == ACCESS_ALLOWED ?
                                    static_cast<Ssl::BumpMode>(answer.kind) : Ssl::bumpNone;
     http->sslBumpNeed(bumpMode); // for processRequest() to bump if needed
     http->al->ssl.bumpMode = bumpMode; // for logging
 
     http->doCallouts();
 }
 #endif
 
 /*
  * Identify requests that do not go through the store and client side stream
  * and forward them to the appropriate location. All other requests, request
  * them.
  */
 void
 ClientHttpRequest::processRequest()
 {
     debugs(85, 4, request->method << ' ' << uri);
 
-    if (request->method == Http::METHOD_CONNECT && !redirect.status) {
+    const bool untouchedConnect = request->method == Http::METHOD_CONNECT && !redirect.status;
+
 #if USE_OPENSSL
-        if (sslBumpNeeded()) {
-            sslBumpStart();
-            return;
-        }
+    if (untouchedConnect && sslBumpNeeded()) {
+        assert(!request->flags.forceTunnel);
+        sslBumpStart();
+        return;
+    }
 #endif
+
+    if (untouchedConnect || request->flags.forceTunnel) {
         getConn()->stopReading(); // tunnels read for themselves
         tunnelStart(this);
         return;
     }
 
     httpStart();
 }
 
 void
 ClientHttpRequest::httpStart()
 {
     PROF_start(httpStart);
     logType = LOG_TAG_NONE;
     debugs(85, 4, logType.c_str() << " for '" << uri << "'");
 
     /* no one should have touched this */
     assert(out.offset == 0);
     /* Use the Stream Luke */
     clientStreamNode *node = (clientStreamNode *)client_stream.tail->data;
     clientStreamRead(node, this, node->readBuffer);
@@ -1778,41 +1787,41 @@
     // We need to check for SslBump even if the calloutContext->error is set
     // because bumping may require delaying the error until after CONNECT.
     if (!calloutContext->sslBumpCheckDone) {
         calloutContext->sslBumpCheckDone = true;
         if (calloutContext->sslBumpAccessCheck())
             return;
         /* else no ssl bump required*/
     }
 #endif
 
     if (calloutContext->error) {
         // XXX: prformance regression. c_str() reallocates
         SBuf storeUriBuf(request->storeId());
         const char *storeUri = storeUriBuf.c_str();
         StoreEntry *e = storeCreateEntry(storeUri, storeUri, request->flags, request->method);
 #if USE_OPENSSL
         if (sslBumpNeeded()) {
             // We have to serve an error, so bump the client first.
             sslBumpNeed(Ssl::bumpClientFirst);
             // set final error but delay sending until we bump
-            Ssl::ServerBump *srvBump = new Ssl::ServerBump(request, e);
+            Ssl::ServerBump *srvBump = new Ssl::ServerBump(request, e, Ssl::bumpClientFirst);
             errorAppendEntry(e, calloutContext->error);
             calloutContext->error = NULL;
             getConn()->setServerBump(srvBump);
             e->unlock("ClientHttpRequest::doCallouts+sslBumpNeeded");
         } else
 #endif
         {
             // send the error to the client now
             clientStreamNode *node = (clientStreamNode *)client_stream.tail->prev->data;
             clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
             assert (repContext);
             repContext->setReplyToStoreEntry(e, "immediate SslBump error");
             errorAppendEntry(e, calloutContext->error);
             calloutContext->error = NULL;
             if (calloutContext->readNextRequest && getConn())
                 getConn()->flags.readMore = true; // resume any pipeline reads.
             node = (clientStreamNode *)client_stream.tail->data;
             clientStreamRead(node, this, node->readBuffer);
             e->unlock("ClientHttpRequest::doCallouts-sslBumpNeeded");
             return;

=== modified file 'src/http/Stream.h'
--- src/http/Stream.h	2016-07-02 06:47:55 +0000
+++ src/http/Stream.h	2016-10-13 16:48:30 +0000
@@ -58,40 +58,43 @@
  *
  *
  * XXX: If an async call ends the ClientHttpRequest job, Http::Stream
  * (and ConnStateData) may not know about it, leading to segfaults and
  * assertions. This is difficult to fix
  * because ClientHttpRequest lacks a good way to communicate its ongoing
  * destruction back to the Http::Stream which pretends to "own" *http.
  */
 class Stream : public RefCountable
 {
     MEMPROXY_CLASS(Stream);
 
 public:
     /// construct with HTTP/1.x details
     Stream(const Comm::ConnectionPointer &aConn, ClientHttpRequest *aReq);
     ~Stream();
 
     /// register this stream with the Server
     void registerWithConn();
 
+    /// whether it is registered with a Server
+    bool connRegistered() const {return connRegistered_;};
+
     /// whether the reply has started being sent
     bool startOfOutput() const;
 
     /// update stream state after a write, may initiate more I/O
     void writeComplete(size_t size);
 
     /// get more data to send
     void pullData();
 
     /// \return true if the HTTP request is for multiple ranges
     bool multipartRangeRequest() const;
 
     int64_t getNextRangeOffset() const;
     bool canPackMoreRanges() const;
     size_t lengthToSend(Range<int64_t> const &available) const;
 
     clientStream_status_t socketState();
 
     /// send an HTTP reply message headers and maybe some initial payload
     void sendStartOfMessage(HttpReply *, StoreIOBuffer bodyData);

=== modified file 'src/http/one/RequestParser.cc'
--- src/http/one/RequestParser.cc	2016-10-04 14:25:15 +0000
+++ src/http/one/RequestParser.cc	2016-10-13 15:08:07 +0000
@@ -3,42 +3,43 @@
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #include "squid.h"
 #include "Debug.h"
 #include "http/one/RequestParser.h"
 #include "http/one/Tokenizer.h"
 #include "http/ProtocolVersion.h"
 #include "profiler/Profiler.h"
 #include "SquidConfig.h"
 
 // the right debugs() level for parsing errors
 inline static int
 ErrorLevel() {
     return Config.onoff.relaxed_header_parser < 0 ? DBG_IMPORTANT : 5;
 }
 
-Http::One::RequestParser::RequestParser() :
-    Parser()
+Http::One::RequestParser::RequestParser(bool preserveParsed) :
+    Parser(),
+    preserveParsed_(preserveParsed)
 {}
 
 Http1::Parser::size_type
 Http::One::RequestParser::firstLineSize() const
 {
     // RFC 7230 section 2.6
     /* method SP request-target SP "HTTP/" DIGIT "." DIGIT CRLF */
     return method_.image().length() + uri_.length() + 12;
 }
 
 /**
  * Attempt to parse the first line of a new request message.
  *
  * Governed by RFC 7230 section 3.5
  *  "
  *    In the interest of robustness, a server that is expecting to receive
  *    and parse a request-line SHOULD ignore at least one empty line (CRLF)
  *    received prior to the request-line.
  *  "
  *
@@ -330,40 +331,53 @@
 
     /* parsed everything before and after the URI */
 
     if (!parseUriField(tok))
         return -1;
 
     if (!tok.atEnd()) {
         debugs(33, ErrorLevel(), "invalid request-line: garbage after URI");
         parseStatusCode = Http::scBadRequest;
         return -1;
     }
 
     parseStatusCode = Http::scOkay;
     buf_ = lineTok.remaining(); // incremental parse checkpoint
     return 1;
 }
 
 bool
 Http::One::RequestParser::parse(const SBuf &aBuf)
 {
+    const bool result = doParse(aBuf);
+    if (preserveParsed_) {
+        assert(aBuf.length() >= remaining().length());
+        parsed_.append(aBuf.substr(0, aBuf.length() - remaining().length())); // newly parsed bytes
+    }
+
+    return result;
+}
+
+// raw is not a reference because a reference might point back to our own buf_ or parsed_
+bool
+Http::One::RequestParser::doParse(const SBuf &aBuf)
+{
     buf_ = aBuf;
     debugs(74, DBG_DATA, "Parse buf={length=" << aBuf.length() << ", data='" << aBuf << "'}");
 
     // stage 1: locate the request-line
     if (parsingStage_ == HTTP_PARSE_NONE) {
         skipGarbageLines();
 
         // if we hit something before EOS treat it as a message
         if (!buf_.isEmpty())
             parsingStage_ = HTTP_PARSE_FIRST;
         else
             return false;
     }
 
     // stage 2: parse the request-line
     if (parsingStage_ == HTTP_PARSE_FIRST) {
         PROF_start(HttpParserParseReqLine);
         const int retcode = parseRequestFirstLine();
 
         // first-line (or a look-alike) found successfully.

=== modified file 'src/http/one/RequestParser.h'
--- src/http/one/RequestParser.h	2016-08-25 17:26:02 +0000
+++ src/http/one/RequestParser.h	2016-10-13 14:32:32 +0000
@@ -13,60 +13,70 @@
 #include "http/RequestMethod.h"
 
 namespace Parser {
 class Tokenizer;
 }
 
 namespace Http {
 namespace One {
 
 /** HTTP/1.x protocol request parser
  *
  * Works on a raw character I/O buffer and tokenizes the content into
  * the major CRLF delimited segments of an HTTP/1 request message:
  *
  * \item request-line (method, URL, protocol, version)
  * \item mime-header (set of RFC2616 syntax header fields)
  */
 class RequestParser : public Http1::Parser
 {
 public:
-    RequestParser();
+    explicit RequestParser(bool preserveParsed = false);
     virtual ~RequestParser() {}
 
     /* Http::One::Parser API */
     virtual void clear() {*this = RequestParser();}
     virtual Http1::Parser::size_type firstLineSize() const;
     virtual bool parse(const SBuf &aBuf);
 
     /// the HTTP method if this is a request message
     const HttpRequestMethod & method() const {return method_;}
 
     /// the request-line URI if this is a request message, or an empty string.
     const SBuf &requestUri() const {return uri_;}
 
+    /// the accumulated parsed bytes
+    const SBuf &parsed() const { Must(preserveParsed_); return parsed_; }
+
 private:
     void skipGarbageLines();
     int parseRequestFirstLine();
+    /// called from parse() to do the parsing
+    bool doParse(const SBuf &aBuf);
 
     /* all these return false and set parseStatusCode on parsing failures */
     bool parseMethodField(Http1::Tokenizer &);
     bool parseUriField(Http1::Tokenizer &);
     bool parseHttpVersionField(Http1::Tokenizer &);
     bool skipDelimiter(const size_t count, const char *where);
     bool skipTrailingCrs(Http1::Tokenizer &tok);
 
     bool http0() const {return !msgProtocol_.major;}
     static const CharacterSet &RequestTargetCharacters();
 
     /// what request method has been found on the first line
     HttpRequestMethod method_;
 
     /// raw copy of the original client request-line URI field
     SBuf uri_;
+
+    /// all parsed bytes (i.e., input prefix consumed by parse() calls)
+    /// meaningless unless preserveParsed_ is true
+    SBuf parsed_;
+    bool preserveParsed_; ///< whether to accumulate parsed bytes (in parsed_)
 };
 
 } // namespace One
 } // namespace Http
 
 #endif /*  _SQUID_SRC_HTTP_ONE_REQUESTPARSER_H */
 

=== modified file 'src/servers/FtpServer.cc'
--- src/servers/FtpServer.cc	2016-07-02 06:47:55 +0000
+++ src/servers/FtpServer.cc	2016-10-13 09:21:57 +0000
@@ -135,41 +135,41 @@
     assert(http != NULL);
 
     HttpRequest *const request = http->request;
     Must(http->storeEntry() || request);
     const bool mayForward = !http->storeEntry() && handleRequest(request);
 
     if (http->storeEntry() != NULL) {
         debugs(33, 4, "got an immediate response");
         clientSetKeepaliveFlag(http);
         context->pullData();
     } else if (mayForward) {
         debugs(33, 4, "forwarding request to server side");
         assert(http->storeEntry() == NULL);
         clientProcessRequest(this, Http1::RequestParserPointer(), context.getRaw());
     } else {
         debugs(33, 4, "will resume processing later");
     }
 }
 
 void
-Ftp::Server::processParsedRequest(Http::Stream *)
+Ftp::Server::processParsedRequest(Http::StreamPointer &)
 {
     Must(pipeline.count() == 1);
 
     // Process FTP request asynchronously to make sure FTP
     // data connection accept callback is fired first.
     CallJobHere(33, 4, CbcPointer<Server>(this),
                 Ftp::Server, doProcessRequest);
 }
 
 /// imports more upload data from the data connection
 void
 Ftp::Server::readUploadData(const CommIoCbParams &io)
 {
     debugs(33, 5, io.conn << " size " << io.size);
     Must(reader != NULL);
     reader = NULL;
 
     assert(Comm::IsConnOpen(dataConn));
     assert(io.conn->fd == dataConn->fd);
 

=== modified file 'src/servers/FtpServer.h'
--- src/servers/FtpServer.h	2016-03-15 18:12:09 +0000
+++ src/servers/FtpServer.h	2016-10-13 07:58:08 +0000
@@ -75,41 +75,41 @@
     // This is a pointer in hope to minimize future changes when MasterState
     // becomes a part of MasterXaction. Guaranteed not to be nil.
     MasterState::Pointer master; ///< info shared among our FTP client and server jobs
 
 protected:
     friend void StartListening();
 
     // errors detected before it is possible to create an HTTP request wrapper
     enum class EarlyErrorKind {
         HugeRequest,
         MissingLogin,
         MissingUsername,
         MissingHost,
         UnsupportedCommand,
         InvalidUri,
         MalformedCommand
     };
 
     /* ConnStateData API */
     virtual Http::Stream *parseOneRequest() override;
-    virtual void processParsedRequest(Http::Stream *context) override;
+    virtual void processParsedRequest(Http::StreamPointer &context) override;
     virtual void notePeerConnection(Comm::ConnectionPointer conn) override;
     virtual void clientPinnedConnectionClosed(const CommCloseCbParams &io) override;
     virtual void handleReply(HttpReply *header, StoreIOBuffer receivedData) override;
     virtual int pipelinePrefetchMax() const override;
     virtual void writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call) override;
     virtual time_t idleTimeout() const override;
 
     /* BodyPipe API */
     virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer) override;
     virtual void noteBodyConsumerAborted(BodyPipe::Pointer ptr) override;
 
     /* AsyncJob API */
     virtual void start() override;
 
     /* Comm callbacks */
     static void AcceptCtrlConnection(const CommAcceptCbParams &params);
     void acceptDataConnection(const CommAcceptCbParams &params);
     void readUploadData(const CommIoCbParams &io);
     void wroteEarlyReply(const CommIoCbParams &io);
     void wroteReply(const CommIoCbParams &io);

=== modified file 'src/servers/Http1Server.cc'
--- src/servers/Http1Server.cc	2016-05-02 06:09:13 +0000
+++ src/servers/Http1Server.cc	2016-10-13 16:49:41 +0000
@@ -63,158 +63,181 @@
 {
     if (!handleRequestBodyData())
         return;
 
     // too late to read more body
     if (!isOpen() || stoppedReceiving())
         return;
 
     readSomeData();
 }
 
 Http::Stream *
 Http::One::Server::parseOneRequest()
 {
     PROF_start(HttpServer_parseOneRequest);
 
     // parser is incremental. Generate new parser state if we,
     // a) dont have one already
     // b) have completed the previous request parsing already
     if (!parser_ || !parser_->needsMoreData())
-        parser_ = new Http1::RequestParser();
+        parser_ = new Http1::RequestParser(mayTunnelUnsupportedProto());
 
     /* Process request */
     Http::Stream *context = parseHttpRequest(this, parser_);
 
     PROF_stop(HttpServer_parseOneRequest);
     return context;
 }
 
 void clientProcessRequestFinished(ConnStateData *conn, const HttpRequest::Pointer &request);
-bool clientTunnelOnError(ConnStateData *conn, Http::Stream *context, HttpRequest *request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes);
+bool clientTunnelOnError(ConnStateData *conn, Http::StreamPointer &context, HttpRequest::Pointer &request, const HttpRequestMethod& method, err_type requestError);
 
 bool
-Http::One::Server::buildHttpRequest(Http::Stream *context)
+Http::One::Server::buildHttpRequest(Http::StreamPointer &context)
 {
     HttpRequest::Pointer request;
     ClientHttpRequest *http = context->http;
     if (context->flags.parsed_ok == 0) {
         debugs(33, 2, "Invalid Request");
         // determine which error page templates to use for specific parsing errors
         err_type errPage = ERR_INVALID_REQ;
         switch (parser_->parseStatusCode) {
         case Http::scRequestHeaderFieldsTooLarge:
         // fall through to next case
         case Http::scUriTooLong:
             errPage = ERR_TOO_BIG;
             break;
         case Http::scMethodNotAllowed:
             errPage = ERR_UNSUP_REQ;
             break;
         case Http::scHttpVersionNotSupported:
             errPage = ERR_UNSUP_HTTPVERSION;
             break;
         default:
             if (parser_->method() == METHOD_NONE || parser_->requestUri().length() == 0)
                 // no method or url parsed, probably is wrong protocol
                 errPage = ERR_PROTOCOL_UNKNOWN;
             // else use default ERR_INVALID_REQ set above.
             break;
         }
         // setLogUri should called before repContext->setReplyToError
         setLogUri(http, http->uri, true);
         const char * requestErrorBytes = inBuf.c_str();
-        if (!clientTunnelOnError(this, context, request.getRaw(), parser_->method(), errPage, parser_->parseStatusCode, requestErrorBytes)) {
+        if (!clientTunnelOnError(this, context, request, parser_->method(), errPage)) {
+            setReplyError(context, request, parser_->method(), errPage, parser_->parseStatusCode, requestErrorBytes);
             // HttpRequest object not build yet, there is no reason to call
             // clientProcessRequestFinished method
         }
 
         return false;
     }
 
     if ((request = HttpRequest::CreateFromUrl(http->uri, parser_->method())) == NULL) {
         debugs(33, 5, "Invalid URL: " << http->uri);
         // setLogUri should called before repContext->setReplyToError
         setLogUri(http, http->uri, true);
 
         const char * requestErrorBytes = inBuf.c_str();
-        if (!clientTunnelOnError(this, context, request.getRaw(), parser_->method(), ERR_INVALID_URL, Http::scBadRequest, requestErrorBytes)) {
+        if (!clientTunnelOnError(this, context, request, parser_->method(), ERR_INVALID_URL)) {
+            setReplyError(context, request, parser_->method(), ERR_INVALID_URL, Http::scBadRequest, requestErrorBytes);
             // HttpRequest object not build yet, there is no reason to call
             // clientProcessRequestFinished method
         }
         return false;
     }
 
     /* RFC 2616 section 10.5.6 : handle unsupported HTTP major versions cleanly. */
     /* We currently only support 0.9, 1.0, 1.1 properly */
     /* TODO: move HTTP-specific processing into servers/HttpServer and such */
     if ( (parser_->messageProtocol().major == 0 && parser_->messageProtocol().minor != 9) ||
             (parser_->messageProtocol().major > 1) ) {
 
         debugs(33, 5, "Unsupported HTTP version discovered. :\n" << parser_->messageProtocol());
         // setLogUri should called before repContext->setReplyToError
         setLogUri(http, http->uri,  true);
 
         const char * requestErrorBytes = NULL; //HttpParserHdrBuf(parser_);
-        if (!clientTunnelOnError(this, context, request.getRaw(), parser_->method(), ERR_UNSUP_HTTPVERSION, Http::scHttpVersionNotSupported, requestErrorBytes)) {
+        if (!clientTunnelOnError(this, context, request, parser_->method(), ERR_UNSUP_HTTPVERSION)) {
+            setReplyError(context, request, parser_->method(), ERR_UNSUP_HTTPVERSION, Http::scHttpVersionNotSupported, requestErrorBytes);
             clientProcessRequestFinished(this, request);
         }
         return false;
     }
 
     /* compile headers */
     if (parser_->messageProtocol().major >= 1 && !request->parseHeader(*parser_.getRaw())) {
         debugs(33, 5, "Failed to parse request headers:\n" << parser_->mimeHeader());
         // setLogUri should called before repContext->setReplyToError
         setLogUri(http, http->uri, true);
         const char * requestErrorBytes = NULL; //HttpParserHdrBuf(parser_);
-        if (!clientTunnelOnError(this, context, request.getRaw(), parser_->method(), ERR_INVALID_REQ, Http::scBadRequest, requestErrorBytes)) {
+        if (!clientTunnelOnError(this, context, request, parser_->method(), ERR_INVALID_REQ)) {
+            setReplyError(context, request, parser_->method(), ERR_INVALID_REQ, Http::scBadRequest, requestErrorBytes);
             clientProcessRequestFinished(this, request);
         }
         return false;
     }
 
     // when absolute-URI is provided Host header should be ignored. However
     // some code still uses Host directly so normalize it using the previously
     // sanitized URL authority value.
     // For now preserve the case where Host is completely absent. That matters.
     if (const auto x = request->header.delById(Http::HOST)) {
         debugs(33, 5, "normalize " << x << " Host header using " << request->url.authority());
         SBuf tmp(request->url.authority());
         request->header.putStr(Http::HOST, tmp.c_str());
     }
 
     http->request = request.getRaw();
     HTTPMSGLOCK(http->request);
 
     return true;
 }
 
 void
+Http::One::Server::setReplyError(Http::StreamPointer &context, HttpRequest::Pointer &request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes)
+{
+        quitAfterError(request.getRaw());
+        if (!context->connRegistered()) {
+            debugs(33, 2, "Client stream deregister it self, nothing to do");
+            clientConnection->close();
+            return;
+        }
+        clientStreamNode *node = context->getClientReplyContext();
+        clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
+        assert (repContext);
+
+        repContext->setReplyToError(requestError, errStatusCode, method, context->http->uri, clientConnection->remote, NULL, requestErrorBytes, NULL);
+
+        assert(context->http->out.offset == 0);
+        context->pullData();
+}
+
+void
 Http::One::Server::proceedAfterBodyContinuation(Http::StreamPointer context)
 {
     debugs(33, 5, "Body Continuation written");
     clientProcessRequest(this, parser_, context.getRaw());
 }
 
 void
-Http::One::Server::processParsedRequest(Http::Stream *context)
+Http::One::Server::processParsedRequest(Http::StreamPointer &context)
 {
     if (!buildHttpRequest(context))
         return;
 
     ClientHttpRequest *http = context->http;
     HttpRequest::Pointer request = http->request;
 
     if (request->header.has(Http::HdrType::EXPECT)) {
         const String expect = request->header.getList(Http::HdrType::EXPECT);
         const bool supportedExpect = (expect.caseCmp("100-continue") == 0);
         if (!supportedExpect) {
             clientStreamNode *node = context->getClientReplyContext();
             quitAfterError(request.getRaw());
             // setLogUri should called before repContext->setReplyToError
             setLogUri(http, urlCanonicalClean(request.getRaw()));
             clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
             assert (repContext);
             repContext->setReplyToError(ERR_INVALID_REQ, Http::scExpectationFailed, request->method, http->uri,
                                         clientConnection->remote, request.getRaw(), NULL, NULL);
             assert(context->http->out.offset == 0);
@@ -222,41 +245,41 @@
             clientProcessRequestFinished(this, request);
             return;
         }
 
         if (Config.accessList.forceRequestBodyContinuation) {
             ACLFilledChecklist bodyContinuationCheck(Config.accessList.forceRequestBodyContinuation, request.getRaw(), NULL);
             if (bodyContinuationCheck.fastCheck() == ACCESS_ALLOWED) {
                 debugs(33, 5, "Body Continuation forced");
                 request->forcedBodyContinuation = true;
                 //sendControlMsg
                 HttpReply::Pointer rep = new HttpReply;
                 rep->sline.set(Http::ProtocolVersion(), Http::scContinue);
 
                 typedef UnaryMemFunT<Http1::Server, Http::StreamPointer> CbDialer;
                 const AsyncCall::Pointer cb = asyncCall(11, 3,  "Http1::Server::proceedAfterBodyContinuation", CbDialer(this, &Http1::Server::proceedAfterBodyContinuation, Http::StreamPointer(context)));
                 sendControlMsg(HttpControlMsg(rep, cb));
                 return;
             }
         }
     }
-    clientProcessRequest(this, parser_, context);
+    clientProcessRequest(this, parser_, context.getRaw());
 }
 
 void
 Http::One::Server::noteBodyConsumerAborted(BodyPipe::Pointer ptr)
 {
     ConnStateData::noteBodyConsumerAborted(ptr);
     stopReceiving("virgin request body consumer aborted"); // closes ASAP
 }
 
 void
 Http::One::Server::handleReply(HttpReply *rep, StoreIOBuffer receivedData)
 {
     // the caller guarantees that we are dealing with the current context only
     Http::StreamPointer context = pipeline.front();
     Must(context != nullptr);
     const ClientHttpRequest *http = context->http;
     Must(http != NULL);
 
     // After sending Transfer-Encoding: chunked (at least), always send
     // the last-chunk if there was no error, ignoring responseFinishedOrFailed.

=== modified file 'src/servers/Http1Server.h'
--- src/servers/Http1Server.h	2016-01-24 17:41:43 +0000
+++ src/servers/Http1Server.h	2016-10-13 07:56:02 +0000
@@ -13,56 +13,58 @@
 
 namespace Http
 {
 namespace One
 {
 
 /// Manages a connection from an HTTP/1 or HTTP/0.9 client.
 class Server: public ConnStateData
 {
     CBDATA_CLASS(Server);
 
 public:
     Server(const MasterXaction::Pointer &xact, const bool beHttpsServer);
     virtual ~Server() {}
 
     void readSomeHttpData();
 
 protected:
     /* ConnStateData API */
     virtual Http::Stream *parseOneRequest();
-    virtual void processParsedRequest(Http::Stream *context);
+    virtual void processParsedRequest(Http::StreamPointer &context);
     virtual void handleReply(HttpReply *rep, StoreIOBuffer receivedData);
     virtual void writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call);
     virtual time_t idleTimeout() const;
 
     /* BodyPipe API */
     virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer);
     virtual void noteBodyConsumerAborted(BodyPipe::Pointer);
 
     /* AsyncJob API */
     virtual void start();
 
     void proceedAfterBodyContinuation(Http::StreamPointer context);
 
 private:
     void processHttpRequest(Http::Stream *const context);
     void handleHttpRequestData();
 
     /// Handles parsing results. May generate and deliver an error reply
     /// to the client if parsing is failed, or parses the url and build the
     /// HttpRequest object using parsing results.
     /// Return false if parsing is failed, true otherwise.
-    bool buildHttpRequest(Http::Stream *context);
+    bool buildHttpRequest(Http::StreamPointer &context);
+
+    void setReplyError(Http::StreamPointer &context, HttpRequest::Pointer &request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes);
 
     Http1::RequestParserPointer parser_;
     HttpRequestMethod method_; ///< parsed HTTP method
 
     /// temporary hack to avoid creating a true HttpsServer class
     const bool isHttpsServer;
 };
 
 } // namespace One
 } // namespace Http
 
 #endif /* SQUID_SRC_SERVERS_HTTP1SERVER_H */
 

=== modified file 'src/tunnel.cc'
--- src/tunnel.cc	2016-08-17 14:24:07 +0000
+++ src/tunnel.cc	2016-10-13 14:10:39 +0000
@@ -51,77 +51,81 @@
 #include <cerrno>
 
 /**
  * TunnelStateData is the state engine performing the tasks for
  * setup of a TCP tunnel from an existing open client FD to a server
  * then shuffling binary data between the resulting FD pair.
  */
 /*
  * TODO 1: implement a read/write API on ConnStateData to send/receive blocks
  * of pre-formatted data. Then we can use that as the client side of the tunnel
  * instead of re-implementing it here and occasionally getting the ConnStateData
  * read/write state wrong.
  *
  * TODO 2: then convert this into a AsyncJob, possibly a child of 'Server'
  */
 class TunnelStateData
 {
     CBDATA_CLASS(TunnelStateData);
 
 public:
-    TunnelStateData();
+    TunnelStateData(ClientHttpRequest *);
     ~TunnelStateData();
     TunnelStateData(const TunnelStateData &); // do not implement
     TunnelStateData &operator =(const TunnelStateData &); // do not implement
 
     class Connection;
     static void ReadClient(const Comm::ConnectionPointer &, char *buf, size_t len, Comm::Flag errcode, int xerrno, void *data);
     static void ReadServer(const Comm::ConnectionPointer &, char *buf, size_t len, Comm::Flag errcode, int xerrno, void *data);
     static void WriteClientDone(const Comm::ConnectionPointer &, char *buf, size_t len, Comm::Flag flag, int xerrno, void *data);
     static void WriteServerDone(const Comm::ConnectionPointer &, char *buf, size_t len, Comm::Flag flag, int xerrno, void *data);
 
     /// Starts reading peer response to our CONNECT request.
     void readConnectResponse();
 
     /// Called when we may be done handling a CONNECT exchange with the peer.
     void connectExchangeCheckpoint();
 
     bool noConnections() const;
     char *url;
     CbcPointer<ClientHttpRequest> http;
     HttpRequest::Pointer request;
     AccessLogEntryPointer al;
     Comm::ConnectionList serverDestinations;
 
     const char * getHost() const {
         return (server.conn != NULL && server.conn->getPeer() ? server.conn->getPeer()->host : request->url.host());
     };
 
     /// Whether we are writing a CONNECT request to a peer.
     bool waitingForConnectRequest() const { return connectReqWriting; }
     /// Whether we are reading a CONNECT response from a peer.
     bool waitingForConnectResponse() const { return connectRespBuf; }
     /// Whether we are waiting for the CONNECT request/response exchange with the peer.
     bool waitingForConnectExchange() const { return waitingForConnectRequest() || waitingForConnectResponse(); }
 
     /// Whether the client sent a CONNECT request to us.
     bool clientExpectsConnectResponse() const {
+        // If we are forcing a tunnel after receiving a client CONNECT, then we 
+        // have already responded to that CONNECT before tunnel.cc started.
+        if (request && request->flags.forceTunnel)
+            return false;
 #if USE_OPENSSL
         // We are bumping and we had already send "OK CONNECTED"
         if (http.valid() && http->getConn() && http->getConn()->serverBump() && http->getConn()->serverBump()->step > Ssl::bumpStep1)
             return false;
 #endif
         return !(request != NULL &&
                  (request->flags.interceptTproxy || request->flags.intercepted));
     }
 
     /// Sends "502 Bad Gateway" error response to the client,
     /// if it is waiting for Squid CONNECT response, closing connections.
     void informUserOfPeerError(const char *errMsg, size_t);
 
     class Connection
     {
 
     public:
         Connection() : len (0), buf ((char *)xmalloc(SQUID_TCP_SO_RCVBUF)), size_ptr(NULL), delayedLoops(0),
             readPending(NULL), readPendingFunc(NULL) {}
 
@@ -268,53 +272,68 @@
     tunnelState->client.writer = NULL;
 
     if (tunnelState->noConnections()) {
         // ConnStateData pipeline should contain the CONNECT we are performing
         // but it may be invalid already (bug 4392)
         if (tunnelState->http.valid() && tunnelState->http->getConn()) {
             auto ctx = tunnelState->http->getConn()->pipeline.front();
             if (ctx != nullptr)
                 ctx->finished();
         }
         delete tunnelState;
         return;
     }
 
     if (!tunnelState->server.writer) {
         tunnelState->server.conn->close();
         return;
     }
 }
 
-TunnelStateData::TunnelStateData() :
-    url(NULL),
-    http(),
-    request(NULL),
-    status_ptr(NULL),
-    logTag_ptr(NULL),
+TunnelStateData::TunnelStateData(ClientHttpRequest *clientRequest) :
     connectRespBuf(NULL),
     connectReqWriting(false),
     started(squid_curtime)
 {
     debugs(26, 3, "TunnelStateData constructed this=" << this);
     client.readPendingFunc = &tunnelDelayedClientRead;
     server.readPendingFunc = &tunnelDelayedServerRead;
+
+    assert(clientRequest);
+    url = xstrdup(clientRequest->uri);
+    request = clientRequest->request;
+    server.size_ptr = &clientRequest->out.size;
+    client.size_ptr = &clientRequest->al->http.clientRequestSz.payloadData;
+    status_ptr = &clientRequest->al->http.code;
+    logTag_ptr = &clientRequest->logType;
+    al = clientRequest->al;
+    http = clientRequest;
+
+    client.conn = clientRequest->getConn()->clientConnection;
+    comm_add_close_handler(client.conn->fd, tunnelClientClosed, this);
+
+    AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
+                                     CommTimeoutCbPtrFun(tunnelTimeout, this));
+    commSetConnTimeout(client.conn, Config.Timeout.lifetime, timeoutCall);
+
+    // TODO: check if should removed:
+    clientRequest->getConn()->flags.tunneling = true;
 }
 
 TunnelStateData::~TunnelStateData()
 {
     debugs(26, 3, "TunnelStateData destructed this=" << this);
     assert(noConnections());
     xfree(url);
     serverDestinations.clear();
     delete connectRespBuf;
 }
 
 TunnelStateData::Connection::~Connection()
 {
     if (readPending)
         eventDelete(readPendingFunc, readPending);
 
     safe_free(buf);
 }
 
 int
@@ -1058,62 +1077,44 @@
         /*
          * Check if this host is allowed to fetch MISSES from us (miss_access)
          * default is to allow.
          */
         ACLFilledChecklist ch(Config.accessList.miss, request, NULL);
         ch.src_addr = request->client_addr;
         ch.my_addr = request->my_addr;
         if (ch.fastCheck() == ACCESS_DENIED) {
             debugs(26, 4, HERE << "MISS access forbidden.");
             err = new ErrorState(ERR_FORWARDING_DENIED, Http::scForbidden, request);
             http->al->http.code = Http::scForbidden;
             errorSend(http->getConn()->clientConnection, err);
             return;
         }
     }
 
     debugs(26, 3, request->method << ' ' << url << ' ' << request->http_ver);
     ++statCounter.server.all.requests;
     ++statCounter.server.other.requests;
 
-    tunnelState = new TunnelStateData;
+    tunnelState = new TunnelStateData(http);
 #if USE_DELAY_POOLS
-    tunnelState->server.setDelayId(DelayId::DelayClient(http));
+    //server.setDelayId called from tunnelConnectDone after server side connection established
 #endif
-    tunnelState->url = xstrdup(url);
-    tunnelState->request = request;
-    tunnelState->server.size_ptr = &http->out.size;
-    tunnelState->client.size_ptr = &http->al->http.clientRequestSz.payloadData;
-    tunnelState->status_ptr = &http->al->http.code;
-    tunnelState->logTag_ptr = &http->logType;
-    tunnelState->client.conn = http->getConn()->clientConnection;
-    tunnelState->http = http;
-    tunnelState->al = http->al;
-    //tunnelState->started is set in TunnelStateData ctor
-
-    comm_add_close_handler(tunnelState->client.conn->fd,
-                           tunnelClientClosed,
-                           tunnelState);
-
-    AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
-                                     CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
-    commSetConnTimeout(tunnelState->client.conn, Config.Timeout.lifetime, timeoutCall);
 
     peerSelect(&(tunnelState->serverDestinations), request, http->al,
                NULL,
                tunnelPeerSelectComplete,
                tunnelState);
 }
 
 void
 TunnelStateData::connectToPeer()
 {
     if (CachePeer *p = server.conn->getPeer()) {
         if (p->secure.encryptTransport) {
             AsyncCall::Pointer callback = asyncCall(5,4,
                                                     "TunnelStateData::ConnectedToPeer",
                                                     MyAnswerDialer(&TunnelStateData::connectedToPeer, this));
             auto *connector = new Security::BlindPeerConnector(request, server.conn, callback, al);
             AsyncJob::Start(connector); // will call our callback
             return;
         }
     }
@@ -1166,47 +1167,74 @@
                                    CommIoCbPtrFun(tunnelConnectReqWriteDone,
                                            tunnelState));
 
     tunnelState->server.write(mb.buf, mb.size, writeCall, mb.freeFunc());
     tunnelState->connectReqWriting = true;
 
     tunnelState->connectRespBuf = new MemBuf;
     // SQUID_TCP_SO_RCVBUF: we should not accumulate more than regular I/O buffer
     // can hold since any CONNECT response leftovers have to fit into server.buf.
     // 2*SQUID_TCP_SO_RCVBUF: HttpMsg::parse() zero-terminates, which uses space.
     tunnelState->connectRespBuf->init(SQUID_TCP_SO_RCVBUF, 2*SQUID_TCP_SO_RCVBUF);
     tunnelState->readConnectResponse();
 
     assert(tunnelState->waitingForConnectExchange());
 
     AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
                                      CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
     commSetConnTimeout(srv, Config.Timeout.read, timeoutCall);
 }
 
+static Comm::ConnectionPointer
+borrowPinnedConnection(HttpRequest *request, Comm::ConnectionPointer &serverDestination)
+{
+    // pinned_connection may become nil after a pconn race
+    if (ConnStateData *pinned_connection = request ? request->pinnedConnection() : nullptr) {
+        Comm::ConnectionPointer serverConn = pinned_connection->borrowPinnedConnection(request, serverDestination->getPeer());
+        return serverConn;
+    }
+
+    return nullptr;
+}
+
 static void
 tunnelPeerSelectComplete(Comm::ConnectionList *peer_paths, ErrorState *err, void *data)
 {
     TunnelStateData *tunnelState = (TunnelStateData *)data;
 
+    bool bail = false;
     if (peer_paths == NULL || peer_paths->size() < 1) {
         debugs(26, 3, HERE << "No paths found. Aborting CONNECT");
+        bail = true;
+    }
+
+    if (!bail && tunnelState->serverDestinations[0]->peerType == PINNED) {
+        Comm::ConnectionPointer serverConn = borrowPinnedConnection(tunnelState->request.getRaw(), tunnelState->serverDestinations[0]);
+        debugs(26,7, "pinned peer connection: " << serverConn);
+        if (Comm::IsConnOpen(serverConn)) {
+            tunnelConnectDone(serverConn, Comm::OK, 0, (void *)tunnelState);
+            return;
+        }
+        bail = true;
+    }
+
+    if (bail) {
         if (!err) {
             err = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, tunnelState->request.getRaw());
         }
         *tunnelState->status_ptr = err->httpStatus;
         err->callback = tunnelErrorComplete;
         err->callback_data = tunnelState;
         errorSend(tunnelState->client.conn, err);
         return;
     }
     delete err;
 
     GetMarkingsToServer(tunnelState->request.getRaw(), *tunnelState->serverDestinations[0]);
 
     if (tunnelState->request != NULL)
         tunnelState->request->hier.startPeerClock();
 
     debugs(26, 3, HERE << "paths=" << peer_paths->size() << ", p[0]={" << (*peer_paths)[0] << "}, serverDest[0]={" <<
            tunnelState->serverDestinations[0] << "}");
 
     AsyncCall::Pointer call = commCbCall(26,3, "tunnelConnectDone", CommConnectCbPtrFun(tunnelConnectDone, tunnelState));
@@ -1220,96 +1248,78 @@
 bool
 TunnelStateData::noConnections() const
 {
     return !Comm::IsConnOpen(server.conn) && !Comm::IsConnOpen(client.conn);
 }
 
 #if USE_DELAY_POOLS
 void
 TunnelStateData::Connection::setDelayId(DelayId const &newDelay)
 {
     delayId = newDelay;
 }
 
 #endif
 
 #if USE_OPENSSL
 void
 switchToTunnel(HttpRequest *request, Comm::ConnectionPointer &clientConn, Comm::ConnectionPointer &srvConn)
 {
     debugs(26,5, "Revert to tunnel FD " << clientConn->fd << " with FD " << srvConn->fd);
-    /* Create state structure. */
-    const SBuf url(request->effectiveRequestUri());
 
-    debugs(26, 3, request->method << " " << url << " " << request->http_ver);
+    /* Create state structure. */
     ++statCounter.server.all.requests;
     ++statCounter.server.other.requests;
 
-    TunnelStateData *tunnelState = new TunnelStateData;
-    tunnelState->url = SBufToCstring(url);
-    tunnelState->request = request;
-    tunnelState->server.size_ptr = NULL; //Set later if Http::Stream is available
-
-    // Temporary static variable to store the unneeded for our case status code
-    static int status_code = 0;
-    tunnelState->status_ptr = &status_code;
-    tunnelState->client.conn = clientConn;
-
-    if (auto conn = request->clientConnectionManager.get()) {
-        Http::StreamPointer context = conn->pipeline.front();
-        if (context && context->http) {
-            tunnelState->logTag_ptr = &context->http->logType;
-            tunnelState->server.size_ptr = &context->http->out.size;
-            tunnelState->al = context->http->al;
+    auto conn = request->clientConnectionManager.get();
+    Must(conn);
+    Http::StreamPointer context = conn->pipeline.front();
+    Must(context && context->http);
 
-#if USE_DELAY_POOLS
-            /* no point using the delayIsNoDelay stuff since tunnel is nice and simple */
-            if (srvConn->getPeer() && srvConn->getPeer()->options.no_delay)
-                tunnelState->server.setDelayId(DelayId::DelayClient(context->http));
-#endif
-        }
-    }
+    debugs(26, 3, request->method << " " << context->http->uri << " " << request->http_ver);
 
-    comm_add_close_handler(tunnelState->client.conn->fd,
-                           tunnelClientClosed,
-                           tunnelState);
+    TunnelStateData *tunnelState = new TunnelStateData(context->http);
 
-    AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
-                                     CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
-    commSetConnTimeout(tunnelState->client.conn, Config.Timeout.lifetime, timeoutCall);
     fd_table[clientConn->fd].read_method = &default_read_method;
     fd_table[clientConn->fd].write_method = &default_write_method;
 
     request->hier.note(srvConn, tunnelState->getHost());
 
     tunnelState->server.conn = srvConn;
+
+#if USE_DELAY_POOLS
+    /* no point using the delayIsNoDelay stuff since tunnel is nice and simple */
+    if (srvConn->getPeer() && srvConn->getPeer()->options.no_delay)
+        tunnelState->server.setDelayId(DelayId::DelayClient(context->http));
+#endif
+
     request->peer_host = srvConn->getPeer() ? srvConn->getPeer()->host : nullptr;
     comm_add_close_handler(srvConn->fd, tunnelServerClosed, tunnelState);
 
     debugs(26, 4, "determine post-connect handling pathway.");
     if (srvConn->getPeer()) {
         request->peer_login = srvConn->getPeer()->login;
         request->peer_domain = srvConn->getPeer()->domain;
         request->flags.auth_no_keytab = srvConn->getPeer()->options.auth_no_keytab;
         request->flags.proxying = !(srvConn->getPeer()->options.originserver);
     } else {
         request->peer_login = nullptr;
         request->peer_domain = nullptr;
         request->flags.auth_no_keytab = false;
         request->flags.proxying = false;
     }
 
-    timeoutCall = commCbCall(5, 4, "tunnelTimeout",
-                             CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
+    AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
+                                     CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
     commSetConnTimeout(srvConn, Config.Timeout.read, timeoutCall);
     fd_table[srvConn->fd].read_method = &default_read_method;
     fd_table[srvConn->fd].write_method = &default_write_method;
 
     auto ssl = fd_table[srvConn->fd].ssl.get();
     assert(ssl);
     BIO *b = SSL_get_rbio(ssl);
     Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
     tunnelState->preReadServerData = srvBio->rBufData();
     tunnelStartShoveling(tunnelState);
 }
 #endif //USE_OPENSSL
 

_______________________________________________
squid-dev mailing list
squid-dev@lists.squid-cache.org
http://lists.squid-cache.org/listinfo/squid-dev

Reply via email to