=== modified file 'src/client_side.cc' --- src/client_side.cc 2013-11-11 12:09:44 +0000 +++ src/client_side.cc 2013-11-15 12:52:36 +0000 @@ -97,6 +97,7 @@ #include "comm/TcpAcceptor.h" #include "comm/Write.h" #include "CommCalls.h" +#include "Debug.h" #include "errorpage.h" #include "fd.h" #include "fde.h" @@ -2184,6 +2185,14 @@ if (*url != '/') return; /* already in good shape */ + assert(http->request); // XXX: remove ASAP. +#if 0 // can we avoid this strdup() entirely ?? + if (http->request->version == Http::ProtocolVersion(2,0)) { + http->uri = xstrdup(url); + return; + } +#endif + /* BUG: Squid cannot deal with '*' URLs (RFC2616 5.1.2) */ if ((host = mime_get_header(req_hdr, "Host")) != NULL) { @@ -2254,6 +2263,9 @@ return parseHttpRequestAbort(csd, "error:invalid-request"); } + if (r == 1) // XXX remove again + debugs(33, 5, "Successful Parse."); + /* Request line is valid here .. */ *http_ver = Http::ProtocolVersion(hp->req.v_maj, hp->req.v_min); @@ -2299,6 +2311,52 @@ return parseHttpRequestAbort(csd, "error:method-not-allowed"); } + bool isHttp20MagicPri = (*method_p == Http::METHOD_PRI && + *http_ver == Http::ProtocolVersion(2,0)); + debugs(33, 5, (isHttp20MagicPri?"found":"not found") << " HTTP/1.0 magic : method=" << *method_p << ", ver=" << *http_ver); + + if (isHttp20MagicPri) { + + /* NOTE: the 'SM\r\n\r\n' string is part of the HTTP/2.0 magic connection header + * even though to the HTTP/1 parser it appears to be a payload + * ensure it is present and gets removed before we start handling the real HTTP/2 frames. + */ + + const ssize_t http2MagicPayloadSz = 6; + if (hp->bufsiz <= (hp->hdr_end+http2MagicPayloadSz)) { + debugs(33, 5, "Incomplete PRI request, waiting for end of HTTP/2.0 magic"); + return NULL; + } + + // double-check that the correct HTTP/2 magic prefix was received + // extra SP characters and CR sequences are tolerated in HTTP/1 parser + // but are not permitted as part of the HTTP/2.0 magic. + const char *http2MagicPrefix = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; + if (memcmp(&hp->buf[hp->req.start], http2MagicPrefix, 24) != 0) { + debugs(33, 5, "Invalid PRI request, does not match exact HTTP/2.0 magic connection header"); +debugs(33, 5, Raw("req", &hp->buf[hp->req.start], 24) ); + // other uses of PRI method and HTTP/2.0 version moniker are not supported + hp->request_parse_status = Http::scMethodNotAllowed; + return parseHttpRequestAbort(csd, "error:method-not-allowed"); + } + + // we only support tunneling intercepted HTTP/2.0 traffic for now + if (csd->port && !csd->port->flags.isIntercepted()) { + hp->request_parse_status = Http::scMethodNotAllowed; + return parseHttpRequestAbort(csd, "error:http-2.0-not-supported"); + } + + // account for the magic prefix "payload" + memmove(const_cast(&hp->buf[hp->hdr_end+1]), &hp->buf[hp->hdr_end + http2MagicPayloadSz +1], hp->bufsiz - http2MagicPayloadSz); + hp->bufsiz -= http2MagicPayloadSz; + + } else if (*method_p == Http::METHOD_PRI || *http_ver == Http::ProtocolVersion(2,0)) { + debugs(33, 5, "Invalid PRI request, requires HTTP/2.0 protocol version"); + // other uses of PRI method or HTTP/2.0 version moniker are not allowed + 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 << "'"); @@ -2700,8 +2758,9 @@ /* RFC 2616 section 10.5.6 : handle unsupported HTTP major versions cleanly. */ /* We currently only support 0.9, 1.0, 1.1 properly */ + /* We support 2.0 experimentally */ if ( (http_ver.major == 0 && http_ver.minor != 9) || - (http_ver.major > 1) ) { + (http_ver.major > 2) ) { clientStreamNode *node = context->getClientReplyContext(); debugs(33, 5, "Unsupported HTTP version discovered. :\n" << HttpParserHdrBuf(hp)); @@ -2854,10 +2913,14 @@ HTTPMSGLOCK(http->request); clientSetKeepaliveFlag(http); - // Let tunneling code be fully responsible for CONNECT requests - if (http->request->method == Http::METHOD_CONNECT) { + // Let tunneling code be fully responsible for CONNECT and PRI requests + if (http->request->method == Http::METHOD_CONNECT || http->request->method == Http::METHOD_PRI) { context->mayUseConnection(true); conn->flags.readMore = false; + + // consume header early so that tunnel gets just the body + connNoteUseOfBuffer(conn, http->req_sz); + notedUseOfBuffer = true; } #if USE_SSL === modified file 'src/client_side_request.cc' --- src/client_side_request.cc 2013-10-25 00:13:46 +0000 +++ src/client_side_request.cc 2013-11-05 07:34:52 +0000 @@ -555,6 +555,7 @@ { // IP address validation for Host: failed. Admin wants to ignore them. // NP: we do not yet handle CONNECT tunnels well, so ignore for them + // NP: we also ignore HTTP/2.0 PRI tunnels, but they are missing Host header entirely. if (!Config.onoff.hostStrictVerify && http->request->method != Http::METHOD_CONNECT) { debugs(85, 3, "SECURITY ALERT: Host header forgery detected on " << http->getConn()->clientConnection << " (" << A << " does not match " << B << ") on URL: " << urlCanonical(http->request)); @@ -1512,7 +1513,7 @@ { debugs(85, 4, "clientProcessRequest: " << RequestMethodStr(request->method) << " '" << uri << "'"); - if (request->method == Http::METHOD_CONNECT && !redirect.status) { + if (!redirect.status && (request->method == Http::METHOD_CONNECT || request->method == Http::METHOD_PRI)) { #if USE_SSL if (sslBumpNeeded()) { sslBumpStart(); === modified file 'src/http/MethodType.h' --- src/http/MethodType.h 2012-10-27 00:13:19 +0000 +++ src/http/MethodType.h 2013-08-23 03:28:51 +0000 @@ -75,6 +75,9 @@ METHOD_UNBIND, #endif + // HTTP/2.0 magic - http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-3.5 + METHOD_PRI, + // Squid extension methods METHOD_PURGE, METHOD_OTHER, === modified file 'src/tunnel.cc' --- src/tunnel.cc 2013-10-25 00:13:46 +0000 +++ src/tunnel.cc 2013-11-15 02:25:28 +0000 @@ -33,6 +33,7 @@ #include "squid.h" #include "acl/FilledChecklist.h" +#include "base/CbcPointer.h" #include "base/Vector.h" #include "CachePeer.h" #include "client_side.h" @@ -41,6 +42,7 @@ #include "comm/Connection.h" #include "comm/ConnOpener.h" #include "comm/Write.h" +#include "Debug.h" #include "errorpage.h" #include "fde.h" #include "http.h" @@ -99,6 +101,7 @@ bool noConnections() const; char *url; + CbcPointer http; HttpRequest::Pointer request; AccessLogEntryPointer al; Comm::ConnectionList serverDestinations; @@ -225,6 +228,7 @@ TunnelStateData::TunnelStateData() : url(NULL), + http(), request(NULL), status_ptr(NULL), connectRespBuf(NULL), @@ -648,6 +652,11 @@ void TunnelStateData::copyRead(Connection &from, IOCB *completion) { + debugs(26, 8, "Payload " << from.len << " bytes from " << from.conn); +#if WHEN_WE_DO_DATA_DUMPS + debugs(26, 9, "\n----------\n" << Raw("TunnelStateData::copyRead", from.buf, from.len) << "\n----------"); +#endif + assert(from.len == 0); AsyncCall::Pointer call = commCbCall(5,4, "TunnelBlindCopyReadHandler", CommIoCbPtrFun(completion, this)); @@ -679,7 +688,19 @@ tunnelState->copyRead(tunnelState->server, TunnelStateData::ReadServer); else tunnelState->copy(tunnelState->server.len, tunnelState->server, tunnelState->client, TunnelStateData::WriteClientDone); - tunnelState->copyRead(tunnelState->client, TunnelStateData::ReadClient); + + // Bug 3371: shovel any payload already pushed into ConnStateData by the client request + if (tunnelState->http.valid() && tunnelState->http->getConn() && tunnelState->http->getConn()->in.notYetUsed) { + struct ConnStateData::In *in = &tunnelState->http->getConn()->in; + debugs(26, 9, "Tunnel PUSH Payload: \n" << Raw("ConnStateData::in.buf", in->buf, in->notYetUsed) << "\n----------"); + + // We just need to ensure the bytes from ConnStateData are in client.buf already + memcpy(tunnelState->client.buf, in->buf, in->notYetUsed); + // NP: readClient() takes care of buffer length accounting. + tunnelState->readClient(tunnelState->client.buf, in->notYetUsed, COMM_OK, 0); + in->notYetUsed = 0; // ConnStateData buffer accounting after the shuffle. + } else + tunnelState->copyRead(tunnelState->client, TunnelStateData::ReadClient); } } @@ -703,13 +724,13 @@ tunnelStartShoveling(tunnelState); } -/// Called when we are done writing CONNECT request to a peer. +/// Called when we are done writing CONNECT or PRI request to a peer. static void tunnelConnectReqWriteDone(const Comm::ConnectionPointer &conn, char *buf, size_t size, comm_err_t flag, int xerrno, void *data) { TunnelStateData *tunnelState = (TunnelStateData *)data; debugs(26, 3, conn << ", flag=" << flag); - assert(tunnelState->waitingForConnectRequest()); + assert(tunnelState->waitingForConnectRequest() || tunnelState->request->method == Http::METHOD_PRI); if (flag != COMM_OK) { *tunnelState->status_ptr = Http::scInternalServerError; @@ -891,6 +912,7 @@ tunnelState->server.size_ptr = size_ptr; tunnelState->status_ptr = status_ptr; tunnelState->client.conn = http->getConn()->clientConnection; + tunnelState->http = http; tunnelState->al = al; comm_add_close_handler(tunnelState->client.conn->fd, @@ -920,20 +942,26 @@ flags.proxying = tunnelState->request->flags.proxying; MemBuf mb; mb.init(); - mb.Printf("CONNECT %s HTTP/1.1\r\n", tunnelState->url); - HttpStateData::httpBuildRequestHeader(tunnelState->request.getRaw(), - NULL, /* StoreEntry */ - tunnelState->al, /* AccessLogEntry */ - &hdr_out, - flags); /* flags */ - packerToMemInit(&p, &mb); - hdr_out.packInto(&p); - hdr_out.clean(); - packerClean(&p); - mb.append("\r\n", 2); - - if (tunnelState->clientExpectsConnectResponse()) { - // hack: blindly tunnel peer response (to our CONNECT request) to the client as ours. + + if (tunnelState->request->method == Http::METHOD_PRI) { + // send full HTTP/2.0 magic connection header to server + mb.append("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 25); + } else { + // assume CONNECT + mb.Printf("CONNECT %s HTTP/1.1\r\n", tunnelState->url); + HttpStateData::httpBuildRequestHeader(tunnelState->request.getRaw(), NULL, tunnelState->al, &hdr_out, flags); + packerToMemInit(&p, &mb); + hdr_out.packInto(&p); + hdr_out.clean(); + packerClean(&p); + mb.append("\r\n", 2); + } + + debugs(11, 2, "Tunnel Server REQUEST: " << tunnelState->server.conn << ":\n----------\n" << + Raw("tunnelRelayConnectRequest", mb.content(), mb.contentSize()) << "\n----------"); + + if (tunnelState->clientExpectsConnectResponse() || tunnelState->request->method == Http::METHOD_PRI) { + // hack: blindly tunnel peer response (to our CONNECT or PRI request) to the client as ours. AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectedWriteDone", CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState)); Comm::Write(srv, &mb, writeCall); @@ -953,7 +981,7 @@ tunnelState->connectRespBuf->init(SQUID_TCP_SO_RCVBUF, 2*SQUID_TCP_SO_RCVBUF); tunnelState->readConnectResponse(); - assert(tunnelState->waitingForConnectExchange()); + assert(tunnelState->waitingForConnectExchange() || tunnelState->request->method == Http::METHOD_PRI); } AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout", === modified file 'src/url.cc' --- src/url.cc 2012-12-27 17:58:29 +0000 +++ src/url.cc 2013-08-23 04:27:50 +0000 @@ -243,7 +243,7 @@ if (sscanf(url, "%[^:]:%d", host, &port) < 1) return NULL; - } else if ((method == Http::METHOD_OPTIONS || method == Http::METHOD_TRACE) && + } else if ((method == Http::METHOD_OPTIONS || method == Http::METHOD_TRACE || method == Http::METHOD_PRI) && strcmp(url, "*") == 0) { protocol = AnyP::PROTO_HTTP; port = urlDefaultPort(protocol); @@ -507,9 +507,24 @@ if (request->protocol == AnyP::PROTO_URN) { snprintf(urlbuf, MAX_URL, "urn:" SQUIDSTRINGPH, SQUIDSTRINGPRINT(request->urlpath)); - } else if (request->method.id() == Http::METHOD_CONNECT) { - snprintf(urlbuf, MAX_URL, "%s:%d", request->GetHost(), request->port); } else { + + switch(request->method.id()) { + case Http::METHOD_CONNECT: + snprintf(urlbuf, MAX_URL, "%s:%d", request->GetHost(), request->port); + break; + + case Http::METHOD_OPTIONS: + case Http::METHOD_TRACE: + case Http::METHOD_PRI: + if (request->urlpath == "*") { + memcpy(urlbuf, "*\0", 2); + break; + } + // else fall through to default + + default: + { portbuf[0] = '\0'; if (request->port != urlDefaultPort(request->protocol)) @@ -523,6 +538,8 @@ request->GetHost(), portbuf, SQUIDSTRINGPRINT(request->urlpath)); + } + } } return (request->canonical = xstrdup(urlbuf)); @@ -543,9 +560,24 @@ if (request->protocol == AnyP::PROTO_URN) { snprintf(buf, MAX_URL, "urn:" SQUIDSTRINGPH, SQUIDSTRINGPRINT(request->urlpath)); - } else if (request->method.id() == Http::METHOD_CONNECT) { - snprintf(buf, MAX_URL, "%s:%d", request->GetHost(), request->port); } else { + + switch(request->method.id()) { + case Http::METHOD_CONNECT: + snprintf(buf, MAX_URL, "%s:%d", request->GetHost(), request->port); + break; + + case Http::METHOD_OPTIONS: + case Http::METHOD_TRACE: + case Http::METHOD_PRI: + if (request->urlpath == "*") { + memcpy(buf, "*\0", 2); + break; + } + // else fall through to default + + default: + { portbuf[0] = '\0'; if (request->port != urlDefaultPort(request->protocol)) @@ -576,6 +608,8 @@ if (Config.onoff.strip_query_terms) if ((t = strchr(buf, '?'))) *(++t) = '\0'; + } + } } if (stringHasCntl(buf)) @@ -822,6 +856,10 @@ if (r->method == Http::METHOD_CONNECT) return 1; + // we support HTTP/2.0 magic PRI requests + if (r->method == Http::METHOD_PRI) + return (r->urlpath == "*"); + // we support OPTIONS and TRACE directed at us (with a 501 reply, for now) // we also support forwarding OPTIONS and TRACE, except for the *-URI ones if (r->method == Http::METHOD_OPTIONS || r->method == Http::METHOD_TRACE) === modified file 'tools/squidclient.cc' --- tools/squidclient.cc 2013-09-21 17:21:48 +0000 +++ tools/squidclient.cc 2013-10-05 14:01:35 +0000 @@ -446,6 +446,9 @@ if (version[0] == '-' || !version[0]) { /* HTTP/0.9, no headers, no version */ snprintf(msg, BUFSIZ, "%s %s\r\n", method, url); + } else if (strcmp(version, "2.0") == 0) { + // send HTTP/2.0 magic buffer, then wait for the server response. + snprintf(msg, BUFSIZ, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\nSETTINGS-FRAME...\nDONE\r\n"); } else { if (!xisdigit(version[0])) // not HTTP/n.n snprintf(msg, BUFSIZ, "%s %s %s\r\n", method, url, version);