| // Copyright (c) 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <iostream> |
| #include <memory> |
| #include <string> |
| |
| #include "net/third_party/quiche/src/quic/core/quic_versions.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_system_event_loop.h" |
| #include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h" |
| #include "net/third_party/quiche/src/quic/tools/quic_client.h" |
| |
| DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, |
| host, |
| "", |
| "The IP or hostname to connect to."); |
| |
| DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to."); |
| |
| DEFINE_QUIC_COMMAND_LINE_FLAG( |
| int32_t, |
| quic_ietf_draft, |
| 0, |
| "QUIC IETF draft number to use over the wire, e.g. 18. " |
| "By default this sets quic_version to T099. " |
| "This also enables required internal QUIC flags."); |
| |
| namespace quic { |
| |
| enum class Feature { |
| // A version negotiation response is elicited and acted on. |
| kVersionNegotiation, |
| // The handshake completes successfully. |
| kHandshake, |
| // Stream data is being exchanged and ACK'ed. |
| kStreamData, |
| // The connection close procedcure completes with a zero error code. |
| kConnectionClose, |
| // An H3 transaction succeeded. |
| kHttp3, |
| // TODO(nharper): Add Retry to list of tested features. |
| }; |
| |
| char MatrixLetter(Feature f) { |
| switch (f) { |
| case Feature::kVersionNegotiation: |
| return 'V'; |
| case Feature::kHandshake: |
| return 'H'; |
| case Feature::kStreamData: |
| return 'D'; |
| case Feature::kConnectionClose: |
| return 'C'; |
| case Feature::kHttp3: |
| return '3'; |
| } |
| } |
| |
| std::set<Feature> AttemptRequest(QuicSocketAddress addr, |
| std::string authority, |
| QuicServerId server_id, |
| ParsedQuicVersionVector versions) { |
| std::set<Feature> features; |
| auto proof_verifier = QuicMakeUnique<FakeProofVerifier>(); |
| QuicEpollServer epoll_server; |
| auto client = QuicMakeUnique<QuicClient>( |
| addr, server_id, versions, &epoll_server, std::move(proof_verifier)); |
| if (!client->Initialize()) { |
| return features; |
| } |
| if (!client->Connect()) { |
| QuicErrorCode error = client->session()->error(); |
| if (error == QUIC_INVALID_VERSION) { |
| // QuicFramer::ProcessPacket returns RaiseError(QUIC_INVALID_VERSION) if |
| // it receives a packet containing a version in the header that is not our |
| // version. It might be possible that we didn't actually process a VN |
| // packet here. |
| features.insert(Feature::kVersionNegotiation); |
| return features; |
| } |
| return features; |
| } |
| if (!client->session()->IsCryptoHandshakeConfirmed()) { |
| return features; |
| } |
| features.insert(Feature::kHandshake); |
| |
| // Construct and send a request. |
| spdy::SpdyHeaderBlock header_block; |
| header_block[":method"] = "GET"; |
| header_block[":scheme"] = "https"; |
| header_block[":authority"] = authority; |
| header_block[":path"] = "/"; |
| client->set_store_response(true); |
| client->SendRequest(header_block, "", /*fin=*/true); |
| |
| // TODO(nharper): After some period of time, time out and don't report |
| // success. |
| while (client->WaitForEvents()) { |
| } |
| |
| if (!client->connected()) { |
| return features; |
| } |
| |
| if (client->latest_response_code() != -1) { |
| features.insert(Feature::kHttp3); |
| } |
| |
| // TODO(nharper): Properly check that we actually sent stream data and |
| // received ACKs for it. |
| features.insert(Feature::kStreamData); |
| // TODO(nharper): Check that we sent/received (which one?) a CONNECTION_CLOSE |
| // with error code 0. |
| features.insert(Feature::kConnectionClose); |
| return features; |
| } |
| |
| std::set<Feature> ServerSupport(std::string host, |
| int port, |
| int32_t ietf_draft) { |
| // Configure version list. |
| QuicVersionInitializeSupportForIetfDraft(ietf_draft); |
| ParsedQuicVersion version = |
| ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99); |
| ParsedQuicVersionVector versions = {version}; |
| QuicEnableVersion(version); |
| |
| // Build the client, and try to connect. |
| QuicSocketAddress addr = tools::LookupAddress(host, QuicStrCat(port)); |
| QuicServerId server_id(host, port, false); |
| std::string authority = QuicStrCat(host, ":", port); |
| |
| ParsedQuicVersionVector versions_with_negotiation = versions; |
| versions_with_negotiation.insert(versions_with_negotiation.begin(), |
| QuicVersionReservedForNegotiation()); |
| auto supported_features = |
| AttemptRequest(addr, authority, server_id, versions_with_negotiation); |
| if (!supported_features.empty()) { |
| supported_features.insert(Feature::kVersionNegotiation); |
| } else { |
| supported_features = AttemptRequest(addr, authority, server_id, versions); |
| } |
| return supported_features; |
| } |
| |
| } // namespace quic |
| |
| int main(int argc, char* argv[]) { |
| QuicSystemEventLoop event_loop("quic_client"); |
| const char* usage = "Usage: quic_client_interop_test [options]"; |
| |
| std::vector<std::string> args = |
| quic::QuicParseCommandLineFlags(usage, argc, argv); |
| if (!args.empty()) { |
| quic::QuicPrintCommandLineFlagHelp(usage); |
| exit(1); |
| } |
| std::string host = GetQuicFlag(FLAGS_host); |
| int port = GetQuicFlag(FLAGS_port); |
| const int32_t quic_ietf_draft = GetQuicFlag(FLAGS_quic_ietf_draft); |
| if (host.empty() || port == 0 || quic_ietf_draft == 0) { |
| quic::QuicPrintCommandLineFlagHelp(usage); |
| exit(1); |
| } |
| |
| auto supported_features = quic::ServerSupport(host, port, quic_ietf_draft); |
| std::cout << "Supported features: "; |
| for (auto feature : supported_features) { |
| std::cout << MatrixLetter(feature); |
| } |
| std::cout << std::endl; |
| } |