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;