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