xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/quic/tools/quic_client_interop_test_bin.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright (c) 2019 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 <iostream>
6 #include <memory>
7 #include <string>
8 #include <utility>
9 
10 #include "absl/strings/str_cat.h"
11 #include "quiche/quic/core/crypto/quic_client_session_cache.h"
12 #include "quiche/quic/core/io/quic_default_event_loop.h"
13 #include "quiche/quic/core/io/quic_event_loop.h"
14 #include "quiche/quic/core/quic_default_clock.h"
15 #include "quiche/quic/core/quic_types.h"
16 #include "quiche/quic/core/quic_versions.h"
17 #include "quiche/quic/test_tools/quic_connection_peer.h"
18 #include "quiche/quic/test_tools/quic_session_peer.h"
19 #include "quiche/quic/tools/fake_proof_verifier.h"
20 #include "quiche/quic/tools/quic_default_client.h"
21 #include "quiche/quic/tools/quic_name_lookup.h"
22 #include "quiche/quic/tools/quic_url.h"
23 #include "quiche/common/platform/api/quiche_command_line_flags.h"
24 #include "quiche/common/platform/api/quiche_system_event_loop.h"
25 #include "quiche/spdy/core/http2_header_block.h"
26 
27 DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, host, "",
28                                 "The IP or hostname to connect to.");
29 
30 DEFINE_QUICHE_COMMAND_LINE_FLAG(
31     std::string, quic_version, "",
32     "The QUIC version to use. Defaults to most recent IETF QUIC version.");
33 
34 DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
35 
36 namespace quic {
37 
38 enum class Feature {
39   // First row of features ("table stakes")
40   // A version negotiation response is elicited and acted on.
41   kVersionNegotiation,
42   // The handshake completes successfully.
43   kHandshake,
44   // Stream data is being exchanged and ACK'ed.
45   kStreamData,
46   // The connection close procedcure completes with a zero error code.
47   kConnectionClose,
48   // The connection was established using TLS resumption.
49   kResumption,
50   // 0-RTT data is being sent and acted on.
51   kZeroRtt,
52   // A RETRY packet was successfully processed.
53   kRetry,
54   // A handshake using a ClientHello that spans multiple packets completed
55   // successfully.
56   kQuantum,
57 
58   // Second row of features (anything else protocol-related)
59   // We switched to a different port and the server migrated to it.
60   kRebinding,
61   // One endpoint can update keys and its peer responds correctly.
62   kKeyUpdate,
63 
64   // Third row of features (H3 tests)
65   // An H3 transaction succeeded.
66   kHttp3,
67   // One or both endpoints insert entries into dynamic table and subsequenly
68   // reference them from header blocks.
69   kDynamicEntryReferenced,
70 };
71 
MatrixLetter(Feature f)72 char MatrixLetter(Feature f) {
73   switch (f) {
74     case Feature::kVersionNegotiation:
75       return 'V';
76     case Feature::kHandshake:
77       return 'H';
78     case Feature::kStreamData:
79       return 'D';
80     case Feature::kConnectionClose:
81       return 'C';
82     case Feature::kResumption:
83       return 'R';
84     case Feature::kZeroRtt:
85       return 'Z';
86     case Feature::kRetry:
87       return 'S';
88     case Feature::kQuantum:
89       return 'Q';
90     case Feature::kRebinding:
91       return 'B';
92     case Feature::kKeyUpdate:
93       return 'U';
94     case Feature::kHttp3:
95       return '3';
96     case Feature::kDynamicEntryReferenced:
97       return 'd';
98   }
99 }
100 
101 class QuicClientInteropRunner : QuicConnectionDebugVisitor {
102  public:
QuicClientInteropRunner()103   QuicClientInteropRunner() {}
104 
InsertFeature(Feature feature)105   void InsertFeature(Feature feature) { features_.insert(feature); }
106 
features() const107   std::set<Feature> features() const { return features_; }
108 
109   // Attempts a resumption using |client| by disconnecting and reconnecting. If
110   // resumption is successful, |features_| is modified to add
111   // Feature::kResumption to it, otherwise it is left unmodified.
112   void AttemptResumption(QuicDefaultClient* client,
113                          const std::string& authority);
114 
115   void AttemptRequest(QuicSocketAddress addr, std::string authority,
116                       QuicServerId server_id, ParsedQuicVersion version,
117                       bool test_version_negotiation, bool attempt_rebind,
118                       bool attempt_multi_packet_chlo, bool attempt_key_update);
119 
120   // Constructs a Http2HeaderBlock containing the pseudo-headers needed to make
121   // a GET request to "/" on the hostname |authority|.
122   spdy::Http2HeaderBlock ConstructHeaderBlock(const std::string& authority);
123 
124   // Sends an HTTP request represented by |header_block| using |client|.
125   void SendRequest(QuicDefaultClient* client,
126                    const spdy::Http2HeaderBlock& header_block);
127 
OnConnectionCloseFrame(const QuicConnectionCloseFrame & frame)128   void OnConnectionCloseFrame(const QuicConnectionCloseFrame& frame) override {
129     switch (frame.close_type) {
130       case GOOGLE_QUIC_CONNECTION_CLOSE:
131         QUIC_LOG(ERROR) << "Received unexpected GoogleQUIC connection close";
132         break;
133       case IETF_QUIC_TRANSPORT_CONNECTION_CLOSE:
134         if (frame.wire_error_code == NO_IETF_QUIC_ERROR) {
135           InsertFeature(Feature::kConnectionClose);
136         } else {
137           QUIC_LOG(ERROR) << "Received transport connection close "
138                           << QuicIetfTransportErrorCodeString(
139                                  static_cast<QuicIetfTransportErrorCodes>(
140                                      frame.wire_error_code));
141         }
142         break;
143       case IETF_QUIC_APPLICATION_CONNECTION_CLOSE:
144         if (frame.wire_error_code == 0) {
145           InsertFeature(Feature::kConnectionClose);
146         } else {
147           QUIC_LOG(ERROR) << "Received application connection close "
148                           << frame.wire_error_code;
149         }
150         break;
151     }
152   }
153 
OnVersionNegotiationPacket(const QuicVersionNegotiationPacket &)154   void OnVersionNegotiationPacket(
155       const QuicVersionNegotiationPacket& /*packet*/) override {
156     InsertFeature(Feature::kVersionNegotiation);
157   }
158 
159  private:
160   std::set<Feature> features_;
161 };
162 
AttemptResumption(QuicDefaultClient * client,const std::string & authority)163 void QuicClientInteropRunner::AttemptResumption(QuicDefaultClient* client,
164                                                 const std::string& authority) {
165   client->Disconnect();
166   if (!client->Initialize()) {
167     QUIC_LOG(ERROR) << "Failed to reinitialize client";
168     return;
169   }
170   if (!client->Connect()) {
171     return;
172   }
173 
174   bool zero_rtt_attempt = !client->session()->OneRttKeysAvailable();
175 
176   spdy::Http2HeaderBlock header_block = ConstructHeaderBlock(authority);
177   SendRequest(client, header_block);
178 
179   if (!client->session()->OneRttKeysAvailable()) {
180     return;
181   }
182 
183   if (static_cast<QuicCryptoClientStream*>(
184           test::QuicSessionPeer::GetMutableCryptoStream(client->session()))
185           ->IsResumption()) {
186     InsertFeature(Feature::kResumption);
187   }
188   if (static_cast<QuicCryptoClientStream*>(
189           test::QuicSessionPeer::GetMutableCryptoStream(client->session()))
190           ->EarlyDataAccepted() &&
191       zero_rtt_attempt && client->latest_response_code() != -1) {
192     InsertFeature(Feature::kZeroRtt);
193   }
194 }
195 
AttemptRequest(QuicSocketAddress addr,std::string authority,QuicServerId server_id,ParsedQuicVersion version,bool test_version_negotiation,bool attempt_rebind,bool attempt_multi_packet_chlo,bool attempt_key_update)196 void QuicClientInteropRunner::AttemptRequest(
197     QuicSocketAddress addr, std::string authority, QuicServerId server_id,
198     ParsedQuicVersion version, bool test_version_negotiation,
199     bool attempt_rebind, bool attempt_multi_packet_chlo,
200     bool attempt_key_update) {
201   ParsedQuicVersionVector versions = {version};
202   if (test_version_negotiation) {
203     versions.insert(versions.begin(), QuicVersionReservedForNegotiation());
204   }
205 
206   auto proof_verifier = std::make_unique<FakeProofVerifier>();
207   auto session_cache = std::make_unique<QuicClientSessionCache>();
208   QuicConfig config;
209   QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(20);
210   config.SetIdleNetworkTimeout(timeout);
211   if (attempt_multi_packet_chlo) {
212     // Make the ClientHello span multiple packets by adding a custom transport
213     // parameter.
214     constexpr auto kCustomParameter =
215         static_cast<TransportParameters::TransportParameterId>(0x173E);
216     std::string custom_value(2000, '?');
217     config.custom_transport_parameters_to_send()[kCustomParameter] =
218         custom_value;
219   }
220   std::unique_ptr<QuicEventLoop> event_loop =
221       GetDefaultEventLoop()->Create(QuicDefaultClock::Get());
222   auto client = std::make_unique<QuicDefaultClient>(
223       addr, server_id, versions, config, event_loop.get(),
224       std::move(proof_verifier), std::move(session_cache));
225   client->set_connection_debug_visitor(this);
226   if (!client->Initialize()) {
227     QUIC_LOG(ERROR) << "Failed to initialize client";
228     return;
229   }
230   const bool connect_result = client->Connect();
231   QuicConnection* connection = client->session()->connection();
232   if (connection == nullptr) {
233     QUIC_LOG(ERROR) << "No QuicConnection object";
234     return;
235   }
236   QuicConnectionStats client_stats = connection->GetStats();
237   if (client_stats.retry_packet_processed) {
238     InsertFeature(Feature::kRetry);
239   }
240   if (test_version_negotiation && connection->version() == version) {
241     InsertFeature(Feature::kVersionNegotiation);
242   }
243   if (test_version_negotiation && !connect_result) {
244     // Failed to negotiate version, retry without version negotiation.
245     AttemptRequest(addr, authority, server_id, version,
246                    /*test_version_negotiation=*/false, attempt_rebind,
247                    attempt_multi_packet_chlo, attempt_key_update);
248     return;
249   }
250   if (!client->session()->OneRttKeysAvailable()) {
251     if (attempt_multi_packet_chlo) {
252       // Failed to handshake with multi-packet client hello, retry without it.
253       AttemptRequest(addr, authority, server_id, version,
254                      test_version_negotiation, attempt_rebind,
255                      /*attempt_multi_packet_chlo=*/false, attempt_key_update);
256       return;
257     }
258     return;
259   }
260   InsertFeature(Feature::kHandshake);
261   if (attempt_multi_packet_chlo) {
262     InsertFeature(Feature::kQuantum);
263   }
264 
265   spdy::Http2HeaderBlock header_block = ConstructHeaderBlock(authority);
266   SendRequest(client.get(), header_block);
267 
268   if (!client->connected()) {
269     return;
270   }
271 
272   if (client->latest_response_code() != -1) {
273     InsertFeature(Feature::kHttp3);
274 
275     if (client->client_session()->dynamic_table_entry_referenced()) {
276       InsertFeature(Feature::kDynamicEntryReferenced);
277     }
278 
279     if (attempt_rebind) {
280       // Now make a second request after switching to a different client port.
281       if (client->ChangeEphemeralPort()) {
282         client->SendRequestAndWaitForResponse(header_block, "", /*fin=*/true);
283         if (!client->connected()) {
284           // Rebinding does not work, retry without attempting it.
285           AttemptRequest(addr, authority, server_id, version,
286                          test_version_negotiation, /*attempt_rebind=*/false,
287                          attempt_multi_packet_chlo, attempt_key_update);
288           return;
289         }
290         InsertFeature(Feature::kRebinding);
291 
292         if (client->client_session()->dynamic_table_entry_referenced()) {
293           InsertFeature(Feature::kDynamicEntryReferenced);
294         }
295       } else {
296         QUIC_LOG(ERROR) << "Failed to change ephemeral port";
297       }
298     }
299 
300     if (attempt_key_update) {
301       if (connection->IsKeyUpdateAllowed()) {
302         if (connection->InitiateKeyUpdate(
303                 KeyUpdateReason::kLocalForInteropRunner)) {
304           client->SendRequestAndWaitForResponse(header_block, "", /*fin=*/true);
305           if (!client->connected()) {
306             // Key update does not work, retry without attempting it.
307             AttemptRequest(addr, authority, server_id, version,
308                            test_version_negotiation, attempt_rebind,
309                            attempt_multi_packet_chlo,
310                            /*attempt_key_update=*/false);
311             return;
312           }
313           InsertFeature(Feature::kKeyUpdate);
314         } else {
315           QUIC_LOG(ERROR) << "Failed to initiate key update";
316         }
317       } else {
318         QUIC_LOG(ERROR) << "Key update not allowed";
319       }
320     }
321   }
322 
323   if (connection->connected()) {
324     connection->CloseConnection(
325         QUIC_NO_ERROR, "Graceful close",
326         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
327     InsertFeature(Feature::kConnectionClose);
328   }
329 
330   AttemptResumption(client.get(), authority);
331 }
332 
ConstructHeaderBlock(const std::string & authority)333 spdy::Http2HeaderBlock QuicClientInteropRunner::ConstructHeaderBlock(
334     const std::string& authority) {
335   // Construct and send a request.
336   spdy::Http2HeaderBlock header_block;
337   header_block[":method"] = "GET";
338   header_block[":scheme"] = "https";
339   header_block[":authority"] = authority;
340   header_block[":path"] = "/";
341   return header_block;
342 }
343 
SendRequest(QuicDefaultClient * client,const spdy::Http2HeaderBlock & header_block)344 void QuicClientInteropRunner::SendRequest(
345     QuicDefaultClient* client, const spdy::Http2HeaderBlock& header_block) {
346   client->set_store_response(true);
347   client->SendRequestAndWaitForResponse(header_block, "", /*fin=*/true);
348 
349   QuicConnection* connection = client->session()->connection();
350   if (connection == nullptr) {
351     QUIC_LOG(ERROR) << "No QuicConnection object";
352     return;
353   }
354   QuicConnectionStats client_stats = connection->GetStats();
355   QuicSentPacketManager* sent_packet_manager =
356       test::QuicConnectionPeer::GetSentPacketManager(connection);
357   const bool received_forward_secure_ack =
358       sent_packet_manager != nullptr &&
359       sent_packet_manager->GetLargestAckedPacket(ENCRYPTION_FORWARD_SECURE)
360           .IsInitialized();
361   if (client_stats.stream_bytes_received > 0 && received_forward_secure_ack) {
362     InsertFeature(Feature::kStreamData);
363   }
364 }
365 
ServerSupport(std::string dns_host,std::string url_host,int port,ParsedQuicVersion version)366 std::set<Feature> ServerSupport(std::string dns_host, std::string url_host,
367                                 int port, ParsedQuicVersion version) {
368   std::cout << "Attempting interop with version " << version << std::endl;
369 
370   // Build the client, and try to connect.
371   QuicSocketAddress addr = tools::LookupAddress(dns_host, absl::StrCat(port));
372   if (!addr.IsInitialized()) {
373     QUIC_LOG(ERROR) << "Failed to resolve " << dns_host;
374     return std::set<Feature>();
375   }
376   QuicServerId server_id(url_host, port, false);
377   std::string authority = absl::StrCat(url_host, ":", port);
378 
379   QuicClientInteropRunner runner;
380 
381   runner.AttemptRequest(addr, authority, server_id, version,
382                         /*test_version_negotiation=*/true,
383                         /*attempt_rebind=*/true,
384                         /*attempt_multi_packet_chlo=*/true,
385                         /*attempt_key_update=*/true);
386 
387   return runner.features();
388 }
389 
390 }  // namespace quic
391 
main(int argc,char * argv[])392 int main(int argc, char* argv[]) {
393   quiche::QuicheSystemEventLoop event_loop("quic_client");
394   const char* usage = "Usage: quic_client_interop_test [options] [url]";
395 
396   std::vector<std::string> args =
397       quiche::QuicheParseCommandLineFlags(usage, argc, argv);
398   if (args.size() > 1) {
399     quiche::QuichePrintCommandLineFlagHelp(usage);
400     exit(1);
401   }
402   std::string dns_host = quiche::GetQuicheCommandLineFlag(FLAGS_host);
403   std::string url_host = "";
404   int port = quiche::GetQuicheCommandLineFlag(FLAGS_port);
405 
406   if (!args.empty()) {
407     quic::QuicUrl url(args[0], "https");
408     url_host = url.host();
409     if (dns_host.empty()) {
410       dns_host = url_host;
411     }
412     if (port == 0) {
413       port = url.port();
414     }
415   }
416   if (port == 0) {
417     port = 443;
418   }
419   if (dns_host.empty()) {
420     quiche::QuichePrintCommandLineFlagHelp(usage);
421     exit(1);
422   }
423   if (url_host.empty()) {
424     url_host = dns_host;
425   }
426 
427   // Pick QUIC version to use.
428   quic::ParsedQuicVersion version = quic::UnsupportedQuicVersion();
429   std::string quic_version_string =
430       quiche::GetQuicheCommandLineFlag(FLAGS_quic_version);
431   if (!quic_version_string.empty()) {
432     version = quic::ParseQuicVersionString(quic_version_string);
433   } else {
434     for (const quic::ParsedQuicVersion& vers : quic::AllSupportedVersions()) {
435       // Use the most recent IETF QUIC version.
436       if (vers.HasIetfQuicFrames() && vers.UsesHttp3() && vers.UsesTls()) {
437         version = vers;
438         break;
439       }
440     }
441   }
442   QUICHE_CHECK(version.IsKnown());
443   QuicEnableVersion(version);
444 
445   auto supported_features =
446       quic::ServerSupport(dns_host, url_host, port, version);
447   std::cout << "Results for " << url_host << ":" << port << std::endl;
448   int current_row = 1;
449   for (auto feature : supported_features) {
450     if (current_row < 2 && feature >= quic::Feature::kRebinding) {
451       std::cout << std::endl;
452       current_row = 2;
453     }
454     if (current_row < 3 && feature >= quic::Feature::kHttp3) {
455       std::cout << std::endl;
456       current_row = 3;
457     }
458     std::cout << MatrixLetter(feature);
459   }
460   std::cout << std::endl;
461 }
462