We are quickly approaching a time when a client connection can freely
migrate between protocols or versions of protocols. Already we have
ssl-bump which can switch a connection from HTTP to HTTPS. I am
expecting switching HTTP<->HTTPS via Upgrade, and HTTP/1<->HTTP/2 via
"magic", Upgrade, or ALPN.

Based on ssl-bump experience with switchedToHttps() and the pain that
can be predicted when there are several permutations of such accessors
to test against I am proposing the attached patch.


* Add a transportVersion member to ConnStateData which holds the current
protocol to be used over the clientConnection socket. This variable can
be altered whenever necessary to cause an on-wire protocol change. New
connections default to the protocol signalled in the http(s)_port directive.

* ssl-bump transforms the transportVersion from whatever it was
previously (usually HTTP or HTTPS) to HTTPS.
 - Also updated ssl-bump to set the traffic type flag tunnelSslBumped on
non-intercept traffic, which can be assumed to be a CONNECT request.

* transparent and reverse-proxy URL reconstruction is updated to use the
new member instead of the http(s)_port protocol= setting. This removes
edge conditions where the URL reconstructor needs to figure out ssl-bump
existence.


Christos,
 I would like some help with two ssl-bump related things if you have the
time spare:

 1) testing the new prepareTransparentURL reconstruction works on
ssl-bumped traffic.

 2) finding switchedToHttps() usage that can be replaced.
  When ssl-bump is operating we should now always have one of these
conditions being true:
   a) ConnStateData::port->flags.tunnelSslBump
   b) ConnStateData::port->intercepted() &&
        ConnStateData::transportVersion.protocol==AnyP::PROTO_HTTPS

The second one is low-priority even if this patch gets added. We will
find them later anyway as the code polishing progresses.

Amos
=== modified file 'src/client_side.cc'
--- src/client_side.cc  2014-04-28 10:28:00 +0000
+++ src/client_side.cc  2014-04-28 10:28:59 +0000
@@ -2099,94 +2099,92 @@
     const bool switchedToHttps = conn->switchedToHttps();
     const bool tryHostHeader = vhost || switchedToHttps;
     if (tryHostHeader && (host = mime_get_header(req_hdr, "Host")) != NULL) {
         debugs(33, 5, "ACCEL VHOST REWRITE: vhost=" << host << " + vport=" << 
vport);
         char thost[256];
         if (vport > 0) {
             thost[0] = '\0';
             char *t = NULL;
             if (host[strlen(host)] != ']' && (t = strrchr(host,':')) != NULL) {
                 strncpy(thost, host, (t-host));
                 snprintf(thost+(t-host), sizeof(thost)-(t-host), ":%d", vport);
                 host = thost;
             } else if (!t) {
                 snprintf(thost, sizeof(thost), "%s:%d",host, vport);
                 host = thost;
             }
         } // else nothing to alter port-wise.
         int url_sz = strlen(url) + 32 + Config.appendDomainLen +
                      strlen(host);
         http->uri = (char *)xcalloc(url_sz, 1);
-        const char *protocol = switchedToHttps ?
-                               "https" : 
AnyP::UriScheme(conn->port->transport.protocol).c_str();
-        snprintf(http->uri, url_sz, "%s://%s%s", protocol, host, url);
+        snprintf(http->uri, url_sz, "%s://%s%s", 
AnyP::UriScheme(conn->transportVersion.protocol).c_str(), host, url);
         debugs(33, 5, "ACCEL VHOST REWRITE: '" << http->uri << "'");
     } else if (conn->port->defaultsite /* && !vhost */) {
         debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: defaultsite=" << 
conn->port->defaultsite << " + vport=" << vport);
         int url_sz = strlen(url) + 32 + Config.appendDomainLen +
                      strlen(conn->port->defaultsite);
         http->uri = (char *)xcalloc(url_sz, 1);
         char vportStr[32];
         vportStr[0] = '\0';
         if (vport > 0) {
             snprintf(vportStr, sizeof(vportStr),":%d",vport);
         }
         snprintf(http->uri, url_sz, "%s://%s%s%s",
-                 AnyP::UriScheme(conn->port->transport.protocol).c_str(), 
conn->port->defaultsite, vportStr, url);
+                 AnyP::UriScheme(conn->transportVersion.protocol).c_str(), 
conn->port->defaultsite, vportStr, url);
         debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: '" << http->uri <<"'");
     } else if (vport > 0 /* && (!vhost || no Host:) */) {
         debugs(33, 5, "ACCEL VPORT REWRITE: http_port IP + vport=" << vport);
         /* Put the local socket IP address as the hostname, with whatever 
vport we found  */
         int url_sz = strlen(url) + 32 + Config.appendDomainLen;
         http->uri = (char *)xcalloc(url_sz, 1);
         http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
         snprintf(http->uri, url_sz, "%s://%s:%d%s",
-                 AnyP::UriScheme(conn->port->transport.protocol).c_str(),
+                 AnyP::UriScheme(conn->transportVersion.protocol).c_str(),
                  ipbuf, vport, url);
         debugs(33, 5, "ACCEL VPORT REWRITE: '" << http->uri << "'");
     }
 }
 
 static void
 prepareTransparentURL(ConnStateData * conn, ClientHttpRequest *http, char 
*url, const char *req_hdr)
 {
     char *host;
     char ipbuf[MAX_IPSTRLEN];
 
     if (*url != '/')
         return; /* already in good shape */
 
     /* BUG: Squid cannot deal with '*' URLs (RFC2616 5.1.2) */
 
     if ((host = mime_get_header(req_hdr, "Host")) != NULL) {
         int url_sz = strlen(url) + 32 + Config.appendDomainLen +
                      strlen(host);
         http->uri = (char *)xcalloc(url_sz, 1);
-        snprintf(http->uri, url_sz, "%s://%s%s", 
AnyP::UriScheme(conn->port->transport.protocol).c_str(), host, url);
+        snprintf(http->uri, url_sz, "%s://%s%s", 
AnyP::UriScheme(conn->transportVersion.protocol).c_str(), host, url);
         debugs(33, 5, "TRANSPARENT HOST REWRITE: '" << http->uri <<"'");
     } else {
         /* Put the local socket IP address as the hostname.  */
         int url_sz = strlen(url) + 32 + Config.appendDomainLen;
         http->uri = (char *)xcalloc(url_sz, 1);
         http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
         snprintf(http->uri, url_sz, "%s://%s:%d%s",
-                 
AnyP::UriScheme(http->getConn()->port->transport.protocol).c_str(),
+                 
AnyP::UriScheme(http->getConn()->transportVersion.protocol).c_str(),
                  ipbuf, http->getConn()->clientConnection->local.port(), url);
         debugs(33, 5, "TRANSPARENT REWRITE: '" << http->uri << "'");
     }
 }
 
 /** 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 HttpParser
  *  \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 ClientSocketContext structure on success or failure.
  */
 static ClientSocketContext *
 parseHttpRequest(ConnStateData *csd, HttpParser *hp, HttpRequestMethod * 
method_p, Http::ProtocolVersion *http_ver)
@@ -2246,41 +2244,41 @@
     /* Will the following be true with HTTP/0.9 requests? probably not .. */
     /* So the rest of the code will need to deal with '0'-byte headers (ie, 
none, so don't try parsing em) */
     assert(req_sz > 0);
 
     hp->hdr_end = req_sz - 1;
 
     hp->hdr_start = hp->req.end + 1;
 
     /* Enforce max_request_size */
     if (req_sz >= Config.maxRequestHeaderSize) {
         debugs(33, 5, "parseHttpRequest: Too large request");
         hp->request_parse_status = Http::scHeaderTooLarge;
         return parseHttpRequestAbort(csd, "error:request-too-large");
     }
 
     /* Set method_p */
     *method_p = HttpRequestMethod(&hp->buf[hp->req.m_start], 
&hp->buf[hp->req.m_end]+1);
 
     /* deny CONNECT via accelerated ports */
     if (*method_p == Http::METHOD_CONNECT && csd->port && 
csd->port->flags.accelSurrogate) {
-        debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << 
csd->port->transport.protocol << " Accelerator port " << csd->port->s.port());
+        debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << 
csd->transportVersion << " Accelerator port " << csd->port->s.port());
         /* XXX need a way to say "this many character length string" */
         debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->buf);
         hp->request_parse_status = Http::scMethodNotAllowed;
         return parseHttpRequestAbort(csd, "error:method-not-allowed");
     }
 
     if (*method_p == Http::METHOD_NONE) {
         /* XXX need a way to say "this many character length string" */
         debugs(33, DBG_IMPORTANT, "clientParseRequestMethod: Unsupported 
method in request '" << hp->buf << "'");
         hp->request_parse_status = Http::scMethodNotAllowed;
         return parseHttpRequestAbort(csd, "error:unsupported-request-method");
     }
 
     /*
      * Process headers after request line
      * TODO: Use httpRequestParse here.
      */
     /* XXX this code should be modified to take a const char * later! */
     req_hdr = (char *) hp->buf + hp->req.end + 1;
 
@@ -3250,58 +3248,60 @@
     * 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->al->http.timedout = true;
     if (Comm::IsConnOpen(io.conn))
         io.conn->close();
 }
 
 ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
         AsyncJob("ConnStateData"),
+        transportVersion(),
 #if USE_OPENSSL
         sslBumpMode(Ssl::bumpEnd),
         switchedToHttps_(false),
         sslServerBump(NULL),
 #endif
         stoppedSending_(NULL),
         stoppedReceiving_(NULL)
 {
     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
     clientConnection = xact->tcpClient;
     port = cbdataReference(xact->squidPort.get());
+    transportVersion = port->transport; // default to the http(s)_port 
protocol= setting. may change later.
     log_addr = xact->tcpClient->remote;
     log_addr.applyMask(Config.Addrs.client_netmask);
 
     in.buf.reserveCapacity(CLIENT_REQ_BUF_SZ);
 
     if (port->disable_pmtu_discovery != DISABLE_PMTU_OFF &&
             (transparent() || port->disable_pmtu_discovery == 
DISABLE_PMTU_ALWAYS)) {
 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
         int i = IP_PMTUDISC_DONT;
         if (setsockopt(clientConnection->fd, SOL_IP, IP_MTU_DISCOVER, &i, 
sizeof(i)) < 0)
             debugs(33, 2, "WARNING: Path MTU discovery disabling failed on " 
<< clientConnection << " : " << xstrerror());
 #else
         static bool reported = false;
 
         if (!reported) {
             debugs(33, DBG_IMPORTANT, "NOTICE: Path MTU discovery disabling is 
not supported on your platform.");
             reported = true;
         }
 #endif
     }
@@ -3927,41 +3927,49 @@
         return;
 
     // commSetConnTimeout() was called for this request before we switched.
 
     // Disable the client read handler until CachePeer selection is complete
     Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
     Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, 
clientNegotiateSSL, this, 0);
     switchedToHttps_ = true;
 }
 
 void
 ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode 
bumpServerMode)
 {
     assert(!switchedToHttps_);
 
     sslConnectHostOrIp = request->GetHost();
     sslCommonName = request->GetHost();
 
     // We are going to read new request
     flags.readMore = true;
-    debugs(33, 5, HERE << "converting " << clientConnection << " to SSL");
+    debugs(33, 5, "converting " << clientConnection << " to SSL");
+
+    // keep version major.minor details the same.
+    // but we are now moving 'inside' the HTTPS traffic
+    transportVersion.protocol = AnyP::PROTO_HTTPS;
+
+    // ssl-bumped native HTTPS connection already has intercept/tproxy flag 
set.
+    // otherwise we need to flag this as ssl-bumped CONNECT tunnel.
+    port->flags.tunnelSslBumping = !port->flags.isIntercepted();
 
     // If sslServerBump is set, then we have decided to deny CONNECT
     // and now want to switch to SSL to send the error to the client
     // without even peeking at the origin server certificate.
     if (bumpServerMode == Ssl::bumpServerFirst && !sslServerBump) {
         request->flags.sslPeek = true;
         sslServerBump = new Ssl::ServerBump(request);
 
         // will call httpsPeeked() with certificate and connection, eventually
         FwdState::fwdStart(clientConnection, sslServerBump->entry, 
sslServerBump->request.getRaw());
         return;
     }
 
     // otherwise, use sslConnectHostOrIp
     getSslContextStart();
 }
 
 void
 ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection)
 {

=== modified file 'src/client_side.h'
--- src/client_side.h   2014-04-22 13:10:49 +0000
+++ src/client_side.h   2014-04-28 10:28:59 +0000
@@ -191,40 +191,43 @@
 
     void readSomeData();
     bool areAllContextsForThisConnection() const;
     void freeAllContexts();
     void notifyAllContexts(const int xerrno); ///< tell everybody about the err
     /// Traffic parsing
     bool clientParseRequests();
     void readNextRequest();
     ClientSocketContext::Pointer getCurrentContext() const;
     void addContextToQueue(ClientSocketContext * context);
     int getConcurrentRequestCount() const;
     bool isOpen() const;
     void checkHeaderLimits();
 
     // HttpControlMsgSink API
     virtual void sendControlMsg(HttpControlMsg msg);
 
     // Client TCP connection details from comm layer.
     Comm::ConnectionPointer clientConnection;
 
+    /// the transport protocol currently being spoken on this connection
+    AnyP::ProtocolVersion transportVersion;
+
     struct In {
         In();
         ~In();
         bool maybeMakeSpaceAvailable();
 
         ChunkedCodingParser *bodyParser; ///< parses chunked request body
         SBuf buf;
     } in;
 
     /** number of body bytes we need to comm_read for the "current" request
      *
      * \retval 0         We do not need to read any [more] body bytes
      * \retval negative  May need more but do not know how many; could be zero!
      * \retval positive  Need to read exactly that many more body bytes
      */
     int64_t mayNeedToReadMoreBody() const;
 
 #if USE_AUTH
     /**
      * Fetch the user details for connection based authentication

Reply via email to