Project description:
   - Squid receives SSL Hello from the client (TCP connection A).

- Squid successfully negotiates an SSL connection with the origin server (TCP connection B).

- Squid successfully negotiates an SSL connection with the client (TCP connection A).

- Squid marks connection B as "idle" and waits an HTTP request from connection A.

- The origin server continues talking to Squid (TCP connection B). Squid detects a network read on an idle connection and closes TCP connection B (and then the associated TCP connection A as well).

This patch:
- When squid detects a network read on server idle connection do an SSL_read to:
    a) see if application data received from server and abort in this case
    b) detect possible SSL error, or SSL shutdown message from server
    c) or ignore if only SSL protocol related packets received.

This is a Measurement Factory project
Handshake Problem during Renegotiation

Here is what happens:

   - Squid receives SSL Hello from the client (TCP connection A).
   - Squid successfully negotiates an SSL connection with the origin server
     (TCP connection B).
   - Squid successfully negotiates an SSL connection with the client
     (TCP connection A).
   - Squid marks connection B as "idle" and waits an HTTP request from
     connection A.
   - The origin server continues talking to Squid (TCP connection B).
     Squid detects a network read on an idle connection and closes TCP
     connection B (and then the associated TCP connection A as well).

This patch:
   - When squid detects a network read on server idle connection do an
     SSL_read to:
       a) see if application data received from server and abort in this case
       b) detect possible SSL error, or SSL shutdown message from server
       c) or ignore if only SSL protocol related packets received.

This is a Measurement Factory project

=== modified file 'src/client_side.cc'
--- src/client_side.cc	2015-09-14 18:02:04 +0000
+++ src/client_side.cc	2015-11-06 18:11:48 +0000
@@ -4864,57 +4864,103 @@
 ConnStateData::startPinnedConnectionMonitoring()
 {
     if (pinning.readHandler != NULL)
         return; // already monitoring
 
     typedef CommCbMemFunT<ConnStateData, CommIoCbParams> Dialer;
     pinning.readHandler = JobCallback(33, 3,
                                       Dialer, this, ConnStateData::clientPinnedConnectionRead);
     Comm::Read(pinning.serverConnection, pinning.readHandler);
 }
 
 void
 ConnStateData::stopPinnedConnectionMonitoring()
 {
     if (pinning.readHandler != NULL) {
         Comm::ReadCancel(pinning.serverConnection->fd, pinning.readHandler);
         pinning.readHandler = NULL;
     }
 }
 
+#if USE_OPENSSL
+bool
+ConnStateData::handleIdleClientPinnedSslRead()
+{
+    // A ready-for-reading connection means that the SSL server either closed
+    // the connection, sent us some unexpected HTTP data, or started SSL 
+    // renegotiations. We should close the connection except for the last case.
+
+    Must(pinning.serverConnection != NULL);
+    SSL *ssl = fd_table[pinning.serverConnection->fd].ssl;
+    if(!ssl)
+        return false;
+
+    char buf[1];
+    const int readResult = SSL_read(ssl, buf, sizeof(buf));
+
+    if (readResult > 0 || SSL_pending(ssl) > 0) {
+        debugs(83, 2, "SSL FD " << pinning.serverConnection->fd << " application data read");
+        return false;
+    }
+
+    switch(const int error = SSL_get_error(ssl, readResult)) {
+    case SSL_ERROR_WANT_WRITE:
+        debugs(83, DBG_IMPORTANT, "SSL FD " << pinning.serverConnection->fd << " SSL_ERROR_WANT_WRITE request for idle pinned connection");
+        // fall through to restart monitoring, for now
+    case SSL_ERROR_NONE:
+    case SSL_ERROR_WANT_READ:
+        startPinnedConnectionMonitoring();
+        return true;
+
+    default:
+        debugs(83, 2, "SSL FD " << pinning.serverConnection->fd << " error: " << error);
+        return false;
+    }
+
+    // not reached
+    return true;
+}
+#endif
+
 /// Our read handler called by Comm when the server either closes an idle pinned connection or
 /// perhaps unexpectedly sends something on that idle (from Squid p.o.v.) connection.
 void
 ConnStateData::clientPinnedConnectionRead(const CommIoCbParams &io)
 {
     pinning.readHandler = NULL; // Comm unregisters handlers before calling
 
     if (io.flag == Comm::ERR_CLOSING)
         return; // close handler will clean up
 
+    Must(pinning.serverConnection == io.conn);
+
+#if USE_OPENSSL
+    if (handleIdleClientPinnedSslRead())
+        return;
+#endif
+
     // We could use getConcurrentRequestCount(), but this may be faster.
     const bool clientIsIdle = !getCurrentContext();
 
     debugs(33, 3, "idle pinned " << pinning.serverConnection << " read " <<
            io.size << (clientIsIdle ? " with idle client" : ""));
 
-    assert(pinning.serverConnection == io.conn);
     pinning.serverConnection->close();
 
     // If we are still sending data to the client, do not close now. When we are done sending,
     // ClientSocketContext::keepaliveNextRequest() checks pinning.serverConnection and will close.
     // However, if we are idle, then we must close to inform the idle client and minimize races.
     if (clientIsIdle && clientConnection != NULL)
         clientConnection->close();
 }
 
 const Comm::ConnectionPointer
 ConnStateData::validatePinnedConnection(HttpRequest *request, const CachePeer *aPeer)
 {
     debugs(33, 7, HERE << pinning.serverConnection);
 
     bool valid = true;
     if (!Comm::IsConnOpen(pinning.serverConnection))
         valid = false;
     else if (pinning.auth && pinning.host && request && strcasecmp(pinning.host, request->url.host()) != 0)
         valid = false;
     else if (request && pinning.port != request->url.port())

=== modified file 'src/client_side.h'
--- src/client_side.h	2015-08-03 09:15:27 +0000
+++ src/client_side.h	2015-09-30 09:02:01 +0000
@@ -419,40 +419,46 @@
     /// generate a fake CONNECT request with the given payload
     /// at the beginning of the client I/O buffer
     void fakeAConnectRequest(const char *reason, 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 SSL squid-to-server connection that
+    /// we thought was idle.
+    /// Returns false if and only if the connection should be closed.
+    bool handleIdleClientPinnedSslRead();
+#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 ClientSocketContext *parseOneRequest() = 0;
 
     /// start processing a freshly parsed request
     virtual void processParsedRequest(ClientSocketContext *context) = 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:
     int connFinishedWithConn(int size);
     void clientAfterReadingRequests();

_______________________________________________
squid-dev mailing list
[email protected]
http://lists.squid-cache.org/listinfo/squid-dev

Reply via email to