| // Copyright (c) 2012 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 "net/tools/quic/quic_simple_client.h" |
| |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "base/run_loop.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_request_info.h" |
| #include "net/http/http_response_info.h" |
| #include "net/quic/crypto/quic_random.h" |
| #include "net/quic/quic_chromium_alarm_factory.h" |
| #include "net/quic/quic_chromium_connection_helper.h" |
| #include "net/quic/quic_chromium_packet_reader.h" |
| #include "net/quic/quic_chromium_packet_writer.h" |
| #include "net/quic/quic_connection.h" |
| #include "net/quic/quic_flags.h" |
| #include "net/quic/quic_protocol.h" |
| #include "net/quic/quic_server_id.h" |
| #include "net/quic/spdy_utils.h" |
| #include "net/spdy/spdy_header_block.h" |
| #include "net/spdy/spdy_http_utils.h" |
| #include "net/udp/udp_client_socket.h" |
| |
| using std::string; |
| using std::vector; |
| |
| namespace net { |
| |
| void QuicSimpleClient::ClientQuicDataToResend::Resend() { |
| client_->SendRequest(*headers_, body_, fin_); |
| delete headers_; |
| headers_ = nullptr; |
| } |
| |
| QuicSimpleClient::QuicSimpleClient(IPEndPoint server_address, |
| const QuicServerId& server_id, |
| const QuicVersionVector& supported_versions, |
| ProofVerifier* proof_verifier) |
| : QuicClientBase(server_id, |
| supported_versions, |
| QuicConfig(), |
| CreateQuicConnectionHelper(), |
| CreateQuicAlarmFactory(), |
| proof_verifier), |
| server_address_(server_address), |
| local_port_(0), |
| initialized_(false), |
| packet_reader_started_(false), |
| weak_factory_(this) {} |
| |
| QuicSimpleClient::QuicSimpleClient(IPEndPoint server_address, |
| const QuicServerId& server_id, |
| const QuicVersionVector& supported_versions, |
| const QuicConfig& config, |
| ProofVerifier* proof_verifier) |
| : QuicClientBase(server_id, |
| supported_versions, |
| config, |
| CreateQuicConnectionHelper(), |
| CreateQuicAlarmFactory(), |
| proof_verifier), |
| server_address_(server_address), |
| local_port_(0), |
| initialized_(false), |
| packet_reader_started_(false), |
| weak_factory_(this) {} |
| |
| QuicSimpleClient::~QuicSimpleClient() { |
| if (connected()) { |
| session()->connection()->CloseConnection( |
| QUIC_PEER_GOING_AWAY, "Shutting down", |
| ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); |
| } |
| STLDeleteElements(&data_to_resend_on_connect_); |
| STLDeleteElements(&data_sent_before_handshake_); |
| } |
| |
| bool QuicSimpleClient::Initialize() { |
| DCHECK(!initialized_); |
| |
| QuicClientBase::Initialize(); |
| |
| if (!CreateUDPSocket()) { |
| return false; |
| } |
| |
| initialized_ = true; |
| return true; |
| } |
| |
| QuicSimpleClient::QuicDataToResend::QuicDataToResend(HttpRequestInfo* headers, |
| base::StringPiece body, |
| bool fin) |
| : headers_(headers), body_(body), fin_(fin) {} |
| |
| QuicSimpleClient::QuicDataToResend::~QuicDataToResend() { |
| if (headers_) { |
| delete headers_; |
| } |
| } |
| |
| bool QuicSimpleClient::CreateUDPSocket() { |
| std::unique_ptr<UDPClientSocket> socket( |
| new UDPClientSocket(DatagramSocket::DEFAULT_BIND, RandIntCallback(), |
| &net_log_, NetLog::Source())); |
| |
| int address_family = server_address_.GetSockAddrFamily(); |
| if (bind_to_address_.size() != 0) { |
| client_address_ = IPEndPoint(bind_to_address_, local_port_); |
| } else if (address_family == AF_INET) { |
| client_address_ = IPEndPoint(IPAddress::IPv4AllZeros(), local_port_); |
| } else { |
| client_address_ = IPEndPoint(IPAddress::IPv6AllZeros(), local_port_); |
| } |
| |
| int rc = socket->Connect(server_address_); |
| if (rc != OK) { |
| LOG(ERROR) << "Connect failed: " << ErrorToShortString(rc); |
| return false; |
| } |
| |
| rc = socket->SetReceiveBufferSize(kDefaultSocketReceiveBuffer); |
| if (rc != OK) { |
| LOG(ERROR) << "SetReceiveBufferSize() failed: " << ErrorToShortString(rc); |
| return false; |
| } |
| |
| rc = socket->SetSendBufferSize(kDefaultSocketReceiveBuffer); |
| if (rc != OK) { |
| LOG(ERROR) << "SetSendBufferSize() failed: " << ErrorToShortString(rc); |
| return false; |
| } |
| |
| rc = socket->GetLocalAddress(&client_address_); |
| if (rc != OK) { |
| LOG(ERROR) << "GetLocalAddress failed: " << ErrorToShortString(rc); |
| return false; |
| } |
| |
| socket_.swap(socket); |
| packet_reader_.reset(new QuicChromiumPacketReader( |
| socket_.get(), &clock_, this, kQuicYieldAfterPacketsRead, |
| QuicTime::Delta::FromMilliseconds(kQuicYieldAfterDurationMilliseconds), |
| BoundNetLog())); |
| |
| if (socket != nullptr) { |
| socket->Close(); |
| } |
| |
| return true; |
| } |
| |
| void QuicSimpleClient::StartPacketReaderIfNotStarted() { |
| if (!packet_reader_started_) { |
| packet_reader_->StartReading(); |
| packet_reader_started_ = true; |
| } |
| } |
| |
| bool QuicSimpleClient::Connect() { |
| // Attempt multiple connects until the maximum number of client hellos have |
| // been sent. |
| while (!connected() && |
| GetNumSentClientHellos() <= QuicCryptoClientStream::kMaxClientHellos) { |
| StartConnect(); |
| StartPacketReaderIfNotStarted(); |
| while (EncryptionBeingEstablished()) { |
| WaitForEvents(); |
| } |
| if (FLAGS_enable_quic_stateless_reject_support && connected() && |
| !data_to_resend_on_connect_.empty()) { |
| // A connection has been established and there was previously queued data |
| // to resend. Resend it and empty the queue. |
| for (QuicDataToResend* data : data_to_resend_on_connect_) { |
| data->Resend(); |
| } |
| STLDeleteElements(&data_to_resend_on_connect_); |
| } |
| if (session() != nullptr && |
| session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) { |
| // We've successfully created a session but we're not connected, and there |
| // is no stateless reject to recover from. Give up trying. |
| break; |
| } |
| } |
| if (!connected() && |
| GetNumSentClientHellos() > QuicCryptoClientStream::kMaxClientHellos && |
| session() != nullptr && |
| session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) { |
| // The overall connection failed due too many stateless rejects. |
| set_connection_error(QUIC_CRYPTO_TOO_MANY_REJECTS); |
| } |
| return session()->connection()->connected(); |
| } |
| |
| void QuicSimpleClient::StartConnect() { |
| DCHECK(initialized_); |
| DCHECK(!connected()); |
| |
| set_writer(CreateQuicPacketWriter()); |
| |
| if (connected_or_attempting_connect()) { |
| // Before we destroy the last session and create a new one, gather its stats |
| // and update the stats for the overall connection. |
| UpdateStats(); |
| if (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) { |
| // If the last error was due to a stateless reject, queue up the data to |
| // be resent on the next successful connection. |
| // TODO(jokulik): I'm a little bit concerned about ordering here. Maybe |
| // we should just maintain one queue? |
| DCHECK(data_to_resend_on_connect_.empty()); |
| data_to_resend_on_connect_.swap(data_sent_before_handshake_); |
| } |
| } |
| |
| CreateQuicClientSession(new QuicConnection( |
| GetNextConnectionId(), server_address_, helper(), alarm_factory(), |
| writer(), |
| /* owns_writer= */ false, Perspective::IS_CLIENT, supported_versions())); |
| |
| session()->Initialize(); |
| session()->CryptoConnect(); |
| set_connected_or_attempting_connect(true); |
| } |
| |
| void QuicSimpleClient::Disconnect() { |
| DCHECK(initialized_); |
| |
| if (connected()) { |
| session()->connection()->CloseConnection( |
| QUIC_PEER_GOING_AWAY, "Client disconnecting", |
| ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); |
| } |
| STLDeleteElements(&data_to_resend_on_connect_); |
| STLDeleteElements(&data_sent_before_handshake_); |
| |
| reset_writer(); |
| packet_reader_.reset(); |
| packet_reader_started_ = false; |
| |
| initialized_ = false; |
| } |
| |
| void QuicSimpleClient::SendRequest(const HttpRequestInfo& headers, |
| base::StringPiece body, |
| bool fin) { |
| QuicSpdyClientStream* stream = CreateReliableClientStream(); |
| if (stream == nullptr) { |
| LOG(DFATAL) << "stream creation failed!"; |
| return; |
| } |
| SpdyHeaderBlock header_block; |
| CreateSpdyHeadersFromHttpRequest(headers, headers.extra_headers, net::HTTP2, |
| true, &header_block); |
| stream->set_visitor(this); |
| stream->SendRequest(std::move(header_block), body, fin); |
| if (FLAGS_enable_quic_stateless_reject_support) { |
| // Record this in case we need to resend. |
| auto* new_headers = new HttpRequestInfo; |
| *new_headers = headers; |
| auto* data_to_resend = |
| new ClientQuicDataToResend(new_headers, body, fin, this); |
| MaybeAddQuicDataToResend(data_to_resend); |
| } |
| } |
| |
| void QuicSimpleClient::MaybeAddQuicDataToResend( |
| QuicDataToResend* data_to_resend) { |
| DCHECK(FLAGS_enable_quic_stateless_reject_support); |
| if (session()->IsCryptoHandshakeConfirmed()) { |
| // The handshake is confirmed. No need to continue saving requests to |
| // resend. |
| STLDeleteElements(&data_sent_before_handshake_); |
| delete data_to_resend; |
| return; |
| } |
| |
| // The handshake is not confirmed. Push the data onto the queue of data to |
| // resend if statelessly rejected. |
| data_sent_before_handshake_.push_back(data_to_resend); |
| } |
| |
| void QuicSimpleClient::SendRequestAndWaitForResponse( |
| const HttpRequestInfo& request, |
| base::StringPiece body, |
| bool fin) { |
| SendRequest(request, body, fin); |
| while (WaitForEvents()) { |
| } |
| } |
| |
| void QuicSimpleClient::SendRequestsAndWaitForResponse( |
| const base::CommandLine::StringVector& url_list) { |
| for (size_t i = 0; i < url_list.size(); ++i) { |
| HttpRequestInfo request; |
| request.method = "GET"; |
| request.url = GURL(url_list[i]); |
| SendRequest(request, "", true); |
| } |
| |
| while (WaitForEvents()) { |
| } |
| } |
| |
| bool QuicSimpleClient::WaitForEvents() { |
| DCHECK(connected()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| DCHECK(session() != nullptr); |
| if (!connected() && |
| session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) { |
| DCHECK(FLAGS_enable_quic_stateless_reject_support); |
| DVLOG(1) << "Detected stateless reject while waiting for events. " |
| << "Attempting to reconnect."; |
| Connect(); |
| } |
| |
| return session()->num_active_requests() != 0; |
| } |
| |
| bool QuicSimpleClient::MigrateSocket(const IPAddress& new_host) { |
| if (!connected()) { |
| return false; |
| } |
| |
| bind_to_address_ = new_host; |
| if (!CreateUDPSocket()) { |
| return false; |
| } |
| |
| session()->connection()->SetSelfAddress(client_address_); |
| |
| QuicPacketWriter* writer = CreateQuicPacketWriter(); |
| set_writer(writer); |
| session()->connection()->SetQuicPacketWriter(writer, false); |
| |
| return true; |
| } |
| |
| void QuicSimpleClient::OnClose(QuicSpdyStream* stream) { |
| DCHECK(stream != nullptr); |
| QuicSpdyClientStream* client_stream = |
| static_cast<QuicSpdyClientStream*>(stream); |
| HttpResponseInfo response; |
| SpdyHeadersToHttpResponse(client_stream->response_headers(), net::HTTP2, |
| &response); |
| if (response_listener_.get() != nullptr) { |
| response_listener_->OnCompleteResponse(stream->id(), *response.headers, |
| client_stream->data()); |
| } |
| |
| // Store response headers and body. |
| if (store_response_) { |
| latest_response_code_ = client_stream->response_code(); |
| response.headers->GetNormalizedHeaders(&latest_response_headers_); |
| latest_response_body_ = client_stream->data(); |
| } |
| } |
| |
| size_t QuicSimpleClient::latest_response_code() const { |
| LOG_IF(DFATAL, !store_response_) << "Response not stored!"; |
| return latest_response_code_; |
| } |
| |
| const string& QuicSimpleClient::latest_response_headers() const { |
| LOG_IF(DFATAL, !store_response_) << "Response not stored!"; |
| return latest_response_headers_; |
| } |
| |
| const string& QuicSimpleClient::latest_response_body() const { |
| LOG_IF(DFATAL, !store_response_) << "Response not stored!"; |
| return latest_response_body_; |
| } |
| |
| QuicConnectionId QuicSimpleClient::GenerateNewConnectionId() { |
| return helper()->GetRandomGenerator()->RandUint64(); |
| } |
| |
| QuicChromiumConnectionHelper* QuicSimpleClient::CreateQuicConnectionHelper() { |
| return new QuicChromiumConnectionHelper(&clock_, QuicRandom::GetInstance()); |
| } |
| |
| QuicChromiumAlarmFactory* QuicSimpleClient::CreateQuicAlarmFactory() { |
| return new QuicChromiumAlarmFactory(base::ThreadTaskRunnerHandle::Get().get(), |
| &clock_); |
| } |
| |
| QuicPacketWriter* QuicSimpleClient::CreateQuicPacketWriter() { |
| return new QuicChromiumPacketWriter(socket_.get()); |
| } |
| |
| void QuicSimpleClient::OnReadError(int result, |
| const DatagramClientSocket* socket) { |
| LOG(ERROR) << "QuicSimpleClient read failed: " << ErrorToShortString(result); |
| Disconnect(); |
| } |
| |
| bool QuicSimpleClient::OnPacket(const QuicReceivedPacket& packet, |
| IPEndPoint local_address, |
| IPEndPoint peer_address) { |
| session()->connection()->ProcessUdpPacket(local_address, peer_address, |
| packet); |
| if (!session()->connection()->connected()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace net |