blob: be659795b6b3917846e2b31bd2232c7e57b22348 [file] [log] [blame]
nharper2eb3f382019-07-24 14:12:31 -07001// 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>
bnc463f2352019-10-10 04:49:34 -07008#include <utility>
nharper2eb3f382019-07-24 14:12:31 -07009
dschinazi7c732882019-10-14 14:43:25 -070010#include "net/third_party/quiche/src/quic/core/quic_types.h"
nharper2eb3f382019-07-24 14:12:31 -070011#include "net/third_party/quiche/src/quic/core/quic_versions.h"
12#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h"
13#include "net/third_party/quiche/src/quic/platform/api/quic_system_event_loop.h"
dschinazidce90b02019-10-14 18:19:54 -070014#include "net/quic/platform/impl/quic_epoll_clock.h"
dschinazi7c732882019-10-14 14:43:25 -070015#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
nharper2eb3f382019-07-24 14:12:31 -070016#include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h"
17#include "net/third_party/quiche/src/quic/tools/quic_client.h"
dschinazi97abe472019-10-14 11:29:08 -070018#include "net/third_party/quiche/src/quic/tools/quic_url.h"
nharper2eb3f382019-07-24 14:12:31 -070019
20DEFINE_QUIC_COMMAND_LINE_FLAG(std::string,
21 host,
22 "",
23 "The IP or hostname to connect to.");
24
25DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
26
nharper2eb3f382019-07-24 14:12:31 -070027namespace quic {
28
29enum class Feature {
nharper7204b812019-10-15 15:29:03 -070030 // First row of features ("table stakes")
nharper2eb3f382019-07-24 14:12:31 -070031 // A version negotiation response is elicited and acted on.
32 kVersionNegotiation,
33 // The handshake completes successfully.
34 kHandshake,
35 // Stream data is being exchanged and ACK'ed.
36 kStreamData,
37 // The connection close procedcure completes with a zero error code.
38 kConnectionClose,
dschinazi8a030dd2019-10-14 13:17:18 -070039 // A RETRY packet was successfully processed.
40 kRetry,
nharper7204b812019-10-15 15:29:03 -070041
42 // Second row of features (anything else protocol-related)
dschinazi9b187de2019-10-15 10:05:31 -070043 // We switched to a different port and the server migrated to it.
44 kRebinding,
nharper7204b812019-10-15 15:29:03 -070045
46 // Third row of features (H3 tests)
47 // An H3 transaction succeeded.
48 kHttp3,
nharper2eb3f382019-07-24 14:12:31 -070049};
50
51char MatrixLetter(Feature f) {
52 switch (f) {
53 case Feature::kVersionNegotiation:
54 return 'V';
55 case Feature::kHandshake:
56 return 'H';
57 case Feature::kStreamData:
58 return 'D';
59 case Feature::kConnectionClose:
60 return 'C';
61 case Feature::kHttp3:
62 return '3';
dschinazi8a030dd2019-10-14 13:17:18 -070063 case Feature::kRetry:
64 return 'S';
dschinazi9b187de2019-10-15 10:05:31 -070065 case Feature::kRebinding:
66 return 'B';
nharper2eb3f382019-07-24 14:12:31 -070067 }
68}
69
70std::set<Feature> AttemptRequest(QuicSocketAddress addr,
71 std::string authority,
72 QuicServerId server_id,
dschinazifdb673f2019-10-18 11:19:53 -070073 bool test_version_negotiation,
dschinazi9b187de2019-10-15 10:05:31 -070074 bool attempt_rebind) {
dschinazifdb673f2019-10-18 11:19:53 -070075 ParsedQuicVersion version(PROTOCOL_TLS1_3, QUIC_VERSION_99);
76 ParsedQuicVersionVector versions = {version};
77 if (test_version_negotiation) {
78 versions.insert(versions.begin(), QuicVersionReservedForNegotiation());
79 }
80
nharper2eb3f382019-07-24 14:12:31 -070081 std::set<Feature> features;
vasilvv0fc587f2019-09-06 13:33:08 -070082 auto proof_verifier = std::make_unique<FakeProofVerifier>();
nharper2eb3f382019-07-24 14:12:31 -070083 QuicEpollServer epoll_server;
dschinazidce90b02019-10-14 18:19:54 -070084 QuicEpollClock epoll_clock(&epoll_server);
vasilvv0fc587f2019-09-06 13:33:08 -070085 auto client = std::make_unique<QuicClient>(
nharper2eb3f382019-07-24 14:12:31 -070086 addr, server_id, versions, &epoll_server, std::move(proof_verifier));
87 if (!client->Initialize()) {
dschinazifdb673f2019-10-18 11:19:53 -070088 QUIC_LOG(ERROR) << "Failed to initialize client";
nharper2eb3f382019-07-24 14:12:31 -070089 return features;
90 }
dschinazifdb673f2019-10-18 11:19:53 -070091 const bool connect_result = client->Connect();
92 QuicConnection* connection = client->session()->connection();
93 if (connection != nullptr) {
94 QuicConnectionStats client_stats = connection->GetStats();
95 if (client_stats.retry_packet_processed) {
96 features.insert(Feature::kRetry);
nharper2eb3f382019-07-24 14:12:31 -070097 }
dschinazifdb673f2019-10-18 11:19:53 -070098 if (test_version_negotiation && connection->version() == version) {
99 features.insert(Feature::kVersionNegotiation);
100 }
101 }
102 if (test_version_negotiation && !connect_result) {
103 // Failed to negotiate version, retry without version negotiation.
104 std::set<Feature> features_without_version_negotiation =
105 AttemptRequest(addr, authority, server_id,
106 /*test_version_negotiation=*/false, attempt_rebind);
107
108 features.insert(features_without_version_negotiation.begin(),
109 features_without_version_negotiation.end());
nharper2eb3f382019-07-24 14:12:31 -0700110 return features;
111 }
112 if (!client->session()->IsCryptoHandshakeConfirmed()) {
113 return features;
114 }
115 features.insert(Feature::kHandshake);
116
117 // Construct and send a request.
118 spdy::SpdyHeaderBlock header_block;
119 header_block[":method"] = "GET";
120 header_block[":scheme"] = "https";
121 header_block[":authority"] = authority;
122 header_block[":path"] = "/";
123 client->set_store_response(true);
124 client->SendRequest(header_block, "", /*fin=*/true);
125
dschinazidce90b02019-10-14 18:19:54 -0700126 const QuicTime request_start_time = epoll_clock.Now();
127 static const auto request_timeout = QuicTime::Delta::FromSeconds(20);
dschinazifdb673f2019-10-18 11:19:53 -0700128 bool request_timed_out = false;
nharper2eb3f382019-07-24 14:12:31 -0700129 while (client->WaitForEvents()) {
dschinazidce90b02019-10-14 18:19:54 -0700130 if (epoll_clock.Now() - request_start_time >= request_timeout) {
131 QUIC_LOG(ERROR) << "Timed out waiting for HTTP response";
dschinazifdb673f2019-10-18 11:19:53 -0700132 request_timed_out = true;
133 break;
dschinazidce90b02019-10-14 18:19:54 -0700134 }
nharper2eb3f382019-07-24 14:12:31 -0700135 }
136
dschinazi7c732882019-10-14 14:43:25 -0700137 if (connection != nullptr) {
138 QuicConnectionStats client_stats = connection->GetStats();
dschinazi7c732882019-10-14 14:43:25 -0700139 QuicSentPacketManager* sent_packet_manager =
140 test::QuicConnectionPeer::GetSentPacketManager(connection);
141 const bool received_forward_secure_ack =
142 sent_packet_manager != nullptr &&
143 sent_packet_manager->GetLargestAckedPacket(ENCRYPTION_FORWARD_SECURE)
144 .IsInitialized();
145 if (client_stats.stream_bytes_received > 0 && received_forward_secure_ack) {
146 features.insert(Feature::kStreamData);
147 }
dschinazi8a030dd2019-10-14 13:17:18 -0700148 }
149
dschinazifdb673f2019-10-18 11:19:53 -0700150 if (request_timed_out || !client->connected()) {
nharper2eb3f382019-07-24 14:12:31 -0700151 return features;
152 }
153
154 if (client->latest_response_code() != -1) {
155 features.insert(Feature::kHttp3);
dschinazi9b187de2019-10-15 10:05:31 -0700156
157 if (attempt_rebind) {
158 // Now make a second request after switching to a different client port.
159 if (client->ChangeEphemeralPort()) {
160 client->SendRequest(header_block, "", /*fin=*/true);
161
162 const QuicTime second_request_start_time = epoll_clock.Now();
163 while (client->WaitForEvents()) {
164 if (epoll_clock.Now() - second_request_start_time >=
165 request_timeout) {
166 // Rebinding does not work, retry without attempting it.
dschinazifdb673f2019-10-18 11:19:53 -0700167 std::set<Feature> features_without_rebind = AttemptRequest(
168 addr, authority, server_id, test_version_negotiation,
169 /*attempt_rebind=*/false);
170 features.insert(features_without_rebind.begin(),
171 features_without_rebind.end());
172 return features;
dschinazi9b187de2019-10-15 10:05:31 -0700173 }
174 }
175 features.insert(Feature::kRebinding);
176 } else {
177 QUIC_LOG(ERROR) << "Failed to change ephemeral port";
178 }
179 }
nharper2eb3f382019-07-24 14:12:31 -0700180 }
181
dschinazidce90b02019-10-14 18:19:54 -0700182 if (connection != nullptr && connection->connected()) {
183 test::QuicConnectionPeer::SendConnectionClosePacket(
184 connection, QUIC_NO_ERROR, "Graceful close");
185 const QuicTime close_start_time = epoll_clock.Now();
186 static const auto close_timeout = QuicTime::Delta::FromSeconds(10);
187 while (client->connected()) {
188 client->epoll_network_helper()->RunEventLoop();
189 if (epoll_clock.Now() - close_start_time >= close_timeout) {
190 QUIC_LOG(ERROR) << "Timed out waiting for connection close";
191 return features;
192 }
193 }
194 const QuicErrorCode received_error = client->session()->error();
195 if (received_error == QUIC_NO_ERROR ||
196 received_error == QUIC_PUBLIC_RESET) {
197 features.insert(Feature::kConnectionClose);
198 } else {
199 QUIC_LOG(ERROR) << "Received error " << client->session()->error() << " "
200 << client->session()->error_details();
201 }
202 }
203
nharper2eb3f382019-07-24 14:12:31 -0700204 return features;
205}
206
rcha702be22019-08-30 15:20:12 -0700207std::set<Feature> ServerSupport(std::string host, int port) {
dschinazifdb673f2019-10-18 11:19:53 -0700208 // Enable IETF version support.
rcha702be22019-08-30 15:20:12 -0700209 QuicVersionInitializeSupportForIetfDraft();
dschinazifdb673f2019-10-18 11:19:53 -0700210 QuicEnableVersion(ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99));
nharper2eb3f382019-07-24 14:12:31 -0700211
212 // Build the client, and try to connect.
213 QuicSocketAddress addr = tools::LookupAddress(host, QuicStrCat(port));
dschinazifdb673f2019-10-18 11:19:53 -0700214 if (!addr.IsInitialized()) {
215 QUIC_LOG(ERROR) << "Failed to resolve " << host;
216 return std::set<Feature>();
217 }
nharper2eb3f382019-07-24 14:12:31 -0700218 QuicServerId server_id(host, port, false);
219 std::string authority = QuicStrCat(host, ":", port);
220
dschinazifdb673f2019-10-18 11:19:53 -0700221 return AttemptRequest(addr, authority, server_id,
222 /*test_version_negotiation=*/true,
223 /*attempt_rebind=*/true);
nharper2eb3f382019-07-24 14:12:31 -0700224}
225
226} // namespace quic
227
228int main(int argc, char* argv[]) {
229 QuicSystemEventLoop event_loop("quic_client");
dschinazi97abe472019-10-14 11:29:08 -0700230 const char* usage = "Usage: quic_client_interop_test [options] [url]";
nharper2eb3f382019-07-24 14:12:31 -0700231
232 std::vector<std::string> args =
233 quic::QuicParseCommandLineFlags(usage, argc, argv);
dschinazi97abe472019-10-14 11:29:08 -0700234 if (args.size() > 1) {
nharper2eb3f382019-07-24 14:12:31 -0700235 quic::QuicPrintCommandLineFlagHelp(usage);
236 exit(1);
237 }
238 std::string host = GetQuicFlag(FLAGS_host);
239 int port = GetQuicFlag(FLAGS_port);
dschinazi97abe472019-10-14 11:29:08 -0700240
241 if (!args.empty()) {
242 quic::QuicUrl url(args[0], "https");
243 if (host.empty()) {
244 host = url.host();
245 }
246 if (port == 0) {
247 port = url.port();
248 }
249 }
250 if (port == 0) {
251 port = 443;
252 }
253 if (host.empty()) {
nharper2eb3f382019-07-24 14:12:31 -0700254 quic::QuicPrintCommandLineFlagHelp(usage);
255 exit(1);
256 }
257
rcha702be22019-08-30 15:20:12 -0700258 auto supported_features = quic::ServerSupport(host, port);
nharper7204b812019-10-15 15:29:03 -0700259 std::cout << "Results for " << host << ":" << port << std::endl;
260 int current_row = 1;
nharper2eb3f382019-07-24 14:12:31 -0700261 for (auto feature : supported_features) {
nharper7204b812019-10-15 15:29:03 -0700262 if (current_row < 2 && feature >= quic::Feature::kRebinding) {
263 std::cout << std::endl;
264 current_row = 2;
265 }
266 if (current_row < 3 && feature >= quic::Feature::kHttp3) {
267 std::cout << std::endl;
268 current_row = 3;
269 }
nharper2eb3f382019-07-24 14:12:31 -0700270 std::cout << MatrixLetter(feature);
271 }
272 std::cout << std::endl;
273}