1 // 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 "quiche/quic/tools/quic_client_base.h"
6
7 #include <algorithm>
8 #include <cstdint>
9 #include <memory>
10 #include <utility>
11
12 #include "quiche/quic/core/crypto/quic_random.h"
13 #include "quiche/quic/core/http/spdy_utils.h"
14 #include "quiche/quic/core/quic_alarm_factory.h"
15 #include "quiche/quic/core/quic_config.h"
16 #include "quiche/quic/core/quic_connection.h"
17 #include "quiche/quic/core/quic_connection_id.h"
18 #include "quiche/quic/core/quic_constants.h"
19 #include "quiche/quic/core/quic_crypto_client_stream.h"
20 #include "quiche/quic/core/quic_error_codes.h"
21 #include "quiche/quic/core/quic_packet_writer.h"
22 #include "quiche/quic/core/quic_path_validator.h"
23 #include "quiche/quic/core/quic_server_id.h"
24 #include "quiche/quic/core/quic_session.h"
25 #include "quiche/quic/core/quic_time.h"
26 #include "quiche/quic/core/quic_types.h"
27 #include "quiche/quic/core/quic_utils.h"
28 #include "quiche/quic/core/quic_versions.h"
29 #include "quiche/quic/platform/api/quic_bug_tracker.h"
30 #include "quiche/quic/platform/api/quic_flags.h"
31 #include "quiche/quic/platform/api/quic_ip_address.h"
32 #include "quiche/quic/platform/api/quic_logging.h"
33 #include "quiche/quic/platform/api/quic_socket_address.h"
34 #include "quiche/common/platform/api/quiche_logging.h"
35
36 namespace quic {
37
38 namespace {
39
40 // Implements the basic feature of a result delegate for path validation for
41 // connection migration. If the validation succeeds, migrate to the alternative
42 // path. Otherwise, stay on the current path.
43 class QuicClientSocketMigrationValidationResultDelegate
44 : public QuicPathValidator::ResultDelegate {
45 public:
QuicClientSocketMigrationValidationResultDelegate(QuicClientBase * client)46 explicit QuicClientSocketMigrationValidationResultDelegate(
47 QuicClientBase* client)
48 : QuicPathValidator::ResultDelegate(), client_(client) {}
49
50 virtual ~QuicClientSocketMigrationValidationResultDelegate() = default;
51
52 // QuicPathValidator::ResultDelegate
53 // Overridden to start migration and takes the ownership of the writer in the
54 // context.
OnPathValidationSuccess(std::unique_ptr<QuicPathValidationContext> context,QuicTime)55 void OnPathValidationSuccess(
56 std::unique_ptr<QuicPathValidationContext> context,
57 QuicTime /*start_time*/) override {
58 QUIC_DLOG(INFO) << "Successfully validated path from " << *context
59 << ". Migrate to it now.";
60 client_->OnSocketMigrationProbingSuccess(std::move(context));
61 }
62
OnPathValidationFailure(std::unique_ptr<QuicPathValidationContext> context)63 void OnPathValidationFailure(
64 std::unique_ptr<QuicPathValidationContext> context) override {
65 QUIC_LOG(WARNING) << "Fail to validate path " << *context
66 << ", stop migrating.";
67 client_->OnSocketMigrationProbingFailure();
68 client_->session()->connection()->OnPathValidationFailureAtClient(
69 /*is_multi_port=*/false, *context);
70 }
71
72 protected:
client()73 QuicClientBase* client() { return client_; }
74
75 private:
76 QuicClientBase* client_;
77 };
78
79 class ServerPreferredAddressResultDelegateWithWriter
80 : public QuicClientSocketMigrationValidationResultDelegate {
81 public:
ServerPreferredAddressResultDelegateWithWriter(QuicClientBase * client)82 ServerPreferredAddressResultDelegateWithWriter(QuicClientBase* client)
83 : QuicClientSocketMigrationValidationResultDelegate(client) {}
84
85 // Overridden to transfer the ownership of the new writer.
OnPathValidationSuccess(std::unique_ptr<QuicPathValidationContext> context,QuicTime)86 void OnPathValidationSuccess(
87 std::unique_ptr<QuicPathValidationContext> context,
88 QuicTime /*start_time*/) override {
89 client()->session()->connection()->OnServerPreferredAddressValidated(
90 *context, false);
91 auto migration_context = std::unique_ptr<PathMigrationContext>(
92 static_cast<PathMigrationContext*>(context.release()));
93 client()->set_writer(migration_context->ReleaseWriter());
94 }
95 };
96
97 } // namespace
98
OnSocketMigrationProbingSuccess(std::unique_ptr<QuicPathValidationContext> context)99 void QuicClientBase::OnSocketMigrationProbingSuccess(
100 std::unique_ptr<QuicPathValidationContext> context) {
101 auto migration_context = std::unique_ptr<PathMigrationContext>(
102 static_cast<PathMigrationContext*>(context.release()));
103 session()->MigratePath(
104 migration_context->self_address(), migration_context->peer_address(),
105 migration_context->WriterToUse(), /*owns_writer=*/false);
106 QUICHE_DCHECK(migration_context->WriterToUse() != nullptr);
107 // Hand the ownership of the alternative writer to the client.
108 set_writer(migration_context->ReleaseWriter());
109 }
110
111 QuicClientBase::NetworkHelper::~NetworkHelper() = default;
112
QuicClientBase(const QuicServerId & server_id,const ParsedQuicVersionVector & supported_versions,const QuicConfig & config,QuicConnectionHelperInterface * helper,QuicAlarmFactory * alarm_factory,std::unique_ptr<NetworkHelper> network_helper,std::unique_ptr<ProofVerifier> proof_verifier,std::unique_ptr<SessionCache> session_cache)113 QuicClientBase::QuicClientBase(
114 const QuicServerId& server_id,
115 const ParsedQuicVersionVector& supported_versions, const QuicConfig& config,
116 QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory,
117 std::unique_ptr<NetworkHelper> network_helper,
118 std::unique_ptr<ProofVerifier> proof_verifier,
119 std::unique_ptr<SessionCache> session_cache)
120 : server_id_(server_id),
121 initialized_(false),
122 local_port_(0),
123 config_(config),
124 crypto_config_(std::move(proof_verifier), std::move(session_cache)),
125 helper_(helper),
126 alarm_factory_(alarm_factory),
127 supported_versions_(supported_versions),
128 initial_max_packet_length_(0),
129 num_sent_client_hellos_(0),
130 connection_error_(QUIC_NO_ERROR),
131 connected_or_attempting_connect_(false),
132 network_helper_(std::move(network_helper)),
133 connection_debug_visitor_(nullptr),
134 server_connection_id_length_(kQuicDefaultConnectionIdLength),
135 client_connection_id_length_(0) {}
136
137 QuicClientBase::~QuicClientBase() = default;
138
Initialize()139 bool QuicClientBase::Initialize() {
140 num_sent_client_hellos_ = 0;
141 connection_error_ = QUIC_NO_ERROR;
142 connected_or_attempting_connect_ = false;
143
144 // If an initial flow control window has not explicitly been set, then use the
145 // same values that Chrome uses.
146 const uint32_t kSessionMaxRecvWindowSize = 15 * 1024 * 1024; // 15 MB
147 const uint32_t kStreamMaxRecvWindowSize = 6 * 1024 * 1024; // 6 MB
148 if (config()->GetInitialStreamFlowControlWindowToSend() ==
149 kDefaultFlowControlSendWindow) {
150 config()->SetInitialStreamFlowControlWindowToSend(kStreamMaxRecvWindowSize);
151 }
152 if (config()->GetInitialSessionFlowControlWindowToSend() ==
153 kDefaultFlowControlSendWindow) {
154 config()->SetInitialSessionFlowControlWindowToSend(
155 kSessionMaxRecvWindowSize);
156 }
157
158 if (!network_helper_->CreateUDPSocketAndBind(server_address_,
159 bind_to_address_, local_port_)) {
160 return false;
161 }
162
163 initialized_ = true;
164 return true;
165 }
166
Connect()167 bool QuicClientBase::Connect() {
168 // Attempt multiple connects until the maximum number of client hellos have
169 // been sent.
170 int num_attempts = 0;
171 while (!connected() &&
172 num_attempts <= QuicCryptoClientStream::kMaxClientHellos) {
173 StartConnect();
174 while (EncryptionBeingEstablished()) {
175 WaitForEvents();
176 }
177 ParsedQuicVersion version = UnsupportedQuicVersion();
178 if (session() != nullptr && !CanReconnectWithDifferentVersion(&version)) {
179 // We've successfully created a session but we're not connected, and we
180 // cannot reconnect with a different version. Give up trying.
181 break;
182 }
183 num_attempts++;
184 }
185 if (session() == nullptr) {
186 QUIC_BUG(quic_bug_10906_1) << "Missing session after Connect";
187 return false;
188 }
189 return session()->connection()->connected();
190 }
191
StartConnect()192 void QuicClientBase::StartConnect() {
193 QUICHE_DCHECK(initialized_);
194 QUICHE_DCHECK(!connected());
195 QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
196 ParsedQuicVersion mutual_version = UnsupportedQuicVersion();
197 const bool can_reconnect_with_different_version =
198 CanReconnectWithDifferentVersion(&mutual_version);
199 if (connected_or_attempting_connect()) {
200 // Clear queued up data if client can not try to connect with a different
201 // version.
202 if (!can_reconnect_with_different_version) {
203 ClearDataToResend();
204 }
205 // Before we destroy the last session and create a new one, gather its stats
206 // and update the stats for the overall connection.
207 UpdateStats();
208 }
209
210 const quic::ParsedQuicVersionVector client_supported_versions =
211 can_reconnect_with_different_version
212 ? ParsedQuicVersionVector{mutual_version}
213 : supported_versions();
214
215 session_ = CreateQuicClientSession(
216 client_supported_versions,
217 new QuicConnection(GetNextConnectionId(), QuicSocketAddress(),
218 server_address(), helper(), alarm_factory(), writer,
219 /* owns_writer= */ false, Perspective::IS_CLIENT,
220 client_supported_versions, connection_id_generator_));
221 if (can_reconnect_with_different_version) {
222 session()->set_client_original_supported_versions(supported_versions());
223 }
224 if (connection_debug_visitor_ != nullptr) {
225 session()->connection()->set_debug_visitor(connection_debug_visitor_);
226 }
227 session()->connection()->set_client_connection_id(GetClientConnectionId());
228 if (initial_max_packet_length_ != 0) {
229 session()->connection()->SetMaxPacketLength(initial_max_packet_length_);
230 }
231 // Reset |writer()| after |session()| so that the old writer outlives the old
232 // session.
233 set_writer(writer);
234 InitializeSession();
235 if (can_reconnect_with_different_version) {
236 // This is a reconnect using server supported |mutual_version|.
237 session()->connection()->SetVersionNegotiated();
238 }
239 set_connected_or_attempting_connect(true);
240 }
241
InitializeSession()242 void QuicClientBase::InitializeSession() { session()->Initialize(); }
243
Disconnect()244 void QuicClientBase::Disconnect() {
245 QUICHE_DCHECK(initialized_);
246
247 initialized_ = false;
248 if (connected()) {
249 session()->connection()->CloseConnection(
250 QUIC_PEER_GOING_AWAY, "Client disconnecting",
251 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
252 }
253
254 ClearDataToResend();
255
256 network_helper_->CleanUpAllUDPSockets();
257 }
258
proof_verifier() const259 ProofVerifier* QuicClientBase::proof_verifier() const {
260 return crypto_config_.proof_verifier();
261 }
262
EncryptionBeingEstablished()263 bool QuicClientBase::EncryptionBeingEstablished() {
264 return !session_->IsEncryptionEstablished() &&
265 session_->connection()->connected();
266 }
267
WaitForEvents()268 bool QuicClientBase::WaitForEvents() {
269 if (!connected()) {
270 QUIC_BUG(quic_bug_10906_2)
271 << "Cannot call WaitForEvents on non-connected client";
272 return false;
273 }
274
275 network_helper_->RunEventLoop();
276
277 return WaitForEventsPostprocessing();
278 }
279
WaitForEventsPostprocessing()280 bool QuicClientBase::WaitForEventsPostprocessing() {
281 QUICHE_DCHECK(session() != nullptr);
282 ParsedQuicVersion version = UnsupportedQuicVersion();
283 if (!connected() && CanReconnectWithDifferentVersion(&version)) {
284 QUIC_DLOG(INFO) << "Can reconnect with version: " << version
285 << ", attempting to reconnect.";
286
287 Connect();
288 }
289
290 return HasActiveRequests();
291 }
292
MigrateSocket(const QuicIpAddress & new_host)293 bool QuicClientBase::MigrateSocket(const QuicIpAddress& new_host) {
294 return MigrateSocketWithSpecifiedPort(new_host, local_port_);
295 }
296
MigrateSocketWithSpecifiedPort(const QuicIpAddress & new_host,int port)297 bool QuicClientBase::MigrateSocketWithSpecifiedPort(
298 const QuicIpAddress& new_host, int port) {
299 if (!connected()) {
300 QUICHE_DVLOG(1)
301 << "MigrateSocketWithSpecifiedPort failed as connection has closed";
302 return false;
303 }
304
305 network_helper_->CleanUpAllUDPSockets();
306 std::unique_ptr<QuicPacketWriter> writer =
307 CreateWriterForNewNetwork(new_host, port);
308 if (writer == nullptr) {
309 QUICHE_DVLOG(1)
310 << "MigrateSocketWithSpecifiedPort failed from writer creation";
311 return false;
312 }
313 if (!session()->MigratePath(network_helper_->GetLatestClientAddress(),
314 session()->connection()->peer_address(),
315 writer.get(), false)) {
316 QUICHE_DVLOG(1)
317 << "MigrateSocketWithSpecifiedPort failed from session()->MigratePath";
318 return false;
319 }
320 set_writer(writer.release());
321 return true;
322 }
323
ValidateAndMigrateSocket(const QuicIpAddress & new_host)324 bool QuicClientBase::ValidateAndMigrateSocket(const QuicIpAddress& new_host) {
325 QUICHE_DCHECK(VersionHasIetfQuicFrames(
326 session_->connection()->version().transport_version));
327 if (!connected()) {
328 return false;
329 }
330
331 std::unique_ptr<QuicPacketWriter> writer =
332 CreateWriterForNewNetwork(new_host, local_port_);
333 if (writer == nullptr) {
334 return false;
335 }
336 // Asynchronously start migration.
337 session_->ValidatePath(
338 std::make_unique<PathMigrationContext>(
339 std::move(writer), network_helper_->GetLatestClientAddress(),
340 session_->peer_address()),
341 std::make_unique<QuicClientSocketMigrationValidationResultDelegate>(this),
342 PathValidationReason::kConnectionMigration);
343 return true;
344 }
345
CreateWriterForNewNetwork(const QuicIpAddress & new_host,int port)346 std::unique_ptr<QuicPacketWriter> QuicClientBase::CreateWriterForNewNetwork(
347 const QuicIpAddress& new_host, int port) {
348 set_bind_to_address(new_host);
349 set_local_port(port);
350 if (!network_helper_->CreateUDPSocketAndBind(server_address_,
351 bind_to_address_, port)) {
352 return nullptr;
353 }
354
355 QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
356 QUIC_LOG_IF(WARNING, writer == writer_.get())
357 << "The new writer is wrapped in the same wrapper as the old one, thus "
358 "appearing to have the same address as the old one.";
359 return std::unique_ptr<QuicPacketWriter>(writer);
360 }
361
ChangeEphemeralPort()362 bool QuicClientBase::ChangeEphemeralPort() {
363 auto current_host = network_helper_->GetLatestClientAddress().host();
364 return MigrateSocketWithSpecifiedPort(current_host, 0 /*any ephemeral port*/);
365 }
366
session()367 QuicSession* QuicClientBase::session() { return session_.get(); }
368
session() const369 const QuicSession* QuicClientBase::session() const { return session_.get(); }
370
network_helper()371 QuicClientBase::NetworkHelper* QuicClientBase::network_helper() {
372 return network_helper_.get();
373 }
374
network_helper() const375 const QuicClientBase::NetworkHelper* QuicClientBase::network_helper() const {
376 return network_helper_.get();
377 }
378
WaitForStreamToClose(QuicStreamId id)379 void QuicClientBase::WaitForStreamToClose(QuicStreamId id) {
380 if (!connected()) {
381 QUIC_BUG(quic_bug_10906_3)
382 << "Cannot WaitForStreamToClose on non-connected client";
383 return;
384 }
385
386 while (connected() && !session_->IsClosedStream(id)) {
387 WaitForEvents();
388 }
389 }
390
WaitForOneRttKeysAvailable()391 bool QuicClientBase::WaitForOneRttKeysAvailable() {
392 if (!connected()) {
393 QUIC_BUG(quic_bug_10906_4)
394 << "Cannot WaitForOneRttKeysAvailable on non-connected client";
395 return false;
396 }
397
398 while (connected() && !session_->OneRttKeysAvailable()) {
399 WaitForEvents();
400 }
401
402 // If the handshake fails due to a timeout, the connection will be closed.
403 QUIC_LOG_IF(ERROR, !connected()) << "Handshake with server failed.";
404 return connected();
405 }
406
WaitForHandshakeConfirmed()407 bool QuicClientBase::WaitForHandshakeConfirmed() {
408 if (!session_->connection()->version().UsesTls()) {
409 return WaitForOneRttKeysAvailable();
410 }
411 // Otherwise, wait for receipt of HANDSHAKE_DONE frame.
412 while (connected() && session_->GetHandshakeState() < HANDSHAKE_CONFIRMED) {
413 WaitForEvents();
414 }
415
416 // If the handshake fails due to a timeout, the connection will be closed.
417 QUIC_LOG_IF(ERROR, !connected()) << "Handshake with server failed.";
418 return connected();
419 }
420
connected() const421 bool QuicClientBase::connected() const {
422 return session_.get() && session_->connection() &&
423 session_->connection()->connected();
424 }
425
goaway_received() const426 bool QuicClientBase::goaway_received() const {
427 return session_ != nullptr && session_->transport_goaway_received();
428 }
429
GetNumSentClientHellos()430 int QuicClientBase::GetNumSentClientHellos() {
431 // If we are not actively attempting to connect, the session object
432 // corresponds to the previous connection and should not be used.
433 const int current_session_hellos = !connected_or_attempting_connect_
434 ? 0
435 : GetNumSentClientHellosFromSession();
436 return num_sent_client_hellos_ + current_session_hellos;
437 }
438
UpdateStats()439 void QuicClientBase::UpdateStats() {
440 num_sent_client_hellos_ += GetNumSentClientHellosFromSession();
441 }
442
GetNumReceivedServerConfigUpdates()443 int QuicClientBase::GetNumReceivedServerConfigUpdates() {
444 // If we are not actively attempting to connect, the session object
445 // corresponds to the previous connection and should not be used.
446 return !connected_or_attempting_connect_
447 ? 0
448 : GetNumReceivedServerConfigUpdatesFromSession();
449 }
450
connection_error() const451 QuicErrorCode QuicClientBase::connection_error() const {
452 // Return the high-level error if there was one. Otherwise, return the
453 // connection error from the last session.
454 if (connection_error_ != QUIC_NO_ERROR) {
455 return connection_error_;
456 }
457 if (session_ == nullptr) {
458 return QUIC_NO_ERROR;
459 }
460 return session_->error();
461 }
462
GetNextConnectionId()463 QuicConnectionId QuicClientBase::GetNextConnectionId() {
464 if (server_connection_id_override_.has_value()) {
465 return *server_connection_id_override_;
466 }
467 return GenerateNewConnectionId();
468 }
469
GenerateNewConnectionId()470 QuicConnectionId QuicClientBase::GenerateNewConnectionId() {
471 return QuicUtils::CreateRandomConnectionId(server_connection_id_length_);
472 }
473
GetClientConnectionId()474 QuicConnectionId QuicClientBase::GetClientConnectionId() {
475 return QuicUtils::CreateRandomConnectionId(client_connection_id_length_);
476 }
477
CanReconnectWithDifferentVersion(ParsedQuicVersion * version) const478 bool QuicClientBase::CanReconnectWithDifferentVersion(
479 ParsedQuicVersion* version) const {
480 if (session_ == nullptr || session_->connection() == nullptr ||
481 session_->error() != QUIC_INVALID_VERSION) {
482 return false;
483 }
484
485 const auto& server_supported_versions =
486 session_->connection()->server_supported_versions();
487 if (server_supported_versions.empty()) {
488 return false;
489 }
490
491 for (const auto& client_version : supported_versions_) {
492 if (std::find(server_supported_versions.begin(),
493 server_supported_versions.end(),
494 client_version) != server_supported_versions.end()) {
495 *version = client_version;
496 return true;
497 }
498 }
499 return false;
500 }
501
HasPendingPathValidation()502 bool QuicClientBase::HasPendingPathValidation() {
503 return session()->HasPendingPathValidation();
504 }
505
506 class ValidationResultDelegate : public QuicPathValidator::ResultDelegate {
507 public:
ValidationResultDelegate(QuicClientBase * client)508 ValidationResultDelegate(QuicClientBase* client)
509 : QuicPathValidator::ResultDelegate(), client_(client) {}
510
OnPathValidationSuccess(std::unique_ptr<QuicPathValidationContext> context,QuicTime start_time)511 void OnPathValidationSuccess(
512 std::unique_ptr<QuicPathValidationContext> context,
513 QuicTime start_time) override {
514 QUIC_DLOG(INFO) << "Successfully validated path from " << *context
515 << ", validation started at " << start_time;
516 client_->AddValidatedPath(std::move(context));
517 }
OnPathValidationFailure(std::unique_ptr<QuicPathValidationContext> context)518 void OnPathValidationFailure(
519 std::unique_ptr<QuicPathValidationContext> context) override {
520 QUIC_LOG(WARNING) << "Fail to validate path " << *context
521 << ", stop migrating.";
522 client_->session()->connection()->OnPathValidationFailureAtClient(
523 /*is_multi_port=*/false, *context);
524 }
525
526 private:
527 QuicClientBase* client_;
528 };
529
ValidateNewNetwork(const QuicIpAddress & host)530 void QuicClientBase::ValidateNewNetwork(const QuicIpAddress& host) {
531 std::unique_ptr<QuicPacketWriter> writer =
532 CreateWriterForNewNetwork(host, local_port_);
533 auto result_delegate = std::make_unique<ValidationResultDelegate>(this);
534 if (writer == nullptr) {
535 result_delegate->OnPathValidationFailure(
536 std::make_unique<PathMigrationContext>(
537 nullptr, network_helper_->GetLatestClientAddress(),
538 session_->peer_address()));
539 return;
540 }
541 session()->ValidatePath(
542 std::make_unique<PathMigrationContext>(
543 std::move(writer), network_helper_->GetLatestClientAddress(),
544 session_->peer_address()),
545 std::move(result_delegate), PathValidationReason::kConnectionMigration);
546 }
547
OnServerPreferredAddressAvailable(const QuicSocketAddress & server_preferred_address)548 void QuicClientBase::OnServerPreferredAddressAvailable(
549 const QuicSocketAddress& server_preferred_address) {
550 const auto self_address = session_->self_address();
551 if (network_helper_ == nullptr ||
552 !network_helper_->CreateUDPSocketAndBind(server_preferred_address,
553 self_address.host(), 0)) {
554 return;
555 }
556 QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
557 if (writer == nullptr) {
558 return;
559 }
560 session()->ValidatePath(
561 std::make_unique<PathMigrationContext>(
562 std::unique_ptr<QuicPacketWriter>(writer),
563 network_helper_->GetLatestClientAddress(), server_preferred_address),
564 std::make_unique<ServerPreferredAddressResultDelegateWithWriter>(this),
565 PathValidationReason::kServerPreferredAddressMigration);
566 }
567
OnPathDegrading()568 void QuicClientBase::OnPathDegrading() {
569 if (!allow_port_migration_ ||
570 session_->GetHandshakeState() != HANDSHAKE_CONFIRMED ||
571 session_->HasPendingPathValidation() ||
572 session_->connection()->multi_port_stats() != nullptr ||
573 config_.DisableConnectionMigration()) {
574 return;
575 }
576 const auto self_address = session_->self_address();
577 if (network_helper_ == nullptr ||
578 !network_helper_->CreateUDPSocketAndBind(session_->peer_address(),
579 self_address.host(), 0)) {
580 return;
581 }
582 QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
583 if (writer == nullptr) {
584 return;
585 }
586 session()->ValidatePath(
587 std::make_unique<PathMigrationContext>(
588 std::unique_ptr<QuicPacketWriter>(writer),
589 network_helper_->GetLatestClientAddress(), session_->peer_address()),
590 std::make_unique<QuicClientSocketMigrationValidationResultDelegate>(this),
591 PathValidationReason::kPortMigration);
592 if (!session()->HasPendingPathValidation()) {
593 QUIC_CODE_COUNT(fail_to_probe_new_path_after_current_one_degraded);
594 }
595 }
596
597 } // namespace quic
598