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;