blob: be659795b6b3917846e2b31bd2232c7e57b22348 [file] [log] [blame]
// 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 <utility>
#include "net/third_party/quiche/src/quic/core/quic_types.h"
#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/quic/platform/impl/quic_epoll_clock.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
#include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h"
#include "net/third_party/quiche/src/quic/tools/quic_client.h"
#include "net/third_party/quiche/src/quic/tools/quic_url.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 {
// First row of features ("table stakes")
// 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,
// A RETRY packet was successfully processed.
kRetry,
// Second row of features (anything else protocol-related)
// We switched to a different port and the server migrated to it.
kRebinding,
// Third row of features (H3 tests)
// An H3 transaction succeeded.
kHttp3,
};
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';
case Feature::kRetry:
return 'S';
case Feature::kRebinding:
return 'B';
}
}
std::set<Feature> AttemptRequest(QuicSocketAddress addr,
std::string authority,
QuicServerId server_id,
bool test_version_negotiation,
bool attempt_rebind) {
ParsedQuicVersion version(PROTOCOL_TLS1_3, QUIC_VERSION_99);
ParsedQuicVersionVector versions = {version};
if (test_version_negotiation) {
versions.insert(versions.begin(), QuicVersionReservedForNegotiation());
}
std::set<Feature> features;
auto proof_verifier = std::make_unique<FakeProofVerifier>();
QuicEpollServer epoll_server;
QuicEpollClock epoll_clock(&epoll_server);
auto client = std::make_unique<QuicClient>(
addr, server_id, versions, &epoll_server, std::move(proof_verifier));
if (!client->Initialize()) {
QUIC_LOG(ERROR) << "Failed to initialize client";
return features;
}
const bool connect_result = client->Connect();
QuicConnection* connection = client->session()->connection();
if (connection != nullptr) {
QuicConnectionStats client_stats = connection->GetStats();
if (client_stats.retry_packet_processed) {
features.insert(Feature::kRetry);
}
if (test_version_negotiation && connection->version() == version) {
features.insert(Feature::kVersionNegotiation);
}
}
if (test_version_negotiation && !connect_result) {
// Failed to negotiate version, retry without version negotiation.
std::set<Feature> features_without_version_negotiation =
AttemptRequest(addr, authority, server_id,
/*test_version_negotiation=*/false, attempt_rebind);
features.insert(features_without_version_negotiation.begin(),
features_without_version_negotiation.end());
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);
const QuicTime request_start_time = epoll_clock.Now();
static const auto request_timeout = QuicTime::Delta::FromSeconds(20);
bool request_timed_out = false;
while (client->WaitForEvents()) {
if (epoll_clock.Now() - request_start_time >= request_timeout) {
QUIC_LOG(ERROR) << "Timed out waiting for HTTP response";
request_timed_out = true;
break;
}
}
if (connection != nullptr) {
QuicConnectionStats client_stats = connection->GetStats();
QuicSentPacketManager* sent_packet_manager =
test::QuicConnectionPeer::GetSentPacketManager(connection);
const bool received_forward_secure_ack =
sent_packet_manager != nullptr &&
sent_packet_manager->GetLargestAckedPacket(ENCRYPTION_FORWARD_SECURE)
.IsInitialized();
if (client_stats.stream_bytes_received > 0 && received_forward_secure_ack) {
features.insert(Feature::kStreamData);
}
}
if (request_timed_out || !client->connected()) {
return features;
}
if (client->latest_response_code() != -1) {
features.insert(Feature::kHttp3);
if (attempt_rebind) {
// Now make a second request after switching to a different client port.
if (client->ChangeEphemeralPort()) {
client->SendRequest(header_block, "", /*fin=*/true);
const QuicTime second_request_start_time = epoll_clock.Now();
while (client->WaitForEvents()) {
if (epoll_clock.Now() - second_request_start_time >=
request_timeout) {
// Rebinding does not work, retry without attempting it.
std::set<Feature> features_without_rebind = AttemptRequest(
addr, authority, server_id, test_version_negotiation,
/*attempt_rebind=*/false);
features.insert(features_without_rebind.begin(),
features_without_rebind.end());
return features;
}
}
features.insert(Feature::kRebinding);
} else {
QUIC_LOG(ERROR) << "Failed to change ephemeral port";
}
}
}
if (connection != nullptr && connection->connected()) {
test::QuicConnectionPeer::SendConnectionClosePacket(
connection, QUIC_NO_ERROR, "Graceful close");
const QuicTime close_start_time = epoll_clock.Now();
static const auto close_timeout = QuicTime::Delta::FromSeconds(10);
while (client->connected()) {
client->epoll_network_helper()->RunEventLoop();
if (epoll_clock.Now() - close_start_time >= close_timeout) {
QUIC_LOG(ERROR) << "Timed out waiting for connection close";
return features;
}
}
const QuicErrorCode received_error = client->session()->error();
if (received_error == QUIC_NO_ERROR ||
received_error == QUIC_PUBLIC_RESET) {
features.insert(Feature::kConnectionClose);
} else {
QUIC_LOG(ERROR) << "Received error " << client->session()->error() << " "
<< client->session()->error_details();
}
}
return features;
}
std::set<Feature> ServerSupport(std::string host, int port) {
// Enable IETF version support.
QuicVersionInitializeSupportForIetfDraft();
QuicEnableVersion(ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99));
// Build the client, and try to connect.
QuicSocketAddress addr = tools::LookupAddress(host, QuicStrCat(port));
if (!addr.IsInitialized()) {
QUIC_LOG(ERROR) << "Failed to resolve " << host;
return std::set<Feature>();
}
QuicServerId server_id(host, port, false);
std::string authority = QuicStrCat(host, ":", port);
return AttemptRequest(addr, authority, server_id,
/*test_version_negotiation=*/true,
/*attempt_rebind=*/true);
}
} // namespace quic
int main(int argc, char* argv[]) {
QuicSystemEventLoop event_loop("quic_client");
const char* usage = "Usage: quic_client_interop_test [options] [url]";
std::vector<std::string> args =
quic::QuicParseCommandLineFlags(usage, argc, argv);
if (args.size() > 1) {
quic::QuicPrintCommandLineFlagHelp(usage);
exit(1);
}
std::string host = GetQuicFlag(FLAGS_host);
int port = GetQuicFlag(FLAGS_port);
if (!args.empty()) {
quic::QuicUrl url(args[0], "https");
if (host.empty()) {
host = url.host();
}
if (port == 0) {
port = url.port();
}
}
if (port == 0) {
port = 443;
}
if (host.empty()) {
quic::QuicPrintCommandLineFlagHelp(usage);
exit(1);
}
auto supported_features = quic::ServerSupport(host, port);
std::cout << "Results for " << host << ":" << port << std::endl;
int current_row = 1;
for (auto feature : supported_features) {
if (current_row < 2 && feature >= quic::Feature::kRebinding) {
std::cout << std::endl;
current_row = 2;
}
if (current_row < 3 && feature >= quic::Feature::kHttp3) {
std::cout << std::endl;
current_row = 3;
}
std::cout << MatrixLetter(feature);
}
std::cout << std::endl;
}