nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 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> |
bnc | 463f235 | 2019-10-10 04:49:34 -0700 | [diff] [blame] | 8 | #include <utility> |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 9 | |
dschinazi | 7c73288 | 2019-10-14 14:43:25 -0700 | [diff] [blame] | 10 | #include "net/third_party/quiche/src/quic/core/quic_types.h" |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 11 | #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" |
dschinazi | dce90b0 | 2019-10-14 18:19:54 -0700 | [diff] [blame] | 14 | #include "net/quic/platform/impl/quic_epoll_clock.h" |
dschinazi | 7c73288 | 2019-10-14 14:43:25 -0700 | [diff] [blame] | 15 | #include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h" |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 16 | #include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h" |
| 17 | #include "net/third_party/quiche/src/quic/tools/quic_client.h" |
dschinazi | 97abe47 | 2019-10-14 11:29:08 -0700 | [diff] [blame] | 18 | #include "net/third_party/quiche/src/quic/tools/quic_url.h" |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 19 | |
| 20 | DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, |
| 21 | host, |
| 22 | "", |
| 23 | "The IP or hostname to connect to."); |
| 24 | |
| 25 | DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to."); |
| 26 | |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 27 | namespace quic { |
| 28 | |
| 29 | enum class Feature { |
nharper | 7204b81 | 2019-10-15 15:29:03 -0700 | [diff] [blame] | 30 | // First row of features ("table stakes") |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 31 | // 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, |
dschinazi | 8a030dd | 2019-10-14 13:17:18 -0700 | [diff] [blame] | 39 | // A RETRY packet was successfully processed. |
| 40 | kRetry, |
nharper | 7204b81 | 2019-10-15 15:29:03 -0700 | [diff] [blame] | 41 | |
| 42 | // Second row of features (anything else protocol-related) |
dschinazi | 9b187de | 2019-10-15 10:05:31 -0700 | [diff] [blame] | 43 | // We switched to a different port and the server migrated to it. |
| 44 | kRebinding, |
nharper | 7204b81 | 2019-10-15 15:29:03 -0700 | [diff] [blame] | 45 | |
| 46 | // Third row of features (H3 tests) |
| 47 | // An H3 transaction succeeded. |
| 48 | kHttp3, |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 49 | }; |
| 50 | |
| 51 | char 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'; |
dschinazi | 8a030dd | 2019-10-14 13:17:18 -0700 | [diff] [blame] | 63 | case Feature::kRetry: |
| 64 | return 'S'; |
dschinazi | 9b187de | 2019-10-15 10:05:31 -0700 | [diff] [blame] | 65 | case Feature::kRebinding: |
| 66 | return 'B'; |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 67 | } |
| 68 | } |
| 69 | |
| 70 | std::set<Feature> AttemptRequest(QuicSocketAddress addr, |
| 71 | std::string authority, |
| 72 | QuicServerId server_id, |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 73 | bool test_version_negotiation, |
dschinazi | 9b187de | 2019-10-15 10:05:31 -0700 | [diff] [blame] | 74 | bool attempt_rebind) { |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 75 | 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 | |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 81 | std::set<Feature> features; |
vasilvv | 0fc587f | 2019-09-06 13:33:08 -0700 | [diff] [blame] | 82 | auto proof_verifier = std::make_unique<FakeProofVerifier>(); |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 83 | QuicEpollServer epoll_server; |
dschinazi | dce90b0 | 2019-10-14 18:19:54 -0700 | [diff] [blame] | 84 | QuicEpollClock epoll_clock(&epoll_server); |
vasilvv | 0fc587f | 2019-09-06 13:33:08 -0700 | [diff] [blame] | 85 | auto client = std::make_unique<QuicClient>( |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 86 | addr, server_id, versions, &epoll_server, std::move(proof_verifier)); |
| 87 | if (!client->Initialize()) { |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 88 | QUIC_LOG(ERROR) << "Failed to initialize client"; |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 89 | return features; |
| 90 | } |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 91 | 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); |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 97 | } |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 98 | 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()); |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 110 | 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 | |
dschinazi | dce90b0 | 2019-10-14 18:19:54 -0700 | [diff] [blame] | 126 | const QuicTime request_start_time = epoll_clock.Now(); |
| 127 | static const auto request_timeout = QuicTime::Delta::FromSeconds(20); |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 128 | bool request_timed_out = false; |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 129 | while (client->WaitForEvents()) { |
dschinazi | dce90b0 | 2019-10-14 18:19:54 -0700 | [diff] [blame] | 130 | if (epoll_clock.Now() - request_start_time >= request_timeout) { |
| 131 | QUIC_LOG(ERROR) << "Timed out waiting for HTTP response"; |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 132 | request_timed_out = true; |
| 133 | break; |
dschinazi | dce90b0 | 2019-10-14 18:19:54 -0700 | [diff] [blame] | 134 | } |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 135 | } |
| 136 | |
dschinazi | 7c73288 | 2019-10-14 14:43:25 -0700 | [diff] [blame] | 137 | if (connection != nullptr) { |
| 138 | QuicConnectionStats client_stats = connection->GetStats(); |
dschinazi | 7c73288 | 2019-10-14 14:43:25 -0700 | [diff] [blame] | 139 | 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 | } |
dschinazi | 8a030dd | 2019-10-14 13:17:18 -0700 | [diff] [blame] | 148 | } |
| 149 | |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 150 | if (request_timed_out || !client->connected()) { |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 151 | return features; |
| 152 | } |
| 153 | |
| 154 | if (client->latest_response_code() != -1) { |
| 155 | features.insert(Feature::kHttp3); |
dschinazi | 9b187de | 2019-10-15 10:05:31 -0700 | [diff] [blame] | 156 | |
| 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. |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 167 | 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; |
dschinazi | 9b187de | 2019-10-15 10:05:31 -0700 | [diff] [blame] | 173 | } |
| 174 | } |
| 175 | features.insert(Feature::kRebinding); |
| 176 | } else { |
| 177 | QUIC_LOG(ERROR) << "Failed to change ephemeral port"; |
| 178 | } |
| 179 | } |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 180 | } |
| 181 | |
dschinazi | dce90b0 | 2019-10-14 18:19:54 -0700 | [diff] [blame] | 182 | 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 | |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 204 | return features; |
| 205 | } |
| 206 | |
rch | a702be2 | 2019-08-30 15:20:12 -0700 | [diff] [blame] | 207 | std::set<Feature> ServerSupport(std::string host, int port) { |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 208 | // Enable IETF version support. |
rch | a702be2 | 2019-08-30 15:20:12 -0700 | [diff] [blame] | 209 | QuicVersionInitializeSupportForIetfDraft(); |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 210 | QuicEnableVersion(ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99)); |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 211 | |
| 212 | // Build the client, and try to connect. |
| 213 | QuicSocketAddress addr = tools::LookupAddress(host, QuicStrCat(port)); |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 214 | if (!addr.IsInitialized()) { |
| 215 | QUIC_LOG(ERROR) << "Failed to resolve " << host; |
| 216 | return std::set<Feature>(); |
| 217 | } |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 218 | QuicServerId server_id(host, port, false); |
| 219 | std::string authority = QuicStrCat(host, ":", port); |
| 220 | |
dschinazi | fdb673f | 2019-10-18 11:19:53 -0700 | [diff] [blame] | 221 | return AttemptRequest(addr, authority, server_id, |
| 222 | /*test_version_negotiation=*/true, |
| 223 | /*attempt_rebind=*/true); |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 224 | } |
| 225 | |
| 226 | } // namespace quic |
| 227 | |
| 228 | int main(int argc, char* argv[]) { |
| 229 | QuicSystemEventLoop event_loop("quic_client"); |
dschinazi | 97abe47 | 2019-10-14 11:29:08 -0700 | [diff] [blame] | 230 | const char* usage = "Usage: quic_client_interop_test [options] [url]"; |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 231 | |
| 232 | std::vector<std::string> args = |
| 233 | quic::QuicParseCommandLineFlags(usage, argc, argv); |
dschinazi | 97abe47 | 2019-10-14 11:29:08 -0700 | [diff] [blame] | 234 | if (args.size() > 1) { |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 235 | quic::QuicPrintCommandLineFlagHelp(usage); |
| 236 | exit(1); |
| 237 | } |
| 238 | std::string host = GetQuicFlag(FLAGS_host); |
| 239 | int port = GetQuicFlag(FLAGS_port); |
dschinazi | 97abe47 | 2019-10-14 11:29:08 -0700 | [diff] [blame] | 240 | |
| 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()) { |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 254 | quic::QuicPrintCommandLineFlagHelp(usage); |
| 255 | exit(1); |
| 256 | } |
| 257 | |
rch | a702be2 | 2019-08-30 15:20:12 -0700 | [diff] [blame] | 258 | auto supported_features = quic::ServerSupport(host, port); |
nharper | 7204b81 | 2019-10-15 15:29:03 -0700 | [diff] [blame] | 259 | std::cout << "Results for " << host << ":" << port << std::endl; |
| 260 | int current_row = 1; |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 261 | for (auto feature : supported_features) { |
nharper | 7204b81 | 2019-10-15 15:29:03 -0700 | [diff] [blame] | 262 | 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 | } |
nharper | 2eb3f38 | 2019-07-24 14:12:31 -0700 | [diff] [blame] | 270 | std::cout << MatrixLetter(feature); |
| 271 | } |
| 272 | std::cout << std::endl; |
| 273 | } |