Add tool for testing quic interop When merging to chromium, the new quic_client_interop_test target should be linux only. gfe-relnote: n/a (tools only change) PiperOrigin-RevId: 259814317 Change-Id: Ib8d26e473929c17941c2764ab01354d706429879
diff --git a/quic/tools/quic_client.cc b/quic/tools/quic_client.cc index e7fe6b4..123cf1e 100644 --- a/quic/tools/quic_client.cc +++ b/quic/tools/quic_client.cc
@@ -5,10 +5,12 @@ #include "net/third_party/quiche/src/quic/tools/quic_client.h" #include <errno.h> +#include <netdb.h> #include <netinet/in.h> #include <string.h> #include <sys/epoll.h> #include <sys/socket.h> +#include <sys/types.h> #include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" #include "net/third_party/quiche/src/quic/core/http/spdy_utils.h" @@ -21,6 +23,7 @@ #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" #include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" #include "net/quic/platform/impl/quic_socket_utils.h" #include "net/third_party/quiche/src/quic/tools/quic_simple_client_session.h" @@ -30,6 +33,29 @@ namespace quic { +namespace tools { + +QuicSocketAddress LookupAddress(std::string host, std::string port) { + addrinfo hint; + memset(&hint, 0, sizeof(hint)); + hint.ai_protocol = IPPROTO_UDP; + + addrinfo* info_list = nullptr; + int result = getaddrinfo(host.c_str(), port.c_str(), &hint, &info_list); + if (result != 0) { + QUIC_LOG(ERROR) << "Failed to look up " << host << ": " + << gai_strerror(result); + return QuicSocketAddress(); + } + + CHECK(info_list != nullptr); + std::unique_ptr<addrinfo, void (*)(addrinfo*)> info_list_owned(info_list, + freeaddrinfo); + return QuicSocketAddress(info_list->ai_addr, info_list->ai_addrlen); +} + +} // namespace tools + QuicClient::QuicClient(QuicSocketAddress server_address, const QuicServerId& server_id, const ParsedQuicVersionVector& supported_versions,
diff --git a/quic/tools/quic_client.h b/quic/tools/quic_client.h index e5d2170..8e43be8 100644 --- a/quic/tools/quic_client.h +++ b/quic/tools/quic_client.h
@@ -29,6 +29,12 @@ class QuicClientPeer; } // namespace test +namespace tools { + +QuicSocketAddress LookupAddress(std::string host, std::string port); + +} // namespace tools + class QuicClient : public QuicSpdyClientBase { public: // This will create its own QuicClientEpollNetworkHelper.
diff --git a/quic/tools/quic_client_interop_test_bin.cc b/quic/tools/quic_client_interop_test_bin.cc new file mode 100644 index 0000000..d972678 --- /dev/null +++ b/quic/tools/quic_client_interop_test_bin.cc
@@ -0,0 +1,175 @@ +// 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; +}
diff --git a/quic/tools/quic_epoll_client_factory.cc b/quic/tools/quic_epoll_client_factory.cc index a74fba3..7cfb00a 100644 --- a/quic/tools/quic_epoll_client_factory.cc +++ b/quic/tools/quic_epoll_client_factory.cc
@@ -14,36 +14,14 @@ namespace quic { -namespace { - -QuicSocketAddress LookupAddress(std::string host, std::string port) { - addrinfo hint; - memset(&hint, 0, sizeof(hint)); - hint.ai_protocol = IPPROTO_UDP; - - addrinfo* info_list = nullptr; - int result = getaddrinfo(host.c_str(), port.c_str(), &hint, &info_list); - if (result != 0) { - QUIC_LOG(ERROR) << "Failed to look up " << host << ": " - << gai_strerror(result); - return QuicSocketAddress(); - } - - CHECK(info_list != nullptr); - std::unique_ptr<addrinfo, void (*)(addrinfo*)> info_list_owned(info_list, - freeaddrinfo); - return QuicSocketAddress(info_list->ai_addr, info_list->ai_addrlen); -} - -} // namespace - std::unique_ptr<QuicSpdyClientBase> QuicEpollClientFactory::CreateClient( std::string host_for_handshake, std::string host_for_lookup, uint16_t port, ParsedQuicVersionVector versions, std::unique_ptr<ProofVerifier> verifier) { - QuicSocketAddress addr = LookupAddress(host_for_lookup, QuicStrCat(port)); + QuicSocketAddress addr = + tools::LookupAddress(host_for_lookup, QuicStrCat(port)); if (!addr.IsInitialized()) { QUIC_LOG(ERROR) << "Unable to resolve address: " << host_for_lookup; return nullptr;