| // 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."); | |
| 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 = std::make_unique<FakeProofVerifier>(); | |
| QuicEpollServer epoll_server; | |
| auto client = std::make_unique<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) { | |
| // Configure version list. | |
| QuicVersionInitializeSupportForIetfDraft(); | |
| 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); | |
| if (host.empty() || port == 0) { | |
| quic::QuicPrintCommandLineFlagHelp(usage); | |
| exit(1); | |
| } | |
| auto supported_features = quic::ServerSupport(host, port); | |
| std::cout << "Supported features: "; | |
| for (auto feature : supported_features) { | |
| std::cout << MatrixLetter(feature); | |
| } | |
| std::cout << std::endl; | |
| } |