=== modified file 'src/client_side.cc' --- src/client_side.cc 2013-07-26 11:26:04 +0000 +++ src/client_side.cc 2013-08-02 09:49:24 +0000 @@ -81,6 +81,7 @@ #include "squid.h" #include "acl/FilledChecklist.h" #include "anyp/PortCfg.h" +#include "base/StringArea.h" #include "base/Subscription.h" #include "base/TextException.h" #include "CachePeer.h" @@ -2301,6 +2302,29 @@ return parseHttpRequestAbort(csd, "error:method-not-allowed"); } + /* deny "PRI * HTTP/2.0" via non-intercepted ports */ + bool isHttp20Magic = (*method_p == Http::METHOD_PRI && + *http_ver == Http::ProtocolVersion(2,0) && + memcmp(&hp->buf[hp->hdr_end+1], "SM\r\n\r\n", 6) == 0); + if (isHttp20Magic) { + /* 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 gets removed before we start handling the real HTTP/2 frames. + */ + memmove(const_cast(&hp->buf[hp->hdr_end+1]), &hp->buf[hp->hdr_end + 6 +1], hp->bufsiz - hp->hdr_end - 6); + hp->bufsiz -= 6; + + // 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"); + } + } else if (*method_p == Http::METHOD_PRI || *http_ver == Http::ProtocolVersion(2,0)) { + // other uses of PRI method or HTTP/2.0 version moniker are not supported + 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 << "'"); @@ -2374,7 +2398,10 @@ */ if (csd->transparent()) { /* intercept or transparent mode, properly working with no failures */ - prepareTransparentURL(csd, http, url, req_hdr); + if (isHttp20Magic) + http->uri = xstrdup(url); + else + prepareTransparentURL(csd, http, url, req_hdr); } else if (internalCheck(url)) { /* internal URL mode */ @@ -2702,8 +2729,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)); @@ -2856,10 +2884,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-07-13 12:19:45 +0000 +++ src/client_side_request.cc 2013-07-15 04:15:51 +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-07-14 11:14:40 +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-06-11 08:11:30 +0000 +++ src/tunnel.cc 2013-08-02 10:03:11 +0000 @@ -33,6 +33,8 @@ #include "squid.h" #include "acl/FilledChecklist.h" +#include "base/CbcPointer.h" +#include "base/StringArea.h" #include "base/Vector.h" #include "CachePeer.h" #include "client_side_request.h" @@ -99,6 +101,7 @@ bool noConnections() const; char *url; + CbcPointer http; HttpRequest::Pointer request; Comm::ConnectionList serverDestinations; @@ -224,6 +227,7 @@ TunnelStateData::TunnelStateData() : url(NULL), + http(), request(NULL), status_ptr(NULL), connectRespBuf(NULL), @@ -647,6 +651,9 @@ void TunnelStateData::copyRead(Connection &from, IOCB *completion) { + debugs(26, 8, "Payload " << from.len << " bytes from " << from.conn); + debugs(26, 9, "\n----------\n" << StringArea(from.buf, from.len) << "\n----------"); + assert(from.len == 0); AsyncCall::Pointer call = commCbCall(5,4, "TunnelBlindCopyReadHandler", CommIoCbPtrFun(completion, this)); @@ -674,11 +681,23 @@ assert(!tunnelState->waitingForConnectExchange()); *tunnelState->status_ptr = Http::scOkay; if (cbdataReferenceValid(tunnelState)) { + if (!tunnelState->server.len) 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) { + debugs(26, 9, "Tunnel PUSH Payload: \n" << StringArea(tunnelState->http->getConn()->in.buf, tunnelState->http->getConn()->in.notYetUsed) << "\n----------"); + + // We just need to ensure the bytes from ConnStateData are in client.buf already + memcpy(tunnelState->client.buf, tunnelState->http->getConn()->in.buf, tunnelState->http->getConn()->in.notYetUsed); + // NP: readClient() takes care of buffer length accounting. + tunnelState->readClient(tunnelState->client.buf, tunnelState->http->getConn()->in.notYetUsed, COMM_OK, 0); + tunnelState->http->getConn()->in.notYetUsed = 0; // ConnStateData buffer accounting after the shuffle. + } else + tunnelState->copyRead(tunnelState->client, TunnelStateData::ReadClient); } } @@ -708,7 +727,6 @@ { TunnelStateData *tunnelState = (TunnelStateData *)data; debugs(26, 3, conn << ", flag=" << flag); - assert(tunnelState->waitingForConnectRequest()); if (flag != COMM_OK) { *tunnelState->status_ptr = Http::scInternalServerError; @@ -890,6 +908,7 @@ tunnelState->server.size_ptr = size_ptr; tunnelState->status_ptr = status_ptr; tunnelState->client.conn = http->getConn()->clientConnection; + tunnelState->http = http; comm_add_close_handler(tunnelState->client.conn->fd, tunnelClientClosed, @@ -918,19 +937,25 @@ 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 */ - NULL, /* 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()) { + + if (tunnelState->request->method == Http::METHOD_PRI) { + // send full HTTP/2.0 magic connection header to server + mb.Printf("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); + } else { + // assume CONNECT + mb.Printf("CONNECT %s HTTP/1.1\r\n", tunnelState->url); + HttpStateData::httpBuildRequestHeader(tunnelState->request.getRaw(), NULL, NULL, &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" << + StringArea(mb.content(), mb.contentSize()) << "\n----------"); + + if (tunnelState->clientExpectsConnectResponse() || tunnelState->request->method == Http::METHOD_PRI) { // hack: blindly tunnel peer response (to our CONNECT request) to the client as ours. AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectedWriteDone", CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState)); @@ -950,8 +975,6 @@ // 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", === modified file 'src/url.cc' --- src/url.cc 2012-12-27 17:58:29 +0000 +++ src/url.cc 2013-07-18 17:28:07 +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,22 @@ 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 == "*") + return (request->canonical = xstrdup(request->urlpath.termedBuf())); + // else fall through to default + + default: + { portbuf[0] = '\0'; if (request->port != urlDefaultPort(request->protocol)) @@ -523,6 +536,8 @@ request->GetHost(), portbuf, SQUIDSTRINGPRINT(request->urlpath)); + } + } } return (request->canonical = xstrdup(urlbuf)); @@ -543,9 +558,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, "*", 2); + return buf; + } + // else fall through to default + + default: + { portbuf[0] = '\0'; if (request->port != urlDefaultPort(request->protocol)) @@ -576,6 +606,8 @@ if (Config.onoff.strip_query_terms) if ((t = strchr(buf, '?'))) *(++t) = '\0'; + } + } } if (stringHasCntl(buf)) @@ -822,6 +854,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-06-03 14:05:16 +0000 +++ tools/squidclient.cc 2013-08-01 23:16:27 +0000 @@ -445,6 +445,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);