// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/strings/string_number_conversions.h" #include "net/base/elements_upload_data_stream.h" #include "net/base/net_errors.h" #include "net/base/request_priority.h" #include "net/base/upload_bytes_element_reader.h" #include "net/cert/cert_status_flags.h" #include "net/http/http_response_headers.h" #include "net/http/http_status_code.h" #include "net/http/http_util.h" #include "net/ssl/ssl_info.h" #include "net/url_request/redirect_info.h" #include "net/url_request/url_request_context.h" #include "net/tools/quic/quic_http_proxy_backend_stream.h" namespace net { // This is the Size of the buffer that consumes the response from the Backend // The response is consumed upto 64KB at a time to avoid a large response // from hogging resources from smaller responses. const int QuicHttpProxyBackendStream::kBufferSize = 64000; /*502 Bad Gateway The server was acting as a gateway or proxy and received an invalid response from the upstream server.*/ const int QuicHttpProxyBackendStream::kProxyHttpBackendError = 502; // Hop-by-hop headers (small-caps). These are removed when sent to the backend. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html // not Trailers per URL above; // http://www.rfc-editor.org/errata_search.php?eid=4522 const std::set QuicHttpProxyBackendStream::kHopHeaders = { "connection", "proxy-connection", // non-standard but still sent by libcurl and rejected // by e.g. google "keep-alive", "proxy-authenticate", "proxy-authorization", "te", // canonicalized version of "TE" "trailer", // not Trailers per URL above; // http://www.rfc-editor.org/errata_search.php?eid=4522 "transfer-encoding", "upgrade", }; const std::string QuicHttpProxyBackendStream::kDefaultQuicPeerIP = "Unknown"; QuicHttpProxyBackendStream::QuicHttpProxyBackendStream( QuicHttpProxyBackend* proxy_context) : proxy_context_(proxy_context), delegate_(nullptr), quic_peer_ip_(kDefaultQuicPeerIP), url_request_(nullptr), buf_(base::MakeRefCounted(kBufferSize)), response_completed_(false), headers_set_(false), quic_response_(new quic::QuicBackendResponse()), weak_factory_(this) {} QuicHttpProxyBackendStream::~QuicHttpProxyBackendStream() {} void QuicHttpProxyBackendStream::Initialize( quic::QuicConnectionId quic_connection_id, quic::QuicStreamId quic_stream_id, std::string quic_peer_ip) { quic_connection_id_ = quic_connection_id; quic_stream_id_ = quic_stream_id; quic_peer_ip_ = quic_peer_ip; if (!quic_proxy_task_runner_.get()) { quic_proxy_task_runner_ = proxy_context_->GetProxyTaskRunner(); } else { DCHECK_EQ(quic_proxy_task_runner_, proxy_context_->GetProxyTaskRunner()); } quic_response_->set_response_type( quic::QuicBackendResponse::BACKEND_ERR_RESPONSE); } void QuicHttpProxyBackendStream::set_delegate( quic::QuicSimpleServerBackend::RequestHandler* delegate) { delegate_ = delegate; delegate_task_runner_ = base::SequencedTaskRunnerHandle::Get(); } bool QuicHttpProxyBackendStream::SendRequestToBackend( const spdy::SpdyHeaderBlock* incoming_request_headers, const std::string& incoming_body) { DCHECK(proxy_context_->IsBackendInitialized()) << " The quic-backend-proxy-context should be initialized"; // Get Path From the Incoming Header Block spdy::SpdyHeaderBlock::const_iterator it = incoming_request_headers->find(":path"); GURL url = proxy_context_->backend_url(); std::string backend_spec = url.spec(); if (it != incoming_request_headers->end()) { if (url.path().compare("/") == 0) { backend_spec.pop_back(); } backend_spec.append(it->second.as_string()); } url_ = GURL(backend_spec.c_str()); if (!url_.is_valid()) { LOG(ERROR) << "Invalid URL received from QUIC client " << backend_spec; return false; } LOG(INFO) << "QUIC Proxy Making a request to the Backed URL: " + url_.spec(); // Set the Method From the Incoming Header Block std::string method = ""; it = incoming_request_headers->find(":method"); if (it != incoming_request_headers->end()) { method.append(it->second.as_string()); } if (ValidateHttpMethod(method) != true) { LOG(INFO) << "Unknown Request Type received from QUIC client " << method; return false; } CopyHeaders(incoming_request_headers); if (method_type_ == "POST" || method_type_ == "PUT" || method_type_ == "PATCH") { // Upload content must be set if (!incoming_body.empty()) { std::unique_ptr reader(new UploadBytesElementReader( incoming_body.data(), incoming_body.size())); SetUpload( ElementsUploadDataStream::CreateWithReader(std::move(reader), 0)); } } // Start the request on the backend thread bool posted = quic_proxy_task_runner_->PostTask( FROM_HERE, base::BindOnce(&QuicHttpProxyBackendStream::SendRequestOnBackendThread, weak_factory_.GetWeakPtr())); return posted; } void QuicHttpProxyBackendStream::CopyHeaders( const spdy::SpdyHeaderBlock* incoming_request_headers) { // Set all the request headers // Add or append the X-Forwarded-For Header and X-Real-IP for (spdy::SpdyHeaderBlock::const_iterator it = incoming_request_headers->begin(); it != incoming_request_headers->end(); ++it) { std::string key = it->first.as_string(); std::string value = it->second.as_string(); // Ignore the spdy headers if (!key.empty() && key[0] != ':') { // Remove hop-by-hop headers if (base::ContainsKey(kHopHeaders, key)) { LOG(INFO) << "QUIC Proxy Ignoring Hop-by-hop Request Header: " << key << ":" << value; } else { LOG(INFO) << "QUIC Proxy Copying to backend Request Header: " << key << ":" << value; AddRequestHeader(key, value); } } } // ToDo append proxy ip when x_forwarded_for header already present AddRequestHeader("X-Forwarded-For", quic_peer_ip_); } bool QuicHttpProxyBackendStream::ValidateHttpMethod(std::string method) { // Http method is a token, just as header name. if (!net::HttpUtil::IsValidHeaderName(method)) return false; method_type_ = method; return true; } bool QuicHttpProxyBackendStream::AddRequestHeader(std::string name, std::string value) { if (!net::HttpUtil::IsValidHeaderName(name) || !net::HttpUtil::IsValidHeaderValue(value)) { return false; } request_headers_.SetHeader(name, value); return true; } void QuicHttpProxyBackendStream::SetUpload( std::unique_ptr upload) { DCHECK(!upload_); upload_ = std::move(upload); } void QuicHttpProxyBackendStream::SendRequestOnBackendThread() { DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread()); url_request_ = proxy_context_->GetURLRequestContext()->CreateRequest( url_, net::DEFAULT_PRIORITY, this); url_request_->set_method(method_type_); url_request_->SetExtraRequestHeaders(request_headers_); if (upload_) { url_request_->set_upload(std::move(upload_)); } url_request_->Start(); VLOG(1) << "Quic Proxy Sending Request to Backend for quic_conn_id: " << quic_connection_id_ << " quic_stream_id: " << quic_stream_id_ << " backend_req_id: " << url_request_->identifier() << " url: " << url_; } void QuicHttpProxyBackendStream::OnReceivedRedirect( net::URLRequest* request, const net::RedirectInfo& redirect_info, bool* defer_redirect) { DCHECK_EQ(request, url_request_.get()); DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread()); // Do not defer redirect, retry again from the proxy with the new url *defer_redirect = false; LOG(ERROR) << "Received Redirect from Backend " << " BackendReqId: " << request->identifier() << " redirectUrl: " << redirect_info.new_url.possibly_invalid_spec().c_str() << " RespCode " << request->GetResponseCode(); } void QuicHttpProxyBackendStream::OnCertificateRequested( net::URLRequest* request, net::SSLCertRequestInfo* cert_request_info) { DCHECK_EQ(request, url_request_.get()); DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread()); // Continue the SSL handshake without a client certificate. request->ContinueWithCertificate(nullptr, nullptr); } void QuicHttpProxyBackendStream::OnSSLCertificateError( net::URLRequest* request, const net::SSLInfo& ssl_info, bool fatal) { request->Cancel(); OnResponseCompleted(); } void QuicHttpProxyBackendStream::OnResponseStarted(net::URLRequest* request, int net_error) { DCHECK_EQ(request, url_request_.get()); DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread()); // It doesn't make sense for the request to have IO pending at this point. DCHECK_NE(net::ERR_IO_PENDING, net_error); if (net_error != net::OK) { LOG(ERROR) << "OnResponseStarted Error from Backend " << url_request_->identifier() << " url: " << url_request_->url().possibly_invalid_spec().c_str() << " RespError " << net::ErrorToString(net_error); OnResponseCompleted(); return; } // Initialite the first read ReadOnceTask(); } void QuicHttpProxyBackendStream::ReadOnceTask() { // Initiate a read for a max of kBufferSize // This avoids a request with a large response from starving // requests with smaller responses int bytes_read = url_request_->Read(buf_.get(), kBufferSize); OnReadCompleted(url_request_.get(), bytes_read); } // In the case of net::ERR_IO_PENDING, // OnReadCompleted callback will be called by URLRequest void QuicHttpProxyBackendStream::OnReadCompleted(net::URLRequest* unused, int bytes_read) { DCHECK_EQ(url_request_.get(), unused); LOG(INFO) << "OnReadCompleted Backend with" << " ReqId: " << url_request_->identifier() << " RespCode " << url_request_->GetResponseCode() << " RcvdBytesCount " << bytes_read << " RcvdTotalBytes " << data_received_.size(); if (bytes_read > 0) { data_received_.append(buf_->data(), bytes_read); // More data to be read, send a task to self quic_proxy_task_runner_->PostTask( FROM_HERE, base::BindOnce(&QuicHttpProxyBackendStream::ReadOnceTask, weak_factory_.GetWeakPtr())); } else if (bytes_read != net::ERR_IO_PENDING) { quic_response_->set_response_type( quic::QuicBackendResponse::REGULAR_RESPONSE); OnResponseCompleted(); } } /* Response from Backend complete, send the last chunk of data with fin=true to * the corresponding quic stream */ void QuicHttpProxyBackendStream::OnResponseCompleted() { DCHECK(!response_completed_); LOG(INFO) << "Quic Proxy Received Response from Backend for quic_conn_id: " << quic_connection_id_ << " quic_stream_id: " << quic_stream_id_ << " backend_req_id: " << url_request_->identifier() << " url: " << url_; // ToDo Stream the response spdy::SpdyHeaderBlock response_headers; if (quic_response_->response_type() != quic::QuicBackendResponse::BACKEND_ERR_RESPONSE) { response_headers = getAsQuicHeaders(url_request_->response_headers(), url_request_->GetResponseCode(), data_received_.size()); quic_response_->set_headers(std::move(response_headers)); quic_response_->set_body(std::move(data_received_)); } else { response_headers = getAsQuicHeaders(url_request_->response_headers(), kProxyHttpBackendError, data_received_.size()); quic_response_->set_headers(std::move(response_headers)); } response_completed_ = true; ReleaseRequest(); // Send the response back to the quic client on the quic/main thread if (delegate_ != nullptr) { delegate_task_runner_->PostTask( FROM_HERE, base::BindOnce( &QuicHttpProxyBackendStream::SendResponseOnDelegateThread, base::Unretained(this))); } } void QuicHttpProxyBackendStream::SendResponseOnDelegateThread() { DCHECK(delegate_ != nullptr); // Proxy currently does not support push resources std::list empty_resources; delegate_->OnResponseBackendComplete(quic_response_.get(), empty_resources); } void QuicHttpProxyBackendStream::CancelRequest() { DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread()); if (quic_proxy_task_runner_.get()) DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread()); delegate_ = nullptr; if (url_request_.get()) { url_request_->CancelWithError(ERR_ABORTED); ReleaseRequest(); } } void QuicHttpProxyBackendStream::ReleaseRequest() { url_request_.reset(); buf_ = nullptr; } quic::QuicBackendResponse* QuicHttpProxyBackendStream::GetBackendResponse() const { return quic_response_.get(); } // Copy Backend Response headers to Quic response headers spdy::SpdyHeaderBlock QuicHttpProxyBackendStream::getAsQuicHeaders( net::HttpResponseHeaders* resp_headers, int response_code, uint64_t response_decoded_body_size) { DCHECK(!headers_set_); bool response_body_encoded = false; spdy::SpdyHeaderBlock quic_response_headers; // Add spdy headers: Status, version need : before the header quic_response_headers[":status"] = base::NumberToString(response_code); headers_set_ = true; // Returns an empty array if |headers| is nullptr. if (resp_headers != nullptr) { size_t iter = 0; std::string header_name; std::string header_value; while (resp_headers->EnumerateHeaderLines(&iter, &header_name, &header_value)) { header_name = base::ToLowerASCII(header_name); // Do not copy status again since status needs a ":" before the header // name if (header_name.compare("status") != 0) { if (header_name.compare("content-encoding") != 0) { // Remove hop-by-hop headers if (base::ContainsKey(kHopHeaders, header_name)) { LOG(INFO) << "Quic Proxy Ignoring Hop-by-hop Response Header: " << header_name << ":" << header_value; } else { LOG(INFO) << " Quic Proxy Copying Response Header: " << header_name << ":" << header_value; quic_response_headers.AppendValueOrAddHeader(header_name, header_value); } } else { response_body_encoded = true; } } } // while // Currently URLRequest class does not support ability to disable decoding, // response body (gzip, deflate etc. ) // Instead of re-encoding the body, we send decode body to the quic client // and re-write the content length to the original body size if (response_body_encoded) { LOG(INFO) << " Quic Proxy Rewriting the Content-Length Header since " "the response was encoded : " << response_decoded_body_size; quic_response_headers["content-length"] = base::NumberToString(response_decoded_body_size); } } return quic_response_headers; } } // namespace net