blob: db0503c5f9b807b1f91d74ac54b44884eb2bcd03 [file] [log] [blame]
QUICHE teama6ef0a62019-03-07 20:34:33 -05001// Copyright (c) 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/third_party/quiche/src/quic/tools/quic_client_base.h"
6
7#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
8#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
9#include "net/third_party/quiche/src/quic/core/quic_server_id.h"
10#include "net/third_party/quiche/src/quic/core/quic_utils.h"
11#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
12#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
13#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
14#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
15
16namespace quic {
17
18QuicClientBase::NetworkHelper::~NetworkHelper() = default;
19
20QuicClientBase::QuicClientBase(
21 const QuicServerId& server_id,
22 const ParsedQuicVersionVector& supported_versions,
23 const QuicConfig& config,
24 QuicConnectionHelperInterface* helper,
25 QuicAlarmFactory* alarm_factory,
26 std::unique_ptr<NetworkHelper> network_helper,
27 std::unique_ptr<ProofVerifier> proof_verifier)
28 : server_id_(server_id),
29 initialized_(false),
30 local_port_(0),
31 config_(config),
32 crypto_config_(std::move(proof_verifier),
33 TlsClientHandshaker::CreateSslCtx()),
34 helper_(helper),
35 alarm_factory_(alarm_factory),
36 supported_versions_(supported_versions),
37 initial_max_packet_length_(0),
38 num_stateless_rejects_received_(0),
39 num_sent_client_hellos_(0),
40 connection_error_(QUIC_NO_ERROR),
41 connected_or_attempting_connect_(false),
42 network_helper_(std::move(network_helper)) {}
43
44QuicClientBase::~QuicClientBase() = default;
45
46bool QuicClientBase::Initialize() {
47 num_sent_client_hellos_ = 0;
48 num_stateless_rejects_received_ = 0;
49 connection_error_ = QUIC_NO_ERROR;
50 connected_or_attempting_connect_ = false;
51
52 // If an initial flow control window has not explicitly been set, then use the
53 // same values that Chrome uses.
54 const uint32_t kSessionMaxRecvWindowSize = 15 * 1024 * 1024; // 15 MB
55 const uint32_t kStreamMaxRecvWindowSize = 6 * 1024 * 1024; // 6 MB
56 if (config()->GetInitialStreamFlowControlWindowToSend() ==
57 kMinimumFlowControlSendWindow) {
58 config()->SetInitialStreamFlowControlWindowToSend(kStreamMaxRecvWindowSize);
59 }
60 if (config()->GetInitialSessionFlowControlWindowToSend() ==
61 kMinimumFlowControlSendWindow) {
62 config()->SetInitialSessionFlowControlWindowToSend(
63 kSessionMaxRecvWindowSize);
64 }
65
66 if (!network_helper_->CreateUDPSocketAndBind(server_address_,
67 bind_to_address_, local_port_)) {
68 return false;
69 }
70
71 initialized_ = true;
72 return true;
73}
74
75bool QuicClientBase::Connect() {
76 // Attempt multiple connects until the maximum number of client hellos have
77 // been sent.
78 while (!connected() &&
79 GetNumSentClientHellos() <= QuicCryptoClientStream::kMaxClientHellos) {
80 StartConnect();
81 while (EncryptionBeingEstablished()) {
82 WaitForEvents();
83 }
84 if (GetQuicReloadableFlag(enable_quic_stateless_reject_support) &&
85 connected()) {
86 // Resend any previously queued data.
87 ResendSavedData();
88 }
89 ParsedQuicVersion version = UnsupportedQuicVersion();
90 if (session() != nullptr &&
91 session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT &&
92 !CanReconnectWithDifferentVersion(&version)) {
93 // We've successfully created a session but we're not connected, and there
94 // is no stateless reject to recover from and cannot try to reconnect with
95 // different version. Give up trying.
96 break;
97 }
98 }
99 if (!connected() &&
100 GetNumSentClientHellos() > QuicCryptoClientStream::kMaxClientHellos &&
101 session() != nullptr &&
102 session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) {
103 // The overall connection failed due too many stateless rejects.
104 set_connection_error(QUIC_CRYPTO_TOO_MANY_REJECTS);
105 }
106 return session()->connection()->connected();
107}
108
109void QuicClientBase::StartConnect() {
110 DCHECK(initialized_);
111 DCHECK(!connected());
112 QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
113 ParsedQuicVersion mutual_version = UnsupportedQuicVersion();
114 const bool can_reconnect_with_different_version =
115 CanReconnectWithDifferentVersion(&mutual_version);
116 if (connected_or_attempting_connect()) {
117 // If the last error was not a stateless reject, then the queued up data
118 // does not need to be resent.
119 // Keep queued up data if client can try to connect with a different
120 // version.
121 if (session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT &&
122 !can_reconnect_with_different_version) {
123 ClearDataToResend();
124 }
125 // Before we destroy the last session and create a new one, gather its stats
126 // and update the stats for the overall connection.
127 UpdateStats();
128 }
129
130 session_ = CreateQuicClientSession(
131 supported_versions(),
132 new QuicConnection(GetNextConnectionId(), server_address(), helper(),
133 alarm_factory(), writer,
134 /* owns_writer= */ false, Perspective::IS_CLIENT,
135 can_reconnect_with_different_version
136 ? ParsedQuicVersionVector{mutual_version}
137 : supported_versions()));
138 if (initial_max_packet_length_ != 0) {
139 session()->connection()->SetMaxPacketLength(initial_max_packet_length_);
140 }
141 // Reset |writer()| after |session()| so that the old writer outlives the old
142 // session.
143 set_writer(writer);
144 InitializeSession();
145 set_connected_or_attempting_connect(true);
146}
147
148void QuicClientBase::InitializeSession() {
149 session()->Initialize();
150}
151
152void QuicClientBase::Disconnect() {
153 DCHECK(initialized_);
154
155 initialized_ = false;
156 if (connected()) {
157 session()->connection()->CloseConnection(
158 QUIC_PEER_GOING_AWAY, "Client disconnecting",
159 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
160 }
161
162 ClearDataToResend();
163
164 network_helper_->CleanUpAllUDPSockets();
165}
166
167ProofVerifier* QuicClientBase::proof_verifier() const {
168 return crypto_config_.proof_verifier();
169}
170
171bool QuicClientBase::EncryptionBeingEstablished() {
172 return !session_->IsEncryptionEstablished() &&
173 session_->connection()->connected();
174}
175
176bool QuicClientBase::WaitForEvents() {
177 DCHECK(connected());
178
179 network_helper_->RunEventLoop();
180
181 DCHECK(session() != nullptr);
182 ParsedQuicVersion version = UnsupportedQuicVersion();
183 if (!connected() &&
184 (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT ||
185 CanReconnectWithDifferentVersion(&version))) {
186 if (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) {
187 DCHECK(GetQuicReloadableFlag(enable_quic_stateless_reject_support));
188 QUIC_DLOG(INFO) << "Detected stateless reject while waiting for events. "
189 << "Attempting to reconnect.";
190 } else {
191 QUIC_DLOG(INFO) << "Can reconnect with version: " << version
192 << ", attempting to reconnect.";
193 }
194 Connect();
195 }
196
QUICHE teamc2653c42019-03-08 13:30:06 -0800197 return HasActiveRequests();
QUICHE teama6ef0a62019-03-07 20:34:33 -0500198}
199
200bool QuicClientBase::MigrateSocket(const QuicIpAddress& new_host) {
201 return MigrateSocketWithSpecifiedPort(new_host, local_port_);
202}
203
204bool QuicClientBase::MigrateSocketWithSpecifiedPort(
205 const QuicIpAddress& new_host,
206 int port) {
207 if (!connected()) {
208 return false;
209 }
210
211 network_helper_->CleanUpAllUDPSockets();
212
213 set_bind_to_address(new_host);
214 if (!network_helper_->CreateUDPSocketAndBind(server_address_,
215 bind_to_address_, port)) {
216 return false;
217 }
218
219 session()->connection()->SetSelfAddress(
220 network_helper_->GetLatestClientAddress());
221
222 QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
223 set_writer(writer);
224 session()->connection()->SetQuicPacketWriter(writer, false);
225
226 return true;
227}
228
229bool QuicClientBase::ChangeEphemeralPort() {
230 auto current_host = network_helper_->GetLatestClientAddress().host();
231 return MigrateSocketWithSpecifiedPort(current_host, 0 /*any ephemeral port*/);
232}
233
234QuicSession* QuicClientBase::session() {
235 return session_.get();
236}
237
238QuicClientBase::NetworkHelper* QuicClientBase::network_helper() {
239 return network_helper_.get();
240}
241
242const QuicClientBase::NetworkHelper* QuicClientBase::network_helper() const {
243 return network_helper_.get();
244}
245
246void QuicClientBase::WaitForStreamToClose(QuicStreamId id) {
247 DCHECK(connected());
248
249 while (connected() && !session_->IsClosedStream(id)) {
250 WaitForEvents();
251 }
252}
253
254bool QuicClientBase::WaitForCryptoHandshakeConfirmed() {
255 DCHECK(connected());
256
257 while (connected() && !session_->IsCryptoHandshakeConfirmed()) {
258 WaitForEvents();
259 }
260
261 // If the handshake fails due to a timeout, the connection will be closed.
262 QUIC_LOG_IF(ERROR, !connected()) << "Handshake with server failed.";
263 return connected();
264}
265
266bool QuicClientBase::connected() const {
267 return session_.get() && session_->connection() &&
268 session_->connection()->connected();
269}
270
271bool QuicClientBase::goaway_received() const {
272 return session_ != nullptr && session_->goaway_received();
273}
274
275int QuicClientBase::GetNumSentClientHellos() {
276 // If we are not actively attempting to connect, the session object
277 // corresponds to the previous connection and should not be used.
278 const int current_session_hellos = !connected_or_attempting_connect_
279 ? 0
280 : GetNumSentClientHellosFromSession();
281 return num_sent_client_hellos_ + current_session_hellos;
282}
283
284void QuicClientBase::UpdateStats() {
285 num_sent_client_hellos_ += GetNumSentClientHellosFromSession();
286 if (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) {
287 ++num_stateless_rejects_received_;
288 }
289}
290
291int QuicClientBase::GetNumReceivedServerConfigUpdates() {
292 // If we are not actively attempting to connect, the session object
293 // corresponds to the previous connection and should not be used.
294 // We do not need to take stateless rejects into account, since we
295 // don't expect any scup messages to be sent during a
296 // statelessly-rejected connection.
297 return !connected_or_attempting_connect_
298 ? 0
299 : GetNumReceivedServerConfigUpdatesFromSession();
300}
301
302QuicErrorCode QuicClientBase::connection_error() const {
303 // Return the high-level error if there was one. Otherwise, return the
304 // connection error from the last session.
305 if (connection_error_ != QUIC_NO_ERROR) {
306 return connection_error_;
307 }
308 if (session_ == nullptr) {
309 return QUIC_NO_ERROR;
310 }
311 return session_->error();
312}
313
314QuicConnectionId QuicClientBase::GetNextConnectionId() {
315 QuicConnectionId server_designated_id = GetNextServerDesignatedConnectionId();
316 return !server_designated_id.IsEmpty() ? server_designated_id
317 : GenerateNewConnectionId();
318}
319
320QuicConnectionId QuicClientBase::GetNextServerDesignatedConnectionId() {
321 QuicCryptoClientConfig::CachedState* cached =
322 crypto_config_.LookupOrCreate(server_id_);
323 // If the cached state indicates that we should use a server-designated
324 // connection ID, then return that connection ID.
325 CHECK(cached != nullptr) << "QuicClientCryptoConfig::LookupOrCreate returned "
326 << "unexpected nullptr.";
327 return cached->has_server_designated_connection_id()
328 ? cached->GetNextServerDesignatedConnectionId()
329 : EmptyQuicConnectionId();
330}
331
332QuicConnectionId QuicClientBase::GenerateNewConnectionId() {
333 return QuicUtils::CreateRandomConnectionId();
334}
335
336bool QuicClientBase::CanReconnectWithDifferentVersion(
337 ParsedQuicVersion* version) const {
338 if (session_ == nullptr || session_->connection() == nullptr ||
339 session_->error() != QUIC_INVALID_VERSION ||
340 session_->connection()->server_supported_versions().empty()) {
341 return false;
342 }
343 for (const auto& client_version : supported_versions_) {
344 if (QuicContainsValue(session_->connection()->server_supported_versions(),
345 client_version)) {
346 *version = client_version;
347 return true;
348 }
349 }
350 return false;
351}
352
353} // namespace quic