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