xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/quic/tools/quic_toy_client.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright (c) 2012 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 // A binary wrapper for QuicClient.
6 // Connects to a host using QUIC, sends a request to the provided URL, and
7 // displays the response.
8 //
9 // Some usage examples:
10 //
11 // Standard request/response:
12 //   quic_client www.google.com
13 //   quic_client www.google.com --quiet
14 //   quic_client www.google.com --port=443
15 //
16 // Use a specific version:
17 //   quic_client www.google.com --quic_version=23
18 //
19 // Send a POST instead of a GET:
20 //   quic_client www.google.com --body="this is a POST body"
21 //
22 // Append additional headers to the request:
23 //   quic_client www.google.com --headers="Header-A: 1234; Header-B: 5678"
24 //
25 // Connect to a host different to the URL being requested:
26 //   quic_client mail.google.com --host=www.google.com
27 //
28 // Connect to a specific IP:
29 //   IP=`dig www.google.com +short | head -1`
30 //   quic_client www.google.com --host=${IP}
31 //
32 // Send repeated requests and change ephemeral port between requests
33 //   quic_client www.google.com --num_requests=10
34 //
35 // Try to connect to a host which does not speak QUIC:
36 //   quic_client www.example.com
37 //
38 // This tool is available as a built binary at:
39 // /google/data/ro/teams/quic/tools/quic_client
40 // After submitting changes to this file, you will need to follow the
41 // instructions at go/quic_client_binary_update
42 
43 #include "quiche/quic/tools/quic_toy_client.h"
44 
45 #include <fstream>
46 #include <iostream>
47 #include <memory>
48 #include <string>
49 #include <utility>
50 #include <vector>
51 
52 #include "absl/strings/escaping.h"
53 #include "absl/strings/str_split.h"
54 #include "absl/strings/string_view.h"
55 #include "quiche/quic/core/crypto/quic_client_session_cache.h"
56 #include "quiche/quic/core/quic_packets.h"
57 #include "quiche/quic/core/quic_server_id.h"
58 #include "quiche/quic/core/quic_utils.h"
59 #include "quiche/quic/core/quic_versions.h"
60 #include "quiche/quic/platform/api/quic_default_proof_providers.h"
61 #include "quiche/quic/platform/api/quic_ip_address.h"
62 #include "quiche/quic/platform/api/quic_socket_address.h"
63 #include "quiche/quic/tools/fake_proof_verifier.h"
64 #include "quiche/quic/tools/quic_url.h"
65 #include "quiche/common/platform/api/quiche_command_line_flags.h"
66 #include "quiche/common/platform/api/quiche_logging.h"
67 #include "quiche/common/quiche_text_utils.h"
68 #include "quiche/spdy/core/http2_header_block.h"
69 
70 namespace {
71 
72 using quiche::QuicheTextUtils;
73 
74 }  // namespace
75 
76 DEFINE_QUICHE_COMMAND_LINE_FLAG(
77     std::string, host, "",
78     "The IP or hostname to connect to. If not provided, the host "
79     "will be derived from the provided URL.");
80 
81 DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
82 
83 DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, ip_version_for_host_lookup, "",
84                                 "Only used if host address lookup is needed. "
85                                 "4=ipv4; 6=ipv6; otherwise=don't care.");
86 
87 DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, body, "",
88                                 "If set, send a POST with this body.");
89 
90 DEFINE_QUICHE_COMMAND_LINE_FLAG(
91     std::string, body_hex, "",
92     "If set, contents are converted from hex to ascii, before "
93     "sending as body of a POST. e.g. --body_hex=\"68656c6c6f\"");
94 
95 DEFINE_QUICHE_COMMAND_LINE_FLAG(
96     std::string, headers, "",
97     "A semicolon separated list of key:value pairs to "
98     "add to request headers.");
99 
100 DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, quiet, false,
101                                 "Set to true for a quieter output experience.");
102 
103 DEFINE_QUICHE_COMMAND_LINE_FLAG(
104     bool, output_resolved_server_address, false,
105     "Set to true to print the resolved IP of the server.");
106 
107 DEFINE_QUICHE_COMMAND_LINE_FLAG(
108     std::string, quic_version, "",
109     "QUIC version to speak, e.g. 21. If not set, then all available "
110     "versions are offered in the handshake. Also supports wire versions "
111     "such as Q043 or T099.");
112 
113 DEFINE_QUICHE_COMMAND_LINE_FLAG(
114     std::string, connection_options, "",
115     "Connection options as ASCII tags separated by commas, "
116     "e.g. \"ABCD,EFGH\"");
117 
118 DEFINE_QUICHE_COMMAND_LINE_FLAG(
119     std::string, client_connection_options, "",
120     "Client connection options as ASCII tags separated by commas, "
121     "e.g. \"ABCD,EFGH\"");
122 
123 DEFINE_QUICHE_COMMAND_LINE_FLAG(
124     bool, version_mismatch_ok, false,
125     "If true, a version mismatch in the handshake is not considered a "
126     "failure. Useful for probing a server to determine if it speaks "
127     "any version of QUIC.");
128 
129 DEFINE_QUICHE_COMMAND_LINE_FLAG(
130     bool, force_version_negotiation, false,
131     "If true, start by proposing a version that is reserved for version "
132     "negotiation.");
133 
134 DEFINE_QUICHE_COMMAND_LINE_FLAG(
135     bool, multi_packet_chlo, false,
136     "If true, add a transport parameter to make the ClientHello span two "
137     "packets. Only works with QUIC+TLS.");
138 
139 DEFINE_QUICHE_COMMAND_LINE_FLAG(
140     bool, redirect_is_success, true,
141     "If true, an HTTP response code of 3xx is considered to be a "
142     "successful response, otherwise a failure.");
143 
144 DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, initial_mtu, 0,
145                                 "Initial MTU of the connection.");
146 
147 DEFINE_QUICHE_COMMAND_LINE_FLAG(
148     int32_t, num_requests, 1,
149     "How many sequential requests to make on a single connection.");
150 
151 DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, ignore_errors, false,
152                                 "If true, ignore connection/response errors "
153                                 "and send all num_requests anyway.");
154 
155 DEFINE_QUICHE_COMMAND_LINE_FLAG(
156     bool, disable_certificate_verification, false,
157     "If true, don't verify the server certificate.");
158 
159 DEFINE_QUICHE_COMMAND_LINE_FLAG(
160     std::string, default_client_cert, "",
161     "The path to the file containing PEM-encoded client default certificate to "
162     "be sent to the server, if server requested client certs.");
163 
164 DEFINE_QUICHE_COMMAND_LINE_FLAG(
165     std::string, default_client_cert_key, "",
166     "The path to the file containing PEM-encoded private key of the client's "
167     "default certificate for signing, if server requested client certs.");
168 
169 DEFINE_QUICHE_COMMAND_LINE_FLAG(
170     bool, drop_response_body, false,
171     "If true, drop response body immediately after it is received.");
172 
173 DEFINE_QUICHE_COMMAND_LINE_FLAG(
174     bool, disable_port_changes, false,
175     "If true, do not change local port after each request.");
176 
177 DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, one_connection_per_request, false,
178                                 "If true, close the connection after each "
179                                 "request. This allows testing 0-RTT.");
180 
181 DEFINE_QUICHE_COMMAND_LINE_FLAG(
182     std::string, server_connection_id, "",
183     "If non-empty, the client will use the given server connection id for all "
184     "connections. The flag value is the hex-string of the on-wire connection id"
185     " bytes, e.g. '--server_connection_id=0123456789abcdef'.");
186 
187 DEFINE_QUICHE_COMMAND_LINE_FLAG(
188     int32_t, server_connection_id_length, -1,
189     "Length of the server connection ID used. This flag has no effects if "
190     "--server_connection_id is non-empty.");
191 
192 DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, client_connection_id_length, -1,
193                                 "Length of the client connection ID used.");
194 
195 DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, max_time_before_crypto_handshake_ms,
196                                 10000,
197                                 "Max time to wait before handshake completes.");
198 
199 DEFINE_QUICHE_COMMAND_LINE_FLAG(
200     int32_t, max_inbound_header_list_size, 128 * 1024,
201     "Max inbound header list size. 0 means default.");
202 
203 DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, interface_name, "",
204                                 "Interface name to bind QUIC UDP sockets to.");
205 
206 DEFINE_QUICHE_COMMAND_LINE_FLAG(
207     std::string, signing_algorithms_pref, "",
208     "A textual specification of a set of signature algorithms that can be "
209     "accepted by boring SSL SSL_set1_sigalgs_list()");
210 
211 namespace quic {
212 namespace {
213 
214 // Creates a ClientProofSource which only contains a default client certificate.
215 // Return nullptr for failure.
CreateTestClientProofSource(absl::string_view default_client_cert_file,absl::string_view default_client_cert_key_file)216 std::unique_ptr<ClientProofSource> CreateTestClientProofSource(
217     absl::string_view default_client_cert_file,
218     absl::string_view default_client_cert_key_file) {
219   std::ifstream cert_stream(std::string{default_client_cert_file},
220                             std::ios::binary);
221   std::vector<std::string> certs =
222       CertificateView::LoadPemFromStream(&cert_stream);
223   if (certs.empty()) {
224     std::cerr << "Failed to load client certs." << std::endl;
225     return nullptr;
226   }
227 
228   std::ifstream key_stream(std::string{default_client_cert_key_file},
229                            std::ios::binary);
230   std::unique_ptr<CertificatePrivateKey> private_key =
231       CertificatePrivateKey::LoadPemFromStream(&key_stream);
232   if (private_key == nullptr) {
233     std::cerr << "Failed to load client cert key." << std::endl;
234     return nullptr;
235   }
236 
237   auto proof_source = std::make_unique<DefaultClientProofSource>();
238   proof_source->AddCertAndKey(
239       {"*"},
240       quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>(
241           new ClientProofSource::Chain(certs)),
242       std::move(*private_key));
243 
244   return proof_source;
245 }
246 
247 }  // namespace
248 
QuicToyClient(ClientFactory * client_factory)249 QuicToyClient::QuicToyClient(ClientFactory* client_factory)
250     : client_factory_(client_factory) {}
251 
SendRequestsAndPrintResponses(std::vector<std::string> urls)252 int QuicToyClient::SendRequestsAndPrintResponses(
253     std::vector<std::string> urls) {
254   QuicUrl url(urls[0], "https");
255   std::string host = quiche::GetQuicheCommandLineFlag(FLAGS_host);
256   if (host.empty()) {
257     host = url.host();
258   }
259   int port = quiche::GetQuicheCommandLineFlag(FLAGS_port);
260   if (port == 0) {
261     port = url.port();
262   }
263 
264   quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions();
265 
266   std::string quic_version_string =
267       quiche::GetQuicheCommandLineFlag(FLAGS_quic_version);
268   if (!quic_version_string.empty()) {
269     versions = quic::ParseQuicVersionVectorString(quic_version_string);
270   }
271 
272   if (versions.empty()) {
273     std::cerr << "No known version selected." << std::endl;
274     return 1;
275   }
276 
277   for (const quic::ParsedQuicVersion& version : versions) {
278     quic::QuicEnableVersion(version);
279   }
280 
281   if (quiche::GetQuicheCommandLineFlag(FLAGS_force_version_negotiation)) {
282     versions.insert(versions.begin(),
283                     quic::QuicVersionReservedForNegotiation());
284   }
285 
286   const int32_t num_requests(
287       quiche::GetQuicheCommandLineFlag(FLAGS_num_requests));
288   std::unique_ptr<quic::ProofVerifier> proof_verifier;
289   if (quiche::GetQuicheCommandLineFlag(
290           FLAGS_disable_certificate_verification)) {
291     proof_verifier = std::make_unique<FakeProofVerifier>();
292   } else {
293     proof_verifier = quic::CreateDefaultProofVerifier(url.host());
294   }
295   std::unique_ptr<quic::SessionCache> session_cache;
296   if (num_requests > 1 &&
297       quiche::GetQuicheCommandLineFlag(FLAGS_one_connection_per_request)) {
298     session_cache = std::make_unique<QuicClientSessionCache>();
299   }
300 
301   QuicConfig config;
302   std::string connection_options_string =
303       quiche::GetQuicheCommandLineFlag(FLAGS_connection_options);
304   if (!connection_options_string.empty()) {
305     config.SetConnectionOptionsToSend(
306         ParseQuicTagVector(connection_options_string));
307   }
308   std::string client_connection_options_string =
309       quiche::GetQuicheCommandLineFlag(FLAGS_client_connection_options);
310   if (!client_connection_options_string.empty()) {
311     config.SetClientConnectionOptions(
312         ParseQuicTagVector(client_connection_options_string));
313   }
314   if (quiche::GetQuicheCommandLineFlag(FLAGS_multi_packet_chlo)) {
315     // Make the ClientHello span multiple packets by adding a custom transport
316     // parameter.
317     constexpr auto kCustomParameter =
318         static_cast<TransportParameters::TransportParameterId>(0x173E);
319     std::string custom_value(2000, '?');
320     config.custom_transport_parameters_to_send()[kCustomParameter] =
321         custom_value;
322   }
323   config.set_max_time_before_crypto_handshake(
324       QuicTime::Delta::FromMilliseconds(quiche::GetQuicheCommandLineFlag(
325           FLAGS_max_time_before_crypto_handshake_ms)));
326 
327   int address_family_for_lookup = AF_UNSPEC;
328   if (quiche::GetQuicheCommandLineFlag(FLAGS_ip_version_for_host_lookup) ==
329       "4") {
330     address_family_for_lookup = AF_INET;
331   } else if (quiche::GetQuicheCommandLineFlag(
332                  FLAGS_ip_version_for_host_lookup) == "6") {
333     address_family_for_lookup = AF_INET6;
334   }
335 
336   // Build the client, and try to connect.
337   std::unique_ptr<QuicSpdyClientBase> client = client_factory_->CreateClient(
338       url.host(), host, address_family_for_lookup, port, versions, config,
339       std::move(proof_verifier), std::move(session_cache));
340 
341   if (client == nullptr) {
342     std::cerr << "Failed to create client." << std::endl;
343     return 1;
344   }
345 
346   if (!quiche::GetQuicheCommandLineFlag(FLAGS_default_client_cert).empty() &&
347       !quiche::GetQuicheCommandLineFlag(FLAGS_default_client_cert_key)
348            .empty()) {
349     std::unique_ptr<ClientProofSource> proof_source =
350         CreateTestClientProofSource(
351             quiche::GetQuicheCommandLineFlag(FLAGS_default_client_cert),
352             quiche::GetQuicheCommandLineFlag(FLAGS_default_client_cert_key));
353     if (proof_source == nullptr) {
354       std::cerr << "Failed to create client proof source." << std::endl;
355       return 1;
356     }
357     client->crypto_config()->set_proof_source(std::move(proof_source));
358   }
359 
360   int32_t initial_mtu = quiche::GetQuicheCommandLineFlag(FLAGS_initial_mtu);
361   client->set_initial_max_packet_length(
362       initial_mtu != 0 ? initial_mtu : quic::kDefaultMaxPacketSize);
363   client->set_drop_response_body(
364       quiche::GetQuicheCommandLineFlag(FLAGS_drop_response_body));
365   const std::string server_connection_id_hex_string =
366       quiche::GetQuicheCommandLineFlag(FLAGS_server_connection_id);
367   QUICHE_CHECK(server_connection_id_hex_string.size() % 2 == 0)
368       << "The length of --server_connection_id must be even. It is "
369       << server_connection_id_hex_string.size() << "-byte long.";
370   if (!server_connection_id_hex_string.empty()) {
371     std::string server_connection_id_bytes;
372     QUICHE_CHECK(absl::HexStringToBytes(server_connection_id_hex_string,
373                                         &server_connection_id_bytes))
374         << "Failed to parse --server_connection_id hex string.";
375     client->set_server_connection_id_override(QuicConnectionId(
376         server_connection_id_bytes.data(), server_connection_id_bytes.size()));
377   }
378   const int32_t server_connection_id_length =
379       quiche::GetQuicheCommandLineFlag(FLAGS_server_connection_id_length);
380   if (server_connection_id_length >= 0) {
381     client->set_server_connection_id_length(server_connection_id_length);
382   }
383   const int32_t client_connection_id_length =
384       quiche::GetQuicheCommandLineFlag(FLAGS_client_connection_id_length);
385   if (client_connection_id_length >= 0) {
386     client->set_client_connection_id_length(client_connection_id_length);
387   }
388   const size_t max_inbound_header_list_size =
389       quiche::GetQuicheCommandLineFlag(FLAGS_max_inbound_header_list_size);
390   if (max_inbound_header_list_size > 0) {
391     client->set_max_inbound_header_list_size(max_inbound_header_list_size);
392   }
393   const std::string interface_name =
394       quiche::GetQuicheCommandLineFlag(FLAGS_interface_name);
395   if (!interface_name.empty()) {
396     client->set_interface_name(interface_name);
397   }
398   const std::string signing_algorithms_pref =
399       quiche::GetQuicheCommandLineFlag(FLAGS_signing_algorithms_pref);
400   if (!signing_algorithms_pref.empty()) {
401     client->SetTlsSignatureAlgorithms(signing_algorithms_pref);
402   }
403   if (!client->Initialize()) {
404     std::cerr << "Failed to initialize client." << std::endl;
405     return 1;
406   }
407   if (!client->Connect()) {
408     quic::QuicErrorCode error = client->session()->error();
409     if (error == quic::QUIC_INVALID_VERSION) {
410       std::cerr << "Failed to negotiate version with " << host << ":" << port
411                 << ". " << client->session()->error_details() << std::endl;
412       // 0: No error.
413       // 20: Failed to connect due to QUIC_INVALID_VERSION.
414       return quiche::GetQuicheCommandLineFlag(FLAGS_version_mismatch_ok) ? 0
415                                                                          : 20;
416     }
417     std::cerr << "Failed to connect to " << host << ":" << port << ". "
418               << quic::QuicErrorCodeToString(error) << " "
419               << client->session()->error_details() << std::endl;
420     return 1;
421   }
422 
423   std::cout << "Connected to " << host << ":" << port;
424   if (quiche::GetQuicheCommandLineFlag(FLAGS_output_resolved_server_address)) {
425     std::cout << ", resolved IP " << client->server_address().host().ToString();
426   }
427   std::cout << std::endl;
428 
429   // Construct the string body from flags, if provided.
430   std::string body = quiche::GetQuicheCommandLineFlag(FLAGS_body);
431   if (!quiche::GetQuicheCommandLineFlag(FLAGS_body_hex).empty()) {
432     QUICHE_DCHECK(quiche::GetQuicheCommandLineFlag(FLAGS_body).empty())
433         << "Only set one of --body and --body_hex.";
434     const bool success = absl::HexStringToBytes(
435         quiche::GetQuicheCommandLineFlag(FLAGS_body_hex), &body);
436     QUICHE_DCHECK(success) << "Failed to parse --body_hex.";
437   }
438 
439   // Construct a GET or POST request for supplied URL.
440   spdy::Http2HeaderBlock header_block;
441   header_block[":method"] = body.empty() ? "GET" : "POST";
442   header_block[":scheme"] = url.scheme();
443   header_block[":authority"] = url.HostPort();
444   header_block[":path"] = url.PathParamsQuery();
445 
446   // Append any additional headers supplied on the command line.
447   const std::string headers = quiche::GetQuicheCommandLineFlag(FLAGS_headers);
448   for (absl::string_view sp : absl::StrSplit(headers, ';')) {
449     QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&sp);
450     if (sp.empty()) {
451       continue;
452     }
453     std::vector<absl::string_view> kv =
454         absl::StrSplit(sp, absl::MaxSplits(':', 1));
455     QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]);
456     QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[1]);
457     header_block[kv[0]] = kv[1];
458   }
459 
460   // Make sure to store the response, for later output.
461   client->set_store_response(true);
462 
463   for (int i = 0; i < num_requests; ++i) {
464     // Send the request.
465     client->SendRequestAndWaitForResponse(header_block, body, /*fin=*/true);
466 
467     // Print request and response details.
468     if (!quiche::GetQuicheCommandLineFlag(FLAGS_quiet)) {
469       std::cout << "Request:" << std::endl;
470       std::cout << "headers:" << header_block.DebugString();
471       if (!quiche::GetQuicheCommandLineFlag(FLAGS_body_hex).empty()) {
472         // Print the user provided hex, rather than binary body.
473         std::cout << "body:\n" << QuicheTextUtils::HexDump(body) << std::endl;
474       } else {
475         std::cout << "body: " << body << std::endl;
476       }
477       std::cout << std::endl;
478 
479       if (!client->preliminary_response_headers().empty()) {
480         std::cout << "Preliminary response headers: "
481                   << client->preliminary_response_headers() << std::endl;
482         std::cout << std::endl;
483       }
484 
485       std::cout << "Response:" << std::endl;
486       std::cout << "headers: " << client->latest_response_headers()
487                 << std::endl;
488       std::string response_body = client->latest_response_body();
489       if (!quiche::GetQuicheCommandLineFlag(FLAGS_body_hex).empty()) {
490         // Assume response is binary data.
491         std::cout << "body:\n"
492                   << QuicheTextUtils::HexDump(response_body) << std::endl;
493       } else {
494         std::cout << "body: " << response_body << std::endl;
495       }
496       std::cout << "trailers: " << client->latest_response_trailers()
497                 << std::endl;
498       std::cout << "early data accepted: " << client->EarlyDataAccepted()
499                 << std::endl;
500       QUIC_LOG(INFO) << "Request completed with TTFB(us): "
501                      << client->latest_ttfb().ToMicroseconds() << ", TTLB(us): "
502                      << client->latest_ttlb().ToMicroseconds();
503     }
504 
505     if (!client->connected()) {
506       std::cerr << "Request caused connection failure. Error: "
507                 << quic::QuicErrorCodeToString(client->session()->error())
508                 << std::endl;
509       if (!quiche::GetQuicheCommandLineFlag(FLAGS_ignore_errors)) {
510         return 1;
511       }
512     }
513 
514     int response_code = client->latest_response_code();
515     if (response_code >= 200 && response_code < 300) {
516       std::cout << "Request succeeded (" << response_code << ")." << std::endl;
517     } else if (response_code >= 300 && response_code < 400) {
518       if (quiche::GetQuicheCommandLineFlag(FLAGS_redirect_is_success)) {
519         std::cout << "Request succeeded (redirect " << response_code << ")."
520                   << std::endl;
521       } else {
522         std::cout << "Request failed (redirect " << response_code << ")."
523                   << std::endl;
524         if (!quiche::GetQuicheCommandLineFlag(FLAGS_ignore_errors)) {
525           return 1;
526         }
527       }
528     } else {
529       std::cout << "Request failed (" << response_code << ")." << std::endl;
530       if (!quiche::GetQuicheCommandLineFlag(FLAGS_ignore_errors)) {
531         return 1;
532       }
533     }
534 
535     if (i + 1 < num_requests) {  // There are more requests to perform.
536       if (quiche::GetQuicheCommandLineFlag(FLAGS_one_connection_per_request)) {
537         std::cout << "Disconnecting client between requests." << std::endl;
538         client->Disconnect();
539         if (!client->Initialize()) {
540           std::cerr << "Failed to reinitialize client between requests."
541                     << std::endl;
542           return 1;
543         }
544         if (!client->Connect()) {
545           std::cerr << "Failed to reconnect client between requests."
546                     << std::endl;
547           if (!quiche::GetQuicheCommandLineFlag(FLAGS_ignore_errors)) {
548             return 1;
549           }
550         }
551       } else if (!quiche::GetQuicheCommandLineFlag(
552                      FLAGS_disable_port_changes)) {
553         // Change the ephemeral port.
554         if (!client->ChangeEphemeralPort()) {
555           std::cerr << "Failed to change ephemeral port." << std::endl;
556           return 1;
557         }
558       }
559     }
560   }
561 
562   return 0;
563 }
564 
565 }  // namespace quic
566